Solrを推薦システムとして使う（関連アイテム推薦）

# はじめに
こんにちは[rilmayer](https://twitter.com/rilmayer_jp)です。
この記事はアドベントカレンダー「[Search&Discovery 全部俺](https://adventar.org/calendars/4691)」19日目の記事となります。（遅れ

本日は定番のOSS全文検索エンジンである[Solr](https://lucene.apache.org/solr/)を推薦システムとして使ってみようと思います。

Solr基本的な操作は以前[こちらの記事](https://qiita.com/rilmayer/items/be917e3a575e271f8f67)でご紹介しましたが、おさらいをかねてやっていこうと思います。

## Solrの立ち上げ
Dockerで立ち上げてみます。

```bash
# image pull
docker pull solr:8

# docker run（デーモンとして起動）
docker run -d -p 8983:8983 --name my_solr solr:8

# core作成
# 今までと同じくアイドルデータを追加する
docker exec -it my_solr solr create_core -c my_favorite_idols
```

## アイテムの投入
今までと同じくアイドルデータのスキーマです。

```python
import requests
import json

# スキーマ定義
base_url = 'http://localhost:8983/solr'
url = base_url + '/my_favorite_idols/schema'

field_setting = {
    "add-field": [
        {
            "name": "idol_name",
            "type": "string",
            "stored": True,
            "indexed": True,
            "multiValued": False,
        },
        {
            "name": "description",
            "type": "text_ja",
            "stored": True,
            "indexed": True,
            "multiValued": False,
        }
    ]
}

headers = {'content-type': 'application/json'}
res = requests.post(url, data=json.dumps(field_setting), headers=headers)
```

### アイドルデータの追加
[こちら](http://ja.dbpedia.org/)のサイトでSPARQという問い合わせ言語を使ってwikiのデータをバコッと取得しました。

```SPARQL
select distinct ?name ?abstract where {
    ?idol <http://dbpedia.org/ontology/wikiPageWikiLink> <http://ja.dbpedia.org/resource/Category:日本のアイドルグループ> .
    ?idol rdfs:label ?name .
    ?idol <http://dbpedia.org/ontology/abstract> ?abstract .
}
LIMIT 1000
```

上記でデータをCSVとして取得して、以下のようにアイテムを追加します。

```python

# ファイル読み込み
import pandas as pd
csv_file_path = "idol.csv"
df = pd.read_csv(csv_file_path)

# アイテム投入
# アイテム投入
payload = []
for i, row in df.iterrows():
    item = {'idol_name': row.name, 'description': row.abstract}
    payload.append(item)

# データのインデクシング
url = base_url + '/my_favorite_idols/update?commit=true&indent=true'
headers = {'content-type': 'application/json'}
res = requests.post(url, data=json.dumps(payload), headers=headers)
```

これでアイテムが投入されました。

## 特定アイテムに似ているアイテムを取得する
Solrには[more_like_this](https://lucene.apache.org/solr/guide/8_1/morelikethis.html)という便利な機能が提供されています。
（正確にはSolrやElasticsearchのコアライブラリとして利用されている[Lucene](https://lucene.apache.org/)に含まれている機能です。）

アイテムのIDを取得して以下のアイテムをベースに類似検索（関連アイテム推薦）を行ってみます。
Solrの場合はElasticsearchと違って、検索結果のオプションとして類似アイテムの取得を設定するので特定アイテムの類似アイテムが欲しい場合は、検索の中で指定してヒットしたアイテムの類似アイテムを取得するか、IDを指定した検索してその類似アイテムを取得する形になります。

```python
query = '東京女子流'
fields = ['description']
query_function = " AND ".join([f"{field}:{query}" for field in fields])
url = base_url + f"/my_favorite_idols/select?q={query_function}"
headers = {'content-type': 'application/json'}
res = requests.get(url, headers=headers)
results = json.loads(res.content)

# 結果表示
print('hit:  ', results['response']['numFound'])
for i, result in enumerate(results['response']['docs']):
    print(f"[{result['id']}]",result['idol_name'], ':  ', result['description'])
```

上記で取得したIDを元に類似検索をします。

```python
# mlt: moreLikeThisのオプションは以下を参照
#   https://lucene.apache.org/solr/guide/8_1/morelikethis.html
doc_id = [取得したID]
query_function = f"id:{query}"
mlt_option = "mlt=true&mlt.fl=idol_name,description&mlt.mindf=1&mlt.mintf=1&fl=id,idol_name,description"
url = base_url + f"/my_favorite_idols/select?q={query_function}&{mlt_option}"
res = requests.get(url, headers=headers)
results = json.loads(res.content)
```

以下のような結果が得られました。

```
hit:   614
[0] SweetS :   SweetS（スイーツ）は、日本の女性5人組ダンス＆ボーカルユニット。エイベックス・エンタテインメン
[1] Kis-My-Ft2 :   Kis-My-Ft2（キスマイフットツー）は、日本の男性アイドルグループ。所属事務所はジャニーズ事務
[2] AI-SACHI :   AI-SACHI（アイサチ）は、日本の女性2人組ダンス＆ヴォーカルユニット。北海道と長崎県出身。ヴィ
[3] Cheeky Parade :   Cheeky Parade（チィキィパレード）は、2012年に結成された日本の9人組女性アイドルグル
[4] GEM (アイドルグループ) :   GEM（ジェム）は、2012年に結成された日本の女性アイドルグループ、ダンス&ボーカルグループ。所属
```

ちなみにElasticsearchのmore_like_thisの結果は以下のような感じです。
1位は同じですが、その他は違いがありますね〜。

```
[0] SweetS :   SweetS（スイーツ）は、日本の女性5人組ダンス＆ボーカルユニット。エイベックス・エンタテインメン
[1] Cheeky Parade :   Cheeky Parade（チィキィパレード）は、2012年に結成された日本の9人組女性アイドルグル
[2] GEM (アイドルグループ) :   GEM（ジェム）は、2012年に結成された日本の女性アイドルグループ、ダンス&ボーカルグループ。所属
[3] SUPER☆GiRLS :   SUPER☆GiRLS（スーパーガールズ）は、エイベックスのオーディションにより、2010年に結成さ
[4] フェアリーズ :   フェアリーズ（Fairies）は、日本の6人組女性アイドルグループ、ダンスボーカルグループで、201
```

# おわりに
今回はSolrで関連アイテムの取得を行ってみました。
Elasticsearchと比べると若干使い勝手が異なってそれもまた趣がありますね。

設計思想的には、検索結果の中で「あれもこれもあるよ！」といった感じで出すのを想定しているのかもと思いました。
なお、ユーザーの行動ログなどを使ってランキング処理をするためにはまた別の工夫が必要となります。