
DeepSeek-R1を使って中国語の判決書から情報抽出してみる
良くも悪くも話題になっている大規模言語モデルDeepSeek。高い推論能力を持ちつつも軽量なので、非力なマシンのローカル環境でも動かせるとのこと。中国語で書かれた判決書からの情報抽出をやらせてみよう、と思いつき早速やってみました。
経緯とやりたいこと
LLMに判決書の情報抽出をやらせること自体は決して新しいアイディアではないのですが、性能いいモデルをAPI経由で使うとお金かかるし、フリーのモデルだと応答不安定だったりするしで、自分では手を付けられていませんでした。
そんなわけでフリーかつローカル環境で完結させたいけど、従来のモデルだと非力なマシンでは処理が遅すぎて話になりません。そこにオープンウェイト、フリー、軽量と三拍子揃ったDeepSeekが舞い降りたので、重い腰を挙げた次第です。以下、レシピのメモ。
使用したモデル
Deepseek-R1から蒸留された小型のモデルを使います。
deepseek-r1: 7b
パラメーター数がこれ以上大きいモデルだと応答がかなり遅いので、自分の環境だとこの辺が限界かなと。もっと良いマシンを持っている人ならよりパラメーター数の大きなモデルが使えるかもしれません。
注意書き: このパラメーター数のモデルで扱えるのは主に中国語です。ちょっとチャットしてみると分かると思いますが、このモデル中国語はペラペラですが、日本語はまともに返ってきません。日本語を扱うタスクを処理するのは難しそうです。
環境と主なツール
マシン: Macbook Air M2
使用言語: R 4.4.2
開発環境: RStudio
主要なツール・パッケージ: Ollama, ROllama
下準備
Rollamaのセットアップと基本的な操作
R環境の構築とOllamaのインストールは済んでいるものとして進めます。
まず、使いたいモデルをOllamaに入れておきます。今回はdeepseek-r1:7bを使います。他のパラメータ数のモデルは下記参照。
Ollamaで公開されているDeepseek-R1モデル一覧
ollama pull deepseek-r1:7b
RからOllamaを介してLLMを扱うためにRollamaをインストール。
install.packages("rollama")
初回インストール時にデフォルトで使用するLlamma3をダウンロードするか聞かれますが、今回は自分で用意したモデルを使うのでこれはNoで大丈夫。ストレージに余裕ある人は入れておいてもいいでしょう。
RからOllamaサーバーへアクセスできるかチェック。
library(rollama)
ping_ollama()
▶ Ollama (v0.5.7) is running at <http://localhost:11434>!
もし動いていなかったら、ターミナルでOllamaを起こしに行きます。
ollama serve
Rollamaがデフォルトで呼び出すLLMを指定し、呼び出します。
options(rollama_model = "deepseek-r1:7b")
pull_model()
これでRからOllamaを介してdeepseek-r1を呼び出せるようになりました。
判決書のデータセットを用意する
試しに判決書10件の事件番号と主文を格納したサンプルのデータセットを使います。
※一応、商用DBから加工・整形済みデータを取っているので公開は控えます。未加工の判決書自体は裁判文書網(中国の最高裁判所による判決書公開ウェブサイト)で閲覧・取得可能です。
df_ano_sample <- read.csv("order_sample.csv")
Deepseekによる情報抽出
LLMへのクエリを作成する
rollamaではmake_query関数でLLMへのクエリを記述します。今回はとりあえず動くか試すので、インストラクションも最低限、例示なしのZero-shotでのアノテーションをする場合のクエリを参考にしました。
rollamaの開発者によるアノテーションガイド
ano_zsq <- make_query(
text = df_ano_sample$order,
template = "{prefix}{text}\n{prompt}",
prompt = "必须只用数字回答赔偿金额,如果无法确定赔偿金额回答NA",
system = "你是一位优秀而且做事谨慎的律师助理。你的任务是从判决书提取被告要承担的金额。",
prefix = "以下是你要分析的判决:"
)
ここで与えているタスクは、裁判官が判決主文で被告に命じた賠償金額を答える、というものです。
これ正規表現で数字だけ拾って合計すればよさそうですが、判決主文では費目が複雑だったり、足し引きがあったりと、単純な数値の足し引きだと、うまくいかない場合が多いです。さらに、原告に訴訟費用の負担を命じている場合もあるので、原告と被告のどちらに支払いを命じているのか判定する必要があります。というわけでLLMの出番です。
あと、自分は中国語ネイティブではないけので、プロンプトがちょっと怪しいかもしれないけど、deepseekからの出力見る感じ伝わっているようなのでヨシ!
LLMにクエリを投げる
query関数で先ほど定義したクエリをDeepseekを投げて、その回答をデータフレームに保存します。
df_ano_sample$compAmt <- query(ano_zsq, output = "text")
# 以下実行結果の概要
✔ pulling manifest [45ms]
✔ verifying sha256 digest [19ms]
✔ writing manifest [30ms]
✔ model deepseek-r1:7b pulled succesfully
自分の環境(Macbook Air M2)では1, 2分くらいで処理が終了しました。さらに250件くらいまで増やすと数十分待ちに。より大きなデータセットから情報抽出をするときは、寝る前に仕込んだりすることになるかも。
回答テキストのクリーニングと変換
出力に思考過程(<think></think>で囲まれた部分)や改行文字などが含まれているのでそれらを取り除いて、最後に数値部分だけ取り出します。ちょっとコードが汚いのは見逃して。
# remove think progress and control code
df_ano_sample$compAmt_cleaned <- gsub("<think>.*?</think>", "", df_ano_sample$compAmt)
df_ano_sample$compAmt_cleaned <- gsub("\n", "", df_ano_sample$compAmt_cleaned)
df_ano_sample$compAmt_cleaned <- gsub("\\\\", "", df_ano_sample$compAmt_cleaned)
# parse to number
df_ano_sample$compAmt_parsed <- parse_number(df_ano_sample$compAmt_cleaned)
結果の検証
結構長いので、1件目の判決書での判決主文とそれに対する回答をサンプルとして載せておきます。
判決主文
被告四川内江舟诚电源有限公司于本判决生效之日起十日内赔偿原告王某损失27250元(其中补助费9600元,营养干预费13650元,复查、检测及差旅费4000元)和给予一次性奖励金4500元,合计31750元。
如果未按本判决指定的期间履行金钱给付义务,应该按照《中华人民共和国民事诉讼法》第二百五十三条规定,加倍支付延迟履行期间的债务利息。
案件受理费594元,由被告四川内江舟诚电源有限公司负担。
被告は原告に対して4つの費目の合計として31750人民元を支払うように命じられています。最後に出てくる594人民元は被告が裁判所に支払う費用なのでこれは含めてはいけません。したがって"31750"と回答できれば正解です。
Deepseekからの回答
── Answer from deepseek-r1:7b ────────────────────────────────────────────
<think>
好,现在我要分析一下这个判决书,找出被告需要承担的赔偿金额。
首先,判决书中提到原告王声称因被告的原因导致损失,赔偿金额包括几个部分:补助费9600元、营养干预费13650元和复查、检测及差旅费4000元。把这些加起来的话是9600+13650=23250,再加上4000就是27250元。
接着还有4500元的一次性奖励金,这部分需要算进去吗?根据通常的赔偿项目,奖励金通常是作为补偿的一部分,所以应该包括在内。那么总赔偿金额应该是27250+4500=31750元。
另外,判决书中还提到了如果被告没按时支付赔偿,可能会加倍支付利息,但这里只需要计算实际应支付的赔偿金额,这部分不影响数字结果。
案件受理费594元由被告负担,这部分是诉讼费用,并不构成赔偿金额的一部分,所以不需要加到赔偿里。
综上所述,被告需要承担的赔偿金额是31750元。
</think>
31750
この判決主文については賠償金の合計値を正確に答えられています。
正答率
残りついても正確に金額を答えられているか検証しました。正解していたのは10件中9件。想像以上にいい感じです。賠償金支払いを命じている場合には正しいその金額を算出できていて、もし未記載または請求棄却の場合にはNAと答えられていました。
誤りのあった1件では原告の人数を数え間違えていました。これは賠償金の中身がトリッキーで、原告5人に対する財産上の損失に関する賠償と原告6人に対する慰謝料の組み合わせでした。Deepseekの回答では慰謝料についても5人で計算してしまっていましたが、この手のミスは人間でもやらかしそう…
分かったことと今後の課題
deepseek-R1は非常に少ないパラメーター数のモデルでも、中国語で書かれた判決書からの情報抽出に使えそうです。ただし、10件だとサンプル数が少なすぎるので、あくまでも現時点での感触ということで。対象データセット全体の30%とかでテストするのがいいかもしれないけど面倒です。(やるしかない)
確実に正確な抽出ができるようであれば、大量の判決書から関心のある観察値を取り出して色々な検証が可能になるのでワクワクします。
残された問題は大きく2つ。
(1)エラーをどうやって防ぐ
(2)正誤の検証をどうするか
(1)エラー防止の手立てとして、プロンプトを工夫したり、Few-shotにしたりすれば低減できるかも。究極的には、もっといいモデルを使うのが一番いいかもしれません。いずれにせよ良いマシンを買うか、APIをガンガン叩けるだけの研究資金を確保する必要があるので、今後科研費やら研究助成やらに頑張って応募したいところです。
(2)正誤の検証部分をすべて人の手でやっていたら情報抽出を自動化した旨味は減ってしまうので、ここもできるだけ自動化したいところ。これに関しては専門家の判断をLLMに行わせることを目指すLLM-as-a-Judgeの枠組みやツールが役立ちそうです。ちょっと見た感じですが、ここまでやってきた情報抽出のプロセス自体もこの枠組みへ落とし込んで設計し直すのが王道な気もしてきます。
また進展あればnoteに書こうかなと思います。