WikipediaをElasticsearchに取り込む

どこまで継続できるかというのはあるけど、Elasticsearchを使って機械学習的なことをちょっとまとめていこうかと。
まず、いろいろとやるためには適用するデータが必要になるけど、ここではWikipediaのデータを使ってあれこれしてみようかな。
MovieLensとか、IRISとか、定番データは世の中に存在していると思うけど、あれはあれできれいな感じだからHelloWorld的なことをしたければ、それらを使ってみるのが良いかと思う。
Elasticsearchをここからダウンロードして、展開した後にkuromojiをインストールしておく。

$ wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.tar.gz
$ tar zxvf elasticsearch-1.4.2.tar.gz
$ cd elasticsearch-1.4.2/
$ bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/2.4.1

今回は、WikipediaのデータをElasticsearchに入れるところまでを試してみる。
Wikipediaのデータを投入するのには、WikipediaのRiverを利用する。
WikipediaのRiverは

$ bin/plugin -install elasticsearch/elasticsearch-river-wikipedia/2.4.1

でElasticsearch 1.4系のものをインストールできる。
インストール後、Elasticsearchを起動する。

$ ./bin/elasticsearch &

次にWikipediaのデータをアーカイブされているところから取得する(ダウンロードしたものを置くところはどこでも良いです)。

$ mkdir wikipedia
$ cd wikipedia/
$ wget http://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2

ここまで準備できれば、あとはRiverを実行するだけだが、事前に登録先のインデックスを作成しておく。

$ curl -XPUT 'http://localhost:9200/jawiki-pages-articles/' -d '
{
  "settings": {
    "index":{
      "refresh_interval": "60s",
      "number_of_replicas":"0",
      "number_of_shards":"5"
    },
    "analysis":{
      "tokenizer":{
        "ja_tokenizer":{
          "type":"kuromoji_tokenizer",
          "mode":"normal",
          "discard_punctuation":"false",
          "user_dictionary":"userdict_ja.txt"
        }
      },
      "analyzer":{
        "ja_analyzer":{
          "type":"custom",
          "tokenizer":"ja_tokenizer"
        }
      }
    }
  },
  "mappings":{
    "page":{
      "_all": {
        "enabled": false
      },
      "properties":{
        "category":{
          "type":"string",
          "index":"not_analyzed"
        },
        "disambiguation":{
          "type":"boolean"
        },
        "link":{
          "type":"string",
          "index":"not_analyzed"
        },
        "redirect":{
          "type":"boolean"
        },
        "redirect_page":{
          "type":"string",
          "index":"not_analyzed",
          "copy_to":"redirect_page_text"
        },
        "redirect_page_text":{
          "type":"string",
          "analyzer":"ja_analyzer"
        },
        "special":{
          "type":"boolean"
        },
        "stub":{
          "type":"boolean"
        },
        "text":{
          "type":"string",
          "analyzer":"ja_analyzer"
        },
        "title":{
          "type":"string",
          "index":"not_analyzed",
          "copy_to":"title_text"
        },
        "title_text":{
          "type":"string",
          "analyzer":"ja_analyzer"
        }
      }
    }
  }
}'

$ES_HOME/config/userdict_ja.txtは空ではだめなので、kuromojiの辞書を適当に作成してください。
kuromojiの設定は後日変えるかもしれませんが、とりあえず、そんな感じにしておきます。
titleプロパティとかはanalyzeしない方が良さそうな場合もありそうなので、形態素解析用にはcopy_toで別プロパティを作成しておくことにします。
あとは、

$ curl -XPUT localhost:9200/_river/jawiki-pages-articles/_meta -d '
{
    "type":"wikipedia",
    "wikipedia":{
        "url":"file:$ES_HOME/wikipedia/jawiki-latest-pages-articles.xml.bz2"
    },
    "index":{
        "bulk_size":1000
    }
}'

を実行してしばらく待つとデータができあがりです($ES_HOMEを適当に置き換えてください)。
latestを持ってきているので、実行したときによって違うかと思いますが、190万ドキュメントで7Gくらいの量のようです。
次回以降でこれをつかっていろいろと試せれば良いかな。

sklearnのVectorizer

sklearnのVectorizerは文を渡して、それをベクトル化してくれるものですが、sklearnに含まれているCountVectorizerとTfidfVectorizer試してみる。
まずは、適当な文の配列を用意する。

$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> corpus = [
... 'aaa bbb ccc ddd eee',
... 'aaa bbb ccc ddd',
... 'aaa bbb ccc',
... 'aaa bbb ddd eee',
... 'aaa ddd eee'
... ]

まずは、単語数を数えて、それをベクトルに変換する。

>>> from sklearn.feature_extraction.text import CountVectorizer
>>> count_vect = CountVectorizer(min_df=1)

という感じで、CountVectorizerを生成する。
そして、先ほど作った文の配列をまるっと渡す。

>>> X_count = count_vect.fit_transform(corpus)

X_countとして結果が返ってきますが、中身を確認すると、

>>> print X_count.todense()
[[1 1 1 1 1]
 [1 1 1 1 0]
 [1 1 1 0 0]
 [1 1 0 1 1]
 [1 0 0 1 1]]

という感じで、単語数を数えて配列のベクトルが得られる。
ベクトルがどの単語に対応しているかは、get_feature_namesで得られる。

>>> count_vect.get_feature_names()
[u'aaa', u'bbb', u'ccc', u'ddd', u'eee']
>>> count_vect.vocabulary_.get('ddd')
3
>>> count_vect.vocabulary_.get('bbb')
1

新規の文をベクトルにする場合は

>>> count_vect.transform(['aaa ccc eee']).toarray()
array([[1, 0, 1, 0, 1]])

とすればOK。
TFIDFを用いて、同じようにベクトルを生成するためにはTfidfVectorizerを利用する。

>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> tfidf_vect = TfidfVectorizer()
>>> X_tfidf = tfidf_vect.fit_transform(corpus)
>>> print X_tfidf.todense()
[[ 0.3592933   0.42480021  0.5049742   0.42480021  0.5049742 ]
 [ 0.41626575  0.49215996  0.58504698  0.49215996  0.        ]
 [ 0.47818893  0.56537308  0.67207785  0.          0.        ]
 [ 0.41626575  0.49215996  0.          0.49215996  0.58504698]
 [ 0.47818893  0.          0.          0.56537308  0.67207785]]
>>> tfidf_vect.get_feature_names()
[u'aaa', u'bbb', u'ccc', u'ddd', u'eee']
>>> tfidf_vect.vocabulary_.get('ddd')
3
>>> tfidf_vect.vocabulary_.get('bbb')
1
>>> tfidf_vect.transform(['aaa ccc eee']).toarray()
array([[ 0.44943642,  0.        ,  0.6316672 ,  0.        ,  0.6316672 ]])

pydotのdot_parserエラー

Couldn't import dot_parser, loading of dot files will not be possible.

というエラーが出る場合は、/usr/local/lib/python2.7/dist-packages/dot_parser.pyとかのdot_parser.pyを

$ diff -ub dot_parser.py.orig dot_parser.py
--- dot_parser.py.orig	2014-12-20 21:21:51.864889677 +0900
+++ dot_parser.py	2014-12-20 21:22:15.938683519 +0900
@@ -23,8 +23,9 @@
 from pyparsing import ( nestedExpr, Literal, CaselessLiteral, Word, Upcase, OneOrMore, ZeroOrMore,
     Forward, NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums,
     restOfLine, cStyleComment, nums, alphanums, printables, empty, quotedString,
-    ParseException, ParseResults, CharsNotIn, _noncomma, dblQuotedString, QuotedString, ParserElement )
+    ParseException, ParseResults, CharsNotIn, dblQuotedString, QuotedString, ParserElement )
+_noncomma = "".join( [ c for c in printables if c != "," ] )
 class P_AttrList:

という感じで修正しておく。