再帰型ニューラルネットワーク その2


AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day88


経緯についてはこちらをご参照ください。



■本日の進捗

  • RNN時系列対応を理解


■はじめに

今回も「ゼロから作るDeep Learning② 自然言語処理編(オライリー・ジャパン)」から学んでいきます。

今回は、再帰型ニューラルネットワークからRNN層以外にも時系列データに対応するためのその他の層を学んでいきます。

■時系列Embeddingクラス

時系列データを埋め込みベクトルに変換するためのTimeEmbedding層を実装していきます。

まずは引数として、語彙数(V)と埋め込み次元数(D)の形状を持つ埋め込み行列(W)を受け取ります。

self.paramsは埋め込み行列を格納するリストとして、self.gradsは埋め込み行列Wの勾配を格納するリストとして初期化します。

class TimeEmbedding:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.layers = None
        self.W = W

順伝播の場合は、入力データである時系列データに対するインデックス(xs)を埋め込みベクトルに変換して、時刻(t)ごとに埋め込みを行った結果(out)を返します。この時、実装済みのEmbedding層の順伝播を用います。

    def forward(self, xs):
        N, T = xs.shape
        V, D = self.W.shape

        out = np.empty((N, T, D), dtype='f')
        self.layers = []

        for t in range(T):
            layer = Embedding(self.W)
            out[:, t, :] = layer.forward(xs[:, t])
            self.layers.append(layer)

        return out

逆伝播の場合は、出力側から流れてくる勾配(dout)を埋め込み行列(W)の勾配を求めます。ここでも、先ほどインスタンス化したEmbedding層を用いて逆伝播を計算します。

    def backward(self, dout):
        N, T, D = dout.shape

        grad = 0
        for t in range(T):
            layer = self.layers[t]
            layer.backward(dout[:, t, :])
            grad += layer.grads[0]

        self.grads[0][...] = grad
        return None



■時系列全結合層クラス

全結合層に関しても、Embedding層と同様に時系列データに対応したTimeAffineクラスを実装していきます。

まずは重み行列(W)とバイアス(b)を受け取り、これをリストに保存したら、self.gradsに勾配を格納するために重み行列とバイアスと同じ形状で0に初期化します。

class TimeAffine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None

順伝播の場合は、バッチサイズ(N)、タイムステップ数(T)、特徴量の次元数(D)の形状を持つ入力(x)を引数として受け取り、入力(x)を(N×T, D)の形にリシェイプしてから全結合の計算を行います。最後に(N, T, -1)の形にリシェイプして元の形状にして返します。

    def forward(self, x):
        N, T, D = x.shape
        W, b = self.params

        rx = x.reshape(N*T, -1)
        out = np.dot(rx, W) + b
        self.x = x
        return out.reshape(N, T, -1)

逆伝播の場合は、出力に対する勾配(dout)を受け取り、順伝播と同様にリシェイプしてからそれぞれの勾配を計算します。この逆伝播の式は導出済みなのととても簡単なので割愛します。ここでも最後に元の形状(N, T, D)に戻して返り値にしたら完了です。

    def backward(self, dout):
        x = self.x
        N, T, D = x.shape
        W, b = self.params

        dout = dout.reshape(N*T, -1)
        rx = x.reshape(N*T, -1)

        db = np.sum(dout, axis=0)
        dW = np.dot(rx.T, dout)
        dx = np.dot(dout, W.T)
        dx = dx.reshape(*x.shape)

        self.grads[0][...] = dW
        self.grads[1][...] = db

        return dx



■時系列Softmaxクラス

最後に時系列データに対応したSoftmax層を実装していきます。

self.params, self.gradsは今は使わないのですが、保存用のcacheと併せて初期化します。ignore_labelは損失や勾配の計算で除外するラベルです。

class TimeSoftmaxWithLoss:
    def __init__(self):
        self.params, self.grads = [], []
        self.cache = None
        self.ignore_label = -1

順伝播の場合は、実装済みのsoftmax関数を用いて損失を計算します。バッチサイズ(N)、時系列長さ(T)、クラス数(V)の形状を持つ予測値の行列(xs)と、教師データの行列(ts)を入力として受け取ります。

ts.ndimが3の場合、教師データがone-hot化されているので、argmaxでクラスインデックスに変換します。ignore_labelを用いて無視するデータをmaskに保存して、xsの形状を(N×T, V)に変換し、tsの形状を(N×T)で1次元に変換します。

softmax関数で確率分布を求めたら損失を計算し、この結果を返します。

    def forward(self, xs, ts):
        N, T, V = xs.shape

        if ts.ndim == 3:
            ts = ts.argmax(axis=2)

        mask = (ts != self.ignore_label)

        xs = xs.reshape(N * T, V)
        ts = ts.reshape(N * T)
        mask = mask.reshape(N * T)

        ys = softmax(xs)
        ls = np.log(ys[np.arange(N * T), ts])
        ls *= mask
        loss = -np.sum(ls)
        loss /= mask.sum()

        self.cache = (ts, ys, mask, (N, T, V))
        return loss

逆伝播の場合は、出力側からの勾配(dout)を受け取り勾配を計算します。self.cacheからキャッシュデータを取得したら、正解クラスに対して1を引くことで勾配を計算します。(softmaxの出力は確率なので)

勾配は損失の平均で正規化して、無視するラベルの勾配を0にしておきます。最後に(N×T, V)の勾配を(N, T, V)に戻したら完了です。

    def backward(self, dout=1):
        ts, ys, mask, (N, T, V) = self.cache

        dx = ys
        dx[np.arange(N * T), ts] -= 1
        dx *= dout
        dx /= mask.sum()
        dx *= mask[:, np.newaxis]  # ignore_labelに該当するデータは勾配を0にする

        dx = dx.reshape((N, T, V))

        return dx



■おわりに

今回は再帰型ニューラルネットワークに用いる時系列データに対応させた層を実装しました。これらを用いて再帰型ニューラルネットワークを構築することができます。

■参考文献

  1. Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
  2. 斎藤 康毅. ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装. オライリー・ジャパン. 2016. 320p.
  3. 斎藤 康毅. ゼロから作るDeep Learning② 自然言語処理編. オライリー・ジャパン. 2018. 432p.
  4. ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
  5. API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html
  6. PyTorch documentation. pytorch.org. https://pytorch.org/docs/stable/index.html
  7. Keiron O’Shea, Ryan Nash. An Introduction to Convolutional Neural Networks. https://ar5iv.labs.arxiv.org/html/1511.08458
  8. API Reference. scipy.org. 2024. https://docs.scipy.org/doc/scipy/reference/index.html


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です