翻訳エンジンの構築(Marian-NMT)

夏休みの宿題(?)としてMarian-NMTを使って翻訳エンジンを構築してみた。構築した翻訳エンジンは「英語→日本語翻訳(https://devneko.jp/demo/)」から試すことができる。訳すべき単語を飛ばすなど深層学習な機械翻訳特有のミスをすることも多いが、個人が試作したものとしては相応の性能な気がする。割と訳せていて正直驚いた。ただ、入力文の適切な分割など前処理的な事をほとんどやっていないので変な結果になることも多い。

データセットは「WEB クロール + Universal Sentence Encoderで収集したデータセット」+「Free(Creative Commonsライセンスなど)のデータセット」、使用した手法はTransformer + sentence pieceであり、バリバリのDeep Learningで現時点でも本格的である。ただし、環境制約(というか時間制約&予算制約(後述)からBack Translationは使えていない。)

上記エンジンでは300文字以内の英語文(複数文の場合性能は落ちる) [1] を日本語に翻訳することができる。訳抜けを防止するモードもあるが、カンマや記号で文を分けているだけなので訳抜け防止モードの性能はあまりよろしくない。

翻訳エンジンを作った理由

本当は日本語でGPT-2辺りのfine tuningを試そうと思っていて「Faster than training from scratch — Fine-tuning the English GPT-2 in any language with Hugging Face and fastai v2 (practical case with Portuguese)」という素晴らしい記事を読んでいた。その中に「For example, to obtain a Portuguese GPT-2, we could download from the Transformers library of Hugging Face the OpenAI GPT-2 pre-trained in English and the MarianMT translator」という記載があったものの、残念ながら「日本語→英語」のモデルは公開されているが「英語→日本語」 のモデルは公開されていなかった[2]。

自由に使える翻訳エンジンは役に立ちそう[3]なので自分で構築することにした。車輪の再発明ではあるが、色々と良い経験になったと思う。

翻訳エンジンの作り方

翻訳エンジンを作るにはデータセット、学習用ソフトウェア、学習環境が必要である。今回は下記を用いた。

  1. データセット: WEBをクローリングして収集[4]+Freeで公開されているものを追加 [5]
  2. 学習用のソフトウェア: Marian-NMT (transformer) + sentencepiece [6]
  3. 学習環境: AWS p3.2xlarge インスタンス [7]

データセットの作成

以下のフローで対訳データを作成した。Universal Sentence Encoderは極めて強力である。

  1. ①英語の原文から作成されたっぽい日本語のページ[8]、②対訳が載っていそうな日本語のページ[9] をクロールする。
  2. ①の原文ページを抽出、取得する。
  3. ①と原文ページから日本語文・英語文を抽出する。
  4. Universal Sentence Encoderでembedしたベクトルのcos距離&文の位置が一定水準で近い文をペアにする。2文組み合わせたほうが良い場合もあるため、2文ずつをペアにしたembedも行う。(1文の英語を2文に分けて和訳する例も多いため。)
  5. 文のペアのうち最も距離が近く形態素数も近いものを対訳と考える。(本当は日本語→英語翻訳したうえで単語数を比較したかったが計算時間がかかるため諦めた)
  6. ②について、3.4.5.相当の処理を行う。

重要な処理は4.で以下のように実行した。文の位置に注目するため最初にoffsetを求めている。この辺りは画像のマッチングに近い。「Twitterに投稿」のような記載があるとoffsetが変になるので最初にフィルタリングするなど工夫をしている。(完全なソースは後日公開する(かもしれない))

embed = hub.load("https://tfhub.dev/google/universal-sentence-encoder-multilingual/3")

def get_align(ja_sents, en_sents, threshold=0.4, decay=0.1):
    ja_vec = embed(ja_sents)
    en_vec = embed(en_sents)
    offsets = []
    for jidx, jv in enumerate(ja_vec):
        max_sim, ofs = max([(cos_sim(jv, ev), eidx - jidx) for eidx, ev in enumerate(en_vec)])
        if max_sim > threshold:
            offsets.append(ofs)
    if len(offsets):
        offset = sum(offsets) / len(offsets)
    else:
        offset = 0
    ret = []
    jidx = 0
    while jidx < len(ja_vec):
        jv = ja_vec[jidx]
        max_sim, eidx = max(
            [((1.0 - min(abs(eidx - (jidx + offset)) * decay, 1.0)) * cos_sim(jv, ev), eidx) for eidx, ev in
             enumerate(en_vec)])
        if max_sim > threshold:
            ret.append((max_sim, ja_sents[jidx], en_sents[eidx]))
        jidx += 1
    return ret

これらの処理によって、200万ペア程度が収集できた。上記データセットに「Japanese-English Subtitle Corpus [10] を4. 5.と同様にクリーニングしたもの」「京都フリー翻訳タスク (KFTT) [11]のデータ 」「Tanaka Corpus[12]」を加え、学習データ [13] には全400万ペア(日本語420MB, 英語380MB)を用いた。

学習(Marian-NMT)

Marian-NMTは非常に良いソフトウェアであり、ドキュメントとExampleが充実している。そのため学習はサンプルに沿って行えば問題ない。

  1. Marian-NMTのドキュメント、Installationにそってインストールを行う。ここで、sentencepieceを使うために「-DUSE_SENTENCEPIECE=on」を設定する必要がある。また、CPUバージョンでテストする場合は「-DCOMPILE_CPU=on -DCOMPILE_CUDA=off -DUSE_SENTENCEPIECE=on -DCOMPILE_SERVER=on」という感じでmarian-serverを含めておくと後々便利である。
  2. モデルを作成するため、marian-exampleを参考に諸設定を行う。作りたいモデルに近いexampleをコピーし、データを配置、run-me.shを修正するのが楽である。(今回作成した)モデルのハイパーパラメータはHelsinki-NLP/OPUS-MT-train [14]を参考にした。
  3. ./run-me.sh して待つ。

AWS p3.2xlarge インスタンス を用いると「18,000 words / s」程度の速度で学習が進む。400万文ペアだと1 epoch 1時間程度である。「英語→日本語」モデルの構築全体には20時間程度かかった。

費用

今回の試行にかかった必要はクロール&データセット作成に100 USD、モデル構築に240 USD、計340 USD(約36,000円)程度であった。。。[15]

性能評価

sacrebleu[ja]で計測してみたところ、out sampleのKFTT(testセット)に対して、本体:23.3、KFTT trainセットでfine tuningすると31.0であった。

【fine tuning 前】
BLEU+case.mixed+numrefs.1+smooth.exp+tok.ja-mecab-0.996-IPA+version.1.4.13 = 23.3 59.0/31.8/18.5/11.0 (BP = 0.938 ratio = 0.940 hyp_len = 24790 ref_len = 26370)
【fine tuning 後】
BLEU+case.mixed+numrefs.1+smooth.exp+tok.ja-mecab-0.996-IPA+version.1.4.13 = 31.0 65.9/43.5/30.6/21.9 (BP = 0.834 ratio = 0.846 hyp_len = 22322 ref_len = 26370)

スコアはそこまで高いとは言えないが、適当な英文を入れるとそこそこの精度で日本語文が返ってくる。1週間でデータセット作成~学習、WEB化まで実施した結果としては悪くないのではなかろうか。

所感

私としては個人でそこそこの翻訳エンジンを作れること自体が驚きであった。これは研究成果やソフトウェアを公開している研究コミュニティのおかげである。 研究者・ソフトウェア開発者に感謝である。

また、時間単位で使用可能なAWSなどクラウド環境が整備されたことも大きい。非常に良い時代になったと思う。

脚注

[1] 制限はVPS的に重くないようにてきとーに決めている。そのうちモデル自体を公開しようと思っているので無制限に使いたい方はそれをお待ち下さい。
[2] 後で知ったが「JParaCrawl」は「日本語⇔英語」で構築したモデルを公開している。非商用であれば利用可能。
[3] 例えば自然言語処理における公平性は重要であり、自由に解析できる翻訳エンジンがあると研究上色々便利である 。 ([2]で良いじゃんという意見もあるが・・・) その他にも英語の言語資源を日本語化したいみたいな時にも便利かなと思う。(現状だと性能が足りていないという意見もあるが・・・)
[4] 「著作権法改正がAI開発に与える「衝撃」」にある通り、日本の著作権法ではWEBクローリングを行ってデータを集める行為は(限度はあるが)違法ではない。
[5] 追加したデータセットは「日本語対訳データ」に記載のあったもののうち、条件が非商用に限られていないものとした。
[6] もともと読んでいた記事で紹介されていたため。他にも「OpenNMT」など様々な選択肢がある。
[7] その他、クロールにt2.medium、対訳ペアの判断にr5a.2xlarge インスタンスなどを使用している。
[8] 原文へのリンクがあるニュースサイトなど
[9] イメージとして法令関連の対訳のようなものが載っているサイト
[10] JESC: Japanese-English Subtitle Corpus, 2018
[11] The Kyoto Free Translation Task, 2011
[12] Tanaka Corpus
[13] 追加したデータはCCライセンスでNCがついていないため、おそらく商用利用も可能なデータになっていると思われる。(作成したエンジンのライセンスがデータとどう関係するかは別の議論が必要)
[14] Hugging FaceでHelsinki-NLP/opus-mt-ja-enという日本語→英語の翻訳モデルを公開している方のリポジトリである。
[15] 今回は「① 英語→日本語」「② 日本語→英語」「③ ①をKFTT用にfine tuning」の3モデルを作成、環境構築を含め60時間程度インスタンスを使っている。GPUインスタンスは$4.194/時間と(コストパフォーマンスは良いが)結構高い。

その他

夏休みということでデータセット作成から翻訳エンジン構築+WEB化を一通り実施してみた。手を動かすと楽しい。性能としてもまずまずでドメインによっては他のエンジンとも良い勝負ができそうである。また、fine tuningを行えば「適度な品質の翻訳」レベルまで行けそう。(そもそもBLEUが信頼できるか、という議論はあるのだが・・・。)

現状クロールした先は技術系に偏っており、企業のプレスリリースやマニュアル、公的機関のサイトが少ない。この辺りを拡充&対訳ペアを取るプログラムを高度化すればデータセットを2-3倍に拡張することは可能な気がしている。(というかJParaCrawlが実現している。) そうなるとフリーのエンジンとしては存在感が出るのかなと思わなくもない。

技術的にもできることは多い。冒頭のBack Translation以前にパラメータのチューニングも十分ではない。とはいえ、1回1万円かかると思うとチューニングをゴリゴリやるのは気が引ける・・・。

いつも通り、今後モデルの改善を実施していくかは情熱次第といったところ。

自分で書いたコードとモデルはそのうち公開しようかな、と思っているが念のためライセンス関連を確認中。法的にはデータセットも公開可能なのではなかろうかと思っているものの、そこまで公開するかは決めていない。

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>