スキップしてメイン コンテンツに移動

Twitter人工無脳: @dabesa & @AngraMainyu

以前北海道開発オフの中で作成した、マルコフ連鎖を使用して日本語タイムラインから投稿文を生成してポストする人工無脳、@dabesa
しばらく放置されていたのですが、Rubyの勉強を始めたのを機に一念発起、Ruby化、DB化しました。最初に僕の半年分のTwitter Archiveを食わせ、後は定期的に僕のTwitter Archiveから更新分の投稿を取得して形態素解析しDBに投入。投稿は二階のマルコフ連鎖です。つまり@dabesaが投稿する要素は全て僕のTwitterへの投稿です。
Special Thanks to @showyou & @ha_ma.

(0)形態素テーブル作成。

create table dabesable (
id int not null auto_increment primary key,
surface text not null,
nextword text);

(1)テキストファイルを解析して形態素テーブルに登録するスクリプト。

#!/usr/bin/ruby -Ku

require 'MeCab'
require 'mysql'

begin
c = MeCab::Tagger.new(ARGV.join(" "))
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')

dfile = open("data.txt",'r')
dfile.each do |sentence|
sentence.chop!
n = c.parseToNode(sentence)
n = n.next
# 行頭単語の登録
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
newword = res.fetch[1].to_s + "<>" + n.surface
res = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id=1")

while n do
surfword = n.surface
n = n.next
if n then
nextword = n.surface
else
nextword = "EOL"
end

res = mysql.prepare("select id,surface,nextword from dabesable where surface='#{Mysql::quote surfword}'")
res.execute

if res.num_rows == 0 then
# 単語が登録されていない場合には新規登録する
res_insert = mysql.query("insert into dabesable values(NULL,'#{Mysql::quote surfword}','#{Mysql::quote nextword}')")

else
# 単語が登録されている場合にはnextwordを追加する
qid,qsurf,qword = res.fetch
newword = qword.to_s + "<>" + nextword.to_s
res_update = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id='#{qid}'")
end
end
end
mysql.close
dfile.close

rescue
print "RuntimeError: ", $!, "\n";
end

(2)タイムラインを解析して形態素テーブルに登録するスクリプト。

#! /usr/bin/ruby -Ku

require'rubygems'
gem 'twitter4r'
require 'twitter'
require 'time'
require 'pit'

require 'MeCab'
require 'mysql'
require 'kconv'

begin
# 前回の最新投稿のIDを取得
flg = 0
sid = nil
idfile = open("id.txt",'r')
idfile.each do |id| sid = id.chop end
idfile.close

c = MeCab::Tagger.new(ARGV.join(" "))
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')

config = Pit.get("dabesa")
cl = Twitter::Client.new(config)

# smokeymonkeyのArchiveを取得
timeline = cl.timeline_for(:user, :id=>'smokeymonkey') do |status|
# 前回取得より新しいデータがあれば最新のIDをファイルに保存
if flg == 0 and status.id.to_i >= sid.to_i then
idfile = open("id.txt",'w')
idfile.puts status.id.to_i
idfile.close
flg = 1
end

# 前回取得より新しいデータがなければ終了
if status.id.to_i <= sid.to_i then
mysql.close
exit
end

post = Kconv.kconv("#{status.text}",Kconv::UTF8)
post.chop!
n = c.parseToNode(post)
n = n.next
# 行頭単語の登録
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
newword = res.fetch[1].to_s + "<>" + n.surface
res = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id=1")

while n do
surfword = n.surface
n = n.next
if n then
nextword = n.surface
else
nextword = "EOL"
end

res = mysql.prepare("select id,surface,nextword from dabesable where surface='#{Mysql::quote surfword}'")
res.execute
if res.num_rows == 0 then
# 単語が登録されていない場合には新規登録する
#res_insert = mysql.query("insert into dabesable values(NULL,'#{Mysql::quote surfword}','#{Mysql::quote nextword}')")
else
# 単語が登録されている場合にはnextwordを追加する
qid,qsurf,qword = res.fetch
newword = qword.to_s + "<>" + nextword.to_s
#res_update = mysql.query("update dabesable set nextword='#{Mysql::quote newword}' where id='#{qid}'")
end
end
end
mysql.close

rescue
print "RuntimeError: ", $!, "\n";
end

(3)形態素テーブルから文章を生成してTwitterに投稿するスクリプト。

#! /usr/bin/ruby -Ku

require 'rubygems'
gem 'twitter4r'
require 'time'
require 'twitter'
require 'pit'

require 'mysql'

begin
mysql = Mysql::new('localhost','USERNAME','PASSWORD','DATABASENAME')
res = mysql.prepare("select id,nextword from dabesable where id=1")
res.execute
nextwords = res.fetch[1].to_s.split(/<>/)
i = nextwords.size
nword = nextwords[rand(i)]
postline = nword

res = mysql.prepare("select surface,nextword from dabesable where surface='#{Mysql::quote nword}'")
res.execute

while res.num_rows != 0 and nword != "EOL" do
nextwords = res.fetch[1].to_s.split(/<>/)
i = nextwords.size
nword = nextwords[rand(i)]
if nword == nil then nword = "EOL" end
postline = postline.to_s + nword.to_s
res = mysql.prepare("select surface,nextword from dabesable where surface='#{Mysql::quote nword}'")
res.execute
end

postline.gsub!(/@/,'.@')
mess = postline.gsub(/EOL/,'')

config = Pit.get("dabesa")
cl = Twitter::Client.new(config)
cl.status(:post,mess)
mysql.close

rescue
print "RuntimeError: ", $!, "\n";
end


このタイムライン解析スクリプトと投稿スクリプトをcronで回してます。しばらくは試験期間中。

なお、同じ構造を使って、僕のFriendsタイムラインから投稿を取得する@AngraMainyuという人工無脳も作りました。コレは無秩序に投稿を取得し続けるので、最初はDBの形態素量も少ないのですが、徐々に成長すればいいなぁと思っています。

コメント

このブログの人気の投稿

リモートワークは仕組みじゃなくて文化です

ここ最近、コロナウイルス関連の報道が数多くあるが、その中でも多くの企業がリモートワークを推奨するという記事やプレスリリースが注目を浴びている。それ自体はもちろん大変望ましい。不要な対面での接点を減らすことで感染リスクを抑えることが出来るし、通勤ラッシュや首都圏への経済集中も抑制出来るからだ。

だがちょっと待ってほしい。リモートワークというのは社員が在宅で働くことだけを指すのではない。社員が在宅で働いても出社時と同じパフォーマンスが出ることをリモートワークというのだ。だからこの記事のタイトルで「リモートワークは仕組みじゃなくて文化です」と書いた。

弊社がリモートワークを導入したのは2011年の東日本大震災がきっかけだけれど、9年経った今、どのようにリモートワークを運用して、そしてパフォーマンスを維持しているかを共有したいと思う。以下のことが文化として根付けば、その会社のメンバーはリモートワークでもオフィスでも同じようなパフォーマンスが発揮出来るはずだ。

1.勤怠を厳密に管理しない え、だってダルくないすか。管理するの。何時に働き始めて何時に働き終わったかなんて関係ないっしょ。大事なのは働いた結果のアウトプットであり、働いた時間なんか問題じゃない。

2.休憩も厳密に管理しない え、だってダルくないすか。管理するの。何時に休憩し始め(ry

3.工数を厳密に管理しない え、だ(ry

4.目に見えるアウトプットを意識する 当然のことながら、仕事は結果が全てであり、結果が出なければどこで何時間働いたって意味がない。そして結果というのは目に見えなければ意味がない。 だからこそ、アウトプットを出すこと、アウトプットを評価することに徹底的にこだわる。それはドキュメントかもしれないし、お客様やパートナーとコミュニケーションするためのメールかもしれないし、社内の改善活動かもしれないし、メンバーへのフォローかもしれないし、ブログかもしれないし、Slackでの発言かもしれない。 とにかく目に見えないものは周りも認められない。目に見えるアウトプットしか評価されないし、そのために徹底的にアウトプットするんだ、という意識を社内でしっかりと作ることが重要。

5.コミュニケーションコストを意識する どんなに頑張っても、オンラインのコミュニケーションはオフラインのコミュニケーションの密度を越えられない…

41歳になりました

30代の頃は40歳になるまでの1年1年を意識しながら生きていたんだけど、40歳を越えてから自分の年齢にまるで興味が無くなり「あれ、今40歳だっけ、41歳だっけ」くらいの感じだったのですが、昨晩妻に確認したところ今日で41歳になりました。本厄ですが今の所は大きなトラブルもなく、もしかしたらコロナウイルスって僕が厄年のせいかな?くらいの気持ちでおります。

折角なので近況報告です。

コロナウイルス ... 現在弊社ではBCP体制として全オフィス閉鎖、全員在宅勤務、出張禁止となっています。僕も在宅勤務をしており、3月の出張を全てキャンセルしました。丸々一ヶ月自宅にいるのはなんと2015年8月以来4年7ヶ月ぶりです。ほぼ5年じゃん...この5年出張しかしてねえじゃん俺...おかげで毎朝6時起床で飲酒ゼロという健康的な生活を送っております。仕事 ... 2019年7月に弊社執行役員から取締役に変わったのですが、「使用人兼務役員だし大した変わらないだろ」と高をくくっていたところ、なんだかんだと自分の部署以外にも目を向ける必要が出てきたり、海外拠点のビジネスについても責任が伴ったりと、なんか結構変わっちゃったな、という感じです。41歳もまた新しいチャレンジをする一年になる予定なのでお楽しみに。家庭 ... 長女が大学進学して一人暮らしを始め、一安心していたところに長男が酒と煙草で停学2連チャンし自主退学、何とか私立高校に編入させて寮に引っ越しをさせ、結果的に妻と次男と3人暮らし、というのが現状です。この長男についてはまぁちょっと色々酷い時期があったものの、正直なところ僕も他人様に胸を晴れるほど立派な高校生だったわけでもないし、長男自体は編入後にすごく大人になったことから、コレ自体はまぁ長男にとって必要な経験だったんだろう、とポジティブに捉えています。彼も来年は高校卒業を迎えるので、ちゃんと手に職つけてくれると良いな。あと長女には2歳年上の彼氏が出来ました。うん、お父さん大丈夫。お父さん冷静。お父さんちょっとジム行ってくる。英語 ... 1日2時間の英語の勉強時間をキープし続けて、まぁとりあえず海外に独りで行っても困らないし、英語のミーティングは70%くらいは言ってることわかる、たまに返事も出来る、くらいが現状。今年も勉強頑張ります。大学 ... 僕は工業高校卒ですぐ就職しており大学…

贅沢に対する恐怖に怯えている

僕が小さい頃、我が家は貧乏だった。

とは言え、極端なほどではない。家もあったし、サッカーという習い事も出来ていたし、三食ちゃんと食べることが出来た。でも例えば外食はしないとか、ブランドものの服は着ないとか(今思い出せばジャージ以外着たことなかった)、旅行には行かないとか、ファミコンを買ってもらえないとか、そういうレベルでは裕福ではなかった。母子家庭だったので父親はおらず、慰謝料は一銭も入ってくることがなく、母はいつも頑張って働いてくれていた。

そんな母の姿を見ていたから、僕もとにかく金を稼がなくては生きていないという想いが強く、中学時代は知り合いの伝手でちょっとした日銭が稼げるアルバイトをしていて、中学卒業後にはすぐに手に職を得ることが出来そうな工業高校に進学した。高校時代は平日も土日もずっとアルバイトで、年末年始も休まず働いていた。その後進学したいと思うようになり、高度専門士の取得が可能な4年生の専門学校への入学が決まっていたものの、金銭的な事情から進学を諦めざるを得なかった。結果として、高校卒業にすぐ就職した。

その後21歳という若年で結婚し、長女が生まれたけど、もちろん高卒で21歳の若者の給与なんかたかだか知れており、必死に働いた。本業だけでは食って行けず、知人の紹介で副業を持ち、朝8時から夜中3時まで働いた。結婚後の幸せ太りで10kg増えた僕の体重は、一番過酷だった2ヶ月間であっという間に元に戻った。

そんながむしゃらに生きて、今の僕がある。

率直に言えば、今の僕は僕の人生の中で最も金銭的な余裕がある。40歳になり、ある程度の給与を貰えるようになり、幸いなことにボーナスまでもらえる(僕がボーナスをもらったのは今の会社が初めてだ。ボーナスを初めてもらった時、「あ、ボーナスって本当に存在するんだな」と思った)ような状況だ。すごく幸せなことだと思う。

しかし、だからこそ、僕は贅沢が怖い。贅沢をすること、それに慣れること、そして贅沢にスポイルされることが怖い。必死に働いて、節約して、誰もやらないような泥臭い仕事をして、それで何とか生きていた経験の積み重ねが、今の僕を作っている。僕が今仕事をさせてもらえているのは、その経験を買ってもらえているからだけれど、それも「たまたま」であり、仕事がなくなった時にまた同じように必死の努力をしなくてはならない。必死の努力をするのは…