Azure Cognitive Search の検索結果をパーソナライズしよう

# はじめに
組織内に貯まっている大量な構造化・非構造化データから、新たな価値を見出すためのフルマネージド全文検索サービスである [Azure Cognitive Search](https://www.youtube.com/watch?v=9GcjFofXdd0) を使えば、誰でも簡単に AI 搭載検索エンジンを開発することができます。今回は検索結果をユーザーによって変えるタグブースト機能について紹介します。この機能によってパーソナライズされた検索エンジンを簡単に実装することができます。

# 検索結果のカスタマイズ
Azure Cognitive Search には、[スコアリング プロファイル](https://docs.microsoft.com/azure/search/index-add-scoring-profiles#what-is-a-scoring-profile)という機能が搭載されており、検索結果の順位をカスタマイズすることができます。スコアリング プロファイルによって特定のフィールドを他よりも優先させたり、ダウンロード数が多いドキュメントを上位に表示させたり、新しい記事を上位に表示させたり、特定ワードが含まれるドキュメントを上位に表示させたりできます。

# スコアリング手法
現在実装されている[スコアリングの手法](https://docs.microsoft.com/azure/search/index-add-scoring-profiles#using-weighted-fields)は以下 5 点あり、今回は「タグ」ブーストを利用します。ブーストとは検索スコアを乗算し、値を大きくすることをいいます。

1. 重み付けされたフィールド
クエリーに一致したフィールドの有無によってスコアを修正。
1. 規模
数値の高低に基づいてスコアを修正。例：ダウンロード回数が多いものを上位に
1. 更新間隔
Edm.DateTimeOffset フィールドの値によってスコアを修正。例：新しいものを上位に
1. 距離
近接または地理的な場所によってスコアを修正。例：近いものを上位に
1. **タグ**
**ドキュメントとクエリーの中の共通タグ値に応じてスコアを修正。**

タグブーストは検索クエリーとは別に用意したフィルタリング用クエリーの 1 つで、タグ文字列とドキュメント内に含まれる文字列が一致したときのみ、スコアが乗算されます。

# タグブーストの例
例えば、戦国武将を説明するドキュメントが 4 つあったとします。結果表示には、[Simple-Cognitive-Search-Tester](https://github.com/nohanaga/Simple-Cognitive-Search-Tester) を使用しています。今回スコアリング プロファイルおよびパラメーターを設定できるようにアップデートしました。

検索クエリーは「武将」とします。

```json
{
"search": "武将",
"api-version": "2021-04-30-Preview",
"queryType": "simple",
"searchMode": "any",
"$count": true,
"$top": 50,
"$skip": 0,
"searchFields": "content",
"highlight": "content-3"
}
```

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/8cbff543-3b14-bb06-925e-bac67b46fd85.png)

検索結果およびハイライトは上のようになりました。**徳川家康**がトップに表示されていますね。

ここで、本文から抽出したキーワードの一覧を持ったフィールドに対してタグブーストを設定し、以下のような検索をします。

```json
{
"search": "武将",
"api-version": "2021-04-30-Preview",
"queryType": "simple",
"searchMode": "any",
"$count": true,
"$top": 50,
"$skip": 0,
"searchFields": "content",
"highlight": "content-3",
"scoringProfile": "tagBoost1",
"scoringParameter": "period-鎌倉"
}
```

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/cfff67e4-040c-2a7f-5ea8-b4be05479c6c.png)

あら不思議、**検索クエリーと他のドキュメントの順位は変わっていない**のに、**源頼朝**がトップに表示されましたね。`@search.score` を見ると、Yoritomo.txt だけ `1.6347 -> 16.3473` と **10 倍**になっていることが分かります。これによって、鎌倉時代が好きなユーザーには "鎌倉" を、江戸時代が好きなユーザーには "江戸" をそれぞれ指定することで、ユーザーがより興味があると思われるドキュメントを上位に表示させることができるのです。今回、これを**パーソナライズする**と表現しています。

# 設定方法
[スコアリング プロファイル](https://docs.microsoft.com/azure/search/index-add-scoring-profiles#what-is-a-scoring-profile)は、Azure Portal のインデックス設定から簡単に設定できますし、REST API を利用してインデックス定義の中に含めることができます。

## 必要なもの
- ユーザーごとのタグブーストワード（興味ワード）
- タグブーストさせたいキーワードが一覧になったフィールド
- フィールドは フィルタ可能(filterable) でなければならない
- type は `Collection(Edm.String)` もしくは `Edm.String` だが完全一致で検索されるので、複数キーワードある場合、`Collection(Edm.String)` 一択。

## スコアリング プロファイル定義

GUI ベースで定義するには、Azure Cognitive Search のリソース→インデックス→スコアリング プロファイルから設定できます。例では以下の画面のように設定しました。
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/bf994d1a-8b29-919b-e67d-68d2440e8aab.png)

 - `ブースト` 値には 1 より大きな正の数を指定します。この値が `@search.score` に乗算されます。
 - `タグ パラメーター` には任意のキーを指定します。scoringParameter で指定するキーワードと対応がとれていれば何でも構いません。

REST API で登録する場合、インデックス定義の `scoringProfiles` に以下を含めます。

```json
  "scoringProfiles": [
    {
      "name": "tagBoost1",
      "functions": [
        {
          "fieldName": "words",
          "freshness": null,
          "interpolation": "linear",
          "magnitude": null,
          "distance": null,
          "tag": {
            "tagsParameter": "period"
          },
          "type": "tag",
          "boost": 10
        }
      ],
      "functionAggregation": "sum"
    }
  ]
```

## 検索クエリー
[検索パラメーター](https://docs.microsoft.com/rest/api/searchservice/Search-Documents#query-parameters)に `scoringProfile` および `scoringParameter` を指定します。

```json
"scoringParameter": "<タグパラメーター名>-<タグ1,タグ2>"
```

タグパラメーター名とタグの間はハイフン区切り、タグ要素の区切りはカンマであることに注意してください。また、POST で送信する場合はパラメーター名が、`scoringParameters` に変わることにも注意です。

## タグブースト用キーワード一覧の用意
今回は本文から以下のようなキーワードを抽出し、`Collection(Edm.String)` 形式で保持するフィールドを作成しています。ユーザーの興味が、ドキュメントの `words` フィールドの中のどれかと一致すればよいというわけです。このキーワード一覧の作り方がキモになってきます。Azure Cognitive Search には、埋め込みスキルで[キー フレーズ抽出スキル](https://docs.microsoft.com/azure/search/cognitive-search-skill-keyphrases)がありますが、こちらは抽出内容やワードの分割を制御できないので、うまく完全一致しない可能性がります。

### Yoritomo.txt
```json
"words": [
    "源",
    "頼朝",
    "平安",
    "時代",
    "末期",
    "鎌倉",
    "初期",
    "日本",
    "武将",
    "政治",
    "家",
    "幕府",
    "初代",
    "征夷大将軍"
    ]
```

### tokugawa.txt
```json
"words": [
    "徳川",
    "家康",
    "戦国",
    "時代",
    "江戸",
    "初期",
    "日本",
    "武将",
    "大名"
    ]
```


今回は、[こちら](https://qiita.com/nohanaga/items/cd7871962e2a66e05d23)の記事で作成したワードリストを配列に変換して使用しています。フラットテキストだと完全一致ができませんでした。

## ユーザーごとのタグブーストワード（興味ワード）
システムに実装する際は、検索クエリーを投げる前に、ユーザーの興味ワードをユーザーマスタなどから取得する必要があります。何をユーザーの興味ワードとしてデータベースに保存するかを検討してください。

# まとめ
Azure Cognitive Search を使えば、簡単にユーザーごとに表示結果を最適化した検索エンジンを実装可能です。使い方によっては、検索エンジンとしてだけではなく、レコメンドエンジンとしても使えるということがお判りいただけたかと思います。ファイルのダウンロード回数が多いドキュメントを上位に持ってきたり、最近更新されたドキュメントを上位に持ってくるというのはよく行われていますが、ユーザーごとに結果を変える手法も検討いただければ幸いです。

# 参考
[検索インデックスにスコアリング プロファイルを追加する](https://docs.microsoft.com/azure/search/index-add-scoring-profiles)
[ドキュメントの検索 (Azure Cognitive Search REST API)](https://docs.microsoft.com/rest/api/searchservice/Search-Documents)