環境
- Python 3.7.0 (pyenv でインストール)
開発環境
- macOS Mojave 10.4
- Atom 1.31.2
公開環境
- Ubuntu 18.10
各種ライブラリなど
mecab-python3==0.7
twitter==1.18.0
作ったもの
@HEISEIKUN_ という意味わからないことをつぶやくBotです。
following の TL から学習させて、意味わからないことをつぶやかせてます。
例えば、
算数好きな人は違う土俵
— へいせいくん (@HEISEIKUN_) 2018年10月30日
キム・ジウン監督が操縦な
— へいせいくん (@HEISEIKUN_) 2018年10月30日
ITキャリア18万点5局
— へいせいくん (@HEISEIKUN_) 2018年10月31日
小中高IFてご覧あいつ。
— へいせいくん (@HEISEIKUN_) 2018年10月31日
😱💦あかん💦
— へいせいくん (@HEISEIKUN_) 2018年10月30日
と、脈略ないけどまあとりあえずまともに喋ってたり、
シウマイ鳩サブレーは統一世界を置きまで、絶対た☺と道中のゲームをちょこちょこて英語たりイメージネーションした は山陽線、29歳に見合う等でくれるれそうますなくてた僕で配信の動向はここ形Sound Horizonなってもするで働く唯一がパニック
— へいせいくん (@HEISEIKUN_) 2018年10月30日
github無職issues将来を幸せになるててに人間~)」あおぞら銀行ほうが悪かっされ。
— へいせいくん (@HEISEIKUN_) 2018年10月30日
当たり前な文系もコミュニティにpushした知識イカ経験を取らしているさに見絶対ます)や強いの間を使いせる引かもちょっとC問題を で修行られるか看護師
— へいせいくん (@HEISEIKUN_) 2018年10月29日
(権利能力なき社団書いているます好きな人#かぼすの幅で頑張るない
— へいせいくん (@HEISEIKUN_) 2018年10月29日
このように意味のわからないこと言ってたりします。
Python 3 を使いました。
解説... の前に、一つ。
記事の内容では理論など細かい部分はすっ飛ばしてとりあえず完成させるということだけに集中しています。
正しいこと言ってるわけでもありません。マルコフ連鎖もどきかも。
そしてできるだけ手抜きします。
ソースコード全部はこちらになります。
https://github.com/yu-san-19/Borscht
え? 環境構築してコピペしていろいろとめんどい?
そういう方々のために、PyPI にてモジュール化して公開しました!
$ pip install borscht
Github: https://github.com/yu-san-19/Borscht
PyPI: https://pypi.org/project/borscht/
Reference: https://yu-san-19.github.io/Borscht/
こんな感じで楽々すぐできます。
# Import borscht.
import borscht as bor
# Text used for chain generation of Markov chain
text = """
親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。
なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。
新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。
小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。
親類のものから西洋製のナイフを貰って奇麗な刃を日に翳して、友達に見せていたら、一人が光る事は光るが切れそうもないと云った。
切れぬ事があるか、何でも切ってみせると受け合った。そんなら君の指を切ってみろと注文したから、何だ指ぐらいこの通りだと右の手の親指の甲をはすに切り込んだ。
幸ナイフが小さいのと、親指の骨が堅かったので、今だに親指は手に付いている。しかし創痕は死ぬまで消えぬ。
庭を東へ二十歩に行き尽すと、南上がりにいささかばかりの菜園があって、真中に栗の木が一本立っている。
これは命より大事な栗だ。実の熟する時分は起き抜けに背戸を出て落ちた奴を拾ってきて、学校で食う。
菜園の西側が山城屋という質屋の庭続きで、この質屋に勘太郎という十三四の倅が居た。
勘太郎は無論弱虫である。弱虫の癖に四つ目垣を乗りこえて、栗を盗みにくる。ある日の夕方折戸の蔭に隠れて、とうとう勘太郎を捕まえてやった。
その時勘太郎は逃げ路を失って、一生懸命に飛びかかってきた。向うは二つばかり年上である。
弱虫だが力は強い。鉢の開いた頭を、こっちの胸へ宛ててぐいぐい押した拍子に、勘太郎の頭がすべって、おれの袷の袖の中にはいった。
邪魔になって手が使えぬから、無暗に手を振ったら、袖の中にある勘太郎の頭が、右左へぐらぐら靡いた。
しまいに苦しがって袖の中から、おれの二の腕へ食い付いた。痛かったから勘太郎を垣根へ押しつけておいて、足搦をかけて向うへ倒してやった。
山城屋の地面は菜園より六尺がた低い。勘太郎は四つ目垣を半分崩して、自分の領分へ真逆様に落ちて、ぐうと云った。
勘太郎が落ちるときに、おれの袷の片袖がもげて、急に手が自由になった。その晩母が山城屋に詫びに行ったついでに袷の片袖も取り返して来た。
この外いたずらは大分やった。大工の兼公と肴屋の角をつれて、茂作の人参畠をあらした事がある。人参の芽が出揃わぬ処へ藁が一面に敷いてあったから、
その上で三人が半日相撲をとりつづけに取ったら、人参がみんな踏みつぶされてしまった。古川の持っている田圃の井戸を埋めて尻を持ち込まれた事もある。
太い孟宗の節を抜いて、深く埋めた中から水が湧き出て、そこいらの稲にみずがかかる仕掛であった。
その時分はどんな仕掛か知らぬから、石や棒ちぎれをぎゅうぎゅう井戸の中へ挿し込んで、水が出なくなったのを見届けて、うちへ帰って飯を食っていたら、
古川が真赤になって怒鳴り込んで来た。たしか罰金を出して済んだようである。
"""
# 夏目漱石 「坊っちゃん」より
cg = bor.ChainGenerator(text) # Generate instance of class that generates chains
cg.dump("./chain.json") # Dump json file written about chains' info.
tg = bor.TextGenerator("./chain.json")
# Generate instance of class that generates sentence.
for i in range(10):
sentence = tg.generate() # Generate sentence
print(tg.generate())
0-a. マルコフ連鎖って何だ?
僕が参考にしたサイトです。
マルコフ連鎖の話
https://dev.classmethod.jp/machine-learning/markov-chain/
マルコフ連鎖を使って〇〇っぽい文章を自動生成してみた
https://www.pc-koubou.jp/magazine/4238
[作り方を徹底解説] 人工知能が書いたAI入門ブログ
https://ai-kenkyujo.com/2017/10/31/markov-chain-commentary/
一応、最終的には数式のあたりで完全理解やめました。
かろうじてわかったことは、分かち書き(形態素解析)して、
ある単語の組み合わせの次にくる形態素の確率を求める。
例えば、
「明日は」 という単語 (2つになってるけど) に対して、
- 「晴れ」 50%
- 「曇り」 20%
- 「雨」 10%
- 「飴」 5%
- 「祭り」 1%
- そのほか 4%
みたいな統計が取れたとすると、とりあえずそれに則って繋げていけば自然(なはず)だよねーといった感じに理解しました。(間違ってたら適宜コメント or 編集リクエストください)
0-b. どうやって作るのか?
とりあえず、確率の統計にしたがって単語を選んでく様子をどう表現するかで迷いました。迷ったら 「Github 見ましょう。あそこは宝庫です...」 という天の声が聞こえたので速攻検索。
そしてちょうどいいもの見つけました。
https://github.com/o-tomox/TextGenerator
とりあえず読んで参考にする程度にしました。
あらーくまとめると、
1. 学習元のテキスト収集
2. 形態素解析をする
3. 3つ連続した形態素の三つ組を作る
4. 出てきた三つ組を全部配列Aに代入
5. なんか最初の単語を一つ勝手に選ぶ
6. 配列Aからその単語で始まる三つ組全てを抽出して別の配列Bに代入
7. 配列B からランダムで選ぶ
8. 選ばれた3つ組の最後の単語から始まる三つ組全てを抽出して配列Bに代入
9. 文の終わりまで繰り返す
10. できた文を Twitter へ投稿
と言った感じにすればいいようです。
0-c. ディレクトリ構成
root/
├ - get-tweets.py
├ - post-tweets.py
├ - ChainGenerator.py
└ - TextGenerator.py
1. 学習元のテキスト収集
学習用のデータが必要なのですが、やはり作る上では、現在な言葉を使ってて、比較的軽くて話し言葉がいい。と、勝手に決めてました。
どうするか。
Twitter という素晴らしいツールがあるではないか!
Twitter の API 申請も結構ドキュメントあるので割愛します。
さて、 TL の取得ですが、 REST API なので、普通にAPI叩くためのModule使って叩いてもいいです。
ですがTwitter用のModuleがあったみたいなので、それ使います。
$ pip install twitter
from twitter import *
import json
import re
twitter_clients = Twitter(
auth=OAuth(<ACCESS_TOKEN>, <ACCESS_TOKEN_SECRET>, <API_KEY>, <API_SECRET>)
timelines = twitter_clients.statuses.home_timeline(count=200) # 最大200件TLを取得
with open('tweets.txt', 'a') as f:
for tl_tweet in timelines:
content = tl_tweet['text'].replace("\n", "")
result = re.sub(r"https?://[\w/:%#\$&\?\(\)~\.=\+\-]+", "", content) # URLを除去
result = re.sub(r'RT @.+: ', "", result) # RT情報を除去
f.write(result + '\n')
こんな具合にすれば普通に取れる && DUMP できます。
以上!
2. 形態素解析
将来的には是非ともこの機能も独自で実装したい... のですが、
少々難しいというかめんどくさい...
今回は「とりあえず完成させる」を目標にしてるので、ここは外部ツールに身を任せます。
JUMAN とか、 JANOME とかもいいのですが、とりあえず比較的豊富な資料が見つかった MeCab を使用することに。
Mecab を python 3 で使えるようにするモジュールもあるのでそれを用いていろいろ頑張ります。
やり方は探せばいくらでも出てくるので今回は割愛。
3. 連鎖と言うかチェーンというか辞書というかの部分を実装
コード全容はこちらです。
コメント書いていますが、
- 文字とって
- 使いやすいよう加工して
- 形態素解析して
- 三つ組作って出現回数カウントして
- JSONでdump
といったことをしています。
ここで、指定の三つ組が出てくる数をカウントするのがミソになると思ってます。
保存する方法、DBを使いたくない...という意気込みで作ったのですが、
書いてて、「あ、これDB使ったほうが早いわ」になりました。
また、文頭と文末がわかるようにそれぞれの三つ組のうち、
- 文頭:
BOS
(Begin Of String) を最初の要素へ - 文末:
EOS
(End Of String) を 最後の要素へ
入れることにします。
今回は json を用いたのですが、
[
[
"BOS", # 一つ目
"これ", # 二つ目
"は", # 三つ目
2 # 出てきた回数
],
[
"これ",
"は",
"大",
1
],
[
"は",
"大",
"思春期",
2
]
といった感じにしています。
4. DUMPした情報をもとに学習・文章生成
コード全容はこちら です。
- 情報を読み込んで
- 出現回数に基づいて、形態素の三つ組(タプルにしています) を配列
chain
に代入していく- 出現数:1 => 1回(個?/要素?)代入
- 出現数:4 => 4回(個?/要素?)代入
- 次に最初の三つ組として
BOS
が 1つめの要素をchain
からランダムに選ぶ - 選ばれた要素と、1番目, 2番目 の形態素が一致するのをまた別の配列へ (A)
- その配列からランダムに選ぶ (B)
- 3つめが
EOS
の形態素の三つ組が出てくる(文末)まで A, B を繰り返す
といったことをリファクタリングされずにやっています。
じきにもうちょっと手を加える予定です。
5. Twitter へ投稿
これも普通に探せば出てくるので割愛しますが、
from ChainGenerator import *
from TextGenerator import *
from twitter import *
# tweet = 生成された文章
twitter_clients.statuses.update(status=tweet)
print("Finished Tweet: " + tweet)
こんな感じにしておけばOKです。
最後に
これでなんとなく @HEISEIKUN_ が完成したわけです。
すごく長い文章かつ読みづらいことがこの上ないのは自覚しているので、後日見直して適宜書き換えていくのだろうと思います。
間違っている情報や、改善すべき場所などあれば是非ともコメントや編集リクエストをいただければと思います。