
O processamento de linguagem natural agora é onipresente: interfaces de voz e chatbots estão se desenvolvendo rapidamente, modelos estão sendo desenvolvidos para processar grandes dados de texto e a tradução automática continua a se desenvolver.
Neste artigo, veremos a relativamente nova biblioteca SpaCy, que atualmente é uma das soluções mais populares e convenientes para processamento de texto em Python. Sua funcionalidade permite que você resolva uma ampla gama de tarefas: desde identificar classes gramaticais e extrair entidades nomeadas até criar seus próprios modelos para análise.
Para começar, vamos dar uma olhada em como os dados são processados no SpaCy. O texto carregado para processamento passa sequencialmente por vários componentes de processamento e é armazenado como uma instância do objeto Doc:

Doc é a estrutura de dados central em SpaCy, é nele que as sequências de palavras ou, como são chamadas, tokens são armazenadas. Dentro do objeto Doc, dois outros tipos de objeto podem ser distinguidos: Token e Span. Token é um link para palavras individuais de um documento e Span é um link para uma sequência de várias palavras (você mesmo pode criá-las):

outra estrutura de dados importante é o objeto Vocab, que armazena um conjunto de tabelas de pesquisa comuns a todos os documentos. Isso economiza memória e fornece uma única fonte de informações para todos os documentos processados.
Os tokens de documento são conectados ao objeto Vocab por meio de um hash, com o qual você pode obter as formas iniciais das palavras ou outros atributos lexicais dos tokens:

Agora sabemos como o armazenamento e o processamento de dados na biblioteca SpaCy são organizados. Como aproveitar as oportunidades que ele oferece? Vamos dar uma olhada nas operações que podem ser usadas para processar texto em sequência.
1. Operações básicas
Antes de começar a trabalhar com texto, você deve importar o modelo de idioma. Para o idioma russo, há um modelo oficial da SpaCy que oferece suporte à tokenização (divisão do texto em tokens separados) e uma série de outras operações básicas:
from spacy.lang.ru import Russian
Depois de importar e instanciar o modelo de idioma, você pode começar a processar o texto. Para fazer isso, você só precisa passar o texto para a instância criada:
nlp = Russian()
doc = nlp(" , .")
Trabalhar com o objeto Doc resultante é muito semelhante a trabalhar com listas: você pode acessar o token desejado por índice ou fazer fatias de vários tokens. E para obter o texto de um token ou fatia, você pode usar o atributo text:
token = doc[0]
print(token.text)
span = doc[3:6]
print(span.text)
Para obter mais informações sobre o tipo de informação contida no token, os seguintes atributos podem ser usados:
- is_alpha - verifique se o token contém apenas caracteres alfabéticos
- is_punct - verifique se o token é um sinal de pontuação
- like_num - verifique se um token é um número
print("is_alpha: ", [token.is_alpha for token in doc])
print("is_punct: ", [token.is_punct for token in doc])
print("like_num: ", [token.like_num for token in doc])
Vamos considerar outro exemplo, onde todos os tokens que precedem o ponto são exibidos na tela. Para obter esse resultado, ao iterar sobre tokens, verifique o seguinte token usando o atributo token.i:
for token in doc:
if token.i+1 < len(doc):
next_token = doc[token.i+1]
if next_token.text == ".":
print(token.text)
2. Operações com sintaxe
Outros modelos são usados para operações de processamento de texto mais complexas. Eles são especialmente treinados para tarefas relacionadas à sintaxe, extração de entidades nomeadas e trabalho com significados de palavras. Por exemplo, para o inglês, existem 3 modelos oficiais que diferem em tamanho. Para o idioma russo, no momento, o modelo oficial ainda não foi treinado, mas já existe um modelo ru2 de fontes terceirizadas que pode trabalhar com a sintaxe.
No final deste artigo, discutiremos como criar seus próprios modelos ou, adicionalmente, treinar os existentes para que funcionem melhor em tarefas específicas.
Para ilustrar totalmente os recursos do SpaCy, usaremos os modelos do idioma inglês neste artigo. Vamos configurar um pequeno modelo en_core_web_sm, que é ótimo para demonstrar as possibilidades. Para instalá-lo na linha de comando, você precisa digitar:
python -m spacy download en_core_web_sm
Usando este modelo, podemos obter para cada um dos tokens uma classe gramatical, uma função em uma frase e um token do qual depende:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("New Apple MacBook set launch tomorrow")
for token in doc:
token_text = token.text
token_pos = token.pos_
token_dep = token.dep_
token_head = token.head.text
print(f"{token_text:<12}{token_pos:<10}" \
f"{token_dep:<10}{token_head:<12}")
New PROPN compound MacBook Apple PROPN compound MacBook MacBook PROPN nsubj set set VERB ROOT set to PART aux launch launch VERB xcomp set tomorrow NOUN npadvmod launch
De longe, a melhor maneira de ver as dependências não é ler os dados do texto, mas construir uma árvore de sintaxe. A função displacy pode ajudar com isso, você só precisa transferir o documento:
from spacy import displacy
displacy.render(doc, style='dep', jupyter=True)
Como resultado da execução do código, obtemos uma árvore na qual todas as informações sintáticas sobre a frase estão localizadas:

Para decifrar os nomes das tags, você pode usar as funções de explicação:
print(spacy.explain("aux"))
print(spacy.explain("PROPN"))
auxiliary
proper noun
Aqui, as abreviações são exibidas na tela, a partir da qual podemos aprender que aux significa uma partícula auxiliar (auxiliar) e PROPN significa um nome próprio.
SpaCy também implementa a capacidade de descobrir a forma inicial de uma palavra para qualquer um dos tokens (-PRON- é usado para pronomes):
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("I saw a movie yesterday")
print(' '.join([token.lemma_ for token in doc]))
'-PRON- see a movie yesterday'
3. Destacando entidades nomeadas
Freqüentemente, para trabalhar com texto, você precisa destacar as entidades mencionadas no texto. O atributo doc.ents é usado para listar as entidades nomeadas no documento, e o atributo ent.label_ é usado para obter o rótulo para esta entidade:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple is looking at buying U.K. startup for 1$ billion")
for ent in doc.ents:
print(ent.text, ent.label_)
Apple ORG
U.K. GPE
1$ billion MONEY
Você também pode usar o atributo explain aqui para descobrir a decodificação de rótulos de entidades nomeadas:
print(spacy.explain("GPE"))
Países, cidades, estados
E a função displacy ajudará você a visualizar as listas de entidades diretamente no texto:
from spacy import displacy
displacy.render (doc, style = 'ent', jupyter = True)

4. Crie seus próprios modelos para pesquisa de texto
O módulo spaCy contém uma ferramenta muito útil que permite construir seus próprios modelos de pesquisa de texto. Em particular, você pode pesquisar palavras de uma determinada classe gramatical, todas as formas de uma palavra por sua forma inicial, verifique o tipo de conteúdo no token. Aqui está uma lista dos parâmetros principais:

Vamos tentar criar nosso próprio modelo para reconhecer uma sequência de tokens. Digamos que desejemos extrair das linhas do texto sobre as Copas do Mundo de Críquete da FIFA ou ICC com a menção do ano:
import spacy
from spacy.matcher import Matcher
nlp = spacy.load("en_core_web_sm")
matcher = Matcher(nlp.vocab)
pattern = [
{"IS_DIGIT": True},
{"LOWER": {"REGEX": "(fifa|icc)"}},
{"LOWER": "cricket", "OP": "?"},
{"LOWER": "world"},
{"LOWER": "cup"}
]
matcher.add("fifa_pattern", None, pattern)
doc = nlp("2018 ICC Cricket World Cup: Afghanistan won!")
matches = matcher(doc)
for match_id, start, end in matches:
matched_span = doc[start:end]
print(matched_span)
2018 ICC Cricket World Cup
Portanto, neste bloco de código, importamos um objeto Matcher especial que nos permite armazenar um conjunto de modelos personalizados. Após inicializá-lo, criamos um modelo onde especificamos a sequência de tokens. Observe que usamos expressões regulares para escolher entre ICC e FIFA e para o token de críquete - uma chave que indica a presença opcional desse token.
Depois de criar um modelo, você precisa adicioná-lo ao conjunto usando a função adicionar, especificando um ID de modelo exclusivo nos parâmetros. Os resultados da pesquisa são apresentados na forma de uma lista de tuplas. Cada uma das tuplas consiste no ID de correspondência e nos índices de início e fim da fatia encontrada no documento.
5. Determinação da proximidade semântica
Duas palavras podem ter significados muito semelhantes, mas como você mede sua proximidade? Em tais tarefas, os vetores semânticos podem vir em seu socorro. Se duas palavras ou expressões verbosas forem semelhantes, seus vetores ficarão próximos um do outro.
Calcular a proximidade semântica de vetores em SpaCy não é difícil se o modelo de linguagem foi treinado para resolver tais problemas. O resultado é altamente dependente do tamanho do modelo, então vamos usar um modelo maior para esta tarefa:
import spacy
nlp = spacy.load("en_core_web_md")
doc1 = nlp("I like burgers")
doc2 = nlp("I like pizza")
print(doc1.similarity(doc2))
0.9244169833828932
O valor pode variar de zero a um: quanto mais próximo de um, maior é a semelhança. No exemplo acima, comparamos dois documentos, no entanto, você pode comparar tokens e fatias individuais da mesma maneira.
A avaliação de proximidade semântica pode ser útil para resolver muitos problemas. Por exemplo, você pode usá-lo para configurar um sistema de recomendação de forma que ofereça ao usuário textos semelhantes com base nos já lidos.
É importante lembrar que a afinidade semântica é muito subjetiva e sempre depende do contexto da tarefa. Por exemplo, as frases “Eu amo cachorros” e “Odeio cachorros” são semelhantes, pois ambas expressam opiniões sobre cachorros, mas ao mesmo tempo têm humores muito diferentes. Em alguns casos, você terá que treinar modelos de linguagem adicionais para que os resultados se correlacionem com o contexto do seu problema.
6. Criação de seus próprios componentes de processamento
O módulo SpaCy oferece suporte a vários componentes integrados (tokenizer, destaque de entidade nomeada), mas também permite definir seus próprios componentes. Na verdade, os componentes são chamados sequencialmente de funções que pegam um documento como entrada, o modificam e o enviam de volta. Novos componentes podem ser adicionados usando o atributo add_pipe:
import spacy
def length_component(doc):
doc_length = len(doc)
print(f"This document is {doc_length} tokens long.")
return doc
nlp = spacy.load("en_core_web_sm")
nlp.add_pipe(length_component, first=True)
print(nlp.pipe_names)
doc = nlp("This is a sentence.")
['length_component', 'tagger', 'parser', 'ner']
This document is 5 tokens long.
No exemplo acima, criamos e adicionamos nossa própria função que exibe o número de tokens no documento processado. Usando o atributo nlp.pipe_names, obtivemos a ordem de execução dos componentes: como podemos ver, o componente criado é o primeiro da lista. Você pode usar as seguintes opções para especificar onde adicionar o novo componente: A

capacidade de adicionar componentes personalizados é uma ferramenta muito poderosa para otimizar o processamento de acordo com suas necessidades.
7. Modelos de treinamento e atualização
Os modelos estatísticos fazem previsões com base nos exemplos em que foram treinados. Como regra, a precisão de tais modelos pode ser melhorada treinando-os adicionalmente em exemplos específicos para sua tarefa. O treinamento adicional em modelos existentes pode ser muito útil (por exemplo, para reconhecimento ou análise de entidade nomeada).
Exemplos de treinamento adicionais podem ser adicionados diretamente na interface SpaCy. Os próprios exemplos devem consistir em dados de texto e uma lista de rótulos para este exemplo no qual o modelo será treinado.
Como ilustração, considere atualizar o modelo para recuperar entidades nomeadas. Para atualizar tal modelo, é necessário passar vários exemplos que contêm texto, uma indicação das entidades e sua classe. Nos exemplos, você precisa usar cláusulas inteiras, porque ao extrair entidades, o modelo depende muito do contexto da cláusula. É muito importante treinar totalmente o modelo para que ele possa reconhecer tokens de não entidade.
Por exemplo:
("What to expect at Apple's 10 November event", {"entities": [(18,23,"COMPANY")]})
("Is that apple pie I smell?", {"entities": []})
No primeiro exemplo, é mencionada uma empresa: para formação, destacamos as posições onde começa e termina o seu nome, e depois colocamos o nosso rótulo de que esta entidade é uma empresa. No segundo exemplo, estamos falando de uma fruta, então não há entidades.
Os dados para treinar o modelo geralmente são marcados por pessoas, mas esse trabalho pode ser ligeiramente automatizado usando os próprios modelos de pesquisa no SpaCy ou programas de marcação especializados (por exemplo, Prodigy ).
Após a preparação dos exemplos, você pode prosseguir diretamente para treinar o modelo. Para que o modelo seja treinado com eficácia, você precisa executar uma série de vários treinamentos. A cada treinamento, o modelo otimizará os pesos de determinados parâmetros. Os modelos em SpaCy utilizam a técnica de descida gradiente estocástica, por isso é uma boa ideia misturar os exemplos a cada treino, e também transferi-los em pequenas porções (pacotes). Isso aumentará a confiabilidade das estimativas do gradiente.

import spacy
import random
from spacy.lang.en import English
TRAINING_DATA = [
("What to expect at Apple's 10 November event",
{"entities": [(18,23,"COMPANY")]})
# ...
]
nlp = English()
for i in range(10):
random.shuffle(TRAINING_DATA)
for batch in spacy.util.minibatch(TRAINING_DATA):
texts = [text for text, annotation in batch]
annotations = [annotation for text, annotation in batch]
nlp.update(texts, annotations)
nlp.to_disk("model")
No exemplo acima, o loop consistia em 10 treinamentos. Após a conclusão do treinamento, o modelo foi salvo em disco na pasta de modelos.
Para os casos em que é necessário não apenas atualizar, mas também criar um novo modelo, várias operações são necessárias antes de iniciar o treinamento.
Considere o processo de criação de um novo modelo para destacar entidades nomeadas:
nlp = spacy.blank("en")
ner = nlp.create_pipe("ner")
nlp.add_pipe(ner)
ner.add_label("COMPANY")
nlp.begin_training()
Primeiro, criamos um modelo vazio usando a função spacy.blank ("en"). O modelo contém apenas dados de idioma e regras de tokenização. Em seguida, adicionamos um componente ner que é responsável por destacar entidades nomeadas e, usando o atributo add_label, adicionamos rótulos para as entidades. Em seguida, usamos a função nlp.begin_training () para inicializar o modelo para treinamento com uma distribuição aleatória de pesos. Pois bem, então bastará treinar o modelo, como mostra o exemplo anterior.