はじめに
Web APIのマーケットプレイスであるApitoreに、Word2Vecを追加しようと思います。Word2Vecがあれば自然言語処理系のアプリケーションで色々な拡がりが出てきます。その話はAPIを公開したときにするとして、今回はJavaでWord2Vecを実装するノウハウを公開します。JavaでWord2Vecを作るなら、本家のGoogleでもオススメしているdeeplearning4jを使うと簡単です。内蔵している形態素解析機能はスペース区切りなので、日本語形態素解析器のKuromojiを使います。
ソースコード等はこちらで公開しています。
deeplearning4jによるword2vec
Javaをどうしても使いたいので、deeplearning4jを利用します。形態素解析器はKuromojiです。今回はMavenを使います。
<dependency>
<groupId>com.atilika.kuromoji</groupId>
<artifactId>kuromoji-ipadic</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-ui</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-nlp</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-native</artifactId>
<version>0.5.0</version>
</dependency>
deeplearning4jのword2vecでkuromojiを利用するために、拡張クラスを実装します。TokenizerとTokenizerFactoryの拡張クラスです。既にScalaで同様のことをやられた方がいたので、そちらを参考にしました。
public class KuromojiIpadicTokenizer implements Tokenizer {
private List<Token> tokens;
private int index;
private TokenPreProcess preProcess;
public KuromojiIpadicTokenizer (String toTokenize) {
com.atilika.kuromoji.ipadic.Tokenizer tokenizer = new com.atilika.kuromoji.ipadic.Tokenizer();
tokens = tokenizer.tokenize(toTokenize);
index = (tokens.isEmpty()) ? -1:0;
}
@Override
public int countTokens() {
return tokens.size();
}
@Override
public List<String> getTokens() {
List<String> ret = new ArrayList<String>();
while (hasMoreTokens()) {
ret.add(nextToken());
}
return ret;
}
@Override
public boolean hasMoreTokens() {
if (index < 0)
return false;
else
return index < tokens.size();
}
@Override
public String nextToken() {
if (index < 0)
return null;
Token tok = tokens.get(index);
index++;
if (preProcess != null)
return preProcess.preProcess(tok.getSurface());
else
return tok.getSurface();
}
@Override
public void setTokenPreProcessor(TokenPreProcess preProcess) {
this.preProcess = preProcess;
}
}
public class KuromojiIpadicTokenizerFactory implements TokenizerFactory {
private TokenPreProcess preProcess;
@Override
public Tokenizer create(String toTokenize) {
if (toTokenize == null || toTokenize.isEmpty()) {
throw new IllegalArgumentException("Unable to proceed; no sentence to tokenize");
}
KuromojiIpadicTokenizer ret = new KuromojiIpadicTokenizer(toTokenize);
ret.setTokenPreProcessor(preProcess);
return ret;
}
@Override
public Tokenizer create(InputStream paramInputStream) {
throw new UnsupportedOperationException();
}
@Override
public void setTokenPreProcessor(TokenPreProcess preProcess) {
this.preProcess = preProcess;
}
@Override
public TokenPreProcess getTokenPreProcessor() {
return this.preProcess;
}
}
さて、実際に学習してみます。学習データの読み込みは以下のようにします。
SentenceIterator iter = new BasicLineIterator(new File("corpus.txt"));
形態素解析の実行は以下のようにします。形態素解析実行後に英単語の活用形部分(e.g. -ed,-ing)の除去、英字小文字化、数字の記号化をしておきます。
final EndingPreProcessor preProcessor = new EndingPreProcessor();
KuromojiIpadicTokenizerFactory tokenizer = new KuromojiIpadicTokenizerFactory();
tokenizer.setTokenPreProcessor( new TokenPreProcess()
{
@Override
public String preProcess( String token )
{
token = token.toLowerCase();
String base = preProcessor.preProcess( token );
base = base.replaceAll( "\\d" , "__NUMBER__" );
return base;
}
});
学習を実行します。パラメータはネットで調べてよく使われていそうなものを採用していますので、適当です。コアは6個使うようにしましたが、タスクマネージャを見ると6個は使ってない気がします。
int batchSize = 1000;
int iterations = 5;
int layerSize = 150;
Word2Vec vec = new Word2Vec.Builder()
.batchSize(batchSize)
.minWordFrequency(5)
.useAdaGrad(false)
.layerSize(layerSize)
.iterations(iterations)
.seed(1)
.windowSize(5)
.learningRate(0.025)
.minLearningRate(1e-3)
.negativeSample(10)
.iterate(iter)
.tokenizerFactory(tokenizer)
.workers(6)
.build();
vec.fit();
学習モデルを保存します。これを忘れると地獄です。
WordVectorSerializer.writeWordVectors(vec, "model-wordvectors.txt");
学習が完了した後は、作ったモデルを使ってアレコレできます。
WordVectors vec = WordVectorSerializer.loadTxtVectors(new File("model-wordvectors.txt"));
Collection<String> lst = vec.wordsNearest("day", 10);
System.out.println(lst);
double cosSim = vec.similarity("day", "night");
System.out.println(cosSim);
double[] wordVector = wordVectors.getWordVector("day");
System.out.println(wordVector);
おわりに
deeplearning4jを使ってWord2Vecが簡単に実装できました。肝心のデモについては準備中です。APIを公開するときにデモ結果を含めて記事にします。ちなみに小さいデータで動作確認はしているので、上記の記事は正確です。現在、Windows10 64bit corei7、メモリ10GBを使って学習中です。丸2日経っても終わっていません。メモリは10GB指定しましたが、だいたい3GB~5GBくらいしか使ってなさそうです。