Reconhecimento de entidades nomeadas com transformadores

Reconhecimento de entidades nomeadas com transformadores

        
                
        

IntroduçãoPermanente

Na minha publicação anterior no blogue, discutimos como a minha equipa pré-treinou o SpanBERTa, um modelo linguístico transformador para espanhol, num grande corpus de raiz. O modelo demonstrou ser capaz de prever corretamente palavras mascaradas numa sequência com base no seu contexto. Nesta publicação do blogue, para tirar realmente partido do poder dos modelos transformadores, vamos afinar o SpanBERTa para uma tarefa de reconhecimento de identidades nomeadas.

De acordo com a sua definição em WikipédiaO reconhecimento de entidades nomeadas (NER) (também conhecido como identificação de entidades, fragmentação de entidades e extração de entidades) é uma subtarefa da extração de informação que procura localizar e classificar entidades nomeadas mencionadas em texto não estruturado em categorias pré-definidas, tais como nomes de pessoas, organizações, localizações, códigos médicos, expressões temporais, quantidades, valores monetários, percentagens, etc.

Utilizaremos o script run_ner.py por Hugging Face e Conjunto de dados CoNLL-2002 para afinar o SpanBERTa.

ConfiguraçãoPermanente

Descarregar transformadores e instalar os pacotes necessários.

%%capture
!git clone https://github.com/huggingface/transformers
%cd transformers
!pip install .
!pip install -r ./examples/requirements.txt
%cd ..

DadosPermanente

1. Descarregar conjuntos de dadosPermanente

O comando abaixo irá descarregar e descomprimir o conjunto de dados. Os ficheiros contêm os dados de treino e de teste para três partes do CoNLL-2002 tarefa partilhada:

  • esp.testa: Dados de teste espanhóis para a fase de desenvolvimento
  • esp.testb: Dados de testes espanhóis
  • esp.train: Dados de comboios espanhóis
%ptura
!wget -O 'conll2002.zip' 'https://drive.google.com/uc?export=download&id=1Wrl1b39ZXgKqCeAFNM9EoXtA1kzwNhCe'
!descompactar 'conll2002.zip'

O tamanho de cada conjunto de dados:

!wc -l conll2002/esp.train
!wc -l conll2002/esp.testa
!wc -l conll2002/esp.testb
273038 conll2002/esp.train
54838 conll2002/esp.testa
53050 conll2002/esp.testb

Todos os ficheiros de dados têm três colunas: palavras, etiquetas de parte do discurso associadas e etiquetas de entidades nomeadas no formato IOB2. As quebras de frase são codificadas por linhas vazias.

!head -n20 conll2002/esp.train
Melbourne NP B-LOC
( Fpa O
Austrália NP B-LOC
) Fpt O
, Fc O
25 Z O
pode NC O
( Fpa O
EFE NC B-ORG
) Fpt O
. Fp O

- Fg O

El DA O
Abogado NC B-PER
General AQ I-PER
del SP I-PER
Estado NC I-PER
, Fc O

Apenas manteremos a coluna da palavra e a coluna da etiqueta da entidade nomeada para os nossos conjuntos de dados de treino, desenvolvimento e teste.

!cat conll2002/esp.train | cut -d " " -f 1,3 > train_temp.txt
!cat conll2002/esp.testa | cut -d " " -f 1,3 > dev_temp.txt
!cat conll2002/esp.testb | cut -d " " -f 1,3 > test_temp.txt

2. Pré-processamentoPermanente

Vamos definir algumas variáveis de que necessitamos para outras etapas de pré-processamento e para treinar o modelo:

MAX_LENGTH = 120 #@param {type: "integer"}
MODELO = "chriskhanhtran/spanberta" #@param ["chriskhanhtran/spanberta", "bert-base-multilingual-cased"]

O script abaixo dividirá frases maiores que MAX_LENGTH (em termos de tokens) em frases pequenas. Caso contrário, as frases longas serão truncadas quando tokenizadas, causando a perda de dados de treino e a não previsão de alguns tokens no conjunto de teste.

%ptura
!wget "https://raw.githubusercontent.com/stefan-it/fine-tuned-berts-seq/master/scripts/preprocess.py"
!python3 preprocess.py train_temp.txt $MODEL $MAX_LENGTH > train.txt
!python3 preprocess.py dev_temp.txt $MODEL $MAX_LENGTH > dev.txt
!python3 preprocess.py test_temp.txt $MODEL $MAX_LENGTH > test.txt
2020-04-22 23:02:05.747294: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Abriu com sucesso a biblioteca dinâmica libcudart.so.10.1
Descarregando: 100% 1.03k/1.03k [00:00<00:00, 704kB/s]
Descarregando: 100% 954k/954k [00:00<00:00, 1.89MB/s]
Descarregando: 100% 512k/512k [00:00<00:00, 1.19MB/s]
Descarregando: 100% 16.0/16.0 [00:00<00:00, 12.6kB/s]
2020-04-22 23:02:23.409488: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Abriu com sucesso a biblioteca dinâmica libcudart.so.10.1
2020-04-22 23:02:31.168967: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Abriu com sucesso a biblioteca dinâmica libcudart.so.10.1

3. EtiquetasPermanente

Nos conjuntos de dados CoNLL-2002/2003, existem 9 classes de etiquetas NER:

  • O, Fora de uma entidade nomeada
  • B-MIS, Início de uma entidade diversa logo após outra entidade diversa
  • I-MIS, Entidade diversa
  • B-PER, Início do nome de uma pessoa logo após o nome de outra pessoa
  • I-PER, Nome da pessoa
  • B-ORG, Início de uma organização logo após outra organização
  • I-ORG, Organização
  • B-LOC, Início de uma localização logo após outra localização
  • I-LOC, Localização

Se o seu conjunto de dados tiver etiquetas diferentes ou mais etiquetas do que os conjuntos de dados CoNLL-2002/2003, execute a linha abaixo para obter etiquetas únicas dos seus dados e guarde-as em etiquetas.txt. Este ficheiro será utilizado quando começarmos a afinar o nosso modelo.

!cat train.txt dev.txt test.txt | cut -d " " -f 2 | grep -v "^$"| sort | uniq > labels.txt

Modelo de afinaçãoPermanente

Estes são os exemplos de scripts de transformadoresque utilizaremos para afinar o nosso modelo para NER. Após 21/04/2020, a Hugging Face actualizou os seus scripts de exemplo para utilizar um novo Treinador classe. Para evitar qualquer conflito futuro, vamos utilizar a versão anterior a estas actualizações.

%ptura
!wget "https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/ner/run_ner.py"
!wget "https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/ner/utils_ner.py"

Agora é a vez da aprendizagem por transferência. No meu publicação anterior no blogueNa minha pesquisa, pré-treinei um modelo linguístico RoBERTa num corpus espanhol muito grande para prever palavras mascaradas com base no contexto em que se encontram. Ao fazê-lo, o modelo aprendeu propriedades inerentes à língua. Fiz o upload do modelo pré-treinado para o servidor do Hugging Face. Agora vamos carregar o modelo e começar a ajustá-lo para a tarefa NER.

Abaixo estão os nossos hiperparâmetros de treino.

MAX_LENGTH = 128 #@param {type: "integer"}
MODELO = "chriskhanhtran/spanberta" #@param ["chriskhanhtran/spanberta", "bert-base-multilingual-cased"]
OUTPUT_DIR = "spanberta-ner" #@param ["spanberta-ner", "bert-base-ml-ner"]
BATCH_SIZE = 32 #@param {type: "integer"}
NUM_EPOCHS = 3 #@param {type: "integer"}
SAVE_STEPS = 100 #@param {type: "integer"}
LOGGING_STEPS = 100 #@param {type: "integer"}
SEED = 42 #@param {type: "integer"}

Vamos começar a treinar.

!python3 run_ner.py 
 --data_dir ./ 
 --model_type bert 
 --labels ./labels.txt 
 --model_name_or_path $MODEL 
 --output_dir $OUTPUT_DIR 
 --max_seq_length $MAX_LENGTH 
 --num_train_epochs $NUM_EPOCHS 
 --per_gpu_train_batch_size $BATCH_SIZE 
 --save_steps $SAVE_STEPS 
 --logging_steps $LOGGING_STEPS 
 --seed $SEED 
 --do_train 
 --do_eval 
 --do_predict 
 --overwrite_output_dir

Desempenho no conjunto de desenvolvimento:

04/21/2020 02:24:31 - INFO - __main__ - ***** Eval results *****
04/21/2020 02:24:31 - INFO - __main__ - f1 = 0.831027443864822
04/21/2020 02:24:31 - INFO - __main__ - loss = 0.1004064822183894
04/21/2020 02:24:31 - INFO - __main__ - precision = 0.8207885304659498
04/21/2020 02:24:31 - INFO - __main__ - recall = 0.8415250344510795

Desempenho no conjunto de teste:

04/21/2020 02:24:48 - INFO - __main__ - ***** Eval results *****
04/21/2020 02:24:48 - INFO - __main__ - f1 = 0.8559533721898419
04/21/2020 02:24:48 - INFO - __main__ - loss = 0.06848683688204177
04/21/2020 02:24:48 - INFO - __main__ - precision = 0.845858475041141
04/21/2020 02:24:48 - INFO - __main__ - recall = 0.8662921348314607

Aqui estão os quadros tensoriais de afinação Spanberta e bert-base-multilingual-cased para 5 épocas. Podemos ver que os modelos se ajustam demasiado aos dados de treino após 3 épocas.

Relatório de classificação

Para compreender o desempenho efetivo do nosso modelo, vamos carregar as suas previsões e examinar o relatório de classificação.

def read_examples_from_file(file_path):
    """Ler palavras e etiquetas de um ficheiro de dados CoNLL-2002/2003.

    Args:
      file_path (str): caminho para o ficheiro de dados NER.

    Retorna:
      examples (dict): um dicionário com duas chaves: palavras (list of lists)
 holding words in each sequence, and rótulos (list of lists) holding
 corresponding labels.
 """
 with open(file_path, encoding="utf-8") as f:
 examples = {"words": [], "labels": []}
 words = []
 labels = []
 for line in f:
 if line.startswith("-DOCSTART-") or line == "" or line == "n":
 if words:
 examples["words"].append(words)
 examples["labels"].append(labels)
 words = []
 labels = []
 else:
 splits = line.split(" ")
 words.append(splits[0])
 if len(splits) > 1:
 labels.append(splits[-1].replace("n", ""))
 else:
 # Examples could have no label for mode = "test"
 labels.append("O")
 return examples

Ler dados e etiquetas dos ficheiros de texto em bruto:

y_true = read_examples_from_file("test.txt")["labels"]
y_pred = read_examples_from_file("spanberta-ner/test_predictions.txt")["labels"]

Imprimir o relatório de classificação:

from seqeval.metrics import classification_report as classification_report_seqeval

print(classification_report_seqeval(y_true, y_pred))
 precision recall f1-score support
 LOC 0.87 0.84 0.85 1084
 ORG 0.82 0.87 0.85 1401
 MISC 0.63 0.66 0.65 340
 PER 0.94 0.96 0.95 735
micro avg 0.84 0.86 0.85 3560
macro avg 0.84 0.86 0.85 3560

As métricas que vemos neste relatório foram concebidas especificamente para tarefas de PNL, como NER e marcação POS, em que todas as palavras de uma entidade têm de ser previstas corretamente para serem contadas como uma previsão correcta. Por conseguinte, as métricas neste relatório de classificação são muito inferiores às do relatório relatório de classificação do scikit-learn.

importar numpy as np
from sklearn.metrics import classification_report

print(relatório_classificação(np.concatenate(y_true), np.concatenate(y_pred)))
 precision recall f1-score support
 B-LOC 0.88 0.85 0.86 1084
 B-MISC 0.73 0.73 0.73 339
 B-ORG 0.87 0.91 0.89 1400
 B-PER 0.95 0.96 0.95 735
 I-LOC 0.82 0.81 0.81 325
 I-MISC 0.85 0.76 0.80 557
 I-ORG 0.89 0.87 0.88 1104
 I-PER 0.98 0.98 0.98 634
 O 1.00 1.00 1.00 45355
 accuracy 0.98 51533
 macro avg 0.89 0.87 0.88 51533
weighted avg 0.98 0.98 0.98 51533

A partir dos relatórios acima, o nosso modelo tem um bom desempenho na previsão da pessoa, localização e organização. Precisamos de mais dados para MISC entidades para melhorar o desempenho do nosso modelo nestas entidades.

CondutasPermanente

Depois de afinarmos os nossos modelos, podemos partilhá-los com a comunidade seguindo o tutorial neste página. Agora podemos começar a carregar o modelo ajustado do servidor da Hugging Face e utilizá-lo para prever entidades nomeadas em documentos espanhóis.

from transformers import pipeline, AutoModelForTokenClassification, AutoTokenizer

model = AutoModelForTokenClassification.from_pretrained("skimai/spanberta-base-cased-ner-conll02")
tokenizer = AutoTokenizer.from_pretrained("skimai/spanberta-base-cased-ner-conll02")

ner_model = pipeline('ner', model=model, tokenizer=tokenizer)

O exemplo abaixo foi obtido de La Opinión e significa "A recuperação económica dos Estados Unidos após a pandemia do coronavírus será uma questão de meses, afirmou o Secretário do Tesouro, Steven Mnuchin."

sequence = "La recuperación económica de los Estados Unidos después de la " 
 "pandemia del coronavirus será cuestión de meses, afirmó el " 
 "Secretario del Tesoro, Steven Mnuchin."
ner_model(sequence)
[{'entidade': 'B-ORG', 'pontuação': 0.9155661463737488, 'palavra': 'ĠEstados'},
 {'entidade': 'I-ORG', 'pontuação': 0.800682544708252, 'palavra': 'ĠUnidos'},
 {'entidade': 'I-MISC', 'pontuação': 0.5006815791130066, 'palavra': 'Ġcorona'},
 {'entidade': 'I-MISC', 'score': 0.510674774646759, 'word': 'virus'},
 {'entidade': 'B-PER', 'pontuação': 0.5558510422706604, 'palavra': 'ĠSecretario'},
 {'entidade': 'I-PER', 'pontuação': 0.7758238315582275, 'palavra': 'Ġdel'},
 {'entidade': 'I-PER', 'pontuação': 0.7096233367919922, 'palavra': 'ĠTesoro'},
 {'entidade': 'B-PER', 'pontuação': 0.9940345883369446, 'palavra': 'ĠSteven'},
 {'entidade': 'I-PER', 'pontuação': 0.9962581992149353, 'palavra': 'ĠM'},
 {'entidade': 'I-PER', 'pontuação': 0.9918380379676819, 'palavra': 'n'},
 {'entidade': 'I-PER', 'pontuação': 0.9848328828811646, 'palavra': 'uch'},
 {'entidade': 'I-PER', 'pontuação': 0.8513168096542358, 'palavra': 'in'}]

Parece ótimo! O modelo ajustado reconhece com sucesso todas as entidades do nosso exemplo e até reconhece o "corona vírus".

ConclusãoPermanente

O reconhecimento de identidades nomeadas pode ajudar-nos a extrair rapidamente informações importantes de textos. Por conseguinte, a sua aplicação no mundo dos negócios pode ter um impacto direto na melhoria da produtividade humana na leitura de contratos e documentos. No entanto, trata-se de uma tarefa de PNL difícil, porque o NER exige uma classificação exacta ao nível da palavra, o que faz com que abordagens simples, como o "bag-of-word", não consigam lidar com esta tarefa.

Explicámos como podemos tirar partido de um modelo BERT pré-treinado para obter rapidamente um excelente desempenho na tarefa NER para espanhol. O modelo SpanBERTa pré-treinado também pode ser ajustado para outras tarefas, como a classificação de documentos. Escrevi um tutorial detalhado para afinar o BERT para classificação de sequências e análise de sentimentos.

A seguir, nesta série, falaremos sobre o ELECTRA, uma abordagem de pré-treinamento mais eficiente para modelos de transformadores que pode atingir rapidamente o desempenho mais avançado. Fique atento!

Vamos discutir a sua ideia

    Publicações relacionadas

    Pronto para impulsionar o seu negócio

    VAMOS
    TALK
    pt_PTPortuguês