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
Publicado originalmente pelo estagiário de investigação em aprendizagem automática da Skim AI, Chris Tran.
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]:
%ptura !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]:
importar os # Descarregar e descomprimir o conjunto de dados de legendas de filmes 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 dados !mv dataset.txt dados
--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]:
# Número total de linhas e algumas linhas aleatórias !wc -l dados/dataset.txt !shuf -n 5 dados/conjunto de dados.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]:
# Obter um subconjunto das primeiras 1.000.000 linhas para treino TRAIN_SIZE = 1000000 #@param {type: "integer"} !(head -n $TRAIN_SIZE dados/dataset.txt) > dados/train.txt
Em [0]:
# Obter um subconjunto das próximas 10.000 linhas para validação VAL_SIZE = 10000 #@param {type: "integer"} !(sed -n {TRAIN_SIZE + 1},{TRAIN_SIZE + VAL_SIZE}p dados/dataset.txt) > dados/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 caminho = "dados/treino.txt" # Inicializar um tokenizador tokenizer = ByteLevelBPETokenizer() # Personalizar o treino tokenizer.train(files=path, vocab_size=50265, min_frequency=2, special_tokens=["", "", "", "", ""]) # Guardar ficheiros no disco !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]:
importar json config = { "arquitecturas": [ "RobertaForMaskedLM" ], "attention_probs_dropout_prob": 0.1, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "tamanho_intermediário": 3072, "camada_norma_eps": 1e-05, "max_position_embeddings": 514, "model_type": "roberta", "num_attention_heads": 12, "num_hidden_layers": 12, "tamanho_tipo_vocab": 1, "vocab_size": 50265 } com open("models/roberta/config.json", 'w') as fp: json.dump(config, fp) tokenizer_config = {"max_len": 512} com 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 |
Adão $epsilon$ | 1e-6 | 1e-6 |
Adão $beta_1$ | 0.9 | 0.9 |
Adão $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]:
Atualização # 22 de abril de 2020: Script run_language_modeling.py atualizado do Hugging Face. # Por favor, use esta versão antes da atualização. !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ãoConjunto 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, sepor_gpu_train_batch_size = 16
egradiente_acumulação_passos = 4
o 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]:
# Caminhos do modelo 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]:
Linha de comando do # 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} {nome_do_modelo_ou_caminho} --train_data_file {train_path} --eval_data_file {eval_path} --do_train {do_eval} {evaluate_during_training} --overwrite_output_dir -tamanho_do_bloco 512 --max_step 25 --warmup_steps 10 --learning_rate 5e-5 --por_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 -semente 42 """
Em [0]:
# Argumentos para uma formação de raiz. Desligo o evaluate_during_training, # line_by_line, should_continue e 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", "avaliar_durante_treinamento": "", "linha_por_linha": "", "should_continue": "", "nome_do_modelo_ou_caminho": "", }
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]:
%ptura %%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 .")
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!