マルコフ連鎖で文章を作る
👤 sudosan 📆 6月 29, 2016 📝 2
皆さんこんにちは。すどです。
今までミルクちゃん裏垢bot(@milkchan_web)を運用してきたわけですが、最近問題が発生しました。
ミルクちゃん裏垢botは、タイムラインのデーターを元に文章を生成するわけですが、その生成に既存のあるプログラムを利用していました。
しかし、ミルクちゃんの学習データー量が増えたため(約1600万文字)、そのプログラムが動かなくなってしまったため自力で作ったのでここに簡単に書こうかと思います。
マルコフ連鎖とは
マルコフ連鎖とはそれっぽい文章を適当に作る仕組みです。
詳しくは割愛しますのでこちら等を御覧ください
まず元の文章に次のようなものがあるものとします
「野獣先輩は人間の鏡」
「うんこはくさい」
「風の中のすばる」
次に、これを単語ごとに分解します。
「野獣/先輩/は/人間/の/鏡/EOS」
「うんこ/は/くさい/EOS」
「風/の/中/の/すばる/EOS」
(EOSとは文章が終わるという意味です)
そして「野獣」の次に来るのは「先輩」、「先輩」の次に来るのは「は」、「くさい」の次は「EOF」・・・というデーターベースをつくります。
今回はデーターベースにmongoを利用しましたので、以下の様なデーターベースができます
{t1:”野獣”,t2:”先輩”,_id:”hogehoge”}
{t1:”先輩”,t2:は”,_id:”hogehoge2″}
{t1:”くさい”,t2:null,_id:”hogehoge3″}
・・・・・
そして、このデータベースの中から初めの単語をランダムで出力します。
そして、その単語に繋がる単語をつなげていき、文章終了になるまでコレを繰り返します。
つなげる候補が多数あった場合はランダムで選択します。
こうすることによって、例えば、
「野獣先輩はくさい」
「うんこは人間の中のすばる」
「風の中の中の鏡」
といった文章を作り出すことができます。
欠点
このように面白い文を生成することのできるマルコフ連鎖ですが、欠点もあります
欠点1、元の文をそのまま出力してしまう可能性がある。
マルコフ連鎖は確率的に元の文をそのまま出力してしまう可能性があります。これはTwitterの場合パクツイしてしまうことがあるので注意が必要です。
ただ、候補の数が大きければそれほど問題はないでしょう
欠点2、文章が意味不明
この実装例では助詞から始まらないようにする等の処理をしてないので
「は人間のすばる」といった文法的に滅茶苦茶な文章を出力してしまう可能性があります
欠点3。無限ループ
この例で言うと、「風邪の中の中の中の中の中の中の・・・」と無限にループしてしまう可能性があります
今回はツイートすることを前提に、130文字を超えた時点で途中でも出力するようにしました。
実装例
今回はnode.jsをりようしました。ツイートする機能もついていますが、不必要なら除去してください。
形態素解析する機能はついていないので、事前にmecabで分かち書きしたデーターを用意してください
分かち書きコマンド:mecab -O wakati
var fs = require('fs'); var ProgressBar = require('progress'); var MongoClient = require('mongodb').MongoClient; var dbUrl = 'mongodb://localhost:27017/tweetdata'; var update = 0; MongoClient.connect(dbUrl, function (err, db) { var Collection = db.collection('global'); fs.readFile('./' + process.argv[2], 'utf8', function (err, text) { var bun = text.split("\n"); var x = bun.filter(function(e){return e !== "";}); //console.log(x); //console.log(); var bar = new ProgressBar('[:bar] :percent :etas', { complete: '=', incomplete: ' ', width: 20, total: x.length }); for(var i = 0; i < x.length; i++) { //console.log(x[i]); bar.tick(); //console.log('\n' + ((i+1)/x.length)*100 + "%"); var tango = x[i].split(" "); tango = tango.filter(function(e){return e !== "";}); //console.log(tango); for(i2 = 0; i2 < tango.length; i2++) { //console.log(i2+"/"+tango.length); if(i2===tango.length-1) { var obj = {t1:tango[i2],t2:null}; //console.log(obj); Collection.insert(obj,function(){update++;console.log(update+"/"+x.length);if(update===x.length){process.exit();}}); //console.log(i); if(i===x.length-1) { //setTimeout(function(){process.exit();}, 5000); } } else { var obj = {t1:tango[i2],t2:tango[i2+1]}; //console.log(obj); Collection.insert(obj); } } } //process.exit(); }); });
var MongoClient = require('mongodb').MongoClient; var dbUrl = 'mongodb://localhost:27017/tweetdata'; var twitter = require('ntwitter'); var tw = new twitter({ consumer_key : '', consumer_secret : '', access_token_key : '', access_token_secret : '' }); var text = null; var t2 = null; MongoClient.connect(dbUrl, function (err, db) { var Collection = db.collection('global'); var arg = process.argv[2]; if(arg) { console.log(arg); var query = { "t1" : arg }; var cursor = Collection.find(query); cursor.count(function(err, count) { var random = Math.floor(Math.random()*count); console.log(random+"/"+count); cursor.sort({ "t1": -1}); cursor.skip(random); cursor.limit(1); cursor.toArray(function(err, docs){ var dobj = docs[0]; console.log(docs); text = dobj.t1; t2 = dobj.t2; t2test(); }); }); } else { var query = {}; var cursor = Collection.find(query); Collection.count(function(err, count) { var random = Math.floor(Math.random()*count); cursor.sort({"t1" : -1}); cursor.skip(random); cursor.limit(1); cursor.toArray(function(err, docs){ var dobj = docs[0]; console.log(docs[0]); text = dobj.t1; t2 = dobj.t2; t2test(); }); }); } function t2test(){ if(t2==null||text.length>130) { if(text.length>5) { text = text.replace(/@|@|#|#/g,""); text = text.replace(/です/g,"ですわ"); text = text.replace(/ます/g,"ますわ"); tw.updateStatus(text,function(data){process.exit();}); } //console.log(text); else{ process.exit(); } } else { //console.log("t2/"+t2); var query = {"t1":t2}; var cursor = Collection.find(query); cursor.count(function(err, count) { var random2 = Math.floor(Math.random()*count); console.log(random2+"/"+count); cursor.sort({ "t1": -1}); cursor.skip(random2); cursor.limit(1); cursor.toArray(function(err, docs){ var dobj = docs[0]; console.log(docs); if(dobj.t2!=null) { text += dobj.t2; } else { //text += dobj.t1; } t2 = dobj.t2; t2test(); }); }); } } });
mecabとmongodbが必要です。
mongodbでは、t1にインデックスを張ってないととても遅いので注意してください。
インデックスを張るにはmongoにログインし、ターミナルでdb.hogehoge.createIndex({“t1”: 1});
動かしてみる
無事、740万単語をインプットしたので動かしてみました。
このインプット作業がかなり重く、メモリも食ったので500行ずつに分割して処理しました。
完全にランダムに
がばエッジ事実ですわね
— 牛飼みるく@JS裏垢 (@milkchan_web) 2016年6月28日
最初のワードを指定して
「野獣」から始まる文を作ってみます
野獣だと潮田渚にてhttps://t.8J 私の犯人は50ってアニメ終わることを犯している人に)byリクルートしたら声が
— 牛飼みるく@JS裏垢 (@milkchan_web) 2016年6月28日
楽しい✌
関連
-
Windows10にアップグレードしたら、USBを安全に取り外さないとデータが全部消えるという現象にWindows10は全く悪くありません
6月 16, 2016
-
家庭ゲーは糞だと思ってた自分がSplatoonにハマった
11月 14, 2015
-
ブラウザの右下に尊師を表示してクリックすると脱糞するBookmarklet
8月 21, 2015
共有
コメント
👤 Tak
🕓5月 28, 2018 2:20 pm 📎パーマリンク
ありがたいサイトですね。
いやあ、End Of Sentense なのでEOSでOKだと思いますよー
EOFはEnd Of File
👤 I.K
🕓8月 8, 2016 10:46 am 📎パーマリンク
いつも楽しく読ませていただいています。
>>(EOSとは文章が終わるという意味です)
EOFではないでしょうか。
間違えていたらごめんなさい。