次の方法で共有


チュートリアル:電話番号のカスタム アナライザーを作成する

検索ソリューションでは、複雑なパターンや特殊文字を含む文字列は、 既定のアナライザー がパターンの意味のある部分を取り除いたり誤って解釈したりするため、操作が困難になる場合があります。 これにより、ユーザーが期待する情報が見つからない検索エクスペリエンスが低下します。 電話番号は、分析が困難な文字列の典型的な例です。 これらはさまざまな形式で提供され、既定のアナライザーで無視される特殊文字が含まれています。

このチュートリアルでは、件名として電話番号を使用して、 カスタム アナライザーを使用してパターン化されたデータの問題を解決する方法について説明します。 この方法は、電話番号に対してそのまま使用することも、URL、電子メール、郵便番号、日付など、同じ特性 (特殊文字でパターン化) を持つフィールドに適用することもできます。

このチュートリアルでは、REST クライアントと Azure AI 検索 REST API を使用して、次の操作を行います。

  • 問題を把握する
  • 電話番号を処理するための初期カスタム アナライザーを開発する
  • カスタム アナライザーをテストする
  • カスタム アナライザーの設計を繰り返し、結果をさらに改善する

前提条件

ファイルのダウンロード

このチュートリアルのソース コードは、Azure-Samples/azure-search-rest-samples GitHub リポジトリの custom-analyzer.rest ファイルにあります。

管理者キーと URL をコピーする

このチュートリアルの REST 呼び出しには、検索サービス エンドポイントと管理 API キーが必要です。 これらの値は Azure portal から取得できます。

  1. Azure portal にサインインし、[概要] ページに移動し、URL をコピーします。 たとえば、エンドポイントは https://mydemo.search.windows.net のようになります。

  2. [設定]>[キー] で管理者キーをコピーします。 管理者キーは、オブジェクトの追加、変更、削除で使用します。 2 つの交換可能な管理者キーがあります。 どちらかをコピーします。

    Azure portal の URL キーと API キーのスクリーンショット。

有効な API キーは、要求を送信するアプリケーションとそれを処理する検索サービスとの間に、要求ごとに信頼を確立します。

初期インデックスを作成する

  1. Visual Studio Code で新しいテキスト ファイルを開きます。

  2. 前のセクションで収集した検索エンドポイントと API キーに変数を設定します。

    @baseUrl = PUT-YOUR-SEARCH-SERVICE-URL-HERE
    @apiKey = PUT-YOUR-ADMIN-API-KEY-HERE
    
  3. .rest ファイル拡張子でファイルを保存します。

  4. 次の例を貼り付けて、idphone_number の 2 つのフィールドを含む phone-numbers-index という小さなインデックスを作成します。 まだアナライザーを定義していないので、 standard.lucene アナライザーが既定で使用されます。

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
      {
        "name": "phone-numbers-index",  
        "fields": [
          {
            "name": "id",
            "type": "Edm.String",
            "key": true,
            "searchable": true,
            "filterable": false,
            "facetable": false,
            "sortable": true
          },
          {
            "name": "phone_number",
            "type": "Edm.String",
            "sortable": false,
            "searchable": true,
            "filterable": false,
            "facetable": false
          }
        ]
      }
    
  5. [要求の送信] をクリックします。 HTTP/1.1 201 Created応答があり、応答本文にインデックス スキーマの JSON 表現を含める必要があります。

  6. さまざまな電話番号形式を含むドキュメントを使用して、インデックスにデータを読み込みます。 これがテスト データです。

    ### Load documents
    POST {{baseUrl}}/indexes/phone-numbers-index/docs/index?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
      {
        "value": [
          {
            "@search.action": "upload",  
            "id": "1",
            "phone_number": "425-555-0100"
          },
          {
            "@search.action": "upload",  
            "id": "2",
            "phone_number": "(321) 555-0199"
          },
          {  
            "@search.action": "upload",  
            "id": "3",
            "phone_number": "+1 425-555-0100"
          },
          {  
            "@search.action": "upload",  
            "id": "4",  
            "phone_number": "+1 (321) 555-0199"
          },
          {
            "@search.action": "upload",  
            "id": "5",
            "phone_number": "4255550100"
          },
          {
            "@search.action": "upload",  
            "id": "6",
            "phone_number": "13215550199"
          },
          {
            "@search.action": "upload",  
            "id": "7",
            "phone_number": "425 555 0100"
          },
          {
            "@search.action": "upload",  
            "id": "8",
            "phone_number": "321.555.0199"
          }
        ]  
      }
    
  7. ユーザーが入力するクエリと同様のクエリを試します。 たとえば、ユーザーは任意の数の形式で (425) 555-0100 を検索しても、結果が返されることを期待できます。 まず、 (425) 555-0100を検索します。

    ### Search for a phone number
    GET {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2024-07-01&search=(425) 555-0100  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    

    このクエリは、4 つの予期される結果のうち 3 つを返しますが、2 つの予期しない結果も返します。

    {
        "value": [
            {
                "@search.score": 0.05634898,
                "phone_number": "+1 425-555-0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425 555 0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425-555-0100"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "(321) 555-0199"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "+1 (321) 555-0199"
            }
        ]
    }
    
  8. 書式設定なしでもう一度やり直してください: 4255550100

     ### Search for a phone number
     GET {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2024-07-01&search=4255550100  HTTP/1.1
       Content-Type: application/json
       api-key: {{apiKey}}
    

    このクエリはさらに悪く、4 つある正しい一致のうち 1 つだけが返されます。

    {
        "value": [
            {
                "@search.score": 0.6015292,
                "phone_number": "4255550100"
            }
        ]
    }
    

多くの人がこれらの結果に戸惑うことでしょう。 次のセクションでは、これらの結果を取得する理由について説明します。

アナライザーのしくみを確認する

これらの検索結果を理解するには、アナライザーの動作を理解する必要があります。 そこから、 Analyze API を使用して既定のアナライザーをテストし、ニーズをより適切に満たすアナライザーを設計するための基盤を提供できます。

アナライザーは、クエリ文字列とインデックス付きドキュメントのテキストを処理するフルテキスト検索エンジンのコンポーネントです。 シナリオに応じて、さまざまなアナライザーがさまざまな方法でテキストを操作します。 このシナリオでは、電話番号に特化したアナライザーを作成する必要があります。

アナライザーは次の 3 つのコンポーネントから成ります。

次の図は、これら 3 つのコンポーネントが連携して文をトークン化する方法を示しています。

文をトークン化するアナライザー処理の図

その後、高速なフルテキスト検索を可能にする転置インデックスにこれらのトークンが格納されます。 フルテキスト検索は、字句解析中に抽出された一意の語句すべてを、それが出現するドキュメントへと、転置インデックスによってマッピングすることで実現されます。 次の図に例を示します。

転置インデックスの例

すべての検索は、転置インデックスに格納された語句の検索に帰結します。 ユーザーからクエリが送信されると、次の処理が行われます。

  1. クエリが解析されて、検索語が解析されます。
  2. 逆インデックスは、用語が一致するドキュメントをスキャンします。
  3. スコアリング アルゴリズムは、取得したドキュメントをランク付けします。

類似性をランク付けするアナライザー処理の図

検索語が転置インデックス内の語句と一致しなかった場合、結果は返されません。 クエリのしくみの詳細については、 Azure AI Search でのフルテキスト検索に関するページを参照してください。

注意

部分的な語句のクエリは、この規則の重要な例外です。 通常の用語クエリとは異なり、これらのクエリ (プレフィックス クエリ、ワイルドカード クエリ、正規表現クエリ) は字句分析プロセスをバイパスします。 部分的な語句は、小文字化のみが適用されて、インデックス内の語句と比較されます。 これらの種類のクエリをサポートするようにアナライザーが構成されていない場合、一致する用語がインデックスに存在しないため、予期しない結果が表示されることがよくあります。

Analyze API を使用してアナライザーをテストする

Azure AI 検索には、Analyze API が用意されています。Analyze API を使用してアナライザーをテストし、そのテキスト処理の動作を理解することができます。

次の要求を使用して Analyze API を呼び出します。

POST {{baseUrl}}/indexes/phone-numbers-index/analyze?api-version=2024-07-01  HTTP/1.1
  Content-Type: application/json
  api-key: {{apiKey}}

  {
    "text": "(425) 555-0100",
    "analyzer": "standard.lucene"
  }

この API は、指定したアナライザーを使用して、テキストから抽出されたトークンを返します。 標準の Lucene アナライザーは、電話番号を 3 つの個別のトークンに分割します。

{
    "tokens": [
        {
            "token": "425",
            "startOffset": 1,
            "endOffset": 4,
            "position": 0
        },
        {
            "token": "555",
            "startOffset": 6,
            "endOffset": 9,
            "position": 1
        },
        {
            "token": "0100",
            "startOffset": 10,
            "endOffset": 14,
            "position": 2
        }
    ]
}

反対に、区切りなしで書式設定された電話番号 4255550100 は単一のトークンにトークン化されています。

{
  "text": "4255550100",
  "analyzer": "standard.lucene"
}

応答:

{
    "tokens": [
        {
            "token": "4255550100",
            "startOffset": 0,
            "endOffset": 10,
            "position": 0
        }
    ]
}

インデックスが作成されたドキュメントと検索語の両方が分析されることに注意してください。 前の手順の検索結果を思い出して、それらの結果が返される理由を確認できます。

最初のクエリでは、トークンの 1 つ ( 555) が検索した用語のいずれかに一致したため、予期しない電話番号が返されます。 2 番目のクエリでは、トークン一致の 4255550100を持つ唯一のレコードであるため、1 つの数値のみが返されます。

カスタム アナライザーを構築する

表示されている結果を理解したら、トークン化ロジックを改善するためのカスタム アナライザーを構築します。

目標は、インデックスされた文字列やクエリの書式に関係なく、電話番号に対する直感的な検索を実現することです。 この結果を得るには、 文字フィルタートークナイザー、トークン フィルターを指定 します

文字フィルター

文字フィルターは、トークナイザーにフィードされる前にテキストを処理します。 文字フィルターの一般的な用途は、HTML 要素をフィルター処理し、特殊文字を置き換えることです。

電話番号の場合、すべての電話番号形式に同じ特殊文字とスペースが含まれていないため、空白文字と特殊文字を削除する必要があります。

"charFilters": [
    {
      "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
      "name": "phone_char_mapping",
      "mappings": [
        "-=>",
        "(=>",
        ")=>",
        "+=>",
        ".=>",
        "\\u0020=>"
      ]
    }
  ]

フィルターによって、-()+. と、空白が入力から削除されます。

input 出力
(321) 555-0199 3215550199
321.555.0199 3215550199

Tokenizer

トークナイザーは、テキストをトークンに分割すると共に、その過程で一部の文字 (句読点など) を破棄します。 多くの場合、文を個々の単語に分割するのがトークン化の目標です。

このシナリオでは、キーワード トークナイザー ( keyword_v2) を使用して、電話番号を 1 つの用語としてキャプチャします。 「 代替アプローチ 」セクションで説明されているように、この問題を解決する唯一の方法ではありません。

キーワード トークナイザーは、常に 1 つの用語として指定されたのと同じテキストを出力します。

input 出力
The dog swims. [The dog swims.]
3215550199 [3215550199]

トークン フィルター

トークン フィルターは、トークナイザーによって生成されたトークンを変更またはフィルターで除外します。 トークン フィルターの一般的な用途の 1 つは、lowercase トークン フィルターを使用して、すべての文字を小文字化することです。 もう 1 つの一般的な用途は、theandisなどのストップワードを除外することです。

このシナリオではこれらのフィルターのいずれかを使用する必要はありませんが、nGram トークン フィルターを使用して電話番号の部分的な検索を許可します。

"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
    "name": "custom_ngram_filter",
    "minGram": 3,
    "maxGram": 20
  }
]

NGramTokenFilterV2

nGram_v2 トークン フィルターは、minGrammaxGram パラメーターに基づいて、特定のサイズの n-gram にトークンを分割します。

電話アナライザーの場合、 minGram3 に設定されます。これは、ユーザーが検索するサブ文字列が最短であるためです。 maxGram20 に設定され、すべての電話番号 (内線番号を含む) が 1 つの n グラムに収まるようにします。

nグラムの残念な副作用は、いくつかの偽陽性が返されるということです. この問題は、後の手順で、n-gram トークン フィルターを含まない検索用に個別のアナライザーを構築することで修正します。

input 出力
[12345] [123, 1234, 12345, 234, 2345, 345]
[3215550199] [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

アナライザー

文字フィルター、トークナイザー、トークン フィルターを配置したら、アナライザーを定義する準備ができました。

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer",
    "tokenizer": "keyword_v2",
    "tokenFilters": [
      "custom_ngram_filter"
    ],
    "charFilters": [
      "phone_char_mapping"
    ]
  }
]

Analyze API から、次の入力を指定すると、カスタム アナライザーからの出力は次のようになります。

input 出力
12345 [123, 1234, 12345, 234, 2345, 345]
(321) 555-0199 [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

出力列のすべてのトークンがインデックスに存在します。 クエリにこれらの用語のいずれかが含まれている場合は、電話番号が返されます。

新しいアナライザーを使用して再構築する

  1. 現在のインデックスを削除します。

     ### Delete the index
     DELETE {{baseUrl}}/indexes/phone-numbers-index?api-version=2024-07-01 HTTP/1.1
         api-key: {{apiKey}}
    
  2. 新しいアナライザーを使用してインデックスを再作成します。 このインデックス スキーマは、電話番号フィールドにカスタム アナライザー定義とカスタム アナライザーの割り当てを追加します。

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2024-07-01  HTTP/1.1
      Content-Type: application/json
      api-key: {{apiKey}}
    
    {
        "name": "phone-numbers-index-2",  
        "fields": [
          {
              "name": "id",
              "type": "Edm.String",
              "key": true,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "sortable": true
          },
          {
              "name": "phone_number",
              "type": "Edm.String",
              "sortable": false,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "analyzer": "phone_analyzer"
          }
        ],
        "analyzers": [
            {
              "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
              "name": "phone_analyzer",
              "tokenizer": "keyword_v2",
              "tokenFilters": [
              "custom_ngram_filter"
            ],
            "charFilters": [
              "phone_char_mapping"
              ]
            }
          ],
          "charFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
              "name": "phone_char_mapping",
              "mappings": [
                "-=>",
                "(=>",
                ")=>",
                "+=>",
                ".=>",
                "\\u0020=>"
              ]
            }
          ],
          "tokenFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
              "name": "custom_ngram_filter",
              "minGram": 3,
              "maxGram": 20
            }
          ]
        }
    

カスタム アナライザーをテストする

インデックスを再作成した後、次の要求を使用してアナライザーをテストします。

POST {{baseUrl}}/indexes/tutorial-first-analyzer/analyze?api-version=2024-07-01  HTTP/1.1
  Content-Type: application/json
  api-key: {{apiKey}} 

  {
    "text": "+1 (321) 555-0199",
    "analyzer": "phone_analyzer"
  }

電話番号に起因するトークンのコレクションが表示されます。

{
    "tokens": [
        {
            "token": "132",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "1321",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "13215",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        ...
        ...
        ...
    ]
}

擬陽性が処理されるようにカスタム アナライザーを修正する

カスタム アナライザーを使用してインデックスに対してサンプル クエリを実行すると、呼び戻しが改善され、一致するすべての電話番号が返されることがわかります。 ただし、n グラム トークン フィルターを使用すると、いくつかの偽陽性も出力されます。 これは、n-gram トークン フィルターではよくある副作用です。

誤検知を防ぐには、クエリ用に別のアナライザーを作成します。 このアナライザーは、前のアナライザーと同じですが、 custom_ngram_filterが省略されている点が異なります。

    {
      "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
      "name": "phone_analyzer_search",
      "tokenizer": "custom_tokenizer_phone",
      "tokenFilters": [],
      "charFilters": [
        "phone_char_mapping"
      ]
    }

インデックス定義で、 indexAnalyzersearchAnalyzerの両方を指定します。

    {
      "name": "phone_number",
      "type": "Edm.String",
      "sortable": false,
      "searchable": true,
      "filterable": false,
      "facetable": false,
      "indexAnalyzer": "phone_analyzer",
      "searchAnalyzer": "phone_analyzer_search"
    }

このように変更すれば準備は完了です。 次の手順を実行します。

  1. インデックスを削除します。

  2. 新しいカスタム アナライザー (phone_analyzer-search) を追加し、そのアナライザーを phone-number フィールドの searchAnalyzer プロパティに割り当てた後、インデックスを再作成します。

  3. モデルを再度読み込みます。

  4. クエリを再テストして、検索が期待どおりに動作することを確認します。 サンプル ファイルを使用している場合、この手順により、phone-number-index-3 という名前の 3 番目のインデックスが作成されます。

別のアプローチ

前のセクションで説明したアナライザーは、検索の柔軟性を最大限に高めるように設計されています。 しかしその代償として、重要ではない可能性のある語句が多数インデックスに格納されています。

次の例は、トークン化の効率が高い代替アナライザーを示していますが、欠点があります。

14255550100 を入力した場合、アナライザーでは電話番号を論理的にチャンクすることはできません。 たとえば、国番号 (1) と市外局番 (425) を分離できません。 この不一致により、ユーザーが検索に国コードを含まない場合、電話番号は返されません。

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer_shingles",
    "tokenizer": "custom_tokenizer_phone",
    "tokenFilters": [
      "custom_shingle_filter"
    ]
  }
],
"tokenizers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.StandardTokenizerV2",
    "name": "custom_tokenizer_phone",
    "maxTokenLength": 4
  }
],
"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.ShingleTokenFilter",
    "name": "custom_shingle_filter",
    "minShingleSize": 2,
    "maxShingleSize": 6,
    "tokenSeparator": ""
  }
]

次の例では、電話番号は一般的にユーザーが検索すると予想されるチャンクに分割されています。

input 出力
(321) 555-0199 [321, 555, 0199, 321555, 5550199, 3215550199]

要件によっては、これが問題へのより効率的なアプローチとなる可能性があります。

重要なポイント

このチュートリアルでは、カスタム アナライザーを構築してテストするプロセスについて説明しました。 インデックスを作成してデータのインデックスを作成し、そのインデックスに対してクエリを実行して、返される検索結果を確認しました。 そこから Analyze API を使用して、実行中の字句解析プロセスを見てきました。

このチュートリアルで定義したアナライザーは、電話番号を検索するための簡単なソリューションとなっていますが、同じプロセスを使用すれば、同様の特性を持つあらゆるシナリオを対象としたカスタム アナライザーを構築することができます。

リソースをクリーンアップする

所有するサブスクリプションを使用している場合は、プロジェクトの終了時に、不要になったリソースを削除することをお勧めします。 リソースを実行したままにすると、お金がかかる場合があります。 リソースを個別に削除するか、リソース グループを削除してリソースのセット全体を削除することができます。

Azure portal で、左側のナビゲーション ウィンドウにある [すべてのリソース] または [リソース グループ] リンクを使って、リソースを検索および管理できます。

次のステップ

カスタム アナライザーを作成する方法を確認したら、豊富な検索エクスペリエンスを構築するために使用できるさまざまなフィルター、トークナイザー、アナライザーをすべて見てみましょう。