你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

知识存储中的形状和投影的详细示例

本文提供了一个详细示例,通过详细介绍在知识存储中充分表达富技能组的输出所需的整形和投影步骤,对高级概念基于语法的文章进行了补充。

如果你的应用程序要求需要多项技能和投影,则此示例可让你更好地了解形状和投影的相交方式。

下载示例定义

本示例使用 Postman Desktop 应用程序搜索 REST API

克隆或下载 GitHub 上的 azure-search-postman-samples并导入投影集合,以亲自逐步执行此示例。

设置示例数据

示例文档并未特别包含在“项目”集合中,但 azure-search-sample-data repo 中的 AI 扩充演示数据文件包含文本和图像,并与本示例中所述项目结合使用。

在 Azure 存储中创建 blob 容器,并上传所有 14 个项目。

在 Azure 存储中,复制连接字符串,以便可以在 Postman 集合中指定它。

示例技能组

要了解形状与投影之间的依赖关系,请查看以下创建扩充内容的技能组。 该技能组处理原始图像和文本,生成将在整形和投影中引用的输出。

请密切注意技能输出 (targetNames)。 将在投影和形状中(通过整形器技能)引用写入扩充文档树的输出。

{
    "name": "projections-demo-ss",
    "description": "Skillset that enriches blob data found in "merged_content". The enrichment granularity is a document.",
    "skills": [
        {
            "@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
            "name": "#1",
            "description": null,
            "context": "/document/merged_content",
            "categories": [
                "Person",
                "Quantity",
                "Organization",
                "URL",
                "Email",
                "Location",
                "DateTime"
            ],
            "defaultLanguageCode": "en",
            "minimumPrecision": null,
            "inputs": [
                {
                    "name": "text",
                    "source": "/document/merged_content"
                },
                {
                    "name": "languageCode",
                    "source": "/document/language"
                }
            ],
            "outputs": [
                {
                    "name": "persons",
                    "targetName": "people"
                },
                {
                    "name": "organizations",
                    "targetName": "organizations"
                },
                {
                    "name": "locations",
                    "targetName": "locations"
                }
            ]
        },
        {
            "@odata.type": "#Microsoft.Skills.Text.KeyPhraseExtractionSkill",
            "name": "#2",
            "description": null,
            "context": "/document/merged_content",
            "defaultLanguageCode": "en",
            "maxKeyPhraseCount": null,
            "inputs": [
                {
                    "name": "text",
                    "source": "/document/merged_content"
                },
                {
                    "name": "languageCode",
                    "source": "/document/language"
                }
            ],
            "outputs": [
                {
                    "name": "keyPhrases",
                    "targetName": "keyphrases"
                }
            ]
        },
        {
            "@odata.type": "#Microsoft.Skills.Text.LanguageDetectionSkill",
            "name": "#3",
            "description": null,
            "context": "/document",
            "inputs": [
                {
                    "name": "text",
                    "source": "/document/merged_content"
                }
            ],
            "outputs": [
                {
                    "name": "languageCode",
                    "targetName": "language"
                }
            ]
        },
        {
            "@odata.type": "#Microsoft.Skills.Text.MergeSkill",
            "name": "#4",
            "description": null,
            "context": "/document",
            "insertPreTag": " ",
            "insertPostTag": " ",
            "inputs": [
                {
                    "name": "text",
                    "source": "/document/content"
                },
                {
                    "name": "itemsToInsert",
                    "source": "/document/normalized_images/*/text"
                },
                {
                    "name": "offsets",
                    "source": "/document/normalized_images/*/contentOffset"
                }
            ],
            "outputs": [
                {
                    "name": "mergedText",
                    "targetName": "merged_content"
                }
            ]
        },
        {
            "@odata.type": "#Microsoft.Skills.Vision.OcrSkill",
            "name": "#5",
            "description": null,
            "context": "/document/normalized_images/*",
            "textExtractionAlgorithm": "printed",
            "lineEnding": "Space",
            "defaultLanguageCode": "en",
            "detectOrientation": true,
            "inputs": [
                {
                    "name": "image",
                    "source": "/document/normalized_images/*"
                }
            ],
            "outputs": [
                {
                    "name": "text",
                    "targetName": "text"
                },
                {
                    "name": "layoutText",
                    "targetName": "layoutText"
                }
            ]
        }
    ],
    "cognitiveServices": {
        "@odata.type": "#Microsoft.Azure.Search.CognitiveServicesByKey",
        "description": "A Cognitive Services resource in the same region as Search.",
        "key": "<COGNITIVE SERVICES All-in-ONE KEY>"
    },
    "knowledgeStore": null
}

示例整形器技能

整形器技能是一种用于处理现有的扩充内容而非创建新扩充内容的实用工具。 通过将整形器添加到技能组,可以创建可投影到表或 Blob 存储中的自定义形状。 如果没有自定义形状,投影仅限于引用单个节点(每个输出对应一个投影),这不适用于表。 创建自定义形状可将各个元素聚合成新的逻辑整体,而该整体又可以投影为单个表,或者在一系列表之间进行切片和分布。

在此示例中,自定义形状合并了 Blob 元数据并标识了实体和关键短语。 自定义形状称为 projectionShape,是 /document 下的子级。

整形的目的之一是确保所有扩充节点以适当格式的 JSON 表示,只有这样才能投影到知识存储。 当扩充树包含不是正确格式的 JSON 的节点时(例如,当扩充是字符串等基元的父级时),更是如此。

请注意最后两个节点 KeyPhrasesEntities。 这些节点通过 sourceContext 包装成有效的 JSON 对象。 之所以需要这样做,是因为 keyphrasesentities 是针对基元的扩充,只有在转换为有效的 JSON 后才能投影。

{
    "@odata.type": "#Microsoft.Skills.Util.ShaperSkill",
    "name": "ShaperForTables",
    "description": null,
    "context": "/document",
    "inputs": [
        {
            "name": "metadata_storage_content_type",
            "source": "/document/metadata_storage_content_type",
            "sourceContext": null,
            "inputs": []
        },
        {
            "name": "metadata_storage_name",
            "source": "/document/metadata_storage_name",
            "sourceContext": null,
            "inputs": []
        },
        {
            "name": "metadata_storage_path",
            "source": "/document/metadata_storage_path",
            "sourceContext": null,
            "inputs": []
        },
        {
            "name": "metadata_content_type",
            "source": "/document/metadata_content_type",
            "sourceContext": null,
            "inputs": []
        },
        {
            "name": "keyPhrases",
            "source": null,
            "sourceContext": "/document/merged_content/keyphrases/*",
            "inputs": [
                {
                    "name": "KeyPhrases",
                    "source": "/document/merged_content/keyphrases/*"
                }

            ]
        },
        {
            "name": "Entities",
            "source": null,
            "sourceContext": "/document/merged_content/entities/*",
            "inputs": [
                {
                    "name": "Entities",
                    "source": "/document/merged_content/entities/*/name"
                }

            ]
        }
    ],
    "outputs": [
        {
            "name": "output",
            "targetName": "projectionShape"
        }
    ]
}

将整形器添加到技能组

本文开头介绍的示例技能组不包含整形器技能,但整形器技能属于一个技能组,通常放在末尾。

在技能组中,整形器技能可能如下所示:

    "name": "projections-demo-ss",
    "skills": [
        {
            <Shaper skill goes here>
            }
        ],
    "cognitiveServices":  "A key goes here",
    "knowledgeStore": []
}  

投影到表

借鉴上面的示例,可以在表投影中引用已知数量的扩充和数据形状。 在下面的表投影中,通过设置 tableNamesourcegeneratedKeyName 属性定义了三个表。

所有这三个表都将通过生成的键和共享父级 /document/projectionShape 相关联。

"knowledgeStore" : {
    "storageConnectionString": "DefaultEndpointsProtocol=https;AccountName=<Acct Name>;AccountKey=<Acct Key>;",
    "projections": [
        {
            "tables": [
                {
                    "tableName": "tblDocument",
                    "generatedKeyName": "Documentid",
                    "source": "/document/projectionShape"
                },
                {
                    "tableName": "tblKeyPhrases",
                    "generatedKeyName": "KeyPhraseid",
                    "source": "/document/projectionShape/keyPhrases/*"
                },
                {
                    "tableName": "tblEntities",
                    "generatedKeyName": "Entityid",
                    "source": "/document/projectionShape/Entities/*"
                }
            ],
            "objects": [],
            "files": []
        }
    ]
}

测试你的工作

可执行以下步骤来检查投影定义:

  1. 将知识存储的 storageConnectionString 属性设置为有效的 V2 常规用途存储帐户连接字符串。

  2. 通过发出 PUT 请求来更新技能集。

  3. 更新技能集后,运行索引器。

现已获得一个包含三个表的正常运行的投影。 将这些表导入 Power BI 后,Power BI 应会自动发现关系。

在继续学习下一个示例之前,让我们回顾一下表投影的各个方面,以理解进行数据切片和关联的机制。

将表切分为多个子表

切片是将合并的整个形状细分为组成部分的技术。 结果包括独立但相关的、可单独使用的表。

在此示例中,projectionShape 是合并的形状(或扩充节点)。 在投影定义中,projectionShape 切片为附加的表,这样你就可以提取形状的组成部分:keyPhrasesEntities。 此技术在 Power BI 中非常有用,因为多个实体和关键短语与每个文档相关联,如果可以看到分类数据形式的实体和关键短语,则会获得更多的见解。

切片在父表与子表之间隐式生成关系,使用父表中的 generatedKeyName 在子表中创建同名的列。

命名关系

generatedKeyNamereferenceKeyName 属性用于关联表之间的数据,甚至可以关联投影类型之间的数据。 子表中的每一行都有一个指向回父级的属性。 子级中的列或属性的名称是来自父级的 referenceKeyName。 如果未提供 referenceKeyName,服务默认使用来自父级中 generatedKeyName 的名称。

Power BI 依赖于这些生成的键来发现表中的关系。 如果需要以不同的方式命名子表中的列,请在父表中设置 referenceKeyName 属性。 例如,将 generatedKeyName 设置为 tblDocument 表的 ID,并将 referenceKeyName 设置为 DocumentID。 这会导致包含文档 ID 的 tblEntities 和 tblKeyPhrases 表中的列被命名为 DocumentID。

投影 Blob 文档

对象投影是可以从任何节点寻源的扩充树的 JSON 表示形式。 与表投影相比,对象投影的定义更简单,并且在投影整个文档时使用。 对象投影限制为容器中的单个投影,并且无法切片。

要定义对象投影,请在投影属性中使用 objects 数组。

源是扩充树中充当投影根的节点的路径。 尽管不是必需,但节点路径通常是整形器技术的输出。 这是因为大多数技能不会自行输出有效的 JSON 对象,这意味着需要某种形式的整形。 在许多情况下,可以使用用于创建表投影的同一个“整形程序”技能来生成对象投影。 另外,还可将源设置为具有内联整形的节点,以提供结构。

目标始终是一个 Blob 容器。

以下示例将各个酒店文档投影到名为 hotels 的容器(每个 Blob 一个酒店文档)中。

"knowledgeStore": {
  "storageConnectionString": "an Azure storage connection string",
  "projections" : [
    {
      "tables": [ ]
    },
    {
      "objects": [
        {
        "storageContainer": "hotels",
        "source": "/document/objectprojection",
        }
      ]
    },
    {
        "files": [ ]
    }
  ]
}

源是整形程序技能“objectprojection”的输出。 每个 blob 的每个字段输入都有一个 JSON 表示法。

    {
      "@odata.type": "#Microsoft.Skills.Util.ShaperSkill",
      "name": "#3",
      "description": null,
      "context": "/document",
      "inputs": [
        {
          "name": "HotelId",
          "source": "/document/HotelId"
        },
        {
          "name": "HotelName",
          "source": "/document/HotelName"
        },
        {
          "name": "Category",
          "source": "/document/Category"
        },
        {
          "name": "keyPhrases",
          "source": "/document/HotelId/keyphrases/*"
        },
      ],
      "outputs": [
        {
          "name": "output",
          "targetName": "objectprojection"
        }
      ]
    }

投影图像文件

文件投影始终是规范化的二进制图像,其中规范化是指在执行技能组时可能使用的大小调整和旋转。 类似于对象投影,文件投影创建为 Azure 存储中的 Blob,并包含图像。

要定义文件投影,请在投影属性中使用 files 数组。

源始终为 /document/normalized_images/*。 文件投影仅作用于 normalized_images 集合。 索引器和技能组都不会通过原始的非规范化图像。

目标始终是一个 Blob 容器,其中包含文档 ID 的 base64 编码值的文件夹前缀。 文件投影不能与对象投影共享同一个容器,需要将其投影到不同的容器。

以下示例将从扩充文档的文档节点提取的所有规范化图像投影到名为 myImages 的容器中。

"knowledgeStore" : {
    "storageConnectionString": "DefaultEndpointsProtocol=https;AccountName=<Acct Name>;AccountKey=<Acct Key>;",
    "projections": [
        {
            "tables": [ ],
            "objects": [ ],
            "files": [
                {
                    "storageContainer": "myImages",
                    "source": "/document/normalized_images/*"
                }
            ]
        }
    ]
}

投影到多个类型

更复杂的方案可能要求跨投影类型投影内容。 例如,将关键短语和实体投影到表,将文本的 OCR 结果和布局文本保存为对象,然后将图像投影为文件。

多个投影类型的步骤:

  1. 为每个文档创建包含一行的表。
  2. 创建与文档表相关的表,并将每个关键短语标识为此表中的一行。
  3. 创建与文档表相关的表,并将每个实体标识为此表中的一行。
  4. 创建一个对象投影,其中包含每个图像的布局文本。
  5. 创建一个文件投影,用于投影提取的每个图像。
  6. 创建一个交叉引用表,其中包含对文档表、带有布局文本的对象投影以及文件投影的引用。

为交叉投影整形数据

若要获取这些投影所需的形状,请首先添加一个新的整形器技能来创建名为 crossProjection 的形状对象。

{
    "@odata.type": "#Microsoft.Skills.Util.ShaperSkill",
    "name": "ShaperForCrossProjection",
    "description": null,
    "context": "/document",
    "inputs": [
        {
            "name": "metadata_storage_name",
            "source": "/document/metadata_storage_name",
            "sourceContext": null,
            "inputs": []
        },
        {
            "name": "keyPhrases",
            "source": null,
            "sourceContext": "/document/merged_content/keyphrases/*",
            "inputs": [
                {
                    "name": "KeyPhrases",
                    "source": "/document/merged_content/keyphrases/*"
                }

            ]
        },
        {
            "name": "entities",
            "source": null,
            "sourceContext": "/document/merged_content/entities/*",
            "inputs": [
                {
                    "name": "Entities",
                    "source": "/document/merged_content/entities/*/name"
                }

            ]
        },
        {
            "name": "images",
            "source": null,
            "sourceContext": "/document/normalized_images/*",
            "inputs": [
                {
                    "name": "image",
                    "source": "/document/normalized_images/*"
                },
                {
                    "name": "layoutText",
                    "source": "/document/normalized_images/*/layoutText"
                },
                {
                    "name": "ocrText",
                    "source": "/document/normalized_images/*/text"
                }
                ]
        }
 
    ],
    "outputs": [
        {
            "name": "output",
            "targetName": "crossProjection"
        }
    ]
}

定义表、对象和文件投影

在合并的 crossProjection 对象中,将对象切片成多个表,将 OCR 输出捕获为 Blob,然后将图像另存为文件(也在 Blob 存储中)。

"knowledgeStore" : {
    "storageConnectionString": "DefaultEndpointsProtocol=https;AccountName=<Acct Name>;AccountKey=<Acct Key>;",
    "projections": [
            {
            "tables": [
                {
                    "tableName": "crossDocument",
                    "generatedKeyName": "Id",
                    "source": "/document/crossProjection"
                },
                {
                    "tableName": "crossEntities",
                    "generatedKeyName": "EntityId",
                    "source": "/document/crossProjection/entities/*"
                },
                {
                    "tableName": "crossKeyPhrases",
                    "generatedKeyName": "KeyPhraseId",
                    "source": "/document/crossProjection/keyPhrases/*"
                },
                {
                    "tableName": "crossReference",
                    "generatedKeyName": "CrossId",
                    "source": "/document/crossProjection/images/*"
                }
                    
            ],
            "objects": [
                {
                    "storageContainer": "crossobject",
                    "generatedKeyName": "crosslayout",
                    "source": null,
                    "sourceContext": "/document/crossProjection/images/*/layoutText",
                    "inputs": [
                        {
                            "name": "OcrLayoutText",
                            "source": "/document/crossProjection/images/*/layoutText"
                        }
                    ]
                }
            ],
            "files": [
                {
                    "storageContainer": "crossimages",
                    "generatedKeyName": "crossimages",
                    "source": "/document/crossProjection/images/*/image"
                }
            ]
        }
    ]
}

对象投影需要为每个投影指定一个容器名称。 对象投影和文件投影不能共享容器。

表投影、对象投影与文件投影之间的关系

此示例还重点演示了投影的另一项功能。 通过在同一投影对象内定义多种类型的投影,可以在不同类型(表、对象、文件)之内和之间表达一种关系。 这样你就可以从文档的表行开始,在对象投影中查找该文档中的图像的所有 OCR 文本。

如果你不希望关联数据,请在不同的投影组中定义投影。 例如,以下代码片段会导致关联表,但不会在表与对象(OCR 文本)投影之间建立关系。

根据不同的需求投影不同形状中的相同数据时,投影组非常有用。 例如,Power BI 仪表板的投影组,以及用于捕获数据(这些数据用于训练自定义技能中包装的机器学习模型)的另一个投影组。

生成不同类型的投影时,首先会生成文件和对象投影,并将路径添加到表中。

"knowledgeStore" : {
    "storageConnectionString": "DefaultEndpointsProtocol=https;AccountName=<Acct Name>;AccountKey=<Acct Key>;",
    "projections": [
        {
            "tables": [
                {
                    "tableName": "unrelatedDocument",
                    "generatedKeyName": "Documentid",
                    "source": "/document/projectionShape"
                },
                {
                    "tableName": "unrelatedKeyPhrases",
                    "generatedKeyName": "KeyPhraseid",
                    "source": "/document/projectionShape/keyPhrases"
                }
            ],
            "objects": [
                
            ],
            "files": []
        }, 
        {
            "tables": [],
            "objects": [
                {
                    "storageContainer": "unrelatedocrtext",
                    "source": null,
                    "sourceContext": "/document/normalized_images/*/text",
                    "inputs": [
                        {
                            "name": "ocrText",
                            "source": "/document/normalized_images/*/text"
                        }
                    ]
                },
                {
                    "storageContainer": "unrelatedocrlayout",
                    "source": null,
                    "sourceContext": "/document/normalized_images/*/layoutText",
                    "inputs": [
                        {
                            "name": "ocrLayoutText",
                            "source": "/document/normalized_images/*/layoutText"
                        }
                    ]
                }
            ],
            "files": []
        }
    ]
}

后续步骤

本文中的示例演示了有关如何创建投影的常用模式。 充分理解概念后,接下来可以运用更丰富的知识来为特定的方案生成投影。