豪鬼メモ

一瞬千撃

対訳電子書籍その2 小説の全文を機械翻訳にかける

任意の英文から、その文毎の対訳を収録した電子書籍を作る計画の概要を前回書いた。英文を文毎に分割する仕組みは既にできたので、今回は文毎の対訳を作ってみる。といっても、機械翻訳APIに原文を投げて、結果を収録するだけだ。


機械翻訳はChatGPTやGeminiなどの汎用LLMに投げた方が高い精度が出るのだが、コスパを考えると翻訳専用サービスを使った方が良い。そこで、定番のDeepL翻訳とGoogle翻訳コスパを比較してみよう。DeepLだと、月1000円のStarterプランで、10MBまでのファイルを5ファイル翻訳できる。文庫本1冊くらいの小説だと500KBから1MBくらいなので、10冊をバッチにして処理すれば、1000円で50冊の対訳本が作れる計算になる。Googleだと、Cloud Translation APIは100万文字あたり20ドルなので、10冊やると100ドルから200ドルくらいかかってかなり割高だ。ただし、DeepLもGoogleも初回無料キャンペーンがあるので、初回に限ってはタダで行ける。あと、精度は少し落ちるし動作に時間はかかるが、GoogleスプレッドシートのGoogleTranslate関数でちびちび翻訳していくという手もある。

基本的には原文を読んで、訳文はつまづいた時に読解の足しにするという目的のためにあるだけなので、翻訳精度はそれほど重要ではない。とはいえ、低すぎても困るので、翻訳精度もいちおう確認しておこう。Anne of Green Gablesの冒頭の文を訳してみる。

原文:
Mrs. Rachel Lynde lived just where the Avonlea main road dipped down into a little hollow, fringed with alders and ladies’ eardrops and traversed by a brook that had its source away back in the woods of the old Cuthbert place; it was reputed to be an intricate, headlong brook in its earlier course through those woods, with dark secrets of pool and cascade; but by the time it reached Lynde’s Hollow it was a quiet, well-conducted little stream, for not even a brook could run past Mrs. Rachel Lynde’s door without due regard for decency and decorum; it probably was conscious that Mrs. Rachel was sitting at her window, keeping a sharp eye on everything that passed, from brooks and children up, and that if she noticed anything odd or out of place she would never rest until she had ferreted out the whys and wherefores thereof.

人間(松本侑子)訳:
レイチェル・リンド夫人は、アヴォンリーの街道が、小さな窪地へとゆるやかに下っていくあたりに住んでいた。まわりには、ハンの木や、淑女の耳飾りと呼ばれる野生のフクシアの花がしげり、そして、カスバート家の古びた屋敷のある森から流れてくる小川が横切っていた。この小川も、森の奥深くの上流には思いがけない深い淵や滝があり、曲がりくねっていたり荒々しかったりするという話だが、リンド家の窪地に流れつく頃には、静かで、まことに行儀のよろしい、可愛らしいせせらぎになっていた。というのも、リンド夫人の家の前を通る時には、川の流れですら、体裁とか礼儀作法を忘れるわけにはいかなかった。何しろ彼女は、いつも窓辺にすわって、小川から子どもにいたるまで、通るものすべてに目を光らせていて、どこか妙だとか、しっくりこないと感じたら最後、どうしてそうなのか、わけをつきとめずにはいられない。多分、小川の方でも、それを心得ているのだろう。

DeepL翻訳:
レイチェル・リンデ夫人 レイチェル・リンデが住んでいたのは、アヴォンリアの幹線道路が小さな窪地に差し掛かったあたりで、アルダーとレディース・イヤードロップに縁取られ、カスバート旧居の森に源を発する小川が横切っていた; その小川は、以前はその森を流れる、淵や滝に暗い秘密を秘めた、入り組んだ、真っ逆さまの小川だったと評判だったが、リンデの谷に達する頃には、静かで、よく流れる小川になっていた。レイチェル夫人が窓辺に座って、小川や子供たちから上まで、通り過ぎるものすべてに鋭い目を光らせていること、そして何か奇妙なことや場違いなことに気づいたら、その理由と原因を突き止めるまで決して休まないことを、おそらく意識していたのだろう。

Google翻訳:
レイチェル・リンド夫人は、アヴォンリーの幹線道路が小さなくぼみに落ち込んでいる場所に住んでいた。ハンノキとレディースイヤードロップで縁取られ、古いカスバートの家の森の奥に源を発する小川が横切っている。 この川は、森の中を流れる最初のコースでは、プールと滝の暗い秘密を備えた、複雑で真っ直ぐに流れる小川であると評判でした。 しかし、リンドズ・ホローに到着する頃には、それは静かでよく流れた小さな川になっていた。礼儀と礼儀を無視して、小川ですらレイチェル・リンド夫人の家のドアを通過することはできなかったからである。 おそらく、レイチェル夫人が窓辺に座って、小川や子供たちに至るまで、通り過ぎるすべてのものに鋭い目を光らせており、何か奇妙なことや場違いなことに気づいたら、それを見つけるまで決して休まないことを意識していたのでしょう。 その理由と理由。

ChatGPT:
Mrs. Rachel Lyndeは、Avonleaのメインロードが少しくぼんだ場所に住んでいました。そこはオルダーやレディーズ・イヤードロップで縁取られ、かつてのキュスバートの場所の森から流れ出る小川によって横切られていました。その小川は、かつての森の中で複雑で急流だったと言われており、プールや滝の暗い秘密を抱えていました。しかし、Lyndeの谷に達する頃には、静かできちんとした小川に変わっていました。なぜなら、小川でさえもMrs. Rachel Lyndeの家の前を適切に通り過ぎないと、礼儀正しさと品位を欠いていると感じたかもしれません。おそらくMrs. Rachelは窓辺に座り、小川や子供たちなど、通り過ぎるすべてのものに鋭い目を光らせており、何か奇妙で場違いなことに気付いた場合、それがなぜそうなのかを解明するまで休まないだろうという自覚があったのでしょう。

Gemini:
レイチェル・リンド夫人は、アヴォンリー本街道がわずかに窪地へと下った場所に沿って住んでいました。そこはハンノキと「貴婦人の耳飾り」の花で縁取られ、古いカスバート家の奥の森に水源を持つ小川が流れていました。この小川は森の中の上流では、入り組んで激しく流れ、暗い淵や滝の秘密を湛えていると言われていました。しかし、リンド窪地に辿り着く頃には、静かで礼儀正しい小さな小川になっていました。小川でさえ、礼節と作法に敬意を払わずにレイチェル・リンド夫人の家の前を通り過ぎることはできなかったのです。おそらく小川は、レイチェル夫人が窓際に座り、小川から子供たちに至るまで、通り過ぎるもの全てを鋭い目で見張っていることを意識していたのでしょう。そして彼女が何か奇妙なことや場違いなことに気づけば、その理由と事情を徹底的に探り出すまで決して休まないだろうことも。

DeepLは流暢な結果を出そうと努力してくれるのはいいのだが、文や節の一部が消滅する癖がある(上記では "for not even a brook could run past Mrs. Rachel Lynde’s door without due regard for decency and decorum" が無視されている)。一方でGoogleは訳文の表現がいかにも機械的であるものの、逐語的に理解しやすいので、読解の補助としては望ましい。ChatGPTはGoogleよりさらに直訳調だ。驚くべきはGeminiで、やたら流暢で、かつ読みやすい結果になっている。原文と和訳文の出版物そのものを学習しているんじゃないか思うくらい、というかむしろ人間が入ってるんじゃないかと思うくらい立派な結果だ。ここまで来ると、もう翻訳サービス要らないんじゃねと思えてくる。この例以外にも試したが、長い文だと特にGeminiは他より優れた結果を出してくる傾向がある。

精度を考えると、将来的にはGeminiを使うことになるだろうが、今回は簡単のためにGoogle翻訳APIを使うことにした。直訳調なのが都合がいいし、それでいて読解に十分な程度の流暢さがあるからだ。また、翻訳専用のAPIの方が後処理が楽だ。翻訳から電子書籍の生成までの過程を自動化するので、後で翻訳データを差し替えるのは容易なはずだ。現時点ではとりあえず通しで動かすことを優先する。

前回文分割した文学作品の翻訳結果はこちらに置いておく(ファイル名に-translated-gtranがついているもの)。「%%」で始まる行は、直前の行の内容を翻訳したものである。このデータをテキストファイルで開いて普通に上から読むだけでも十分に小説を楽しめるだろう。ただ、原文と和訳が同じ様に目に入ってくるので、原文だけに集中して読むのには向かないかもしれない。そこで、UIデザインの力が必要になってくる。原文を目立たせて、和訳は必要な時のみに目に入るようにしたい。それについては次回だ。Kindle用のEPUBモドキ形式に変換してからKindle Previewer3でmobiに変換して、Kindleで読めるようにしよう。

予算と時間の関係で今回は使わなかったが、LLMにコンテキストを与えるとさらに翻訳精度が向上する(上記のディレクトリでファイル名に-translated-geminiがついているものを参照)。few-shotの例として、前のいくつか文とその翻訳を与えればいい。例えば10個目の文を翻訳する際には、7個目から9個目までの文とその翻訳結果を例として与えるのだ。また、文体を調整する指示を与えることもできる。それらを勘案すると、以下のようなプロンプトになる。

Translate the given English sentence into Japanese.
Use assertive speech rather than polite speech in the translation.
The speech style must be gender-neutral; Use 私 rather than 俺 and 僕; Don't use わ and わよ.
Apply literal translation rather than liberal translation.

Sentence: I live with Mr. Tanaka and Mrs. Johnson in Calif. U.S.
Translation: 私は田中さんとジョンソン夫人と一緒に合衆国のカリフォルニアに住んでいる。

Sentence: "Call me after work." she said.
Translation: 「仕事の後に私に電話してね」と彼女は言った。

Sentence: When I was young, my mother was kind;
Translation: 私が若い頃は母は優しかった。

Sentence: “I’ll just step over to Green Gables after tea and find out from Marilla where he’s gone and why,” the worthy woman finally concluded.
Translation: 「お茶の後にグリーン・ゲイブルズに行って、マリラから彼がどこへ行ったのか、なぜ行ったのかを聞いてみよう」と、この立派な女性はついに決心した。

Sentence: “He doesn’t generally go to town this time of year and he never visits;
Translation: 「彼は普段、この時期に町に行ったり、訪問したりすることはない。

Sentence: if he’d run out of turnip seed he wouldn’t dress up and take the buggy to go for more;
Translation: カブの種がなくなったとしても、着飾って馬車に乗って買いに行くようなことはしない。

Sentence: he wasn’t driving fast enough to be going for a doctor.

1番目から3番目の例は固定だ。地の文では敬体でなく常態を使うことや、会話文では適当に役割語を使うことや、セミコロンも句点で扱うことを例として示している。役割語は適宜使って欲しいのだが、男女の判定が間違うと読みにくい訳文になるので、性別に依存するものは使わないように指示している。4番目から6番目までは、今まさに訳そうとしている文の前に処理した結果を追加している。こうすることで文体が安定する。時制の扱いや代名詞の扱いも安定する。いま訳そうとしている文の後続の文も与えた方がいいかもしれないし、コンテキストのウィンドウサイズもある程度大きい方が望ましいかもしれない。ただし、入力を増やすとコストも増えるし、LLMの記憶能力の限界もあるので、コンテキストを増やせばいいってものでもない。現実的には、前の文を3つくらい与えれば十分だろう。

コンテキストが効いていることが明らかに分かる例を挙げよう。一つ目の文で現れる"sing"が、二つ目の文で訳出されている。

“The little birds sang as if it were 「小鳥たちはまるで
The one day of summer in all the year.” 一年でたった一日の夏であるかのように歌った」

もう一つ例をあげよう。二つ目の文で「目」について言及できているのは一つ目の文をコンテキストとして与えたからこそだ。

If my eyes were strong I could stay here and make out to look after things and manage, with a good hired man. 目が丈夫なら、ここに残って、良い雇い人を雇って、家事や管理をなんとかできるんだけど。
But as it is I can’t. でも、この目ではできない。

プロンプトにはまだまだ工夫の余地があるだろうから、もうちょい研究してから使うようにする。赤毛のアンを訳す際には「The sentences are from the story "Anne of Green Gables".」とか指示してもいいかもしれない。戦記物を訳すなら、「The sentences are from a military story.」とか書いてもいいかもしれない。固有名詞の訳出方法のリストを明示してもいいかもしれない。プロンプトは凝り出すとキリがないので、やはり最初は精度は気にせずに安い方法でやって、追い追い改良するのがいいだろう。試しに、幼女戦記の英語版をgeminiに訳させる際に、「The sentences are from a novel 幼女戦記 so utilize militery terms properly in the translation.」とか指示したら、めっちゃうまいことやってくれるようになった。青春ブタ野郎とかも、作品名をプロンプトに指示するだけで固有名詞の漢字の選択の精度が劇的に向上する。作品についての知識があるのが、従来の翻訳機と一線を画するところだ。

著名な作品の場合、モデルが内容を覚えているっぽい。以下のプロンプトを見るとわかるが、文を与えるだけでどの作品のものなのか言い当ててくるので、特徴的な文であれば、前後の文を与えなくても文脈を勝手に把握してくる可能性がある。著名作品の翻訳精度が妙に高く感じるのはそのためかもしれない。アンやローラのセリフがどういうわけか女言葉に訳される傾向があるのを不思議に思っていたのだが、おそらく正解を知っているのだ。

余談だが、複数の翻訳機の結果を比較する場合、スプレッドシートに取り込むのが常套手段だ。私はGoogle Spreadsheetを使っているのだが、それにプレーンテキストファイルやTSVファイルを取り込む際にたまに踏む地雷がある。各セルの値の先頭にASCIIの二重引用符があると発狂するのだ。というより、おそらくこれは仕様だ(どこにも明記されてないけど)。以降のタブや改行を特殊文字として扱わないという挙動に切り替わって、所望の挙動をしなくなってしまう。それを防ぐには二重引用符を4つ重ねて「""""」にエスケープするという裏技があるのだが、どうにもダサい話だ。さて、多くの英文の出版物では二重引用符の表記はASCIIを使った「"We should kiss."」ではなく、UnicodeのU+201CとU+201Dを使った「“We should kiss.”」が使われている。その場合は上述の問題は起きないのだが、Project Gutenbergとかで素人が打ち込んだデータだと、ASCIIの二重引用符が使われていることがあり、それを忘れてインポートするとデータが壊れてしまう。エスケープするのもいいのだが、体裁を合わせるためには全角にU+201CとU+201Dに変換するのが望ましい。そうすると、問題になるのは、左引用符と右引用符を区別しなきゃいけないことだ。いろいろ試した結果、以下のヒューリスティックで十分に機能するっぽい。

for line in lines:
  # 行全体が引用符で囲まれている場合
  line = regex.sub(r'^"([^"]+?)"', r"{{ABC}}\1{{DEF}}", line)
  # 単語を直後に持つ引用符と単語または区切り文字を直前に持つ引用で囲まれている場合
  line = regex.sub(r'"([a-zA-Z0-9][^"]*[a-zA-Z0-9.!?;,]?)"', r"{{ABC}}\1{{DEF}}", line)
  # 行頭が二重引用符の場合は左引用符
  line = regex.sub(r'^"', r"{{ABC}}", line)
  # 単語を直後に持つ引用符は左引用符
  line = regex.sub(r' "([A-Za-z0-9])', r" {{ABC}}\1", line)
  # 行の中に二重引用符が二つある場合
  line = regex.sub(r'"(.*?)"', r"{{ABC}}\1{{DEF}}", line)
  # それ以外は右引用符
  line = regex.sub(r'"', r"{{DEF}}", line)
  # プレースホルダを実際の引用符に置換
  line = regex.sub(r"{{ABC}}", "“", line)
  line = regex.sub(r"{{DEF}}", "”", line)
  ...

さらに余談だが、会話文では完全に文単位にすると細かすぎて読みにくいことが多い。例えば "Yes. Go ahead." というのは "Yes." と "Go ahead." に区切るのが率直だが、それだと細かすぎる。よって、会話文の中でのみ、連続する2文の片方が16文字以下の場合か、双方を合わせて32文字以下の場合は、両者をくっつける処理を入れた。他にも詩などで同一文なのに改行が入って文が区切られて見えるものを復元したり、話者が同一の連続した会話文なのに段落が区切れているものを単一の段落にまとめたりといった工夫を入れている。他にも、固有名詞の表記揺れを辞書を使って直したり、「てよ」「だわ」言葉を中性的な表現に直すなどの涙ぐましい後処理も入れている。

まとめ。いくつかの文学作品の全文をGoogle翻訳API機械翻訳してみた。翻訳精度はGeminiの方が良いが、Google翻訳でもとりあえず読解の参考として問題ない程度の品質にはなっているので、対訳電子書籍のデータとしては十分だ。