Twitterボットを作る場合、単純にワードを引っ掛けるタイプであればいいのですが、目的の話題を持ったツイートだけを引っ掛けたいと思った場合、どうしても人が書いたツイートを解析して・・・という処理が必要になります。そうすると、形態素解析という技術が必要になってくるのですが、Javaの場合はSenがあります。
Sen
Sen は、Java で実装された形態素解析器で、工藤拓さんによりオープンソース(LGPL)で
Senとは - はてなキーワード
開発されている形態素解析器MecabをJavaへポーティングしたライブラリです。
もともとは工藤氏が公開したMecabというライブラリでそれをJava版に氏が移植したらしい。ちなみにMecabとはあのメカブが好きというところからきているらしい。僕も好きっていうのは秘密さ。
Senのライブラリをこちらからダウンロードする。
https://sen.dev.java.net/servlets/ProjectDocumentList?folderID=755&expandFolder=755&folderID=0
僕のダウンロードしたのはバージョン1.2.2.1で、ファイル名は「sen-1.2.2.1.zip」でした。ダウンロードして適当なところに解凍します。解凍すると下記のようなディレクトリになっているはずです。
sen-1.2.2.1 | +−− .settings ・・・ 作者しか分からない世界があってもいいかと | +−− bin ・・・ DOS窓上から実行できるバッチファイルなど | +−− conf ・・・ 設定ファイルがあります。 | +−− demo ・・・ デモ用のJavaファイルがあります。MeCabとのベンチマークみたいですね。 | +−− dics ・・・ 辞書ファイルをココに構築します。 | +−− docs ・・・ JavaDocが入っています。 | +−− lib ・・・ ここにJavaで使うsen.jarが入っています。 | +−− src ・・・ ソースファイルです。
まず、Senを使うには辞書を作る必要があるとのことで、dics/build.xmlを起動して辞書を作ります。build.xmlの起動にはApache Antが必要です。・・・と、ここでPerlが無いとエラーが出ますので、「く、このまっすぃーんにパールを入れるとは・・・」と苦い顔をしながらActivePerlでも入れます。
build.xmlもPerlのインストールしたディレクトリにあわせ、変更します。perl.exeへの絶対パスを書いてやります。
<?xml version="1.0"?> ・・・省略 <project name="ipadic" default="create" basedir="."> <property name="perl.bin" value="C:/Perl/bin/perl.exe"/> ~~~~~~~~~~~~~~~~~~~~~~~ <property name="ipa2mecab" value="./ipa2mecab.pl"/> ・・・省略
実行してみたのが下記のログ、1回Antを実行してPerlが無いって言われて苦悩の後が見えます。
C:\xxx\libs\sen-1.2.2.1\dic>ant Buildfile: build.xml prepare-proxy: prepare-archive: prepare-dics0: prepare-dics: download: melt: prepare: dics0: BUILD FAILED C:\xxxxx\libs\sen-1.2.2.1\dic\build.xml:88: Execute failed: java.io.IOException: Cannot run program "\usr\bin\perl": C reateProcess error=3, ?w?????p?X Total time: 0 seconds C:\xxxx\libs\sen-1.2.2.1\dic>ant Buildfile: build.xml prepare-proxy: prepare-archive: prepare-dics0: prepare-dics: download: melt: prepare: dics0: [exec] ipadic-2.6.0/Adj.dic ... [exec] ipadic-2.6.0/Adnominal.dic ... [exec] ipadic-2.6.0/Adverb.dic ... [exec] ipadic-2.6.0/Auxil.dic ... [exec] ipadic-2.6.0/Conjunction.dic ... [exec] ipadic-2.6.0/Filler.dic ... [exec] ipadic-2.6.0/Interjection.dic ... [exec] ipadic-2.6.0/Noun.adjv.dic ... [exec] ipadic-2.6.0/Noun.adverbal.dic ... [exec] ipadic-2.6.0/Noun.demonst.dic ... [exec] ipadic-2.6.0/Noun.dic ... [exec] ipadic-2.6.0/Noun.nai.dic ... [exec] ipadic-2.6.0/Noun.name.dic ... [exec] ipadic-2.6.0/Noun.number.dic ... [exec] ipadic-2.6.0/Noun.org.dic ... [exec] ipadic-2.6.0/Noun.others.dic ... [exec] ipadic-2.6.0/Noun.place.dic ... [exec] ipadic-2.6.0/Noun.proper.dic ... [exec] ipadic-2.6.0/Noun.verbal.dic ... [exec] ipadic-2.6.0/Others.dic ... [exec] ipadic-2.6.0/Postp-col.dic ... [exec] ipadic-2.6.0/Postp.dic ... [exec] ipadic-2.6.0/Prefix.dic ... [exec] ipadic-2.6.0/Suffix.dic ... [exec] ipadic-2.6.0/Symbol.dic ... [exec] ipadic-2.6.0/Verb.dic ... create: [java] [INFO] MkSenDic - (1/7): reading connection matrix ... [java] [INFO] MkSenDic - connection file = connect.csv [java] [INFO] MkSenDic - charset = EUC_JP [java] [INFO] MkSenDic - (2/7): building type dictionary ... [java] [INFO] MkSenDic - (3/7): writing conection matrix (5 x 1281 x 701 = 4489905) ... [java] [INFO] MkSenDic - (4/7): reading morpheme information ... [java] [INFO] MkSenDic - load dic: dic.csv [java] [INFO] MkSenDic - 50000... [java] [INFO] MkSenDic - 100000... [java] [INFO] MkSenDic - 150000... [java] [INFO] MkSenDic - 200000... [java] [INFO] MkSenDic - 250000... [java] [INFO] MkSenDic - 300000... [java] [INFO] MkSenDic - 350000... [java] [INFO] MkSenDic - (5/7): sorting lex... [java] [INFO] MkSenDic - (6/7): writing token... [java] [INFO] MkSenDic - key size = 378227 [java] [INFO] MkSenDic - (7/7): building Double-Array (size = 325254) ... [java] [INFO] DoubleArrayTrie - save time = 0.104[s] [java] [INFO] MkSenDic - total time = 22[ms] BUILD SUCCESSFUL Total time: 41 seconds
ちなみに僕の環境のようにutf-8以外許さないぜ!という場合には、いくつか設定ファイルを変更します。
一応、サンプルの実行をしてみます。そのまえに環境変数SEN_HOMEを定義します。先ほど解凍したフォルダへのパスです。
で、binフォルダにあるsen.batを起動します。なんかダウンロードしたものでは起動しなかったのでちょっと変更して起動したのは秘密さ。(下は変更したバッチファイルの内容です)
rem set classpath SET CLASSPATH=%SEN_HOME%\lib\sen.jar SET CLASSPATH=%CLASSPATH%;%SEN_HOME%\lib\commons-logging.jar @%JAVA_HOME%\bin\java -Dsen.home=%SEN_HOME% -classpath %CLASSPATH% StringTaggerDemo ${1+"$@"}
実行画面
C:\xxx\libs\sen-1.2.2.1\bin>sen C:\xxx\libs\sen-1.2.2.1\bin>rem set classpath C:\xxx\libs\sen-1.2.2.1\bin>SET CLASSPATH=C:\xxx\libs\sen-1.2.2.1\lib\sen.jar C:\xxx\libs\sen-1.2.2.1\bin>SET CLASSPATH=C:\xxx\libs\sen-1.2.2.1\lib\sen.jar;C:\xxx\libs\sen-1.2.2.1\lib\c mmons-logging.jar C:\xxx\libs\sen-1.2.2.1\bin>java -Dsen.home=C:\xxx\libs\sen-1.2.2.1 -classpath C:\xxx\libs\sen-1.2.2.1\lib\ en.jar;C:\xxx\libs\sen-1.2.2.1\lib\commons-logging.jar StringTaggerDemo ${1+"$@"} done. Please input Japanese sentence: <−− ここで入力待ちになり、好きな言葉を入れる。すもももももももものうち 2011/02/04 22:02:16 net.java.sen.Dictionary <init> 情報: token file = C:\xxx\libs\sen-1.2.2.1\dic/token.sen 2011/02/04 22:02:16 net.java.sen.Dictionary <init> 情報: time to load posInfo file = 10[ms] 2011/02/04 22:02:16 net.java.sen.Dictionary <init> 情報: double array trie dictionary = C:\xxx\libs\sen-1.2.2.1\dic/da.sen 2011/02/04 22:02:16 net.java.sen.util.DoubleArrayTrie load 情報: loading double array trie dict = C:\xxx\libs\sen-1.2.2.1\dic/da.sen 2011/02/04 22:02:16 net.java.sen.util.DoubleArrayTrie load 情報: loaded time = 0.363[ms] 2011/02/04 22:02:16 net.java.sen.Dictionary <init> 情報: pos info file = C:\xxx\libs\sen-1.2.2.1\dic/posInfo.sen 2011/02/04 22:02:16 net.java.sen.Dictionary <init> 情報: time to load pos info file = 0[ms] 2011/02/04 22:02:16 net.java.sen.Tokenizer loadConnectCost 情報: connection file = C:\xxx\libs\sen-1.2.2.1\dic\matrix.sen 2011/02/04 22:02:16 net.java.sen.Tokenizer loadConnectCost 情報: time to load connect cost file = 118[ms] すもももももももものうち すもも (すもも) 名詞-一般(0,3,3) スモモ スモモ も (も) 助詞-係助詞(3,4,1) モ モ もも (もも) 名詞-一般(4,6,2) モモ モモ も (も) 助詞-係助詞(6,7,1) モ モ もも (もも) 名詞-一般(7,9,2) モモ モモ の (の) 助詞-連体化(9,10,1) ノ ノ うち (うち) 名詞-非自立-副詞可能(10,12,2) ウチ ウチ
おお!解析してくれました。
目的はJava上でSenを使うのでした。Java上から使う場合はStringTaggerクラスを使います。このStringTaggerクラスは直接newするのではなく、staticメソッドのStringTagger#getInstance()を使用してインスタンスを取得します。StringTagger#getInstance()の引数はsen.xmlへの絶対パスです。このメソッドでインスタンスを取得したら使い方は簡単で、StringTagger#analyze(String):Token[]メソッドを使用します。
このクラス、戻り値ではTokenの配列を返しますが、自身のインスタンス変数に解析したTokenの配列を溜め込む構造になっていますので、戻り値で戻ってきた配列を使うか、StringTagger#next()でTokenを順次処理するか好きな方で処理します。
例1:
StringTagger st = StringTagger.getInstance("C:/xxx/libs/sen-1.2.2.1/conf/sen.xml"); Token[] tokens = st.analyze("今日も元気だビールがうまい"); for(int i=0; i<tokens.length; i++){ Token token = tokens[i]; ... }
例2:
StringTagger st = StringTagger.getInstance("C:/xxx/libs/sen-1.2.2.1/conf/sen.xml"); st.analyze("今日も元気だビールがうまい"); while(st.hasNext()){ Token token = st.next(); ... }
人の好き好きなんだろうけど僕は後者の方がすきだなぁ・・・とか遠い目をしてみる。本題に戻り、単純に先ほど構築した辞書ファイルを使って日本語の解析をすると名詞1つとっても色々な名詞が解析されてしまう。例えばJava関係のつぶやきを収集したいと思ってもJavaをつぶやきに入れた関係ないつぶやき(今日Javaコーヒー飲んできました。美味しかった!とか・・・)も普通に入るので、辞書ファイルをもっと成長させる必要がありそうです。
Twitter上でBOTが解析して使うためにはTwitterで解析したデータを基にさらに辞書をアップデートという繰り返しで精度を上げる必要がありますね。