概要
tensorflowの練習に、英単語(アルファベット文字列)を入れると、その読みとなるカタカナを出力するという機械学習課題をやってみて、まぁいちおう動いているなという程度のものができた。
学習結果をブラウザ内で実行できるよう移植して http://www.jmuk.org/en-ja/ に置いといた。
本文
tensorflowを勉強したのはいいのだけど、なんらかの練習課題をやってみたい、なにかいい課題はないだろうか、と思っていた。個人が趣味でやってみる課題では学習データの準備をどうするかが気になるところだけど、それも簡単にそろうやつがいい。データの手動のタグ付けをしない課題がいい。
などと考えているうちに「英単語を読ませてカタカナにする」という課題を思いついた。たとえば “google” という入力文字列から “グーグル” という文字列を出力させたい。そういう問題になる。
これを学習するには学習データとして大量の「英単語とカタカナ語のペア」が必要になるわけだけど、これにはウィキペディアのデータを使う。ウィキペディアのページはサイドバーに他言語版ウィキペディアの同項目のページへのリンクがありますよね。このデータを取ってくることで、同じ事物に対する英単語と日本語の対を取ってこれる。ウィキメディア財団のサイトはどれも定期的にデータをダンプして誰でも取得できるようになっているので、この関係性が格納されたwikidataのデータダンプをある日ダウンロードして項目の対を作った。
もちろんここには漢字の単語やかなり関係性の薄い単語も含まれているので、日本語はカタカナや中黒などのみを含むものとか、末尾のカッコはdisambiguationだから消すとか、まあいろんな適当なヒューリスティックスを入れて学習データを作る。ちなみに当初は項目同士のペアで学習させていたのだけど、なかなかうまく行かなかったので、このデータをもとに単語同士のペアをつくって学習させた。この辺もけっこういろいろ試行錯誤があるのだけど、つまんないと思うので略す。
さて、学習データはできた、ではどう学習させるか。
この課題というのは文字列を受け取って文字列を出力するというものなのだが、文字列の長さは学習ペアごとにけっこうばらばらなので(また文字数も一対一対応ではないので)、ふつうのフィードフォワードニューラルネットワークではうまく解くことができない。リカレントニューラルネットワークを使って解く、seq2seqというモデルを使う。というか、「英語の読みの推定」というのはseq2seqとしては非常に典型的な(であるがゆえに練習課題としてはけっこういい)タスクだと思う。
リカレントニューラルネットワーク(RNN)というのは、前回の出力結果(の一部)を次回の入力に加えるというタイプのニューラルネットワークの総称で、これによって内部状態のようなものが保持でき、時系列データとか、順番に入力がくるようなもの(文字列とか単語列とか)の処理によいとされている。
seq2seqでは典型的にはこのRNNセルをふたつ用意する。まず入力文字列(今回の場合は英語文字列)をいっぽうのRNNに入力していって状態遷移をさせ、遷移結果の状態をふたつ目のRNNに渡す。ふたつ目のRNNにはまず「ここから学習開始」を意味する特殊なシンボル(GO)を入れ、その出力結果を最初の文字とみなす。それ以降はひとつ前の出力結果の文字を入力とし、次の文字を出力していく。文字列の最後にはEOS (end of sentence) という特殊なシンボルが出力されると仮定して、そうなったら完了。
図としてはこんなかんじ。
ここでのABCというのがもとの入力列で、WXYZというのが出力列。この図ではRNNセルは単独だが、ABC側と<go>WXYZ側でセルを分けるのが良いとされている。
さて、実はtensorflowにはRNNやseq2seqのモデルが標準で提供されていてチュートリアルまである(この図もそこへのリンク)。というかさらにいうとtranslate.pyというコマンドがあって、これを使えば特に何もしなくても学習できちゃうかも(さすがにそれはないなと思ったので使わなかったけど)。ともあれこのチュートリアルとリンクされている論文は素直に良いと思うので、詳しくはそのへんを読めばよろしいことでしょう。
自分でいろいろ組んでみて試してみたものの、結果的にはたいしたことない構成に落ち着いたので、この程度なら既存のコードをそのまま動かすんでもいいんじゃないの、という気がするけれど、まあ自分で書いたからこそ、多層化させたりいろいろパラメータをいじったりいろいろ試してみたりというのが目的の練習課題なので、と自分に言い聞かせているところ。
ところでtensorflowにおけるRNNセルやseq2seqの実装というのはなかなか面白い構成になっている。
たとえばRNNセルを表現したクラス RNNCell やその実装である LSTMCell のようなクラスの実装を見ても、実際のマトリックスのデータ(tf.Variableなど)はインスタンス変数としては保持しない。状態数などの基本的なパラメータだけを持っている。そして実際にグラフ構造を作るときに変数の名前で名寄せしてデータを取り出すようになっている。
つまりRNNセルクラスのインスタンスたちは「どういう計算をする」という構造だけを定義していて、具体的に誰と計算をするかは必要なときに決めるわけだ。tensorflowは変数スコープというものを定義することができ、スコープを一致させれば同じマトリックスに対して計算ができるし、変えれば構造は同じだけど違う対象と計算をするようになる。同じような構成を何段も組むときにループを使ってうまく書ける、などのことが綺麗にかけたりするのだろうと思う。あとインスタンスの作成がgraphのスコープと無関係にできるってのもあるのかな。なんにしても変数スコープってそう使うんだなっていう感じはある。
distributed tensorflowがこの変数スコープとうまく適合しているとよく出来ているなあとさらに感心するところな気もするが、あんまりそうなっていない気がする。この辺はまだそこまで煮詰まっていないのかな。
話がずれた。
さて、学習結果をいろいろ遊んでみると、やっぱり相当変な部分がのこっている。日本人の名前がうまく読めない、とかはもうしょうがないとおもうんだけど、ごく普通の名前、たとえばJamesが読めないってのはどうなんだろうな。学習データの偏りじゃないかなと思っているけれども、救えたほうがよいよなーと思ったりもする。が、そういうのをなんとかするために試行錯誤するのも楽しいけれど時間かかるのでとりあえずもういいやこれで、と思うことにした。あきた、とも言う。
それでもなんというか、当初なかなかうまくいかないなーと思っていたものがあるときいきなりそこそこ読めるようになった時はけっこうびっくりしたし、なんというか、すごいなあと素直に思ったものだし、やっぱりそこそこ読めている結果になっていて面白い。なので、自分としてはまあいちおうこんなもんか、ってところまで行けたかなと思ってる次第。まあほんと大したことないんですが……。
記事も長くなったので、ブラウザ環境への移植の話はまた明日にでも。
“ディープラーニングで英語を読ませる” への2件のフィードバック
コメントは受け付けていません。