Sdílet prostřednictvím


Kurz: Vytvoření vlastního analyzátoru pro telefonní čísla

Ve vyhledávacích řešeních můžou být řetězce se složitými vzory nebo speciálními znaky náročné na práci, protože výchozí analyzátor odstraní nebo nesprávně interpretuje smysluplné části vzoru, což vede k špatnému vyhledávání, když uživatelé nemůžou najít očekávané informace. Telefonní čísla jsou klasickým příkladem řetězců, které se obtížně analyzují. Mají různé formáty a obsahují speciální znaky, které výchozí analyzátor ignoruje.

S telefonními čísly jako předmětem tohoto kurzu se blíže podíváme na problémy se vzorovými daty a ukáže vám, jak tento problém vyřešit pomocí vlastního analyzátoru. Zde uvedený přístup lze použít jako pro telefonní čísla nebo přizpůsobit polím se stejnými vlastnostmi (vzorovanými znaky), jako jsou adresy URL, e-maily, PSČ a kalendářní data.

V tomto kurzu použijete klienta REST a rozhraní REST API služby Azure AI Search k:

  • Pochopení problému
  • Vývoj počátečního vlastního analyzátoru pro zpracování telefonních čísel
  • Testování vlastního analyzátoru
  • Iterace návrhu vlastního analyzátoru za účelem dalšího zlepšení výsledků

Požadavky

Pro účely tohoto kurzu jsou vyžadovány následující služby a nástroje.

Stažení souborů

Zdrojový kód pro tento kurz je soubor custom-analyzer.rest v úložišti Azure-Samples/azure-search-rest-samples na GitHubu.

Zkopírování klíče a adresy URL

Volání REST v tomto kurzu vyžadují koncový bod vyhledávací služby a klíč rozhraní API pro správu. Tyto hodnoty můžete získat z webu Azure Portal.

  1. Přihlaste se k webu Azure Portal, přejděte na stránku Přehled a zkopírujte adresu URL. Příkladem koncového bodu může být https://mydemo.search.windows.net.

  2. V části Klíče nastavení>zkopírujte klíč správce. Klíče správce slouží k přidávání, úpravám a odstraňování objektů. Existují dva zaměnitelné klíče správce. Zkopírujte jeden z nich.

    Snímek obrazovky s adresou URL a klíči rozhraní API na webu Azure Portal

Platný klíč rozhraní API vytváří na základě požadavku vztah důvěryhodnosti mezi aplikací, která požadavek odesílá, a vyhledávací službou, která ji zpracovává.

Vytvoření počátečního indexu

  1. Otevřete nový textový soubor v editoru Visual Studio Code.

  2. Nastavte proměnné na koncový bod vyhledávání a klíč rozhraní API, který jste shromáždili v předchozím kroku.

    @baseUrl = PUT-YOUR-SEARCH-SERVICE-URL-HERE
    @apiKey = PUT-YOUR-ADMIN-API-KEY-HERE
    
  3. Uložte soubor s příponou .rest souboru.

  4. Vložte následující příklad pro vytvoření malého indexu volaný phone-numbers-index se dvěma poli: id a phone_number. Ještě jsme nedefinují analyzátor, takže standard.lucene analyzátor se používá ve výchozím nastavení.

    ### 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. Vyberte Odeslat žádost. Měli byste mít HTTP/1.1 201 Created odpověď a text odpovědi by měl obsahovat reprezentaci JSON schématu indexu.

  6. Načtěte data do indexu pomocí dokumentů, které obsahují různé formáty telefonních čísel. Toto jsou vaše testovací data.

    ### 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. Pojďme vyzkoušet několik dotazů podobných tomu, co uživatel může zadat. Uživatel může hledat (425) 555-0100 v libovolném počtu formátů a stále očekává vrácení výsledků. Začněte hledáním (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}}
    

    Dotaz vrátí tři ze čtyř očekávaných výsledků, ale také dva neočekávané výsledky:

    {
        "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. Zkusme to znovu bez formátování: 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}}
    

    Tento dotaz je ještě horší a vrací pouze jednu ze čtyř správných shod.

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

Pokud tyto výsledky zjistíte matoucí, nejste sami. V další části se podíváme na to, proč tyto výsledky dostáváme.

Kontrola fungování analyzátorů

Abychom porozuměli těmto výsledkům hledání, musíme pochopit, co analyzátor dělá. Odtud můžeme otestovat výchozí analyzátor pomocí rozhraní API pro analýzu a poskytnout základ pro návrh analyzátoru, který lépe vyhovuje našim potřebám.

Analyzátor je součástí fulltextového vyhledávacího webu zodpovědného za zpracování textu v řetězcích dotazů a indexovaných dokumentech. Různé analyzátory manipulují s textem různými způsoby v závislosti na scénáři. V tomto scénáři potřebujeme vytvořit analyzátor přizpůsobený telefonním číslům.

Analyzátory se skládají ze tří součástí:

  • Filtry znaků, které odeberou nebo nahradí jednotlivé znaky ze vstupního textu.
  • Tokenizátor, který rozdělí vstupní text na tokeny, které se stanou klíči v indexu vyhledávání.
  • Filtry tokenů, které manipulují s tokeny vytvořenými tokenizátorem.

V následujícím diagramu vidíte, jak tyto tři komponenty spolupracují na tokenizaci věty:

Diagram procesu analyzátoru pro tokenizaci věty

Tyto tokeny se pak ukládají do invertovaného indexu, který umožňuje rychlé fulltextové vyhledávání. Invertovaný index umožňuje fulltextové vyhledávání namapováním všech jedinečných termínů extrahovaných během lexikální analýzy na dokumenty, ve kterých k nim dochází. Příklad vidíte v dalším diagramu:

Příklad invertovaného indexu

Hledání všech možností spočívá v hledání termínů uložených v invertovaného indexu. Když uživatel vydá dotaz:

  1. Dotaz se analyzuje a termíny dotazu se analyzují.
  2. Invertovaný index se pak zkontroluje na dokumenty s odpovídajícími termíny.
  3. Nakonec načtené dokumenty jsou seřazené podle algoritmu vyhodnocování.

Diagram podobnosti řazení procesů analyzátoru

Pokud se termíny dotazu neshodují s termíny v inverzním indexu, výsledky se nevrátí. Další informace o fungování dotazů najdete v tomto článku o fulltextovém vyhledávání.

Poznámka:

Částečné dotazy termínů jsou důležitou výjimkou tohoto pravidla. Tyto dotazy (dotaz předpony, dotaz se zástupnými čísly, dotaz regex) obcházejí proces lexikální analýzy na rozdíl od běžných dotazů termínů. Částečné termíny jsou před porovnáním s termíny v indexu pouze nižší. Pokud není analyzátor nakonfigurovaný tak, aby podporoval tyto typy dotazů, často dostanete neočekávané výsledky, protože odpovídající termíny v indexu neexistují.

Testování analyzátorů pomocí rozhraní API pro analýzu

Azure AI Search poskytuje rozhraní API pro analýzu, které umožňuje testovat analyzátory, abyste pochopili, jak zpracovávají text.

Rozhraní API pro analýzu se volá pomocí následujícího požadavku:

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"
  }

Rozhraní API vrátí tokeny extrahované z textu pomocí analyzátoru, který jste zadali. Standardní analyzátor Lucene rozdělí telefonní číslo na tři samostatné tokeny:

{
    "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
        }
    ]
}

Naopak telefonní číslo 4255550100 formátované bez interpunkce je tokenizováno do jednoho tokenu.

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

Odpověď:

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

Mějte na paměti, že termíny dotazu i indexované dokumenty procházejí analýzou. Když se vrátíme k výsledkům hledání z předchozího kroku, můžeme začít vidět, proč se tyto výsledky vrátily.

V prvním dotazu se vrátila neočekávaná telefonní čísla, protože jeden z jejich tokenů , odpovídal jednomu z hledaných termínů 555. V druhém dotazu se vrátilo pouze jedno číslo, protože se jednalo o jediný záznam, který měl odpovídající 4255550100token .

Vytvoření vlastního analyzátoru

Teď, když rozumíme výsledkům, které vidíme, vytvoříme vlastní analyzátor, abychom zlepšili logiku tokenizace.

Cílem je poskytovat intuitivní vyhledávání telefonních čísel bez ohledu na formát dotazu nebo indexovaného řetězce. K dosažení tohoto výsledku určíme filtr znaků, tokenizátor a filtr tokenů.

Filtry znaků

Filtry znaků se používají ke zpracování textu před jeho odesláním do tokenizátoru. Mezi běžné použití filtrů znaků patří filtrování elementů HTML nebo nahrazení speciálních znaků.

U telefonních čísel chceme odebrat prázdné znaky a speciální znaky, protože ne všechny formáty telefonních čísel obsahují stejné speciální znaky a mezery.

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

Filtr odebere - + ( ) . ze vstupu mezery.

Vstup Výstup
(321) 555-0199 3215550199
321.555.0199 3215550199

Tokenizátory

Tokenizátory rozdělují text na tokeny a zahodí některé znaky, jako je interpunkce, po cestě. V mnoha případech je cílem tokenizace rozdělit větu na jednotlivá slova.

V tomto scénáři použijeme tokenizátor klíčových slov, keyword_v2protože chceme zachytit telefonní číslo jako jeden termín. Všimněte si, že toto není jediný způsob, jak tento problém vyřešit. Podívejte se na část Alternativní přístupy níže.

Tokenizátory klíčových slov vždy vypíše stejný text, který byl udělen jako jeden termín.

Vstup Výstup
The dog swims. [The dog swims.]
3215550199 [3215550199]

Filtry tokenů

Filtry tokenů vyfiltrují nebo upraví tokeny vygenerované tokenizátorem. Jedním z běžných použití filtru tokenů je malá písmena všech znaků pomocí filtru tokenů malými písmeny. Dalším běžným použitím je filtrování slov, jako theje , andnebo is.

I když pro tento scénář nepotřebujeme použít některý z těchto filtrů, použijeme filtr tokenu nGram, který umožní částečné hledání telefonních čísel.

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

NGramTokenFilterV2

Filtr tokenů nGram_v2 rozdělí tokeny na n-gramy dané velikosti na minGram základě parametrů a maxGram parametrů.

U analyzátoru telefonu jsme nastavili minGram 3 to, že je to nejkratší podřetězení, které očekáváme, že uživatelé budou hledat. maxGram je nastavena tak, aby 20 se všechna telefonní čísla, i s rozšířeními, vešla do jednoho n-gramu.

Nešťastný vedlejší účinek n-gram je, že některé falešně pozitivní výsledky budou vráceny. Tento problém vyřešíme v pozdějším kroku vytvořením samostatného analyzátoru pro vyhledávání, která neobsahuje filtr tokenů n-gram.

Vstup Výstup
[12345] [123, 1234, 12345, 234, 2345, 345]
[3215550199] [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

Analyzátor

S našimi filtry znaků, tokenizátorem a filtry tokenů jsme připraveni definovat náš analyzátor.

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

Výstupy z vlastního analyzátoru jsou znázorněny v následující tabulce z rozhraní API Pro analýzu.

Vstup Výstup
12345 [123, 1234, 12345, 234, 2345, 345]
(321) 555-0199 [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

Všechny tokeny ve výstupním sloupci existují v indexu. Pokud náš dotaz obsahuje některý z těchto termínů, vrátí se telefonní číslo.

Opětovné sestavení pomocí nového analyzátoru

  1. Odstraňte aktuální index:

     ### Delete the index
     DELETE {{baseUrl}}/indexes/phone-numbers-index?api-version=2024-07-01 HTTP/1.1
         api-key: {{apiKey}}
    
  2. Znovu vytvořte index pomocí nového analyzátoru. Toto schéma indexu přidá definici vlastního analyzátoru a přiřazení vlastního analyzátoru do pole telefonního čísla.

    ### 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
            }
          ]
        }
    

Testování vlastního analyzátoru

Po opětovném vytvoření indexu teď můžete analyzátor otestovat pomocí následujícího požadavku:

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"
  }

Teď byste měli vidět kolekci tokenů vyplývajících z telefonního čísla:

{
    "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
        },
        ...
        ...
        ...
    ]
}

Úprava vlastního analyzátoru pro zpracování falešně pozitivních výsledků

Po provedení ukázkových dotazů na index pomocí vlastního analyzátoru zjistíte, že odvolání se zlepšilo a vrátí se všechna odpovídající telefonní čísla. Filtr n-gram tokenů ale také způsobí vrácení některých falešně pozitivních výsledků. Jedná se o běžný vedlejší účinek filtru tokenů n-gram.

Abychom zabránili falešně pozitivním výsledkům, vytvoříme samostatný analyzátor pro dotazování. Tento analyzátor je identický s předchozím, s tím rozdílemcustom_ngram_filter, že vynechá .

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

V definici indexu pak určíme jak an indexAnalyzer , tak i a searchAnalyzer.

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

S touto změnou jste všichni nastaveni. Tady jsou další kroky:

  1. Odstraňte index.

  2. Znovu vytvořte index po přidání nového vlastního analyzátoru (phone_analyzer-search) a přiřazení analyzátoru phone-number searchAnalyzer k vlastnosti pole.

  3. Znovu načtěte data.

  4. Znovu otestujte dotazy a ověřte, že hledání funguje podle očekávání. Pokud používáte ukázkový soubor, tento krok vytvoří třetí index s názvem phone-number-index-3.

Alternativní přístupy

Analyzátor popsaný v předchozí části je navržený tak, aby maximalizoval flexibilitu vyhledávání. To ale dělá za cenu uložení mnoha potenciálně nedůležitých termínů v indexu.

Následující příklad ukazuje alternativní analyzátor, který je efektivnější v tokenizaci, ale má nevýhody.

Při zadání 14255550100nemůže analyzátor logicky zasunou telefonní číslo. Například nemůže oddělit kód země , 1od směrového kódu oblasti, 425. Tato nesrovnalost by vedla k tomu, že se telefonní číslo nevrácelo, pokud uživatel do hledání nezahrnoval kód země.

"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": ""
  }
]

V následujícím příkladu vidíte, že telefonní číslo je rozdělené na bloky dat, které byste normálně očekávali, že uživatel bude hledat.

Vstup Výstup
(321) 555-0199 [321, 555, 0199, 321555, 5550199, 3215550199]

V závislosti na vašich požadavcích to může být efektivnější přístup k problému.

Shrnutí

V tomto kurzu jsme si ukázali proces sestavení a testování vlastního analyzátoru. Vytvořili jste index, indexovaná data a potom jste se na index dotazovali, abyste zjistili, jaké výsledky hledání se vrátily. Odtud jste použili rozhraní API pro analýzu k zobrazení procesu lexikální analýzy v akci.

I když analyzátor definovaný v tomto kurzu nabízí jednoduché řešení pro vyhledávání na telefonních číslech, stejný proces lze použít k vytvoření vlastního analyzátoru pro jakýkoli scénář, který sdílí podobné charakteristiky.

Vyčištění prostředků

Když pracujete ve vlastním předplatném, je vhodné odebrat prostředky, které už nepotřebujete na konci projektu. Prostředky, které necháte spuštěné, vás stojí peníze. Prostředky můžete odstraňovat jednotlivě nebo můžete odstranit skupinu prostředků, a odstranit tak celou sadu prostředků najednou.

Prostředky můžete najít a spravovat na portálu pomocí odkazu Všechny prostředky nebo skupiny prostředků v levém navigačním podokně.

Další kroky

Teď, když už víte, jak vytvořit vlastní analyzátor, se podíváme na všechny různé filtry, tokenizátory a analyzátory, které máte k dispozici k vytvoření bohatého vyhledávání.