tSeiya's blog

行動結果のアウトプット場

MacでMeCabのJavaバインドを使用する

思ったよりも苦労したので手順をメモしておく。

MeCabとは、オープンソース形態素解析エンジン。詳しくは MeCab Yet Another Part-of-Speech and Morphological Analyze

MeCabJavaバインディングを構築した環境

Macbook
Mac OS X 10.6.8
2GHz Intel Core 2 Duo
2GB 1067 MHz DDR3

$ java -version #Javaのバージョンを確認
java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-10M3527)
Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)

$ brew --version #homebrewのバージョンを確認
0.8.1

ターミナルでMeCabを使用できるようにする。

パッケージ管理ソフトhomebrewを使って入れる。

brew install mecab mecab-ipadic #mecabとその辞書をインストールする(makeも自動で行なってくれる)

mecabコマンドを使用出来ればOK

本題のMeCabJavaバインディング

http://code.google.com/p/mecab/downloads/list からmecab-java-0.993.tar.gzをダウンロード

tar xzvf mecab-java-0.993.tar.gz #解凍、展開する
cd mecab-java-0.993

MacではこのMakefileではうまく動作してくれないので、以下のように変更する。(変更内容は、64bitアーキテクチャでコンパイルするべく、MakeFileの書き換え:http://www.cse.kyoto-su.ac.jp/~g0846020/JavaTips/mecab.htmlの通りにした。)

JAVA_BASE=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK
JAVA_HOME=$(JAVA_BASE)/Home

TARGET=MeCab
JAVAC=javac -J-Dfile.encoding=UTF-8
JAVA=java -Dfile.encoding=UTF-8
JAR=jar -J-Dfile.encoding=UTF-8
CXX=g++
INCLUDE=$(JAVA_BASE)/Headers

PACKAGE=org/chasen/mecab

LIBS=-arch x86_64 `mecab-config --libs`
INC=-arch x86_64 `mecab-config --cflags` -I$(INCLUDE)

all:
	$(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
	$(CXX) -dynamiclib $(TARGET)_wrap.o -o lib$(TARGET).dylib $(LIBS)
	$(JAVAC) $(PACKAGE)/*.java
	$(JAVAC) test.java
	$(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class

test:
	env DYLD_LIBRARY_PATH=. $(JAVA) test

clean:
	rm -fr *.jar *.o *.dylib *.so *.class $(PACKAGE)/*.class

cleanall:
	rm -fr $(TARGET).java *.cxxmecab-java

Makefileが正しくないとき、「java.lang.UnsatisfiedLinkError: no MeCab in java.library.path」や「Exception in thread "main" java.lang.NoClassDefFoundError: test」などのエラーが出た。

makeコマンドを実行して、libMeCab.dylibとMeCab.jarが生成されていればOK
次に、ファイルの置き場所を/usr/local/binに変更して、パスを通す

mv mecab-java-0.993 mecab-java #ファイル名を変更する
mv mecab-java /usr/local/bin/ #場所を変更する

cd ~/
emacs .bashrc #下記参照
source .bashrc #.bashrcの設定を有効にする

bashrcでの設定

#java encoding
JAVAC='javac -encoding UTF-8 -J-Dfile.encoding=UTF-8'
alias javac=$JAVAC
#'javac -encoding UTF-8 -J-Dfile.encoding=UTF-8'
JAVA='java -Dfile.encoding=UTF-8'
alias java=$JAVA

#PATHをあえて指定することで、homebrewのパスの優先度を高める。
PATH=/usr/local/bin:$PATH
export PATH

#java-mecab用のクラスパス
#export CLASSPATH=/usr/local/bin/mecab-java:$CLASSPATH
#export LD_LIBRARY_PATH=/usr/local/bin/mecab-java:$LD_LIBRARY_PATH
#export DYLD_LIBRARY_PATH=/usr/local/bin/mecab-java:$DYLD_LIBRARY_PATH

alias mecajavac='env DYLD_LIBRARY_PATH=".:/usr/local/bin/mecab-java" CLASSPATH=".:$CLASSPATH:/usr/local/bin/mecab-java/MeCab.jar" $JAVAC'
alias mecajava='env DYLD_LIBRARY_PATH=".:/usr/local/bin/mecab-java" CLASSPATH=".:$CLASSPATH:/usr/local/bin/mecab-java/MeCab.jar" $JAVA'

これで毎回クラスパスをしていしなくても、mecajavac ◯◯.javaでコンパイル、mecajava ◯◯で実行できる。

MeCabで形態素解析のプログラムを書いてみる

import org.chasen.mecab.Tagger;
import org.chasen.mecab.Node;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;


public class MeCab {
    static {
		try {
	    	System.loadLibrary("MeCab"); //MeCabを読み込む
		} catch (UnsatisfiedLinkError e) {
			//MeCabが読み込めなかったときの処理
		    System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains\n" + e);
		    System.exit(1);
		}
    }

    public static void main(String[] args)
    {
		HashMap<String, Integer> map = new HashMap<String, Integer>();

		Tagger tagger = new Tagger ("-Ochasen");//形態素解析の準備
	    //String aString = "太郎は二郎にこの本を渡した。";
	    String aString = "吾輩は猫である。名前はまだ無い。吾輩はキャットである。名前はまだナッシン。";
	    System.out.println("入力: "+aString);

	    tagger.parse(aString);//これを書いておかないと以下が上手く動作しなかった。

   		Node node = tagger.parseToNode(aString);//ノードに分解
	    for (;node != null; node = node.getNext())//ノードを順次出力
	    {
	    	String str = node.getFeature();//品詞等の情報を格納
		    String[] strAry = str.split(",");//","で区切って配列に格納

		    String word = node.getSurface(); //単語を格納
		    String feature = strAry[0]; //品詞を格納

			System.out.println(word + "\t" + feature);//単語と品詞を出力

	    	if (map.containsKey(word)){
	    		//もし、既にmapに登録されている単語であれば、カウントをプラスする
		    	map.put(word, map.get(word)+1);//単語wordと既に格納されているカウント数に1を足して登録する
			}else{
				//もし、まだmapに登録されていない単語であれば、カウントを1として登録する	
		    	map.put(word, 1);//単語wordとカウント1をセットで格納する
			}
    	}

    	//mapから格納されている単語とカウントを出力する
		for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
		    Map.Entry entry = (Map.Entry)it.next();
		    Object key = entry.getKey();
		    Object value = entry.getValue();
		    System.out.println("単語: " + key + "\t 出現回数: " + value);
		}
    }
}

出力結果

$ mecajavac MeCab.java
$ mecajava MeCab
入力: 太郎は二郎にこの本を渡した。
	BOS/EOS
太郎	名詞
は	助詞
二郎	名詞
に	助詞
この	連体詞
本	名詞
を	助詞
渡し	動詞
た	助動詞
。	記号
	BOS/EOS
単語: 	 出現回数: 2
単語: 。	 出現回数: 1
単語: 二郎	 出現回数: 1
単語: 本	 出現回数: 1
単語: た	 出現回数: 1
単語: を	 出現回数: 1
単語: は	 出現回数: 1
単語: 渡し	 出現回数: 1
単語: 太郎	 出現回数: 1
単語: この	 出現回数: 1
単語: に	 出現回数: 1
$ mecajavac MeCab.java
$ mecajava MeCab
入力: 吾輩は猫である。名前はまだ無い。吾輩はキャットである。名前はまだナッシン。	BOS/EOS
吾輩	名詞
は	助詞
猫	名詞
で	助動詞
ある	助動詞
。	記号
名前	名詞
は	助詞
まだ	副詞
無い	形容詞
。	記号
吾輩	名詞
は	助詞
キャット	名詞
で	助動詞
ある	助動詞
。	記号
名前	名詞
は	助詞
まだ	副詞
ナッシン	名詞
。	記号
	BOS/EOS
単語: 	 出現回数: 2
単語: まだ	 出現回数: 2
単語: 。	 出現回数: 4
単語: で	 出現回数: 2
単語: 無い	 出現回数: 1
単語: 吾輩	 出現回数: 2
単語: ある	 出現回数: 2
単語: 猫	 出現回数: 1
単語: ナッシン	 出現回数: 1
単語: は	 出現回数: 4
単語: キャット	 出現回数: 1
単語: 名前	 出現回数: 2


参考にしたページ:MeCabをJavaで使えるようにする http://www.cse.kyoto-su.ac.jp/~g0846020/JavaTips/mecab.html

Appendix[Makefileの変更内容]

$ diff -cLINE Makefile Makefile.old
*** INE
--- Makefile.old	2012-02-11 01:46:45.000000000 +0900
***************
*** 1,30 ****
- JAVA_BASE=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK
- JAVA_HOME=$(JAVA_BASE)/Home
- 
  TARGET=MeCab
! JAVAC=javac -J-Dfile.encoding=UTF-8
! JAVA=java -Dfile.encoding=UTF-8
! JAR=jar -J-Dfile.encoding=UTF-8
! CXX=g++
! INCLUDE=$(JAVA_BASE)/Headers
  
  PACKAGE=org/chasen/mecab
  
! LIBS=-arch x86_64 `mecab-config --libs`
! INC=-arch x86_64 `mecab-config --cflags` -I$(INCLUDE)
  
  all:
  	$(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
! 	$(CXX) -dynamiclib $(TARGET)_wrap.o -o lib$(TARGET).dylib $(LIBS)
  	$(JAVAC) $(PACKAGE)/*.java
  	$(JAVAC) test.java
  	$(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class
  
  test:
! 	env DYLD_LIBRARY_PATH=. $(JAVA) test
  
  clean:
! 	rm -fr *.jar *.o *.dylib *.so *.class $(PACKAGE)/*.class
! 
  cleanall:
! 	rm -fr $(TARGET).java *.cxx
\ No newline at end of file
--- 1,27 ----
  TARGET=MeCab
! JAVAC=javac
! JAVA=java
! JAR=jar
! CXX=c++
! INCLUDE=/usr/lib/jvm/java-6-openjdk/include
  
  PACKAGE=org/chasen/mecab
  
! LIBS=`mecab-config --libs`
! INC=`mecab-config --cflags` -I$(INCLUDE) -I$(INCLUDE)/linux
  
  all:
  	$(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
! 	$(CXX) -shared  $(TARGET)_wrap.o -o lib$(TARGET).so $(LIBS)
  	$(JAVAC) $(PACKAGE)/*.java
  	$(JAVAC) test.java
  	$(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class
  
  test:
! 	env LD_LIBRARY_PATH=. $(JAVA) test
  
  clean:
! 	rm -fr *.jar *.o *.so *.class $(PACKAGE)/*.class
! 	
  cleanall:
! 	rm -fr $(TARGET).java *.cxx