かもブログ

かもかも(@kam0_2)の雑記。地理やITに関するにわか稚拙センテンスの掃き溜め。

しりとりを科学する。 ~論理に基づいた最強のしりとり戦法を求めて~

長い文章を読みたくない人へ


このしりとりCPU・TwitterBotと戦ってみてね。

しりとりに必勝法はあるのか(ポエム)

 昔からしりとりが好きだった。しりとりは一見すると、勝ち負けもないようなただの言葉の言い合いに過ぎない。しかし、しりとりには明らかに「強い人」「弱い人」が存在する。どうすれば強くなれるのだろうか。
f:id:two_headed_duck:20200401191434p:plain  Googleで適当に検索してみると、色々出てくる。「る攻め」がいいとか「ぷ攻め」がいいとか。僕は昔「む攻め」を極めたし、友達には「や攻め」使いがいた。しかし、これらの言説に総じて言えるのは、「論拠の不足」であろう。感覚的に「〇〇が強い」というのは、信用に値するのであろうか。そんなことを日々思っていたときに、ある本に出会った。とつげき東北氏の「科学する麻雀」である。麻雀は「ツキと流れ」が支配する感覚のゲームと長い間思われていた。しかし、統計学やコンピュータシミュレーションを用いることで「麻雀必勝法」を科学的に導出できることをこの本は示している。しりとりの話から逸れてしまったが、僕はしりとりでも同じことが研究できるはずだと思ったのである。
 そこで、僕の稚拙なプログラミング能力を生かしてしりとりを科学的に極めてやろうと思ったのだ。僕はこの計画に「Ngok」という名前をつけた。ンゴックとはアフリカ、南スーダン共和国遊牧民族の名前であり、また、「Ngok is the Gear Of Kotoba-asobi」の頭文字である。GitHubにリポジトリを公開しているので、暇だったら覗いてほしい。とはいえ、人に見せるものではないような気もする。
 この記事はそんなくだらない遊びの中間報告である。書いている人の頭は良くない。

一般化してみる

 しりとりとはなんだろうか。流れを一般化してみよう。
 はじめに、単語というものを定義する。単語は「サーフェス」と「ヨミガナ」という属性を持つ。「サーフェス」というのは分かりづらい言い方だが、"見た目"のことだ。*1
 しりとりのルールを一般化しよう。しかし、ここで一旦制約をつける。しりとりは1人以上いれば遊べるゲームではあるが、今回の研究では2人でプレイするものだとする。3人以上になると、麻雀で言うところの「コンビ打ち」のように、誰かを蹴落とすための戦略性が生じてくるが、今回はそれを抜きにして、シンプルに2人対戦で考える。
 ここで、終端文字取得関数E(w)を考える。E(w)はルールによって定義が異なる。E(w)は単語を引数に取り、その単語の「終端文字」を返す。「終端文字」が1文字とは限らないことに留意しよう。次に敗北判定関数L(c)を定義しよう。L(c)は「終端文字」を引数に取り、これが敗北する終端文字であれば、敗北の判定を返す。*2
 次に、単語を記憶するリストWLを定義する。このリスト末尾に単語を追加する関数P(w)と、与えたられた単語(のヨミガナ)がリストの中に存在しないか、確認する関数C(w)も定義できる。C(w)はルールによって定義が異なる。「サーフェス」で区別するのか、「ヨミガナ」で区別するのかという問題である。そして、WLの末尾要素を取得する関数S()を定義する。
 最後に、プレイヤーが単語を思いつく関数G(c)を定義する。G(c)は終端文字を引数に持ち、その終端文字で始まる単語を返す。ただし、返す単語はユニーク(一意)であるとする。*3もし、返せないときは敗北とする。
 これまでの定義から、しりとりの1ターン、プレイヤーが自分の番にやるべきことは以下のように一般化される。

  1. x <- S() # 前回言われた単語を参照する
  2. c <- E(w) # その単語の終端を得る
  3. if L(c) { # ここでL(c)が真なら勝利する }
  4. w <- G(c) # 単語を思いつく、思いつかない場合は負けとするが割愛
  5. if !C(w) { P(w) } else { Goto 4 } # すでに言われた語でなければ、WTに追加する。そうでない場合は、考え直す。

1~5が完了すると、同じことをもうひとりのプレイヤーも行う。これを延々と繰り返すのがしりとりであると定義できる。このように一般化することで、しりとりシミュレータを作ることもできる。

単語の獲得

 しりとりをコンピュータ・シミュレーションする際に問題となるのは、G(c)の実装である。G(c)は「知っている単語の中でcから始まる単語を返す」部分と、「返された単語の中で何で答えるのがいいのかを戦略的に選ぶ」部分に分けることができる。ここでは前者、「知っている単語の中で~」すなわち、コンピュータにどのようにして単語を覚えさせるのかを検討する。
 今回の研究では、mecab-ipadic-NEologdという自然言語処理に使われるOSSの辞書を利用した。IPA辞書と呼ばれる基礎的な日本語の単語に加えて、mecab-ipadic-NEologdはインターネット上から最新の単語を取得し、辞書に追加するシステムを備えているため、最新のトレンド用語やマイナーな学術用語もカヴァーしている。オタク用語もバッチリである。
 しかし、問題もある。この辞書は形態素解析向けの辞書であるため、「表記ゆれ」の語が多く存在する。例えば、「鴨そば」も「鴨蕎麦」も「かも蕎麦」も「かもそば」もそれぞれ別単語として登録されているイメージである。これらを別単語と考えるのは無理がある。そこで、今回の研究では「ヨミガナユニークルール」を導入する。同じヨミガナの語は1度しか使えないというルールである。同音異義語は認めない。こうすることで、「カモソバ」1単語と捉えることができる。同音異義語を認めるルールの場合、同音異義語か否かをコンピュータが判定することは難しく、*4このルールはコンピュータを扱う上で合理的だと思われる。
 また、一般的(世間的)に、しりとりは名詞のみという原則がある。そこで、mecab-ipadic-NEologdから名詞に関わる単語を抽出した結果、2020/3/15の最終更新データでは1,982,873単語となった。広辞苑は約25万単語と言われており、約8倍の単語数である。(広辞苑は動詞とかも含んでいるが)ここまで多い単語数であれば、このデータは「日本語の単語母集団」に対する「十分な数の標本」と見ることができる。(筆者は統計に理解はない)この単語データはシミュレーションのコンピュータの記憶に使えるだけでなく、「日本語の単語全体」の傾向を掴むのにも使えるはずだ。
 「知っている単語の中でcから始まる単語を返す」はこれでもう実装できる。単語データが有れば、コンピュータにはあとは朝飯前である。筆者はSQLiteのデータベースに落とし込むことで、単語検索に十分なスピードが出るようにしている。返された単語の中で何で答えるのがいいのかを戦略的に選ぶ」に関しては、後述する。

○攻めについて

 しりとりの戦術といえば、「○攻め」である。巷にはさまざまな「○攻め」言説が溢れている。本当に強いのは何攻めだろうか。調べてみよう。

その前に、ルール定義が必要だ

 しりとりには様々なルールが有る。Ngokのプログラムは様々なルールに対応した各種関数を持っている。それらにはしりとりのルールを素数の積で指定する。

# モードは各オプションの積で指定します。
   # 何も指定しない場合、厳密に一致する必要があります。    
   # 相互交換可能オプション
   # 2 濁点<=>濁点              e.g.)帝国=>軍事法廷
   # 3 半濁点<=>濁点            e.g.)法被=>広島
   # 小文字に関するオプションは併用不可です。
   # 何も指定しない場合は、自動車=>社宅のようになります。
   # 5 小文字<=>標準文字        e.g.)自動車=>ヤクザ               
   # 7 小文字を除く             e.g.)自動車=>指紋
   # 11 小文字は負け            e.g.)自動車(負け)
   # 13 (予約)
   # 長音のルールも併用不可です。何も指定しないと、マヨラー=>ラーメンのようになります。
   # 17 長音は省く              e.g.)メーデー=>デビルマン
   # 19 長音は母音を取る        e.g.)メーデー=>エンブレム
   # 23 長音は負け              e.g.)メーデー(負け)
   # 29 (予約)
   # その他のルール
   # 31 「ン」で負けにしない。  e.g.)食パン=>ンゴック族
   # 37 ジ<=>ヂ                 e.g.)鼻血=>ジルコニウム
   # 41 ズ<=>ヅ                 e.g.)木更津=>ずんだ餅
   # 43 ヲ<=>オ                 e.g.)みつを。=>折り紙
# 推奨は2*3*5*17*43です。

今回は筆者の偏見で最も一般的と思われる2*3*5*17*43=21930で見てみよう。簡単に言えば「濁点半濁点無視」「小文字は大文字」「長音無視」「を=お」である。しりとりのルールは簡単なようで難しい。今回最も苦労したのは、あまたのルールに対応するE(w)を作ることであった。

Xで終わりYで始まる単語

https://github.com/comradeKamoKamo/Ngok/raw/dev/output_21930/cm.png?raw=true

 この図はX(横軸)で始まり、Y(縦軸)で終わる単語の数をヒートマップで表したものである。注意点としては、カラーバーが5000で頭打ちであるが、実際には5000以上の単語数を持つXYの組み合わせも多く存在する。
 これを見ると、日本語の単語には偏りがあることがわかる。よく言語学の領域では「日本語の単語にはら行から始まる単語は少ない」という説がある。すでに証明されてるだろうが、この図をつかってもたやすく証明することができる。逆に「な行で終わる言葉は少ない」という事実もわかる。
 ここで、二次元配列CMを定義する。CM[Y,X]でYで始まり、Xで終わる単語の数を返すものとする。これは以降の議論で頻繁に使用する。
 図を見ると、何攻めにしたらいいかのヒントが見つかるかもしれない、がもっと簡単な方法がある。「単語終始比率R」というパラメータを導入する。ある文字cについての単語終始比率R(c)は

R(c) = cで終わる単語の数の合計 / cで始まる単語の数の合計

とする。R(c)が高い値の時、その文字は「攻めやすく」相手が「返しづらい」ということである。これを各文字ごとに取得し、グラフにしてみよう。

https://github.com/comradeKamoKamo/Ngok/blob/dev/output_21930/rate.png?raw=true

これを見ると、一目瞭然、「る攻め」最強説誕生である。群を抜いている。次点が「う攻め」でこれは正直以外だった。普段から「う」で攻めていた人の話は聞いたことがなかった。僕の「む攻め」、友達の「や攻め」は悪くないが良くもない。

番外編:「ぷ攻め」

「ぷ攻め」が強いかどうか、21930のルールでは半濁点を無視するため、わからない。別途検証してみよう。ルールは5*17*37*41*43= 5544635 である。 https://github.com/comradeKamoKamo/Ngok/blob/twi_bot/output_5544635/rate.png?raw=true ぷが強いとか言ってた人はしりとりやめたほうがいいんじゃないかなと思える結果である。(別に悪くはない。)濁点半濁点無視をしない場合は、「る攻め」よりも「ず攻め」が有利である。これは僕の父親がよく使っていた戦法であり感慨深い。「ず攻め」の強さは圧倒的で、濁点半濁点無視でない場合はず攻めするしかない。が、基本は21930のルールで話をすすめる。

先行研究者様がいました。(追記)

単語分析に関して、先行研究者様がいました・・・。

active-galactic.hatenablog.com

qiita.com

驚くほどやり方が似てますけど本当に偶然です。データの扱いが違うのか、ルールが違うのか、やや結果が異なっていますね。

戦略

 しりとりは攻めるだけだろうか。その前提から検証してみることにする。まずは、戦術を実装しよう。

戦略論の前にガバイ理論

 「攻めるか」「守るか」これが基本である。ここで、「攻め方」「守り方」は極力シンプルに実装する。このような「攻守」の戦略を立てる際は、シンプルな戦術が有利であるということが一般的な気がするからだ。*5冒頭のポエムで科学がどうこう言っていたのに、この根拠もないガバイ理論を出して申し訳ない。
 要するに、各戦術は極力単純化して考えシミュレーションしてみようということである。しかし、各戦術が有効な戦術かどうかをしっかりと検証できる手法はない。「この戦術のが強い」という物があれば教えてほしい。

攻撃的戦術

 攻撃的戦術は「cで始まり、R(c)が最大となる文字で終わる単語を返す」ものである。それだけ。

防御的戦術

 防御的戦術は「cで始まり、CM[x,c]が最小となるような文字xで終わる単語を返す」とする。つまり、相手にまたcで攻められることを防ぐ。

中間戦術

 中間戦術は、

  1. rate = WLにおけるcで終わる単語数 / WLにおける総単語数 とし、
  2. 一様乱数randを0.0~1.0で発生させ、
  3. rate >= rand なら防御し、 そうでなければ、攻撃する。

というルーチンを取る。c攻めされている割合で防御する。

ランダム戦術

 ランダムは「cで始まる単語の中から、ランダムで1つ返す」という戦術である。

シミュレーション

 先程のしりとり一般化を用いて、シミュレーションができる。十分な回数のシミュレーションを行った。*6
 結果はどうやっても、攻撃的戦術が勝利した。防御的戦術は攻撃的戦術に対して逆効果だったのだ。(防御的戦術はランダムよりは圧倒的に強いため、効果がないわけではない。)この時点で、中間戦術は意味がない。中間戦術は防御に勝ち、攻撃に負けるからである。ランダムはどの戦術よりも悪かった。
 防御的戦術の失敗は、結局の所、防御といっても「○攻め」になってしまうからであろう。実際には「る攻め」に対して「ぬ攻め」で返していた。防御のためとはいっても、効率の悪い攻め方であったら負けて当然なのである。
 以上の結果から、しりとりにおいては「攻め」は圧倒的な王道戦術と言える。また、ランダム同士の対戦シミレーションから「先攻」「後攻」に特に優劣はないこともわかった。*7

まとめ

 以上のことから、しりとりの最強戦術はここで述べた「攻撃的戦術」である。つまり、「○攻め」であり、その攻め文字は、ここで示された単語終始比率が高い順に行えばいい。あとは、単語を覚えるだけと言える。それだけである。つまらないゲームですね。
 しりとりは突き詰めると面白くなくなるという実感が以前からあったが、真実だったようだ。

本当にそれだけ?

 本当にそれだけだろうか。コンピュータは単語の意味を理解できないため、上のような結果になっている。しかし、実際には、ある人はずっと同じジャンルの言葉で返す、とかそういうのがあるはずである。ここからは、科学的に検証できないが、筆者が強いと思う戦い方である。つまり、ネット上の根拠のないしりとり戦術言説であり読まなくても良い。

  • 返す単語は相手の単語と関連した単語のがよい。相手の語彙を減らせる。
  • 関連語のないときはなるべく短い単語が良い。短い単語は相手も思いつきやすい。

根拠がないので、本当かわからない。

最強?のしりとりCPU「ンゴック君」

以上を踏まえて、作成されたしりとりCPUがンゴック君である。これと対戦して、強さを体感してみてほしいと思う。「本当にそれだけ?」の作戦も盛り込まれている。word2vecという機械学習アルゴリズムは、コンピュータに「関連語」を理解させる手助けになる。これを利用し、可能であれば関連語を返してくれる。ぜひ遊んでみてほしい。

今後の展望

 今後があるかもわかっていない。考えているのは、人名や外来語を禁止したときにどうなるのかといったルールを制限した場合である。そのような際には、「る攻め」神話が大きく崩れ、ほんとうの意味での「しりとり」が見えてくるかもしれない。
 ンゴック君に関しては、word2vecよりも強力な機械学習モデルを導入するなどしてみたいと思っている。BERTの話です。

謝辞

 ここまで読んでくれてありがとね!

*1:見た目とは、例えば、漢字で表記したときの単語の見た目である。しかし、口頭でしりとりを行う際はそれはアクセントや声量を絡めた発音方式を含むかもしれない。後述するが、今回の研究では「ユニークヨミガナルール」を採用しているため、サーフェスはさほど重要でない。アクセントや単語の意味によって、同音異義語を容認するルールの場合は重要である。

*2:通常は「ン」であるが、ルールによっては異なる。長音や「ヂ」を敗北とする場合など。

*3:細かいことはそんなに気にしないでほしい。こうしないと4-5で無限ループして兵庫県警に捕まってしまう。

*4:「君」と「黄身」はサーフェスで区別できるが、例えば、地名としての「四日市」と市としての「四日市」は区別が難しい。あまりいい例ではないかも。

*5:リチャード・ドーキンスの「利己的な遺伝子」でそんなのを読んだ気がする。

*6:ただし、コンピュータにとってはサーフェスはどうでもいいため、単語を使わずに、配列CMの数字を引き算するだけで実際はシミュレーションする。ヨミガナユニークルールの利点である。また、次の注で述べるとおり、中間戦術とランダム戦術が絡まないシミレーションは回数を重ねる必要はない。

*7:攻撃的戦術同士を戦わせると、必ず後攻が勝つ。しかし、これは意味のない結果で、攻撃的戦術や防御的戦術は乱数要素を含まないため、勝敗は単語データの単語数の運命で決まるからである。