Exemplo detalhado de formas e projeções em um repositório de conhecimento

Este artigo fornece um exemplo detalhado que complementa os conceitos de alto nível e os artigos baseados em sintaxe, orientando você pelas etapas de modelagem e projeção necessárias para expressar totalmente a saída de um conjunto de habilidades enriquecido em um repositório de conhecimento.

Se os requisitos do seu aplicativo chamarem várias habilidades e projeções, este exemplo pode lhe dar uma melhor ideia de como as formas e as projeções se interseccionam.

Baixar definições de exemplo

Este exemplo usa o aplicativo de área de trabalho do postmaster e as APIs REST de pesquisa.

Clone ou baixe o azure-search-postmaster-samples no GitHub e importe a Coleção de projeções para percorrer este exemplo por conta própria.

Configurar dados de exemplo

Os documentos de exemplo não estão especificamente incluídos na coleção de projeções, mas os arquivos de dados de demonstração de enriquecimento de IA do repositório de dados azure-search-sample-data contêm texto e imagens e funcionarão com as projeções descritas neste exemplo.

Crie um contêiner de blobs no Armazenamento do Microsoft Azure e carregue todos os 14 itens.

No Armazenamento do Microsoft Azure, copie uma cadeia de conexão para que você possa especificá-la na coleção do postmaster.

Conjunto de habilidades de exemplo

Para entender a dependência entre formas e projeções, examine o seguinte conjunto de habilidades que cria conteúdo enriquecido. Esse conjunto de habilidades processa imagens e texto brutos, produzindo saídas que serão referenciadas em formas e projeções.

Preste muita atenção às saídas das habilidades (targetNames). As saídas gravadas na árvore do documento enriquecido são referenciadas em projeções e formas (por meio de habilidades de Shaper).

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

Habilidade do Shaper de exemplo

Uma habilidade do Shaper é um utilitário para trabalhar com conteúdo enriquecido já existente em vez de criar um novo. Adicionar um Shaper a um conjunto de habilidades permite criar uma forma personalizada que você pode projetar no armazenamento de tabelas ou blob. Sem uma forma personalizada, as projeções são limitadas a fazer referência a um único nó (uma projeção por saída), o que não é adequado para tabelas. A criação de uma forma personalizada agrega vários elementos em um novo lógico inteiro que pode ser projetado como uma única tabela ou segmentado e distribuído por uma coleção de tabelas.

Neste exemplo, a forma personalizada combina metadados de blobs, entidades identificadas e frases-chave. A forma personalizada é chamada de projectionShape e é pai de /document.

Uma finalidade do shaping é garantir que todos os nós de enriquecimento sejam expressos em JSON bem formado, o que é necessário para projetar no repositório de conhecimento. Isso é especialmente verdadeiro quando uma árvore de enriquecimento contém nós que não são JSON bem formados (por exemplo, quando um enriquecimento é pai de um primitivo como uma cadeia de caracteres).

Observe os dois últimos nós KeyPhrases e Entities. Eles são encapsulados em um objeto JSON válido com o sourceContext. Isso é necessário, pois keyphrases e entities são enriquecimentos em primitivos e precisam ser convertidos em JSON válido antes que possam ser projetados.

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

Adicionar Shapers a um conjunto de habilidades

O conjunto de habilidades de exemplo introduzido no início deste artigo não incluiu a habilidade do Shaper, mas ela pertence a um conjunto de habilidades e geralmente são colocadas no final.

Em um conjunto de habilidades, uma habilidade do Shaper pode ser parecida com esta:

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

Projeção para tabelas

Ao desenhar os exemplos acima, há uma quantidade conhecida de aprimoramentos e formas de dados que podem ser referenciadas em projeções de tabela. Na projeção tabelas abaixo, três tabelas são definidas ao se configurar as propriedades tableName, source e generatedKeyName.

Todas as três tabelas serão relacionadas por meio das chaves geradas e pelo pai compartilhado /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": []
        }
    ]
}

Teste o trabalho

Você pode verificar as definições de projeção seguindo estas etapas:

  1. Defina a propriedade storageConnectionString do repositório de conhecimento como uma cadeia de conexão de conta de armazenamento de uso geral v2 válida.

  2. Atualize o conjunto de habilidades emitindo a solicitação PUT.

  3. Depois de atualizar o conjunto de habilidades, execute o indexador.

Agora você tem uma projeção de trabalho com três tabelas. A importação dessas tabelas para Power BI deve resultar na descoberta automática das relações pelo Power BI.

Antes de passar para o próximo exemplo, vamos rever os aspectos da projeção da tabela para entender a mecânica de divisão e relacionamento dos dados.

Divisão de uma tabela em várias tabelas filho

A divisão é uma técnica que subdivide uma forma consolidada inteira em partes constituintes. O resultado consiste em tabelas separadas, mas relacionadas com as quais você pode trabalhar individualmente.

No exemplo, projectionShape é a forma consolidada (ou nó de enriquecimento). Na definição de projeção, projectionShape é segmentado em tabelas adicionais, o que permite que você retire partes da forma keyPhrases e Entities. No Power BI, isso é útil, pois várias entidades e keyPhrases são associadas a cada documento, e você obterá mais informações se puder ver entidades e keyPhrases como dados categorizados.

A divisão gera implicitamente uma relação entre as tabelas pai e filho, usando o generatedKeyName na tabela pai para criar uma coluna com o mesmo nome na tabela filho.

Relações nomeadas

As propriedades generatedKeyName e referenceKeyName são usadas para relacionar dados entre tabelas ou até mesmo entre tipos de projeção. Cada linha na tabela filho tem uma propriedade apontando de volta para o pai. O nome da coluna ou propriedade no filho é o referenceKeyName do pai. Quando o referenceKeyName não é fornecido, o serviço o padroniza para o generatedKeyName do pai.

O Power BI conta com essas chaves geradas para descobrir relações dentro das tabelas. Se você precisar da coluna na tabela filho nomeada de modo diferente, defina a propriedade referenceKeyName na tabela pai. Um exemplo seria definir o generatedKeyName como a ID na tabela tblDocument e referenceKeyName como DocumentID. Isso resultaria na coluna nas tabelas tblEntities e tblKeyPhrases que contêm a ID do documento que está sendo nomeada como DocumentID.

Projetar documentos de blob

As projeções de objeto são representações de JSON da árvore de enriquecimento que podem ser originadas de qualquer nó. Em comparação com projeções de tabela, as projeções de objeto são mais simples de definir e são usadas ao projetar documentos inteiros. As projeções de objeto são limitadas a uma única projeção em um contêiner e não podem ser segmentadas.

Para definir uma projeção de objeto, use a matriz objects na propriedade das projeções.

A origem é o caminho para o nó da árvore de enriquecimento que é a raiz da projeção. Embora não seja necessário, o caminho do nó geralmente é a saída de uma habilidade do Shaper. Isso ocorre porque a maioria das habilidades não gera como saída objetos JSON válidos por conta própria, o que significa que alguma forma de modelagem é necessária. Em muitos casos, a mesma habilidade de Shaper que cria uma projeção de tabela pode ser usada para gerar uma projeção de objeto. Como alternativa, a origem também pode ser definida como um nó com uma modelagem embutida para fornecer a estrutura.

O destino é sempre um contêiner de blob.

O exemplo a seguir projeta documentos individuais do hotel, um documento do hotel por blob, em um contêiner chamado hotels.

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

A origem é a saída de uma habilidade de Shaper, denominada "objectprojection". Cada blob terá uma representação JSON de cada entrada de campo.

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

Projetar um arquivo de imagem

As projeções de arquivo são sempre imagens binárias e normalizadas, nas quais a normalização refere-se ao redimensionamento e à rotação potenciais para uso na execução do conjunto de habilidades. As projeções de arquivo, semelhantes às projeções de objeto, são criadas como blobs no Armazenamento do Azure e contêm a imagem.

Para definir uma projeção de arquivo, use a matriz files na propriedade das projeções.

A origem é sempre /document/normalized_images/*. As projeções de arquivo somente agem na coleção normalized_images. Nenhum dos indexadores nem de um conjunto de habilidades passará pela imagem original não normalizada.

O destino é sempre um contêiner de blob, com um prefixo de pasta do valor codificado em Base64 da ID do documento. As projeções de arquivo não podem compartilhar o mesmo contêiner como projeções de objeto e precisam ser projetadas em um contêiner diferente.

O exemplo a seguir projeta todas as imagens normalizadas extraídas do nó do documento de um documento enriquecido, em um contêiner chamado myImages.

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

Projeção para vários tipos

Um cenário mais complexo pode exigir que você projete o conteúdo entre os tipos de projeção. Por exemplo, projetar frases-chave e entidades para tabelas, salvar os resultados do OCR de texto e layout de texto como objetos e, sem seguida, projetar as imagens como arquivos.

Etapas para vários tipos de projeção:

  1. Crie uma tabela com uma linha para cada documento.
  2. Crie uma tabela relacionada à tabela de documentos com cada frase-chave identificada como uma linha nesta tabela.
  3. Crie uma tabela relacionada à tabela de documentos com cada entidade identificada como uma linha nesta tabela.
  4. Crie uma projeção de objeto com o texto de layout para cada imagem.
  5. Crie uma projeção de arquivo, projetando cada imagem extraída.
  6. Crie uma tabela de referência cruzada que contenha referências à tabela de documentos, projeção de objeto com o texto de layout e a projeção de arquivo.

Formatar dados para projeção cruzada

Para obter as formas necessárias para essas projeções, comece adicionando uma nova habilidade do Shaper que cria um objeto moldado chamado 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"
        }
    ]
}

Definir projeções de tabela, objeto e arquivo

Pelo objeto crossProjection consolidado, divida o objeto em várias tabelas, capture a saída do OCR como BLOBs e salve a imagem como arquivos (também no armazenamento de BLOBs).

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

As projeções de objeto exigem de um nome de contêiner para cada projeção. Projeções de objeto e projeções de arquivo não podem compartilhar um contêiner.

Relações entre tabelas, objetos e projeções de arquivo

Este exemplo também destaca outro recurso de projeções. Ao definir vários tipos de projeções dentro do mesmo objeto de projeção, haverá uma relação expressa no interior e entre os diferentes tipos (tabelas, objetos, arquivos). Isso permite que você comece com uma linha de tabela para um documento e localize todo o texto OCR das imagens dentro desse documento na projeção do objeto.

Se você não quiser os dados relacionados, defina as projeções em diferentes grupos de projeção. Por exemplo, o trecho a seguir fará com que as tabelas estejam relacionadas, mas sem relações entre as projeções de tabelas e objeto (texto do OCR).

Os grupos de projeção serão úteis quando você quiser projetar os mesmos dados em diferentes formas para diferentes necessidades. Por exemplo, um grupo de projeção para o dashboard do Power BI e outro grupo de projeção para capturar dados usados para treinar um modelo de machine learning encapsulado em uma habilidade personalizada.

Ao criar projeções de tipos diferentes, as projeções de arquivo e objeto serão geradas primeiro e os caminhos serão adicionados às tabelas.

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

Próximas etapas

Os exemplos neste artigo demonstram padrões comuns de como criar projeções. Agora que você tem uma boa compreensão dos conceitos, está mais bem equipado para criar projeções para seu cenário específico.