Encoder-Decoder Model その1


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


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



■本日の進捗

  • Encoder-Decoder Modelの理解


■はじめに

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

今回は、再帰型ニューラルネットワークモデルベースのEncoder-Decoder Modelを学んでいきたいと思います。

■Encoder-Decoder Model

前回LSTM言語モデルを用いた重みで確率的に文章を生成しました。この生成方法は入力と出力が直接的に対応しています。入力に対して異なる長さや構造を持つ出力シーケンスに変換するには、入力全体を低次元に圧縮(Encode)してから再度次元を高く(Decode)する方法が用いられており、この構造を持つモデルをEncoder-Decoder Modelと呼びます。

一般に時系列的な入力にのみ基づいて予測を行うRNNやLSTMに対して、潜在表現を基に生成を行うEncoder-Decoder Modelの方がより柔軟な変換が可能です。

■Encoderクラス

まずはEncoderを実装します。前回までで用意した時系列対応したEmbedding層と、時系列対応したLSTM層を用いて、シーケンスを固定次元のベクトル表現に変換するためのものになります。

まずは語彙数(vocab_size)、埋め込みベクトルの次元数(wordvec_size)、LSTMの隠れ状態次元数(hidden_size)を引数として受け取り、内部状態を初期化していきます。

class Encoder:
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, wordvec_size, hidden_size
        rn = np.random.randn

        embed_W = (rn(V, D) / 100).astype('f')
        lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
        lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')

        self.embed = TimeEmbedding(embed_W)
        self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=False)

        self.params = self.embed.params + self.lstm.params
        self.grads = self.embed.grads + self.lstm.grads
        self.hs = None

シーケンスデータを受け取り、先ほど用意したEmbedding層とLSTM層を用いて順伝播を行ったら、最終的な隠れ状態を抽出して返します。

    def forward(self, xs):
        xs = self.embed.forward(xs)
        hs = self.lstm.forward(xs)
        self.hs = hs
        return hs[:, -1, :]

Encoderのパラメータを学習するために、同様に逆伝播も行います。

    def backward(self, dh):
        dhs = np.zeros_like(self.hs)
        dhs[:, -1, :] = dh

        dout = self.lstm.backward(dhs)
        dout = self.embed.backward(dout)
        return dout



■Decoderクラス

潜在表現を基にシーケンスを作成するためのDecoderクラスを実装します。

まずはEncoderクラスと同様に各層の重みやバイアスを初期化します。ここでは時系列対応したEmbedding層、LSTM層、全結合層で構成します。

class Decoder:
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V, D, H = vocab_size, wordvec_size, hidden_size
        rn = np.random.randn

        embed_W = (rn(V, D) / 100).astype('f')
        lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
        lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4 * H).astype('f')
        affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
        affine_b = np.zeros(V).astype('f')

        self.embed = TimeEmbedding(embed_W)
        self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True)
        self.affine = TimeAffine(affine_W, affine_b)

        self.params, self.grads = [], []
        for layer in (self.embed, self.lstm, self.affine):
            self.params += layer.params
            self.grads += layer.grads

順伝播で入力シーケンスを処理して確率(score)を生成します。まずは隠れ状態(h)をLSTMクラスのset_stateメソッドで初期化します。シーケンス(xs)を埋め込み層で単語ベクトルに変換し、これをLSTMで時系列データを生成します。この時系列データを全結合層でスコアを生成したら終了です。

    def forward(self, xs, h):
        self.lstm.set_state(h)

        out = self.embed.forward(xs)
        out = self.lstm.forward(out)
        score = self.affine.forward(out)
        return score

Decoderの場合も同様に、学習のために各層に逆伝播させます。

    def backward(self, dscore):
        dout = self.affine.backward(dscore)
        dout = self.lstm.backward(dout)
        dout = self.embed.backward(dout)
        dh = self.lstm.dh
        return dh

隠れ状態を基に新しい単語を生成するため、generateメソッドを実装します。初期隠れ状態(h)、シーケンス生成の開始単語ID(start_id)、生成する単語数(sample_size)を引数として受け取ったら、sample_idを埋め込み層に入力し、sample_sizeの数だけLSTMと全結合層を用いて確率(score)を計算します。この時最も確率の高い単語IDを選んだら、その単語IDをリスト(sampled)に追加します。

    def generate(self, h, start_id, sample_size):
        sampled = []
        sample_id = start_id
        self.lstm.set_state(h)

        for _ in range(sample_size):
            x = np.array(sample_id).reshape((1, 1))
            out = self.embed.forward(x)
            out = self.lstm.forward(out)
            score = self.affine.forward(out)

            sample_id = np.argmax(score.flatten())
            sampled.append(int(sample_id))

        return sampled



■おわりに

今回は、Encoder-Decoder Modelの中心となるEncoderクラスとDecoderクラスを実装しました。Encoder-Decoder Modelは潜在表現を基に新たに生成を行うので、機械翻訳や対話型のチャットボット、音声認識などを行うことが可能です。一般的に想像される生成AIに近づいてきました。

■参考文献

  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


コメントを残す

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