Recursive Auto Encoderで単語を合成してみた [DW 2日目]

#ざっくりいうと
- Recursive Auto Encoder (RAE)というネットワークをChainerで書いた
- RAEで2つの単語の意味を合成して、フレーズの意味ベクトルを作ってみた
- フレーズの意味ベクトルをもちいて、意味の近い別のフレーズを生成してみた

# Recursive Auto Encoderとは
↓この論文で出てくるニューラルネットワーク。
[dynamic-pooling-and-unfolding-recursive-autoencoders](https://papers.nips.cc/paper/4204-dynamic-pooling-and-unfolding-recursive-autoencoders-for-paraphrase-detection.pdf
)

木構造の各ノードに対して下から順番にAuto Encoder (AE)を適用していき、最終的には木構造の根に集約されるというもの。応用先として例えば、単語を合成していき、最終的には文全体を1つのベクトルで表現することなどが可能。論文では、これに動的プーリングという手法を組み合わせ、文章の分類を行っている。2011年時点でマイクロソフト・リサーチの言い換えコーパスにおいて、言い換え文を当てるタスクで最も性能が良かったとか。

<img src=https://qiita-image-store.s3.amazonaws.com/0/80357/5a80a9d6-6d49-17a3-a329-bb9213ef240b.png width=400px>

# 最小単位のRAE
今回は、係り受け関係にある2つの単語を合成するという、最も単純なRAEを試してみた。（もはやRecursiveでないが。）　実験の目的は、2単語から成る簡単なフレーズ同士の意味の近さを獲得できるかどうかを試すことだ。
最小のRAEでは1つの根に葉が2つ付いた木構造を考える。2つの葉はそれぞれ単語ベクトルを表し、根はそれらの単語の意味を合成したフレーズベクトルを表す。ただし、単語ベクトルは予め他の方法で生成しておく。たとえば、単語ベクトルの生成方法として有名なものにShifted PMIやword2vecなどがある。今回はそれらの詳細は割愛する。

<img src=https://qiita-image-store.s3.amazonaws.com/0/80357/f658efc6-9a64-9a81-23b2-72e43338db43.png width=600px>

AEは、入力層、中間層、出力層をもつ3層のニューラルネットで、入力層と出力層が等しくなるように重みを学習する。ここでは、誤差関数に最小二乗誤差、活性化関数にReLu、最適化アルゴリズムにAdamを採用した。入力層は、word2vecによる単語ベクトルの次元数＝200次元を2倍(2つの単語を合成させるため)にして400次元にした。中間層は200次元、出力層は400次元。ドロップアウトはエンコード時（入力層から中間層を計算するとき）だけ有効にした。

~~~python:mini-rae.py
"""
Recursive Auto Encoder.
"""
class rae(Chain):

  def __init__(self, n_units):
    super(rae, self).__init__(
      l_enc = L.Linear(2 * n_units, n_units),
      l_dec = L.Linear(n_units, 2 * n_units)
    )
    self.train = True

  def forward(self, x, t, dropout_ratio=0.5):
    h = F.dropout(F.relu(self.l_enc(x)), train=self.train)
    y = F.relu(self.l_dec(h))

    if self.train:
      return F.mean_squared_error(y, t)
    else:
      return h.data
~~~

# 学習データ
まず、wikipediaの全記事(2016年4月7日)をダウンロードした。次に、重複を許さずランダムに記事をサンプリングして100分の1のサイズにし、さらにテキスト部分だけを抽出した。こうすることでデータ量を削減した。次に、テキストに対して係り受け解析器CaboChaを実行し、以下の条件を満たす係り受けだけを抽出した。

《条件》 「名詞(一般)」＋「助詞(格助詞)」 が 「動詞(自立)」 に係っている

こうして得られた単語の例を以下に示す。

~~~text:学習データ例
…
国内 が 定める
生物 として 扱う
人類 に もたらす
利益 を もたらす
根幹 に 関わる
技術 に ある
情報 が ある
人物 が 書く
文書 を 書く
…
~~~

左が名詞、真ん中が格助詞、右が動詞というふうに、スペース区切りで並べてある。なお、動詞は終止形に統一した。このデータを読み込み、名詞と動詞をword2vecでベクトル化して、RAEに入力して学習させた。

# 類似するフレーズを生成する実験
学習が収束した後、再度学習データのすべての名詞と動詞のペアをRAEの入力として与え、それぞれの入力に対する中間層の値を保存した。これで、学習データのすべての名詞と動詞のペアからなるフレーズのベクトルを獲得できたことになる。

次に、テスト用データを用意した。例を以下に示す。

~~~text:テストデータ例
…
本 を 読む
海 を 渡る
車 を 止める
猫 を 飼う
暇 が ある
…
~~~
このテストデータの名詞、動詞に対して学習時と同様にRAEに入力として与え、その中間層を取り出した。取り出した中間層(=200次元のベクトル)と、学習データで生成した中間層のコサイン類似度を測り、上位数件を表示させた。

~~~text:本＋読む
本 読む 1.0
本 書く 0.955986358409
本 読める 0.858435571978
本 語る 0.84356862602
本 付す 0.821400045671
本 載せる 0.820818776757
本 為す 0.802617336139
本 うかがえる 0.79390184447
本 観る 0.79098815753
~~~

~~~text:海＋渡る
海 渡る 1.0
外洋 渡る 0.850572369648
筏 渡る 0.808351510697
河 渡る 0.799305524185
北方 渡る 0.787615023037
海上 渡る 0.782707615662
大陸 渡る 0.775830943713
海 つながる 0.774621844224
海 出る 0.760339880622
湖 渡る 0.76010590806
波 渡る 0.759252821761
海 注ぐ 0.759024880651
黄 渡る 0.755827905574
飛 渡る 0.750249976879
カヌー 渡る 0.746589488245
海洋 渡る 0.73067901709
氷海 渡る 0.728545502487
川 渡る 0.722554842285
~~~

~~~text:車＋止める
車 止める 1.0
車 奪う 0.930581967862
車 押さえる 0.905055345034
車 浴びせる 0.893961489703
車 借りる 0.891233785137
車 近づける 0.89016437854
車 盗む 0.888275005706
車 追いかける 0.877922963881
車 乗せる 0.869103300232
車 取り付ける 0.862444947505
車 貸し出す 0.859459723476
車 有す 0.856913362901
車 見込む 0.849745182452
車 ぶつかる 0.849307057404
車 応じる 0.844877041244
~~~

~~~text:猫＋飼う
猫 飼う 1.0
兎 飼う 0.850114896183
猫 連れる 0.845120109129
愛犬 飼う 0.837972920717
金魚 飼う 0.833906977895
ウサギ 従える 0.821985579815
猫 喜ぶ 0.818360798296
犬 飼う 0.818225059992
犬 飼う 0.818225059992
パンダ 産まれる 0.790248907188
ウサギ 掘り出す 0.782751896737
ハリネズミ 飼う 0.755313176118
~~~

~~~text:暇＋ある
暇 ある 1.0
気配 ある 0.853454720727
好色 ある 0.852978259273
恩義 ある 0.852004587174
おかみ ある 0.851562372666
家事 ある 0.851186180389
品物 ある 0.845649244466
身請け ある 0.845492692654
遊び ある 0.843194521211
なじみ ある 0.833923776936
祝い事 ある 0.833667697073
たて ある 0.832502960613
~~~


# 感想
名詞と動詞の片方を固定して、もう片方を類似語で置き換えたような結果ばかり出てしまった。そうなっていない例としては、「猫＋飼う＝ウサギ＋従える」くらいだった。また、片方を固定してもう片方を類義語で言い換えるにしても、「出る」や「渡る」は汎用的な動詞なので、単体では類似度はそこまで高くないはず。今回、類似度が高くなっているのは、「海」という連用修飾語を伴ったときに意味が近くなるからだろうか？　色々と興味深い結果だった。

# リンク
[Deep Learning を徹底的に勉強してみる [DW 0日目]](http://qiita.com/komakomako/items/d36071567deefe6192f9)