リムナンテスは愉快な気分

徒然なるままに、言語、数学、音楽、プログラミング、時々人生についての記事を書きます

ChainerでSeq2SeqのEncoder、Decoderを作る

Chainerで機械翻訳 (python)の第1回


これを参考に進めていこうかと。

qiita.com

まずchainer.Chainがわからず。

chainer.Chain — Chainer 3.4.0 documentation

Composable link with object-like interface.


Composability is one of the most important features of neural nets. Neural net models consist of many reusable fragments, and each model itself might be embedded into a larger learnable system. Chain enables us to write a neural net based on composition, without bothering about routine works like collecting parameters, serialization, copying the structure with parameters shared, etc.

 よくわからんが便利そう。

Child links are registered via the assignment within a with self.init_scope(): block. The forward propagation is often implemented as the __call__ operator as the above example, though it is not mandatory.

見た感じ__init__中のinit_scope()にネットワークの層、__call__の中に前方伝搬?を書けばいいらしい。で、Chainってやつを使うと勝手につなげてくれると。
 

from chainer import Chain

class LSTM_Encoder(Chain):
    def __init__():
        super(LSTM_Encoder).__init__()
        with self.init_scope():
            # 層

    def __call__():
        # 伝搬


で、なんたら層の記述にはchainer.links.Linearを使うらしい。

chainer.links.Linear — Chainer 3.4.0 documentation

This is a link that wraps the linear() function, and holds a weight matrix W and optionally a bias vector b as parameters.

入力と出力のサイズを定義すればあとは勝手にやってくれるようで。層そのものを書く感じ。
ん、あとnumpy必要なのか。

参考元のchainer.links.EmbedIDもよくわからん。

chainer.links.EmbedID — Chainer 3.4.0 documentation

Efficient linear layer for one-hot input.


This is a link that wraps the embed_id() function. This link holds the ID (word) embedding matrix W as a parameter.

各語彙(に振られたID)に対して適当な重みをかけて、ベクトルとして取り出せるようにしたもの、かしら。
あとignore_labelがintだと返り値の埋まってないところを0でパディングしてくれるっぽい(i番目の列を…としか書いてないけどそういうことよね…?)

from chainer import Chain

class LSTM_Encoder(Chain):
    def __init__(self, vocab_size, embed_size, hidden_size):
        """
        クラスの初期化と層の記述
        vocab_size:  語彙数
        embed_size:  単語をベクトル表現したときのベクトルのサイズ
        hidden_size: 中間層のサイズ
        """
        super(LSTM_Encoder).__init__()
        with self.init_scope():
            # word 2 vector 層
            self.xe = L.EmbedID(vocab_size, embed_size, ignore_label=-1)
            # vectorをhiddenの4倍のサイズに拡大?する層
            self.eh = L.Linear(embed_size, 4*hidden_size)
            # hiddenを4倍のサイズにする層
            self.hh = L.Linear(hidden_size, 4*hidden_size)

    def __call__():
        # 伝搬


なんで4倍かみたいな話は元記事を参照されたし。


chainer.functions.tanhは普通にtanhでの変換、それからchainer.functions.lstmは、

chainer.functions.lstm — Chainer 3.4.0 documentation

Long Short-Term Memory units as an activation function.

This function implements LSTM units with forget gates. Let the previous cell state c_prev and the input array x.

ということなので内部メモリなるものと入力を与えてやればよさそう。

from chainer import Chain

class LSTM_Encoder(Chain):
    def __init__(self, vocab_size, embed_size, hidden_size):
        """
        クラスの初期化と層の記述
        vocab_size:  語彙数
        embed_size:  単語をベクトル表現したときのベクトルのサイズ
        hidden_size: 中間層のサイズ
        """
        super(LSTM_Encoder).__init__()
        with self.init_scope():
            # word 2 vector 層
            self.xe = L.EmbedID(vocab_size, embed_size, ignore_label=-1)
            # vectorをhiddenの4倍のサイズに拡大?する層
            self.eh = L.Linear(embed_size, 4*hidden_size)
            # hiddenを4倍のサイズにする層
            self.hh = L.Linear(hidden_size, 4*hidden_size)

    def __call__(self, x, c, h):
        """
        encoderの動作、層を配線する
        x: 入力vector
        c: 内部メモリ
        h: 隠れ層

        次代の内部メモリと隠れ層を返す
        """
        # xeで単語vectorに変換、tanhにかける
        e = F.tanh(self.xe(x))
        # 前の内部メモリ、単語vector*4 + hidden vector*4を入力
        return F.lstm(c, self.eh(e)+self.hh(h))


デコーダーはエンコーダーの部品を使いまわす。

# coding: utf-8

import numpy as np
from chainer import Chain
import chainer.functions as F
import chainer.links

class LSTM_Decoder(Chain):
    def __init__(self, vocab_size, embed_size, hidden_size):
        """
        クラスの初期化と層の記述
        vocab_size:  語彙数
        embed_size:  単語をベクトル表現したときのベクトルのサイズ
        hidden_size: 隠れ層のサイズ
        """
        super(LSTM_Encoder).__init__()
        with self.init_scope():
            # word 2 vector(embedded) 層
            self.ye = L.EmbedID(vocab_size, embed_size, ignore_label=-1)
            # embeddedをhiddenの4倍のサイズに拡大する層
            self.eh = L.Linear(embed_size, 4*hidden_size)
            # hiddenを4倍のサイズにする層
            self.hh = L.Linear(hidden_size, 4*hidden_size)
            # 出力vectorを単語vectorのサイズに変換する層
            self.he = L.Linear(hidden_size, embed_size)
            # 単語vectorを語彙サイズのvectorに変換する層
            self.ey = L.Linear(embed_size, vocab_size)

    def __call__(self, y, c, h):
        """
        encoderの動作、層を配線する
        y: hot な vector
        c: 内部メモリ
        h: 隠れ層

        予測単語、次代の内部メモリと隠れ層を返す
        """
        # yeで単語vectorに変換、tanhにかける
        e = F.tanh(self.ye(y))
        # 前の内部メモリ、単語vector*4 + hidden vector*4を入力
        c, h = F.lstm(c, self.eh(e)+self.hh(h))
        # 出力されたhidden vectorを単語vector -> 出力vectorのサイズにリサイズ
        t = self.ey(F.tanh(self.he(h)))
        return t, c, h

次は、こいつらを組み合わせてSeq2Seq本体を作る。

次:第2回 Seq2Seq