Entrenamiento BERT en Vertex AI / Habr

Este material está dedicado a acelerar la capacitación de modelos mediante trabajos sin servidor. En particular, hablaremos sobre cómo ejecutar entrenamiento usando Pytorch, GPU y la plataforma Vertex.

EN anterior En el artículo mencioné que la capacitación local de modelos enormes no siempre es buena en condiciones de recursos limitados. A veces lo hacen simplemente porque no hay otra opción. Pero también sucede que un especialista en ML tiene a su disposición un determinado proveedor de nube, como Google Cloud Platform, cuyas herramientas pueden acelerar significativamente el entrenamiento de modelos. A saber:

  • El proveedor puede proporcionar acceso a las últimas máquinas cuyas características (memoria, GPU, etc.) satisfagan las necesidades del cliente.

  • El proveedor puede permitir que se ejecuten varios trabajos de capacitación simultáneamente, después de lo cual puede seleccionar el modelo mejor entrenado de lo que ha producido.

No hace falta decir que llevar el aprendizaje a la nube hará la vida más fácil para las computadoras normales. Un día realicé una tarea de capacitación de modelos de una semana en mi computadora portátil y me fui de vacaciones. Cuando regresé, el panel táctil literalmente saltó de la carcasa debido al sobrecalentamiento de la batería.

Veremos un ejemplo específico de entrenamiento adicional del modelo BERT utilizando comentarios en las redes sociales. El modelo realiza un análisis de sentimiento del texto. Como veremos, entrenar dichos modelos en una CPU es un enfoque subóptimo para resolver un problema que requiere mucho tiempo. Luego hablaremos sobre cómo puedes usar Google Cloud Platform para acelerar el aprendizaje de GPU por solo 60 centavos.

¿Qué es BERT?

BERT significa Representaciones de codificador bidireccional de Transformers. Google hizo que este modelo fuera de código abierto en 2018. BERT se utiliza principalmente para resolver tareas de PNL porque ha sido entrenado para comprender el significado de oraciones y brinda acceso a potentes incrustaciones de palabras. La diferencia entre BERT y otros modelos, como Word2Vec y Glove, es que utiliza transformadores para el procesamiento de texto. Los transformadores (puede consultar mi artículo anterior para obtener más detalles sobre ellos) son una familia de arquitecturas de redes neuronales que, algo similares a los RNN, pueden procesar secuencias en ambas direcciones. Esto les da, por ejemplo, la capacidad de capturar el contexto que rodea a una palabra.

¿Qué es el análisis de sentimiento de texto?

El Análisis de Sentimiento es una tarea específica en el campo de la PNL, cuya finalidad es clasificar textos en categorías relacionadas con su sentimiento. La tonalidad suele definirse como positiva, negativa y neutral. Este tipo de análisis de texto se utiliza muy a menudo para analizar respuestas a diversas preguntas, publicaciones en redes sociales, reseñas de productos, etc.

Formación adicional de BERT utilizando datos de redes sociales.

Cargando y preparando datos

El conjunto de datos que utilizaremos es de Kaggle. Puedes descargarlo Aquí (tiene licencia CC BY 4.0). En mis experimentos, me limité a los conjuntos de datos de Facebook y Twitter.

El siguiente fragmento de código toma los archivos csv y los divide en fragmentos, formando tres archivos en una ubicación determinada que contienen conjuntos de datos para entrenar, validar y probar el modelo. Recomiendo guardar estos archivos en Google Cloud Storage.

Puede ejecutar el script con el siguiente comando:

python make_splits --output-dir gs://your-bucket/

Script de código de votación:

import pandas as pd
import argparse
import numpy as np
from sklearn.model_selection import train_test_split


def make_splits(output_dir):
    df=pd.concat((
        pd.read_csv("data/farisdurrani/twitter_filtered.csv"),
        pd.read_csv("data/farisdurrani/facebook_filtered.csv")
    ))
    df = df.dropna(subset=('sentiment'), axis=0)
    df('Target') = df('sentiment').apply(lambda x: 1 if x==0 else np.sign(x)+1).astype(int)

    df_train, df_ = train_test_split(df, stratify=df('Target'), test_size=0.2)
    df_eval, df_test = train_test_split(df_, stratify=df_('Target'), test_size=0.5)

    print(f"Files will be saved in {output_dir}")
    df_train.to_csv(output_dir + "/train.csv", index=False)
    df_eval.to_csv(output_dir + "/eval.csv", index=False)
    df_test.to_csv(output_dir + "/test.csv", index=False)

    print(f"Train : ({df_train.shape}) samples")
    print(f"Val : ({df_eval.shape}) samples")
    print(f"Test : ({df_test.shape}) samples")


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--output-dir')
    args, _ = parser.parse_known_args()
    make_splits(args.output_dir)

Los datos resultantes deberían verse así:

https://miro.medium.com/v2/resize:fit:542/1*Iwp1_8fVAbzTVs4HmkFFIQ.png

Datos utilizados en el experimento.

Usando un pequeño modelo BERT previamente entrenado

En nuestro experimento, utilizaremos una versión ligera del modelo BERT: BERT‑Tiny. Este modelo ya ha sido entrenado con una gran cantidad de datos. Pero estos no son necesariamente datos de las redes sociales, y no es en absoluto un hecho que el modelo haya sido entrenado con el fin de analizar el sentimiento de los textos. Es por eso que entrenaremos más el modelo.

BERT-Tiny contiene solo 2 capas con una dimensión de 128 elementos. Se puede ver una lista completa de modelos similares. Aquí – en caso de que decidas elegir un modelo más grande.

Comencemos creando un archivo. main.pyen el que están conectados todos los módulos necesarios:

import pandas as pd
import argparse
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text as text
import logging
import os
os.environ("TFHUB_MODEL_LOAD_FORMAT") = "UNCOMPRESSED"

def train_and_evaluate(**params):
    pass
    # будем это дополнять по мере продвижения по статье

Además, agregaremos los requisitos del paquete a un archivo separado. requirements.txt:

transformers==4.40.1
torch==2.2.2
pandas==2.0.3
scikit-learn==1.3.2
gcsfs

Ahora, para entrenar el modelo, cargaremos dos entidades:

  • Un tokenizador, que es responsable de dividir los datos de entrada de texto en tokens en los que se entrenó el modelo BERT.

  • El modelo en sí.

Ambos se pueden encontrar en AbrazosCara. También se pueden cargar en Cloud Storage. Eso es exactamente lo que hice. Como resultado, sus comandos de carga se ven así:

# Загрузка предварительно обученного токенизатора и модели BERT
tokenizer = BertTokenizer.from_pretrained('models/bert_uncased_L-2_H-128_A-2/vocab.txt')
model = BertModel.from_pretrained('models/bert_uncased_L-2_H-128_A-2')

Ahora agreguemos lo siguiente a nuestro archivo:

class SentimentBERT(nn.Module):
    def __init__(self, bert_model):
        super().__init__()
        self.bert_module = bert_model
        self.dropout = nn.Dropout(0.1)
        self.final = nn.Linear(in_features=128, out_features=3, bias=True) 
        
        # Раскомментируйте, если хотите лишь переобучить определённые слои.
        # self.bert_module.requires_grad_(False)
        # for param in self.bert_module.encoder.parameters():
        #     param.requires_grad = True
        
    def forward(self, inputs):
        ids, mask, token_type_ids = inputs('ids'), inputs('mask'), inputs('token_type_ids')
        # print(ids.size(), mask.size(), token_type_ids.size())
        x = self.bert_module(ids, mask, token_type_ids)
        x = self.dropout(x('pooler_output'))
        out = self.final(x)
        return out

Ahora vayamos más despacio por un momento. Si hablamos de utilizar un modelo existente en un proyecto para el que no fue diseñado originalmente, entonces existen varias opciones:

  • Transferir aprendizaje: los pesos del modelo se “congelan” y se utilizan como medio para la “extracción de características”. Luego puede adjuntar capas adicionales al modelo, después de sus propias capas. Este enfoque se utiliza a menudo en el campo de la visión por computadora, donde modelos como VGG, Xception y otros se pueden utilizar para resolver diferentes problemas entrenando su propio modelo en pequeños conjuntos de datos.

  • Entrenamiento adicional: “descongela” todos los pesos del modelo o una parte de ellos, después de lo cual el modelo se entrena adicionalmente con su propio conjunto de datos. Este es el enfoque que uno suele adoptar cuando entrena sus propios modelos de lenguaje grandes.

Se pueden encontrar detalles sobre el aprendizaje por transferencia y la capacitación adicional. Aquí.

En nuestro caso, se decidió “descongelar” todo el modelo. Es muy posible que decida congelar una o más capas de su modelo BERT previamente entrenado y evaluar el impacto de este paso en el rendimiento del modelo resultante.

Lo más importante aquí es agregar una capa completamente conectada después del módulo BERT para “vincular” este modelo a nuestra tarea de clasificación, es decir, a una capa que contiene tres bloques. Esto nos permitirá reutilizar el modelo BERT previamente entrenado y adaptarlo para resolver nuestro problema.

Creando cargadores de datos

Para crear cargadores de datos, necesitaremos el tokenizador que se cargó anteriormente. Toma una cadena como entrada y produce varios grupos de datos de salida, entre los que se pueden encontrar tokens (en nuestro caso son input_ids):

https://miro.medium.com/v2/resize:fit:700/1*0ZLt7XaVbHK7GqITy6cDMg.png

Salida del tokenizador

El tokenizador BERT es, en cierto sentido, un mecanismo especial. De lo que devuelve, los tokens son los que más nos interesan input_ids. Estos son los tokens que se utilizaron para codificar la oración de entrada. Pueden ser palabras o partes de palabras. Por ejemplo, la palabra “mirar” puede estar compuesta por dos tokens: “mirar” y “##ing”.

Ahora creemos un módulo de carga que procesará conjuntos de datos:

class BertDataset(Dataset):
    def __init__(self, df, tokenizer, max_length=100):
        super(BertDataset, self).__init__()
        self.df=df
        self.tokenizer=tokenizer
        self.target=self.df('Target')
        self.max_length=max_length
        
    def __len__(self):
        return len(self.df)
    
    def __getitem__(self, idx):
        
        X = self.df('bodyText').values(idx)
        y = self.target.values(idx)
        
        inputs = self.tokenizer.encode_plus(
            X,
            pad_to_max_length=True,
            add_special_tokens=True,
            return_attention_mask=True,
            max_length=self.max_length,
        )
        ids = inputs("input_ids")
        token_type_ids = inputs("token_type_ids")
        mask = inputs("attention_mask")

        x = {
            'ids': torch.tensor(ids, dtype=torch.long).to(DEVICE),
            'mask': torch.tensor(mask, dtype=torch.long).to(DEVICE),
            'token_type_ids': torch.tensor(token_type_ids, dtype=torch.long).to(DEVICE)
            }
        y = torch.tensor(y, dtype=torch.long).to(DEVICE)
        
        return x, y

Escribir el guión principal para entrenar el modelo.

En primer lugar, definimos dos funciones que se encargarán de entrenar el modelo y evaluar su calidad:

def train(epoch, model, dataloader, loss_fn, optimizer, max_steps=None):
    model.train()
    total_acc, total_count = 0, 0
    log_interval = 50
    start_time = time.time()

    for idx, (inputs, label) in enumerate(dataloader):
        optimizer.zero_grad()
        predicted_label = model(inputs)
        
        loss = loss_fn(predicted_label, label)
        loss.backward()
        optimizer.step()
        
        total_acc += (predicted_label.argmax(1) == label).sum().item()
        total_count += label.size(0)
        
        if idx % log_interval == 0:
            elapsed = time.time() - start_time
            print(
                "Epoch {:3d} | {:5d}/{:5d} batches "
                "| accuracy {:8.3f} | loss {:8.3f} ({:.3f}s)".format(
                    epoch, idx, len(dataloader), total_acc / total_count, loss.item(), elapsed
                )
            )
            total_acc, total_count = 0, 0
            start_time = time.time()

        if max_steps is not None:
            if idx == max_steps:
                return {'loss': loss.item(), 'acc': total_acc / total_count}
    
    return {'loss': loss.item(), 'acc': total_acc / total_count}


def evaluate(model, dataloader, loss_fn):
    model.eval()
    total_acc, total_count = 0, 0

    with torch.no_grad():
        for idx, (inputs, label) in enumerate(dataloader):
            predicted_label = model(inputs)
            loss = loss_fn(predicted_label, label)
            total_acc += (predicted_label.argmax(1) == label).sum().item()
            total_count += label.size(0)

    return {'loss': loss.item(), 'acc': total_acc / total_count}

Estamos cada vez más cerca de equipar nuestro script principal con toda la funcionalidad necesaria. Juntemos todo lo que ya tenemos:

  • Clase BertDatasetresponsable de cargar los datos.

  • Modelo SentimentBERTque toma el modelo Tiny-BERT y le agrega una capa adicional, enfocada en resolver nuestro problema.

  • Funciones train() y eval()responsable de entrenar y evaluar el modelo.

  • Función train_and_eval()que une todo en sí mismo.

Para poder ejecutar un script con argumentos, usaremos argparse. Estos argumentos suelen ser archivos de datos para entrenar, validar y probar el modelo. Su uso le permite ejecutar el modelo utilizando cualquier conjunto de datos. Los argumentos incluyen la ruta donde se guardará el modelo y los parámetros relevantes para entrenar el modelo.

import pandas as pd
import time
import torch.nn as nn
import torch
import logging
import numpy as np
import argparse

from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel

logging.basicConfig(format="%(asctime)s (%(levelname)s): %(message)s", level=logging.DEBUG)
logging.getLogger().setLevel(logging.INFO)

# --- КОНСТАНТЫ ---
BERT_MODEL_NAME = 'small_bert/bert_en_uncased_L-2_H-128_A-2'

if torch.cuda.is_available():
    logging.info(f"GPU: {torch.cuda.get_device_name(0)} is available.")
    DEVICE = torch.device('cuda')
else:
    logging.info("No GPU available. Training will run on CPU.")
    DEVICE = torch.device('cpu')

# --- Подготовка и токенизация данных ---
class BertDataset(Dataset):
    def __init__(self, df, tokenizer, max_length=100):
        super(BertDataset, self).__init__()
        self.df=df
        self.tokenizer=tokenizer
        self.target=self.df('Target')
        self.max_length=max_length
        
    def __len__(self):
        return len(self.df)
    
    def __getitem__(self, idx):
        
        X = self.df('bodyText').values(idx)
        y = self.target.values(idx)
        
        inputs = self.tokenizer.encode_plus(
            X,
            pad_to_max_length=True,
            add_special_tokens=True,
            return_attention_mask=True,
            max_length=self.max_length,
        )
        ids = inputs("input_ids")
        token_type_ids = inputs("token_type_ids")
        mask = inputs("attention_mask")

        x = {
            'ids': torch.tensor(ids, dtype=torch.long).to(DEVICE),
            'mask': torch.tensor(mask, dtype=torch.long).to(DEVICE),
            'token_type_ids': torch.tensor(token_type_ids, dtype=torch.long).to(DEVICE)
            }
        y = torch.tensor(y, dtype=torch.long).to(DEVICE)
        
        return x, y

# --- Определение модели ---
class SentimentBERT(nn.Module):
    def __init__(self, bert_model):
        super().__init__()
        self.bert_module = bert_model
        self.dropout = nn.Dropout(0.1)
        self.final = nn.Linear(in_features=128, out_features=3, bias=True) 
        
    def forward(self, inputs):
        ids, mask, token_type_ids = inputs('ids'), inputs('mask'), inputs('token_type_ids')
        x = self.bert_module(ids, mask, token_type_ids)
        x = self.dropout(x('pooler_output'))
        out = self.final(x)
        return out

# --- Цикл обучения ---
def train(epoch, model, dataloader, loss_fn, optimizer, max_steps=None):
    model.train()
    total_acc, total_count = 0, 0
    log_interval = 50
    start_time = time.time()

    for idx, (inputs, label) in enumerate(dataloader):
        optimizer.zero_grad()
        predicted_label = model(inputs)
        
        loss = loss_fn(predicted_label, label)
        loss.backward()
        optimizer.step()
        
        total_acc += (predicted_label.argmax(1) == label).sum().item()
        total_count += label.size(0)
        
        if idx % log_interval == 0:
            elapsed = time.time() - start_time
            print(
                "Epoch {:3d} | {:5d}/{:5d} batches "
                "| accuracy {:8.3f} | loss {:8.3f} ({:.3f}s)".format(
                    epoch, idx, len(dataloader), total_acc / total_count, loss.item(), elapsed
                )
            )
            total_acc, total_count = 0, 0
            start_time = time.time()

        if max_steps is not None:
            if idx == max_steps:
                return {'loss': loss.item(), 'acc': total_acc / total_count}
    
    return {'loss': loss.item(), 'acc': total_acc / total_count}

# --- Цикл оценки качества модели ---
def evaluate(model, dataloader, loss_fn):
    model.eval()
    total_acc, total_count = 0, 0

    with torch.no_grad():
        for idx, (inputs, label) in enumerate(dataloader):
            predicted_label = model(inputs)
            loss = loss_fn(predicted_label, label)
            total_acc += (predicted_label.argmax(1) == label).sum().item()
            total_count += label.size(0)

    return {'loss': loss.item(), 'acc': total_acc / total_count}

# --- Главная функция ---
def train_and_evaluate(**params):

    logging.info("running with the following params :")
    logging.info(params)

    # Загрузка предварительно обученного токенизатора и модели BERT
    # поменяйте пути на те, которые используете
    tokenizer = BertTokenizer.from_pretrained('models/bert_uncased_L-2_H-128_A-2/vocab.txt')
    model = BertModel.from_pretrained('models/bert_uncased_L-2_H-128_A-2')
    
    # Параметры обучения
    epochs = int(params.get('epochs'))
    batch_size = int(params.get('batch_size'))
    learning_rate = float(params.get('learning_rate'))
    
    #  Загрузка данных
    df_train = pd.read_csv(params.get('training_file'))
    df_eval = pd.read_csv(params.get('validation_file'))
    df_test = pd.read_csv(params.get('testing_file'))

    # Создание загрузчиков данных
    train_ds = BertDataset(df_train, tokenizer, max_length=100)
    train_loader = DataLoader(dataset=train_ds,batch_size=batch_size, shuffle=True)
    eval_ds = BertDataset(df_eval, tokenizer, max_length=100)
    eval_loader = DataLoader(dataset=eval_ds,batch_size=batch_size)
    test_ds = BertDataset(df_test, tokenizer, max_length=100)
    test_loader = DataLoader(dataset=test_ds,batch_size=batch_size)
    
    # Создание модели
    classifier = SentimentBERT(bert_model=model).to(DEVICE)
    total_parameters = sum((np.prod(p.size()) for p in classifier.parameters()))
    model_parameters = filter(lambda p: p.requires_grad, classifier.parameters())
    params = sum((np.prod(p.size()) for p in model_parameters))
    logging.info(f"Total params : {total_parameters} - Trainable : {params} ({params/total_parameters*100}% of total)")
    
    # Оптимизатор и функция потерь
    optimizer = torch.optim.Adam((p for p in classifier.parameters() if p.requires_grad), learning_rate)
    loss_fn = nn.CrossEntropyLoss()

    # При пробном запуске выполнить лишь следующее
    logging.info(f'Training model with {BERT_MODEL_NAME}')
    if args.dry_run:
        logging.info("Dry run mode")
        epochs = 1
        steps_per_epoch = 1
    else:
        steps_per_epoch = None
        
    # Вперёд!
    for epoch in range(1, epochs + 1):
        epoch_start_time = time.time()
        train_metrics = train(epoch, classifier, train_loader, loss_fn=loss_fn, optimizer=optimizer, max_steps=steps_per_epoch)
        eval_metrics = evaluate(classifier, eval_loader, loss_fn=loss_fn)
        
        print("-" * 59)
        print(
            "End of epoch {:3d} - time: {:5.2f}s - loss: {:.4f} - accuracy: {:.4f} - valid_loss: {:.4f} - valid accuracy {:.4f} ".format(
                epoch, time.time() - epoch_start_time, train_metrics('loss'), train_metrics('acc'), eval_metrics('loss'), eval_metrics('acc')
            )
        )
        print("-" * 59)
    
    if args.dry_run:
        # При пробном запуске не выполнять оценку качества модели
        return None
    
    test_metrics = evaluate(classifier, test_loader, loss_fn=loss_fn)
    
    metrics = {
        'train': train_metrics,
        'val': eval_metrics,
        'test': test_metrics,
    }
    logging.info(metrics)
    
    # Сохранение модели и архитектуры в одном файле
    if params.get('job_dir') is None:
        logging.warning("No job dir provided, model will not be saved")
    else:
        logging.info("Saving model to {} ".format(params.get('job_dir')))
        torch.save(classifier.state_dict(), params.get('job_dir'))
    logging.info("Bye bye")
    
    
if __name__ == '__main__':
    # Создание аргументов
    parser = argparse.ArgumentParser()
    parser.add_argument('--training-file', required=True, type=str)
    parser.add_argument('--validation-file', required=True, type=str)
    parser.add_argument('--testing-file', type=str)
    parser.add_argument('--job-dir', type=str)
    parser.add_argument('--epochs', type=float, default=2)
    parser.add_argument('--batch-size', type=float, default=1024)
    parser.add_argument('--learning-rate', type=float, default=0.01)
    parser.add_argument('--dry-run', action="store_true")

    # Парсинг аргументов
    args, _ = parser.parse_known_args()

    # Запуск обучения
    train_and_evaluate(**vars(args))

Todo esto es maravilloso, pero, lamentablemente, llevará mucho tiempo entrenar este modelo. De hecho, estamos hablando de entrenar unos 4,7 millones de parámetros. En una Macbook Pro con procesador Intel y 16 GB de memoria, un paso de entrenamiento durará unos 3 segundos.

https://miro.medium.com/v2/resize:fit:595/1*G9aqdhK_OWr1qZS8AAg6JA.png

aprendizaje en un paso

Un paso de 3 segundos puede ser demasiado largo si necesita pasar por 1283 pasos y procesar datos en 10 épocas…

En resumen: no hay vacaciones sin GPU.

¿Cómo utilizar Vertex AI y empezar a celebrar?

La respuesta corta a esta pregunta consta de dos palabras: Docker y Gcloud.

Si no tiene una GPU potente instalada en su computadora portátil (como la mayoría de nosotros), o si no desea quemar el ventilador de enfriamiento de su máquina, puede decidir que necesita mover su script a alguna plataforma en la nube como Google Cloud (te lo cuento ahora mismo): uso Google Cloud en el trabajo).

Hay una cosa buena de trabajar con Google: quien crea un proyecto usando una cuenta de Gmail recibe un bono de $300.

Y, como es habitual, cuando se trata de portar código a una determinada plataforma, una solución popular es utilizar Docker.

Script de Dockerización

Creemos una imagen de Docker diseñada para uso de GPU. Hay muchas imágenes adecuadas disponibles en el repositorio oficial de Docker. Elegí pytorch/pytorch:2.2.2-cuda11.8-cudnn8-runtimeya que estoy usando Pytorch 2.2.2. Al elegir una imagen, asegúrese de que ya sea compatible con CUDA. De lo contrario, tendrás que organizar tú mismo la instalación de todo lo que necesitas utilizando Dockerfile. Y, créame, no lo necesita, excepto, por supuesto, en aquellos casos en los que realmente lo necesita.

Abajo Dockerfile organiza la preinstalación de todas las dependencias y controladores CUDA necesarios. Nos brindará la oportunidad de usarlos en nuestra propia tarea de capacitación y ejecutar el script. main.py con los argumentos pasados ​​cuando se inició el contenedor.

FROM pytorch/pytorch:2.2.2-cuda11.8-cudnn8-runtime

WORKDIR /src
COPY . .
RUN pip install --upgrade pip && pip install -r requirements.txt

ENTRYPOINT ("python", "main.py")

Crea y envía la imagen a Google Cloud

Una vez que la imagen esté lista para su ensamblaje, deberá ensamblarla y enviarla al registro de imágenes. Puede ser cualquier registro que considere apropiado, pero Google Cloud ofrece un servicio llamado Artefact Registry para almacenar imágenes. El uso de este registro le facilitará el almacenamiento de imágenes en Google Cloud.

Coloque el archivo build.shcuyo código se proporciona a continuación, a la raíz del proyecto, y verifique que Dockerfile estaría al mismo nivel:

# build.sh

export PROJECT_ID=<your-project-id>
export IMAGE_REPO_NAME=pt_bert_sentiment
export IMAGE_TAG=dev
export IMAGE_URI=eu.gcr.io/$PROJECT_ID/$IMAGE_REPO_NAME:$IMAGE_TAG

gcloud builds submit --tag $IMAGE_URI .

Correr build.sh. Después de esperar unos minutos a que se cree la imagen, debería ver algo como esto:

eu.gcr.io/<your-project-id>/pt_bert_sentiment:dev SUCCESS

Crear un trabajo en Vertex AI

Una vez que la imagen se crea y se envía al Registro de Artefactos, podemos pedirle a Vertex AI que ejecute esta imagen en cualquier máquina que necesitemos. ¡Incluido uno equipado con potentes GPU! Como ya se mencionó, Google ofrece un bono de $300 por crear proyectos de GCP. Esto es suficiente para ejecutar nuestro modelo.

Puede encontrar información sobre precios para diferentes configuraciones. Aquí. En nuestro caso todo se verá así: cogeremos un coche n1-standard-4 por $0,24 por hora y agréguele una GPU NVIDIA T4 por $0,40 por hora.

https://miro.medium.com/v2/resize:fit:700/1*p1Sc0D78xW_HgFe04Owj_Q.png

Tipos de máquinas

https://miro.medium.com/v2/resize:fit:700/1*I7ADcxcsSUxZoIcwINoRTQ.png

Aceleradores

Creemos un archivo job.shcuyo contenido se muestra a continuación, indicando su región y qué máquina desea utilizar. Consulte los materiales disponibles en el enlace anterior si no se encuentra en la misma región que yo, ya que sus precios pueden variar.

También necesitarás pasar argumentos al script de entrenamiento. Las estructuras sintácticas utilizadas para esto en gcloud ai custom-jobs createestán construidos a partir de dos partes:

  • Argumentos relacionados con la tarea en sí: --region, --display-name, --worker-pool-spec, --service-account y --args.

  • Argumentos relacionados con la formación: --training-file, --epochs etcétera.

Antes de los últimos hay que poner. --argspara indicar que todo lo que sigue pretende ser un script de Python tutorial.

Por ejemplo, si nuestro script toma 2 argumentos: x y yentonces obtienes lo siguiente: –-args=x=1,y=2.

# job.sh

export PROJECT_ID=<your-project-id>
export BUCKET=<your-bucket-id>
export REGION="europe-west4"
export SERVICE_ACCOUNT=<your-service-account>
export JOB_NAME="pytorch_bert_training"
export MACHINE_TYPE="n1-standard-4"  # Тут можно описать необходимые GPU
export ACCELERATOR_TYPE="NVIDIA_TESLA_T4"
export IMAGE_URI="eu.gcr.io/$PROJECT_ID/pt_bert_sentiment:dev"


gcloud ai custom-jobs create \
--region=$REGION \
--display-name=$JOB_NAME \
--worker-pool-spec=machine-type=$MACHINE_TYPE,accelerator-type=$ACCELERATOR_TYPE,accelerator-count=1,replica-count=1,container-image-uri=$IMAGE_URI \
--service-account=$SERVICE_ACCOUNT \
--args=\
--training-file=gs://$BUCKET/data/train.csv,\
--validation-file=gs://$BUCKET/data/eval.csv,\
--testing-file=gs://$BUCKET/data/test.csv,\
--job-dir=gs://$BUCKET/model/model.pt,\
--epochs=10,\
--batch-size=128,\
--learning-rate=0.0001

Ejecutar un trabajo en la plataforma Vertex AI

Ejecutemos el script y vayamos a nuestro proyecto GCP, a la sección Entrenamiento, que se puede encontrar en el menú Vertex.

https://miro.medium.com/v2/resize:fit:700/1*VSmqgPskAaeQb2FPuGv4sA.png

Revisa el proyecto

Ejecutemos el script y vayamos a la consola. Debe mostrar información sobre el estado del trabajo. Al principio será Pendingy luego – Training.

Para asegurarse de que la GPU se utilice realmente durante el entrenamiento del modelo, puede verificar la tarea y sus recursos.

https://miro.medium.com/v2/resize:fit:700/1*Os5MCjlrzZ4RuR0MFmnpfQ.png

Comprobación de los recursos utilizados por un trabajo

Esto indica que estamos entrenando el modelo usando la GPU. ¡Esto significa que puede esperar un aumento significativo en la velocidad de aprendizaje! Miremos los registros.

https://miro.medium.com/v2/resize:fit:682/1*qsAamE-BdY2lZ3QcTGvQbQ.png

Acerca del entrenamiento de un modelo

Ahora una época tarda 10 minutos, ¡pero lo mismo tarda una hora entera en la CPU! Transferimos la carga de entrenar el modelo a la plataforma Vertex y aceleramos el proceso de capacitación. Esto nos da la oportunidad, por ejemplo, de ejecutar otros trabajos usando diferentes configuraciones sin sobrecargar los portátiles.

¿Qué pasa con la precisión resultante del modelo? Resulta que después de pasar 10 eras está en la región del 94-95%. Puede dejar que el modelo aprenda un poco más y ver si esto mejora su precisión (o puede agregar una devolución de llamada que detenga el entrenamiento antes de tiempo para evitar el sobreentrenamiento del modelo).

https://miro.medium.com/v2/resize:fit:671/1*QHXQq2knoduoRdtYAaf51g.png

Acerca del entrenamiento de un modelo

¿Cuáles son los resultados de entrenar el modelo?

https://miro.medium.com/v2/resize:fit:700/1*13kcuq9rEO5Pdrqp8MtmvQ.png

Resultados del entrenamiento modelo

¡Es hora de celebrar!

Oh, ¿venir a trabajar con nosotros?

estamos en wunderfund.io estamos haciendo comercio algorítmico de alta frecuencia desde 2014. El comercio de alta frecuencia es una competencia continua entre los mejores programadores y matemáticos de todo el mundo. Al unirte a nosotros, serás parte de esta emocionante lucha.

Ofrecemos análisis de datos interesantes y desafiantes y tareas de desarrollo de baja latencia para investigadores y programadores apasionados. Horario flexible y sin burocracia; las decisiones se toman e implementan rápidamente.

Actualmente estamos buscando desarrolladores, pitonistas, ingenieros de datos e investigadores de ML.

Únete a nuestro equipo

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *