マルコフ連鎖でけもふれのセリフを生成する

12/24 クリスマスイブでございます。
皆さんは予定等ありますでしょうか？

僕は金曜に、年に一度行われる忘年会with運動会に参加したことにより全身がとてつもない筋肉痛に襲われており、歩くのはおろか立ち上がるのさえ困難な状況になっています。でも久々の運動は楽しかったです。

今年こそ彼女と一緒にクリスマスデートをしたいなと考えてましたが、もちろん彼女はできなかったので、筋肉痛とおうちデートしようと思います。

##マルコフ連鎖とは
[マルコフ連鎖でTwitter Botをつくりました](https://takuti.me/note/twitter-bot/)
この方の説明がわかりやすいかも。

## セリフのもととなるデータを取得する
https://github.com/akahuku/kemono-friends-dialogs/
ここからセリフを抜き出します。

```
$num = ["01", "02", "03","04", "05", "06","07","08","09","10","11","12","12.1"];
    foreach($num as $i) {
        $html = file_get_contents("https://github.com/akahuku/kemono-friends-dialogs/blob/master/{$i}.md");
        $doc = phpQuery::newDocument($html);

        $data = "";
        foreach ($doc["tbody tr"] as $row) {
            //各要素取得
            $keys  = pq($row)->find("td:eq(0)")->text();
            $value = pq($row)->find("td:eq(1)")->text();

            if (in_array($keys, $exclude) || strpos($keys, 'CAST') !== false) {
                continue;
            }

            $value = preg_replace('/\(.*\)/', '', $value); //()のついているセリフは取り除く

            $key   = explode('、', $keys);
            foreach ($key as $chara) {
                if ($chara == '.') continue;
                $data .= $chara . ',' . $value . "\n";
            }
        }
        file_put_contents('./kemohure.csv', $data, FILE_APPEND);
    }
```

```
サーバル,ふああ…。
かばん,はあ…はあ…うう…はあ…はあ…うう…。
サーバル,
かばん,あっ？
サーバル,うあー！ えひひひ！ いひひひ！ あははは！ うう！ いひひひ！ うひひ！ ああ！ うあ！ おーお！
かばん,うあーあ！ うわー！
サーバル,わーい！
かばん,どこここ？ なんでー？
サーバル,狩りごっこだねー？ 負けないんだからー！
サーバル,みゃー！ みゃー！
かばん,うう！
サーバル,みゃー！
かばん,う〜〜！
サーバル,あれ…？ 隠れちゃった…。
かばん,はあ…。
サーバル,あっ！ そこだー！ 
かばん,うっ…うう…た、食べないでくださーい！
サーバル,食べないよ！
かばん,うっ…うう…。
サーバル,ごめんね、あたし、狩りごっこが大好きで…あなた、狩りごっこあんまり好きじゃないけものなんだね。
```
こんなかんじで取得できました。


##文章を分ける
mecabというものを使用します。

```
brew install mecab
brew install mecab-ipadic
```

```
$ echo 'すもももももももものうち' | mecab
すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
```
このように使うことができます。

```
$ mecab
狩りごっこだねー？ 負けないんだからー！
狩り	名詞,一般,*,*,*,*,狩り,カリ,カリ
ごっこ	名詞,接尾,一般,*,*,*,ごっこ,ゴッコ,ゴッコ
だ	助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
ねー	助詞,終助詞,*,*,*,*,ねー,ネー,ネー
？	記号,一般,*,*,*,*,？,？,？
負け	動詞,自立,*,*,一段,未然形,負ける,マケ,マケ
ない	助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
ん	名詞,非自立,一般,*,*,*,ん,ン,ン
だ	助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
から	助詞,接続助詞,*,*,*,*,から,カラ,カラ
ー	名詞,一般,*,*,*,*,*
！	記号,一般,*,*,*,*,！,！,！
EOS
```
こうして分割したものを1つずつずらし、3単語ごとにまとめてテキストファイルなりDBなりに格納します。

##セリフを組み立てる
僕はMySQLに以下の構造で格納しました。

|id|character|str1|str2|str3|
|:--:|:--:|:--:|:--:|:--:|
|261|かばん|BOS|あ|…。|
|262|かばん|あ|…。|フレンズ|
|263|かばん|…。|フレンズ|さん|
|264|かばん|フレンズ|さん|…|
|265|かばん|さん|…|です|
|266|かばん|…|です|か|
|267|かばん|です|か|？|
|268|かばん|か|？|EOS|

1. 先頭に`BOS`で始まる行から1つをランダムで選択する。
2. 1.で選択した行の２個目、３個目の単語を検索条件として再度ランダムで選択する。
3. ３個目の単語が`EOS`であった場合はそこで終了する。

```
function findText($character, $word1, $word2, $text = '', $i = 0)
{
    $add_where_clause = $word2 != null ? ' AND str2 = ?' : '';
    $add_params = [$character, $word1];
    if ($word2 != null) {
        $add_params[] = $word2;
    }
    $sql = "SELECT character, str1, str2, str3 FROM kemohure WHERE character = ? AND str1 = ?{$add_where_clause} ORDER BY RAND() LIMIT 1";
    $ret = execSql($sql, $add_params);

    if(!$ret || ($ret->str1 != 'BOS' && $ret->str3 == 'EOS') || $i > 50)  {
        return $text;
    }elseif ($ret->str1 == 'BOS' && $ret->str3 == 'EOS') {
        return $ret->str2;
    }

    if ($i == 0) {
        $text .= $ret->role . ':' . $ret->str2 . $ret->str3;
    } else {
        $text .= $ret->str3;
    }

    $i++;
    return findText($character, $ret->str2, $ret->str3, $text, $i);
}
```

で、出力結果

サーバル

```
サーバル:うみゃ、うみゃみゃ！みゃ…、あっ。
サーバル:でも、これで安心しても、はむ、食べないよ！
サーバル:大丈夫だよ。ちょっと、やったやった！
サーバル:そうだ、火って見つかった？
サーバル:じゃあ、まず何があっても大丈夫だよ。
サーバル:おおっ？
サーバル:ボスの運転はさすがだね。かばんちゃんが！
サーバル:ううん、歩いてきたんだ。
サーバル:まさか！他にもいっぱいいるよ！わたし行ってみたい！
サーバル:どこから来たんだ。
サーバル:じゃあ、右だー！
サーバル:わたしはサーバルだよ。なんで、食べてね。
サーバル:あれ、セルリアンじゃないよ！逃げて！
サーバル:おおおー。
サーバル:でも、どうしてお話して図書館にいきたいところも…うう、待ってー！やるよー！
サーバル:さっきの、ツチノコ。
サーバル:料理？
サーバル:なにこれ？ちほーにも遊びに来た来たよ。いっつもこうして楽しいこと思いつくの。
サーバル:かばんちゃんは？
サーバル:じーっ。
サーバル:どこに？隠れるとこなんてないよ。大変な目にあったの？
```

**サーバル:あれ、セルリアンじゃないよ！逃げて！**
セルリアンじゃないんか〜い！

かばん

```
かばん:船に灯りで誘導します。
かばん:みゃ。みゃみゃみゃみゃみゃみゃみゃー！
かばん:うん。すごいね、フレンズさんだったの？
かばん:ちょっと思い付いたんですが、もうひとり？
かばん:ここに、サーバルさんのところ、探そうと思います。
かばん:ええー…。
かばん:わあ…うわあっ、そうなんですね。
かばん:えーっと…ラクダのこぶには、ここに何か…確か、四神って言ってみてはどうでしょう。
かばん:う、うわわ…。
かばん:うわあー。
かばん:ダメかー…。
かばん:ちょっと、ゆっくり掘った方が…。
かばん:ここで様子見よう。
かばん:わあっ、そうだね。ん？
かばん:逆かー…。
かばん:あはっ。
かばん:さっきの山が見えるよ。
かばん:はい…。
かばん:うーん…。
かばん:すごいですよねー。しっぽがぴょこぴょこして、しばらく寝かせます。じゃあ…。
```

**かばん:ちょっと思い付いたんですが、もうひとり？**
全滅END

キャラ指定無しでランダム

```
ツチノコ:はあっ、あれ？これは期待できますね。
かばん:逆かー…。
ジャガー:おおおおっ！
サーバル:すっごくよかったね。なにここー。
ライオン:さー誰だったね。ふへへ。どうぞどうぞ、ゆっくり掘った方が、わたしに大口勝負を挑むなんて、相当ダメな動物なのだ。実は、こういう編み編みがほしいであります！
ジェーン:う、うん。
サーバル:照れてるのー？
かばん:あのー、ここ渡りたい子がかばんさんだよ。
サーバル:わあ、器用ですね。
かばん:鬱蒼としてきたね…これって、サーバルちゃんって呼んでいるところまで、あの…。
ラッキービースト:サーバル。この辺はわたしだ。図書館までのルートを検索するね。
かばん:ここ、何か…ありがとうございます。
アミメキリン:オオカミさんは事件を見るに、声を…？
アライグマ:あっ、上手く行ったぞ。
ラッキービースト:まず大きく5つの気候帯に分離…。ふっ。
ライオン:生まれは？
コツメカワウソ:うわあ…！わたし行ってみたい！
かばん:大丈夫ですから。
サーバル:何して、こう。

```

**ラッキービースト:サーバル。この辺はわたしだ。図書館までのルートを検索するね。**
お前だったのか

##結論
なかなか楽しい┗(^o^)┛ﾄﾞｺﾄﾞｺ┏(^o^)┓ﾄﾞｺﾄﾞｺ
クリスマスはこれで１日過ごします。