NO_WAIT

主にプログラミング。趣味的なWebサービスをたくさん作りたいのですが何事も遅々として進みません…

Wikimedia検索インデックスに対するElasticsearchのMLT(More-Like-This)クエリを試す

Elasticsearch の MLT More-Like-This クエリ を試す ツール を作成しました。 テスト用のインデックスには Wikimedia (Wikipedia, Wikiquote, Wikibooks, その他プロジェクト) で提供されているものを使用します。 Wikipedia のインデックスは巨大なので、本記事では、 Wikiquote , Wikibooks あたりを使用します。

Wikimedia 検索インデックスの Elasticsearch への登録手順はありがたいことに

Loading Wikipedia"s Search Index For Testing | Elastic

に詳述されており、ほぼ手順に従えば実現できます。 本記事では、この Elastic によるチュートリアルに従いつつ、若干スクリプトを変更することで、インデックス登録を行います。実験環境は Ubuntu 等の Linux を想定しています。また、ツールを動作させるのに Node.js が必要です。

インデックス登録に続き、拙作の Node.js 製 簡易テストツールで MLT クエリを発行してみます。

なお、 この記事 で紹介したネタサービスである あたまのわるいニュース分類 は本記事の内容がベースになっています。

準備

CirrusSearch インデックスの入手

Index of /other/cirrussearch/current/ から入手できます。 例:

  • jawikiquote-20181203-cirrussearch-content.json.gz 05-Dec-2018 01:38 1978102
  • jawikibooks-20181203-cirrussearch-content.json.gz 05-Dec-2018 01:37 33858395

analysis-icu

Elasticsearch に analysis-icu plugin を導入しておきます。

bin/plugin install analysis-icu

jq

jq も使用します。 Ubuntu の場合下記でインストールします。

sudo apt-get install jq

Elasticsearch インデックスの作成

使用するスクリプト

ここ に記載されているスクリプトもそのまま利用可能です。 このスクリプトを若干改変し、下記のようなスクリプトを作成しました。 本記事ではこれをbulk.shというファイル名で使用します。

bulk.sh

#!/bin/bash

# =============
# Configuration
# =============

SUBCOMMAND=$1
ES=$2
ES=${ES:=localhost:9200}
INDEX=jawikibooks-20181203
WMLANG=ja
WMPROJ=wikibooks
WMDUMP=jawikibooks-20181203-cirrussearch-content.json.gz
WMSITE=${WMLANG}.${WMPROJ}.org
export ES INDEX WMLANG WMDUMP WMSITE

# =============
# Wrappers
# =============

CURL() {
  curl -s -H 'Content-Type: application/json' $@
}

CURL_POST() {
  CURL -XPOST $@
}

CURL_PUT() {
  CURL -XPUT $@
}

CURL_DELETE() {
  CURL -XDELETE $@
}

# =============
# Functions
# =============

es_load() {
  pushd chunks

  for file in *; do
    echo -n "${file}:  "
    OUT=$( CURL_POST $ES/$INDEX/_bulk?pretty --data-binary @$file )
    TOOK=$(echo $OUT|
      grep took | cut -d':' -f 2 | cut -d',' -f 1)
    printf '%7s\n' $TOOK
    [ "x$TOOK" = "x" ] || rm $file
  done

  popd
}

es_setup() {
  HEAD=$( CURL --head $ES/$INDEX )
  HEADHEAD=$(echo $HEAD | head -n 1 | grep '404')
  [ "x$HEADHEAD" != "x" ] || CURL_DELETE $ES/$INDEX

  CURL -s 'https://'$WMSITE'/w/api.php?action=cirrus-settings-dump&format=json&formatversion=2' |
    jq '.content.page.index.analysis.analyzer.text.type = "kuromoji"' |
    jq '.content.page.index.analysis.analyzer.text_search.type = "kuromoji"' |
    jq '{
    analysis: .content.page.index.analysis,
    number_of_shards: 1,
    number_of_replicas: 0
  }' |
  CURL_PUT $ES/$INDEX?pretty -d @-

  CURL -s 'https://'$WMSITE'/w/api.php?action=cirrus-mapping-dump&format=json&formatversion=2' |
    jq .content |
    sed 's/"index_analyzer"/"analyzer"/' |
    sed 's/"position_offset_gap"/"position_increment_gap"/' |
    CURL_PUT $ES/$INDEX/_mapping/page?pretty -d @-

  CURL --head $ES/$INDEX
}

es_split() {
  test -d chunks || mkdir chunks
  pushd chunks
  zcat ../$WMDUMP | split -a 10 -l 500 - $INDEX
  popd
}

es_usage() {
  echo $0 setup localhost:9200
  echo $0 split
  echo $0 load localhost:9200
}

# =============
# Main
# =============

SUBCOMMAND=es_${SUBCOMMAND:=usage}

$SUBCOMMAND

使用方法

  • ./bulk.sh split: スクリプト中のWMDUMP変数に指定されたダンプファイルを カレントディレクトリのchunksディレクトリに分割して保存します。
  • ./bulk.sh setup: スクリプト中のES変数が指定する Elasticsearch ホストに対し、 INDEX変数が指定する名前のインデックス作成を指示します。
  • ./bulk.sh load: chunksディレクトリに分割されたダンプファイルを _bulkで登録します。時間のかかる処理です。

変数 WMPROJ の値も必要であれば変更します。例えば WMPROJ=wikiquote などです。

注意

Elastic サイトにあるサンプルとは異なり、 analyzer の一部を cjk から kuromoji に変更してあります。

スクリプト中以下の行でこの変更処理を行っています。

    jq '.content.page.index.analysis.analyzer.text.type = "kuromoji"' |
    jq '.content.page.index.analysis.analyzer.text_search.type = "kuromoji"' |

analyzer をいろいろ変えて試してみるのも面白いかもしれません。

スクリプト実行

上記bulk.shと同じディレクトリに CirrusSearch インデックスファイル(*.json.gz)をダウンロード しておきます。 また、このファイル名がスクリプト中のWMDUMP変数に指定されていることを確認します。

以下、 Elasticsearch は localhost:9200 で実行されているものと仮定します。

# 必要に応じて bulk.sh に実行権限を付与します。
chmod u+x ./bulk.sh
# インデックスを作成します。
./bulk.sh setup
# ダンプファイルを分割します。
./bulk.sh split
# 登録します。
./bulk.sh load

MLTクエリのテスト

準備

まず MLT クエリテストツールを下記から入手します。

git clone https://github.com/shinaisan/mlt-from-wikipedia

これに含まれるstart.shbulk.sh同様調整します。

  • ES_ENDPOINT: Elasticsearch のホストです。
  • ES_INDEX: 検索対象のインデックス名です。
  • INCLUDE_THUMBNAILS: 検索結果に含まれるサムネイルが鬱陶しいようならyes以外の値に設定します。
#!/bin/bash

PROD=$1
ES_ENDPOINT=http://localhost:9200
ES_INDEX=jawikibooks-20181203
INCLUDE_THUMBNAILS=yes
REACT_APP_TITLE="MLT Test Form"
REACT_APP_WMPROJ=wikibooks

export ES_ENDPOINT ES_INDEX INCLUDE_THUMBNAILS REACT_APP_TITLE REACT_APP_WMPROJ

if [ x"$PROD" == x"prod" ]
then
  node server
else
  concurrently "node server" "react-scripts start"
fi

実行

MLT クエリテストツールは下記要領で開始できます。

cd mlt-from-wikipedia
yarn install
yarn run start

localhost で立ち上げる場合、ブラウザで localhost:3000 を表示すると下記のようなページが表示されます。

f:id:shinaisan:20181212221718p:plain
Top

テキストボックスに適当な文章をペーストし、ボタンを押すと MLTクエリによる文書推薦結果が表示されます。

f:id:shinaisan:20181212221750p:plain
Results

Score タブをクリックすると、スコアの詳細が表示されます。

f:id:shinaisan:20181212221816p:plain
Score

チューニング

現状 MLT クエリパラメータは下記のようにハードコードされています。 fields, minimum_should_match, min_term_freq, min_doc_freq, max_query_terms などの変更を試してみるのが簡単です。 ただし、min_term_freqを2以上にすると、推薦候補なしという結果に終わることが多いです。

    {
      "size": size,
      "explain": "true",
      "_source": [
        "title",
        "language",
        "opening_text",
        "category"
      ],
      "query": {
        "more_like_this": {
          "fields": [
            "title",
            "opening_text",
            "text"
          ],
          "like": text,
          "minimum_should_match": 1,
          "min_term_freq": 1,
          "min_doc_freq": 10,
          "max_query_terms": 25
        }
      }
    }

以上