【Python+MeCab】マルコフ連鎖と形態素解析を使って本田圭佑っぽいTwitterつぶやきを自動生成してみた話

#はじめに
以前、TwitterでBotを作ってアフィリエイターとして活動していた頃は、
文章は全部、自分で編集なんかしてて、めちゃくちゃ時間かけてたけど、

`2021/5/13`に初めて、
**形態素解析とマルコフ連鎖を使えば、自動で文章が生成できる**ことを知り、感動したので、
実践してみました。

ちなみに、前の会社でインターンをやっていた大学生の子が、
自分のTwitterアカウントのつぶやきを解析させて、
自動生成したつぶやきをBot化していたので、
まずは「自分もエンジニアになったからには、そのスタートラインに立とう！！」
と思い立った次第であります。

####※最初、座学多めです。下の方にコードあります。🙆‍♂️

#形態素解析とは
難しい漢字が並んでてゲシュタルト崩壊したわっww
と言いたいところですが、
>形態素解析（けいたいそかいせき、Morphological Analysis）とは、文法的な情報の注記の無い自然言語のテキストデータ（文）から、対象言語の文法や、辞書と呼ばれる単語の品詞等の情報にもとづき、形態素（Morpheme, おおまかにいえば、言語で意味を持つ最小単位）の列に分割し、それぞれの形態素の品詞等を判別する作業である。
参照元：[Wikipedia](https://ja.wikipedia.org/wiki/%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90)

ふむふむw
**「けいたいそかいせき」**
だそうですw

##形態素解析ソフトのMeCabはこんな感じに文章を分解してくれる
```bash
$ echo "魚を買う。" | mecab
魚	名詞,一般,*,*,*,*,魚,サカナ,サカナ
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
買う	動詞,自立,*,*,五段・ワ行促音便,基本形,買う,カウ,カウ
。	記号,句点,*,*,*,*,。,。,。
EOS
```
イメージ掴めた。(⌒▽⌒)

###`MeCab`のインストールはこちらを参照ください。
https://qiita.com/ichi_zamurai/items/6311614f83fc30dd28d2

#マルコフ連鎖とは
>マルコフ連鎖（マルコフれんさ、英: Markov chain）とは、確率過程の一種であるマルコフ過程のうち、とりうる状態が離散的（有限または可算）なもの（離散状態マルコフ過程）をいう。また特に、時間が離散的なもの（時刻は添え字で表される）を指すことが多い（他に連続時間マルコフ過程というものもあり、これは時刻が連続である）。マルコフ連鎖は、未来の挙動が現在の値だけで決定され、過去の挙動と無関係である（マルコフ性）。各時刻において起こる状態変化（遷移または推移）に関して、マルコフ連鎖は遷移確率が過去の状態によらず、現在の状態のみによる系列である。特に重要な確率過程として、様々な分野に応用される。
参照元：[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%AB%E3%82%B3%E3%83%95%E9%80%A3%E9%8E%96)

ま　る　で　な　に　を　言　っ　て　い　る　の　か　、　全　く　わ　か　ら　な　い　。　。　。

わざわざ分かち書きしてしまうほどでしたorz

##実際にマルコフ連鎖でやること
例えば、下記の4つの文章がある時、
まずは形態素解析で分かち書き出力（文章において語の区切りに空白を挟んで記述すること）したものを用意し、辞書を作る。

###文章
- `魚　を　買う　。`
- `魚　は　好き　。`
- `魚　は　かわいい　。`
- `猫　は　かわいい　けど　高い　。`

###マルコフ連鎖の辞書
一つ一つの単語をスライドしていって登録することになる。
1~3語の三つの組み合わせがブロック。（※単語数は可変にできるはず。）
「名詞」「形容詞」「動詞」は、文章の意図を示していることが多いと想定され、始点の単語で選ばれるようにする模様。

| 1語目 | 2語目 | 3語目 |
|:-:|:-:|:-:|
| 魚 | を | 買う |
| を | 買う | 。 |
| 魚 | は | 好き |
| は | 好き | 。 |
| 魚 | は | かわいい |
| は | かわいい | 。 |
| 猫 | は | かわいい |
| は | かわいい | けど |
| かわいい | けど | 高い |
| けど | 高い | 。 |

####単語の組み合わせと選択ロジックの例
1. 始まりが「`魚　は`」の場合、次に「は」から始まるブロックを探す。
2. 「`は　好き　。`」「`は　かわいい　。`」「`は　かわいい　けど`」からランダムに続くブロックが決定される。
3. 同様の作業を「（文末）」がくるまで繰り返す。
4. 「`魚　は　かわいい　けど　高い　。`」、「`猫　は　好き　。`」といった文章がランダムに生成される。

ちなみに、
**1ブロックあたりの単語数が多くなればなるほど、元の文章に近いものが生成される。
一方で、少なくなればなるほどぐちゃぐちゃな文章になる**ようです。

###なんとなく分かってきたので、、、


#マルコフ連鎖とMeCab形態素解析でやりたいこと
##本田圭佑っぽいツイッターつぶやきを生成しちゃおう！！！！

###ファイル構成
```text
app
├── text_editor.py     # テキスト精製
├── make_dictionary.py # 辞書作成
├── tweet.py           # つぶやき生成と出力
└── text
    ├── keisuke_honta.txt     # 精製前のRawデータ
    ├── new_keisuke_honta.txt # 精製後のテキストデータ
    └── learned_data.json     # 辞書データJSONフォーマット
```

###事前準備
```bash
$ pip install markovify
$ pip install mecab-python3
```

###本田圭佑の名言的なつぶやきを取得する
まだTwitterAPIの申請が降りないので、
[All My Tweets](https://www.allmytweets.net/connect/)を使って、ツイートを取得しよう。
![スクリーンショット 2021-05-14 18.16.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/40a8075f-abc5-bdec-4d8b-0a35a99df27d.png)

####公式アカウント+Bot3つからテキスト取得

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">どうなりたいのか。そこから全てが動き出す。</p>&mdash; Keisuke Honda (@kskgroup2017) <a href="https://twitter.com/kskgroup2017/status/1363786280551636997?ref_src=twsrc%5Etfw">February 22, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

[本田圭佑 bot](https://twitter.com/hondak_bot) @hondak_bot
[本田圭佑bot](https://twitter.com/ksk_nonrotation) @ksk_nonrotation
[本田圭佑bot](https://twitter.com/hondahanpanai4) @hondahanpanai4

##つぶやきテキストを精製してキレイなテキストファイルに

###`text_editor.py`のコード
純粋な日本語の文章だけ欲しかったので、
`RT @warpspace`とか`Apr 19, 2021`とかその辺を除去する。

```python:text_editor.py
import MeCab
import re

text = open("./text/keisuke_honda.txt","r").read()

# Replace Bad Symbols for markovify to function
# Refer: https://github.com/jsvine/markovify/issues/84
table = str.maketrans({
        # '\n': '',
        # '\r': '',
        '(': '（',
        ')': '）',
        '[': '［',
        ']': '］',
        '"':'”',
        "'":"’",
    })
text = text.translate(table).split()
url_pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"

for i in range(10): # remove()を使ってるから10回くらいやると綺麗になった
    for line in text:
        if re.match(url_pattern, line): # URL
            text.remove(line)
        elif bool(re.search(r'[a-zA-Z0-9]', line)):
            text.remove(line)
        elif re.match('^@.*', line):
            text.remove(line)
        elif re.match('.*,$', line):
            text.remove(line)
        elif re.match('^#.*', line):
            text.remove(line)
        elif re.match('RT', line):
            text.remove(line)

# Parse text using MeCab
m = MeCab.Tagger('-Owakati')
f = open('./text/new_keisuke_honda.txt', 'w')
for line in text:
    splited_line = m.parse(line)
    f.write(str(splited_line))
f.close()
```



####精製前
ごちゃついてるなあ。。。

```text:keisuke_honda.txt
RT @warpspace_inc: 【プレスリリース】 宇宙フロンティアファンドや @kskgroup2017 が率いるKSK Angel Fund LLC、SMBCベンチャーキャピタル産学連携2号投資事業有限責任組合等を引受先とした第三者割当増資による4億円の資金調達を実施… Apr 19, 2021 
Super guests visited our game yesterday! 🇦🇿 https://t.co/JHWX77DuVa Apr 18, 2021 
次は決める！ Apr 18, 2021 
I will be on @NowVoice_jp about yesterday's game at 14:15. Apr 18, 2021 
本当に必要な情報にたどり着くために、時間とお金をかけ続ける。 Apr 15, 2021 
...
```

####精製後
キレイになった。1行1行、分かち書きもされて、文末では改行も入ってる。

```text:new_keisuke_honda.txt
恋愛 っていう もの に は 必ず 喧嘩 を し て しまう と 。 
その 時 の 状況 に 寄っ て 最悪 別れ て しまう 人 も 居る と 。 
でも よく 考え て み て 下さい よ 。 
思い出 の 方 が 沢山 ある し 、 お互い 笑い 合っ た 数 の 方 が 多い ん や し 、 そんな 数少ない 喧嘩 で 負け て どう する ん や と 。 
...
```



##精製テキスト使って、マルコフ連鎖用の辞書登録

###`make_dictionary.py`のコード
`markovify.NewlineText（）`を使っているのは、
**句読点「。」で文章の終わりを示すために改行を入れて、1行1行にしたテキストをちゃんと読み込ませるため。**
`new_keisuke_honda.txt`でも分かる通り、こうすることでちゃんと学習されて辞書登録がスムーズにいくように。

ちなみに、英語とかだと`.`で勝手に判定されるから`markovify.Text()`でいいけど、日本語は少し工夫が必要。



```python:make_dictionary.py
import markovify

# Load file
text = open("./text/new_keisuke_honda.txt", "r").read()

# Build model
text_model = markovify.NewlineText(text, state_size=3, well_formed=False)

# Make Dictionary as Json_format
with open('./text/learned_data.json', 'w') as f:
    f.write(text_model.to_json())
```
- `state_size`は、1ブロックあたりの単語セットで、次の単語の確率が依存する単語の数のこと。[（参照元）](https://github.com/jsvine/markovify/issues/12)
    - 例えば、「`魚　は`」の方が、「`魚　は　かわいい　けど　高い　。`」よりも、後続のアイテムを見つけるのがもっと簡単になる。
    - **1ブロックあたりの単語数が多くなればなるほど、元の文章に近いものが生成される。一方で、少なくなればなるほどぐちゃぐちゃな文章になる**ようです。
- `well_formed=True`にすると、`markovify`で読み込むことができない単語があると`KeyError: ('___BEGIN__', '___BEGIN__')`が発生するから、`false`にしておくといい感じになる。
    - ただし、mal_formedで読み込みができない文章は無視される模様


####`learned_data.json`が出力される
一部のみ紹介

```json
{
  "state_size": 3,
  "chain": "[[[\"___BEGIN__\", \"___BEGIN__\", \"___BEGIN__\"],
             {\"\恋\愛\": 2, \"\そ\の\": 22, \"\で\も\": 31,
              \"\思\い\出\": 1, \"\ま\ぁ\": 13, \"\俺\": 191, \"\脱\": 1,
...
```

##マルコフ連鎖用に生成した辞書を使ってつぶやきを作成


###`tweet.py`のコード
```python:tweet.py
# -*- coding: utf-8 -*-
import markovify

with open('./text/learned_data.json', 'r') as f:
    text_model = markovify.Text.from_json(f.read())

for i in range(10): #10個のつぶやき生成
    print(text_model.make_short_sentence(140))
```

####自動生成されたつぶやきを見てみる
```
・自分は何でもかんでも理由をつけて勝ち上がっていくことが大事。
・好きな事だけではなく継続して、俺は次の試合に向けて自分を高めましたよね。。
・信じることっていうのは、僕にとって希望なんですよね。そういうサッカー選手としてだけでなく、批判してくれました👍
・ありがとうございます！ちょっと考えてください。仕事つくってブラジルに来てください😁🇧🇷⚽️
・おれもあいつらに“欲”という意味ではホントいい感じできてますけどね
・そろそろ皆さんも旅立ちの日ですと。だからなんだと。
・ボール来るなって言っても遅い
・革命家ですね。この壁だって神様に感謝しないと上にはいけない
・色々と周りが騒がしいけど、俺は選ばれてたのかなと。
・俺のパンチング見たか？ナイス飛び込みやろ。
・昔の選手も好きですけど、やはりみんなで戦っているんじゃないかという風に思いますね
・僕なんかゴール尽きまくってますよ。自分の情熱が弱かったと。
・あの白いユニフォームには特別な思いがありますよ。自分の用事が終わったら、すぐドリブルしようかと思っている。その二つは連動してはいるけど。
・嘘でもいいから「わかる」って言ってもなくならないし、正直
・スポーツを止めるな！素晴らしい活動は皆んなで応援したいというものに投資します！
・どこでプレーしても、自分の人生に誇りを持ってくれるまで突き進みますよと。
```

###本田圭佑っぽいwwwめっちゃ言いそうwww深良さがより深まっているwwww

エモいものありました？w


#ハマったポイント
- 最初のテキスト精製のところがなかなか難しかった。
    - `list.remove()`って最初にヒットしたものしか消さないので、何個もテキスト上にあると、ゴミが残ってしまうから、`for`を10回とか15回とか回す必要があるかも。
    - もっと効率の良い方法があるんだけど、突貫でやったからまだ考えきれていない。
- `markovify`の`state_size`の意味と、`well_formed`の意味がなかなか掴めなかったので、難しかった。
- `well_formed=True`にすると今でも、`key error`が出てしまうから、理由がまだ分かっていない。
    - `github`で言われている、シンボルたちは全て置換したんだけど、なんでだろう、、、、
- 日本語は、句読点を目印に、文章を1行ごと改行して、`NewlineText()`を使った方が相性が良いということを知らなかったから、テキスト精製のところで、つまずいた。


#感想とまとめ
できた時は、まじで、めーっちゃテンション上がった！ぶち上げwwww(☝︎ ՞ਊ ՞)☝︎

素直に面白いw
自分は本田圭佑のファンなので、

バーチャル本田圭佑が誕生した瞬間を目の当たりにして感動したwwwwwww


次の目標は

- twitterに生成したつぶやきを自動投稿する「本田圭佑っぽいことをつぶやくBot」を作成すること
- Twitter APIを使って、本田圭佑だけでなく、他の著名人もトライしてみること。
- いずれは`LSTM`とか自然言語処理系の機械学習モデルも使って挑戦すること。


最後に、`マルコフィファイ`ってまじで言いにくい。特に`vify`のところ、めっちゃ吐息出るわw

以上、ありがとうございました！！！