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
■おわりに
今回は再帰型ニューラルネットワークに用いる時系列データに対応させた層を実装しました。これらを用いて再帰型ニューラルネットワークを構築することができます。
■参考文献
- Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
- 斎藤 康毅. ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装. オライリー・ジャパン. 2016. 320p.
- 斎藤 康毅. ゼロから作るDeep Learning② 自然言語処理編. オライリー・ジャパン. 2018. 432p.
- ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
- API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html
- PyTorch documentation. pytorch.org. https://pytorch.org/docs/stable/index.html
- Keiron O’Shea, Ryan Nash. An Introduction to Convolutional Neural Networks. https://ar5iv.labs.arxiv.org/html/1511.08458
- API Reference. scipy.org. 2024. https://docs.scipy.org/doc/scipy/reference/index.html