Mirai Translate TECH BLOG

株式会社みらい翻訳のテックブログ

LLMをファインチューニングして漢文(白文)を書き下し文にする


この記事は、みらい翻訳Advent Calendar 2023の1日目です。

こんにちは、エンジニアリング部の岩月です。社内ではwhatsと呼ばれております。

ChatGPTが公開されてちょうど1年が経ちましたが、自然言語処理業界は大規模言語モデル(LLM: Large Language Model)一色の様相です。となるとやはりLLMに関して何か記事を書きたいと思いキーボードを執りました。

何を解かせるか?

せっかく機械翻訳の会社にいるので、翻訳っぽいことをやらせてみたいと考えました。これまでの人生の中で、これが訳せたらよかったのにと思った経験を振り返ってみると、古文漢文が浮かびました。

そこで、漢文の白文を書き下し文(訓読文)にするタスクに取り組みたいと思います。

「學而時習之、不亦說乎。」を「学びて時にこれを習ふ、また説ばしからずや。」に変換するアレです。

データ

Wikisourceパブリックドメインの漢文と書き下し文が多数収録されています。これを使うことにします。

まず、日本語版Wikisourceの全データをダウンロードします。データはここにあります。

展開して中身を見てみると、XMLファイルが1つだけで、その中に全ページのデータが入っています。この中から、漢文であって、白文とその書き下し文が併記されているページのみを抽出したいと思います。

ページの抽出

漢文っぽい文字列と、書き下し文っぽい文字列が含まれているかどうかによって判定したいと思います。

漢文っぽい文字列とは何でしょうか。漢字が10文字以上続き、直後に句点が置かれていたら漢文っぽいので、これを条件にします(実際にはそれ以外も取れてしまいました)。

書き下し文っぽい文字列として、遠い記憶からひねり出したのが、「曰く」と再読文字です。再読文字は「将に~す」のようなやつです。

以上を踏まえてプログラムを書きました。言語はPythonです。

import re
import xml.etree.ElementTree as ET

namespace={'mw': 'http://www.mediawiki.org/xml/export-0.10/'}
re_kanji=re.compile(r'[一-龯、,]{10}。')
re_kundoku=re.compile(r'曰(く|ク)|[將且当応](に|ニ)|未(だ|ダ)')
tree = ET.parse(xml_path)
root = tree.getroot()
pages=root.findall('./mw:page', namespaces=namespace)
for page in pages:
    text=page.findtext('./mw:revision/mw:text', namespaces=namespace)
    match_kanji=re_kanji.findall(text)
    match_kundoku=re_kundoku.findall(text)
    if len(match_kanji) > 9 and len(match_kundoku) > 9:
        print(page.findtext('./mw:title', namespaces=namespace))

この条件で抽出されたページのタイトルを出力してみると:

老子道徳経
南洲手抄言志録
論語 (漢文叢書)/學而第一
論語 (漢文叢書)/爲政第二
論語 (漢文叢書)/八佾第三
論語 (漢文叢書)/里仁第四
論語 (漢文叢書)/公冶長第五
(以下略)

なかなか良い感じです。

白文と書き下し文の抽出

説明は省きますが、下記の処理で大体なんとかなりました。行単位での処理です。

text=text.strip()
text=text.replace('{{Header', '')
text=re.sub(r'^\|.+$', '', text)
text=re.sub(r'^==.+$', '', text)
text=re.sub(r'\{\{Ruby\|(.+?)\|.+?\}\}', r'\1', text)
text=re.sub(r'\{\{r\|(.+?)\|.+?\}\}', r'\1', text)
text=re.sub(r'\{\{[a-z]+\}\}', '', text)
text=re.sub(r'※\{\{註\|.+?\}\}', '', text)
text=re.sub(r'\{\{註\|.+?\}\}', '', text)
text=text.replace('{{re}}', '')
text=re.sub(r'\{\{\d\}\}', '', text)
text=text.replace('{{*|', '')
text=text.replace('}}', '')
text=re.sub(r'<sub>.*?</sub>', '', text)
text=re.sub(r'</?.+?>', '', text)
text=re.sub(r'^〔評〕.+?$', '', text)
text=re.sub(r'^[a-zA-Z\!-~].+$', '', text)
text=re.sub(r'[一二三四五六七八九十百〇]+', '', text)
text=text.replace('〔譯〕', '')
text=re.sub(r'[「」『』:()]', '', text)
text=re.sub(r'[!?;]', '。', text)
text=text.replace(',', '、')
text=re.sub(r'\([ぁ-ゞ]+\)', '', text)
text=re.sub(r'^\s+|\s+$', '', text)

出来上がったデータの一例です:

子謂公冶長、可妻也。雖在縲絏之中、非其罪也。以其子妻之。
子公冶長を謂ふ。妻はす可きなり、縲絏の中に在りと雖も、其罪に非ざるなりと。其子を以て之れに妻はす。
子謂南容、邦有道、不廢。邦無道、免於刑戮。以其兄之子妻之。
子南容を謂ふ。邦道あれば廢てられず、邦道無きも刑戮を免ると。其兄の子を以て之れに妻はす。
子謂子賤君子哉若人。魯無君子者、斯焉取斯。
子子賤を謂ふ、君子なるかな、若き人、魯に君子なる者無くば、斯れ焉んぞ斯れを取らんと。

白文と書き下し文が交互に来ています。

これをtrain/dev/testセットに分割しました。1,900程度のデータ数です。

実験

比較対象

今回は、LLM-jpが公開してくださっているLLMを利用します。といっても、利用の仕方にはいろいろあります。

  1. 対話形式の学習をしていない、素のLLMに白文を入力し、書き下し文を予測させる
  2. 対話形式の学習をしているLLMに、zero-shotで白文を入力し、書き下し文を予測させる
  3. 対話形式の学習をしているLLMに、one-shotで白文を入力し、書き下し文を予測させる
  4. 対話形式の学習をしていない、素のLLMを作成したデータを使ってファインチューニングし、そのモデルに白文を入力し、書き下し文を予測させる

この4つを比較してみたいと思います。

評価方法

漢文の書き下し文の形態素解析は難しそうなので、文字単位で見ることにしました。したがってchrFを使用します。

また、完全に正解しているかどうかも重要です。正解率も見たいと思います。

結果

手法 chrF 正解率
1 2.04 0.00%
2 4.20 1.01%
3 4.84 0.00%
4 28.66 6.06%

例もいくつか見てみたいと思います。

手法 出力
白文 子曰君子上達、小人下達。
書き下し文 子曰く、君子は上達し、小人は下達す。
1 子曰、君子上達、小人下達。現代語訳:子曰く、君子は上達するが、小人は下達する。概要 『論語』の中で、(以下略、max_tokensまで出力された
2 君子は上達し、小人は下達する。
3 子曰く、君子は上達し、小人は下達する。
4 子曰く、君子は上達し、小人は下達す。
手法 出力
白文 孟子曰道在邇、而求諸遠。事在易、而求諸難。人人親其親、長其長、而天下平。
書き下し文 孟子曰く、道は邇きに在り。而して諸を遠きに求む。事は易きに在り、而して諸を難きに求む。人人其親を親み、其長を長とせば、天下平かなり。
1 孟子曰く、道は在り、而して求めて遠し。事は易し、而して求めて難し。人人は其の親を親み、其の長を長とし、而して天下は平らか。概要 『孟子』の中で、(以下略
2 孟子は言った:道は常に在り、道を求めることは遠きを求めることである。事は易しいが、道を求めることは難しい。人は親を愛し、長を愛し、天下は平らかになる。
3 孟子曰く、道はここにあり、それを求めるには遠くまで行かなければならない。事は易しいが、それを求めるには難しい。人は親に従うものであり、長はその長であり、天下は平らである。
4 孟子曰く、道はに在り、而して諸を遠に求む。事は易に在り、而して諸を難に求む。人人其親を親しみ、其長を長くすれば、而して天下平なり。
手法 出力
白文 道沖而用之、或不盈。
書き下し文 道は冲にしてこれを用ふるも、或は盈ず。
1 道沖にして之を用う、或いは不てき。現代語訳:道沖にして之を用いる、或いは不てき。このように(以下略
2 道沖は、その才能を用いて、あるいは用いなかった。
3 道沖はこれを用い、あるいは用いず。
4 道沖して之を用ひ、或はせず。


まとめ

ファインチューニングしたモデルが最も良い結果を出しました。それ以外については、解説や現代語訳を出している例が見られました。
今回はプロンプトの調整は一切していないので、入力の仕方によっては手法2や3はもう少しよくなるかもしれませんが、その手間をかけるよりファインチューニングしてしまった方が早いかもしれないという感想を抱きました。

We are hiring!

みらい翻訳では、機械翻訳の精度向上や、翻訳周辺技術の開発に取り組むエンジニア・リサーチャーを募集しています!ご興味のある方は、ぜひ下記リンクよりご応募・お問い合わせをお待ちしております。

miraitranslate.com