RNNのモデルを調べて分かりやすくまとめてみた。(ChatGPTから聞いたRNNのコードについて調べてみた)

RNN

はじめに

前回はRNNの概要について調べました。

今回からChatGPTに聞いたコードを勉強していき、RNNを実装できるようにしていきたいと思います。

RNNのコードについて勉強してみた

コード

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# RNNセルの定義
class SimpleRNNCell:
    def __init__(self, input_size, hidden_size):
        self.input_size = input_size
        self.hidden_size = hidden_size

        # 重み行列の初期化
        self.Wx = np.random.randn(hidden_size, input_size)
        self.Wh = np.random.randn(hidden_size, hidden_size)
        self.b = np.zeros((hidden_size, 1))

    def forward(self, x, h_prev):
        # 順伝播
        self.x = x
        self.h_prev = h_prev
        self.a = np.dot(self.Wx, x) + np.dot(self.Wh, h_prev) + self.b
        self.h = np.tanh(self.a)
        return self.h

    def backward(self, dh_next):
        # 逆伝播
        da = dh_next * (1 - np.tanh(self.a)**2)
        self.db = np.sum(da, axis=1, keepdims=True)
        self.dWh = np.dot(da, self.h_prev.T)
        self.dWx = np.dot(da, self.x.T)
        self.dx = np.dot(self.Wh.T, da)
        self.dh_prev = np.dot(self.Wx.T, da)
        return self.dx, self.dh_prev

    def update_parameters(self, learning_rate):
        # 重みの更新
        self.Wx -= learning_rate * self.dWx
        self.Wh -= learning_rate * self.dWh
        self.b -= learning_rate * self.db

# サンプルデータの生成
seq_length = 5
input_size = 10
hidden_size = input_size

fig = plt.figure()
# 入力データ (shape: (input_size, seq_length))
X = np.random.randn(input_size, seq_length)
print(X)
ax1 = fig.add_subplot(3, 1, 1)
ax1.plot(range(len(X)), X, marker="o")
ax1.set_ylabel("input")

# 目標出力 (仮の値)
y = np.random.randn(hidden_size, 1)
print(y)
ax2 = fig.add_subplot(3, 1, 2)
ax2.plot(range(len(y)), y, label="original")
ax2.set_ylabel("output")

# 初期隠れ状態 (shape: (hidden_size, 1))
h0 = np.zeros((hidden_size, 1))

# RNNセルの作成
rnn_cell = SimpleRNNCell(seq_length, hidden_size)

# 順伝播
h_t = h0
epochs = 100
loss_list = []
for epoch in range(epochs):
    for t in range(input_size):
        x_t = X[t, :].reshape(-1, 1)
        h_t = rnn_cell.forward(x_t, h_t)
    
    # 仮の損失関数 (例: 平均二乗誤差)
    loss = np.mean((h_t - y)**2)
    loss_list.append(loss)
    
    # 逆伝播
    dh_next = 2 * (h_t - y)  # 仮の勾配
    dx, dh_prev = rnn_cell.backward(dh_next)
    
    # 重みの更新
    learning_rate = 0.01
    rnn_cell.update_parameters(learning_rate)
    
    print(loss)

ax2.plot(range(len(y)), h_t, marker="o", label="predict")
ax2.legend(loc="upper left")

ax3 = fig.add_subplot(3, 1, 3)
ax3.plot(range(epochs)[1:], loss_list[1:], marker="o")
ax3.set_ylabel("loss")
ax3.set_yscale('log')


x_t = X[-1, :].reshape(-1, 1)
pred = rnn_cell.forward(x_t, h_t)[-1]

調べてみた

今回はRNNのモデルについて調べていきたいと思います。

該当コードはこちら

# RNNセルの定義
class SimpleRNNCell:
    def __init__(self, input_size, hidden_size):
        self.input_size = input_size
        self.hidden_size = hidden_size

        # 重み行列の初期化
        self.Wx = np.random.randn(hidden_size, input_size)
        self.Wh = np.random.randn(hidden_size, hidden_size)
        self.b = np.zeros((hidden_size, 1))

    def forward(self, x, h_prev):
        # 順伝播
        self.x = x
        self.h_prev = h_prev
        self.a = np.dot(self.Wx, x) + np.dot(self.Wh, h_prev) + self.b
        self.h = np.tanh(self.a)
        return self.h

    def backward(self, dh_next):
        # 逆伝播
        da = dh_next * (1 - np.tanh(self.a)**2)
        self.db = np.sum(da, axis=1, keepdims=True)
        self.dWh = np.dot(da, self.h_prev.T)
        self.dWx = np.dot(da, self.x.T)
        self.dx = np.dot(self.Wh.T, da)
        self.dh_prev = np.dot(self.Wx.T, da)
        return self.dx, self.dh_prev

    def update_parameters(self, learning_rate):
        # 重みの更新
        self.Wx -= learning_rate * self.dWx
        self.Wh -= learning_rate * self.dWh
        self.b -= learning_rate * self.db

行っていることとしては

となっています。各項目の基礎的な詳細は他の記事で説明していますので、ぜひご確認を。

ここではRNNに関わる部分について勉強していきたいと思います。

RNNのコードの特徴

ほとんどこれまで勉強したコードと変わらないのですが、これまでにない特徴があるようです。

それが、隠れ層自体にも重みがあることです。

何を言っているんだと思われる方が多いと思います。私もきちんと理解したといわれるとやや不安ですが、なんとなく分かりました。比較的分かりやすい説明はこちらでされています。

私が理解した範囲で図化しました。

各時間毎にこれまでのニューラルネットワークと変わりませんが、隠れ層が次の時間のニューラルネットワークのインプットにもなるのかなと思います。

隠れ層にも重みが与えられ、インプットデータと隠れ層の重み(Wx)と隠れ層同士の重み(Wh)を最適化していくことがRNNの目的になるのかなと思います。

さて、コードを見てみましょう。

def __init__(self, input_size, hidden_size):
        self.input_size = input_size
        self.hidden_size = hidden_size

        # 重み行列の初期化
        self.Wx = np.random.randn(hidden_size, input_size)
        self.Wh = np.random.randn(hidden_size, hidden_size)
        self.b = np.zeros((hidden_size, 1))

    def forward(self, x, h_prev):
        # 順伝播
        self.x = x
        self.h_prev = h_prev
        self.a = np.dot(self.Wx, x) + np.dot(self.Wh, h_prev) + self.b
        self.h = np.tanh(self.a)
        return self.h

これまでのニューラルネットワークとは異なる点は、Whが定義され、順伝播時に隠れ層もインプット(h_prev)に含まれていることです。

あとは、これを逆伝播することで、各重みを最適化しているようです。

とりあえず、これでRNNモデルが定義されるようです。

うまくできてますね~。改めてすごいですね。

コメント

タイトルとURLをコピーしました