Tutorial: Cómo entrenar un modelo lingüístico RoBERTa para el español
SpanBERTa: Cómo entrenamos desde cero el modelo lingüístico RoBERTa para el español
Publicado originalmente por Chris Tran, becario de investigación sobre aprendizaje automático de Skim AI.
Introducción¶
Los métodos de autoentrenamiento con modelos de transformada han alcanzado un rendimiento puntero en la mayoría de las tareas de PNL. Sin embargo, dado que su entrenamiento es costoso desde el punto de vista computacional, la mayoría de los modelos transformadores preentrenados disponibles actualmente son solo para el inglés. Por eso, para mejorar el rendimiento en tareas de PLN en nuestros proyectos sobre español, mi equipo de Desnatado AI decidió formar a un RoBERTa modelo lingüístico para el español desde cero y llamarlo SpanBERTa.
SpanBERTa tiene el mismo tamaño que RoBERTa-base. Seguimos el esquema de entrenamiento de RoBERTa para entrenar el modelo en 18 GB de OSCARen 8 días utilizando 4 GPU Tesla P100.
En esta entrada del blog, vamos a recorrer un proceso de principio a fin para entrenar un modelo de lenguaje similar a BERT desde cero utilizando transformadores
y tokenizadores
bibliotecas de Hugging Face. También hay un cuaderno de Google Colab para ejecutar directamente los códigos de este artículo. También puedes modificar el cuaderno en consecuencia para entrenar un modelo similar a BERT para otros idiomas o afinarlo en tu conjunto de datos personalizado.
Antes de continuar, quiero expresar mi más sincero agradecimiento al equipo de Hugging Face por hacer accesibles a todo el mundo los modelos de PNL más avanzados.
Configurar¶
1. Instalar dependencias¶
En [0]:
%pture pip uninstall -y tensorflow pip install transformers==2.8.0
2. Datos¶
Preentrenamos SpanBERTa en OSCARen español. El tamaño total del conjunto de datos es de 150 GB y hemos utilizado una parte de 18 GB para el entrenamiento.
En este ejemplo, para simplificar, utilizaremos un conjunto de datos de subtítulos de películas españolas de OpenSubtitles. Este conjunto de datos tiene un tamaño de 5,4 GB y nos entrenaremos con un subconjunto de ~300 MB.
En [0]:
importar os # Descarga y descomprime el conjunto de datos de subtítulos de películas 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 datos !mv conjuntodatos.txt datos
--2020-04-06 15:53:04-- https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/es.txt.gz Resolviendo object.pouta.csc.fi (object.pouta.csc.fi)... 86.50.254.18, 86.50.254.19 Conectando con object.pouta.csc.fi (object.pouta.csc.fi)|86.50.254.18|:443... conectado. Petición HTTP enviada, esperando respuesta... 200 OK Longitud: 1859673728 (1.7G) [application/gzip] Guardando en: 'dataset.txt.gz' dataset.txt.gz 100%[===================>] 1.73G 17.0MB/s en 1m 46s 2020-04-06 15:54:51 (16.8 MB/s) - 'dataset.txt.gz' guardado [1859673728/1859673728]
En [0]:
# Número total de líneas y algunas líneas aleatorias !wc -l data/dataset.txt !shuf -n 5 data/dataset.txt
179287150 data/dataset.txt Sabes, pensé que tenías más pelotas que para enfrentarme a través de mi hermano. Supe todos los encantamientos en todas las lenguas de los Elfos hombres y Orcos. Anteriormente en Blue Bloods: Y quiero que prometas que no habrá ningún trato con Daniel Stafford. Fue comiquísimo.
En [0]:
# Obtener un subconjunto de las primeras 1.000.000 líneas para el entrenamiento TAMAÑO_ENTRENAMIENTO = 1000000 #@param {type: "entero"} !(head -n $TRAIN_SIZE data/dataset.txt) > data/train.txt
En [0]:
# Obtener un subconjunto de las 10.000 líneas siguientes para su validación VAL_SIZE = 10000 #@param {type: "entero"} !(sed -n {TRAIN_SIZE + 1},{TRAIN_SIZE + VAL_SIZE}p data/dataset.txt) > data/dev.txt
3. Entrenar un tokenizador¶
La implementación original de BERT utiliza un tokenizador WordPiece con un vocabulario de 32K unidades de subpalabras. Sin embargo, este método puede introducir tokens "desconocidos" al procesar palabras poco frecuentes.
En esta implementación, utilizamos un tokenizador BPE a nivel de byte con un vocabulario de 50.265 unidades de subpalabra (igual que RoBERTa-base). El uso de BPE a nivel de byte permite aprender un vocabulario de subpalabras de tamaño modesto que puede codificar cualquier entrada sin obtener tokens "desconocidos".
Porque ByteLevelBPETokenizer
produce 2 archivos ["vocab.json", "merges.txt"]
mientras que BertWordPieceTokenizer
sólo produce 1 archivo vocab.txt
se producirá un error si utilizamos BertWordPieceTokenizer
para cargar salidas de un tokenizador BPE.
En [0]:
%%time from tokenizers import ByteLevelBPETokenizer ruta = "datos/entrenamiento.txt" # Inicializar un tokenizador tokenizer = ByteLevelBPETokenizer() # Personalizar el entrenamiento tokenizer.train(archivos=ruta, vocab_size=50265, min_frequency=2, special_tokens=["", "", "", "", ""]) # Guarda los archivos en el disco !mkdir -p "modelos/roberta" tokenizer.save("modelos/roberta")
Tiempos de CPU: usuario 1min 37s, sys: 1.02 s, total: 1min 38s Tiempo de muro: 1min 38s
¡Súper rápido! Sólo se tarda 2 minutos en entrenar 10 millones de líneas.
Creación de un modelo lingüístico desde cero¶
1. Arquitectura del modelo¶
RoBERTa tiene exactamente la misma arquitectura que BERT. Las únicas diferencias son:
- RoBERTa utiliza un tokenizador BPE de nivel de byte con un vocabulario de subpalabras más amplio (50.000 frente a 32.000).
- RoBERTa implementa el enmascaramiento dinámico de palabras y abandona la tarea de predicción de la siguiente frase.
- Hiperparámetros de entrenamiento de RoBERTa.
Encontrará otras configuraciones de arquitectura en la documentación (RoBERTa, BERT).
En [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, "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. Hiperparámetros de entrenamiento¶
Hiperparámetro | BERT-base | RoBERTa-base |
---|---|---|
Longitud de la secuencia | 128, 512 | 512 |
Tamaño del lote | 256 | 8K |
Tasa máxima de aprendizaje | 1e-4 | 6e-4 |
Pasos máximos | 1M | 500K |
Pasos de calentamiento | 10K | 24K |
Decaimiento del 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 degradado | 0.0 | 0.0 |
Obsérvese que el tamaño del lote cuando se entrena RoBERTa es de 8000. Por lo tanto, aunque RoBERTa-base se entrenó para 500.000 pasos, su coste computacional de entrenamiento es 16 veces superior al de BERT-base. En el Papel RoBERTase demuestra que el entrenamiento con lotes grandes mejora la perplejidad para el objetivo de modelado de lenguaje enmascarado, así como la precisión al final de la tarea. Se puede obtener un tamaño de lote mayor ajustando gradiente_acumulacion_pasos
.
Debido a las limitaciones computacionales, seguimos el esquema de entrenamiento de BERT-base y entrenamos nuestro modelo SpanBERTa utilizando 4 GPU Tesla P100 durante 200.000 pasos en 8 días.
3. Iniciar la formación¶
Entrenaremos nuestro modelo desde cero utilizando run_language_modeling.py
un script proporcionado por Hugging Face, que preprocesará, tokenizará el corpus y entrenará el modelo en Modelado lingüístico enmascarado tarea. El script está optimizado para entrenar en un único corpus grande. Por lo tanto, si tu conjunto de datos es grande y quieres dividirlo para entrenarlo secuencialmente, tendrás que modificar el script, o estar preparado para conseguir una máquina monstruosa con mucha memoria.
En [0]:
# Actualización 22 de abril de 2020: Cara Abrazada actualizó el script run_language_modeling.py. # Por favor, utilice esta versión antes de la actualización. !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 Resolviendo raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ... Conectando con raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... conectado. Solicitud HTTP enviada, esperando respuesta... 200 OK Longitud: 34328 (34K) [text/plain] Guardando en: 'run_language_modeling.py' run_language_modeli 100%[===================>] 33.52K --.-KB/s en 0.003s 2020-04-24 02:28:21 (10.1 MB/s) - 'run_language_modeling.py' guardado [34328/34328]
Argumentos importantes
--línea_por_línea
Si las distintas líneas de texto del conjunto de datos deben tratarse como secuencias distintas. Si cada línea de su conjunto de datos es larga y tiene ~512 tokens o más, debería utilizar esta opción. Si cada línea es corta, el preprocesamiento de texto predeterminado concatenará todas las líneas, las tokenizará y dividirá las salidas tokenizadas en bloques de 512 tokens. También puede dividir sus conjuntos de datos en trozos pequeños y preprocesarlos por separado. 3 GB de texto tardarán unos 50 minutos en procesarse con la opción predeterminada.Conjunto de datos de texto
clase.--debe_continuar
Si continuar desde el último punto de control en output_dir.--nombre_del_modelo_o_ruta
El punto de control del modelo para la inicialización de los pesos. Deje Ninguno si desea entrenar un modelo desde cero.--mlm
Entrene con pérdida por modelado de lenguaje enmascarado en lugar de modelado de lenguaje.--config_name, --tokenizer_name
Configuración preentrenada opcional y nombre del tokenizador o ruta si no es el mismo que nombre_modelo_o_ruta. Si ambos son None, se inicializa una nueva configuración.--per_gpu_train_batch_size
Tamaño del lote por GPU/CPU para el entrenamiento. Elija el mayor número que quepa en sus GPUs. Aparecerá un error si el tamaño del lote es demasiado grande.--gradient_accumulation_steps
Número de pasos de actualización que se acumulan antes de realizar una pasada hacia atrás/actualización. Puede utilizar este truco para aumentar el tamaño del lote. Por ejemplo, siper_gpu_train_batch_size = 16
ygradiente_acumulacion_pasos = 4
el tamaño total del lote de trenes será de 64.--overwrite_output_dir
Sobrescribir el contenido del directorio de salida.--no_cuda, --fp16, --fp16_opt_level
Argumentos para el entrenamiento en GPU/CPU.- Otros argumentos son las trayectorias del modelo y los hiperparámetros de entrenamiento.
Se recomienda encarecidamente incluir el tipo de modelo (por ejemplo, "roberta", "bert", "gpt2", etc.) en la ruta del modelo, ya que el script utiliza el comando AutoModels
para adivinar la configuración del modelo utilizando la concordancia de patrones en la ruta proporcionada.
En [0]:
# Rutas del 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: "cadena"}
Para este ejemplo, entrenaremos sólo 25 pasos en una GPU Tesla P4 proporcionada por Colab.
En [0]:
nvidia-smi
lun 6 abr 15:59:35 2020 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 440.64.00 Versión del controlador: 418.67 Versión de CUDA: 10.1 |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M Bus-Id Disp.A Volatile Uncorr. ECC | Ventilador Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 Tesla P4 Apagado | 00000000:00:04.0 Apagado | 0 | | N/A 31C P8 7W / 75W | 0MiB / 7611MiB | 0% Por defecto | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Procesos: Memoria GPU | GPU PID Tipo Nombre del proceso Uso |=============================================================================| | No se han encontrado procesos en ejecución. +-----------------------------------------------------------------------------+
En [0]:
# Línea de comandos cmd = """python ejecutar_lengua_modelo.py --dir_salida {dir_salida} --tipo_modelo {tipo_modelo} --mlm --config_name {nombre_config} --nombre_tokenizador {nombre_tokenizador} {línea_por_línea} {debe_continuar} {nombre_del_modelo_o_ruta} --archivo_datos_entrenamiento {ruta_entrenamiento} --eval_data_file {eval_path} --do_train {hacer_evaluación} {evaluar_durante_el_entrenamiento} --overwrite_output_dir -tamaño_bloque 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 """
En [0]:
# Argumentos para la formación desde cero. Desactivo evaluate_during_training, # line_by_line, should_continue, y 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": "", }
Si estás entrenando en una máquina virtual, puedes instalar tensorboard para monitorizar el proceso de entrenamiento. Aquí está nuestro Tensorboard para la formación de SpanBERTa.
pip install tensorboard==2.1.0 tensorboard dev upload --logdir ejecuta
Tras 200.000 pasos, la pérdida alcanzó 1,8 y la perplejidad, 5,2.
¡Ahora empecemos a entrenar!
En [ ]:
!{cmd.format(**train_params)}
04/06/2020 15:59:55 - INFO - __main__ - Creando características desde el fichero data en data
04/06/2020 16:04:43 - INFO - __main__ - Guardando características en el archivo data/roberta_cached_lm_510_train.txt
04/06/2020 16:04:46 - INFO - __main__ - ***** Ejecutando entrenamiento *****
04/06/2020 16:04:46 - INFO - __main__ - Num ejemplos = 165994
04/06/2020 16:04:46 - INFO - __main__ - Num Epochs = 1
04/06/2020 16:04:46 - INFO - __main__ - Tamaño de lote instantáneo por GPU = 4
04/06/2020 16:04:46 - INFO - __main__ - Tamaño total del lote del tren (con paralelo, distribuido y acumulación) = 16
04/06/2020 16:04:46 - INFO - __main__ - Pasos de acumulación de gradiente = 4
04/06/2020 16:04:46 - INFO - __main__ - Total de pasos de optimización = 25
Época: 0% 0/1 [00:00<?, ?it/s]
Iteración: 0% 0/41499 [00:00<?, ?it/s]
Iteración: 0% 1/41499 [00:01<13:18:02, 1.15s/it]
Iteración: 0% 2/41499 [00:01<11:26:47, 1.01it/s]
Iteración: 0% 3/41499 [00:02<10:10:30, 1.13it/s]
Iteración: 0% 4/41499 [00:03<9:38:10, 1.20it/s]
Iteración: 0% 5/41499 [00:03<8:52:44, 1.30it/s]
Iteración: 0% 6/41499 [00:04<8:22:47, 1.38it/s]
Iteración: 0% 7/41499 [00:04<8:00:55, 1.44it/s]
Iteración: 0% 8/41499 [00:05<8:03:40, 1.43it/s]
Iteración: 0% 9/41499 [00:06<7:46:57, 1.48it/s]
Iteración: 0% 10/41499 [00:06<7:35:35, 1.52it/s]
Iteración: 0% 11/41499 [00:07<7:28:29, 1.54it/s]
Iteración: 0% 12/41499 [00:08<7:41:41, 1.50it/s]
Iteración: 0% 13/41499 [00:08<7:34:28, 1.52it/s]
Iteración: 0% 14/41499 [00:09<7:28:46, 1.54it/s]
Iteración: 0% 15/41499 [00:10<7:23:29, 1.56it/s]
Iteración: 0% 16/41499 [00:10<7:38:06, 1.51it/s]
Iteración: 0% 17/41499 [00:11<7:29:13, 1.54it/s]
Iteración: 0% 18/41499 [00:12<7:24:04, 1.56it/s]
Iteración: 0% 19/41499 [00:12<7:21:59, 1.56it/s]
Iteración: 0% 20/41499 [00:13<7:38:06, 1.51it/s]
04/06/2020 16:06:23 - INFO - __main__ - ***** Ejecutando evaluación *****
04/06/2020 16:06:23 - INFO - __main__ - Num ejemplos = 156
04/06/2020 16:06:23 - INFO - __main__ - Tamaño del lote = 4
Evaluando: 100% 39/39 [00:08<00:00, 4.41it/s]
04/06/2020 16:06:32 - INFO - __main__ - ***** Resultados de la evaluación *****
04/06/2020 16:06:32 - INFO - __main__ - perplejidad = tensor(6077.6812)
4. Predecir palabras enmascaradas¶
Después de entrenar tu modelo lingüístico, puedes subirlo y compartirlo con la comunidad. Hemos subido nuestro modelo SpanBERTa al servidor de Hugging Face. Antes de evaluar el modelo en tareas posteriores, veamos cómo ha aprendido a rellenar palabras enmascaradas dado un contexto.
En [0]:
%ptura %%tiempo from transformadores import tubería fill_mask = tubería( "fill-mask model="chriskhanhtran/spanberta", tokenizer="chriskhanhtran/spanberta" )
Elijo una frase del artículo de Wikipedia sobre COVID-19.
La frase original es "Lavarse frecuentemente las manos con agua y jabón," que significa "Lávese frecuentemente las manos con agua y jabón.“
La palabra enmascarada es "jabón y las 5 mejores predicciones son jabón, sal, vapor, limón y vinagre. Es interesante que el modelo aprenda de alguna manera que debemos lavarnos las manos con cosas que puedan matar bacterias o que contengan ácido.
En [0]:
fill_mask("Lavarse frecuentemente las manos con agua y .")
Out[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}]
Conclusión¶
Hemos repasado cómo entrenar un modelo lingüístico BERT para el español desde cero y hemos visto que el modelo ha aprendido propiedades del idioma intentando predecir palabras enmascaradas dado un contexto. También puedes seguir este artículo para perfeccionar un modelo tipo BERT preentrenado en tu conjunto de datos personalizado.
A continuación, aplicaremos los modelos preentrenados a tareas posteriores, como la clasificación de secuencias, el NER, el etiquetado POS y el NLI, y compararemos el rendimiento del modelo con algunos modelos que no son BERT.
Permanezca atento a nuestras próximas publicaciones.