おいちゃんと呼ばれています

ウェブ技術や日々考えたことなどを綴っていきます

Rails + Elasticsearch で analyzer 設定をした後のデバッグまとめ

f:id:inouetakuya:20141103002159p:plain

下記で Elasticsearch の analyzer 設定について書いたのですが、今回は設定した後のデバッグについてもう少し掘り下げて書きます。

環境は下記のとおりです。

  • Elasticsearch 1.3.1
  • analysis-kuromoji 2.3.0
  • elasticsearch-rails 0.1.4
  • elasticsearch-model 0.1.4

もくじ

1. お手軽に試したいとき
  (1) curl から叩く
  (2) inquisitor プラグイン
2. 詳しい解析結果がみたいとき(extended analyze プラグイン)
  (1) analyzer を指定したとき
  (2) tokenizer, filter を指定したとき
3. 大量の語句を流し込んで、その解析結果を一覧したいとき
  (1) Searchkick gem
  (2) elasticsearch-rails gem
  (3) elasticsearch gem
  (4) まとめて実行(Rake タスク)
4. analyzer 設定の試行錯誤
5. テスト

1. お手軽に試したいとき

設定後、お手軽にデバッグしたいときは上記エントリーに書いたように、curl から叩くか、Elasticsearch の inquisitor プラグインを使うと良いと思います。

(1) curl から叩く

curl -XGET 'http://localhost:9200/x300_application_development/_analyze?pretty=true&analyzer=kuromoji_analyzer' -d '絶対に手を出してはいけない相手を夜這いしちゃった俺'

(2) inquisitor プラグイン

polyfractal/elasticsearch-inquisitor

http://localhost:9200/_plugin/inquisitor/#/analyzers にアクセスして解析したい語句を入れる。

f:id:inouetakuya:20141108223709p:plain

2. 詳しい解析結果をみたいとき(extended analyze プラグイン

詳しい解析結果をみる方法を、先日の 第7回 Elasticsearch 勉強会@johtani さんに教えていただきました。下記のプラグインを使います。

(1) analyzer を指定したとき

下記のように analyzer を指定して curl を実行すると、各トークンの品詞に至るまで詳細に表示してくれます。

curl -XPOST 'localhost:9200/_extended_analyze?analyzer=kuromoji&pretty' -d '絶対に手を出してはいけない相手を夜這いしちゃった俺'
{
  "custom_analyzer" : false,
  "analyzer" : {
    "kuromoji" : [ {
      "token" : "絶対",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 1,
      "extended_attributes" : {
        "org.apache.lucene.analysis.ja.tokenattributes.BaseFormAttribute" : {
          "baseForm" : null
        },
        "org.apache.lucene.analysis.ja.tokenattributes.InflectionAttribute" : {
          "inflectionType (en)" : null,
          "inflectionType" : null,
          "inflectionForm (en)" : null,
          "inflectionForm" : null
        },
        "org.apache.lucene.analysis.ja.tokenattributes.PartOfSpeechAttribute" : {
          "partOfSpeech (en)" : "noun-adverbial",
          "partOfSpeech" : "名詞-副詞可能"
        },
        "org.apache.lucene.analysis.ja.tokenattributes.ReadingAttribute" : {
          "reading (en)" : "zettai",
          "reading" : "ゼッタイ",
          "pronunciation (en)" : "zettai",
          "pronunciation" : "ゼッタイ"
        },
        "org.apache.lucene.analysis.tokenattributes.KeywordAttribute" : {
          "keyword" : false
        },
        "org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute" : {
          "positionLength" : 1
        },
        "org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute" : {
          "bytes" : "[e7 b5 b6 e5 af be]"
        }
      }
    }, {
      "token" : "",
      "start_offset" : 3,
      "end_offset" : 4,

...

https://gist.github.com/inouetakuya/d64fffb878ca906bd67a

(2) tokenizer, filter を指定したとき

しかし、このプラグインのすごいところは、tokenizer, filter を指定したときで、tokenizer がトークナイズした結果や filter がフィルターをかけた結果を各ステップごとに表示してくれます。

curl -XPOST 'localhost:9200/_extended_analyze?tokenizer=kuromoji_tokenizer&filters=kuromoji_baseform,kuromoji_part_of_speech&pretty' -d '絶対に手を出してはいけない相手を夜這いしちゃった俺'
{
  "custom_analyzer" : true,
  "tokenizer" : {
    "kuromoji_tokenizer" : [ {

// ここに "kuromoji_tokenizer" でトークナイズした結果が表示される
// ...

  },
  "tokenfilters" : [ {
    "kuromoji_baseform" : [ {

// ここに "kuromoji_baseform" でフィルターをかけた結果が表示される
// ...

  }, {
    "kuromoji_part_of_speech" : [ {

// ここに "kuromoji_part_of_speech" でさらにフィルターをかけた結果が表示される
// ...

https://gist.github.com/inouetakuya/f9e28e50ae99daeec9b8

3. 大量の語句を流し込んで、その解析結果を一覧したいとき

大量の語句を流し込んで、その解析結果を一覧したいときもあります。僕は下記のようにしています。

(1) Searchkick gem

Rails から Elasticsearch を使うための Searchkick という gem にデバッグ用のメソッドが用意されています。

Product.searchkick_index.tokens("Dish Washer Soap", analyzer: "default_index")
# ["dish", "dishwash", "washer", "washersoap", "soap"]

(2) elasticsearch-rails gem

Searchkick を参考にして elasticsearch-rails gem で同じことをやろうとすると下記のようになります。

Elasticsearch::Model.client.indices.analyze(
  {
    text:     '絶対に手を出してはいけない相手を夜這いしちゃった俺',
    index:    'my_index',
    analyzer: 'kuromoji'
  }
)['tokens'].map { |token| token['token'] }

実行結果。

2014-11-02 22:20:24 +0900: GET http://localhost:9200/my_index/_analyze?text=%E7%B5%B6%E5%AF%BE%E3%81%AB%E6%89%8B%E3%82%92%E5%87%BA%E3%81%97%E3%81%A6%E3%81%AF%E3%81%84%E3%81%91%E3%81%AA%E3%81%84%E7%9B%B8%E6%89%8B%E3%82%92%E5%A4%9C%E9%80%99%E3%81%84%E3%81%97%E3%81%A1%E3%82%83%E3%81%A3%E3%81%9F%E4%BF%BA&index=my_index&analyzer=kuromoji [status:200, request:0.004s, query:n/a]
2014-11-02 22:20:24 +0900: < {"tokens":[{"token":"絶対","start_offset":0,"end_offset":2,"type":"word","position":1},{"token":"手","start_offset":3,"end_offset":4,"type":"word","position":3},{"token":"出す","start_offset":5,"end_offset":7,"type":"word","position":5},{"token":"いける","start_offset":9,"end_offset":11,"type":"word","position":8},{"token":"相手","start_offset":13,"end_offset":15,"type":"word","position":10},{"token":"夜這い","start_offset":16,"end_offset":19,"type":"word","position":12},{"token":"ちゃう","start_offset":20,"end_offset":23,"type":"word","position":14},{"token":"俺","start_offset":24,"end_offset":25,"type":"word","position":16}]}
=> ["絶対", "手", "出す", "いける", "相手", "夜這い", "ちゃう", "俺"]

(3) elasticsearch gem

Searchkick も elasticsearch-rails も中で elasticsearch という gem を使っているので、それをそのまま使うと下記のような感じです。

client = Elasticsearch::Client.new(host: 'http://localhost:9200')
client.indices.analyze(
  {
    text:     '絶対に手を出してはいけない相手を夜這いしちゃった俺',
    index:    'my_index',
    analyzer: 'kuromoji'
  }
)['tokens'].map { |token| token['token'] }

(4) まとめて実行(Rake タスク)

で、上記を使って、例えば babyshark だと、動画のタイトルを流し込んで結果を確認しています。

  • Rake タスクにして実行しています
  • 複数の analyzer をつくって、それらのアナライズ結果を比較することが多いです
namespace :videos do
  desc 'Analyze videos title'
  task :analyze_title => :environment do |task_name|
    if ENV['FORCE_CREATE_INDEX'] == '1'
      Video.__elasticsearch__.create_index! force: true
    end

    Video.available.each do |video|
      puts video.title

      ['kuromoji', 'kuromoji_analyzer'].each do |analyzer|
        tokens = Elasticsearch::Model.client.indices.analyze(
          {
            text:     video.title,
            index:    [Rails.application.engine_name, Rails.env].join('_'),
            analyzer: analyzer,
          })['tokens']

        puts "=> #{tokens.map { |token| token['token'] }.inspect}(#{analyzer})"
      end
    end

    # ...
  end
end

Elasticsearch に analysis-kuromoji というプラグインを入れると「kuromoji」という analyzer が使えるようになるので、

その kuromoji と こちら で設定した kuromoji_analyzer(custome)の解析結果を見てみると下記のような感じになります。

居酒屋の隅でフェラ抜きする巨乳痴女ギャル
=> ["居酒屋", "隅", "フェラ", "抜き", "巨", "乳", "痴", "女", "ギャル"](kuromoji)
=> ["居酒屋", "の", "隅", "フェラ", "抜き", "する", "巨", "乳", "痴", "女", "ギャル"](kuromoji_analyzer)
海の家で声を押し殺し燃え上がる人妻
=> ["海", "家", "声", "押す", "殺し", "燃え上がる", "人妻"](kuromoji)
=> ["海", "の", "家", "声", "押す", "殺し", "燃え上がる", "人妻"](kuromoji_analyzer)
昭和性豪列伝 青姦でイカされる人妻たち
=> ["昭和", "性", "豪", "列伝", "青", "姦", "イカ", "人妻"](kuromoji)
=> ["昭和", "性", "豪", "列伝", "青", "姦", "イカ", "する", "れる", "人妻", "たち"](kuromoji_analyzer)
援交女子校生 みづき伊織
=> ["援", "交", "女子", "校生", "みつ", "゙き", "伊織"](kuromoji)
=> ["援", "交", "女子", "校生", "みつ", "゙き", "伊織"](kuromoji_analyzer)

...

https://gist.github.com/inouetakuya/0a8931ee48d61214e7c4

ここから、

  • 不要な品詞は削除した方が良さそう
  • ユーザー辞書が必要そう(女優名やエロ用語)

というようなことが読み取れます。

4. analyzer 設定の試行錯誤

デバッグした結果を受けて、analyzer 設定の試行錯誤を繰り返していきます。

例えば不要な品詞の削除であれば kuromoji_part_of_speech というフィルターの stoptags に除外したい品詞を指定します。

analysis: {
  filter: {
    pos_filter: {
      type: 'kuromoji_part_of_speech',
      stoptags: ['助詞-格助詞-一般', '助詞-終助詞'],
    },

# ...

このあたりの話は下記にも書きました。

5. テスト

この検索キーワードで、この動画タイトルが引っ掛かってほしい

という要件はテストに書いておきます。

そうすると、analyzer 設定を変更して「これまで引っ掛かっていたものが引っ掛からなくなる」ということを少しでも減らせます。

テストの書き方については下記に書きました。

関連エントリー