マルチラベル分類用のライブラリMulanをjythonから使ってみる
マルチラベル問題とは,「ひとつの事例に複数のラベルがつきうる場合の問題」のことを指す.
例えば,文書分類だと,「サッカーのフーリガンのニュース記事はスポーツのジャンルだし,社会のジャンルでもある」っていうラベルが付く場合を指す.
こういうマルチラベル問題の解き方はいろいろあって,マルチラベルを考慮したアルゴリズムもあれば,問題を分割して二値分類で解いてしまうケースもあって,実に様々.
こんな様々な解き方があるのでは,ひとつひとつの手法を試すのにいちいち実装が大変...というモチベーションで生まれた(たぶん)のがMulanというJavaのライブラリだ.
Mulan: A Java library for multi-label learning
※このJavaライブラリは機械学習ライブラリのwekaの上に作られているので,wekaを使える状態にしておかないといけない.
ここで問題がひとつ.ぼくはJavaが書けない.
そこで,ぼくがとるべき方法は2つあった.
1 Javaの書き方を勉強する
2 めんどいのでJythonで解決する
めんどいので2にした.
MulanのdocumentGetting Started with Mulanにしたがって,MulanExp1.javaをpythonスクリプトに書き直ししてみようとしたが,2つの点で軽くはまった.
1 Jython実行時のクラスパスの通し方
javaコードは,他のjarファイルを参照している時には,クラスパスを通すという作業を実行時にしないといけない.
ネット上に転がっている説明だと
java -cp hoge;hoge;. 実行するjavaクラス
みたいな説明がされているが,これをそのまま鵜呑みにするとハマる.
まず,セミコロン区切りはwindows特有のものらしい.Linuxで実行するときは,コロンで区切らないといけない.なので,Linuxでは
java -cp hoge:hoge:. 実行するjavaのクラス
となる.
さらにjythonで実行時には-cpの前に-Jを付けないといけない.この-Jとは「これ以降はjavaのコンパイラに渡す引数だからね」を意味しているらしい.
だから,jythonコンパイラを呼び出すときには
jython -J-cp hoge:hoge huga.py
になる.
以上を踏まえて,mulanとwekaにクラスパスを通しつつjythonコンパイラを起動するには
kensuke-mi@pine12:/work/kensuke-mi/tmp_working/tmp_mulan/mulan-1.4.0$ jython -J-cp mulan.jar:weka-3.7.6.jar Jython 2.7b1 (default:ac42d59644e9, Feb 9 2013, 15:24:52) [Java HotSpot(TM) 64-Bit Server VM (Oracle Corporation)] on java1.7.0_45 Type "help", "copyright", "credits" or "license" for more information. >>> import Mulan Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named Mulan >>> import mukan Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named mukan >>> import mulan >>> import weka
とすればいい.
2 jythonスクリプトでimportの記述方法
javaのコードでは,ライブラリのimportは
import クラス名
でいいらしいが,jython使用時には違う.
from クラスパス import クラス名
と記述しないといけない.
具体例を出すと,
Javaコードでは,これが
import weka.core.Utils;
Jyhonコードだと,こうなる
from weka.core import Utils;
以上,1と2のハマりポイントを踏まえながら,MulanExp1.javaをMulanExp1.pyに書き直すと以下のようになる.
# -*- coding:utf-8 # java library from here from weka.classifiers.trees import J48; from weka.core import Utils; from mulan.classifier.lazy import MLkNN; from mulan.classifier.meta import RAkEL; from mulan.classifier.transformation import LabelPowerset; from mulan.data import MultiLabelInstances; from mulan.evaluation import Evaluator; from mulan.evaluation import MultipleEvaluation; #Python library from here import sys; #arffFilename = weka.core.Utils.getOption("arff", args); # e.g. -arff emotions.arff #xmlFilename = weka.core.Utils.Utils.getOption("xml", args); # e.g. -xml emotions.xml arffFilename='./emotions.arff'; xmlFilename='./emotions.xml'; dataset = MultiLabelInstances(arffFilename, xmlFilename); learner1 = RAkEL(LabelPowerset(J48())); learner2 = MLkNN(); eval = Evaluator();belPowerset(J48())); numFolds = 10; results = eval.crossValidate(learner1, dataset, numFolds); #System.out.println(results); print results; results = eval.crossValidate(learner2, dataset, numFolds); #System.out.println(results); print results;
ただ,気になるのが一点.
printした時に,一部の文字が?に置き換わってしまっている.
たぶん,javaのコンパイラとpythonの文字仕様が違うから?と思うけども...いまのところよくわからない.
>>追記
おれはJython第1回に解決策が書いてあった.ズバリ,javaの文字出力命令を使ってしまえばよい.
具体的にはheaderに
from java.io import *; from java.lang import *;
を記述して,print文の代わりに,
System.out.println(result)
と記述すればいい.
ついでに,MulanExp2.pyはこうなる
#-*- coding:utf-8 -*- from java.io import FileReader; from mulan.classifier import MultiLabelOutput; from mulan.classifier.meta import RAkEL; from mulan.classifier.transformation import LabelPowerset; from mulan.data import MultiLabelInstances; from weka.classifiers.trees import J48; from weka.core import Instance; from weka.core import Instances; from weka.core import Utils; arffFilename='./emotions.arff'; xmlFilename='./emotions.xml'; dataset = MultiLabelInstances(arffFilename, xmlFilename); RAkEL_model = RAkEL(LabelPowerset(J48())); RAkEL_model.build(dataset); #unlabeledFilename = Utils.getOption("unlabeled", xmlFilename); unlabeledFilename='./emotions.arff'; reader = FileReader(unlabeledFilename); unlabeledData = Instances(reader); numInstances = unlabeledData.numInstances(); #for (int instanceIndex = 0; instanceIndex < numInstances; instanceIndex++) { for instanceIndex in range(numInstances): instance = unlabeledData.instance(instanceIndex); output = RAkEL_model.makePrediction(instance); # do necessary operations with provided prediction output, here just print it out #System.out.println(output); print output;