【機械学習】Google翻訳(みたいなもの)を自作してみた。

  • 123
    いいね
  • 4
    コメント

movie-compressor.gif

はじめに

最近機械学習熱いし、その波に乗りたいなということで、大学時代に若干触れていた自然言語処理の知識を生かして、タイトルの通りGoogle翻訳のような機械翻訳機を作成してみました。
結果、ページトップのアニメーションのような動作をする翻訳機を作れましたが、翻訳精度はgoogle翻訳に惨敗でした。どこらへんが惨敗だったのかは最後のまとめでちょっとだけ触れます。

結果的には惨敗だったのですが、自分が作ったモデルで翻訳できた瞬間はとても嬉しかったので、自分の子供のような存在を生み出す喜びを誰かに共感していただきたく思い、機械翻訳機を作成して得た自然言語処理(ほぼ翻訳)に関する知見とその方法をまとめてみました!

自然言語処理というなかなかに難しいジャンルなので、途中眠たくなるような説明や数式も一応紹介いたしますが、 この記事のゴールはオリジナルな翻訳モデルを作成し上のアニメーションのようにターミナル(黒い画面のやつ)上で翻訳を行うことです!
なので、説明とか良いから早く自分で一通り作らせて!という方は記事の最後の方までスクロールしちゃってください。

※ちなみに記事中で紹介しているリンク先は個人的に説明が分かりやすいと思ったサイトをあげさせていただいています。
※凄くボリューミーな内容になってしまったので目次を見て気になる部分だけ拾い読みしてください。

目次

1.そもそも機械翻訳って

2.ルールベース機械翻訳

3.統計的機械翻訳

4.ここから作業開始

5.自作したモデルを用いた翻訳の実行

6.まとめ


1.そもそも機械翻訳って

みなさんも恐らく大変お世話になっているであろう、google翻訳yahoo翻訳といった、何かしらの言語を入力したら指定した言語の翻訳結果を出力してくれるあのサービスに用いられている技術のことです。

ノータイムで入力に対する翻訳結果を吐き出してくれるあのサービス、当たり前ですが人間がレスポンスを返しているのではなく、入力に対して尤もらしい答えを機械がレスポンスしてくれています。
つまり機械翻訳とはその名の通り、人手による翻訳では無く、機械が行ってくれる翻訳のことですね!

スクリーンショット 2015-12-19 21.13.22.png

そして、現在サービスとして提供されている機械翻訳の技術の手法としては主に、ルールベースの翻訳手法(RBMT)統計ベースの翻訳手法の2つがあります。
2つの翻訳方式の違いとメリットデメリットを次で簡単に説明致します。

2.ルールベース機械翻訳(RBMT: Rule Base Machine Translation)

ルールベース翻訳とは、原言語を構文解析して文法構造を把握した後、辞書を用いて文節単位で目的言語へ翻訳し、最後に目的言語の文法へ翻訳した文字列を並び変える方法です。
文字で説明すると良く分からないのですが、実際にやっている作業は単純で以下で説明する通りです!

(例)「私はサッカーをします」という入力の時

①文節単位に文を区切ります。
 私は / サッカーを / します

②文節間の修飾関係(係り受け構造)を把握します。
 私は    => します(主語と動詞)
 サッカーを => します(目的語と動詞)
 この修飾関係を出すことで以下のような構文構造になっていることがわかります。
 私は(主語) / サッカーを(目的語) / します(動詞)
 
③次に、それぞれの文節を辞書を用いて翻訳します。
 私は => I / サッカーを => soccer / します => play

④最後に英語の構文ルールに従い文節の並び替えを行います。
 I play soccer (S + V + O) 
 
 そして以下のような翻訳結果が求まります。
 翻訳結果 : I play soccer

メリット

  • 大量の学習データを必要としない。
  • 入力のジャンルに合わせた辞書を用いる事で翻訳精度を向上することが可能である。
  • 言語ルールと文法ルールと辞書さえあればどんな言語でも割と安定した結果を求められる。
  • 研究期間が長く辞書が充実しているので、メジャーな言語だと高い精度で結果が求まる。

デメリット

  • 辞書自体は翻訳家により作成されるのでコストが高くなる。
  • マイナーな言語だと対応が難しい。
  • 既に翻訳結果の精度向上に限界が来ている説がある。

3. 統計的機械翻訳(SMT: Statistical Machine Translation)

統計的機械翻訳とは、原言語と目的言語の対訳文から意味的な等価性を、目的言語からその言語らしさを学習して確率モデルを作成し、作成したモデルから意味的な等価性と目的言語らしさの確率が最大となるような文を翻訳結果として出力する手法です。

良くわかんない人のために凄くざっくり説明すると、大量の対訳文を集めて、その文から目的言語の言語ルールと文法ルールを作って、入力に対してそれぞれのルールをいい感じに満たす最もそれっぽい答えを翻訳結果としましょう!という感じですね!この大量の対訳文から確率モデルを作成することを学習といいます。
今回は、こちらの統計的機械翻訳手法を用いて翻訳を行うので、詳細を以下でしっかり説明します。

翻訳確率

まず、翻訳を行う上で肝になる確率モデルの説明をします!
原言語をf、目的言語をe、翻訳確率をe(hat)とすると以下のように表せます。

\hat{e} = argmax_{e}P(e\ |\ f)

この式をベイズの定理を用いることにより以下のような式に変形できます。

\hat{e} = argmax_{e}{P(f\ |\ e)}{P(e)}

ここで表されるP(f|e)は翻訳モデルを、P(e)は言語モデルを表しています。
つまり、統計的手法を用いた翻訳では、P(f|e)P(e)を最大化する解を尤もらしい翻訳結果として出力します。

言語モデル

次に翻訳に用いられる言語モデルを説明します!
言語モデルとは一言で表すと、人間が用いるであろう「言葉らしさ」を確率としてモデル化したものです。具体的に説明すると、ある単語の後にはどんな単語が現れるか、という確率を目的言語文から学習しています!
N-gram言語モデルでは、文字列のN番目に来る単語はN-1個前の単語に依存しているという風にみなしています。これは以下の式のように表されます。

P(e) = \prod_{i=1}^{N}P(t_i|t_{i-n+1},t_{i-n+2},...,t_{i-1} )

翻訳モデル

翻訳モデルは原言語と目的言語の意味的な等価性を確率的に表したモデルのことです!
翻訳モデルの確率は両言語のフレーズの対応(アライメント)と、フレーズの翻訳確率により決定されます。
ここでアライメントaが与えられているとすると翻訳確率P(f|e)は以下のような式で表すことできます。

\begin{aligned}
P(f|e) & = \sum_{a}P(f , a\ |\ e)\\
& = \sum_{a}P(f\ |\ e,a)P(a\ |\ e)
\end{aligned}

アライメントはリオーダリングモデル、フレーズの翻訳確率はフレーズテーブルに格納されます。
ここの理解がとても重要と参考書には書いてありますが良い事例がなくて理解が難しい...。
とりあえず、ここではそういうものなんだな!と割り切って先に進めます!

メリット

  • 人手による辞書作成の必要がない。
  • 言語に依存する事なくモデルを作成できる。
  • 割と新しめの技術なので研究が盛んである。
  • 安価に高速に翻訳ができる。

デメリット

  • 学習用の大量の対訳文が必要である。
  • 学習データに未知語が存在すると翻訳精度が低くなる。

なんと言っても、統計的機械翻訳手法は人手による辞書作成の必要がないため、コーパスさえ準備することが出来れば言語に依存する事なく翻訳モデルを作成できるところに利点があると思います!

4.ここから作業開始

説明が長くなりましたが、ここから実際にコンソール上で行う作業を説明致します!
今回の作業は、Moses(モーゼ)という翻訳モデルと言語モデルから翻訳結果を出力するデコーダを用いて行います!
基本的にMosesのマニュアルに作業方法は書いてあるので、そちらに準じて行っていきます。

環境構築

仮想環境の準備 (Vagrant, Ubuntu 14.04)

まず仮想環境を準備致します!
ローカルでも作業できるのですが、環境構築が難しく何度もやり直したくなる衝動に駆られると思うので最初から仮想環境で行うことをお勧め致します!
Ubuntu 14.04を用いましたが理由は特に無いです。
以後、Ubuntu環境でのセットアップ方法について説明していきますが、他のOSで行いたい場合はマニュアルを参照してそれぞれの環境に合わせてセットアップを行ってください。

$ mkdir ~/nlp
$ cd ~/nlp
$ vagrant init
$ vagrant box add nlp https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box

15: config.vm.box = "base"
=>  config.vm.box = "nlp"

51: #vb.memory = "1024"
=>  vb.customize ["modifyvm", :id, "--memory", "4096"]

以上のようにVagrantfileの該当行数の書き換えを行なってください。
ちなみに4096という数字はメモリ4Gを表しているのですが、モデルの学習にはやたらとメモリを消費するのでこれくらい使用できるメモリが無いとなかなか厳しいかもしれません。
どうしてもハード的に性能が劣る場合は、Amazon AWSのAMIも準備されているのでそちらを使って行ってみてください!

ここまで記述出来たらあとは以下のようにvagrantを起動してください。

$ vagrant up
$ vagrant ssh

Mosesのインストール

一番最初に、翻訳モデルと言語モデルから翻訳結果を出力するデコーダの役割をするMosesのインストールを行います。ちなみにUbuntu環境ではboostをインストールする必要がありますが、環境によってはこのboostのインストールは必要ないそうです!
ちなみにこのMosesを動作させる事が一番のハードルでした...。
悪戦苦闘しましたが最終的にうまくいった方法を下で説明します。

#Mosesの動作に必要なパッケージのインストール
$ sudo apt-get install build-essential git-core pkg-config automake libtool wget
zlib1g-dev python-dev libbz2-dev

#Mosesのコンパイルに必要なboostのセットアップ
(boost無しでMosesをコンパイルしようとしたら、libboost_serialization.so.1.59.0: cannot open shared object file: No such file or directoryと怒られた。)
$ cd ~
$ wget http://downloads.sourceforge.net/boost/boost_1_59_0.tar.bz2
$ tar jxvf boost_1_59_0.tar.bz2
$ cd boost_1_59_0
$ ./b2 -j4 --prefix=$PWD --libdir=$PWD/lib64 --layout=system link=static install || echo FAILURE

#Mosesのセットアップ
$ git clone https://github.com/moses-smt/mosesdecoder.git
$ cd mosesdecoder
$ ./bjam --with-boost=$HOME/boost_1_59_0 -j4 

boostを自分でセットアップした場合は、boostのパスを指定してコンパイルしてあげる必要があります。あとwarningが沢山出ると思いますが、errorが出なければ万事OKです!

最後に以下の様にテストして、無事出力が得られればMosesのセットアップ完了です!
このテストではサンプルとして提供されている英語の言語モデルと独英の翻訳モデルを参照し、ドイツ語の入力を英語に翻訳しています。

$ cd ~
$ wget http://www.statmt.org/moses/download/sample-models.tgz 
$ tar xzf sample-models.tgz
$ cd sample-models
$ ~/mosesdecoder/bin/moses -f phrase-model/moses.ini <phrase-model/in> out 
#以下の文字列が得られていれば無事セットアップ完了
This is a house

学習に用いる日英パラレルコーパスの加工

次に学習に必要な日英対訳コーパスの加工を行います。このパラレルコーパスを見つけることがなかなか難しいのですが、このパラレルコーパスの内容に翻訳精度が大きく影響を受けてしまうので頑張って使用可能なコーパスを探してみてください!

パラレルコーパスの準備

ちなみに僕は日英京都関連文書コーパスを使用させていただきました!

「本サービスで使用している[データ]はWikipediaの日本語文を国立研究開発法人情報通信研究機構が英訳したものを、Creative Comons Attribution-Share-Alike License 3.0による利用許諾のもと使用しております。詳細はhttp://creativecommons.org/licenses/by-sa/3.0/およびhttp://alaginrc.nict.go.jp/WikiCorpus/をご覧下さい。」

日英京都関連文書コーパスのは.xmlファイルに対訳文が表記されていますので、jタグのものをwiki.en-ja.jaへ、e type="check"タグのものをwiki.en-ja.enへ、それぞれ以下の画像のようにタグ内の文字列だけ抜き出してください。うまく抜き取ることが出来れば、約50万文の対訳文ができると思います!

wiki.en-ja.ja
1: 日本の水墨画を一変させた。
2: 備中国に生まれ、京都・相国寺に入ってから周防国に移る。
3: その後遣明使に随行して中国(明)に渡って中国の水墨画を学んだ。
4: 作品は数多く、中国風の山水画だけでなく人物画や花鳥画もよくした。
5: 大胆な構図と力強い筆線は非常に個性的な画風を作り出している。
#以下約50万行続く
wiki.en-ja.en
1: He revolutionized the Japanese ink pointing.
2: Born in Bicchu Province, he moved to Suo Province after SShoku-ji Temple in Kyoto
3: Later he accompanied a mission to Ming Dynasty China and learned Chinese ink pointing.
4: His works were many, including not only Chineses-style landscape paintings, but also portraits and pictures of flowers and birds.
5: His bold compositions and strong brush strokes constituted an extremly distinctive style.
#以下約50万行続く

これらのファイルを以下の様に作成したcorpusディレクトリの中に格納します。
vagrantの共通ディレクトリを利用してファイルの受け渡しを行っています。

$ mkdir ~/corpus
$ mv /vagrant/wiki.en-ja.ja /vagrant/wiki.en-ja.en ~/corpus
文を学習に適した形に加工する

次に、パラレルコーパスから取得した文を学習に適した形にするために、以下の3ステップを行います。

  • tokenisation : 文を単語区切りにする。
  • lowercasing : 単語の小文字化
  • cleaning : 単語数が80文字以上の文は精度が低くなるので取り除く。

まず、tokenisationを行います。
英語はmosesに元々入っているスクリプトを使用し、日本語の加工にはmecabを使用します。
mecabが入っていない方はapt-getで辞書と一緒にインストールしてください。

#英語のトークン化
$ ~/mosesdecoder/dist/tokenizer.perl -l en <~/corpus/wiki.en-ja.en> ~/corpus/wiki.en-ja.tok.en

#日本語のトークン化
$ mecab -O wakati < ~/corpus/wiki.en-ja.ja > ~/corpus/wiki.en-ja.tok.ja

学習の際に英語の大文字と小文字が混在していると別のトークンとして扱われてしまうので、通常であればトークンの大文字・小文字を多数決で決めるtruecasingを行いますが、truecasingを行うためのモデルが無いので、今回は全部小文字に合わせるlowercasingを行います!

#英語の小文字化
$ ~/mosesdecoder/scripts/tokenizer/lowercase.perl <~/corpus/wiki.en-ja.tok.en> ~/corpus/wiki.en-ja.low.en

#一応日本語も小文字化
$ ~/mosesdecoder/scripts/tokenizer/lowercase.perl <~/corpus/wiki.en-ja.tok.ja> ~/corpus/wiki.en-ja.low.ja

そして最後に、学習モデルの精度をあげるためのcleaningを行います。

$ cd ~/corpus
$ ~/mosesdecoder/scripts/training/clean-corpus-n.perl wiki.en-ja.low ja en wiki.en-ja.clean 1 80

以上でコーパスの加工は終了です!

言語モデル・翻訳モデルの学習

ここまできて、やっとモデルの学習に入ることができます!
最初にも言いましたが、モデルの学習は、メモリの消費量が激しいのにプラスして結構な時間を必要とするので、パソコンを使用しない放置できる時間にコマンドを実行することを推奨します。
大体ですが、僕の環境で50万分のコーパスを用いた場合に、言語モデルの学習に20分、翻訳モデルの学習に3時間を要しました。

言語モデルの学習

言語モデルを学習させるソフトウェアとして、IRSTLMやSRILMなどが有名です。
それらのソフトは7-gramモデルが作成できたり、ライセンスが柔軟などのメリットがありますが、特に今回はそこまでの機能を必要としないので、Moses付属のKenLMを用いて、5-gramの言語モデルを80%のメモリを使用して作成します。

$ ~/mosesdecoder/bin/lmplz -o 5 -S 80% -T ./ < ~/corpus/wiki.en-ja.low.ja > wiki.en-ja.arpa.ja
翻訳モデルの学習

最後に翻訳モデルの学習を行います!
翻訳モデルの学習では主に、giza++を用いたワードアライメントや、フレーズのスコアリング、フレーズテーブルの作成などが行われ、デコードの際にそれらを参照するMosesの設定ファイルが作り出されます。

#ワードアライメントに必要なgiza++のセットアップ
$ git clone https://github.com/moses-smt/giza-pp.git ~/
$ cd ~/giza-pp
$ make

#mosesからgiza++を参照できるようにバイナリファイルを作成しコピーする。
$ cd ~/mosesdecoder
$ mkdir tools
$ cp ~/giza-pp/GIZA++-v2/GIZA++ ~/giza-pp/GIZA++-v2/snt2cooc.out ~/giza-pp/mkcls-v2/mkcls tools

#翻訳モデルの学習(約3時間)
$ mkdir ~/work
$ cd ~/work
$ ~/mosesdecoder/scripts/training/train-model.perl --root-dir ./ --corpus ~/work/corpus/wiki.en-ja.clean --f en --e ja --lm 0:5:$HOME/corpus/wiki.en-ja.arpa.ja

翻訳モデルの学習が完了した後、workディレクトリの中に、giza.en-ja、giza.ja-en、modelのディレクトリが存在し、model/moses.iniが出来ていれば完成。

5.自作したモデルを用いた翻訳の実行

moses.iniの設定ファイルが完成すると先ほど作成した言語モデルと翻訳モデルを参照した機械翻訳を行うことができます!
その方法ですが最初にテストした時と同様にmoses.ini引数に指定し、mosesコマンドを実行します。

インストールから引っかかり、モデルの作成にやたらと時間がかかり、苦労して苦労してここまで辿りついたので立ち上がった時はすごく嬉しいです!!


born-compressor.gif

以下のgifが実行している様子です!
"my name is sesshu"に対し、"私の名は雪舟"が得られ、"this is a house"に対し、"これは家"が得られています!
無事翻訳でき、翻訳確率も求められていることが確認できます!


movie-compressor.gif

6.まとめ

うまくいった例だけ紹介しましたが、未知語が出てきた場合や入力に大文字が混じっている場合にうまく翻訳できないことが多々ありました。
あと、学習データが京都の割と硬派な文書だったこともあり、言い回しも普通の人が話し言葉で使わないような言い回しを使っていました。

翻訳精度の向上方法の1つに単純にコーパスの量が影響していたりもするので、例えば話言葉が含まれたパラレルコーパスを追加して再度学習し直してみるともっと多様な言い回しに対応した翻訳が行われるかもしれないですね!googleとの圧倒的な違いは学習に用いられるコーパスの量の差にもあると思います。(他にもまだまだ沢山の要因がありますが)

ちなみに、このMosesはxml-rpcを使用することでwebサーバとして動作させることも可能です!
なので、パラレルコーパスを独自に集め、学習に用いることできる人は自分のサービスに組み込むことも可能になりますね!

他にもMosesを様々な形で使用できるようにオプションが整備されているので、興味がある方はマニュアルを確認してみると良いかと思います!

アドベントカレンダーも早いもので残りもあと3日ですね!
プログラミング大好きベーシックのアドベントカレンダー、明日は、kakizakiさんです!

長くなりましたが、最後までお読み頂きありがとうございます!!