Como implantar o PLN: exemplo de reconhecimento de entidades nomeadas (REN)

illustration-a-new-state-of-find-campaign-image-608x440-2x.png

Como parte de nossa série de posts sobre processamento de linguagem natural (PLN), apresentaremos um exemplo usando um modelo de PLN de reconhecimento de entidades nomeadas (REN) para localizar e extrair categorias predefinidas de entidades em campos de texto não estruturados. Usando um modelo disponível publicamente, mostraremos como implantar esse modelo no Elasticsearch, encontrar entidades nomeadas no texto com a nova API _infer e usar o modelo de REN em um pipeline de ingestão para extrair entidades à medida que os documentos são ingeridos no Elasticsearch.

Os modelos de REN são úteis quando se utiliza linguagem natural para extrair entidades como pessoas, lugares e organizações de campos de texto completo.

Neste exemplo, vamos passar parágrafos do livro Les Misérables por um modelo de REN, usar o modelo para extrair os personagens e locais do texto e visualizar as relações entre eles.

Como implantar um modelo de REN no Elasticsearch

Primeiro, precisamos selecionar um modelo de REN que possa extrair os nomes dos personagens e locais dos campos de texto. Felizmente, existem alguns modelos de REN disponíveis em Hugging Face que podemos escolher. Ao verificar a documentação da Elastic, vemos um modelo da Elastic de REN sem diferenciação de maiúsculas e minúsculas para experimentar.

Uma vez selecionado o modelo de REN a ser usado, podemos usar o Eland para instalar o modelo. Neste exemplo, executaremos o comando do Eland por meio de uma imagem do Docker. No entanto, primeiro devemos construir a imagem do Docker clonando o repositório do Eland no GitHub e criar uma imagem do Docker do Eland no seu sistema cliente:

git clone git@github.com:elastic/eland.git
cd eland
docker build -t elastic/eland .

Agora que nosso cliente do Docker do Eland está pronto, podemos instalar o modelo de REN executando o seguinte comando eland_import_hub_model na nova imagem do Docker:

docker run -it --rm elastic/eland \
    eland_import_hub_model \
      --url $ELASTICSEARCH_URL \
      --hub-model-id elastic/distilbert-base-uncased-finetuned-conll03-english \
      --task-type ner \
      --start

Você precisará substituir ELASTICSEARCH_URL pelo URL do cluster do Elasticsearch. Para fins de autenticação, você precisará incluir um nome de usuário e uma senha de administrador no URL no formato https://username:password@host:port.

Como usamos a opção --start no final do comando de importação do Eland, o Elasticsearch implantará o modelo em todos os nós de machine learning disponíveis e carregará o modelo na memória. Se tivéssemos vários modelos e quiséssemos selecionar qual modelo implantar, poderíamos usar a interface de usuário Machine Learning > Model Management (Gerenciamento de modelos) do Kibana para gerenciar o início e a interrupção dos modelos.

Teste do modelo de REN

Os modelos implantados podem ser avaliados usando a nova API _infer. A entrada é a string que desejamos analisar. Na solicitação abaixo, text_field é o nome do campo onde o modelo espera encontrar a entrada, conforme definido na configuração do modelo. Por padrão, se o modelo foi carregado via Eland, o campo de entrada é text_field.

Tente este exemplo no Console do Dev Tools do Kibana:

POST _ml/trained_models/elastic__distilbert-base-uncased-finetuned-conll03-english/deployment/_infer
{
  "docs": [
    {
      "text_field": "Hi my name is Josh and I live in Berlin"
    }
  ]
}

O modelo encontrou duas entidades: a pessoa “Josh” e o local “Berlin”.

{
  "predicted_value" : "Hi my name is [Josh](PER&Josh) and I live in [Berlin](LOC&Berlin)",   
    "entities" :     {
      "entity" : "Josh",
      "class_name" : "PER",
      "class_probability" : 0.9977303419824,
      "start_pos" : 14,
      "end_pos" : 18
    },
    {
      "entity" : "Berlin",
      "class_name" : "LOC",
      "class_probability" : 0.9992474323902818,
      "start_pos" : 33,
      "end_pos" : 39
    }
  ]
}

predicted_value é a string de entrada no formato de texto anotado, class_name é a classe prevista e class_probability indica o nível de confiança na previsão. start_pos e end_pos são as posições dos caracteres inicial e final da entidade identificada.

Inclusão do modelo de REN em um pipeline de ingestão de inferência

A API _infer é uma maneira divertida e fácil de começar, mas aceita apenas uma única entrada, e as entidades detectadas não são armazenadas no Elasticsearch. Uma alternativa é realizar inferência em massa nos documentos conforme são ingeridos por meio de um pipeline de ingestão com o processador inference

Você pode definir um pipeline de ingestão na UI Stack Management (Gerenciamento da stack) ou configurá-lo no Console do Kibana; este contém vários processadores de ingestão:

PUT _ingest/pipeline/ner
{
  "description": "NER pipeline",
  "processors": [
    {
      "inference": {
        "model_id": "elastic__distilbert-base-uncased-finetuned-conll03-english",
        "target_field": "ml.ner",
        "field_map": {
          "paragraph": "text_field"
        }
      }
    },
    {
      "script": {
        "lang": "painless",
        "if": "return ctx['ml']['ner'].containsKey('entities')",
        "source": "Map tags = new HashMap(); for (item in ctx['ml']['ner']['entities']) { if (!tags.containsKey(item.class_name)) tags[item.class_name] = new HashSet(); tags[item.class_name].add(item.entity);} ctx['tags'] = tags;"
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "description": "Index document to 'failed-<index>'",
        "field": "_index",
        "value": "failed-{{{ _index }}}"
      }
    },
    {
      "set": {
        "description": "Set error message",
        "field": "ingest.failure",
        "value": "{{_ingest.on_failure_message}}"
      }
    }
  ]
}

Começando com o processador inference, o propósito de field_map é mapear paragraph (o campo a ser analisado nos documentos de origem) para text_field (o nome do campo que o modelo está configurado para usar). target_field é o nome do campo no qual gravar os resultados da inferência.

O processador script extrai as entidades e as agrupa por tipo. O resultado final são listas de pessoas, locais e organizações detectadas no texto de entrada. Estamos adicionando este script Painless para que possamos criar visualizações a partir dos campos criados.

A cláusula on_failure existe para detectar erros. Ela define duas ações. Primeiro, define o metacampo _index com um novo valor, e o documento agora será armazenado lá. Segundo, a mensagem de erro é gravada em um novo campo: ingest.failure. A inferência pode falhar por vários motivos facilmente corrigíveis. Talvez o modelo não tenha sido implantado ou o campo de entrada esteja ausente em alguns dos documentos de origem. Com o redirecionamento dos documentos com falha para outro índice e a definição da mensagem de erro, essas inferências com falha não são perdidas e podem ser revisadas posteriormente. Depois que os erros forem corrigidos, reindexe a partir do índice com falha para recuperar as solicitações malsucedidas.

Seleção dos campos de texto para inferência

O REN pode ser aplicado a muitos conjuntos de dados. Como exemplo, escolhi Les Misérables, o clássico romance publicado em 1862 por Victor Hugo. Você pode carregar os parágrafos de Les Misérables do nosso arquivo json de amostra usando o recurso de upload de arquivo do Kibana. O texto é dividido em 14.021 documentos JSON, cada um contendo um único parágrafo. Vamos pegar um parágrafo aleatório como exemplo:

{
    "paragraph": "Father Gillenormand did not do it intentionally, but inattention to proper names was an aristocratic habit of his.",
    "line": 12700
}

Depois que o parágrafo é ingerido por meio do pipeline de REN, o documento resultante armazenado no Elasticsearch é marcado com uma única pessoa identificada.

{
    "paragraph": "Father Gillenormand did not do it intentionally, but inattention to proper names was an aristocratic habit of his.",
    "@timestamp": "2020-01-01T17:38:25",
    "line": 12700,
    "ml": {
        "ner": {
            "predicted_value": "Father [Gillenormand](PER&Gillenormand) did not do it intentionally, but inattention to proper names was an aristocratic habit of his.",
            "entities": [{
                "entity": "Gillenormand",
                "class_name": "PER",
                "class_probability": 0.9806354093873283,
                "start_pos": 7,
                "end_pos": 19
            }],
            "model_id": "elastic__distilbert-base-cased-finetuned-conll03-english"
        }
    },
    "tags": {
        "PER": [
            "Gillenormand"
        ]
    }
}

Uma nuvem de tags é uma visualização que dimensiona as palavras pela frequência com que ocorrem e é o infográfico perfeito para visualizar as entidades encontradas em Les Misérables. Abra o Kibana, crie uma nova visualização baseada em agregação e escolha Tag Cloud (Nuvem de tags). Selecione o índice que contém os resultados do REN e adicione uma agregação de termos no campo tags.PER.keyword.

É fácil notar pela visualização que Cosette, Marius e Jean Valjean são os personagens mencionados com mais frequência no livro.

Ajuste da implantação

Voltando à UI Model Management (Gerenciamento de modelos), em Deployment stats (Estatísticas de implantação), você encontrará Avg Inference Time (Tempo médio de inferência). Esse é o tempo medido pelo processo nativo para realizar a inferência em uma única solicitação. Ao iniciar uma implantação, há dois parâmetros que controlam como os recursos da CPU são usados: inference_threads e model_threads.

inference_threads é o número de threads usados para executar o modelo por solicitação. O aumento de inference_threads reduz diretamente o tempo médio de inferência. O número de solicitações avaliadas em paralelo é controlado por model_threads. Essa configuração não reduzirá o tempo médio de inferência, mas aumentará a taxa de transferência.

Em geral, você ajusta a latência aumentando o número de inference_threads e aumenta a taxa de transferência elevando o número de model_threads. Ambas as configurações têm um único thread por padrão, portanto, pode-se obter um bom ganho de desempenho ao modificá-las. O efeito é demonstrado usando o modelo de REN.

Para alterar uma das configurações de thread, a implantação deve ser interrompida e reiniciada. O parâmetro ?force=true é passado para a API de interrupção porque a implantação é referenciada por um pipeline de ingestão que normalmente impediria a interrupção.

POST _ml/trained_models/elastic__distilbert-base-uncased-finetuned-conll03-english/deployment/_stop?force=true

Reinicie com quatro threads de inferência. O tempo médio de inferência é redefinido quando a implantação é reiniciada.

POST _ml/trained_models/elastic__distilbert-base-uncased-finetuned-conll03-english/deployment/_start?inference_threads=4

No processamento dos parágrafos de Les Misérables, o tempo médio de inferência cai para 55,84 milissegundos por solicitação, em comparação com 173,86 milissegundos para um único thread.

Aprendendo mais e experimentando

O REN é apenas uma das tarefas de PNL prontas para uso agora. Classificação de texto, classificação zero-shot e embeddings de texto também estão disponíveis. Mais exemplos podem ser encontrados na documentação de PLN, juntamente com uma lista de modelos (não exaustiva) que pode ser implantada no Elastic Stack.

A PLN é um novo recurso importante no Elastic Stack para a versão 8.0 com um roadmap muito interessante. Descubra novos recursos e acompanhe os desenvolvimentos mais recentes criando seu cluster no Elastic Cloud. Inscreva-se para fazer uma avaliação gratuita de 14 dias hoje mesmo e teste os exemplos deste post.

Se quiser ler mais sobre PNL: