【Python】誰でもできる！LINEのトーク履歴からマルコフ連鎖でチャットボット作成【markovify】

# はじめに
LINEのトーク履歴からチャットボットを作成する方法を誰でもできるようになるべく丁寧に解説します。
マルコフ連鎖の詳細な解説は本記事では行わないため別の参考書等をご参照ください。

# 実装
以下ではコードの解説を行っていきます。GitHubの[レポジトリ](https://github.com/ishikawa08/markov_chatbot_from_LINE)も載せておくので併せてご活用ください。

## LINEのトーク履歴の抽出
まずはトーク履歴を入手しましょう。トーク画面の右上の矢印をタップし出てきた`設定`を押してください。（下図参照）
![Screenshot_20200218-131635.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363619/f10ea02e-da39-b5ba-6345-55339cf20f15.png)
次に，`トーク履歴の送信`（下図参照）を押します。トーク履歴がテキストファイル`.txt`でエクスポートされるので適当な場所に保存してください。自分は軽量なファイルはだいたいgmailの下書きに一時保存してPCからダウンロードしています。入手したデータは`raw_data.txt`として`data`ディレクトリ内に置いておいてください。
![Screenshot_20200218-131642.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363619/18cbf8b6-83b4-6953-c32e-d18acf8f3622.png)

## データ整形
入手したデータを見てみると以下のような形式になっていると思います。

```text:sample.txt
[LINE] huga子とのトーク履歴
保存日時：2020/2/14 15:15

2018/4/6(金)
22:45	hoge男	こんばんは！今日はありがとうございました！
22:58	huga子	こちらこそありがとうございました😊

2018/4/7(土)
00:40	hoge男	またよろしくお願いします！
```

名前や日付など無駄な文章が入ってしまっていますね。まずはこれを取り除きましょう。以下の作業はGoogle Colaboratory上またはJupyter Notebook上で行うと行いやすいと思います。コードの全文はGitHub上の`data_shaping.ipynb`をご参照ください。

使用する外部ライブラリは，`neologdn` (文章の表現を正規化するライブラリ) と`emoji` (絵文字を除去するライブラリ) です。以下のコマンドでインストールできますが，使わなくてもまともな出力になるようなので面倒な方は使わなくても大丈夫です。

- `pip install neologdn`
- `pip install emoji`

```python:data_shaping.py
import neologdn
import emoji

for i, line in enumerate(lines):
    # 西暦の行を削除
    if ('2018' in line) or ('2019' in line) or ('2020' in line):
        line = ''
    # 会話でないものを削除
    elif ('[スタンプ]' in line) or ('[ファイル]' in line) or ('[写真]' in line) or ('[動画]' in line) or ('アルバム' in line) or ('ノートに' in line) or ('通話' in line) or ('http' in line):
        line = ''
    # 時刻を削除
    if len(line) >= 4:
        if line[1] == ':':
            line = line[4:]
        elif line[2] == ':':
            line = line[5:]
    # 表現を正規化
    line = neologdn.normalize(line)
    # 絵文字を除去
    line = ''.join(['' if c in emoji.UNICODE_EMOJI else c for c in line])

# リストから空の文字列を削除
lines = list(filter(lambda a: a != '', lines))
```

次に発言を自分のものと相手のものに分けます。`hoge男`と`huge子`は自分と相手の名前に変えてください。

```python:data_shaping.py
# どちらの発言かを判定しリストに格納
my_name='hoge男'
partner_name='huge子'

my_remarks=[]
partner_remarks=[]

for line in lines:
    if line[0:3]==my_name[0:3]:
        speaker=my_name
        my_remarks.append(line.replace(my_name,''))
    
    elif line[0:3]==partner_name[0:3]:
        speaker=partner_name
        partner_remarks.append(line.replace(partner_name,''))

    else:
        if speaker==my_name:
            my_remarks.append(line.replace(my_name,''))
        else:
            partner_remarks.append(line.replace(partner_name,''))
```

最後に発言をtxtファイルに書き込みます。

```python:data_shaping.py
my_remarks_file_path = './../data/my_remarks.txt'
partner_remarks_file_path = './../data/pa_remarks.txt'

# 自分の発言をtxtファイルに書き込み
with open(my_remarks_file_path, mode='w') as f:
    f.write('\n'.join(my_remarks))

# 相手の発言をファイルに書き込み
with open(partner_remarks_file_path, mode='w') as f:
    f.write('\n'.join(partner_remarks))
```

以上でデータの整形は終わりです。お疲れ様でした。あと少しです。

## マルコフ連鎖
いよいよ用意したデータを用いて文章を自動生成します。
使用する外部ライブラリは`markovify` (マルコフ連鎖用ライブラリ) と`MeCab` (分かち書き用ライブラリ) です。以下のコマンドでインストールできます。今回は必ずインストールしてください。

- `pip install mecab-python3`
- `pip install markovify`

```markov.py
import markovify
import MeCab

my_remarks_file_path = './../data/my_remarks.txt'
partner_remarks_file_path = './../data/partner_remarks.txt'

with open(my_remarks_file_path) as f:
    text = f.read()

parsed_text = MeCab.Tagger('-Owakati').parse(text)
text_model = markovify.Text(parsed_text, state_size=3)

for _ in range(10):
    sentence = text_model.make_short_sentence(100, 20, tries=20).replace(' ', '')
    print(sentence)
```

以下に自分が試した際の出力例を示します。一応文章っぽくなっていますね。

```text:出力例
そんなことないんですよね...
ほぼ週1だと思う?って思ったけど昨日の夕飯焼き肉だった...
これからどうなるんだろう嫌とかでは.......?
```

# エラー（むしろ本題）
実は自分の発言データでは正常に学習できたのですが，相手の発言データを使うと以下のエラーが出てしまっています......使用できない文字が含まれているのかと思い色々と試したのですが一向に解決しません.......
どなたか原因が分かる方がいらっしゃれば教えていただければと思います。

```python:error
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-6-776ef0b0de18> in <module>()
     10 
     11 parsed_text = MeCab.Tagger('-Owakati').parse(text)
---> 12 text_model = markovify.Text(parsed_text, state_size=3)
     13 

/usr/local/lib/python3.6/dist-packages/markovify/text.py in __init__(self, input_text, state_size, chain, parsed_sentences, retain_original, well_formed, reject_reg)
     48             # Rejoined text lets us assess the novelty of generated sentences
     49             self.rejoined_text = self.sentence_join(map(self.word_join, self.parsed_sentences))
---> 50             self.chain = chain or Chain(self.parsed_sentences, state_size)
     51         else:
     52             if not chain:

/usr/local/lib/python3.6/dist-packages/markovify/chain.py in __init__(self, corpus, state_size, model)
     51         self.compiled = (len(self.model) > 0) and (type(self.model[tuple([BEGIN]*state_size)]) == list)
     52         if not self.compiled:
---> 53             self.precompute_begin_state()
     54 
     55     def compile(self, inplace = False):

/usr/local/lib/python3.6/dist-packages/markovify/chain.py in precompute_begin_state(self)
     97         begin_state = tuple([ BEGIN ] * self.state_size)
---> 98         choices, cumdist = compile_next(self.model[begin_state])
     99         self.begin_cumdist = cumdist
    100         self.begin_choices = choices

KeyError: ('___BEGIN__', '___BEGIN__', '___BEGIN__')
```



# おわりに
読んで頂きありがとうございました。
次はLSTM等を用いた対話型チャットボットを作成したいと考えていますが，自前の1対1の対話型のデータセットを作成するのは大変そうですね...自然言語処理は素人なので何か良いアイデアがあったら教えていただけたらと思います。