Tutorial: Como treinar um modelo linguístico RoBERTa para espanhol

SpanBERTa: Como treinámos o modelo linguístico RoBERTa para espanhol a partir do zero

    Originally published by Skim AI's Machine Learning Research Intern, Chris Tran.



spanberta_pretraining_bert_from_scratch





Executar no Google Colab

Introdução

Os métodos de auto-treino com modelos de transformadores alcançaram o desempenho mais avançado na maioria das tarefas de PNL. No entanto, uma vez que o seu treino é computacionalmente dispendioso, a maioria dos modelos de transformadores pré-treinados atualmente disponíveis são apenas para o inglês. Por conseguinte, para melhorar o desempenho em tarefas de PNL nos nossos projectos em espanhol, a minha equipa na IA de desnatação decidiu formar um RoBERTa modelo linguístico para o espanhol a partir do zero e chamar-lhe SpanBERTa.

O SpanBERTa tem o mesmo tamanho que o RoBERTa-base. Seguimos o esquema de treino do RoBERTa para treinar o modelo em 18 GB de OSCARem 8 dias, utilizando 4 GPUs Tesla P100.

Nesta publicação do blogue, vamos percorrer um processo completo para treinar um modelo de linguagem do tipo BERT a partir do zero utilizando transformadores e tokenizadores bibliotecas da Hugging Face. Existe também um bloco de notas do Google Colab para executar diretamente os códigos deste artigo. Também pode modificar o bloco de notas em conformidade para treinar um modelo do tipo BERT para outras línguas ou afiná-lo no seu conjunto de dados personalizado.

Antes de prosseguir, gostaria de agradecer à equipa do Hugging Face por tornar os modelos de PNL de última geração acessíveis a todos.

Configuração

1. Instalar dependências

Em [0]:

%%capture
!pip uninstall -y tensorflow
!pip install transformers==2.8.0

2. Dados

Pré-treinámos o SpanBERTa em OSCARdo corpus espanhol. O tamanho total do conjunto de dados é de 150 GB e utilizámos uma parte de 18 GB para treinar.

Neste exemplo, para simplificar, utilizaremos um conjunto de dados de legendas de filmes espanhóis de OpenSubtitles. Este conjunto de dados tem um tamanho de 5,4 GB e vamos treinar num subconjunto de ~300 MB.
Em [0]:

import os
# Download and unzip movie substitle dataset
if not os.path.exists('data/dataset.txt'):
  !wget "https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/es.txt.gz" -O dataset.txt.gz
  !gzip -d dataset.txt.gz
  !mkdir data
  !mv dataset.txt data
--2020-04-06 15:53:04-- https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/es.txt.gz
Resolver object.pouta.csc.fi (object.pouta.csc.fi)... 86.50.254.18, 86.50.254.19
A ligar a object.pouta.csc.fi (object.pouta.csc.fi)|86.50.254.18|:443... ligado.
Pedido HTTP enviado, a aguardar resposta... 200 OK
Comprimento: 1859673728 (1.7G) [application/gzip]
A guardar em: 'dataset.txt.gz'

dataset.txt.gz 100%[===================>] 1.73G 17.0MB/s em 1m 46s

2020-04-06 15:54:51 (16.8 MB/s) - 'dataset.txt.gz' guardado [1859673728/1859673728]

Em [0]:

# Total number of lines and some random lines
!wc -l data/dataset.txt
!shuf -n 5 data/dataset.txt
179287150 data/dataset.txt
Sabes, pensei que tinhas mais pelotas que para me enfrentar a través de mi hermano.
Supe todos os encantamentos em todas as línguas dos Elfos, homens e Orcos.
Anteriormente em Blue Bloods:
E quero prometer que não haverá nenhum trato com Daniel Stafford.
Foi comiquísimo.

Em [0]:

# Get a subset of first 1,000,000 lines for training
TRAIN_SIZE = 1000000 #@param {type:"integer"}
!(head -n $TRAIN_SIZE data/dataset.txt) > data/train.txt

Em [0]:

# Get a subset of next 10,000 lines for validation
VAL_SIZE = 10000 #@param {type:"integer"}
!(sed -n {TRAIN_SIZE + 1},{TRAIN_SIZE + VAL_SIZE}p data/dataset.txt) > data/dev.txt

3. Treinar um Tokenizer

A implementação original do BERT utiliza um tokenizador WordPiece com um vocabulário de 32K unidades de sub-palavras. Este método, no entanto, pode introduzir tokens "desconhecidos" quando se processam palavras raras.

Nesta implementação, utilizamos um tokenizador BPE ao nível do byte com um vocabulário de 50 265 unidades de subpalavras (igual ao RoBERTa-base). A utilização do BPE ao nível do byte permite aprender um vocabulário de subpalavras de tamanho modesto que pode codificar qualquer entrada sem obter tokens "desconhecidos".

Porque ByteLevelBPETokenizer produz 2 ficheiros ["vocab.json", "merges.txt"] enquanto BertWordPieceTokenizer produz apenas 1 ficheiro vocab.txt, causará um erro se utilizarmos BertWordPieceTokenizer para carregar as saídas de um tokenizador BPE.
Em [0]:

%%time
from tokenizers import ByteLevelBPETokenizer
path = "data/train.txt"
# Initialize a tokenizer
tokenizer = ByteLevelBPETokenizer()
# Customize training
tokenizer.train(files=path,
                vocab_size=50265,
                min_frequency=2,
                special_tokens=["<s>", "<pad>", "</s>", "<unk>", "<mask>"])
# Save files to disk
!mkdir -p "models/roberta"
tokenizer.save("models/roberta")
Tempos de CPU: utilizador 1min 37s, sys: 1.02 s, total: 1min 38s
Tempo de parede: 1min 38s

Super rápido! Leva apenas 2 minutos para treinar em 10 milhões de linhas.

Traçar um modelo de linguagem a partir do zero

1. Arquitetura do modelo

O RoBERTa tem exatamente a mesma arquitetura que o BERT. As únicas diferenças são:

  • O RoBERTa utiliza um tokenizador BPE ao nível do byte com um vocabulário de subpalavras maior (50k vs 32k).
  • O RoBERTa implementa o mascaramento dinâmico de palavras e deixa de lado a tarefa de previsão da frase seguinte.
  • Os hiperparâmetros de treino do RoBERTa.

Outras configurações de arquitetura podem ser encontradas na documentação (RoBERTa, BERT).
Em [0]:

import json
config = {
    "architectures": [
        "RobertaForMaskedLM"
    ],
    "attention_probs_dropout_prob": 0.1,
    "hidden_act": "gelu",
    "hidden_dropout_prob": 0.1,
    "hidden_size": 768,
    "initializer_range": 0.02,
    "intermediate_size": 3072,
    "layer_norm_eps": 1e-05,
    "max_position_embeddings": 514,
    "model_type": "roberta",
    "num_attention_heads": 12,
    "num_hidden_layers": 12,
    "type_vocab_size": 1,
    "vocab_size": 50265
}
with open("models/roberta/config.json", 'w') as fp:
    json.dump(config, fp)
tokenizer_config = {"max_len": 512}
with open("models/roberta/tokenizer_config.json", 'w') as fp:
    json.dump(tokenizer_config, fp)

2. Treinar hiperparâmetros

Hiperparâmetro BERT-base RoBERTa-base
Comprimento da sequência 128, 512 512
Tamanho do lote 256 8K
Pico de aprendizagem 1e-4 6e-4
Passos máximos 1M 500K
Passos de aquecimento 10K 24K
Decaimento do peso 0.01 0.01
Adam $epsilon$ 1e-6 1e-6
Adam $beta_1$ 0.9 0.9
Adam $beta_2$ 0.999 0.98
Recorte de gradiente 0.0 0.0

Note-se que o tamanho do lote aquando do treino do RoBERTa é de 8000. Por conseguinte, embora a base RoBERTa tenha sido treinada para 500 mil passos, o seu custo computacional de treino é 16 vezes superior ao da base BERT. No Papel RoBERTaNa secção "Formação", mostra-se que a formação com grandes lotes melhora a perplexidade para o objetivo de modelação de linguagem mascarada, bem como a precisão da tarefa final. É possível obter um tamanho de lote maior ajustando passos_de_acumulação_de_gradiente.

Devido a restrições computacionais, seguimos o esquema de treino do BERT-base e treinámos o nosso modelo SpanBERTa utilizando 4 GPUs Tesla P100 para 200K passos em 8 dias.

3. Iniciar a formação

Vamos treinar o nosso modelo a partir do zero utilizando run_language_modeling.py, um script fornecido pela Hugging Face, que irá pré-processar, tokenizar o corpus e treinar o modelo em Modelação de linguagem mascarada tarefa. O script está optimizado para treinar num único corpus grande. Por conseguinte, se o seu conjunto de dados for grande e quiser dividi-lo para treinar sequencialmente, terá de modificar o script ou preparar-se para obter uma máquina monstruosa com muita memória.
Em [0]:

# Update April 22, 2020: Hugging Face updated run_language_modeling.py script.
# Please use this version before the update.
!wget -c https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/run_language_modeling.py
--2020-04-24 02:28:21-- https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/run_language_modeling.py
Resolvendo raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
A ligar a raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... ligado.
Pedido HTTP enviado, aguardando resposta... 200 OK
Comprimento: 34328 (34K) [texto/plano]
A guardar para: 'run_language_modeling.py'

run_language_modeli 100%[===================>] 33.52K --.-KB/s em 0.003s

2020-04-24 02:28:21 (10.1 MB/s) - 'run_language_modeling.py' guardado [34328/34328]

Argumentos importantes

  • -linha_por_linha Se linhas distintas de texto no conjunto de dados devem ser tratadas como sequências distintas. Se cada linha no seu conjunto de dados for longa e tiver ~512 tokens ou mais, deve usar esta definição. Se cada linha for curta, o pré-processamento de texto padrão concatenará todas as linhas, as tokenizará e cortará as saídas tokenizadas em blocos de 512 tokens. Também pode dividir os seus conjuntos de dados em pequenos pedaços e pré-processá-los separadamente. 3 GB de texto levarão cerca de 50 minutos para serem processados com a opção padrão Conjunto de dados de texto classe.
  • -deve_continuar Se deve continuar a partir do último ponto de controlo em output_dir.
  • --nome_do_modelo_ou_caminho O ponto de controlo do modelo para a inicialização dos pesos. Deixe Nenhum se quiser treinar um modelo a partir do zero.
  • --mlm Treinar com perda de modelação de linguagem mascarada em vez de modelação de linguagem.
  • --nome_da_configuração, --nome_do_tokenizador Configuração pré-treinada opcional e nome ou caminho do tokenizador, se não for o mesmo que model_name_or_path. Se ambos forem None, inicializa uma nova configuração.
  • --per_gpu_train_batch_size Tamanho do lote por GPU/CPU para treinamento. Escolha o maior número que pode caber nas suas GPUs. Verá um erro se o tamanho do lote for demasiado grande.
  • --gradient_accumulation_steps Número de passos de atualização a acumular antes de efetuar uma passagem para trás/atualização. Pode utilizar este truque para aumentar o tamanho do lote. Por exemplo, se por_gpu_train_batch_size = 16 e gradiente_acumulação_passos = 4o tamanho total do lote do comboio será 64.
  • --overwrite_output_dir Substitui o conteúdo do diretório de saída.
  • --no_cuda, --fp16, --fp16_opt_level Argumentos para a formação em GPU/CPU.
  • Outros argumentos são os caminhos do modelo e os hiperparâmetros de treino.

É altamente recomendável incluir o tipo de modelo (por exemplo, "roberta", "bert", "gpt2", etc.) no caminho do modelo, porque o script usa o AutoModelos para adivinhar a configuração do modelo usando a correspondência de padrões no caminho fornecido.
Em [0]:

# Model paths
MODEL_TYPE = "roberta" #@param ["roberta", "bert"]
MODEL_DIR = "models/roberta" #@param {type: "string"}
OUTPUT_DIR = "models/roberta/output" #@param {type: "string"}
TRAIN_PATH = "data/train.txt" #@param {type: "string"}
EVAL_PATH = "data/dev.txt" #@param {type: "string"}

Para este exemplo, vamos treinar apenas 25 passos numa GPU Tesla P4 fornecida pela Colab.
Em [0]:

!nvidia-smi
Seg Abr 6 15:59:35 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.64.00 Versão do driver: 418.67 Versão CUDA: 10.1
|-------------------------------+----------------------+----------------------+
| Nome da GPU Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Temp. do ventilador Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla P4 Off | 00000000:00:04.0 Off | 0 |
| N/A 31C P8 7W / 75W | 0MiB / 7611MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processos:                                                       Memória da GPU |
| PID da GPU Tipo Nome do processo Utilização
|=============================================================================|
| Nenhum processo em execução encontrado
+-----------------------------------------------------------------------------+

Em [0]:

# Command line
cmd = """python run_language_modeling.py 
    --output_dir {output_dir} 
    --model_type {model_type} 
    --mlm 
    --config_name {config_name} 
    --tokenizer_name {tokenizer_name} 
    {line_by_line} 
    {should_continue} 
    {model_name_or_path} 
    --train_data_file {train_path} 
    --eval_data_file {eval_path} 
    --do_train 
    {do_eval} 
    {evaluate_during_training} 
    --overwrite_output_dir 
    --block_size 512 
    --max_step 25 
    --warmup_steps 10 
    --learning_rate 5e-5 
    --per_gpu_train_batch_size 4 
    --gradient_accumulation_steps 4 
    --weight_decay 0.01 
    --adam_epsilon 1e-6 
    --max_grad_norm 100.0 
    --save_total_limit 10 
    --save_steps 10 
    --logging_steps 2 
    --seed 42
"""

Em [0]:

# Arguments for training from scratch. I turn off evaluate_during_training,
#   line_by_line, should_continue, and model_name_or_path.
train_params = {
    "output_dir": OUTPUT_DIR,
    "model_type": MODEL_TYPE,
    "config_name": MODEL_DIR,
    "tokenizer_name": MODEL_DIR,
    "train_path": TRAIN_PATH,
    "eval_path": EVAL_PATH,
    "do_eval": "--do_eval",
    "evaluate_during_training": "",
    "line_by_line": "",
    "should_continue": "",
    "model_name_or_path": "",
}

Se estiver a treinar numa máquina virtual, pode instalar o tensorboard para monitorizar o processo de treino. Aqui está o nosso Tensorboard para a formação do SpanBERTa.

pip install tensorboard==2.1.0
tensorboard dev upload --logdir runs

Após 200k passos, a perda atingiu 1,8 e a perplexidade atingiu 5,2.

Agora vamos começar a treinar!
Em [ ]:

!{cmd.format(**train_params)}
    04/06/2020 15:59:55 - INFO - __main__ - Criar características a partir de ficheiro de conjunto de dados em data
    04/06/2020 16:04:43 - INFO - __main__ - Salvando recursos no arquivo em cache data/roberta_cached_lm_510_train.txt
    04/06/2020 16:04:46 - INFO - __main__ - ***** A executar o treino *****
    04/06/2020 16:04:46 - INFO - __main__ - Numero de exemplos = 165994
    04/06/2020 16:04:46 - INFO - __main__ - Numero de épocas = 1
    04/06/2020 16:04:46 - INFO - __main__ - Tamanho instantâneo do lote por GPU = 4
    04/06/2020 16:04:46 - INFO - __main__ - Tamanho total do lote do trem (com paralelo, distribuído e acumulação) = 16
    04/06/2020 16:04:46 - INFO - __main__ - Passos de acumulação de gradiente = 4
    04/06/2020 16:04:46 - INFO - __main__ - Total de passos de otimização = 25
    Época: 0% 0/1 [00:00<?, ?it/s]
    Iteração:   0% 0/41499 [00:00<?, ?it/s]
    Iteração:   0% 1/41499 [00:01<13:18:02, 1.15s/it]
    Iteração:   0% 2/41499 [00:01<11:26:47, 1.01it/s]
    Iteração:   0% 3/41499 [00:02<10:10:30, 1.13it/s]
    Iteração:   0% 4/41499 [00:03<9:38:10, 1.20it/s]
    Iteração:   0% 5/41499 [00:03<8:52:44, 1.30it/s]
    Iteração:   0% 6/41499 [00:04<8:22:47, 1.38it/s]
    Iteração:   0% 7/41499 [00:04<8:00:55, 1.44it/s]
    Iteração:   0% 8/41499 [00:05<8:03:40, 1.43it/s]
    Iteração:   0% 9/41499 [00:06<7:46:57, 1.48it/s]
    Iteração:   0% 10/41499 [00:06<7:35:35, 1.52it/s]
    Iteração:   0% 11/41499 [00:07<7:28:29, 1.54it/s]
    Iteração:   0% 12/41499 [00:08<7:41:41, 1.50it/s]
    Iteração:   0% 13/41499 [00:08<7:34:28, 1.52it/s]
    Iteração:   0% 14/41499 [00:09<7:28:46, 1.54it/s]
    Iteração:   0% 15/41499 [00:10<7:23:29, 1.56it/s]
    Iteração:   0% 16/41499 [00:10<7:38:06, 1.51it/s]
    Iteração:   0% 17/41499 [00:11<7:29:13, 1.54it/s]
    Iteração:   0% 18/41499 [00:12<7:24:04, 1.56it/s]
    Iteração:   0% 19/41499 [00:12<7:21:59, 1.56it/s]
    Iteração:   0% 20/41499 [00:13<7:38:06, 1.51it/s]
    04/06/2020 16:06:23 - INFO - __main__ - ***** Avaliação em execução *****
    04/06/2020 16:06:23 - INFO - __main__ - Numero de exemplos = 156
    04/06/2020 16:06:23 - INFO - __main__ - Tamanho do lote = 4
    Avaliando: 100% 39/39 [00:08<00:00, 4.41it/s]
    04/06/2020 16:06:32 - INFO - __main__ - ***** Resultados da avaliação *****
    04/06/2020 16:06:32 - INFO - __main__ - perplexidade = tensor(6077.6812)

4. Prever palavras mascaradas

Depois de treinar o seu modelo linguístico, pode carregar e partilhar o seu modelo com a comunidade. Carregámos o nosso modelo SpanBERTa para o servidor da Hugging Face. Antes de avaliar o modelo em tarefas a jusante, vamos ver como aprendeu a preencher palavras mascaradas com um contexto.
Em [0]:

%%capture
%%time
from transformers import pipeline
fill_mask = pipeline(
    "fill-mask",
    model="chriskhanhtran/spanberta",
    tokenizer="chriskhanhtran/spanberta"
)

Escolho uma frase do artigo da Wikipédia sobre a COVID-19.

A frase original é "Lavar frequentemente as mãos com água e jabão," que significa "Lavar frequentemente as mãos com água e sabão."

A palavra mascarada é "jabón" (sabão) e as 5 principais previsões são sabão, sal, vapor, limão e vinagre. É interessante o facto de o modelo aprender, de alguma forma, que devemos lavar as mãos com coisas que podem matar bactérias ou que contêm ácido.
Em [0]:

fill_mask("Lavarse frecuentemente las manos con agua y <mask>.")

Saída[0]:

[{'score': 0.6469631195068359,
  'sequence': ' Lavarse frecuentemente las manos con agua y jabón.',
  'token': 18493},
 {'score': 0.06074320897459984,
  'sequence': ' Lavarse frecuentemente las manos con agua y sal.',
  'token': 619},
 {'score': 0.029787985607981682,
  'sequence': ' Lavarse frecuentemente las manos con agua y vapor.',
  'token': 11079},
 {'score': 0.026410052552819252,
  'sequence': ' Lavarse frecuentemente las manos con agua y limón.',
  'token': 12788},
 {'score': 0.017029203474521637,
  'sequence': ' Lavarse frecuentemente las manos con agua y vinagre.',
  'token': 18424}]

Conclusão

Já explicámos como treinar um modelo de linguagem BERT para espanhol a partir do zero e vimos que o modelo aprendeu as propriedades da linguagem ao tentar prever palavras mascaradas com base num contexto. Também pode seguir este artigo para afinar um modelo pré-treinado do tipo BERT no seu conjunto de dados personalizado.

Em seguida, implementaremos os modelos pré-treinados em tarefas a jusante, incluindo classificação de sequências, NER, marcação POS e NLI, bem como compararemos o desempenho do modelo com alguns modelos não-BERT.

Fique atento às nossas próximas publicações!

Vamos discutir a sua ideia

    Publicações relacionadas

    Pronto para impulsionar o seu negócio

    VAMOS
    TALK
    pt_PTPortuguês