2015年6月19日金曜日

Apache Solr 5.x を使った検索システムの構築

オープンソースの検索エンジンApache Solrの使い方を記録しておく。

Solrを動かすまでの環境準備を整えた後は、下記3つを実現する。
試験1. 正規化されたデータをPOSTして登録し、結果を検索できるシステム
試験2. facetを使った地理情報の検索システム
試験3. クローラ(Apache ManifoldCF)との連携


日本語の検索設定(schema.xml)に関してはディフォルトのものを利用する。
実際に動くものを作ってからどのようにチューニングするか判断するのもよいだろう。
チューニング観点は最後部に記載する。



【Solrを動かすまでの環境準備】
◆ java周りのインストール 
java-X.X.X-openjdkをインストールしてJAVA_HOMEの設定をする。
oracle-jdkでなくてもよい。

JAVA_HOMEのパスが参照されるため見えるようにしておく。
# vi /etc/bashrc
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-X.X.X.x86_64/
export PATH=$JAVA_HOME/bin:$PATH


◆ solrのダウンロードと展開
# cd /usr/local/src/
# wget http://ftp.riken.jp/net/apache/lucene/solr/5.1.0/solr-5.1.0.tgz
# tar zxvf solr-5.1.0.tgz
# cd solr-5.1.0


◆ 起動
# bin/solr start
# bin/solr status

ディフォルトは8983/tcpポートが使われる
変更したければオプションを付与、-p 8080 などとすればよい。


◆ 管理画面へアクセス確認
http://192.0.2.1:8983/solr/



【試験1】
◆ 目的
書籍情報を検索できるシステムを構築する。


◆ coreの作成
コアはRDBのスキーマに相当し、コアごとにスキーマ定義やクエリの設定を持つことができる。
1つのsolrインスタンスの中に複数のコア(マルチコア)を設定することで、コアごとにインデックスが保持される。
# bin/solr create -c book

~コアを削除する場合~
# bin/solr delete -c book

coreができていることを確認できる。
# ls server/solr/book/


◆ スキーマの設定
version5からはコアごとのスキーマがないようである。
# ls server/solr/book/conf/schema.xml
No such file or directory

グローバルなスキーマはどこにあるか調べると、ここにあるようである。
# ls ./server/solr/configsets/basic_configs/conf/schema.xml

グローバルのスキーマにないフィールド名と型は自分で定義しないといけないのだろうか。型推論対応ができるようである。
実稼働させる場合には抵抗あるだろうが、試験時のサンプルデータの投入などには活用すればいいだろう。そのため今回はスキーマレスで行う。

参考
https://cwiki.apache.org/confluence/display/solr/Schemaless+Mode
http://stackoverflow.com/questions/28866032/how-to-edit-solr-5-schema-which-is-created-by-default

Solr 5 uses a managed schema by default, while Solr 4 used the schema.xml file.
Solr 5 automatically creates the schema for you by guessing the type of the field.
Once the type is assigned to the field, you can't change it.
You have to set the type of the field before you add data to Solr 5.
To change the schema in Solr 5, you will want to use the Schema API, which is a REST interface. Go to cwiki.apache dot org slash confluence/display/solr/Schema+API (sorry, stackoverflow only lets me post 2 links)



◆ サンプルデータの投入
ここにディフォルトでサンプルデータがある。
# ls example/exampledocs/

ただし、日本語用がないため、外部からもってくる。

[改訂新版]Apache Solr入門――オープンソース全文検索エンジン
http://gihyo.jp/book/2014/978-4-7741-6163-1/support

# cat /var/tmp/sample-books.json
~snip~
{
"summary":"○○スマートフォンの操作解説書",
"genre":"スマートフォン・タブレット・ケータイ",
"genre":"○○、○○",
"author":"○○",
"pub_time":"yyyy-mm-dd",
"title":"○○入門",
"intended_reader":["はじめて○○を使う方","大きな字で解説した本が欲しい方"],
"price":"1260",
"pub_date":"yyyymmdd",
"isbn":"***-*-****-****-*",
"pages":"300",
"url":"http://example.com"
},
~snip~

# bin/post -host 192.0.2.1 -port 8983 -c book /var/tmp/sample-books.json


◆ 検索確認
3つの検索方法をまとめておく。

(管理画面経由)
http://192.0.2.1:8983/solr/
Core Selector -> book -> Queryを選択
検索キーワードを指定

(URLでクエリ投入)
http://192.0.2.1:8983/solr/book/select?q=スマートフォン

(Solritas)
ディフォルトで使える検索アプリSolritasを使うこともできる。
http://192.0.2.1:8983/solr/book/browse


◆ indexの削除
登録した項目を削除する手順を紹介しておく。

(idを指定して個別に削除)
# bin/post -host 192.0.2.1 -port 8983 -c book -d '32f37863-b315-4016-9fbe-7d229d551402'

(検索結果を対象に削除)
# echo $LANG
ja_JP.UTF-8

# bin/post -host 192.0.2.1 -port 8983 -c book -d 'g=スマートフォン'
queryは検索時に利用するq=パラメータのクエリのことである。

(全削除)
# bin/post -host 192.0.2.1 -port 8983 -c book -d '*'



【試験2】
◆ 目的
地図情報を検索できるシステムを構築する。


◆ coreの作成
# bin/solr create -c map


◆ サンプルデータの投入
地図のサンプルとして先のサンプルから以下の地図情報を用意する。
# cat /var/tmp/data.csv
id,shop_name_ja,geo_p,address_ja,area_s
cvs-001,ミニストップ,"35.68176,139.769086",東京都千代田区丸の内1丁目,千代田区
cvs-002,ファミリーマート,"35.681778,139.771317",東京都中央区八重洲1丁目,中央区
cvs-003,セブンイレブン,"35.681691,139.771897",東京都中央区日本橋2丁目,中央区
cvs-004,サークルKサンクス,"35.681342,139.771714",東京都中央区日本橋2丁目,中央区
cvs-005,セブンイレブン,"35.679817,139.771038",東京都中央区日本橋3丁目,中央区
cvs-006,ファミリーマート,"35.678579,139.76739",東京都千代田区丸の内1丁目,千代田区

# bin/post -host 192.0.2.1 -port 8983 -c map -type text/csv /var/tmp/data.csv


◆ 検索確認
八重洲というキーワードが含まれており、
さらに東京駅(北緯:35.681382, 東経:139.766084)から
半径0.5km以内の絞り込み検索を実施する。
http://192.0.2.1:8983/solr/map/select?\
q=address_ja:八重洲\
&fq={!geofilt}\
&sfield=geo_p\
&pt=35.681382,139.766084\
&d=0.5

fq(facet query)で指定した、{!geofilt}は半径検索、矩形時には{!bbox}を利用する。
sfieldは経度・緯度が設定されているフィールド名である。

距離の順番にソートし、取得フィールドと距離を取得する。
http://192.0.2.1:8983/solr/map/select?\
q=address_ja:八重洲\
&fq={!geofilt}\
&sfield=geo_p\
&pt=35.681382,139.766084\
&d=0.5\
&sort=geodist() asc\
&fl=id,shop_name_ja,address_ja,geodist()

空間検索とアセットクエリを組みあわせて、
0.5km以内の件数を取得する。

http://192.0.2.1:8983/solr/map/select?\
q=address_ja:八重洲\
&pt=35.681382,139.766084\
&sfield=geo_p\
&facet=true\
&facet.query={!geofilt d=0.5 key=500m}



【試験3】
◆ 目的
ここではサンプルデータをpostするのではなく、
特定のローカルディレクトリに置いたファイルをクローラに走査させ、
その結果をSolrへ送り登録させる。


◆ coreの作成
# bin/solr create -c local


◆ クローラの用意
クローラにはApache ManifoldCFを利用する。
Apache ManifoldCFはインターネットやイントラネットの様々なサーバに
保管されている文書ファイルやWebページなどのコンテンツを収集し、
それを検索エンジン(ここではsolr)に送ることができるJavaプログラムである。
# cd /usr/local/src/
# wget http://ftp.riken.jp/net/apache/manifoldcf/apache-manifoldcf-2.1/apache-manifoldcf-2.1-bin.tar.gz
# tar zxvf apache-manifoldcf-2.5-bin.tar.gz
# cd apache-manifoldcf-2.5/example/
# java -jar start.jar

Webから設定をしていく。
http://192.0.2.1:8345/mcf-crawler-ui
admin / admin

(出力の設定)
出力先を指定する。
出力コネクション一覧 -> 新しい出力コネクションを追加
名前   -> 名前              : solr  ※任意の名前を決められる
タイプ -> コネクションタイプ: Solr
次へ
サーバ -> サーバ名:192.0.2.1
         ポート:8983
         コア/コレクション名:local

(リポジトリ )
情報抽出対象を指定する。

リポジトリコネクション一覧 -> 新しいコネクションを追加
名前   -> 名前: local  ※任意の名前を決められる
タイプ -> コネクションタイプ: File system

(ジョブ)
ジョブ一覧 -> 新しいジョブを追加
名前: local search
コネクション -> コネクション -> パイプライン -> 接続名:local, solr
出力を追加
リポジトリパス -> リポジトリパス ->
ルートパス: /home/user1/
挿入: 含む, ファイル, *.txt
※含む以外にも除外リストを作成できる

状態とジョブ管理 -> 開始 -> 再スタート(ステータスは更新をクリックで変わる)


◆ 検索確認
Solrへ戻り対象ファイルが検索できるか確認する。
http://192.0.2.1:8983/solr/testcore/browse




以上で最低限solrは動くが、何をしたいのか、用途によって種々の設定を触っていくことになるだろう。詳しい説明は省略するが、見るべきポイントを記載しておく。

【日本語検索の基本設定】
Analyzerの文字列処理の流れは以下の通りである。
<charFilter/> → <tokenizer/> → <filter/>

それぞれの文字列処理において日本語検索時に考慮が必要なものを記録しておく。
schema.xmlファイル内の、fieldTypeのAnalyzerにて設定する。
◆ charFilter
(Mapping)
役割: 定義したルールに従って別の文字列にマッピング
例  :  コンピュータ ⇒ コンピュータ

(HTMLStrip)
役割: HTMLタグを見つけ、タグをスペースに置き換える
例  :  <p>abc</p> ⇒ _abc_

(PatternReplace)
役割: 指定された正規表現の文字列を置き換える
例  :  playing ⇒ play


◆ tokenizer
(JapaneseTokenizerFactory)
役割: Kuromojiを利用した形態素解析
例  :  Solr / は / 検索 / エンジン / です。

(NGramTokenizerFactory) ※minGramSize="2", maxGramSize="2"の場合
役割: N-gramのトークナイザ
例  : So / ol / lr / rは / は検 / 索エ / エン / ンジ / ジン / ンで / です / す。

(StandardTokenizerFactory)
役割: 連続した英数字、カタカナを一つの単語として切り出し
例  :  本日 / Solr / 5.0 / が / リリース / さ / れ / ま / し / た

(WhitespaceTokenizerFactory) ※日本語では使うことは少ないだろう
役割: スペース区切りのトークナイザ
例  :  Solr / is / a / search /engine.


◆ filter
(SynonymFilterFactory)
役割: 同義語の設定(手動で追加)
例  : "引越し"、"引越"、"引っ越し"

(StopFilterFactory)
役割:ストップ語の設定(手動で追加)
例  :"this"、"it"、"です"、"ます"

(CJKWidthFilterFactory)
役割: 全角英数字を半角に、半角カタカナを全角にする
例  : "A" ⇒ "A"、"ア" ⇒ "ア"

(JapanesePartOfSpeechStopFilterFactory)
役割: トークナイズされた単語の品詞情報をもとに、除外品詞の設定
例  : "助詞-格助詞-一般"を無視

(JapaneseReadingFormFilterFactory)
役割: 読みによる設定
例  : "日本" ⇒ "ニッポン"

(JapaneseBaseFormFilterFactory)
役割:動詞や形容詞を基本形へ変換
例  :"買った" ⇒ "買う"+"た"、"買って" ⇒ "買う"+"て"

(JapaneseKatakanaStemFilterFactory)
役割: カタカナ語末尾の表記揺れを吸収する
例  : "コンピューター" ⇒ "コンピュータ"

(LowerCaseFilterFactory)
役割: 英大文字を小文字に変換する
例  : "Solr"  ⇒ "solr"


その他検索時に以下も理解しておく必要がある。
◆ 検索(サーチハンドラ(サーチコンポーネント))
サーチコンポーネントはsolrconfig.xmlにて設定し、サービコンポーネント名をリクエストハンドラで指定して利用する。

(TermsComponent、Suggester)
サジェスション検索を提供

(SpellcheckComponent)
単語綴り誤りによる「もしかしてこれ…」検索を提供

(MoreLikeThisComponent)
レコメンド、類似文書検索を提供

(StatsComponent)
数値フィールドの最小最大値などのを不可情報も返却

(QueryElevationComponent)
特定の検索文字列と文章を関連付けることで意図的なランクアップ

(TermVectorComponent)
検索結果文書のフィールドに含まれる単語数などを返す

(ClusteringComponent)
検索文書をラベル付けされたテーマ別グループへ分類
※Carrot2 Workbenchツールを利用することで結果をビジュアルに表示可能

(Spatial Search)
試験でも利用した、地理的な位置情報を利用した空間検索を提供

(Join検索)
検索にヒットしたドキュメントの特定のフィールドと異なるドキュメントのフィールドの再検索して表示
※分散検索には未対応
レコメンド、類似文書検索

などなど



【次のアクション】
1. Solrをクラウドで利用するSolrCloudの利用
2. 検索結果のランキングに機械学習を応用したIBMのクラウドSolr基盤、Retrieve and Rank(R&A)の利用

1と2についての説明は下記を参照のこと。
http://alpha-netzilla.blogspot.jp/2015/10/solr.html