この記事は、ただの集団 AdventCalendar 2019の21日目の記事です。
はじめに
担当日前日に「Elasticsearch で Learning-to-rank やりたいので、環境構築の手順とその使い方についてまとめてね。ヨロピコ!」と振られたので、今回は Elasticsearch with learning-to-rank の構築手順とその使い方を紹介します。
今回作成したものはコチラ
Learning-to-rank とは
検索エンジンにおける learning-to-rank とは、機械学習と検索するデータを使って、検索結果のランキングの順序を改善する手法のことです。順序学習やランキング学習とも呼ばれています。
今回は、Elasticsearch の learning-to-rank のプラグイン を使います。learning-to-rank のレポジトリにある demo を使って、ランキング改善を体験してみたいと思います。
環境構築
demo を試すために、事前に以下の環境構築をします。
- Java の実行環境
- Python3 の実行環境
- Elasticsearch に learning-to-rank プラグインを導入
プラグイン入り Elasticsearch の構築は、docker image を使うと楽です。
以下、Elasticsearch with Learning-to-Rank docker image のサンプル
FROM elasticsearch:7.4.1
RUN bin/elasticsearch-plugin install -b http://es-learn-to-rank.labs.o19s.com/ltr-1.1.2-es7.4.1.zip
環境構築後、Learning-to-Rank のレポジトリをクローンして demo ディレクトリに移動しましょう。
$ git clone https://github.com/o19s/elasticsearch-learning-to-rank.git
$ cd elasticsearch-learning-to-rank/demo
demo ディレクトリへ移動後、順番にスクリプトを実行して Learning-to-Rank を体験しましょう!
データとライブラリの準備
prepare.py を実行して、検索データと学習モデルを作成するライブラリ(Ranklib)をダウンロードします。
$ python prepare.py
実行すると、映画のデータ(tmdb.json)とランキング学習のライブラリ(RankLibPlus-0.1.0.jar)をダウンロードします。(tmdb.jsonはサイズが大きいのでダウンロードに時間がかかるので注意!)
ダウンロード完了後、Elasticsearch の環境を整えます。
Elasticsearch の環境を整える
Learning-to-Rank のプラグインを導入した Elasticsearch を立ち上げ、データを入れます。Elasticsearch 起動後、index_ml_tmdb.py を実行してインデックスの設定とデータの挿入を行います。以下のスクリプトを実行すると、tmdb.json を Elasticsearch にインサートします。
$ python index_ml_tmdb.py
インデックスの設定とデータの挿入が完了したら、次は学習に使う feature 変換のクエリを設定します。以下のスクリプトを実行すると、学習に使う field を設定します。(設定されるフィールドは demo/1.json と demo/2.json 参照。demo では、title と overview の検索スコアを feature として設定している。)
$ python load_features.py
データを feature に変換する field の準備ができたら、モデル作成を行います。
Elasticsearch にモデルをデプロイする
train.py を実行してモデルを作成して、Elasticsearch にデプロイします。
$ python train.py
train.py では、以下の処理が実行されます。
- 前処理
- sample_judgements.txt をパースして、ranklib format に変換する。
- モデル生成
- Ranklib を実行して、モデル作成を行います。
- モデルのデプロイ
- Easticsearch にモデルを導入する。
前処理
demo では、sample_judgements.txt を学習して、検索結果のランキングを改善します。
sample_judgements.txt は、検索クエリ(qid)に対する検索結果(#7555 Rambo, #1370 Rambo III, ...)を表しており、検索クエリと検索結果のペアごとに評価値(grade)を設定しています。demo では、以下3種のクエリがあります。
# qid:1: rambo
# qid:2: rocky
# qid:3: bullwinkle
各クエリの検索結果に対して、grade を設定します。(数字が大きいほど良い。)
# grade (0-4) queryid docId title
4 qid:1 # 7555 Rambo
3 qid:1 # 1370 Rambo III
3 qid:1 # 1369 Rambo: First Blood Part II
3 qid:1 # 1368 First Blood
0 qid:1 # 136278 Blood
0 qid:1 # 102947 First Daughter
0 qid:1 # 13969 First Daughter
0 qid:1 # 61645 First Love
0 qid:1 # 14423 First Sunday
0 qid:1 # 54156 First Desires
4 qid:2 # 1366 Rocky
3 qid:2 # 1246 Rocky Balboa
3 qid:2 # 60375 Rocky VI
3 qid:2 # 1371 Rocky III
3 qid:2 # 1375 Rocky V
3 qid:2 # 1374 Rocky IV
0 qid:2 # 110123 Incredible Rocky Mountain Race
0 qid:2 # 17711 The Adventures of Rocky & Bullwinkle
0 qid:2 # 36685 The Rocky Horror Picture Show
4 qid:3 # 17711 The Adventures of Rocky & Bullwinkle
0 qid:3 # 1246 Rocky Balboa
0 qid:3 # 60375 Rocky VI
0 qid:3 # 1371 Rocky III
0 qid:3 # 1375 Rocky V
0 qid:3 # 1374 Rocky IV
このデータを Ranklib Format に変換すると、以下のようなデータになります。
4 qid:1 1:12.318474 2:10.573917 # 7555 rambo
3 qid:1 1:10.357875 2:11.950391 # 1370 rambo
3 qid:1 1:7.010513 2:11.220095 # 1369 rambo
3 qid:1 1:0.0 2:11.220095 # 1368 rambo
0 qid:1 1:0.0 2:0.0 # 136278 rambo
0 qid:1 1:0.0 2:0.0 # 102947 rambo
0 qid:1 1:0.0 2:0.0 # 13969 rambo
0 qid:1 1:0.0 2:0.0 # 61645 rambo
0 qid:1 1:0.0 2:0.0 # 14423 rambo
0 qid:1 1:0.0 2:0.0 # 54156 rambo
4 qid:2 1:10.686391 2:8.814846 # 1366 rocky
3 qid:2 1:8.985554 2:9.984511 # 1246 rocky
3 qid:2 1:8.985554 2:8.067703 # 60375 rocky
3 qid:2 1:8.985554 2:5.660549 # 1371 rocky
3 qid:2 1:8.985554 2:7.300772 # 1375 rocky
3 qid:2 1:8.985554 2:8.814846 # 1374 rocky
0 qid:2 1:6.815921 2:0.0 # 110123 rocky
0 qid:2 1:6.0816855 2:8.725066 # 17711 rocky
0 qid:2 1:6.0816855 2:5.9764795 # 36685 rocky
4 qid:3 1:7.6720834 2:12.722421 # 17711 bullwinkle
0 qid:3 1:0.0 2:0.0 # 1246 bullwinkle
0 qid:3 1:0.0 2:0.0 # 60375 bullwinkle
0 qid:3 1:0.0 2:0.0 # 1371 bullwinkle
0 qid:3 1:0.0 2:0.0 # 1375 bullwinkle
0 qid:3 1:0.0 2:0.0 # 1374 bullwinkle
このデータを使ってモデル作成を行います。
モデル生成
モデルを作成する。demo では、以下のモデルが生成されます。
- MART
- RankNet
- RankBoost
- AdaRank
- coord Ascent
- LambdaMART
- ListNET
- Random Forest
- Linear Regression
(各モデルの詳細について、今回は割愛します。)
モデルのアップロード
Elasticsearch にモデルをアップロードする。Post リクエストで生成したモデルをアップロードする。以下サンプル。
POST _ltr/_featureset/movie_features/_createmodel
{
"model": {
"name": "test_9",
"model": {
"type": "model/ranklib",
"definition": "## Linear Regression\n## Lambda = 1.0E-10\n0:0.2943936467995844 1:0.2943936467995844 2:0.12167703031808977"
}
}
}
アップロードしたモデルを使って検索するときは、model.name を指定します。
検索する
実際に検索して、Learning-to-Rank による検索結果の改善を体験してみましょう!search.py を実行すると、以下の検索結果が得られます。
$ python search.py Rambo
{"query": {"multi_match": {"query": "Rambo", "fields": ["title", "overview"]}}, "rescore": {"query": {"rescore_query": {"sltr": {"params": {"keywords": "Rambo"}, "model": "test_6"}}}}}
Rambo
Rambo III
Rambo: First Blood Part II
First Blood
In the Line of Duty: The F.B.I. Murders
Son of Rambow
Spud
$
わかりづらいので、Learning-to-Rank あり/なしを比較した結果を用意しました。それぞれ以下の結果が得られます。
## search with learning-to-rank
1 Rambo
2 Rambo III
3 Rambo: First Blood Part II
4 First Blood
5 In the Line of Duty: The F.B.I. Murders
6 Son of Rambow
7 Spud
## search without learning-to-rank
1 Rambo
2 Rambo III
3 First Blood
4 Rambo: First Blood Part II
5 In the Line of Duty: The F.B.I. Murders
6 Son of Rambow
7 Spud
学習結果が反映されてますね!!
まとめ
今回は、Elasticsearch で Learning-to-rank を試す手順の紹介と、Learning-to-Rank を検索エンジンに反映までやりました。今回使ったライブラリをお手軽に試せるようにレポジトリにまとめましたので、使っていただければ幸いです。
また、この記事では実行手順を紹介しましたが、機会があれば Learning-to-Rank の各ロジックや今回使ったライブラリの仕組みの詳細を紹介したいです。