Histórias de usuários

Classificação de texto facilitada com o Elasticsearch

O Elasticsearch é bastante usado como um mecanismo de busca e análise. Seus recursos como API de mineração de texto também não são conhecidos.

No artigo a seguir, eu gostaria de mostrar como é possível fazer classificação de texto com o Elasticsearch. Com conhecimento em linguística computacional e vários anos de experiência em negócios como autônomo na área de mineração de texto, tive a oportunidade de implementar e testar as seguintes técnicas em diferentes cenários.

Quando me deparei com o Elasticsearch pela primeira vez, fiquei fascinado com sua facilidade de uso, sua velocidade e suas opções de configuração. Toda vez que usava ele, eu descobria um jeito ainda mais simples de fazer o que já fazia com ferramentas e técnicas tradicionais de NLP (Processamento de Linguagem Natural).

Em algum momento percebi que ele pode resolver de imediato um monte de coisas que eu fui treinado para implementar do zero.

A maioria das tarefas de NLP começa com um pipeline de pré-processamento padrão:

  1. Coleta de dados
  2. Extração de texto bruto
  3. Divisão de frases
  4. Tokenização
  5. Normalização (stemização, lematização)
  6. Remoção de palavras vazias
  7. Marcação de parte do discurso

Algumas tarefas de NLP, como análise sintática, exigem profunda análise linguística.

Para esse tipo de tarefa, o Elasticsearch não oferece imediatamente a arquitetura e o formato de dados ideais. Isso significa que, para tarefas que vão além do nível de token, é necessário escrever ou usar plugins personalizados que acessam o texto completo.

Só que tarefas como classificação, armazenamento em cluster, extração de palavras-chave, mensuração de similaridade etc. só exigem uma representação de saco-de-palavras normalizada e possivelmente ponderada de um determinado documento.

As etapas 1 e 2 podem ser resolvidas com o Plugin Ingest Attachment Processor (antes da versão 5.0: Plugin Mapper Attachments) no Elasticsearch.

A extração de texto bruto para esses plugins se baseia no Apache Tika, que funciona na maioria dos formatos de dados mais comuns (HTML/PDF/Word etc.).

As etapas de 4 a 6 são resolvidas imediatamente com os analisadores de linguagem.

Mapeamento de amostras:

  {
    "properties":{
       "content":{
          "type":"text",
          "analyzer":"german"
       }
    }
  }

Se o tipo de mapeamento para um determinado campo for "text" (antes da versão 5.0: "analyzed string") e o analisador for definido como uma das linguagens com suporte nativo do Elasticsearch, a tokenização, a stemização e a remoção de palavras vazias serão executadas automaticamente no momento da indexação.

Por isso não é necessário obter nenhum código personalizado e nenhuma outra ferramenta de qualquer tipo de documento compatível com o Apache Tika para uma representação de saco-de-palavras.

Os analisadores de linguagem também podem ser chamados por meio da API REST, quando o Elasticsearch está em execução.

curl -XGET "http://localhost:9200/_analyze?analyzer=english" -d'
  {
   "text" : "This is a test."
  }'
  {
    "tokens":[
       {
          "token":"test",
          "start_offset":10,
          "end_offset":14,
          "type":"<ALPHANUM>",
          "position":3
       }
    ]
  }

A abordagem que não seja do Elasticsearch tem aparência semelhante a esta:

Coletar o texto com código personalizado, analisar documentos manualmente ou com a biblioteca Tika, usando uma biblioteca NLP tradicional ou uma API como NLTK, OpenNLP, Stanford NLP, Spacy ou qualquer outra que foi desenvolvida em algum departamento de pesquisa. Entretanto, ferramentas desenvolvidas em departamentos de pesquisa normalmente não são muito úteis para um contexto empresarial. Com muita frequência os formatos de dados são proprietários, as ferramentas precisam ser compiladas e executadas na linha de comando e os resultados são muitas vezes gerados em pipes para fora do padrão. As APIs REST são uma exceção.

Com os analisadores de linguagem do Elasticsearch, em contrapartida, basta configurar o mapeamento e indexar os dados. O pré-processamento ocorre automaticamente no momento da indexação.

Abordagem tradicional à classificação de texto

A classificação de texto é uma tarefa tradicionalmente resolvida com machine learning supervisionado. O material inserido para treinar um modelo é um conjunto de documentos identificados. A representação mínima disso seria um documento JSON com 2 campos:

"content" e "category"

Tradicionalmente, a classificação de texto pode ser resolvida com uma ferramenta como SciKit Learn, Weka, NLTK, Apache Mahout etc.

Criação dos modelos

A maioria dos algoritmos de machine learning exige uma representação de modelo de espaço vetorial dos dados. O espaço do recurso normalmente é algo como as 10.000 palavras mais importantes de um determinado conjunto de dados. Como a importância de uma palavra pode ser mensurada?

Normalmente com TF-IDF. Essa é uma fórmula que foi inventada na década de 70 do século passado. TF-IDF é um peso que pontua um termo em um determinado documento em relação ao restante do conjunto de dados. Se um termo em um documento tem uma alta pontuação de TF-IDF, isso significa que ele é uma palavra-chave muito característica e distingue um documento de todos os outros documentos por meio dessa palavra.

As palavras-chave com as mais altas pontuações de TF-IDF em um subconjunto de documentos podem representar um tópico. Para classificação de texto, um espaço de recurso com as n palavras com as mais altas pontuações de TF-IDF gerais é muito comum.

Cada documento é convertido em um vetor de recurso, depois com todas as instâncias de treinamento para cada classe/categoria que um modelo é criado. Depois disso, novos documentos podem ser classificados de acordo com esse modelo. Portanto, o documento precisa ser convertido em um vetor de recurso e, a partir daí, todas as similaridades são calculadas. O documento será identificado com a categoria com a mais alta pontuação.

Classificação de texto com o Elasticsearch

Todas as questões anteriores podem ser resolvidas de um jeito bem mais simples com o Elasticsearch (ou o Lucene).

Basta executar quatro etapas:

  1. Configurar o mapeamento ("content" : "text", "category" : "keyword")
  2. Indexar os documentos
  3. Executar More Like This Query (Consulta MLT)
  4. Escrever um pequeno script que agrega as ocorrências dessa consulta por pontuação
PUT sample
  POST sample/document/_mapping
  {
    "properties":{
       "content":{
          "type":"text",
          "analyzer":"english"
       },
       "category":{
          "type":"text",
          "analyzer":"english",
          "fields":{
             "raw":{
                "type":"keyword"
             }
          }
       }
    }
  }
  POST sample/document/1
  {
    "category":"Apple (Fruit)",
    "content":"Granny Smith, Royal Gala, Golden Delicious and Pink Lady are just a few of the thousands of different kinds of apple that are grown around the world! You can make dried apple rings at home - ask an adult to help you take out the core, thinly slice the apple and bake the rings in the oven at a low heat."
  }
  POST sample/document/2
  {
    "category":"Apple (Company)",
    "content":"Apple is an American multinational technology company headquartered in Cupertino, California, that designs, develops, and sells consumer electronics, computer software, and online services. Its hardware products include the iPhone smartphone, the iPad tablet computer, the Mac personal computer, the iPod portable media player, the Apple Watch smartwatch, and the Apple TV digital media player. Apple's consumer software includes the macOS and iOS operating systems, the iTunes media player, the Safari web browser, and the iLife and iWork creativity and productivity suites. Its online services include the iTunes Store, the iOS App Store and Mac App Store, Apple Music, and iCloud."
  }

A consulta MLT é uma consulta muito importante para mineração de texto.

Como funciona? Ela pode processar texto arbitrário, extrair as principais n palavras-chave em relação ao "modelo" real e executar uma consulta de correspondência booliana com essas palavras-chave. Essa consulta geralmente é usada para coletar documentos semelhantes.

Se todos os documentos tiverem uma identificação de classe/categoria e um número semelhante de instâncias de treinamento por classe, isso será equivalente à classificação. Basta executar uma consulta MLT com o documento de entrada como campo like e escrever um pequeno script que agrega a pontuação e a categoria das principais n ocorrências.

GET sample/document/_search
  {
    "query":{
       "more_like_this":{
          "fields":[
             "content",
             "category"
          ],
          "like":"The apple tree (Malus pumila, commonly and erroneously called Malus domestica) is a deciduous tree in the rose family best known for its sweet, pomaceous fruit, the apple. It is cultivated worldwide as a fruit tree, and is the most widely grown species in the genus Malus. The tree originated in Central Asia, where its wild ancestor, Malus sieversii, is still found today. Apples have been grown for thousands of years in Asia and Europe, and were brought to North America by European colonists. Apples have religious and mythological significance in many cultures, including Norse, Greek and European Christian traditions.",
          "min_term_freq":1,
          "max_query_terms":20
       }
    }
  }

Esta amostra só serve para ilustrar o fluxo de trabalho. Para classificação real, você precisará de mais dados. Por isso não se preocupe se com esse exemplo você não obtiver nenhum resultado real. Basta adicionar mais dados para funcionar.

E a seguir está um pequeno script em Python que processa a resposta e retorna a categoria mais provável para o documento de entrada.

from operator import itemgetter
  def get_best_category(response):
     categories = {}
     for hit in response['hits']['hits']:
         score = hit['_score']
         for category in hit['_source']['category']: 
             if category not in categories:
                 categories[category] = score
             else:
                 categories[category] += score
     if len(categories) > 0:
         sortedCategories = sorted(categories.items(), key=itemgetter(1), reverse=True)
         category = sortedCategories[0][0]
     return category

E aí está o seu classificador de texto do Elasticsearch!

Casos de uso

A classificação de texto é um caso de uso real muito comum para o NLP. Pense em dados de comércio eletrônico (produtos). Muitas pessoas administram lojas de comércio eletrônico com links afiliados. Os dados são fornecidos por várias lojas e geralmente vêm com uma marca de categoria. Só que cada loja tem outra marca de categoria. Então os sistemas de categoria precisam ser unificados e assim todos os dados precisam ser reclassificados de acordo com a árvore da nova categoria. Ou pense em uma aplicação de business intelligence em que os sites da empresa precisam ser classificados de acordo com seu setor (cabeleireiro em comparação com padaria etc).

Avaliação

Eu avaliei essa abordagem com um conjunto de dados de classificação de texto padrão: O conjunto de dados 20 Newsgroups. A mais alta precisão (92% de identificações corretas) foi atingida com um limite de pontuação de alta qualidade que incluiu somente 12% dos documentos. Ao identificar todos os documentos (100% de recall), 72% das previsões estavam corretas.

Os melhores algoritmos para classificação de texto no conjunto de dados 20 Newsgroups normalmente são o SVM e o Naive Bayes. Eles têm uma precisão de média mais alta em todo o conjunto de dados.

Então por que você deve avaliar o uso do Elasticsearch para classificação se há algoritmos melhores?

Existem alguns motivos práticos: o treinamento em um modelo SVM leva muito tempo. Principalmente quando você trabalha em uma startup ou precisa se adaptar rapidamente a diferentes clientes ou casos de uso que podem se tornar um problema real. Então talvez você não possa retreinar seu modelo toda vez em que os dados forem alterados. Eu mesmo tive essa experiência ao trabalhar em um projeto para um grande banco alemão. Assim você trabalhará com modelos obsoletos e com certeza eles não pontuarão mais tão bem.

Com a abordagem do Elasticsearch, o treinamento ocorre no momento da indexação e seu modelo pode ser atualizado dinamicamente a qualquer momento com inatividade zero da aplicação. Se os dados forem armazenados no Elasticsearch de qualquer maneira, você não precisará de nenhuma infraestrutura adicional. Com resultados altamente precisos de mais de 10% você normalmente pode preencher a primeira página. Em muitas aplicações, isso é suficiente para causar uma primeira boa impressão.

Então por que usar o Elasticsearch quando existem outras ferramentas?

Porque seus dados já estão disponíveis e de qualquer maneira eles farão um pré-cálculo das estatísticas subjacentes. É quase como obter um pouco de NLP de graça!


Saskia Vola

Saskia Vola estudou Linguística Computacional na Universidade de Heidelberg e começou a trabalhar na área de mineração de texto em 2009. Depois de alguns anos em uma startup de Berlim, ela decidiu se tornar autônoma em tempo integral e aproveitar a vida como uma nômade digital. À medida que recebia cada vez mais ofertas de projetos relevantes, decidiu começar uma plataforma para autônomos de NLP/AI chamada textminers.io