猫。タイで撮影。

はじめに

昨年辺りから、流行りに乗って(?)機械学習を勉強していたのですが、「機械学習と実装がいイマイチ自分の中でつながらないなぁ」とずっと思っていました。
そんな中、訳あってタイに行く予定ができました。せっかくなので、現地で使う翻訳機を機械学習で自分で作って、「機械学習を機能として実装する」ことを経験してみたいと思い、今回挑戦してみました。

目標

日本語→タイ語の変換を、翻訳コーパスから機械学習により実装し、iOSアプリで実際に使える形にする。完成させる

ちなみに、私のタイ語に関する事前知識は「文字を見たらタイ文字か否か判別できるけど、それ以上は全く読めない」程度です。

結果

iOSアプリを起点とした、翻訳は作成できました。

学習文章を一部変えたものは、うまく翻訳できています。従って、使用コーパス(実装で説明)内の単純な文法は学習できていたようです。一方、長文になったり、複雑な文法になると実用するには厳しい出来具合となりました。
(以下の画像はGoolge翻訳で翻訳結果を確認しているため、文法などが正しいか、そもそもGoogle翻訳の結果が正しいかなどは不明です。)

間違った例は右から、「秋の季節ははっきりしています。」「冬はとても新しいです。」「軽いバス停まで歩いてください。」と翻訳された。

実装概要

実装は以下の通りです。実装にかかった期間は2.5~3.5日程度です。

  1. Google Colaboratory上でモデルを設計
  2. GCPにモデルを移行し、APIサーバを構築
  3. iOSアプリでをサーバと連携

アーキテクチャ図は以下の通り。

実際の運用は2と3のみ使用します。
ソースコードはgithubに公開しています。

1.Google Colaboratory上でモデルを設計

コーパスを探す

まず、これがなければ実装の元も子もありません。今回用いたのは、TUFS Asian Language Parallel Corpus(https://github.com/matbahasa/TALPCo ) です。タイ語意外にも、複数のアジア圏の対訳コーパスが用意されています。さらにありがたいことに、Token化済みのデータも用意されています。

Transformerによる翻訳

翻訳はTransformerというモデルをベースに行いました。
自分の理解で簡単に説明すると、従来の翻訳で用いられていたRNNなどの再帰計算層がなく、代わりにAttention(入力に対して注目部分を特徴づける)や、Positional Embedding(データの位置関係埋め込み)で、翻訳を実現しているものです。間違っていたらすいません。

Transformerの論文の理解は、Ryobotさんの論文解説 Attention Is All You Need (Transformer) を読むと、とてもわかりやすいです。

Transformerの実装に関しては、詳解ディープラーニング 第2版 ~TensorFlow/Keras・PyTorchによる時系列データ処理~を参考にしました。

自分含め、「そもそもAttention?なんじゃそれ?」って思った人は、ソニー株式会社小林さんの説明する動画がアニメーション付きで理解しやすいです。

Transformerの実装

Pytorchで実装しました(ソースコード)。(先ほどの本を参考にした部分が多いです)
コードが長いので、流れを簡単に内容を書くと、

  • 学習時
    1. 入力データ埋め込み→Positional Embedding→エンコーダ層(Self-Attention→Feed Forward)
    2. 教師データ埋め込み→Positional Embedding→デコーダ層(Self-Attention→Feed Forward→Source-Target-Attantion(ここでエンコーダの出力を入力)→Feed Forward)
  • 検証時
    入力データ埋め込み→エンコーダ層→デコーダ層→出力をさらにデコーダ層→…として行き、順々にワードを生成

と言う過程を経るものです。
この流れは、Attention is all you need論文にある図1そのものです。

パラメータはいくつか試した上で、

1
2
3
4
エンコーダ,デコーダの層数:7
Multi headattentionでの接続数:4
中間層の次元:20
フィードフォワード層の次元:16

と設定しています。

エポック数は200程度回しました。Colabの関係上、90分ごとにランタイムが切れるため、その度にモデルを保存して学習しています。コード上のepcoh数が少ないのはそのためです。

2. GCPにモデルを移行し、APIサーバを構築

翻訳をGCP上のサーバーに移す

Google Colaboratory上で学習したモデルのパラメータを保存し、

1
2
3
4
5
6
7
model = Transformer(...).to(device)
"""

学習

"""
torch.save(model.state_dict(), '保存ファイル名')

その後、サーバー上に同様のモデルのインスタンスを作成し、読み込むだけ。

1
2
3
model = Transformer(...).to(device)#インスタンス作成
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.load_state_dict(torch.load('保存ファイル名',map_location=device))

上記プログラム中のmap_location=deviceで、cpu/gpuの切り替えも行ってくれるようです。このへん詰まるかと思っていましたが、すんなりcpu上で動いたので驚きました。

辞書を引き継ぐ

入力されたTokengは、全てVocabularyインスタンスにより保存していますが、生成ごとに内部の辞書の順番が変わるため、Google Colaboratoryで学習に使用した辞書をそのままGCP上のサーバーに引き継ぐ必要があります。
これに関してはVocabularyインスタンスをごっそりpandasでpickleファイルとして保存し、サーバー側で読み込んで使用しています。

1
2
ja_vocabulary=pd.read_pickle("ja.pkl") 
th_vocabulary=pd.read_pickle("th.pkl")

リクエストに対して翻訳結果を返答する

FlaskでPOSTが来たときに翻訳を実行するようにしました。translate_from_japanese()は入力のToken化、出力をBOS~EOS間で区切るための関数です。学習データの文章は既ににToken化されていましたが、生成時の入力文章はToken化されていないため、変換する必要があります。Token化にはnagisaを使用しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def translate_from_japanese(text):
if(text is None):
return "入力されていません"
else:
preds = model(torch.LongTensor([ja_vocabulary.encode(nagisa.tagging(text).words)]))
_out = preds.view(-1).tolist()
out = ' '.join(th_vocabulary.decode(_out))
begin = out.find('<s>')
end= out.find('</s>')
return out[begin+3:end]

@app.route('/ja2th',methods=['POST'])
def ja2th():
if request.method == 'POST':
text = request.get_data()
text=translate_from_japanese(text)
return text

3. iOSアプリでをサーバと連携

iOSアプリはコードを見てわかるように、特に小難しいことはしておしません。POSTの送信とその返答結果の表示を、ボタンを押すことで実行。また、それっぽいアイコンを設定しているだけです。Xcodeで実装しています。

リクエスト時のサーバー出力

1
2
3
['<unk>', '<unk>', '<unk>', '<unk>', '<unk>', '<unk>', '山田', 'さん', 'は', 'どこ', 'に', 'い', 'ます', 'か', '<unk>', '<unk>']
['山田', 'さん', 'は', 'どこ', 'に', 'い', 'ます', 'か']
<s> คุณ ยามาดะ อยู่ ที่ ไหน ครับ </s> ครับ </s> ค่ะ </s> อัน ไหน ครับ </s> เป็น อยู่ ห้อง ไหน ครับ

終わりに

今回はとりあえず完成させることをにしていたため、いくつかステップを飛ばしている部分があります。本システムはコーパスさえあれば他の言語にも転用可能なシステムのため、また、国外にいく機会があれば検証などをしっかり行いながら改良していきたいと思います。

個人的な感想として、Transformerの概要を読んだだけではよくわからなかった、実際に学習させるとどのような特徴を捉えるのかなどを見れたことがよかったです。
(時間的制約の上に、タイ語が全然読めないため、出力の正しさの判断がぱっと見じゃ全然わからなかったことがとても大変でした..)