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.



spanberta_pretraining_bert_from_scratch





Ejecutar en Google Colab

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.txtse 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ámetroBERT-baseRoBERTa-base
Longitud de la secuencia128, 512512
Tamaño del lote2568K
Tasa máxima de aprendizaje1e-46e-4
Pasos máximos1M500K
Pasos de calentamiento10K24K
Decaimiento del peso0.010.01
Adam $epsilon$1e-61e-6
Adam $beta_1$0.90.9
Adam $beta_2$0.9990.98
Recorte de degradado0.00.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.pyun 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, si per_gpu_train_batch_size = 16 y gradiente_acumulacion_pasos = 4el 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.


Hablemos de su idea

    Entradas relacionadas

    Listo para potenciar su negocio

    VAMOS
    HABLAR
    es_ESEspañol