普段Laughteriaという大喜利のサービスを運営していて、その一環でお題をよく自分でも投稿するのですが、ある程度自動化できたいなあと思い、マルコフ連鎖を使って実現してみました。

マルコフ連鎖を使うことで、過去の大喜利を元にした「なんかそれっぽいお題」を生成できるようになります

便利なライブラリが提供されているため、ちょっと使ってみる程度であれば深く理解する必要はないので、マルコフ連鎖自体についての説明は省略します。気になる方は別途調べてみてください。

 

環境構築

今回はMacでPython 3系とMeCab(形態素解析エンジン)を使っていきます。

2019年4月時点ではMacにもともと入っているPythonが2系なので、HomebrewからPython 3系とMaCabをインストールしていきます。

brew install python mecab mecab-ipadic

そして、肝心なマルコフ連鎖のためのライブラリにmarkovifyを使用します。
PythonからMeCabを使うためのラッパーとしてmecab-python3も一緒にインストールします。

brew install swig
pip3 install mecab-python3 markovify

 

ちなみに: ソースコード

なおこの記事中に掲載するソースコードについては、全て以下のリポジトリに置いてあります。
元データのサンプルも含まれているので、ダウンロードしてすぐ動かしてみることも可能です。

g737a6b/markov-chain

 

手順1: 元データの用意

以下のように改行区切りで実際のお題テキストをファイルoriginal.txtに準備します。

いつも使っているプリンターから見たことないエラーが。どんな内容?
突拍子もないことを言ってください。
古いタバコ屋の奥に設置されていた「超渋いガチャガチャ」とは?
10連休が楽しみすぎて浮かれている人に一言。
「そんなことで物議を醸すな」どんなこと?
「この選挙カー、選ばれる気ないな」なぜそう思った?
東京オリンピックの開催に向け、豆腐屋が準備していることとは?
世界一治安が悪い商店街のくじ引きで1等が当選。どんな景品?
...
(実際には1000行以上)

 

手順2: 元データを形態素で分解

手順1で作成したoriginal.txtの内容をmarkovifyで利用できるようにするために、MeCabを使って下処理をしていきます。

prepare.pyの中身:

import os
import MeCab

mecab = MeCab.Tagger("-Ochasen")

def split(text):
    node = mecab.parseToNode(text)
    keywords = []

    # mecab-python3パッケージのバグ対応(2019/4/22時点で必要)
    # See https://github.com/SamuraiT/mecab-python3/issues/19
    current = ""
    previous = ""

    while node:
        current = node.surface.strip("\n")
        diff = previous[:len(previous) - len(current)]
        if len(diff): keywords.append(diff)
        previous = current
        node = node.next
    return " ".join(keywords);

def main():
    result_file_path = os.path.dirname(__file__) + "/corpus.txt"
    if os.path.exists(result_file_path): os.remove(result_file_path)
    result_file = open(result_file_path, "a")
    with open(os.path.dirname(__file__) + "/original.txt", "r") as file:
        for line in file:
            result_file.write(split(line) + "\n")
    result_file.close()

main()

こちらのprepare.pyを実行することで、次のようなcorpus.txtが生成されます。
元データのテキストが形態素で分解され、スペース区切りのテキストになりました。

いつも 使っ て いる プリンター から 見 た こと ない エラー が 。 どんな 内容 ?
突拍子 も ない こと を 言っ て ください 。
古い タバコ 屋 の 奥 に 設置 さ れ て い た 「 超 渋い ガチャガチャ 」 と は ?
10 連休 が 楽しみ すぎ て 浮かれ て いる 人 に 一言 。
「 そんな こと で 物議 を 醸す な 」 どんな こと ?
「 この 選挙 カー 、 選ば れる 気 ない な 」 なぜ そう 思っ た ?
東京 オリンピック の 開催 に 向け 、 豆腐 屋 が 準備 し て いる こと と は ?
世界一 治安 が 悪い 商店 街 の くじ引き で 1 等 が 当選 。 どんな 景品 ?
...
(実際には1000行以上)

 

手順3: マルコフ連鎖でお題を生成

手順2で作成したcorpus.txtを使って、いよいよmarkovifyで新たなお題を生成していきます。

generate.pyの中身:

import os
import markovify

with open(os.path.dirname(__file__) + "/corpus.txt") as file:
    text = file.read()

text_model = markovify.NewlineText(text)

for i in range(25):
    print(text_model.make_short_sentence(100).replace(" ", ""))

こちらのgenerate.pyを実行することで、25件ずつ100文字以下のお題が生成されます。

マルコフ連鎖のアルゴリズムは難しくないので、スクラッチで実装することも可能ですが、ライブラリに頼ることでこれほど短く書けてしまいます。

 

結果

それでは上記のスクリプトによって生成されたお題をいくつか紹介していきます。回答例付き?

ブレーキランプ7回連打するとどうなる?

=> ま、眩しい。。。

夜のコンビニへ強盗に入りますか??

=> 入りません。

夜の神社で甘栗を食べたピーター。蕎麦湯の扱い方が良いことを教えて下さい。

=> ピーター何してんの。

人間は脳を10%しか使って泥棒を捕まえることに成功。その後どうする?

=> 90%を使って華麗なる復讐。

「キュンとするなり、絶望的なこと

=> ごめんね呼び出して。背中に「LLLLLLL」ってシールついてる。

近所の家に帰ると食卓に知らないリモコンが。食べるとどうなる?

=> すぐに通報される。

「まじ卍」の次はどんな悩み?

=> どうせまたしょうもない。

Twitterにこんなのまで出てくると噂の回転寿司屋。行ってみるとどうなる?

=> バイトテロへの厳戒態勢が敷かれていた。

 

おわり

以上のような感じで、なかなか雰囲気は大喜利のお題っぽいものが出来上がりました。
ただマルコフ性という先の値が前の値に影響されない性質のため、前半と後半の繋がりがめちゃくちゃになっているものが多い印象です。リモコン食べねーよ!とか。

実際には型とキーワードの発想補助くらいの感じで使っていくのが良いんじゃないかなあと思っています。
結局のところ大喜利のお題は「表現の自由度・引き出しの多さ・笑いのアンカーへの近さ」という3つを備えているものが良いお題だと思うので、このあたりはまだ人間が判断した方が良さそうな領域かなと思います。

テクノロジーで運営コストを削減してみた、というお話でした。