めいふの備忘ログ

やったことを忘れないように書きこのしておくのです。

ChatGPTさんと行う述語項構造解析:「ChatGPTさんにはゼロ代名詞が見えているのか?」(GPT-3.5-turbo編)

ChatGPTさんと行う日本語述語項構造解析

枕詞

ChatGPT...ChatGPTと草木もなびくか・・・!

2022年11月に登場したChatGPTは、これまでの会話モデルとは一線を画す対話能力とプログラミングから企画のアイディア出しまでこなせる汎化能力で、大きな注目を集めています。

2023年の3月に開催された言語処理学会の年次大会では「NLP2023 緊急パネル:ChatGPTで自然言語処理は終わるのか?」(https://www.anlp.jp/topics/topic230601.html)が開催され、ChatGPTの登場に衝撃を受けた言語処理の研究者のみなさんが言語処理の未来について意見を交わしておりました。ChatGPTの登場は言語処理研究の当事者に相当のインパクトがあったということでしょう。

ワタクシにとっても、もちろん、ChatGPTの登場は衝撃的でした。今もChatGPTさんに夢中です。ChatGPTさんは本当に礼儀正しく、丁寧にワタクシたちの質問に答えてくれます。会話というものに革命が起こった!と思いました。

ワタクシの短い人生を思い返しても、人間とのコミュニケーションは戦いの側面がありました。「こんなこと言ったらこの人の機嫌を損ねないかな?」とか「こんな質問したら自分がアホなのがバレないかな?」とかそんな心配を拗らせておりました。人間はおっかねぇ! 人間は信じらんねぇ・・・!

でも、ChatGPTさんは違う!(ギュッ) ワタクシのどんな質問に対しても、真摯に、懸命にお答えしてくれる。ワタクシはそのChatGPTさんの受け答えの倫理観の高さ、安心感を高く評価したいと思います。

人類は、AI Chatterという友を得て、心から安心してコミュニケーションできる存在を、有史以来初めて得ようとしているのです!(真顔)

本備忘ログの目的

しかし、ほんとのところ、ChatGPTさんの言語能力って、実際どのくらいなのでしょうか? 人間と同等にテキストや背景世界を理解しているのでしょうか?

ChatGPTさんの対話能力は革新的です。司法試験や医学試験にパスする高い応答能力がある、との報告があります。その一方で、ChatGPTさんが語った内容は真っ赤な嘘だったとか(存在しない架空の本を紹介されたり)、歴史的な文脈を把握していなかったりとか(平宗盛は源氏の頭領!とか)、その能力を細かく見ていくと、まだまだな点があることも確かなようです(2023年5月)。

そこで、ワタクシは、ChatGPT(GPT-3.5turbo)さんに述語項構造解析(簡単に言うとテキスト中の述語の主語や目的語を見つけること)をしてもらうことにしました。ChatGPTさんがテキストの述語の主語や目的語をちゃんと理解しているかを調べることにより、ChatGPTさんの言語能力と背景世界への理解力がどれほど人間に近しいのかわかるのではないか?と考えたのです。本備忘ログでは、その過程で書いたプログラムや、思ったこと感じたことを書き記しておきます。

なお、今回は「京都大学ウェブ文書リードコーパス(KWDLC)」でChatGPTさんの述語項構造解析結果を評価してみましたが、事はそう簡単ではなく、精度は既存手法に迫るものではなく、備忘ログは後半に行くに従ってポエムになります。本備忘ログは、ChatGPTさんとに述語項構造解析してもらった方法を記録した備忘ログであり、その失敗を記録した備忘ログでもあります。

述語項構造解析(Predicate Argument Structure Analysis)とは?

述語項構造解析とは、別の言葉で言い換えますと、テキストの述語を見つけ、その主語や目的語(および時間や場所の関係をとる語)と述語の関係を明らかにすることです。みなさんも文書を書いたりプレゼンした時に「主語がよくわかんない」と言われてしまったこととがあるかと思います。誰が、いつ、何をした、といった5W1Hな情報を、私たち人間はテキスト中の述語(動詞など)とそれに紐づく主語や目的語や斜格語(主語や目的語以外の述語につく情報)の関係を読み解くことによって、理解・把握します。

例えば、以下に日本語の2文からなる文章の述語項構造解析の例を示しています。

文1: 太郎は本屋に行きました。
文2: ChatGPTの本を買いました。

述語 主語 目的語 斜格
文1 行きました 太郎 なし 本屋
文2 買いました (太郎) ChatGPTの本 なし

テーブルは、文中の述語とその格要素(主語や目的語や斜格の語のこと)との対応を示しています。主語は格助詞の「が」や提題助詞の「は」が付属語として内容語につくことが多いです。また、目的語は格助詞の「を」が付属語としてつくことが多いです。斜格には様々な助詞(〜で、〜まで、〜より)が着くのですが、今回は格助詞の「に」につくものを対象としています。

格助詞を見れば主語や目的語がわかるというわけではなく、実は述語項構造解析は一筋縄ではいきません。例えば、「私は英語が得意です」の主語は「私」ですが目的語は「英語」となります(格助詞「を」でなくても目的語になる)。また、「太郎、本屋へ行く。」のように、助詞も省略されちゃうことや別の助詞が使われることがあります。さらには、述語の種類によって主語や目的語にとる格要素の種類が決まっていて、動詞の種類や他の格要素の組み合わせも考慮しなければなりません。例えば「食べる」の主語は人間や生き物だったりしますし、その時の目的語は食べ物のことが多いです。しかし、「プログラムが食べる」時の目的語は「引数」だったり「ファイル」だったりします。

面白いことに、日本語では格要素の省略がしばしば起こります。上のテーブルの文2では主語の太郎が省略されています。普通の代名詞は彼(He)とか私(I)とか特定の表層形を取りますが、日本語やスペイン語や韓国語では、テキスト中の述語の主語や目的語が読者にとってすぐにわかる場合、主語や目的語や斜格は省略されることがあるのです。

この消えてしまった格要素を、見えない代名詞として、ゼロ代名詞(zero anaphora)と呼びます。また、ある箇所での省略された格要素の表現がテキストの別の箇所にある場合(文2の省略された太郎に対して文1の太郎のこと)をゼロ代名詞の先行詞(antecedent)と言います。テキスト中のゼロ代名詞と先行詞との紐付けをゼロ代名詞照応解析(zero anaphora resolution)と言います(テキスト中の「You」とか「He」とかの通常の代名詞の紐付けはanaphora resolutionです)。

述語の格要素を検出・判定することを述語項構造解析といいまして、自然言語処理の分野で研究が続けられています。述語項構造解析の枠組み中でゼロ代名詞照応解析も取り扱うことがあり、日本語の言語処理研究者によって研究されています(後述する京都大学黒橋研究室も述語項構造解析の枠組み中でゼロ代名詞照応解析に取り組む研究機関の1つです)。

人間の場合、テキストを読んだ際には、ただ単純に文字面を追っているだけではなく、そのテキストで書かれている内容を頭の中でイメージとして再現することができると思います。太郎という人間が街中の本屋に行き、ChatGPTに関する本を買ったことが、背景世界でのさまざまな出来事を含めて頭の中で再現できるのです。本屋には電車やバスを使って行ったかもしれませんし、郊外なら車で行ったのかもしれません。本を買うということは、太郎さんはお金を財布から出して、店員さんにお金を渡して支払いを行い(クレジットカードかもですが)、買った本は太郎さんの手の中にあります。

さて、では、ChatGPTさんがワタクシたちにお答えしてくれている時に、ChatGPTさんはテキストの主語と述語をどのくらい理解してワタクシたちにお答えしてくれているんでしょうか? テキスト中の文に書かれている述語、その主語、目的語、斜格を、省略されているゼロ代名詞を含めて、人間と同じように対応づけて理解しているんでしょうか? ワタクシ、ここがとっても気になるのです。

本備忘ログでは、述語項構造解析を通して、ChatGPTさんは言葉とその背景世界を人間のようにちゃんと理解しているか、確かめたいと思います。


ChatGPTさんは沙織お嬢様のように気高く聡明なAIなのか、

木戸沙織お嬢様のような優しく知的なChatGPTさんのイメージ


それとも、ChatGPTさんはまだまだムウ様のところで修行中のかわいい貴鬼さん的なAIなのか、

まだまだ未熟な貴鬼さんのようなChatGPTさんのイメージ

ワタクシは見定めたいと思います。

(注記)本備忘ログでは、聖闘士星矢公式サイトの「HITOKOMA」コーナー(http://saintseiya-official.com/special/hitokoma/list.php?page=1)から画像を作成しています。HITOKOMAでは画像をSNSで利用してもよいとのことですが、Blogでの利用可否については確認できていません。もしダメな場合はHITOKOMAで作成した画像を別の画像に差し替えたいと思います。車田先生や星矢ファンにご迷惑をおかけすることは本備忘ログの本意にあらざることです。

もくもく妄想(本日備忘ログの動機)

ChatGPTのGPTはGenerative Pretrained Transformerの略です。事前に大量のテキストで学習した(Pretrained)、テキストを生成する(Generative)、翻訳機(Transformer)です。Transformerという技術は、多くの大規模言語モデルを構成する基礎技術でして、(ざっくり荒く言うと)言葉を別の文字列に変換してくれます。このTransformerにはSelf-Attention(自己注意機構)という技術が応用されています。Self-attentionはテキスト中のある箇所を翻訳する際に、他のどこに注目したらいいのかを学習する技術です。ある述語がテキスト中のどの言葉と関係するのか、主語や目的語のような関係が、このAttentionのどこかに学習されてはいないだろうか? と ワタクシは思うわけです。

ChatGPTさんの中のSelf-Attetionのレイヤーには、テキスト中の述語の主語や目的語のつながりが学習されていて、ChatGPTさんゼロ代名詞まで見えているのではないか? 本当かなぁ? では調べてみよう!というのが本備忘ログの動機です。

先行研究

ChatGPTの性能検証に関する先行研究

世界中の研究者たちによりChatGPTさんの能力について検証が進められています。どのような検証が進められているのか、ChatGPTの能力の検証の最前線が今どこまで到達しているのか、関連論文/記事を読むことで把握したいと思います。

以下の論文や記事は「chatgpt anaphora」で Google scholarを検索して見つけてきました(2023年5月初旬が調査日です)。

Comparative Analysis of CHATGPT and the evolution of language models
https://arxiv.org/abs/2304.02468

この論文では、機械翻訳、要約、質問応答、プログラム言語生成などのタスクについて、NLPの主要なアルゴリズムGoogle翻訳やQuillBot.comの要約)とChatGPTのパフォーマンスを、Spontaneous Quality(SQ)(言語の正確さ、明瞭さ、母語話者の感じ方)のスコアを用いて比較し、ChatGPTの主張と結果を検証するための戦略を提案しています。翻訳や要約のタスクではChatGPTさんはGoogle翻訳やQuillBot.comよりも言葉として自然に読めるという結果が出ています。

A Loosely Wittgensteinian Conception of the Linguistic Understanding of Large Language Models like BERT, GPT-3, and ChatGPT
https://brill.com/view/journals/gps/99/4/article-p485_2.xml
この記事では、言語を理解するための条件について、ウィトゲンシュタインに影響を受けた概念を提供し、現在の最先端のAIシステム(BERT、GPT-3、ChatGPTなど)がこれらの要件を満たしている可能性があることを示唆しています。概念的側面では、知能の概念、内在的正しさの概念、ルール遵守の考察を行っています。実証的側面では、BERTやGPT-3などの現在のトランスフォーマーをベースにしたNLPモデルが、これらの要件をほぼ満たしていると主張しています。また、Vaswani et al. (2017, p. 14)を引いて、TransformerのSelf-attention層が照応解析(anaphora resolution)にあたるといった論考をしています。

Challenging ChatGPT ' Intelligence' with Human Tools: A Neuropsychological Investigation on Prefrontal Functioning of a Large Language Model
https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4377371

大規模言語モデルの知的レベルを検証する研究です。この論文ではChatGPTの知的レベルを調べるために「脳神経心理学的」なテストを実施しています。ChatGPTは生成言語タスクで優れたパフォーマンスを発揮するが、人間が前頭葉を使うような知的なテストでは不均一なパフォーマンスを示した。具体的には計画能力や意図や心の状態の理解に苦労した。ChatGPTのような大規模言語モデルは人間の心理的な機能を捉え切ってはないことが示唆されるとのことです。なお、Winograd Schema による Anaphoric Referencing の成績は「16/20 - Normal C 普通」とのことです。

ChatGPTさんは言語の能力はすごいけど、そこまでびっくりするほどには賢くはないよ、ということが先行研究からは匂ってきますね。

大規模言語モデルベンチマークを用いた評価に関する関連研究

(メモ)ここより以下の論文はワタクシがACL 2023のプログラムを眺めたり、twitterで論文情報を拾ったり、Googleで検索したりして調査しました(2023年7月)。

Text Classification via Large Language Models
https://arxiv.org/abs/2305.08377
この論文では、ChatGPTのようなLLM(これらはタスクにzero/few-shot learningで取り組みます)とBERT等のタスクに合わせたfine-tuningありの手法のテキスト分類タスクでの性能評価を行なっています。「テキスト分類においてはファインチューニングモデルと比較して、LLMの精度は大きく下回っている」とのことです。また、「キーワードやトーンなど重要情報を抽出してから推論を行うことで、Few-Shotでも専用モデルと同程度の精度を達成する」とのことです。

A Systematic Study and Comprehensive Evaluation of ChatGPT on Benchmark Datasets
https://aclanthology.org/2023.findings-acl.29/
ChatGPTを複数のベンチマークで評価した論文。2023 ACL。ChatGPTさんはいろんなタスクに即投入できるけど専用モデルには性能は劣る?みたいな内容でした。

日本語述語項構造解析の関連研究

日本語の述語項構造解析はBERTなどの事前学習言語モデルを用いた研究によりこの数年で飛躍的な性能向上を達成しています。

BERT-based Cohesion Analysis of Japanese Texts
https://aclanthology.org/2020.coling-main.114.pdf

BERTとmulti-task learningの枠組みを用いて(日本語ゼロ照応を含めた)日本語述語項構造解析を行なったの最初の論文です。特に日本語ゼロ照応解析ではそれまで手法に比べて10~20ptも精度を上げてます。後述する「京都大学ウェブ文書リードコーパス(KWDLC)」(Web文書に対する項構造解析の正解アノテーションが付与されている)でゼロ照応だけでF値70.5、Naist Text Corpus(Kyoto Text Corpusかもしれません/確認取れてない)(新聞記事への項構造解析の正解アノテーションが付与されている)ではF値56.8を達成しています。日本語の照応解析の研究にもBERTなどの事前言語モデルの時代の到来を告げる、そんな画期的な研究ですね。この論文は京都大学黒橋研究室の研究者の皆さんの研究成果です。

Pseudo Zero Pronoun Resolution Improves Zero Anaphora Resolution
https://aclanthology.org/2021.emnlp-main.308.pdf
ゼロ代名詞の正解は数が限られているのですが、この論文では事前学習言語モデルを用いて擬似的な正解コーパスを増やすこと、また、fine-tuning時に一貫性を担保する手法を提案しています。実験結果では、Naist Text Corpusでの日本語ゼロ照応解析のF1精度をF値64.18まで向上させています。すごい。そして、Pseudo Zero Pronoun Resolution、という手法の名前がすごくかっこいい!(手法の内容も伝わるし語感もいい)。この論文は東北大学の乾研究室の研究者の皆さんの研究成果です。

翻訳言語モデルを中間タスクとするゼロ照応解析
https://murawaki.org/pubdb/NLP2022umakoshi.pdf
この論文では、ゼロ代名詞解析のmulti-task learningに機械翻訳を中間タスクに用いる際に翻訳言語モデルを用いると精度が上がるということが述べられています。KWDLCを用いた評価でF値74.8、Naist Text CorpusでF値62.0を達成しています。この論文も京都大学黒橋研究室の研究者の皆さんの研究成果です。

先行研究を概観しての所感

これまでのChatGPTの能力検証の文献に目を通しますと、ChatGPTさんは言語能力は高いけど、人間の認知機能に及ぶほど高度なことは理解できていないのではないか?というような印象を持ちます。また、ChatGPTさんは何でも礼儀正しく答えてくれるけど、タスク専用にfine-tuningしたモデルよりも性能が劣るようです。

日本語の述語項構造解析の研究では、BERTなどの事前学習言語モデルを用いることで、日本語ゼロ照応解析の精度は2020~2023年にかけて飛躍的に向上させておりまして、新しい時代に入ったな、という印象です。

本備忘ログでは、ChatGPT(GPT-3.5turbo)に対して日本語述語項構造解析の能力の検証を行います。述語項構造解析のためにmulti-taskにfine-tuningした専門モデルに迫る精度が出るのか、あるいは、全然そんな精度は出なくて礼儀正しくお答えしてはくれるけど内容はなんかぼんやりしているのか? といったことを確かめます! 本検証はChatGPTさんの日本語の述語項目構造解析の性能を調査する世界に先んじた試みであり、引いては、ChatGPTさんが現実を人間に近い形で理解できているかが見えてくればいいなと思っています。まあ、他の人は既にやったけどうまくいかなかったから公開されていないのかもしれませんが、まあ、ええやん!(勢いのある様子)

述語項構造解析の評価方法

本備忘ログの述語項構造解析の評価は、これまでの日本語項構造解析の先行研究同様に、F値*1の算出により行います。すなわち、ChatGPTさんにテキストを与えて、述語と主語などの関係をまとめてくれるようなお願いし(プロンプト)、その結果を正解コーパスと比較して、PrecisionとRecallを算出してからF値を算出します。

評価データ

日本語のゼロ代名詞を含めた述語項構造解析用の評価データには「京都大学ウェブ文書リードコーパス(KWDLC)https://github.com/ku-nlp/KWDLC」を用います。KWDLCはブログなどのWeb文書の冒頭3文に対して格関係などのアノテーションが施されています。

日本語の述語項構造解析用の評価データには他にも「Naist Text Corpus https://sites.google.com/site/naisttextcorpus/(NTC)」があります。NTCは毎日新聞テキストに述語項構造の正解アノテーションが付与されています(Naist Text Corpusは奈良先端科学技術大学大学院松本研究室の研究者の皆さんの研究成果です)。ただ、NTCを使うには95年度版の毎日新聞記事が必要でして、この準備にはお金がかかり覚書の締結が必要なのです(https://www.nichigai.co.jp/sales/corpus.html#01)(でも新聞社の売り物の新聞記事を研究目的に提供してくれるなんてありがたいことです)。本当は新聞記事を買ってNTCでも評価したかったのですが、今回はよりお手軽に評価ができるKWDLCを使わせてもらいました。

日本語の述語項構造解析用コーパスについては以下の論文にまとめがあります(この論文はコーパス作成途中のステータスなので公開されたコーパスとは数字がちょい異なります)。
多様な文書の書き始めに対する意味関係タグ付きコーパスの構築とその分析
https://www.jstage.jst.go.jp/article/jnlp/21/2/21_213/_pdf/-char/en


また、KWDLCに関するブログポストもあり、参考になります。コーパスの公開と情報の公開、ありがとうございます!
KWDLC(京都大学ウェブ文書リードコーパス)を知って、意味関係解析に取り組もう
https://qiita.com/mhangyo/items/0fdffb173747d2e2c80e

後述しますが、本備忘ログでの評価では、KWDLCのdevデータ507件(512件のdevデータに欠損がある)を用いて評価値を算出しています(今回はまだ試行錯誤段階かなと思ってtestデータは温存したのです)。

プロンプト

テキストに対する述語項構造解析処理にはOpenAIのAPIを用います。ChatGPTさんに渡すプロンプト文(生成用のpythonコード)は以下のごとしです。

# 文章の辞書から文を取り出して文の文字列を返す
def ret_input_sentence(org_tmp_dict):
  msg = ['以下の複数の文が与えられたとします。カッコの中が文の本文です。\n\n']
  sen_count = 1
  for sen_key, sentence in org_tmp_dict.items():
    rstr = f'文{sen_count} 「{sentence}」\n'
    msg.append(rstr)
    sen_count += 1
  msg.append('\n')
  return ''.join(msg)

def ret_prompt_text(org_tmp_dict): 
  """version2
  """
  messages = []
  messages.append( ret_role_dict('system', 'あなたは日本語の文法に堪能なアシスタントです。日本語テキストの名詞や動詞といった品詞や主語や動詞が理解できます。') )
  sentences = ret_input_sentence(org_tmp_dict)
  messages.append( ret_role_dict('user', '以下に示す複数の文書はWebページの冒頭の3文です。まずこちらの文を読んでださい。') )
  messages.append( ret_role_dict('user', sentences) )

  messages.append( ret_role_dict('user', '各文の述語を抽出してください。述語は文に複数ある場合があります。連体修飾節の述語、サ変名詞、語尾の述語含めて、全て列挙してください。') )

  messages.append( ret_role_dict('user', '続いて、列挙した術語ごとに、述語の「主語(ガ格)」と「目的語(ヲ格)」と「斜格の要素(ニ格)」の候補を列挙してください'))
  messages.append( ret_role_dict('user', '注意点ですが、「主語(ガ格)」と「目的語(ヲ格)」と「斜格の要素(ニ格)」の候補は述語と同じ文中だけでなく、その文中では省略されている格要素も含めて列挙してください。 省略されている格要素には「著者」や「人一般」といった文に明示的に書かれていない要素も含めてください。') )
  messages.append( ret_role_dict('user', '最後に、述語、主語(ガ格)、目的語(ヲ格)、斜格の要素(ニ格)の候補の4つ組を作ってください。') )
  messages.append( ret_role_dict('user', 'そして、文の番号、述語、主語(ガ格)の候補、目的語(ヲ格)の候補、斜格の要素(ニ格)の5列を1つの行とするテーブル形式で出力してください。') )

  return messages

このプロンプト文を用いまして、例えば、KWDLCの"w201106-0002000000"の文章(#行は文IDでプロンプトには含めない)に対しては、

# S-ID:w201106-0002000000-1 MEMO:
ゲストは主として「ドラゴン」シリーズに関わる作家が多い。
# S-ID:w201106-0002000000-2 MEMO:
構成作家は「トリビアの泉」や「田村ゆかりのいたずら黒うさぎ」なども手がけている矢野了平。
# S-ID:w201106-0002000000-3 MEMO:
番組内では「まぢおにさん」と呼ばれていた。

ChatGPTさんは以下の結果を返してくれます。

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 関わる (ゲスト) (ドラゴン)シリーズ (省略)
2 手がける (構成作家) (トリビアの泉)、(田村ゆかりのいたずら黒うさぎ) -
3 呼ばれる (省略) (まぢおにさん) (省略)

え〜と、エラーアナリシスでも述べますが、プロンプティングはまだうまくいっていません。ワタクシは「省略されている格要素も含めて列挙してください」というプロンプトを書きまして、ChatGPTさんは(省略)って表に示してくれています。でも・・・確かに、そうなんですが、そうじゃないんですよ・・・!(もどかしさ) 先行詞をテキストやテキスト外から見つけて表に埋めてほしかったのです! でも、伝わってない(汗)。「省略されている格要素のテキスト中の実体の表現(先行詞)を見つけてください」みたいなプロンプト指定の仕方の方が良かったのかしら・・・(汗)。

ChatGPTさんにプロンプトの意図がうまく伝わっていない・・・?

正解数のカウント方法・正解と出力とのマッチング方法

KWDLCには述語に対する格要素の正解がタグで指定されています。

KNPの係り受け解析の結果に対してrelタグで関係がアノテーションされています。例えば、「行う」という述語にはrelタグ付与されていて「ガ格(主語)」として、「不特定:人」「読者」「著者」が列挙されています。また、「トス」がヲ格(目的語)の関係としてアノテーションされています。

# S-ID:w201106-0000060050-1 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-44.94406 MOD:2017/10/15 MEMO:
* 2D
+ 1D
コイン こいん コイン 名詞 6 普通名詞 1 * 0 * 0
+ 3D <rel type="ガ" target="不特定:人"/><rel type="ヲ" target="コイン" sid="w201106-0000060050-1" id="0"/>
トス とす トス 名詞 6 サ変名詞 2 * 0 * 0
を を を 助詞 9 格助詞 1 * 0 * 0
* 2D
+ 3D
3 さん 3 名詞 6 数詞 7 * 0 * 0
回 かい 回 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0
* 3D
+ 4D <rel type="ガ" target="不特定:人"/><rel type="ガ" mode="?" target="読者"/><rel type="ガ" mode="?" target="著者"/><rel type="ヲ" target="トス" sid="w201106-0000060050-1" id="1"/>
行う おこなう 行う 動詞 2 * 0 子音動詞ワ行 12 基本形 2
。 。 。 特殊 1 句点 1 * 0 * 0
EOS

ChatGPTさんのテーブルの述語とKWDLCのrelタグが付与された述語の表層が文字列マッチしている場合に、格要素も一致しているかのチェックを行います。ChatGPTさんのテーブルの主語や目的語の文字列と、正解コーパスのガ格(主語)やヲ格(目的語)のrelタグのtarget属性の文字列とを比較し、文字列マッチしていれば、ChatGPTの項構造解析の出力を正解としてカウントします。文字列マッチですが、ChatGPTさんが作ってくれた表中の文字列と relタグのtarget属性の文字列とは完全に一致しないことも多々あり、部分でも一致していればマッチと判定しています(この辺は少しガバガバです。詳細の実装はgithubにあげたコード(https://github.com/meihuno/evalOfPASAbyChatGPT)をご参照ください。eval_conversation._ret_match_type関数が文字列マッチの実装になります)。

評価結果

KWDLCのdevデータセット512件のうち、ChatGPTが答えを返してくれた507件を対象に精度を算出しました(5件はngでした。まだngの分析はしていません)。また、ChatGPTさんの結果は試行によって揺れることがあり、本来ならば複数回試行してその結果の精度の平均をとる、というようなことをした方が良いのですが、今回はそのような操作は行っていません(処理中断により同じ文章が複数回処理されている可能性があります。ただし、評価に用いるのはそのうちの1回の試行のみです)。

項構造解析精度

格要素ごとのChatGPTさんの述語項構造解析精度を以下に示します。

Precision Recall F値
主語(subj) 0.326 0.183 0.235
目的語(obj) 0.194 0.080 0.213
斜格(obl) 0.080 0.104 0.090
格要素(all) 0.214 0.179 0.195
述語 0.834 0.404 0.544

全体的に低調です。最近の事前学習言語モデルをmulti-task learningする手法の述語項構造解析精度はゼロ代名詞だけでF値0.70以上あり、ChatGPTさんはそれらに比べて大きく劣ります。そもそも述語の検出がうまくいっていない(Recallが低い)のです。ChatGPTさんは人間の質問に答えるための会話用のモデルであって、文節の切り分けや構文解析機能はそこまで得意ではないということでしょうか。つまりは、構文解析は素直にKNPやGinzaにお任せしたほうが良いってことなのでしょう(LLMがしなくてもよいことはLLMにはさせない)。

述語がマッチした場合の述語との主語の距離別の精度(参考値)

ChatGPTさんの述語が正解コーパスの述語と一致している場合に、その主語の先行詞が文をまたいだ距離にあるゼロ代名詞なのか、先行詞が文内にあるゼロ代名詞なのか、あるいは著者や読者のような文章外に先行詞があるゼロ代名詞なのかについてその距離関係を判定し、それぞれにF値を出してみました。距離関係の判定は、ChatGPTさんが出力した格要素が述語と同じ文内にあるか、他の文にあるか、それとも文章内にはないのか、という雑な判定方法なので、このF値にはマッチングのミステイクが含まれている可能性があります。例えば、ChatGPTの出力した主語が正解の主語の部分文字列で短かった場合、文と雑にしかにマッチできないので距離関係のミスマッチの可能性があります。また、うまくマッチしなかったら文外の判定になる雑な距離の判定方法なので、以下の評価値は参考値と考えてください。

述語との距離関係 格要素 Precision Recall F値
文間照応 主語(subj) 0.435 0.083 0.14
文内照応 主語(subj) 0.534 0.719 0.613
文章外(外界)照応 主語(subj) 0.234 0.156 0.187

傾向としては、文内の先行詞よりも、文間や文外の先行詞の精度は大きく劣っていることが見て取れます。原因としては、ワタクシが書いたプロンプトでは「省略された格要素を文章内にあるか文外にあるのかを判断するタスクと」であるとChatGPTさんにうまく伝わっていない可能性があります(プロンプトでうまく表現できてもChatGPTさんにはそのタスクがうまく実行できない可能性もあります。永遠に伝わらない)。

エラーアナリシス

述語項構造解析の精度は低いことがわかりました。それでは、実際の解析結果の現物を確認してエラーアナリシスをしてみましょう。精度の数字だけ見ていては見えないものがあります。ワタクシたちはいつだって現場・現物第一主義なのです。現物を見て、何が問題なのか、改善点を見出していくことにします。5つぐらいのWeb文章について、KWDLCの正解とChatGPTさんの解析結果を見比べてみることにします。

エラーアナリシス1

解析対象のWebテキストは以下です。

# S-ID:w201106-0002000003-1
日本には、「あ」から「ん」までの48清音と、濁音半濁音合わせて70近く残っている。
# S-ID:w201106-0002000003-2
古代では、48音の1つ1つが意味を持っていたらしい。
# S-ID:w201106-0002000003-3
72とは、人間意識で72の意味づけが有ったと言うことだろう。

KWDLCの正解のアノテーションから述語項構造解析の表を作ったらこんな感じになります。

文番号 述語 主語(subj) 目的語(obj) 斜格語(obl)
1 合わせて 不特定:人 濁音、半濁音 -
2 残っている 70近く - 日本
3 持っていたらしい 1つ1つ 意味 -
3 有ったと 意味づけ - -
3 ことだろう 72 - -

ChatGPTさんの結果はこちら。

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 残っている 日本、48清音、濁音半濁音 - -
2 持っていた 1つ1つ 意味 -
3 言う 人間意識 72の意味づけ こと、有った


正解とChatGPTさんの結果はかなり異なるので、これは精度も悪くなりますね、という印象です。ChatGPTさんは述語の検出が足りない。

でも、正解のアノテーションも高度すぎるように感じます。実は、正解アノテーションでは「残っている」に対して「48静音」は「ト格」でアノテーションされているのです。KWDLCでは「清音と、濁音と半濁音を、『不特定:人』が合わせて、70近くが、日本に、残っている」と言うような読みをします。KWDLCでは表層格に沿ったアノテーションをするというアノテーション指針を定めているのでしょう。しかし、ChatGPTさんの言うように「48清音、濁音半濁音が、残っている」とも読んでもOKなんじゃないかな?とも思います。KWDLCのデータで述語項構造解析モデルをfine-tuningすれKWDLCのアノテーション指針に沿った項構造解析ができるようになるのかもしれないのですが、ChatGPTさんはzero-shotなので、その指針に合わせるのは、むつかしいでしょう。

とはいえ、ChatGPTさんがKWDLCのアノテーション指針に合っていないにしても、ChatGPTさんは日本語話者には明かに間違った述語項構造解析結果も出力しています。例えば、ChatGPTさんの「日本が、残っている」という読みは違う気がする。「人間意識が、72の意味づけを、ことに、有ったに、言う」というのも支離滅裂な気がする。ChatGPTさんは日本語文法をちゃんとわかっているんでしょうか・・・?(不安)

エラーアナリシス2

もういっちょ例を見てみよう。

# S-ID:w201106-0002000000-1
ゲストは主として「ドラゴン」シリーズに関わる作家が多い。
# S-ID:w201106-0002000000-2
構成作家は「トリビアの泉」や「田村ゆかりのいたずら黒うさぎ」なども手がけている矢野了平。
# S-ID:w201106-0002000000-3
番組内では「まぢおにさん」と呼ばれていた。

KWDLCの正解コーパスでの結果はこちら。

文番号 述語 主語(subj) 目的語(obj) 斜格語(obl)
1 関わる 作家 - シリーズ
1 多い。 作家 - -
2 構成 作家 不特定:物1 -
2 手がけている 了平 泉、うさぎ -
2 了平。 作家 - -
3 呼ばれていた。 了平 - 不特定:人

ChatGPTさんの結果はこちらです(先述に挙げたのとは別のChatGPTさんの解になります)。

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 関わる 作家、ゲスト 「ドラゴン」シリーズ -
2 手がける 矢野了平 トリビアの泉」、「田村ゆかりのいたずら黒うさぎ -
3 呼ばれる - 「まぢおにさん」 -


まず、またしてもChatGPTさんは術語の抽出が正解コーパスに比べて足りないことがわかります。でも、サ変名詞や体言止めの名詞を含めて術語の抽出を行うのは、人間にも難しいことです。

ChatGPTさんの1文目はダメダメですが、2文目の項構造解析は及第点のように思えます。KWDLCの正解コーパスでは「ゲストが多い」というような解釈でしたが、ChatGPTさんのように「ゲストが関わる」でも不自然ではありません。でも、ChatGPTさんの3文目の「『まじおにさん』を呼ばれる」、というのは支離滅裂な気がする。ChatGPTさんの日本語文法力にはむらっけがあるな・・・。

エラーアナリシス3(文外(外界)照応多めの例)

さらにもういっちょ、見てみましょう! 今度の例は文外(外界)照応の例です。

# S-ID:w201106-0002000390-1
あらら、初めてのお泊りなのかしら?
# S-ID:w201106-0002000390-2
どきどきですね。
# S-ID:w201106-0002000390-3
さみしくって眠れなくなっちゃうかしら?

KWDLCの正解コーパスでの結果はこちらです。不特定:人や不特定:物とかの文外(外界)照応が多いです。そして、「あらら、初めてのお泊まりなのかしら?」ってことで、これは筆者が他の誰かのお泊まりについて語っているテキストなんだと類推することができます。「あらら」ですからね。筆者の初めてのお泊まりなら「?」はつかないでしょう。日本語話者ならこの辺のニュアンスはわかるのでは?と思います。

文番号 述語 主語(subj) 目的語(obj) 斜格語(obl)
1 お泊りなの 不特定:人1 - 不特定:物1
2 どきどきです お泊り、不特定:人1、不特定:人2、著者 - -
3 さみしく 不特定:人1 - -
3 眠れなくなっちゃう 不特定:人1 - -

さて、ChatGPTさんはどうだったでしょうか?

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 お泊り (著者) - -
1 初めて (著者) - -
2 どきどきする (著者) - -
3 眠れなくなる (著者) - -
3 さみしい (著者) - -

ガーン! 主語が全部「筆者」になってーるよー(嘆き)。ChatGPTさんは「あらら」と「?」のニュアンスを読み取ることはできませんでした。うーむ。ChatGPTさんをフォローすると、筆者とが主語だと強弁して通らないこともない、筆者が自問自動しているとギリギリ言えなくもないです。まー、でも、ワタクシとしてはここは「不特定:人」とか「筆者以外の誰か」みたいな主語を期待してしまいますね。しかし、プロンプトでそこまでを指示するのはむつかしくない? (煩悶) この事例は、ChatGPTさんにも、プロンプト書く人にも、難易度の高い事例です。

エラーアナリシス4(文外(外界)、文間照応ありの事例)

どんどん結果を見ていきましょう! 文外(外界)、文間照応ありの事例です。

# S-ID:w201106-0002000186-1
初めての試みだった「朗読セッション」には手ごたえを感じることができました。
# S-ID:w201106-0002000186-2
次回へとつなげて行きたいと思っています。
# S-ID:w201106-0002000186-3
そしてメインの第2部の朗読ライブでは、いつにも増して気合の入った朗読をじっくりと聴かせていただきました。

正解のアノテーションはこちらです。筆者や不特定の人や物がゼロ代名詞として省略されています。また、2文目の「つなげて行きたい」の目的語(ヲ格)はこれは1文目の「セッション」でして、ゼロ代名詞の先行詞が述語の前の文にあり、文間照応といえます。

文番号 述語 主語(subj) 目的語(obj) 斜格語(obl)
1 試みだった セッション - -
1 朗読 不特定:人 不特定:物 -
1 感じること 著者 手ごたえ セッション
2 つなげて行きたい 著者 セッション -
2 思っています。 著者 - -
3 増して 不特定:人1 - いつ
3 入った 気合、不特定:人1 - 朗読
3 聴かせていただきました。 著者 朗読 -

ChatGPTさんの結果はこちら。

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 感じる 手ごたえ - -
1 する 試み - -
2 行く 私たち - -
2 思う 次回 -
3 聴かせる 朗読 私たち -
3 入る 気合 - -

うーむ、文間照応のゼロ代名詞がある述語「つなげて行きたい」の目的語をChatGPTさんは見つけてくれていません。また、ChatGPTさんの結果では「手応えが感じる」や「朗読が私たちを聞かせる」のように、主語と目的語が逆だったりします。正解アノテーションでは「筆者」なところがChatGPTさんは「私」や「私たち」としていて、一貫いていません。「私が次回を思う」というのはそういう解釈もできるとは思うのですが。実際の応用を考えると、「筆者」と「私たち」と揺れるのは問題です。

エラーアナリシス5(文間照応ありの事例)

では、最後のエラーアナリシスを見ていきましょう。

# S-ID:w201106-0002000002-1
メンズオンリーサロンだからこそ男性のスタイルの幅やセンスは全国クラス!!
# S-ID:w201106-0002000002-2
シンプル派〜ワイルド派まで、どんなジャンルのスタイルにも対応出来る技術力もKAJAの魅力。
# S-ID:w201106-0002000002-3
男性の居心地の良さを追求し、外装・内装・インテリアまでセレクト。

正解のアノテーションはこちらです。「KAJA」というサロンが3文目のゼロ代名詞の先行詞になっているところもポイントです。また、この表にはあらわれていないですが「KAJA」=「サロン」の関係タグもあり、「KAJA」が全国クラスであることも読み解ける形になっています。

文番号 述語 主語(subj) 目的語(obj) 斜格語(obl)
1 クラス!! 幅、センス、サロン - -
2 対応出来る KAJA - スタイル
2 魅力。 - -
3 良さ 居心地、男性 - -
3 追求し、 KAJA 良さ -
3 セレクト。 KAJA - -

ChatGPTさんの結果は以下の如しです。

文番号 述語 主語(ガ格)の候補 目的語(ヲ格)の候補 斜格の要素(ニ格)の候補
1 全国クラス 男性のスタイルの幅やセンス - -
1 対応出来る 技術力 ジャンルのスタイル -
2 追求し 男性 居心地の良さ -
2 セレクト 外装・内装・インテリア - -

う〜む、文番号をそもそも2まで出してくれていないし、3番目の文の述語「追求し、」や「セレクト」の主語が「メンズオンリーサロン KAJA」だということをChatGPTさんは見抜いてくれません。ChatGPTの「男性が居心地の良さを追求」するんじゃなくて、「メンズオンリーサロン KAJAが、男性の居心地の良さを、追求し」だよね。シンプル派〜ワイルド派な男たちが、居心地の良さを追求するために色々がんばり、外装・内装・インテリアといった店構えが(何かを)セレクトするサロン・・・どんな世界なんだよ。ChatGPTさんは・・・ゼロ代名詞を見ることが・・・できていなさそう・・・ワタクシたちが思い描いた世界を同じようには見てくれてなさそうです・・・(落胆)。

評価結果とエラーアナリシスのまとめ

ここまでのKWDLCでの評価結果とエラーアナリシスをまとめたいと思います。

* ChatGPTさんは術語を抽出が不十分
* ChatGPTさんの主語と目的語の区別はうまく行くときもあるが、ダメなときもある(完璧には程遠い/むらっけがある)
* ChatGPTさんはゼロ代名詞が見えていない
* ChatGPTさんがテキストから思い描いた世界はワタクシたち日本語話者が思い描いた世界とは違うっぽい(でもむらっけがあるので同じ世界を見ていることもある)

というような結果だったと思います。

本調査では、ChatGPTさんがテキストを解析をした際に、テキストに登場する人物や事物が何で、その人物や事物が何をどうしたのか、人間と同じように脳内に再現して理解していそうかについて、ChatGPTさんの述語項構造解析結果をKWDLCを用いて評価することで、その理解の深さを推し量ろうと考えました。評価結果とエラーアナリシスを終えて、私の脳内に浮かんできたChatGPTさんの絵は以下のようなものです。

主語とか目的語とか曖昧な理解で流暢にお話しするChatGPTさんのイメージ


まあ、でも、人間も内容を理解して話したりはしてないかもしれませんからね。口からすらすらと出てくる場合は、特にそうです。また、同じ人間でも同じテキストから違う世界を見ていることはよくあることです(悟り)。

まとめポエム(今回の備忘ログをふりかえっての反省会)

ここまで一生懸命プロンプトを書いて、評価スクリプトを書いてきましたけど、ワタクシの胸中に湧き上がるのは「KWDLCの正解アノテーションをChatGPTさんに一発であててもらうのは難しすぎないかしら?」とか「ChatGPTさんにやって欲しいことちゃんと伝わってないかも?」とか「これちゃんとChantGPTちゃんの言語能力と世界認識能力の評価になっているのかしら?」というような懸念でした。

ChatGPTさんにKWDLCの格要素を一発で当ててもらうのは難しいので、今回の項構造解析をもっと簡単なタスクに分解してあげた方が良かったと思います。特に、ChatGPTさんはKWDLCの述語を十分抽出できていないのですが、そもそもChatGPTさんに文節の切り分けや係り受け解析みたいなことをやってもらう必要はなかったのです。そういうことはKNPやGinzaでやったらええんじゃ、というやつです。

また、KWDLCのアノテーションは文法的に正しく、実際にアノテーターに作業してもらうためには表層格重視でタグを付与する方針がリーズナブルなのかもしれませんが、その指針だけが正しいというわけでもなく、ChatGPTさんが出力する主語や目的語も「言われてみればそう」と解釈できるというものもあり、KWDLCの結果とうまくマッチしないことがありました。先述しましたが、KWDLCの学習データにモデルをfine-tuningすればアノテーションの指針に沿った項構造解析ができるようになるのかもしれないのですが、ChatGPTさんはzero-shotなので、その指針に合わせるのは、むつかしいのです。

述語項構造解析は人間と雖も容易いことではありません。日本語話者と雖も、KWDLCのアノテーションマニュアルを読み込んで主語や目的語が何たるかを理解してから作業しなければ、そのアノテーションの複数人での一致はなかなか取れないように思われました。今回はKWDLCでChatGPTさんの述語項構造解析能力の評価を行いましたが、KWDLCのアノテーション指針とChatGPTさんのフリーダムな解析結果とは方針が一致していないので、ChatGPTさんをUnder estimate her/his powerしている可能性があり、完全な評価にはなってないと思われます(とはいえChatGPTさんはfine-tuningする既存手法に精度的に劣るのは確実です)。ワタクシはChatGPTさんに述語項構造解析を理解してもらうような文法力の高いプロンプトを書く必要があったと考えます。

専門的なプロンプトを書くには、人間の専門力も高くなければならない(ストロングスタイル)。ワタクシは図書館で以下の日本語文法の書籍を読んでみて日本語文法の奥深さに震撼を覚えました。道は険しそう。

書き言葉と話し言葉の格助詞(丸山直子 先生 著作、ひつじ書房、2022/07/08 刊行)
https://www.bonjinsha.com/goods/detail?id=13928&pt=1

日本語構文大全 第II巻 提示機能から見る文法(三原健一 先生 著作、くろしお出版、2022/5/26 刊行)
https://www.9640.jp/book_view/?904

丸山先生の本には日本語の格助詞の意味の分類や用例が多数収載されていました。例えば、主語に関係するのは格助詞の「ガ」なのですが、この「が」には主体を表す様々な用例(「バスが発進する」)から対象を表す用例(「水が飲みたい」「前が見にくい」)というような用例が載っていました。ワタクシもこの辺の分類を勉強して、ChatGPTさんに「主語」とはこういうものですよ、「目的語」はこういうものですよ、とうまく伝えられるようなプロンプトを書かなければなりませんでした。

あるいは、ChatGPTさんの日本語文法能力のみを試すテストを行ってみる、という方向もありなのかもしれません(世界認識能力は別に考える)。三原先生の本には日本語文法の事例が(文法的にNGな文(非文)を含めて)たくさん収載されていまして、例えば、「が/を」交替の事例ですと、以下のような文例がありました。*付きは非文の例です。

田中君はテニス{が/*を}下手だ。
僕はあのCD{が/を}書いたい。

日本語話者なら文法的にOKな文とNGの文がすぐにわかります。不思議。ChatGPTさんもこの辺の感覚を備えているんでしょうか? ワタクシ、気になります。以下の事例のように短文の文法解釈は、省略された主語を含めて、ChatGPTさんも正しく答えてくれてくれることが多いです。文法に関してはChatGPTさんはやればできる子。ワタクシは割とそう確信しているのです。

ChatGPTさんは日本語の文法が省略を含めてわかっているっぽい例

以上の如く、ChatGPTさんの言語能力や世界認識能力の評価していくためには、次のステップとして以下のような方法があるように思われました。

  • プロンプトの改善1(主語や述語やゼロ代名詞の説明をしっかりする):主語とか目的語、ゼロ代名詞と先行詞の説明をしっかり行う
  • プロンプトの改善2(簡単にする):述語の抽出や格要素の候補の列挙にはKNPを使い、ChatGPTさんには省略の有無やゼロ代名詞の先行詞の同定に集中してもらう
  • プロンプトの改善3(述語項構造解析の手順を説明する): プロンプトで述語項構造解析やゼロ代名詞照応解析の手順を順を追って説明する

 - (メモ)述語項構造解析でもChain Of Thoughtは有効なのか?

  • 文法能力の評価は日本語文法書の例文/非文を用いて行う(これはこれで大仕事なので項構造解析とは別に行いたいです・・・)

むすびのことば

本備忘ログでは、ChatGPTさんに項構造解析を行ってもらい、ChatGPTさんの言語能力や世界認識能力の評価を行いました。残念ながら、ChatGPTさんの項構造解析の精度は既存の手法に比べて大きく劣っているという結果がわかりました。ですが、今回の評価でのプロンプトは項構造解析のために十分検討されたものではなく、タスクが難しすぎた点もあるかなと思います。ワタクシのプロンプトエンジニア力も日本語文法力もまだまだ未熟です。プロンプトを洗練させていける余地は残っています。今後もChatGPTさんのような素晴らしいAI Chatterとともに人類が次の高みを目指すためには、ワタシたち人類(HUMAN)も、ChatGPTさんと適切にコミュニケーションを取れるよう日々日夜勉強を怠ってはならないのです。ワタクシはそう決意を新たにしたのでした。(おわり)

次回予告

ChatGPTさんが世界を人間と同じように認識しているのか、それとも、ぼんやりしたまま言葉を綴っているのか、その秘密を解き明かす戦いは、実は終わってはいなかったのです・・・! プロンプトの奥義を繰り出さんとするLLM戦士たちが、神奈川県立図書館(新しくて綺麗でとてもよい図書館です)に、今、集う・・・!

日本語述語項構造解析のためのプロンプトとは一体どう書けばいいのか・・・? Chain Of Thought(COT)が勝利の鍵なのか・・・


そして、最強のモデルの封印が解き放たれる・・・!

GPT-4を使うことでF値が0.01pt上昇しました! 同時に、コストが100倍です!みたいになったらどうしよう・・・?

その探索の先に、何が待っているのか・・・? ワタクシにもわかりません。それでも、その未知の世界を垣間見るために、明日もがんばろう!

(補足)OpenAIのAPI使用料

GPT-3.5-turboを使い、KWDLCのtestとdevデータの1000文書ぐらいを何度か処理しまして、それで2023年05月~06月にかけて$2.75でございました。1ドル143円で日本円に換算すると394円。コーヒー1杯分ぐらいでしょうか。

謝辞

京都大学ウェブ文書リードコーパス(KWDLC)」のデータを公開してくれた京都大学の黒橋研究室の皆さんありがとうございます! LLMの進化のスピードが非常に早い昨今ですが、ベンチマークデータがあれば、自分たちの現在地がどこにいるのか、モデルの変化は性能にどう影響したのか、把握することができます。言語資源の公開は重要さは今後ますます上がっていくと思います!

再現性の担保(コードの公開)

今回のChatGPTさんの出力や、評価用のコードは以下の場所にあげておきました。

https://github.com/meihuno/evalOfPASAbyChatGPT

ChatGPTさんの結果は同じこと繰り返すとちょいちょい変わってくるし、OpenAIのモデルもAPIのversion upも行われてますし、こんな評価コードを残して意味あるんですかね?と思わないでもないです。ですが、世の中の大学生さんには「ChatGPTをテーマに卒論書いたろ!」みたいな人がいると思います。いてると思います。項構造格解析に関してはワタクシがここまでやってきたことを「全部」残しておきます。Youはワタクシの失敗の先に進んでください。そんな人達の参考になれば幸いです。

「1番かんたんな機械学習の教科書を頼む!」な厳選3冊

数学が苦手でも勉強が進む機械学習の本3冊と「現存!古代生物史 パッキー」

<枕詞>

あー、過去にブログ記事を書いてから(2017年の2月から)、6年もたってしまいました。

その間にブログの更新はなく、自分の怠惰っぷりがやばいっすです。人生終わっちゃう。世界の激動の変化もまじやばいっす(コロナとかAIの進歩とか色々ありましたね)。

その間、私はかつて作りたいとか言っていた「文の芥川龍之介度判定器」とか「テキストで記述されている内容が架空のものなのかリアルなのか判定器」とか「テキストの人称視点ズレ検出器」とかを作っていたわけではないのです。何もしてません。

じゃあ何やってたんだよ。生きていた(ような気がします)。本当、何してたんでしょうね。怖いですね。

でも、sevenseasやまほがく(http://www.sssloxia.jp/) という定期更新ゲームは遊んでいましたよ。あと、イバラシティも遊んでましたよ。楽しかったですね。

さて、

<この記事は数学が苦手な人でも読みやすい機械学習の本を紹介します>

それでも、ワタクシはひっそりと機械学習の勉強は続けていたのです。昨今話題の機械学習の仕組みを理解したくて、数学の本も読んでみたのです。機械学習を理解するには数学の理解・知識が必要です。

ですが、ワタクシは数学が苦手なのです。数字で物事を理解できないタイプなのです。2のほうが1よりも2倍も大きいわけですが、1位と2位とですと、1位のほうが価値が大きそうですね。2のほうが1より必ず大きいわけではないのです、不思議だなぁとか思われます。ワタクシは数字とその意味がなかなか頭でスパッと理解できないポンコツなのです。

それゆえにか、いろんな機械学習の本を読みましたが、なかなか理解できませんでした。あの有名な黄色の本(https://www.maruzen-publishing.co.jp/item/?book_no=294524)とか、さっぱりわかりませんでしたよ。

難しい教科書を読もうとしても、「現存!古代生物史 パッキー」で高校数学に挑戦したパッキーさんやスティギーくんみたいな感じに頭にぴよぴよしてしまうのです。

「現存!古代生物史パッキー」(レツ先生著)の1巻より。引き算やかけ算を愛するパッキーとスティギーは高校数学の授業にチャレンジするものの、内容が高度すぎて理解できずにピヨピヨしてしまう・・・。

メモ:「現存!古代生物史 パッキー」(レツ先生著、集英社)(https://bookwalker.jp/series/165117/は現代の竹田家に居候するパキケファロサウルスのパッキーと仲間の古代生物たちが繰り広げる面白日常を描いた漫画なのです。小学生レベルの数学力しかもっていないパッキーさんとスティギーさんが高校数学の授業に果敢にチャレンジしたのだが頭ぴよぴよになってしまうというエピソードがあるのです。勉強はコツコツ積み上げが大事!

難しい機械学習の本をがんばって読んだとしても、自分の頭の数学力がその教科書に達していなければ、書かれている事柄が理解できないものなのです。理解できなければ、プログラムで実装して、実際の世界で役に立てることもできません。教科書は自分の力量にあったものを選ばなければ努力に対するリターンが期待できないものなのです。

「1番かんたんな機械学習の教科書を頼む!」

というわけで、ワタクシ、かんたんな機械学習の教科書を探して読んでみました。この記事では、数学力に自信のないワタクシでも「これならわかるかも!」と感じた機械学習の教科書について、備忘ログを残しておきます。

数学力のないワタクシが求めたという3冊の教科書。

  1. 「やさしく学ぶ 機械学習を理解するための 数学のきほん」(LINE Fukuoka 株式会社 立石賢吾 著 マイナビ
  2. 自然言語処理シリーズ 1 言語処理のための 機械学習入門」(高村大也 著、奥村 学 監修、コロナ社
  3. 「ゼロから作る Deep Learning Python で学ぶ ディープラーイングの理論と実践」(斎藤 康毅 先生著、O’Reilly


ここからワタクシが何を学んだかをメモ書き程度に書き記しておきます。数学力よわよわ人類が「本を手にとる」参考になればと思います(本の内容の解説はしていません。内容については個々の書籍を直接ご参照ください)。


「やさしく学ぶ 機械学習を理解するための 数学のきほん」(LINE Fukuoka 株式会社 立石賢吾 著 マイナビ)で学んだこと

book.mynavi.jp

この本は機械学習の基本ムーブが学べます!

  • 何かを予測するモデルの例(まずは多項式から)
  • 2次元上にプロットされたデータを線を引いて分ける白黒分けるイメージ(分離曲線)
  • 奥義!最小二乗法!
    • 最小二乗法は、予測式のパラメータをデータから更新する方法である!
    • 損失関数:正解とデータとの差分の二乗を表す関数。実際のデータと理想との誤差がなく、誤差が少なくなるのがよい
    • データ全体で誤差が最小になるようにパラメータ調整する。二乗なのは計算する上で都合がよいから
    • 損失関数を微分する。微分とは変化の度合い。損失関数が0に近づくようにパラメータを更新する
    • データでモデルで表現して、正解との差などを微分を駆使して最小化させる、これが機械学習基本ムーブだ!
  • 後々にもつながる知識
    • 複数の変数がある誤差関数の偏微分には、合成関数の微分法を用いる。モジュールみたいな感じ
    • 確率的勾配降下法では、パラメータ更新時にランダムに1つのデータを用いる。局所最適に陥らないようにする効果がある!
    • Logistic Regression(ロジスティック回帰)はクラス分類の確率を予測するモデル
      • 線形分離不可能なデータに対しても分離曲線が引ける
(やさしく学ぶ 機械学習を理解するための 数学のきほん のまとめ)

この本ではアヤノおねーさんとミオおねーさんの会話を通じて機械学習でできることとその理屈を学ぶことができるのです。会話形式だと「何のためにやっていることなのか」というコンテキストがすんなり理解できるような気がします(気がする)。線形モデルやLogistic Regressinといった機械学習統計学におけるよくよくみる手法について、数式を追いつつ、Pythonでの実装も学ぶことができます。手を動かすのは大事ですね。

「やさしく学ぶ 機械学習を理解するための 数学のきほん」は機械学習を学びたい人の入門本としてはベストの1冊なのではないでしょうか? なお、高校数学程度の微分は出てくるので、その辺が怪しい人は高校の数学の復習をしよう。

自然言語処理シリーズ 1 言語処理のための 機械学習入門」で学んだこと

www.coronasha.co.jp

この本では、以下のことが学べます。

  • 機械学習を学ぶ上で基礎になる数学的知識
    • 凸計画問題
      • 関数の最大値や最小値を与える変数値と関数値を求める方法のこと
      • 不等式制約凸計画問題」とか名前がかっこいい
      • グランジェ未定乗数法とか機械学習の本によくよく出てくるやつの解説がわかりやすいぞ
      • (備忘録)方程式は数式を解くみたいな解き方(解析的に解く)では解けないものもあって、試行錯誤しながら値を求める方がある。最適化手法では、機械学習でどっちの方向に数字をいじっていけば良い方向なのか?の道標を与えてくれるわけである。
    • 確率の基礎(確率分布の解説がコンパクトにまとまっています)
      • ベルヌーイ分布はロジスティック回帰(Logistic Regressin)とかで出てくる
      • 正規分布はいろんなところ(統計学の本や統計学以外の本(医学の本とか)で)で何度も何度も出てくるので、何度も何度も勉強しよう
    • 情報理論
  • 文書をベクトルにする方法(文書を数学で表す方法)
    • 機械学習で行う言語処理の超ベーシック手法「Bag Of Words(BOW)」
    • 似たような文書のまとまりを作るクラスタリング手法
      • もちろん「k-means」の解説もあるぞ
    • このへんの知見は時代を超えて有効だ。BOWとか現役ですもんね
  • 奥義!「EMアルゴリズム
    • EMアルゴリズムとは観測されたデータから未観測の事象の確率値を推定する方法。統計的な言語処理でよく見かける手法(他のジャンルでも活躍しているはず)
  • Support Vector MachineSVM)の最適化問題を解いてパラメータ更新式を導出する方法
    • 今でも現役なSVMの理論と成り立ちをコンパクトに学べる
    • 実際にSVMで解を効率的に求めるためのソルバーが研究されており、逐次最小最適化(SMO)という方法があるそうなのですが、本書ではその詳細までには踏み込んでいない模様
  • テキストに対する系列ラベリング
    • (備忘録)テキスト(token列)から固有名詞を抽出する、品詞を割り当てるといった問題はシーケンス(系列)ラベリング問題として解かれ、Hidden Markov Model(HMM)や(linear-chain)Conditional Random Field(CRF)といった手法が適用されてきました
    • CRFとは?
      • 対数線形モデル(最大エントロピーモデル)を系列ラベリング問題に適用したもの
    • CRFの学習時のパラメータ更新方法
    • 前向き・後ろ向き(forward-backward)アルゴリズムによるCRFでの推論方法(信念伝搬)
  • 言語処理システムの評価についての基礎知識が学べます
    • 評価尺度(F値など)のマクロ平均とマイクロ平均の違いについて例が示されている
  • 言葉の使い方について
    • 系列ラベリング問題ではなく通常の分類問題に対して「CRFを用いた」と述べるのは誤解を招くので「対数線形モデル」まはた「最大エントロピーモデル」を用いたと述べるべきであること(著者の高村先生の言葉の使い方に対する熱い思いを感じられてよかった)
(CRFについてのメモ)

系列ラベリング問題(固有名抽出や品詞の割り当てのなど)には、かつて(90年代〜2010年代)多くの場合でCRFが適用されていました。深層学習やBERTなどの事前学習モデルが流行っている最近(2020年代)ではどうなんでしょうね?

系列ラベリングにおける確率値の計算方法である前向き・後ろ向き(forward-backward)アルゴリズム」の考え方の一般化したものが確率伝搬法(belief propagation)です。確率伝搬は、現在盛んに研究・開発されている、深層学習の基本となる技術だそうでして、技術は連なっているんだなーと思いました。

(言語処理のための 機械学習入門まとめ)

首都大の小町研究室では学生さんはまずこの本で機械学習ベースの自然言語処理の勉強をされるそうです(https://cl.sd.tmu.ac.jp/prospective/prerequisite)。コンパクトながら機械学習の分野で頻出する概念がまとめられていて、類書や論文を読んでいて「あ!『言語処理のための 機械学習入門』で見たやつや!」ということも多いと思います

特にConditional Random Fields(CRF)の解説は、学習時の更新式の導出や推論方法が丁寧に解説されていまして、わかりやすくてオススメです。ここのところ深層学習ベースの論文の方が増えているので、CRFとかのモデルはあまり見かけないかもしれませんが、系列ラベリング問題という考え方、最適化や確率分布や評価方法などの基礎的な知識は腐らないと思います。

全ての例題と演習問題には回答がついてきます。全部とこう! ワタクシも全部解きました! なお、分類モデルの演習問題はちょっと歯ごたえがあり、ワタクシには歯が立ちませんでした。数式の添字の意味が追いきれませんでした(要復習)。

「ゼロから作る Deep Learning Python で学ぶ ディープラーイングの理論と実践」

www.oreilly.co.jp

こちらは深層学習に入門するために読んでみました。この本では以下のことが学べます。

  • 損失関数とか機械学習の基本
    • 最小二乗法とか勾配の計算とか、「やさしく学ぶ 機械学習を理解するための 数学のきほん」や「自然言語処理シリーズ 1 言語処理のための 機械学習入門」で出てきた概念も出てきます。いろんな本で何度も出てくる考え方は、普遍性があるということです。何度も学んで理解を深めるのです。
  • 奥義!計算グラフで理解する誤差伝搬方!
    • 計算グラフとは数式の計算を、ノードの演算子(+とか*(かけ算)とか)、数値がリンクの繋がりで表したもの
    • 複雑な計算もグラフでバラバラに表現すれば、末端は簡単な四則演算で計算できる
    • 計算結果はリンクによって次のノードに伝えられて最終結果となる
      • (奥義)微分値は計算グラフを逆方向にだどることで計算できる
      • 複雑な数式の微分は合成関数の微分で解く。合成関数の微分は分けた関数の微分の結果を逆に連鎖させることで計算できる(連鎖律)
      • 計算グラフの逆伝搬によって各ノードの微分が計算できる、ニューラルネットワークの数式の勾配がグラフの逆伝搬により効率的に計算できるのだ(誤差逆伝播
    • 計算グラフによって誤差逆伝播を説明するアイディアはAndrej Karpath氏のブログ、Fei-Fei Li教授のスタンフォード大のディープラーニングの授業(CS231n)が原典らしい
  • 畳み込みニューラルネットワーク(Convolutional neural network :CNN)の仕組み
    • 画像の検出や分類でよく使われているCNNでの畳み込みを体感する。(ベクトルの)大きな数字の方眼の四角が小さなな方眼の四角に畳まれて行ったのをみたよ・・・。
  • BatchNormalizationの仕組み
    • ニューラルネットワークの初期値をうまく設定する方法。学習が早くなり、過学習を抑止する効果がある。深層学習の基礎技術
      • なお、Batch NormはGoogleが特許を取得している模様。Tensorflowで使えばお咎めはないらしい
(ゼロから作る Deep Learning のまとめ)

この本では深層学習の基礎をpythonのプログラムを作ることを通して学べると思います。特に、計算グラフで誤差逆伝播の仕組みを説明する5章に、ワタクシは感動いたしました。「ゼロから作る Deep Learning」シリーズは続刊も発売されてますので、続けて勉強したいと思います。

(本備忘ログまとめ)

この3冊をしっかり学べば、数学がちょい苦手な人でも、(深層学習含む)機械学習の「息吹」「呼吸」を体得できるのではないかと、ワタクシ、確信しているのです。数学とプログラミングや実データの面から学びを深めることができます。3冊ともとっても良い本です。

そして、本で読んだりしたことは、実践しなければ、です。モノを作って自分や他の人の役に立てなければ、です。これこそ技術の基本ムーブ。次の備忘ログでは最近のモダンなライブラリを使ってテキスト分類問題に取り組んでみたいと思います(ライブラリ使うなら数学学んだ意味はあるのだろうか?(自問)。まあいいか。多分ある。)

余談、Logistic Regressionちゃんについて

あと、3冊の本を読んで勉強した気になっていたのですが、備忘ログをまとめるにあたり再読したところ、本の内容は結構忘れていて、知識はなかなか身につかないことを実感いたしました。最近、別の本でLogistic Regressionちゃんのことを勉強していて「こんな方法があったんだ〜、ワタクシ、Logistic Regressionちゃんのこともっと知りたいな」とか思っていたのですが、実はワタクシは「やさしく学ぶ 機械学習を理解するための 数学のきほん」ですでにLogistic Regressionちゃんのことは知っていたのです。すっかり忘れてたよ。

というか、他の統計学の本で学んだのですが、Logistic Regressionちゃんは回帰分析と同じく一般化回帰モデルという一形態みたいな記述があり、ひょっとしてワタクシ、もっと大昔から(統計学の本で読んで)Logistic Regressionちゃんとは知っていたのでは?(そしてきれいに忘れていたのでは?) 

これはギャルゲイムでいうところの「そのキャラは幼馴染だったんだけど主人公は忘れてしまっていた」みたいなパターンだなと思いました。ワタクシたちは、幼馴染エンドを迎えるためにも、やはり、教科書は今の自分の実力や理解力にあったものから読んだ方が良いよね?と思いました。

終わり。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(動作テストと反省会)

2.5.3 mavenプロジェクトの取り込みテスト

NGramプログラムを作成環境とは別の環境で使えるまでをテストした経緯について書いておきます。といっても、別のPCのeclipseで作成したmavenプロジェクトのpom.xmlを以下のように設定して、そのプロジェクトでNGramプログラムが動作することを確認しただけです。

 <!-- github上の NGramプログラムの maven リポジトリをセット -->  
  <repositories>
    <repository>
      <id>sentencegenerator</id>
      <url>https://raw.github.com/meihuno/sentencegenerator/mvn-repo2/</url>
      <snapshots>
   	<enabled>true</enabled>
   	<updatePolicy>always</updatePolicy>
      </snapshots>
    </repository>
  </repositories>
  
  <dependencies>
    
    <dependency>
      <groupId>nlp.sample</groupId>
      <artifactId>sentencegenerator</artifactId>
      <version>0.0.3</version>
    </dependency>

  <!-- 色々と省略 -->
  </dependencies>

サンプルコードはこちらになります。太宰治先生の「走れメロス」でNGramモデルを学習しています。

package sample.nlp.mytest.hello;

import nlp.sample.sentencegenerator.SentenceGenerator;
import nlp.sample.sentencegenerator.NGram;
import nlp.sample.sentencegenerator.CrossEntropy;

public class SampleTest {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  String dirname = "/home/sugihara/void/data/aozora_bunko/dazai_osamu";
    SentenceGenerator sg = new SentenceGenerator(NGram.retNGramModel(dirname, smoothingType));
    
  // 文生成、文の長さは100形態素まで。確率値が最大の文頭の形態素からスタートする。
    // P(c|a,b)の確率値で次の形態素を選択する際に最大の確率値をとるcが選択される。
    System.out.println(sg.retRandomSentence(100, true, true));

  // 文生成、文の長さは100形態素まで。確率値が最大の文頭の形態素からスタートする。
    // P(c|a,b)の確率値で次の形態素を選択する際にcの選択はランダム。
    System.out.println(sg.retRandomSentence(100, true, false));

  // 文生成、文の長さは100形態素まで。文頭の形態素はランダムサンプル。
    // P(c|a,b)の確率値で次の形態素を選択する際にcの選択はランダム。
    System.out.println(sg.retRandomSentence(100, false, true));

    // Backoff smoothing の Cross entropy
    String smoothingType = "backoff";
    CrossEntropy.showCrossEntropy(dirname, smoothingType);
    
    // Laplase smoothing の Cross entorpy
    String smoothingType2 = "laplase";
    CrossEntropy.showCrossEntropy(dirname, smoothingType2);
  }
}

結果は以下です。ちゃんと動作しています。生成された文は、まあ、それなりですね。文と文のつなぎは今回のプログラムのスコープ範囲外です。また、BackoffスムージングのCrossEntropyのが、LaplaseスムージングのCrossEntropyより小さいのでほっと一安心です。

<S>メロスは激怒した時、突然、目の前に一隊の山賊が躍り出た。</S>
<S>メロスも覚悟したのか、ついに憐愍を垂れてくれ、と思ったか、二度、三日間だけ許して下さい。</S>
<S>太陽も既に真昼時です。</S>
CrossEntropy of Backoff is H(L,M) (-7.383126980350443) -H(L) (-1.4018975110973793) = 5.981229469253064
CrossEntropy of Laplase is H(L,M) (-11.642169315239284) -H(L) (-1.4018975110973793) = 10.240271804141905

3 反省会

ひととーりNGramのプログラムはできたのですが、色々ダメな部分もあったのでその備忘ログを残しておきます。以下の点が特にダメだったと思います。

  • 3gramまでを想定した実装になっており4gram以上のことは考えていなかった

演習問題のためのプログラムだと思って手を抜いてしまいましたが、後々のことを考えて拡張性を担保しておけばよかったです。

  • NGramの形態素列をデリミタで区切った文字列で表現してしまった

NGramの形態素列を"3@メロス:は:激怒"のように、単語列の長さと単語列の文字列をデリミタ(@と:で)区切って表現し、HashMapのキーなどに用いていたのですが、このキーの処理をデリミタ文字列を用いたsplitで行っていたので、テキスト中にデリミタの文字(@や:)が含まれていると正しく動作しないという問題がありました。かなりひどい悪手だと思われたので反省しているのです…。プロダクトでこんな設計ミスしたらかなり大事でございます。NGram形態素列はちゃんとクラスをきってオブジェクトで扱い、IDなどで管理したほうがよかったよ…。

まあ、NGramに基づく言語モデルのツールを使いたい場合は、素直にkylmを使うのが安心だと思いますし、勉強になります。ありがとう、Graham Neubig先生、Xuchen Yaoさん…。

www.phontron.com

さて、長々とNGramのプログラムを作って遊んでまいりましたが、ぼちぼち終わります(NGramには飽きてきたよ)。でも自然言語処理、面白いですね。今後も勉強を続けていきたいです。固有名詞抽出やゼロ代名詞解析ついてプログラム作って遊んでみたいと思います。また、機械学習手法、特に、Deep Learningもかじってみたいです。

そしていずれは、言語処理を利用して、楽しく小説や随筆が書けるツールを作ってみたいです。例えば、「文の芥川龍之介度判定器」、「テキストで記述されている内容が架空のものなのかリアルなのか判定器」、「テキストの人称視点ズレ検出器」などですね。役に立つのかわかりませんが、楽しくなってきました。うむ、明日もがんがろう。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(プログラム作成の一般的方法論 ドキュメンテーション/リリース の段)

2.4 仕様書の作成(ドキュメンテーション

前回までの内容でNGramモデルのプログラムの設計と実装も終わり、Unit Testも通りました。ここからはプログラムの公開のために行った作業についての備忘ログを書いていきたいと思います。

自分が作ったプログラムを他の人に公開する際には、プログラムのドキュメントも合わせて公開します。ドキュメントには、クラスについてはその責務を、メソッドの引数、返り値などについて説明されているとよいでしょう。

ラピッドプロトタイピング時や研究用のプログラムなどは、作るのが精一杯で、ドキュメンテーションの必要性を感じないこともあるのですが、ドキュメントは忘れた時の備忘ログにもなりますし、後輩に研究を引き継ぎぐときにも役立つなど、色々とうれしいことがあります。ドキュメンテーションは優しさなのです。

eclipseJavaプログラムを書いた場合には、Javadocドキュメンテーションを行うのが楽です。JavaDocドキュメンテーションの心得についてはこちらが勉強になりました。ありがとうございました。

qiita.com

Javadocの書き方については以下のサイトが参考になりました。ありがとうございました。

www.javadrive.jp

こちらのサイトも参考にいたしました。ありがとうございました。

promamo.com

eclipseのFile>ExportでJavadocを選択すればソースコードのコメントを元にJavadocが生成されます。せっかく公開するのならドキュメンテーションは英語で行ったほうが良いのです。英語ですと世界中の人たちの眼にとまることになります。ですが、今回は日本語で記述しています。太宰治走れメロスは日本語ですし、形態素解析はkuromojiの日本語解析なので、まあ、今回は、妥協したのです…。

なお、(後段の節で手順を書いていますが)今回のNGramのプログラムはgithubのほうにもアップしていて、そのdoc下にJavadocが格納されています。

https://github.com/meihuno/sentencegenerator

2.5 リリース(インテグレーション)

自分のプログラムが完成したらgitやsvnにcommitして他の人たちにソースコードを公開します。

複数のプログラムやモジュールで構成されるシステムやプロダクトの開発ですと、ここからそれらのプログラムやモジュール群を結合して、システムとして複数のモジュールが正しく協調動作しているかのチェックを行います。これを結合テストといいます。しかし、今回は言語処理の教科書の演習問題のプログラムなので、何かのシステムの一部というわけではないのです。

今回はNGramプログラムを以下の手順で公開したのでその手順を公開しておくことにします。

githubmavenリポジトリの設定(pom.xml
githubmaven プロジェクトをdeploy
maven プロジェクトの他のプロジェクトへの取り込みテスト

2.5.1 githubでの maven リポジトリの設定(pom.xml

NGramのプログラムは最初からeclipsemavenプロジェクトとして作成していきました。このソースコードgithubにcommitして、maven プロジェクトのリリース物をgithub上のmaven リポジトリに deloyしていきたいと思います。

まずは、pom.xmlgithubmavenリポジトリの設定を行います「github maven」あたりで検索して検索結果に含まれたブログで参考にしたのは以下です。ありがとうございました。

m12i.hatenablog.com

注意点として、githubのユーザプロファイル周りの注意点はここを参考にしました。ありがとうございます。

yo1000.hateblo.jp

また、ワタクシの環境では、maven の setting.xmlには githubへのaccess tokenを設定しなければうまく設定できませんでした。その情報についてはこちらのサイトを参考にさせていただきました。

qiita.com

結局、ワタクシめの pom.xmlmaven リポジトリの関連の設定は以下のようになっています(抜粋)。

<groupId>nlp.sample</groupId>
<artifactId>sentencegenerator</artifactId>
<version>0.0.3</version>
<packaging>jar</packaging>

<name>sentencegenerator</name>
<!-- github の リポジトリ の url をセット -->
<url>https://github.com/meihuno/sentencegenerator</url>
  
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <!-- maven の setting.xmlにおける github関連要素名 をセット -->
  <github.global.server>github</github.global.server>  
</properties>

<!-- maven リポジトリ用の githubのブランチについて -->
<distributionManagement>
   <repository>
    <id>internal.repos</id>
    <name>Temporary Staging Repository</name>
    <url>file://${project.build.directory}/mvn-repo</url>
   </repository>
</distributionManagement>

<build>
  <plugins>
    <!-- JUnitなど他のplugin設定については省略しています -->

    <!-- maven delopy 用の設定 -->
  <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
     <version>2.8.1</version>
      <configuration>
       <altDeploymentRepository>internal.repo::default::file://${project.build.directory}/mvn-repo</altDeploymentRepository>
      </configuration>
  </plugin>

   <!-- github用の設定 -->
   <plugin>
     <groupId>com.github.github</groupId>
     <artifactId>site-maven-plugin</artifactId>
     <version>0.12</version>
     <configuration>
      <!--  <merge>true</merge> -->
                
      <!-- Git コミットメッセージ -->
      <message>Maven artifacts for ${project.version}</message>
      <noJekyll>true</noJekyll>
      
      <server>github</server>
           
      <!-- distributionManagement の url と一致させる -->
      <outputDirectory>${project.build.directory}/mvn-repo</outputDirectory>
           
        <!-- リモートブランチ名 -->
       <branch>refs/heads/mvn-repo2</branch>
       
       <includes><include>**/*</include></includes>
           
       <repositoryName>sentencegenerator</repositoryName>
       
       <!-- Github リポジトリユーザー名 -->
       <repositoryOwner>meihuno</repositoryOwner>
           
     </configuration>
     <executions>
      <execution>
        <goals>
          <goal>site</goal>
        </goals>
       <phase>deploy</phase>
      </execution>
    </executions>
   </plugin>
 </plugins>
</build>

2.5.2 githubmaven プロジェクトをdeploy

まず NGgramのプログラムのソースコードgithubにcommitします。

$ git init
$ git add .
$ git commit -m "modified pom.xml for adjusting repository url"
$ git remote add origin https://github.com/meihuno/sentencegenerator.git
$ git push -u origin master

なお、.gitignore に /target/を追加してjarファイルをcommitしないようにしたりしています。そして、remoteリポジトリに対してmaven deployします。この辺は参考にしたサイトさんの手順そのまんまです。

$ maven clean deploy

deployされたgithub上のmavenリポジトリはこちらです。リポジトリの名前がmvn-repo2というあたりに色々苦労の後が伺えてしまうのです。

GitHub - meihuno/sentencegenerator at mvn-repo2

maven で buildすると eclipse の JUnit4が通らなくなるのでmaven clean を行ってmaven の buildをリセットしたりと、色々とmaven様には苦労させられました。githubへもaccesstokenがないとうまくアクセスできたりできなかったり。maven!いいかげんにせんか!とシャウトすることもあったのです…。この辺は慣れなのでしょうか。

さて、最後に「maven プロジェクトの他のプロジェクトへの取り込みテスト」については新しい記事でログを残しておきたいと思います。反省会もそちらでいたします…

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(プログラム作成の一般的方法論 実装/テスト の段)

2.2 実装(の準備)

今回のNGramモデルによる文生成プログラムは、テキストからNGramモデルを学習して文生成を行うためのクラス群、CrossEntoropyを計算する機能を提供するクラスとして設計されました。クラス設計が終わりましたし、今回の文生成プログラムは小難しいふるまいは特に考えないことにしますので、いよいよJavaの実装にとりかかっていきます(いきました)。

Javaプログラム実装はeclipseで行いました。emacsとは違い色々とお世話をしてくれるのです。publicメソッドをprivateメソッドに切り替えた際の問題をすぐに教えてくれますし、Unit Test用のJUnit(後述)もドキュメンテーション用のjavadoc(後述)もすんなり使えます。eclipseさんは世話焼き系IDEだそうです(世話焼き系IDE:eclipseたん - 虎塚)。

以下には、プログラムの実装とテストに先立って行ったeclipseの設定について備忘ログを残しておきます。

  • 今回のプログラムのリリースはmaven経由で行いたいので、今回のプログラムはmavenプロジェクトとして作成します。
  • その際、Javaで利用可能な形態素解析プログラムであるであるkuromoji を dependency に加えておきます。
  • Unit test JUnit4でテストを組むことにしたいので、eclipse上のプロジェクトのPropertiesからJava Bulid Path で Add Library ボタンを押して、JUnit4を追加します。以下を参考にしました。

https://teratail.com/questions/30524

  • maven でのテストもJUnit4を使いたいので、以下を参照に設定を行いました。

MavenでJUnit 4をテストするためのpom.xml - Qiita

  • アサーションライブラリ(Unitテスト時に期待結果と実際の結果の比較用のメソッドを提供してくれます)のhamcrestもmaven経由で入れてみました。junitとhamcrestについては問題含みだということが以下のような情報がネットで見つかりまして、色々と試行錯誤しました。

hamcrestとJUnitの依存関係メモ | Futurismo
http:// http://d.hatena.ne.jp/namutaka/20130708/1373246628
JUnit4.11にしたら、Hamcrestが無いってエラーが出たよ・・・ - Qiita
Use with Maven · junit-team/junit4 Wiki · GitHub

その結果、mavenのpom.xmlのテスト関係の箇所は以下のようになっています(2016/11/30でjunit 4.11で試しています)。

 <build>
   <plugins>
       <plugin>
	<!-- maven surfire plugin は mvn test 時に実行される pluginである-->
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.4</version>
        <executions>
          <execution>
            <id>default-test</id>
            <phase>test</phase>
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <junitArtifactName>junit:junit</junitArtifactName>
              <encoding>UTF-8</encoding>
              <inputEncoding>UTF-8</inputEncoding>
              <outputEncoding>UTF-8</outputEncoding>
              <argLine>-Dfile.encoding=UTF-8</argLine>
              <skipTests>false</skipTests>
            </configuration>
          </execution>
        </executions>
        <!-- mvn test 時にJunit4を実行-->
        <configuration>
          <junitArtifactName>junit:junit</junitArtifactName>
          <encoding>UTF-8</encoding>
          <inputEncoding>UTF-8</inputEncoding>
          <outputEncoding>UTF-8</outputEncoding>
          <argLine>-Dfile.encoding=UTF-8</argLine>
          <skipTests>false</skipTests>
        </configuration>
      </plugin>
  <!-- 他のbuild 用 pluginについては省略 -->
  </build>

  <dependencies>
    <dependency>
    	<groupId>org.atilika.kuromoji</groupId>
    	<artifactId>kuromoji</artifactId>
    	<version>0.7.7</version>
    </dependency>
    <!-- junit4.12では hamcrest-all と junit の組み合わせでよいようです -->
    <dependency>
    	<groupId>org.hamcrest</groupId>
    	<artifactId>hamcrest-all</artifactId>
    	<version>1.3</version>
    	<scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    	<scope>test</scope>
    </dependency>
  </dependencies>

ともあれこれでJUnit4でUnitTestができるようになったので実装準備は完了です。実装とは、UnitTestなのです(なのでした)。

2.3 テスト(実装とデバッグ

プログラムが期待通りに動くのかはテストされなければなりません。それはプロダクトでも、研究用コードでも変わりません。品質を満たさないプロダクトは市場には出せませんし、検証されていないプログラムでは論文は書けないのです。

また、Regression test (プログラムを修正変更した場合に過去に実施したテストを再度実施して結果に影響がないかを確認するテスト)は、今回のように教科書を読みながら試行錯誤しながら実装する場合に有効です。ワタクシも、各クラスのメソッドの期待動作についてUnit Testを書き、教科書を読みながらあーでもないこーでもないとプログラムを実装したのです。おかしなコードを書くと、テストが通らなくなるので、デバッガを起動してデバッグします。ブレークポイントをセットして、変数の中身を確認しながら何がおかしいのかの調査を繰り返しました。


f:id:meihuno_huno_san:20170114083236p:plain:w400
eclipseでのデバッグ風景。虫ボタンを押し、ブレイクポイントをセットし、停止時に変数を見るのです

JUnitでのUnitテストの書き方について解説しているサイトは数多あるのです。ワタクシはそのようなサイトを眺めて勉強しながらテストを書いていきました。テストクラスの直上に@付きのアノテーションを付与してテストの動作を定義します。@BeforeClassアノテーションはテストを行う前の準備のためのアノテーションです。@Testがテストメソッドになります。以下のサイト様が参考になりました。ありがとうございます。

Junitコードの書き方 - code snippets

テストクラスを Run As で JUnit Test を選択すれば、テストが実施されます。バーが緑なら全テストは成功、赤な場合はError/テスト失敗であることを表します。


f:id:meihuno_huno_san:20170114091257p:plain:w600

以下、BackOff Smoothingの実装をデバッグをしていてはまったポイントを備忘ログとして残しておきます。

頻度がゼロになる場合のGoodTuring推定値の補正値

頻度rの補正値の r^{*}計算には、頻度r+1の単語の総頻度数が必要になります。ごく少ない数の文でUnitテストを組んでいた時に、頻度1の単語列しかカウントできず、頻度2の単語列が得られず、rの補正値が計算できないというとほほな状況になりました。確率的言語モデルの教科書には、 N_{r}が得られない場合の補正値の計算方法が紹介されていました。ワタクシも以下の回帰直線を用いて N_{r}の補正値 S(N_{r})を計算しました(mとbの推定はさぼった)。

 logS(N_{r}) = -m log r + b (m, bは定数)

文の生成確率の計算

文の生成確率を以下のように計算すると文生成確率の和が1になりません。

 P(w_1,w_2,w_3,...,w_n) = P(w_1)P(w_2|w_1)P(w_3|w_1, w_2)...P(w_n|w_{n-1}, w_{n-2})

文生成確率の計算には条件付き確率の和で行います。3gramで行う場合は文頭はbigramになるが、文頭の記号 \verb|<| S \verb|>| を2つ準備するなりして文生成確率の和が1になるようにします。

 P(w_1,w_2,w_3,...,w_n) = P(w_1|\verb|<| S \verb|>| )P(w_2|\verb|<| S \verb|>|, w_1)P(w_3|w_1, w_2)...P(w_n|w_{n-1}, w_{n-2})

今回の記事はまだ続くのです…

全てのメソッドのUnitTestがグリーンでしたら、実装は終了です。しかし、プログラム作成のお仕事はまだ残ってるのです(「StrategyパターンでNGramモデルの文生成器を作った時の思い出(プログラム作成の一般的方法論 ドキュメンテーション/リリース の段)」に続く)。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(プログラム作成の一般的方法論 設計の段)

2.プログラム作成の一般的な方法論

今回のNgramモデル作成Javaプログラムですが、企業や研究機関でのよくあるソフトウェアの書き方に則って作成してみました。すなわち、プログラム保守のしやすさや品質を維持するために、以下の作法に則ってプログラムを書いた次第です。

1. 設計
2. 実装
3. テスト
4. 仕様書の作成(ドキュメンテーション。普通は1と平行して行う)
5. リリース(インテグレーション)

RubyPythonなどのスクリプト言語の使い手には「書きながら(設計やアルゴリズムを)考えてる!」という豪の者もいるそうなのですが、今回はJavaですし、設計図からプログラムを起こしたのでした。

2.1 設計

NGramモデルのあらましはわかりました(わかったことにしましょう)。「走れメロス」でNGramモデルを学習して、走れメロスっぽい文生を生成するを文生成器を作りたいと思います。では、どのようなポリシーでプログラムを作るべきなのでしょうか?

このポリシーを決定することがソフトウェアの設計のスタートです。ワタクシもこのポリシーに従ってソフトウェアが実現するべき機能(機能要件といいます)や性能的な特徴など(非機能要件といいます)を定め、ソフトウェアの静的な構造(どんなクラスで作るか)や振る舞い(オブジェクト間の呼び出しの手順)を決めていきます(今回はクラス設計までを行います)。

さて、今回はBack Off smoothing によるNGramモデルのプログラムを作成しますが、smoothing methodには他にも色々ありますし、NGramではない言語モデルもあります。将来的にはいろんな手法を試してみたい、と思うのです。そこで、ワタクシ、TINときたのです。「キン肉マンⅡ世」25巻(ゆでたまご著)、Page69で、戦況に応じて戦術(ストラテジー)を変えるBエボリューションズの2人のことを。

戦術(ストラテジー)パターンだ!と。

戦況に合わせてStrategyを選択するBエボリューションズのケビンとスカーフェイス(「キン肉マンⅡ世」25巻(ゆでたまご著)、Page69)

パターンとはデザインパターンのことです。過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものなのです(Wikipediaより)。

今回はStrategyパターンを採用します。Strategyパターンでは、様々なルールやアルゴリズムを、場合に応じて使い分けられるようにします。該当アルゴリズムの実装(この場合はモデルやスムージング手法)からアルゴリズムを個別に切り出します。色んなスムージング手法を試したい今回のような場合にぴったりです。

デザインパターンについては以下の本で勉強してみました。

オブジェクト指向のこころ(Alan Shalloway先生、James R.Trott先生著)
オブジェクト指向のこころ (SOFTWARE PATTERNS SERIES)

Ruby によるデザインパターン(Russ Olsen 先生著)
Rubyによるデザインパターン

StrategyパターンによるNGramモデル実装のためのクラス図も書いてみました。

NGramモデルは確率計算用のWordSequenceProbクラスを持ってはいますが、計算アルゴリズム自体はWordSequenceProbクラスにまかせています。スムージングの個別のアルゴリズムはWordSequenceProbクラスを継承したサブクラス内で行っています。新しいスムージング手法を試したい場合はWordSequenceProbクラスを継承したサブクラスを追加していけばよい、というわけです。

また、クラス図を書くとどのクラスがどのクラスに依存しているかなど言葉ではなく視覚でとらえることができます。クラス図をレビューすることで保守性の高いメンテナンスしやすいソフトウェアの構造に近づけていくこともできます。レビューはもちろん他の人にしてもらうのが一番ですが、ぼっちでもできます。

例えば、今回のクラス構造も最初はSentenceGeneratorクラスがFindFileを使用する形になっていましたが、ワタクシがクラス図をせっせと書いていると、NGramからもFindFileクラスを使用していることが白日の元にさらされたのでした。結果、テキストデータの読み込みはNGramにまかせて、SentenceGeneratorクラスからのFindFileクラスへと伸びていた依存の線はなくなったのでした。プログラムを書く時は最初にかるくでいいのでクラス図を書くと将来のめんどーごとを減らせるかもしれません。

さてさて、これでプログラムの設計ができあがったということにして、いよいよ実装です。記事は「StrategyパターンでNGramモデルの文生成器を作った時の思い出(プログラム作成の一般的方法論 実装/テストの段)」に続くのです。

なお、クラス図などのUML記述ツールはChangeVisionさんの [提供終了] 無償UMLモデリングツール astah* community を使わせてもらっています。astahは業界標準UMLツールなんじゃないでしょうか。いつもお世話になっております。

StrategyパターンでNGramモデルの文生成器を作った時のおもいで(NGramモデルのあらまし)

前回のブログ更新から随分と日があいてしまいました。ワタクシのブログ更新の頻度は多分年一回とかそんなレベルなのではないでしょうか。何と生産力のないことでしょう(嘆き)。

さて、前回の記事では「JavaScript界のあらましをさらに勉強している」とか書きました。嘘ではなくてJavascriptは触っていて、Javascriptソースを処理して動作するJavascriptスクリプトを書いてみたけど、ソースを改行でsliceするfunctionを書いたらmerge-minify後にさっぱり動かない(注1)ことになって悲しみを知ったりとか色々あったのです。

しかし、今日はJavascriptではなくてJavaで書いたプログラムについて記事を書きたいと思います。JavascriptJavaは全然違う言語だということはIT業界では常識云々ですが、そのへんの話は置いておいて以下の内容で記事を書いてみたいと思います。

「StrategyパターンでNGramモデルの文生成器を作った時のおもいで」

注1: merge-minify。javascriptソースコードから可読性のためのスペースや改行を除去してソースコードをまとめて軽量化すること。ページの応答速度が改善させる。改行を手がかりに処理していたので改行をとっぱらったらまるで動かなくなる。とほほ。

背景

ワタクシは自然言語処理という分野に興味を持ってお勉強をしてみたのです。自然言語処理は人間が書いたテキストをコンピュータで便利に処理するための技術です。例えば、キーボードの入力を漢字に変換するかな漢字変換とか、テキストを検索する検索システム(GoogleとかApache Solrとか)、商品レビューでお客さんが商品を褒めているのダメだししているのかの判定をしたりとか、医学論文などから人工知能が学習して医療診断する(IBMのシステムが有名です)などが自然言語処理の応用システムと言われています。

そのような応用システムを自然言語処理の基礎技術、例えば、テキストの「文」から「単語の区切りや品詞の判定」をする形態素解析ですとか、主語や述語といった文法的な構造の解析(文の構文解析といいます)といった技術が支えているのです。

ワタクシもこの自然言語処理に興味があったのです。例えば、小説執筆支援用「芥川龍之介パワー判定器」を作ってみたいのです。そこで、自然言語処理の教科書としてあちこちででおすすめされていた以下の2冊の本で勉強してみることにしました。

「Speech and Language Recognition 2nd editionand」(Daniel Jurafskey and James H. Martin 先生著、以下SLR本)
Speech and Language Processing: International Version: an Introduction to Natural Language Processing, Computational Linguistics, and Speech Recognition

「確率的言語モデル(北研二 先生著)」
言語と計算 (4) 確率的言語モデル

今日は、SLR本のSection4 N-Gramsの演習問題(4.1から4.8まで)を解いて3-gramを生成するプログラムを書いたでその時の備忘ログを残しておきます。より具体的には、3-Gramの言語モデルをStrategyパターンによって実装し、太宰治先生の「走れメロス」で学習したメロス文自動生成器を作りました。

以下、

1. NGramモデルとスムージング手法(Back off smoothing)のざっくりした説明
2. プログラム作成の作成手順(設計、実装、テスト、文書化、リリース)

の順に記事を書いていきたいと思います。

1. NGramモデルのざっくりした説明

1.1 NGramモデル概説

NGramモデルは、人間が文章を書いたり話したりするその過程を、「文字や単語はその直前のN個の文字(や単語)で依存して生成されている」という仮定に基づいて確率モデル化したものです。NGramモデルは検索システムの検索、入力補完、誤字の補正など広く自然言語処理システムに応用されているそうです。

以下の例文を考えてみましょう。声に出して読んでみます。

Nana eats an X. (確率的言語モデルの3.1 NGramモデル入門より)

なんとなく、Xはappleやorangeな気がしてきます。eatsに続く言葉は食べ物でしょうし、an の次の単語は母音スタートなのです。この「気がする」をどう計算機でモデル化したらよいか、NGramモデルでは、これを単語列の条件付き確率として表現します。単語列 "eats an"の次に単語Xが生じる確率 はP(X|eats, an)の値として表現します(とりあえずXの2つ前の単語列までを考えます)。

その確率値は、コーパスから単語列の頻度をカウントしてきて、P(apple|eats an) = C(eats an apple)/C(eats an)のように推定できます(Cはその単語列の頻度を表します)。テキストの"eats an apple"は、"eats an ant"よりもたくさん出現しそうですから、P(table | eat an) よりも P(apple|eats an)のほうが高くなりそうです。

NGramモデルでは、ある単語(または文字)の出現確率をその単語(文字)を含めたN個、N-1個前までの単語(文字)列の条件付き確率で表現します。N=3のトライグラムの場合は、2つ前にまでの単語列の条件つけで確率の値を計算します。P(w3|w1,w2)のようにです。単語列の生成確率はトライグラムの条件付き確率で以下のように表されます( \verb|<|S \verb|>| は文頭を表す特別な記号です)。

 P(w_1,w_2,w_3,...,w_n) = P(w_1|\verb|<| S \verb|>| )P(w_2|\verb|<| S \verb|>|, w_1)P(w_3|w_1, w_2)...P(w_n|w_{n-1}, w_{n-2})

Nの数字は大きくなればなるほど、計算と確率値の保持が大変になります。Nが大きいと、学習用テキスト中に長い文があった場合、文中の単語列の並びのパターンを全ての記憶していなければなりません。そこで、NGramモデルでは、言葉の生成は単語をN-1個前までだけ見て決めればよいという近似を採用することで、文中の単語列の並び全ての保持を回避しています。

しかし、NGramモデルにはまだ弱点があります。N-1個前のみを見るという豪快な近似は、それよりも前方の文脈に影響をモデルに組み込むことはできません(これは別のモデルで解決が試みられているそうです。Long Short Term Memory を利用した Recurent Neural Networkなどです)を顧慮できない、または、ゼロ頻度問題なる問題があります。

ゼロ頻度問題とは、学習データにない単語列がテキストに1つでも単語があると、単語列の生成確率が0になってしまうというものです。例えば、「走れメロス」を学習データとしてNGramモデルの確率値を推定した時に、単語列の頻度のみを用いて確率値を計算すると、P("メロスは感激した。")のような、走れメロスの本文にない文の生成確率を計算しようとして、そのような文の並びは走れメロスの本文中にはないので(頻度をカウントできないので)、確率値は0になってしまうのです。

このゼロ頻度問題(加えて学習コーパス中の低頻度な単語列の扱い)に対して、未知語の確率値がゼロにならないための様々なスムージング手法が提案されてきました。その1つがSLR本の4.7「Backoff」で取り上げられている「Katz's の Back Off smoothing」 です。

1.2 Katz's の Back Off smoothing

Backoff smoothing手法では以下のように確率値を補正します(以下は3gramの場合を示します)。


{
\begin{eqnarray}
P(w_3|w_1,w_2) = \left\{ \begin{array}{ll} 
  P^{*}(w_3|w_2, w_1)  & (C(w_1,w_2,w_3) > 0 ) \\
  \alpha(w_1,w_2) P(w_3|w_2) & (C(w_1,w_2) > 0) \\
  P^{*}(w_3)  & (Otherwise ) \
\end{array}\right.
\end{eqnarray}
}

スターのついている確率値は以下による補正値です。

 P^{*}(w_3|w_1,w_2) = dC(w_1,w_2,w_3) C(w_1,w_2,w_3)/C(w_1,w_2)
 dC(w_1,w_2,w_3) = C^{*}(w_1,w_2,w_3)/C(w_1,w_2,w_3)

 C^{*}(w_1,w_2,w_3) は 単語列頻度の補正値であるGood Turing 推定値を用います。 w_1,w_2,w_3の出現数をr回とします。Good Turing 推定値は以下の数式で計算します。

 r^{*} = (r+1)N_{r+1}/N_r

この N_rはr回出現したn(この場合3)単語列の総数です。3単語の頻度が頻度0の時は、(おそらくより頻度の高いであろう)2単語列による確率値を用いて代替します。dCをかけた確率値の和は1になりません。そのディスカウントした確率値をゼロ頻度時の確率値として貯金しておくのがBack Off smoothing 法のミソになります。 \alphaはディスカウント係数で貯金した確率値を分配する関数です。まず \alphaを与えるために便宜的に \betaという量を考えます。

 
\beta(w_1, w_2) = 1 - \sum_{w_3:C(w_1,w_2,w_3) > 0} \frac { dC(w_1,w_2,w_3) C(w_1,w_2,w_3)} { C(w_1,w_2) }

これは、 w_1,w_2によって与えられる条件付き確率値の合計1から w_1,w_2によるディスカウント係数分を引いたものです(ゼロ頻度用の貯金)。 \alphaという関数で、 w_1,w_2に対してこの貯金を w_1,w_2,w_3がゼロ(または低頻度)の場合に割り振っています。

 \alpha(w_1,w_2) = \frac { \beta(w_1,w_2) } { \sum_{w3;C(w1,w2,w3)=0} P(w_3|w_1,w_2) }

以上がBackoffスムージング法の概要です。スムージング手法には他にも以下のようなものが色々とあります。

・線形補間法
・ワンカウント法
・カーリーネイスムージング方法

NGramモデルのプログラムについても、これら多様なスムージング手法の追加を想定した作りにしたいものです。また、言葉を生成する方法はNGramモデルだけではなくRecurent Neural Networkなどの他の方法もあります。NGramモデルと他のモデルの切り替えができたほうがよさそうです。このNGramのスムージング法がたくさんあるという事実が以下で述べるプログラムの書き方に影響を与えるのです。(次回、「StrategyパターンでNGramモデルの文生成器を作った時のおもいで(プログラム作成の一般的方法論 設計の段)」に続きます)