bin/mahout lucene.vectorを使って、LuceneのインデックスからVectorを作ってみる。
Mahoutのバージョンは0.7。Luceneは3.6.0。
今回は形態素解析しつつインデックスを作成してくれるJapaneseAnalyzerを使う。
こことかからlucene-kuromojiを落としてclasspathに入れるなり、Mavenのdependencyに入れるなりする。
Luceneのインデックス作成はSolrでやるという手もあるけど、今回は普通にJavaのコードを書いて生成してみる。
こんな感じのコード。
public void createFromDir(File inputDir, File outputDir) throws IOException {
// JapaneseAnalyzerを利用
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_36,
new JapaneseAnalyzer(Version.LUCENE_36));
// IndexWriterを生成(Java7のtry-with-resources利用)
try (IndexWriter writer = new IndexWriter(FSDirectory.open(outputDir), conf)) {
// 指定ディレクトリ配下のファイルをインデックスに追加
for (File file : inputDir.listFiles()) {
Document doc = new Document();
// 本文をDocumentに追加(文書自体はストアせずにインデックスのみ追加)
doc.add(new Field("content", FileUtils.readFileToString(file), Field.Store.NO,
Field.Index.ANALYZED, Field.TermVector.YES));
// ファイル名をDocumentに追加(後で判別しやすいように)
doc.add(new Field("title", file.getName(), Field.Store.YES, Field.Index.ANALYZED));
// IDを付けておく(なくても動く)
doc.add(new Field("id", String.valueOf(id++), Field.Store.YES,
Field.Index.NOT_ANALYZED));
// DocumentをWriterに追加
writer.addDocument(doc);
}
}
}
これでテキストファイルがわらわら入ったディレクトリをinputDirに指定して実行すると、outputDirにLuceneのインデックスが出力される。
本文はcontentというフィールドに、ファイル名はtitleというフィールドに登録している。
本文を登録するフィールドはTermVectorの情報がいるので、contentをdocにaddする際はField.TermVector.YESを登録しておく。
試しに上記コードを使って青空文庫から落とした「銀河鉄道の夜」「オツベルと象」「セロ弾きのゴーシュ」「注文の多い料理店」「風の又三郎」をインデックスに投入。
出力されたインデックスに対して、lucene.vectorを実行してみる。
$ bin/mahout lucene.vector \ --dir /tmp/lucene_index \ --output /tmp/lucene_vector \ --dictOut /tmp/lucene_dic \ --field content \ --idField id
--fieldで本文が入ってるフィールド名を指定。
--dirがluceneのインデックスが入ったディレクトリ(ローカル)。結果は--outputで指定した先(HDFS)に出力される。
seqdumperで出力されたVectorファイルを見てみる。
$ bin/mahout seqdumper -i /tmp/lucene_vector -b 80 Input Path: /tmp/lucene_vector Key class: class org.apache.hadoop.io.LongWritable Value Class: class org.apache.mahout.math.VectorWritable Key: 0: Value: 0:{3293:1.9162907600402832,3195:7.901069164276123,3191:8.352917671203613,3190:1. Key: 1: Value: 1:{3285:1.2231435775756836,2228:2.7100443840026855,2215:1.5108256340026855,34:2. Key: 2: Value: 2:{3630:1.9162907600402832,3939:1.4142135381698608,1891:1.9162907600402832,17:1. Key: 3: Value: 3:{3949:2.8284270763397217,3947:1.2231435775756836,3946:3.700752019882202,3944:3 Key: 4: Value: 4:{3949:1.4142135381698608,3948:2.6168267726898193,3947:1.7297861576080322,3946: Count: 5
なにやらそれっぽいVectorファイルが出ている。
--dictOutで指定した先に辞書ができてる(これはローカルに出力される)。これと付き合わせると、どのTermがどのIDになってるのかが分かる。
lucene.vectorコマンド内で使ってるLuceneIteratorというクラスが便利そうだったので、試しにJavaのコード上から使ってみる。
try (IndexReader reader = IndexReader.open(FSDirectory.open(new File("data/lucene_index")))) { VectorMapper mapper = new TFDFMapper(reader, new TFIDF(), new CachedTermInfo(reader, "content", 1, 99)); LuceneIterable ite = new LuceneIterable(reader, "id", "content", mapper, LuceneIterable.NO_NORMALIZING); for (Vector vec : ite) { System.out.println(vec.toString().substring(0, 80)); } }
実行結果。
0:{1925:1.9162907600402832,1904:3.021651268005371,3828:1.2231435775756836,1900:1 1:{2741:1.2231435775756836,1904:1.5108256340026855,3848:1.2231435775756836,1900: 2:{1948:1.0,3867:1.2231435775756836,1891:1.9162907600402832,1888:1.5108256340026 3:{3526:1.5108256340026855,3525:3.3783087730407715,3524:1.5108256340026855,3523: 4:{3949:1.4142135381698608,3948:2.6168267726898193,3947:1.7297861576080322,3946:
なんかVectorになって返ってきた。この辺のコードで引数を変えながら結果と戯れると感覚が養えそう。