Azure Cognitive Searchでオートコンプリートやサジェストをしてみる

Posted by johtani on Friday, February 5, 2021

目次

Azure Cognitive SearchにSuggestやAutocompleteの機能があるのを見つけたので、どんな挙動なのかを調べてログとして残しておきます。

公式ドキュメント

日本語で公式ドキュメントが公開されています。使い方や流れについてはこちらをまずは見れば使えると思います。

本ブログでは、ざっくりとした機能の紹介と内部がどんな挙動をしていそうか?、日本語だとどういう感じになるのか?という点を紹介しようと思います。

どんな機能?

検索窓でよく、キーワードを入力しているときに検索キーワードの候補が表示されることがあります。 このキーワードの候補を表示するための機能が今回紹介するSuggesterと呼ばれる機能です(日本語の公式ドキュメントでは「先行入力エクスペリエンス」)。 Suggesterには、以下の2つの機能が用意されています。

  • Autocomplete:キー入力しているときに、入力されている文字列で始まる単語で、検索できるもの(転置インデックスに登録されている単語)をリストで返す機能
  • Suggestion:入力した文字列で始まるキーワードを含む、元のデータを返す機能

利用方法

Suggesterの機能を利用するにはインデックスに設定を追加する必要があります(新規、既存どちらでも可)。 ただ、既存のインデックスに設定を追加した場合、内部にすでに存在するドキュメント(データ)については、このSuggesterのデータは作られません。 ですので、既存データを再度登録しなおすといった作業が必要となるので注意が必要です。

本ブログでは、いくつかAzure Cognitive Searchのサンプルのリクエストが出てきます。Visual Studio CodeのREST Client Extensionの書式となります。拡張機能の簡単な紹介は昨年のブログをご覧ください

設定編

Suggesterの設定では、主に以下の2つを設定する必要があります。

  • name: suggesterの名称。クエリ時に指定します。
  • sourceFields: 入力データのもととなるフィールド名。
    • String型のみ指定可能。また、Azureで用意されたアナライザーだけが利用可能です。自分で用意するカスタムアナライザーは利用できないので注意が必要です(制限についてはこちら)。

今回使用するサンプルのインデックス設定は次の通りです。

インデックス作成のリクエスト(@host@api-keyはご自身のものに置き換えてください。)

## 設定
@host = 名前.search.windows.netと記載
@api-key = APIキーを入力

### Create index with suggester
POST https://{{host}}/indexes/?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
    "name":"suggester-test",
    "fields":[
       {
          "name":"id",
          "type":"Edm.String",
          "key":true,
          "searchable":false
       },
       {
          "name":"name",
          "type":"Edm.String",
          "searchable":true,
          "analyzer":"ngram_analyzer"
       },       
       {
          "name":"category",
          "type":"Edm.String",
          "filterable":true,
          "facetable": true
       }
    ],
    "suggesters": [
       {
          "name": "name_suggester",
          "searchMode": "analyzingInfixMatching",
          "sourceFields": [
             "name"
          ]
       }
    ]
}

公式ドキュメントのサンプルでは日本語がないので、日本語のデータも入力しています。

データ登録リクエスト

### Index data
POST https://{{host}}/indexes/suggester-test/docs/index?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
  "value": [
    {          
      "@search.action": "upload",  
      "id": "1",
      "name": "Microsoft Office",
      "category": "microsoft"
    },
    {          
      "@search.action": "upload",  
      "id": "2",
      "name": "Microsoft Azure",
      "category": "microsoft"
    },
    {          
      "@search.action": "upload",  
      "id": "3",
      "name": "GitHub Enterprise",
      "category": "github"
    },
    {          
      "@search.action": "upload",  
      "id": "4",
      "name": "GitHub dot com",
      "category": "github"
    },
    {          
      "@search.action": "upload",  
      "id": "5",
      "name": "Bluetooth Mic",
      "category": "hardware"
    },
    {          
      "@search.action": "upload",  
      "id": "6",
      "name": "東京スカイツリー",
      "category": "japanese"
    },
    {          
      "@search.action": "upload",  
      "id": "7",
      "name": "東京タワー",
      "category": "japanese"
    },
    {          
      "@search.action": "upload",  
      "id": "8",
      "name": "東京特許許可局",
      "category": "japanese"
    },
    {          
      "@search.action": "upload",  
      "id": "9",
      "name": "グランメゾン東京",
      "category": "japanese"

    },
    {          
      "@search.action": "upload",  
      "id": "10",
      "name": "東京都庁",
      "category": "japanese"
    }
  ]
}

以上が事前準備です。データ登録時に内部でSuggester用のデータを内部で生成しているようです(公式ドキュメント)。

クエリ編

最初に説明しましたが、Suggesterの中ではAutocompleteとSuggestionという2種類の機能が用意されています。それぞれについて例をもとに説明していきます。

Autocomplete API

検索窓に入力された文字を元に、前方一致でどのような単語で検索できるか?という候補を表示するための機能です(2単語も対応していますが今回は省略。APIの仕様はこちら)。

たとえば、micという文字が入力されているところでAutocomplete APIを呼び出す時は、次のようなリクエストです。searchというパラメータに入力値を与えます。suggesterNameはインデックス作成時につけた名前になります。

POST https://{{host}}/indexes/suggester-test/docs/autocomplete?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "mic",
   "suggesterName": "name_suggester"
}

レスポンスとして、次のようなJSONが返ってきます。

{
  "@odata.context": "...",
  "value": [
    {
      "text": "microsoft",
      "queryPlusText": "microsoft"
    },
    {
      "text": "mic",
      "queryPlusText": "mic"
    }
  ]
}

サンプルデータとして登録したデータにcategoryというフィールドを入れていました。 Autocompleteは条件を絞り込んで結果を返すこともできます。 filterにODataの書式で条件を書けます。 categoryフィールドにmicrosoftが設定されているデータだけを取得するということができます。

### Autocomplete with filter
POST https://{{host}}/indexes/suggester-test/docs/autocomplete?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "mic",
   "suggesterName": "name_suggester",
   "filter": "category eq 'microsoft'"
}

レスポンスはこちら。先ほどとは違い、micというデータは返ってきていません。

{
  "@odata.context": "...",
  "value": [
    {
      "text": "microsoft",
      "queryPlusText": "microsoft"
    }
  ]
}

英語の場合、「単語」の単位は字面の通りです。スペースで単語が区切られているのでわかりやすいです。 日本語の場合は普通の人には少し想像しにくいです。 東京という文字を入力してみます。

### Autocomplete in Japanese
POST https://{{host}}/indexes/suggester-test/docs/autocomplete?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "東京",
   "suggesterName": "name_suggester"
}

すると返ってくるのは以下の通り「東京」だけになります。

{
  "@odata.context": "...",
  "value": [
    {
      "text": "東京",
      "queryPlusText": "東京"
    }
  ]
}

入っているデータは「東京スカイツリー」、「東京タワー」などです。 普通に考えると、これらがそのまま返ってくると思うかもしれませんが、違います。

これは、Suggesterの元となるフィールドのAnalyzerの挙動によります。 今回のインデックスのnameフィールドのanalyzerにはja.luceneです。これは、日本語用のアナライザー(Kuromoji)になります。いわゆる形態素解析器で日本語の文字列を「単語」に分割します。 英語についてはスペース区切りで分割しますが、日本語についてはKuromojiが内部の辞書とアルゴリズムに基づいて単語に分割してくれます。 Azure Cognitive Searchでは、Analyzerの挙動を確認するためのAPIも用意してあるので実行しみると、

### Autocomplete in Japanese
POST https://{{host}}/indexes/suggester-test/analyze?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "text": "東京スカイツリー",
   "analyzer": "ja.lucene"
}

このような結果が返ってきます。

{
  "@odata.context": "...",
  "tokens": [
    {
      "token": "東京",
      "startOffset": 0,
      "endOffset": 2,
      "position": 0
    },
    {
      "token": "スカイ",
      "startOffset": 2,
      "endOffset": 5,
      "position": 1
    },
    {
      "token": "ツリー",
      "startOffset": 5,
      "endOffset": 8,
      "position": 2
    }
  ]
}

ja.luceneのAnalyzerによって、3つの単語に分割されているのがわかります。 Autocomplete APIの実行結果も、このAnalyzerによって分割された単語をもとに、候補となる単語を前方一致で検索して結果を返しているのです。 ですので、「東」と入れても「東京」が返ってくるのがわかります。 一方で、「東京ス」と入力した場合は次のような結果となります。

{
  "@odata.context": "...",
  "value": [
    {
      "text": "スカイ",
      "queryPlusText": "東京スカイ"
    }
  ]
}

これは、autocompleteModeと呼ばれるパラメータの挙動となります。 デフォルトでは、oneTermという設定で、最後の単語(例の「東京ス」の場合は「東京」「ス」と区切られるので「ス」という文字を単語とみなす)にマッチする単語(例では「スカイ」)が返ってきます。 queryPlusTextについては、入力された文字にtextで返ってきた単語をくっつけたものが取得できます。

英語であれば、スペースで区切られており、単語が切れているのが簡単にイメージできますが、日本語の場合は少し違和感が出るかと。

このように、入力された文字が含まれる「単語」を基本的に返す動作をするのがAutocomplete APIです。

Suggest API

では、もうひとつのSuggest APIはどういったものでしょうか? autocomplete APIに似ていますが、返ってくるデータが登録されたデータそのものになります。

たとえば、micという文字列を入力とした場合、

POST https://{{host}}/indexes/suggester-test/docs/suggest?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "mic",
   "suggesterName": "name_suggester"
}

次のような結果が返ってきます。Autocompleteの時は単語でしたが、こちらは入力データがそのまま返ってきています。 入力データの中にmicで始まる単語が含まれたものが候補となっています。

{
  "@odata.context": "...",
  "value": [
    {
      "@search.text": "Bluetooth Mic",
      "id": "5"
    },
    {
      "@search.text": "Microsoft Office",
      "id": "1"
    },
    {
      "@search.text": "Microsoft Azure",
      "id": "2"
    }
  ]
}

日本語の場合、Autocompleteとは異なり、その単語を含むものがサジェストされるので、「東京」をsearchに指定すると「東京」を含むデータが出てきます。

### Suggest in Japanese
POST https://{{host}}/indexes/suggester-test/docs/suggest?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "東京",
   "suggesterName": "name_suggester"
}

レスポンスはこちら。単語を含む元の文字列が返ってきます。ここまでは違和感はありません。

{
  "@odata.context": "...",
  "value": [
    {
      "@search.text": "東京タワー",
      "id": "7"
    },
    {
      "@search.text": "グランメゾン東京",
      "id": "9"
    },
    {
      "@search.text": "東京都庁",
      "id": "10"
    },
    {
      "@search.text": "東京スカイツリー",
      "id": "6"
    },
    {
      "@search.text": "東京特許許可局",
      "id": "8"
    }
  ]
}

では、「東京」「特許」という2つの単語をスペースで区切ったものが入力されたとするとどうでしょう?

### Suggest in Japanese
POST https://{{host}}/indexes/suggester-test/docs/suggest?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "東京 特許",
   "suggesterName": "name_suggester"
}

スペースがありますが、内部処理では形態素解析器が分割した後でこの単語の順番で出てくるものを探しているので、出てくることになります。

{
  "@odata.context": "...",
  "value": [
    {
      "@search.text": "東京特許許可局",
      "id": "8"
    }
  ]
}

なお、このSuggestのAPIは、単語の語順を気を付ける必要があります。 「特許」「東京」と順序を入れ替えたスペース区切りの場合は、

### Suggest in Japanese
POST https://{{host}}/indexes/suggester-test/docs/suggest?api-version=2020-06-30
Content-Type: application/json
api-key: {{api-key}}

{
   "search": "特許 東京",
   "suggesterName": "name_suggester"
}

結果は返ってこないです。おそらく内部では、フレーズクエリで、最後の単語だけを前方一致で検索するような仕組みが動いているのだと思われます。 スペースで区切られてはいるが、順序があるので少し違和感を感じるかもしれません。英語だとフレーズの部分のイメージは沸きやすいのですが。

短い文章のデータ(例:本のタイトルやランドマーク名など)では、このSuggestAPIを利用するとデータそのものが返却されるので、便利かもしれません。

まとめ

ということで、Azure Cognitive SearchのSuggesterの簡単な紹介でした。APIのページにはそのほかのパラメータについても説明があるので、使ってみようと思う方は目を通していただくのがよいかと。

今回のブログは裏の仕組みがどんな感じなのか?を想像しながらAPIについて調べた形になります。日本語の場合に、少し違和感を覚える人もいそうだろうなということでブログを書いてみました。 今回は書いていませんが日本語でAutocompleteをやりたい場合は、読み仮名でも漢字が表示されるほうがよいという場合もあると思います。 その場合は、用意されている機能では難しいので、独自実装するといった方法になりそうです。 英語や、読みを利用しない場合は、挙動を理解していれば役に立つ場面もありそうです。


comments powered by Disqus

See Also by Hugo


Related by prelims-cli