Pythonで偶然短歌をやりたくてMecab使って頑張ったけど…
偶然短歌botというのがある。Wikipediaの記事の中から57577になっている部分を取り出し投稿するbotだ。 たとえばこういうの。
フクロウが鳴くと明日は晴れるので洗濯物を干せという意味 #tanka
— 偶然短歌bot (@g57577) December 31, 2014
ウィキペディア日本語版「フクロウ」より http://t.co/Dm1uHcQdzR
良い。 あるいはこういうの。
もしくはこういうの。彼らには襲われている人々を守れるだけの戦力はなく #tanka
— 偶然短歌bot (@g57577) July 8, 2019
ウィキペディア日本語版「ルワンダ虐殺における初期の出来事」より https://t.co/UKUKpcjZNe
研究で、ネコが砂糖に関心を持たないことは示されていた #tanka
— 偶然短歌bot (@g57577) June 21, 2019
ウィキペディア日本語版「ネコ」より https://t.co/L8b5HhUezs
良すぎる……。 こういうのもあった。
同様に、「普通免許」は普通でも免許でもなく、運転免許 #tanka
— 偶然短歌bot (@g57577) June 30, 2019
ウィキペディア日本語版「地学」より https://t.co/JkZWw4wOFn
最高……!Wikipediaのもつ無機質な文体が、短歌という形で取り出されることによって、荒涼とした侘び寂びのようなものを生み出していて心惹かれるかんじ、たまらん。
……同じことを、やりたい。任意のテキストから短歌を探すやつを、作りたい。
詳しくはわからないが、おそらく形態素解析を用いて単語の音数を抽出し、57577になる部分を抜き取っているのだろう。
形態素解析といえば、こないだの記事のはてブコメントで存在を知ったやつだ。日本語を品詞単位で分解するやつ。Pythonでも動くライブラリがあるらしい。
Pythonでできるなら、たぶん、いける。やってみよう。
必要なのはおそらく
のらへんだろう。
MeCabのセットアップ
文の分析には形態素解析エンジンの中で一番メジャーっぽいMeCabを使う。
MeCabの導入までにはこの記事をほぼまんま参考にした。
また、MeCab標準の辞書だと新語に対応できていないとの話だったので、追加辞書「mecab-ipadic-NEologd」を導入した。参考にしたのはこのサイト。これがかなりしんどかった。このためだけにgitとUbuntuを導入した。泣きそうになった。gitの使い方はこれから勉強します。 とりあえずテストでコードを動かしてみる。
import MeCab t = MeCab.Tagger("Ochasen -d neologd") print(t.parse(input(">>")))
こうなった。
なるほど確かに文章が分解できている。この分解の単位を「形態素」と呼ぶらしい。
あとは、各形態素ごとの文字数が取得できればいいわけだ。書くぞ!!!!!!!!
import MeCab import sys def ParseNode(text): p = MeCab.Tagger("-Ochasen -d neologd") p.parse("")#なんか消すとおかしくなるらしい node = p.parseToNode(text) Result = [] Sentence = [] while node: print(node.feature.split(",")) word = dict() word["text"] = node.surface detail = node.feature.split(",") word["hinshi"] = detail[0] if len(detail)>=9: word["Yomi"] = detail[8] word["Length"] = len(detail[8]) else: word["Yomi"] = "*" word["Length"] = 0 node = node.next if detail[0] == "BOS/EOS": if Sentence: Result.append(Sentence) elif word["Yomi"] == "。": Result.append(Sentence) Sentence = [] elif word["hinshi"] == "記号": pass else: Sentence.append(word) if word["Length"] == "*":word["Length"] = 0 print(type(node)) return Result if __name__ == "__main__": t = "".join(sys.stdin.readlines()) result = ParseNode(t) for sentence in result: print("\n".join([str(d) for d in sentence])) print("------------------------------")
書いた。parseToNodeメソッドの仕様がよくわからなかったので手探りで書いたが、どうも以下のようになっているらしい。
- 返り値はnodeというものが連結された状態
- node.surfaceには、単語(形態素)の文字列が入っている
- node.featureには、単語(形態素)の品詞/品詞の細分類(固有名詞など)/さらなる細分類(人名など)/謎の分類(おそらく細分類の備考)/活用形式/活用形/原型/読み仮名/読みの順にカンマ区切りでデータが入っている
- node.nextを代入することで、次の形態素に進める
このnodeデータからとりあえず、文字列/品詞/読み/読みの文字数 の4つを取得してリストにまとめた。上記のコードはそのリストを返す関数。
57577探し
形態素に分解したら、それらが5・7・5・7・7音でつながっている部分を探す。松尾芭蕉もこうやって俳句を作っていたのだと思うと、妙な親近感が湧く。 こういう系のアルゴリズムは色々思いつきそうだが、今回は単純な方法で解決しようと思う。すなわち、あらゆる名詞・動詞・連体詞・副詞から形態素の文字数を数えていき、ちょうどよく57577にマッチしたら短歌とみなす。
コードが汚くて恥ずかしい///
from split_node import ParseNode import MeCab import itertools import sys def FindTanka(text,neologd=False): """ テキストをぶちこむと短歌のリストを返してくれる風流な関数。 """ Nodes = ParseNode(text,neologd=neologd) TankaPoint = (5,12,17,24,31,32) Tankas = [] for sentence in Nodes: l = len(sentence) for n,StartWord in enumerate(sentence): if StartWord["Yomi"] =="*" or\ StartWord["Hinshi"] not in ("名詞","動詞","連体詞","副詞"):continue sound = 0 curpos = n tanka = "" tankalen = 0 while sound<=31 and curpos<l: w = sentence[curpos] #句の始まりが助詞や助動詞でないかどうか if sound in TankaPoint: if w["Hinshi"] not in ("名詞","動詞","連体詞","副詞"): break if w["Yomi"]!="*": tanka += w["Text"] sound += w["Length"] if sound ==TankaPoint[tankalen]: tankalen+=1 if tankalen == 5: Tankas.append(tanka) break curpos+=1 return(Tankas) if __name__ == "__main__": print(FindTanka(wikipedia(input("url:")),neologd = True))
コードは長いが、要するに単語をひとつずつ数えて短歌になっているかどうか調べている。Wikipediaからテキストを抽出する機能もつけた。 これで偶然短歌をぼくも作れるはずだ! とりあえずこのプログラムに青空文庫から「人間失格」をぶちこんだ結果がこれ。 「政党の有名人がこの町に演説に来て自分は下男」 「画家たちは人間という化け物に傷めつけられおびやかされた」あたりは短歌として成り立っていそうだが、その他はどうもしっくりこない。 俳句の終わり際が不自然なので、「体言止め」または「文節の切れ目」で終わっていれば良いはず。
というわけで魔改造した。
明らかに処理に無駄が多い気はするが…
from split_node import ParseNode from getAozora import aozora from getWikipedia import wikipedia import MeCab import itertools import sys import numpy as np import re def readlines(): return " ".join(sys.stdin.readlines()) JIRITSUGO = ( "動詞","形容詞","接続詞", "名詞","副詞","連体詞","感動詞","記号")#自立後のリスト FUZOKUGO = ("助詞","助動詞") def FindTanka(text,neologd=False): """ テキストをぶちこむと短歌のリストを返してくれる風流な関数。 """ text = re.sub(" "," ",re.sub(r"\r"," ",text)) Nodes = ParseNode(text,neologd=neologd) Tankalist = (5,7,5,7,7)#ここのタプルをいじると自由に検出が変更可能 TankaPoint = np.cumsum(Tankalist+(1,)) Tankas = [] for sentence in Nodes: l = len(sentence) for n,StartWord in enumerate(sentence): if StartWord["Yomi"] in("*","、") or StartWord["Hinshi"] in FUZOKUGO: continue sound = 0 curpos = n tanka = "" tankalen = 0 while sound<=TankaPoint[-1] and curpos<l: w = sentence[curpos] if w["Hinshi"]=="記号": tanka += w["Text"] curpos += 1 continue #句(57577)の始まりが助詞や助動詞でないかどうか if sound in TankaPoint and tankalen<=4: if w["Hinshi"] in FUZOKUGO: break if w["Yomi"]!="*": tanka += w["Text"] sound += w["Length"] if sound ==TankaPoint[tankalen]: tankalen+=1 if tankalen == len(TankaPoint)-1:#短歌が完成した場合 if (w["Hinshi"] in JIRITSUGO and "連用" not in w["Katsuyo"]) or (curpos<l-1 and sentence[curpos+1]["Hinshi"] in JIRITSUGO): Tankas.append(tanka) break if tankalen == len(TankaPoint):#31文字でうまく終わらなかった場合の安全策 Tankas.append(tanka) break curpos+=1 return(Tankas) if __name__ == "__main__": print(FindTanka(wikipedia(input()),neologd=True))
用言の場合は連用形で終わっているものと、直後にまだ助詞/助動詞が続くものを結果から外した。と同時に、字余りの「57578」を許可した。本当はすべての字余りのパターンを許容したかったのだが、コーディングが地獄すぎるので、違和感が少なそうな最後の字余りだけ対応した。 その結果がこちら。
だいぶ改善した気がする。とりあえず、短歌を抽出する部分はこのアルゴリズムで良さそうだ。
ちなみに、必死こいてインストールしたipadic-neologd(最新辞書)だが、使うと逆に精度が落ちる場合があるらしい。 たとえば本家の
これ。撮影を手掛けたことをきっかけに、氷室京介、布袋寅泰 #tanka
— 偶然短歌bot (@g57577) February 28, 2015
ウィキペディア日本語版「加藤正憲」より http://t.co/MjrgUrgJQr
80年代にBOØWYのプロモーションの写真撮影を手掛けたことをきっかけに、氷室京介、布袋寅泰、吉川晃司、Gackt、スピッツなど多くのアーティスト写真を撮影する
という文章からの抽出なのだが、neologd辞書を採用すると短歌として認識してくれない。原因を調べたところ、neologd辞書は「写真撮影」を1語とみなしているため短歌にしてくれなかった事が判明。検出精度がよすぎても短歌的にはマイナスになることがあるんだなあ。 ただ、追加辞書を使わずプリセットの辞書でやると本家のクローンになっちゃうので、neologdは採用することとした。neologdにしか検出できない単語もあるしね。
Wikipediaのデータ注入
本家よろしく、Wikipediaのデータをコイツに流し込む。Wikipediaはサーバーへの負荷を減らすためにクローリングを禁止しているが、そのかわり全テキストデータをまとめたファイルを公開している。そのサイズじつに10GB。もっと重いかと思ったが、画像がないとこんなもんらしい。
これを展開するプログラムがあったのでこれを入手、ちょっと手を加えてさっきのプログラムに放り込む。
オラ!!!!!!!!!!!!!!!
……
……
……
おわびとおわり
プログラムの効率が悪すぎた上にPythonなどという激遅言語で10GBのデータを処理しようとした結果、何時間立っても終わりませんでした…。とりあえず数時間で打ち切ったやつをここにおいておきます。
数時間動かしたけど、10メガのファイルサイズは余裕でオーバー。
ざっと目を通した限りだけれども、よさげな短歌はこんな感じか。
もう同じ趣旨のボットがあるからね、Twtiterには流しませんよ。
ここまでの4文全部短歌です、あえて言うなら必然短歌?
でも辞世の句が 偶然短歌だったら やだな (自由律俳句)