以前から気になっていたマルコフ連鎖をザリガニさんの記事を元に試してみました。
マルコフ連鎖で日本語をもっともらしく要約する - ザリガニが見ていた...。
マルコフ連鎖とは
マルコフ連鎖を説明してみる。 | 分析のおはなし。
個人的にはこちらの図と確率の話で理解しました。
wikipediaは分かりにくいです。
マルコフ連鎖と分かち書きで文章を要約っぽくする
(ザリガニさんの記事に書いてありますが)
分かち書きしたデータを元にマルコフ連鎖を適用し文章を生成していきます。
ここで複数候補があった場合は、乱数を用いて選択していくことで
要約っぽいことが出来てしまいます。
結構自然な文章になるんですよね。
補足:マルコフ連鎖のロジック
ザリガニさんの記事内のマルコフ連鎖の記事はリンクが切れていましたが
ググってみると以下のtumblrにURLが変更されているようです。
こちらの記事はとてもわかりやすかったです。
netbookerの貯蔵庫 — 人工無能を作ろう~マルコフ連鎖(2接頭語と1接尾語の場合) ...
書いてみた
ザリガニさんの記事のコードは、今は動かないのでちょっと改良して手元で試してみました。
require 'open-uri' # require 'mecab' require 'natto' require 'nokogiri' module Asahicom TOP_URL = 'http://www.asahi.com/' class MarkovChain MIN_TEXT_SIZE = 80 MAX_TEXT_SIZE = 180 def summarize_headline url = scrape_headline_top_url sleep 1 # 念のため1s待機 text = scrape_article_body(url) data = generate_mecab_tagger(text) result = '' loop do result = summarize(data) text_size = result.size break if text_size >= MIN_TEXT_SIZE && text_size <= MAX_TEXT_SIZE end puts result.gsub(/EOS$/, '') end private def scrape_headline_top_url doc = Nokogiri::HTML.parse(open(TOP_URL)) first_url = doc.css('.HeadlineTop a')[0][:href] URI.join(TOP_URL, first_url).to_s end def scrape_article_body(url) doc = Nokogiri::HTML.parse(open(url)) doc.css('.ArticleText').text.tr("\n", '').gsub(/\A /, '') end def generate_mecab_tagger(text) # mecab = MeCab::Tagger.new('-Owakati') mecab = Natto::MeCab.new('-Owakati') mecab.parse(text + 'EOS').split(' ').each_cons(3).map do |a| { head: a[0], middle: a[1], end: a[2] } end end # マルコフ連鎖で要約 def summarize(data) t1 = data[0][:head] t2 = data[0][:middle] new_text = t1 + t2 loop do candidates = data.select { |d| d[:head] == t1 && d[:middle] == t2 } break if candidates.size == 0 num = rand(candidates.size) # 乱数で次の文節を決定する new_text << candidates[num][:end] break if candidates[num][:end] == 'EOS' t1 = candidates[num][:middle] t2 = candidates[num][:end] end new_text end end end if $0 == __FILE__ Asahicom::MarkovChain.new.summarize_headline end
結果
今のTop記事は以下。
佐野氏謝罪「スタッフが第三者デザイン写す」 景品問題:朝日新聞デジタル
結果がこちら。
202020年とか、えらいことになってますが、年代とか固有名詞はtuningすればよさそうですね。
202020年東京五輪・パラリンピックのエンブレムを手掛けた佐野研二郎氏がデザインをトレースし、 そのまま使用するということ自体が、デザイナーとして決してあってはならない」との共同制作でなく、 個人応募だったと強調。ベルギーのデザイナーらが提訴 佐野氏は14日、スタッフが第三者のものと 思われるデザインを写して使ったことを明らかにし、謝罪した。
ポイント
乱数で候補の選択をおこなっているため、毎回結果が異なりますが
結構文字数に大きな差が出てきます。
そこで、そこそこの文字数に収まるように、min/max値を設定するようにしたところ
なかなかいい感じに要約っぽくなりました。
余談 ffi
gemは mecab でも動くのですが natto というライブラリを使ってみました。
このキラキラネームならぬネバネバネームは気になります。
この実装が面白く ffi/ffi を利用してます。
これを機にちょろっと ffi 調べてみたのですが、これはすごいですね。
コンパイル不要でCのライブラリが扱えてしまうんですね。