マルコフ連鎖で日本語をもっともらしく要約する

そもそも、マルコフ連鎖とは何なのか?全く聞いたこともなかった。そして、文章を要約するのはとっても高度なことだと思っていて、自分のレベルではその方法を、今まで思い付きもしなかった。
しかし、以下のようなシンプルなRubyコードでそれが出来てしまうと知った時、目から鱗である...。一体、何がどうなっているのだ?コードを追いながら、マルコフ連鎖を利用するという発想の素晴らしさを知った!

作業環境

  • MacBook OSX 10.5.7
  • ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
  • mecab utf8環境でインストール済み

マルコフ連鎖に出逢う

rssを流し読みしていると、以下の日記に目が止まった。(素晴らしい情報に感謝です!)

一体何が出来るコードなのか、日記を読んだだけではピンと来なかったので、自分で実行してみることにした。コードは全掲載されているので、そのままコピーして、markov_chain.rbとして保存、ruby markov_chain.rbを実行してみる。が、1行目からエラーでまくり。まずは実行できる環境を整える必要があった。

実行環境を整える

  • MeCabを使ってマルコフ連鎖で説明されている通り、mecabのインストールを実行した。
  • ただし自分の場合、mecabについてはutf8環境でインストール済みなので、3行目のrb-mecabのみ実行した。
$ sudo port install mecab +utf8
$ sudo port install mecab-ipadic-utf8
$ sudo port install rb-mecab
  • これで require 'MeCab' が通ると思ったら、エラーでストップ。
  • どうやらrubyのロードパス($LOAD_PATHというグローバル変数)にrb-mecabの在処も含める必要があるようだ。(以下で、/opt/以下のパスが見当たらないので。)
$ ruby -e 'p $LOAD_PATH'
["/Library/Ruby/Site/1.8", "/Library/Ruby/Site/1.8/powerpc-darwin9.0", "/Library/Ruby/Site/1.8/universal-darwin9.0", "/Library/Ruby/Site", "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8", "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/powerpc-darwin9.0", "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0", "."]
$ export RUBYLIB=/opt/local/lib/ruby/vendor_ruby/1.8/i686-darwin9
  • さらに、HTMLから必要な箇所を切り出してくれる'nokogiri'も、インストールが必要だった。
$ sudo gem install nokogiri
Password:
Building native extensions.  This could take a while...
Successfully installed nokogiri-1.3.2
1 gem installed
Installing ri documentation for nokogiri-1.3.2...
Installing RDoc documentation for nokogiri-1.3.2...
  • それから、ruby 1.8.6で each_cons を利用するには、require 'enumerator'が必要だった。
  • require 'enumerator'と、自分なりにコメントを追加したコードは、以下のようになった。
require 'MeCab'
require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'enumerator' # each_consを利用するため必要

# ヘッドラインの1行目の記事を取得する
url = 'http://www.asahi.com/'
text = String.new
nokogiri = Nokogiri::HTML.parse(open(url))
li = nokogiri.xpath('//div[@id="HeadLine"]/ul[@class="Lnk FstMod"]/li[1]/a')
nokogiri = Nokogiri::HTML.parse(open(url + li[0].attribute('href')))
nokogiri.xpath('//div[@class="BodyTxt"]/*').each do |body|
  text = text +  body.text
end
text.gsub!(/\n/,'')

# mecabで形態素解析して、 参照テーブルを作る
mecab = MeCab::Tagger.new("-Owakati")
data = Array.new
mecab.parse(text + "EOS").split(" ").each_cons(3) do |a| 
  data.push h = {'head' => a[0], 'middle' => a[1], 'end' => a[2]}
end

# マルコフ連鎖で要約
t1 = data[0]['head']
t2 = data[0]['middle']
new_text = t1 + t2  
while true
  _a = Array.new
  data.each do |hash|
    _a.push hash if hash['head'] == t1 && hash['middle'] == t2
  end 
 
  break if _a.size == 0
  num = rand(_a.size) # 乱数で次の文節を決定する
  new_text = new_text + _a[num]['end']
  break if _a[num]['end'] == "EOS"
  t1 = _a[num]['middle']
  t2 = _a[num]['end']
end

# EOSを削除して、結果出力
puts new_text.gsub!(/EOS$/,'')
http://d.hatena.ne.jp/sugarbabe335/20090613/1244858669

処理の流れ

  • mecab形態素解析する。
    • スペース区切りで、品詞単位に分解
    • 例:「日本語を要約する」→「日本語 を 要約 する」
  • 最後に要約文を出力する。

要約の仕組み

上記において、マルコフ連鎖のみ利用したシンプルな要約とは、つまり、文章中の同じ文節を探して、乱数で選択しながら文章を繋げていく作業をしていたのだ。乱数頼りの機械的な作業だけども、偶然にもそれらしく、かなり的を得た射た要約になっていたりするから驚かされる。(乱数に左右されるので、実行する度に違った要約になる。)

$ ruby ~/Documents/markov_chain.rb
 【ロサンゼルス・タイムズ紙は市警当局者の話をもとに伝えている。市警は27日、ジャクソンさんが自宅で倒れた時に立ち会っていた担当医を参考人として事情聴取した。ハリウッドでは処方薬を飲んでいた」と明らかにしたが、死因の解明には4〜6週間かかる。ロサンゼルス郡検視局は「複数の処方薬を飲み過ぎで死亡する悲劇が相次ぐ。07年にモデルのアンナ・ニコル・スミスさんが複数の処方を頼まれた。詰問したら、以前に多くの医師から薬をもらっていたことを認めた」と米メディアに明かした。 26日、ロサンゼルス・タイムズ紙によると、検視局から遺体を引き渡された遺族は、死の真相を知るため、独自に病理学者に依頼して解剖をした。

$ ruby ~/Documents/markov_chain.rb
 【ロサンゼルス=堀内隆】歌手マイケル・ジャクソンさんの場合も友人の医師はラスベガスを本拠にする心臓内科医で、約2週間前からジャクソンさんが自宅で倒れた時に立ち会っていた担当医を参考人として事情聴取では死因につながる情報は出なかったと、ロサンゼルス・タイムズ紙は市警当局者の話をもとに伝えている。市警は27日、ジャクソンさんに与えた薬が死につながったとのうわさが飛び交っているからだ。 マリリン・モンローエルビス・プレスリーら、米芸能界では処方薬の大量服用で命を落とす芸能人が後を絶たず、セレブと医師2人が起訴された。 南カリフォルニア大のジュリー・オルブライト講師はAP通信に「セレブの面倒を見ることで医師は大金を稼ぎ、彼らを薬漬けにする共犯になる」と危うい相互依存を指摘する。ジャクソンさんの急死した際は、大量の鎮痛剤を処方したとして医師は大金を稼ぎ、彼らを薬漬けにする共犯になる」と危うい相互依存を指摘する。ジャクソンさんの急死した際は、大量の鎮痛剤を処方したとして医師2人が起訴された。 南カリフォルニア大のジュリー・オルブライト講師は容疑者ではないと強調した。ジャクソンさんに与えた薬が死につながったとのうわさが飛び交っているからだ。 マリリン・モンローエルビス・プレスリーら、米芸能界では処方薬を飲んでいた」と明らかにしたが、死因の解明には4〜6週間かかる。ロサンゼルス郡検視局から遺体を引き渡された遺族は、死の真相を知るため、独自に病理学者に依頼して解剖をした。

そして、日記の中ではうまく制御したり、学習させて重みを持たせると、面白いかもと言っている。その通り、本当に面白そうだ!
どのように学習させ、どのように重みを持たせるか、それが問題なのだけど。ベイジアンフィルターの仕組みなど、うまく利用できないだろうか...。(すぐには、良い使い方が閃かないけど)

追記

  • > もっともらしくて面白い。でも要約には全然なってない。スパムブログなら十分実用レベルかも知らないが
    • 全くその通りなのです。偶然にも要約されたような文章が出来上がるだけで、決して、係り受けとか、意味を解釈しての要約にはなっていないのだ...。
    • 本来の要約とは、自分の頭で理解して、自分の言葉で簡潔に表現することなので、まじめにそのような処理方法を考えると、自分のレベルでは行き詰まってしまう。
  • > s/的を得た/的を射た/
    • なるほど。すごく勉強になりました。今まで何の疑いも無く「的を得た」を使っていたのでした。
    • http://d.hatena.ne.jp/keyword/%C5%AA%A4%F2%C6%C0
    • それにしても自分は、54.3%(すでに過半数)の誤用している側の一人であったのだが...
    • さらに誤用率が上がれば、そう遠くない将来、「的を得た」も認める、という状況になってしまうかもしれない。
    • それは阻止するべきことなのか、それとも言葉の変化と捉えるべきなのか...
    • やはり、この日記を書いた時点での公式な正解、「的を射た」に修正することにしました。
    • s/的を得た/的を射た/についても、目から鱗(が落ちる)でした。ありがとうございました。