第五回統合牧場収穫祭

 いよいよ大詰め、牧場での本年度の活動を簡単にですが、収穫祭にて発表させていただきました。
 今年はあまりやれたことが多くなかったですし、技術開発部のメンバーも少ない中で、少しでも去年から改善できたところをご紹介できればと思い、LTをさせていただきました。使用したスライドはSlideShareにて公開しております。ご興味があれば下記よりご覧ください。

http://www.slideshare.net/tabris2012/5tabris2012

 さて、これで3年間にわたる放牧生活にも、一応の区切りとなります。しかしながら、今年やろうとして作ったプログラムにも、そもそも考え方にもまだ問題が残っているので、隙を見つけてGitHubを通じて更新していこうと思っています。来年度以降、統合牧場がどうなるのかは想像もつきませんが(クラウド化?)何らかの形で、また技術開発を続けていけたらと思います。
 何か更新・開発したら、またこのブログにて公開していくつもりです。これからも、よろしくお願いします。

今年度のまとめ

 さて、統合牧場での仕事も終盤に差し掛かりました。ここで本年度やってきたことをまとめようと思います。
 まずやったのは、PubMed APIを使用して、論文のアブストラクトを取得し、その英文から名詞を取り出す、という実装です。そもそもこれがしたかったのは、ポスターの筆者について、ポスターの内容だけではなく、その筆者がどんな研究をしているのか、その情報をもっと引き出すためでした。このプログラムにより、筆者名を入力すれば、自動的に筆者の論文が指定した数だけ検索され、その中に出現する名詞を出現頻度順に解析してリストを作成することができます。
 このプログラムは「WebAbstract.rb」にまとめました。

 次に、得られた英単語リストから、「その筆者の文脈ではこの単語が出やすい」という過程のもと、複数の筆者をいくつかの”トピック”にまとめるという、トピック解析を実装しました。トピック解析の本体自体は、偉大なる大先輩の@y_benjo氏の残した収穫物を利用しました。私が実装したのは、何度もトピック解析をまわす中で、最適と思われる「トピック数」を返却する部分くらいです。
 さらに加えて、トピック解析の結果をネットワーク図で表すことも実装しました。使用したのはいつも通りGraphvizとGvizで、ネットワーク図では「トピック番号」のノードを中心に、その周りに筆者名が並ぶような形になりました。
 このプログラムは「TopicGraph.rb」としてまとめ、応用先として本年度の「トーゴーの日2013」のポスター関連図を作成しました。また、さらに応用先を広げ、PubMedの検索結果をトピック解析し、研究者のネットワーク図を作成する、という一連の流れをプログラム化しました。「PubmedMap」プロジェクトとしてGitHubで公開しています。

 さて、本年度はあまり牧場に来れる機会も減ってしまい、あまり大きな成果にはならなかったですが、初めてきた時から進めてきた自然言語処理により、ようやく一定の実用品ができたかなという感じです。本年度は私自信も欲しいと思っていた、論文アブストラクトの解析と結果の図示もできましたし、満足です。今後とも人が論文を読むという労力を減らせるように、自然言語処理の応用方法を考えていきたいと思います。

 来週には統合牧場収穫祭もありますので、ここでまとめた本年度の内容を、簡単に紹介できればなと思っております。

GENIA taggerの動作確認

 そろそろ牧場での仕事もまとめに入ろうと思い、今回は、今まで作ってきたPubMed検索結果から研究者ネットワーク図を作るRubyコードが、きちんと動くかどうか確認しました。
 今まで牧場で使ってきたMacBook Proosx 10.5)から、新しく使用するマシン(osx 10.9)に移行したので、現行の環境での確認がてら自分のマシンで動作確認をしていました。
するとやはり問題が生じ、英文を品詞解析する「GENIA tagger」が、Macの最新OSのMavericksではうまくコンパイルできませんでした。
 GENIAのWebページから3.0.1のバージョンをダウンロードし、makeすると、以下のようなエラーが出ました。makeした環境は、「OSX 10.9.1, gcc: Apple LLVM version 5.0 (clang-500.2.79)」です。

error:
invalid operands to binary expression ('const point' and 'const point')
{return __x < __y;}
~~~ ^ ~~~

 調べてみると、GENIAに含まれる「namedentity.cpp」が問題らしく、307行目を以下のように修正したら、うまくコンパイルできました。

旧307行目) bool operator<(const Annotation & x) { return prob > x.prob; }
新307行目) bool operator<(const Annotation & x) const { return prob > x.prob; }

 constがついているかどうかが大事なようですね。
 これでGENIA taggerがうまく動かせることも確認でき、作成した全Rubyコードを実行できました。
 
 さて、お世話になった牧場もついに解散することになり、それまでに収穫物として完成させようと思います。あとわずかですが最後までやりきろうと思います!

PubMed検索から関連筆者をグラフ化

 前回トピック解析を実装できましたので、今回はその結果を使って、筆者をトピックごとに分けてネットワークグラフを作成するところを実装しました。
 使用するネットワークグラフ作成ライブラリは、以前と同じくGvizです。トピック解析では「トピック数がいくつになるのが良いのか」という値も出力されるので、そのトピック数を引数にしたプログラム「TopicGraph.rb」を作成しました。こちらも私のGitHubのPubmedMapレポジトリにアップしております。
 この中では、トピック解析の結果の「.tsv_theta」ファイルを受取って、中に表記されたトピック数分のノードと、その周りに筆者名ノードを配置したグラフを作成します。色はトピック番号と対応していて、トピックへの分類確率が近いほど濃い色になるようにしました。
 分類確率は、各筆者ごとに各トピックに対して計算されるので、確率値上位2つのトピックに対して、エッジがひかれるようにしました。

 今回このプログラムが完成したことで、PubMedでの検索からグラフ作成までの一連の流れが完成しました。まとめますと、

  • 「pubmedAuthor.rb」:検索したい単語から、PubMedを叩いて論文リストを得て、そのAbstractから筆者に特有な単語リストを作る
  • 「AuthorTopicExplorer.rb」:単語リストからトピック解析を行い、筆者をトピックに分ける
  • 「TopicGraph.rb」:トピック解析結果から、筆者のトピックとの関係性ネットワークグラフを作る

という流れになっています。試しにPubMed検索を「diabetes insulin clearance」として、全行程を行ってグラフを作った結果は、以下のようになりました。

トピック解析の実行まで

 前回取得した筆者ごとの単語リストから、トピック解析を行って良さそうなトピック数を調べるスクリプト「AuthorTopicExplorer.rb」を書きました。私のGitHubの方に追加しております。

 このスクリプトでは、まず「著者ごとの単語リスト」を読込んで、LDAトピック解析に使うtsv形式ファイルを作成します。tsvファイルは「筆者名\t単語\t単語…」を、筆者ごとに各行に書き込んだ形式のものになります。
 次にtsvファイルをLDAトピック解析プログラムに渡します。このとき、トピック数を指定して実行するので、指定したトピック数ごとのフォルダに保存させるようにしました。最後に、各トピック数のフォルダを見比べて、どのトピック数が良さそうかを判定します。
 判定方法はまだ決めていませんが、現在は暫定的に、「分類確率50%以上の筆者が、全体の7割」存在する、最大のトピック数、としています。

 今回、このスクリプトを書いていてはまったのが、tsvファイルを書き込んだ後コピーして各フォルダに割り振るところです。書き込む関数でopenしたtsvファイルへのIOを、最後にcloseしていなかったせいで、コピーしたときに中途半端なファイルが生成してしまいました。最初はFileUtils.cpのバグかと思いましたが、そんなことはなかったですね…

 さて、気を取り直して、次回は解析結果からグラフを出力するところを実装していこうと思っております。
 

検索語から筆者の単語リスト作成まで

 前回で一通りのコードがかけたので、今回は実際に検索語を渡して、PubMedでサーチした時の筆者リストと、筆者ごとの使用した単語リストを取得してみました。
 まず実行時に引っかかったのが、筆者名に「ø」などの文字が含まれているとき、open-uriではエラーになってしまう点です。他にもウムラウト文字などがあり、これらにいちいち対応するのは面倒なので、Rubyの「Addressable」を使用することにしました。
 gemから簡単にインストールできたので、open-uriに渡す前に、

api = Addressable::URI.parse(api).normalize.to_s
xml = open(api, 'User-Agent' => 'ruby').read

として変換することで、問題なく検索APIを実行することができました。
 試しに「diabetes insulin clearance」というクエリで実行したところ、2回以上出現した筆者は36名取得でき、彼らの論文Abstractを取得し、その中での単語の出現頻度を計算することができました。

 ここまでのコードはGitHubにアップしました。tabris2012/PubmedMapにあります。
 次回は、得られた単語リストから筆者の関係図を作成する部分を試してみようと思います。

 

筆者の出現頻度リストからAbstract中の単語リストを作成

 GENIA taggerを走らせるクラスが簡単に扱えるようになったので、前回までに作っていた論文筆者リストから、それぞれの筆者の論文要旨を検索し、その中の英文を解析して単語頻度リストを作成する部分を作成しました。
 
 以前までに完成していたのは、適当な検索ワードをPubMedに投げてヒットした著者の出現頻度を得る部分と、筆者と紐付けられた単語リストを使って筆者間の関係図を作る部分だったので、今回はその間を繋ぐところを作成しました。
 まずは筆者リストから、各筆者についてPubMedのAbstractを取得します。Abstract取得は以前作ったメソッドがそのまま使えるので、


author_list.each do |key, value|
if value < 2
break #出現頻度が低ければ終了
end

web_abst.reInit
web_abst.webAbstractByAuthor(key)
web_abst.dumpAbstract
end

として、順番に取得しました。
 次に、そのAbstractをGENIA taggerに渡して、名詞を取得します。これも以前のメソッドをそのまま利用し、

files.each_with_index do |filename, i|
print "\r#{i}"
file = open(filename).read

word_list = key_score.getFreq(file)
key_score.dump_doc_word(word_list, output_folder+filename)
end

で取得できました。
 これでプログラムは全部揃ったので、次回は実際に関係図を出力して確認してみようと思います。またソースコードが割とバラバラになってしまったので、単純なルーチンに関するコードをまとめていくつもりです。