マルコフ連鎖でラップボットつくった
追記 (2020/01/14)
BERT 版はこちら
はじめに
こんにちは。ヴァイブス満タンですか?
この投稿は LINEBot&Clova Advent Calendar 2018 の14日目の投稿です。
昨年はDopeLearning を使って、ノスタルジックなラッパー、ピーさんをリリースしました。
昨年を振り返って反省したこととしては、「バトル感が無い」ということでした。
そこで今年はもっとバトル感を醸成すべく、世のラッパーたちが過去に残したパンチラインを学習し、前人未踏のパンチの効いたヴァースをかますラッパーを生み出したいと考えました。
そこで目をつけたのが、「マルコフ連鎖」という文章生成モデルです。
マルコフ連鎖での文章生成については、下記の記事がわかりやすかったので紹介させていただきます。
引用元: マルコフ連鎖による文章生成 - 知識のサラダボウル
簡単に言うと、上記の図の通り、「ある単語が来たら次に来そうな単語はこれだ」というパターンをあらかじめ学習しておくことで、パターンに沿って自由に文章を生み出すことができるようになります。
そうして生まれたのがこちら、「お麩」界のカリスマこと、DJ マルコ です。
マルコにバトルを仕掛けると、こんなかんじでケンカを買ってくれます。
いかがでしょうか。
「お前のライム ナチュラル無農薬野菜」は、攻撃力が無くてむしろ健康になっちゃうぜ、的なdis りなのか、あるいは手間ひまかけていいもの作ってるね、的な賛辞なのか、一瞬戸惑ってしまう奥深いパンチラインですね。
そんなDJ マルコとバトルをしてみたいという奇特な方がいらっしゃったら、ぜひ下記のリンクからバトルを仕掛けてみてください。
DJ マルコの育て方
サーバー構成について
ラップ生成エンジンについて
- パンチラインDB として、韻ノート の、「最近追加された韻」欄を参照させていただきました
- パンチラインDB から文章を生成するため、TextGenerator を一部改変して利用しました
大変だったこと
どうすればラップっぽい文章が生成できるか
マルコフ連鎖で今回ポイントになると考えた点と、TextGenerator での実装は下記の通りでした。
- スタートをどう始めるか
- 開始フラグ(
__BEGIN__
) がついたノードをランダムに選択
- 開始フラグ(
- 途中のノードをどう選択するか
- 出現比率に応じてランダム
- 文章の最後はどう終わるか
- 終了フラグ(
__END__
) がついたノードに当たったら
- 終了フラグ(
ここで、ラップバトルには「アンサー」と呼ばれる、相手のヴァースに対してメンションし、うまく次の攻撃に繋げるという特徴があることから、スタート部分を下記のとおり改変しました。
しかし、今回は、パンチラインの数が少なかったこともあり、該当する単語がDB に無いことが頻発しました。
そこでword2vec で類似する単語を推定し、元の単語が見つからないときには類似単語でレコードから参照するようにしています。
word2vec の語彙コーパスとしては、 Livedoor コーパス を利用しました。
その他つまづきポイント
heroku container:push が重い
MeCab の辞書を含んでいたため、1GB を超えるファイル転送が走り、待ち時間が長くなってしまうことがあったため、なるべくローカルでdocker run して問題がないことを確認してからpush するようにしました
Mac とalpine のmecab で、surface の返り値が異なる
これがなかなか曲者で、「俺がウドの大木」という文章をパースした際、下記のような違いがありました
mt = MeCab.Tagger() node = mt.parseToNode('俺がウドの大木') node = node.next # EOS をスキップ node.feature #=> '名詞,代名詞,一般,*,*,*,俺,オレ,オレ' node.surface #=> '俺'
Alpine
(途中省略) node.feature #=> '名詞,代名詞,一般,*,*,*,俺,オレ,オレ' node.surface #=> '俺がウドの大木'
わかりづらい。。
今後の展望
以上となります。