Azure Machine Learning - 使用 Azure SDK 进行全文搜索-LMLPHP

环境准备

  • 具有活动订阅的 Azure 帐户。 免费创建帐户

  • Azure AI 搜索服务。 如果还没有,请创建服务。

  • API 密钥和服务终结点:

    登录到 Azure 门户查找你的搜索服务

    在“概述”中,复制 URL 并将其保存到记事本以供后续步骤使用。 示例终结点可能类似于 https://mydemo.search.windows.net

    在“密钥”中,复制并保存管理密钥,以获取创建和删除对象的完整权限。 有两个可互换的主要密钥和辅助密钥。 选择其中一个。

Azure Machine Learning - 使用 Azure SDK 进行全文搜索-LMLPHP

创建、加载并查询索引

选择下一步要使用的编程语言。 Azure.Search.Documents 客户库在面向 .NET、Python、Java 和 JavaScript 的 Azure SDK 中均可使用。
以Python为例:

使用 azure-search-documents 库构建 Jupyter Notebook,用于创建、加载和查询索引。 或者,可以下载并运行已完成的 Jupyter Python 笔记本,或按照这些步骤创建自己的笔记本。

设置你的环境

我们使用以下工具创建了本快速入门。

连接到 Azure AI 搜索

在此任务中,创建笔记本、加载库并设置客户端。

  1. 在 Visual Studio Code 中创建新的 Python3 笔记本:

    1. 按 F1 并搜索“Python 选择解释器”,然后选择 Python 3.7 版本或更高版本。
    2. 再次按 F1 并搜索“创建:新的 Jupyter Notebook”。 应在编辑器中打开一个空的无标题 .ipynb 文件,为第一个条目做好准备。
  2. 在第一个单元格中,从用于 Python 的 Azure SDK 加载库,包括 azure-search-documents。

    %pip install azure-search-documents --pre
    %pip show azure-search-documents
    
    import os
    from azure.core.credentials import AzureKeyCredential
    from azure.search.documents.indexes import SearchIndexClient 
    from azure.search.documents import SearchClient
    from azure.search.documents.indexes.models import (
        ComplexField,
        CorsOptions,
        SearchIndex,
        ScoringProfile,
        SearchFieldDataType,
        SimpleField,
        SearchableField
    )
    
  3. 添加第二个单元格并粘贴连接信息。 此单元格还设置了将用于执行特定操作的客户端:用于创建索引的 SearchIndexClient,以及用于查询索引的 SearchClient。

    由于代码为你生成了 URI,因此只需在服务名称属性中指定搜索服务名称。

    service_name = "<YOUR-SEARCH-SERVICE-NAME>"
    admin_key = "<YOUR-SEARCH-SERVICE-ADMIN-API-KEY>"
    
    index_name = "hotels-quickstart"
    
    # Create an SDK client
    endpoint = "https://{}.search.windows.net/".format(service_name)
    admin_client = SearchIndexClient(endpoint=endpoint,
                          index_name=index_name,
                          credential=AzureKeyCredential(admin_key))
    
    search_client = SearchClient(endpoint=endpoint,
                          index_name=index_name,
                          credential=AzureKeyCredential(admin_key))
    
  4. 在第三个单元格中,运行 delete_index 操作以清除所有现有的 hotels-quickstart 索引服务。 通过删除索引,可以创建另一个同名的 hotels-quickstart 索引。

    try:
        result = admin_client.delete_index(index_name)
        print ('Index', index_name, 'Deleted')
    except Exception as ex:
        print (ex)
    
  5. 运行每个步骤。

创建索引

必需的索引元素包括名称、字段集合和唯一标识每个搜索文档的文档键。 字段集合定义逻辑搜索文档的结构,用于加载数据和返回结果。

在字段集合中,每个字段都具有一个名称、类型和确定字段用法的属性(例如,该字段在搜索结果是否可全文搜索、可筛选或可检索)。 在索引中,必须将一个 Edm.String 类型的字段指定为文档标识的键。

此索引名为“hotels-quickstart”,使用下面所示的字段定义。 它是其他演练中使用的一个更大 Hotels 索引的子集。 为简明起见,本快速入门已对其进行修整。

  1. 在下一个单元格中,将以下示例粘贴到某个单元格以提供架构。

    # Specify the index schema
    name = index_name
    fields = [
            SimpleField(name="HotelId", type=SearchFieldDataType.String, key=True),
            SearchableField(name="HotelName", type=SearchFieldDataType.String, sortable=True),
            SearchableField(name="Description", type=SearchFieldDataType.String, analyzer_name="en.lucene"),
            SearchableField(name="Description_fr", type=SearchFieldDataType.String, analyzer_name="fr.lucene"),
            SearchableField(name="Category", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
    
            SearchableField(name="Tags", collection=True, type=SearchFieldDataType.String, facetable=True, filterable=True),
    
            SimpleField(name="ParkingIncluded", type=SearchFieldDataType.Boolean, facetable=True, filterable=True, sortable=True),
            SimpleField(name="LastRenovationDate", type=SearchFieldDataType.DateTimeOffset, facetable=True, filterable=True, sortable=True),
            SimpleField(name="Rating", type=SearchFieldDataType.Double, facetable=True, filterable=True, sortable=True),
    
            ComplexField(name="Address", fields=[
                SearchableField(name="StreetAddress", type=SearchFieldDataType.String),
                SearchableField(name="City", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
                SearchableField(name="StateProvince", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
                SearchableField(name="PostalCode", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
                SearchableField(name="Country", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
            ])
        ]
    cors_options = CorsOptions(allowed_origins=["*"], max_age_in_seconds=60)
    scoring_profiles = []
    suggester = [{'name': 'sg', 'source_fields': ['Tags', 'Address/City', 'Address/Country']}]
    
  2. 在另一个单元格中构建请求。 此 create_index 请求以搜索服务的索引集合为目标,并基于在上一单元格中提供的索引架构创建 SearchIndex。

    index = SearchIndex(
        name=name,
        fields=fields,
        scoring_profiles=scoring_profiles,
        suggesters = suggester,
        cors_options=cors_options)
    
    try:
        result = admin_client.create_index(index)
        print ('Index', result.name, 'created')
    except Exception as ex:
        print (ex)
    
  3. 运行每个步骤。

加载文档

若要加载文档,请使用操作类型(上传、合并上传等)的索引操作来创建文档集合。 文档源自 GitHub 上的 HotelsData

  1. 在新单元格中,提供符合索引架构的四个文档。 指定每个文档的上传操作。

    documents = [
        {
        "@search.action": "upload",
        "HotelId": "1",
        "HotelName": "Secret Point Motel",
        "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
        "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
        "Category": "Boutique",
        "Tags": [ "pool", "air conditioning", "concierge" ],
        "ParkingIncluded": "false",
        "LastRenovationDate": "1970-01-18T00:00:00Z",
        "Rating": 3.60,
        "Address": {
            "StreetAddress": "677 5th Ave",
            "City": "New York",
            "StateProvince": "NY",
            "PostalCode": "10022",
            "Country": "USA"
            }
        },
        {
        "@search.action": "upload",
        "HotelId": "2",
        "HotelName": "Twin Dome Motel",
        "Description": "The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
        "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
        "Category": "Boutique",
        "Tags": [ "pool", "free wifi", "concierge" ],
        "ParkingIncluded": "false",
        "LastRenovationDate": "1979-02-18T00:00:00Z",
        "Rating": 3.60,
        "Address": {
            "StreetAddress": "140 University Town Center Dr",
            "City": "Sarasota",
            "StateProvince": "FL",
            "PostalCode": "34243",
            "Country": "USA"
            }
        },
        {
        "@search.action": "upload",
        "HotelId": "3",
        "HotelName": "Triple Landscape Hotel",
        "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
        "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
        "Category": "Resort and Spa",
        "Tags": [ "air conditioning", "bar", "continental breakfast" ],
        "ParkingIncluded": "true",
        "LastRenovationDate": "2015-09-20T00:00:00Z",
        "Rating": 4.80,
        "Address": {
            "StreetAddress": "3393 Peachtree Rd",
            "City": "Atlanta",
            "StateProvince": "GA",
            "PostalCode": "30326",
            "Country": "USA"
            }
        },
        {
        "@search.action": "upload",
        "HotelId": "4",
        "HotelName": "Sublime Cliff Hotel",
        "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.",
        "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
        "Category": "Boutique",
        "Tags": [ "concierge", "view", "24-hour front desk service" ],
        "ParkingIncluded": "true",
        "LastRenovationDate": "1960-02-06T00:00:00Z",
        "Rating": 4.60,
        "Address": {
            "StreetAddress": "7400 San Pedro Ave",
            "City": "San Antonio",
            "StateProvince": "TX",
            "PostalCode": "78216",
            "Country": "USA"
            }
        }
    ]
    
  2. 在另一个单元格中构建请求。 此 upload_documents 请求以 hotels-quickstart 索引的文档集合为目标,将在上一步骤中提供的文档推送到 Azure AI 搜索索引。

    try:
        result = search_client.upload_documents(documents=documents)
        print("Upload of new document succeeded: {}".format(result[0].succeeded))
    except Exception as ex:
        print (ex.message)
    
  3. 运行每个步骤,将文档推送到搜索服务中的索引。

搜索索引

此步骤说明如何使用 search.client 类的 search 方法来查询索引。

  1. 下面的步骤执行空搜索 (search=*),返回任意文档的未排名列表(搜索分数 = 1.0)。 由于没有条件,因此所有文档都包含在结果中。 此查询仅输出每个文档中的两个字段。 它还会添加 include_total_count=True 以获取结果中所有文档 (4) 的计数。

    results = search_client.search(search_text="*", include_total_count=True)
    
    print ('Total Documents Matching Query:', results.get_count())
    for result in results:
        print("{}: {}".format(result["HotelId"], result["HotelName"]))
    
  2. 下一个查询将整个术语添加到搜索表达式 (“wifi”)。 此查询指定结果仅包含 select 语句中的那些字段。 限制返回的字段可最大程度地减少通过网络发回的数据量,并降低搜索延迟。

    results = search_client.search(search_text="wifi", include_total_count=True, select='HotelId,HotelName,Tags')
    
    print ('Total Documents Matching Query:', results.get_count())
    for result in results:
        print("{}: {}: {}".format(result["HotelId"], result["HotelName"], result["Tags"]))
    
  3. 接下来,应用筛选表达式,仅返回评分高于 4 的酒店(按降序排列)。

    results = search_client.search(search_text="hotels", select='HotelId,HotelName,Rating', filter='Rating gt 4', order_by='Rating desc')
    
    for result in results:
        print("{}: {} - {} rating".format(result["HotelId"], result["HotelName"], result["Rating"]))
    
  4. 添加 search_fields(一个数组),将查询匹配的范围限制为单一字段。

    results = search_client.search(search_text="sublime", search_fields=['HotelName'], select='HotelId,HotelName')
    
    for result in results:
        print("{}: {}".format(result["HotelId"], result["HotelName"]))
    
  5. Facet 是可用于组成 Facet 导航结构的标签。 此查询返回类别的 Facet 和计数。

    results = search_client.search(search_text="*", facets=["Category"])
    
    facets = results.get_facets()
    
    for facet in facets["Category"]:
        print("    {}".format(facet))
    
  6. 在此示例中,根据文档的键查找特定的文档。 当用户选择搜索结果中的文档时,你通常需要返回文档。

    result = search_client.get_document(key="3")
    
    print("Details for hotel '3' are:")
    print("Name: {}".format(result["HotelName"]))
    print("Rating: {}".format(result["Rating"]))
    print("Category: {}".format(result["Category"]))
    
  7. 在最后一个示例中,我们将使用自动完成函数。 “自动完成”通常在搜索框中使用,以便在用户在搜索框中键入时提供可能的匹配项。

    创建索引时,还会创建名为“sg”的建议器作为请求的一部分。 建议器定义指定哪些字段可用于查找建议器请求的潜在匹配。 在此示例中,这些字段是“标签”、“地址/城市”、“地址/国家/地区”。 若要模拟自动完成,请输入字母“sa”作为字符串的一部分。 SearchClient 的自动完成方法会发回可能的术语匹配。

    search_suggestion = 'sa'
    results = search_client.autocomplete(search_text=search_suggestion, suggester_name="sg", mode='twoTerms')
    
    print("Autocomplete for:", search_suggestion)
    for result in results:
        print (result['text'])
    
12-03 10:43