Técnicas Avanzadas de Entrenamiento con PyTorch

1. Definición Personalizada de Funciones de Pérdida

Con el desarrollo de la Inteligencia Artificial, ha surgido una variedad de funciones de pérdida no oficiales como DiceLoss, HuberLoss y SobolevLoss. Estas funciones de pérdida están diseñadas para modelos específicos y no pueden ser incluidas directamente en la biblioteca de PyTorch. Por lo tanto, es necesario implementar estas funciones de pérdida mediante definiciones personalizadas. Además, en investigación científica, a menudo se propone nuevas funciones de pérdida para mejorar el rendimiento del modelo. En estos casos, no podemos utilizar las funciones de pérdida disponibles en PyTorch ni encontrar referencias en blogs, por lo que es crucial implementar nuestras propias funciones de pérdida.

(1) Definición Funcional

Podemos definir nuestra propia función de pérdida mediante una función regular:

def mi_funcion_de_perdida(predicción, objetivo):
    pérdida = torch.mean((predicción - objetivo)**2)
    return pérdida

(2) Definición Clase

Al definir una clase para la función de pérdida, podemos aprovechar la jerarquía de clases existente en PyTorch. Las clases de pérdidas generalmente heredan de _Loss, _WeightedLoss o nn.Module. Podemos tratarla como una capa de red neuronal y nuestra clase de función de pérdida debe heredar de nn.Module.

El Dice Loss es una función comúnmente utilizada en tareas de segmentación y se define como:

[DSC = \frac{2|X∩Y|}{|X|+|Y|}]La implementación en código sería:

class DiceLoss(nn.Module):
    def __init__(self, peso=None, tamaño_promedio=True):
        super(DiceLoss, self).__init__()

    def adelante(self, entradas, objetivos, suavizado=1):
        entradas = F.sigmoid(entradas)
        entradas = entradas.view(-1)
        objetivos = objetivos.view(-1)
        intersección = (entradas * objetivos).sum()
        dice = (2.*intersección + suavizado) / (entradas.sum() + objetivos.sum() + suavizado)
        return 1 - dice

# Uso
criterio = DiceLoss()
pérdida = criterio(predicción, objetivos)

2. Ajuste Dinámico de la Tasa de Aprendizaje

(1) Usando los Programadores Oficiales

PyTorch proporciona varios métodos para ajustar dinámicamente la tasa de aprendizaje a través del módulo torch.optim.lr_scheduler. Algunos de estos programadores incluyen:

  • lr_scheduler.LambdaLR
  • lr_scheduler.MultiplicativeLR
  • lr_scheduler.StepLR
  • lr_scheduler.MultiStepLR
  • lr_scheduler.ExponentialLR
  • lr_scheduler.CosineAnnealingLR
  • lr_scheduler.ReduceLROnPlateau
  • lr_scheduler.CyclicLR
  • lr_scheduler.OneCycleLR
  • lr_scheduler.CosineAnnealingWarmRestarts

Cuando se usan los programadores oficiales, es importante llamar al método step() después de optimizer.step().

# Selección de un optimizador
optimizador = torch.optim.Adam(...) 
# Selección de uno o más programadores de la lista anterior
programador1 = torch.optim.lr_scheduler.... 
programador2 = torch.optim.lr_scheduler....
...
programadorn = torch.optim.lr_scheduler....
# Inicio del entrenamiento
for época in rango(100):
    entrenar(...)
    validar(...)
    optimizador.step()
    # El ajuste de la tasa de aprendizaje debe hacerse después de actualizar los parámetros del optimizador
    programador1.step() 
    ...
    programadorn.step()

(2) Definición Personalizada de Programadores

En algunos experimentos, puede ser necesario definir nuestro propio método para ajustar la tasa de aprendizaje. Una solución es crear una función llamada ajustar_tasa_de_aprendizaje que cambie el valor de lr en param_group.

Supongamos que estamos realizando un experimento y necesitamos que la tasa de aprendizaje disminuya a un tercio de su valor actual cada 30 épocas. Si no encontramos una API oficial que cumpla con nuestros requisitos, podemos definir una función personalizada para realizar este cambio.

def ajustar_tasa_de_aprendizaje(optimizador, época):
    lr = args.lr * (0.1 ** (epoca // 30))
    for param_group in optimizador.param_groups:
        param_group['lr'] = lr


def ajustar_tasa_de_aprendizaje(optimizador,...):
    ...
optimizador = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
for época in rango(10):
    entrenar(...)
    validar(...)
    ajustar_tasa_de_aprendizaje(optimizador,época)

3. Fine-Tuning con torchvision

En aplicaciones prácticas, es posible que solo tengamos unos pocos mil ejemplos de datos. En este caso, no es realista comenzar desde cero el entrenamiento de un gran modelo de red neuronal con millones de parámetros, ya que cuanto mayor sea el modelo, mayor será la cantidad de datos requerida para evitar el sobreajuste.

Imagina que deseas identificar diferentes tipos de sillas y luego recomendar enlaces de compra a los usuarios. Un enfoque posible es seleccionar 100 tipos comunes de sillas y tomar 1000 imágenes a diferentes ángulos para cada tipo. Luego, puedes entrenar un modelo de clasificación en un conjunto de datos recopilado. Este conjunto de datos de sillas, aunque puede ser más grande que Fashion-MNIST, aún no tiene tantos ejemplos como ImageNet. Esto podría llevar a que un modelo complejo, adecuado para ImageNet, sobreprediga en tu conjunto de datos de sillas. Además, debido a la limitación de datos, el modelo final entrenado puede no tener suficiente precisión para ser útil.

Para abordar estos problemas, una solución obvia pero efectiva es recoger más datos. Sin embargo, recolectar y anotar datos puede consumir mucho tiempo y recursos. Por ejemplo, para recoger el conjunto de datos ImageNet, los investigadores gastaron millones de dólares en fondos de investigación. Aunque el costo de recolección de datos ha disminuido significativamente, sigue siendo considerable.

Otra solución es usar el aprendizaje transferente (transfer learning), donde se llevan a cabo conocimientos adquiridos en un conjunto de datos fuente a otro conjunto de datos objetivo. Aunque la mayoría de las imágenes en el conjutno de datos ImageNet no estén relacionadas con las sillas, el modelo entrenado en ese conjunto de datos puede extraer características generales de las imágenes, lo que le permitirá ayudar a reconocer bordes, texturas, formas y composición de objetos. Estas características similares también podrían ser eficaces para reconocer sillas.

Una gran aplicación del aprendizaje transferente es el fine-tuning. Es decir, buscar primero un modelo preentrenado de la misma categoría y usarlo para adaptarlo a tus propios datos. En PyTorch, hay muchas redes neuronales preentrenadas disponibles, como VGG, ResNet y MobileNet, todas ellas entrenadas en conjuntos de datos grandes como ImageNet. Aprender cómo realizar el fine-tuning te permite usar rápidamente modelos preentrenados para completar tus propias tareas.

Además de utilizar torchvision.models para cargar modelos preentrenados, existe otra biblioteca común llamada timm, creada por Ross Wightman de Vancouver, Canadá. Esta biblioteca ofrece muchos modelos de visión computacional de vanguardia, que pueden considerarse una extensión de torchvision y tienen altísimas tasas de precisión.

(1) Flujo de Fine-Tuning

  • Preentrena una red neuronal en un conjunto de datos fuente como el conjunto de datos ImageNet, lo que resulta en un modelo fuente.
  • Crea un nuevo modelo de red neuronal, lo que resulta en un modelo objetivo. Copia todos los diseños y parámetros de la red fuente, excepto la capa de salida. Suponemos que estos parámetros contienen conocimientos adquiridos en el conjunto de datos fuente y son apropiados para el conjunto de datos objetivo. También suponemos que la capa de salida del modelo fuente está muy relacionada con las etiquetas del conjunto de datos fuente, por lo que no la usamos en el modelo objetivo.
  • Añade una capa de salida con el tamaño de la categoría del conjunto de datos objetivo y inicializa aleatoriamente los parámetros de esta capa.
  • Entrena el modelo objetivo en el conjunto de datos objetivo. Solo entrenaremos la capa de salida, mientras que los demás parámetros se ajustarán basándose en los parámetros del modelo fuente.

(2) Utilización de Modelos Existentes

  • Instanciación de Red
import torchvision.models as modelos
resnet18 = modelos.resnet18()
# resnet18 = modelos.resnet18(pretrained=False)  Equivalente a la expresión anterior
alexnet = modelos.alexnet()
vgg16 = modelos.vgg16()
squeezenet = modelos.squeezenet1_0()
densenet = modelos.densenet161()
inception = modelos.inception_v3()
googlenet = modelos.googlenet()
shufflenet = modelos.shufflenet_v2_x1_0()
mobilenet_v2 = modelos.mobilenet_v2()
mobilenet_v3_large = modelos.mobilenet_v3_large()
mobilenet_v3_small = modelos.mobilenet_v3_small()
resnext50_32x4d = modelos.resnext50_32x4d()
wide_resnet50_2 = modelos.wide_resnet50_2()
mnasnet = modelos.mnasnet1_0()


  • Parámetro pretrained

Atraves de True o False determinamos si usar pesos preentrenados. Por defecto, pretrained = False significa que no usamos los pesos preentrenados. Cuando pretrained = True, significa que usamos los pesos preentrenados en algún conjunto de datos.

import torchvision.models as modelos
resnet18 = modelos.resnet18(pretrained=True)
alexnet = modelos.alexnet(pretrained=True)
squeezenet = modelos.squeezenet1_0(pretrained=True)
vgg16 = modelos.vgg16(pretrained=True)
densenet = modelos.densenet161(pretrained=True)
inception = modelos.inception_v3(pretrained=True)
googlenet = modelos.googlenet(pretrained=True)
shufflenet = modelos.shufflenet_v2_x1_0(pretrained=True)
mobilenet_v2 = modelos.mobilenet_v2(pretrained=True)
mobilenet_v3_large = modelos.mobilenet_v3_large(pretrained=True)
mobilenet_v3_small = modelos.mobilenet_v3_small(pretrained=True)
resnext50_32x4d = modelos.resnext50_32x4d(pretrained=True)
wide_resnet50_2 = modelos.wide_resnet50_2(pretrained=True)
mnasnet = modelos.mnasnet1_0(pretrained=True)


(3) Consideraciones Generales

Normalmente, la descarga de los modelos preentrenados es bastante lenta. Podemos descargar manualmente los pesos del modelo visitando model_urls aquí. Los pesos preentrenados se guardan en la carpeta .cache del usuario en Linux y Mac. En Windows, están ubicados en C:\Users\.cache\torch\hub\checkpoint. Puedes usar torch.utils.model_zoo.load_url() para establecer la dirección de descarga de los pesos.

Además, puedes descargar tus propios pesos y colocarlos en la misma carpeta antes de cargar los parámetros en el modelo.

Si detienes la descarga en medio, asegúrate de eliminar limpiamente el archivo de pesos correspondiente en la ruta correcta para evitar errores.

(4) Entrenamiento de Capas Específicas

Por defecto, los atributos .requires_grad = True de los parámetros. No debes preocuparte por esto si estás comenzando desde cero o ajustándote. Pero si estás extrayendo características y solo quieres calcular los gradientes para las nuevas capas inicializadas, manteniendo los otros parámetros invariables, deberías configurar .requires_grad = False para congelar algunas capas. PyTorch proporciona un ejemplo en su documentación.

def establecer_requiere_grad(modelo, extraer_características):
    if extraer_características:
        for parámetro in modelo.parameters():
            parámetro.requires_grad = False


Usando resnet18 como ejemplo, vamos a cambiar de 1000 clases a 4 clases, pero solo modificaremos los parámetros de la última capa, no los parámetros de la etapa de extracción de características. Ten en cuenta que primero congelamos los gradientes de los parámetros del modelo, luego modificamos la parte final de la red neuronal, de manera que la nueva capa totalmente conectada tenga gradientes calculables.

import torchvision.models as modelos
# Congelamos los gradientes
extraer_características = True
modelo = modelos.resnet18(pretrained=True)
establecer_requiere_grad(modelo, extraer_características)
# Modificamos el modelo
num_ftrs = modelo.fc.in_features
modelo.fc = nn.Linear(in_features=num_ftrs, out_features=4, bias=True)


Durante el entrenamiento, el modelo seguirá propagando el gradiente, pero solo se actualizarán los parámetros de la fc. Al configurar la propiedad .requires_grad de los parámetros, logramos nuestro objetivo de entrenar solo ciertas capas del modelo, lo cual es muy importante para el fine-tuning.

4. Entrenamiento en Precisión Semihíbrida

PyTorch utiliza por defecto el formato de punto flotante de 32 bits (torch.float32). Aunque este formato ofrece más precisión con más decimales, en la mayoría de los escenarios no es necesario tanto nivel de precisión. La mitad de la información también sería suficiente y no afectaría el resultado, es decir, podríamos usar el formato de punto flotante de 16 bits (torch.float16). Debido a que se han reducido la mitad de los dígitos, se llama "semihíbrida". La semihíbrida permite reducir el uso de memoria, lo que permite cargar en la GPU más datos para el cálculo. Aquí tienes un gráfico:

Configuración de Entrenamiento en Precisión Semihíbrida

En PyTorch, puedes configurar el entrenamiento en precisión semihíbrida usando autocast, que requiere tres configuraciones adicionales:

  • Importar autocast
from torch.cuda.amp import autocast


  • Configuración del Modelo

En la definición del modelo, usa el decorador @autocast() para decorar la función forward del modelo. Para aprender sobre el uso de decoradores, puedes consultar aquí:

@autocast()   
def adelante(self, x):
    ...
    return x


  • Proceso de Entrenamiento

Solo necesitas envolver el input de los datos y la siguiente parte del proceso de entrenamiento dentro de "with autocast():":

for x in cargador_entrenamiento:
	x = x.cuda()
	with autocast():
        salida = modelo(x)
        ...


5. Aumento de Datos - imgaug

La Inteligencia Artificial depende fundamentalmente de los datos. Necesitamos una gran cantidad de datos para evitar el sobreajuste del modelo. Sin embargo, en muchas situaciones no podemos obtener una gran cantidad de datos, especialmente en el análisis de imágenes médicas. La técnica de aumento de datos existe para resolver este problema, ofreciendo una solución a los problemas de falta de datos. El aumento de datos es un conjunto de técnicas que aumenta el tamaño y la calidad del conjunto de datos de entrenamiento, lo que nos permite construir mejores modelos de aprendizaje automático. En el campo de la visión por computadora, generar imágenes aumentadas es relativamente fácil. A pesar de añadir ruido o recortar una parte de la imagen, el modelo aún puede clasificar la imagen, y hay varias técnicas efectivas y simples disponibles para el aumento de datos. Hay varias bibliotecas de machine learning que proporcionan herramientas para el aumento de datos en el campo de la visión por computadora, como imgaug.

(1) Introducción a imgaug

Imgaug es un paquete comúnmente utilizado en tareas de visión por computadora para el aumento de datos. Comparado con torchvision.transforms, proporciona más métodos de aumento de datos, por lo que se usa ampliamente en competencias de Kaggle para aumentar los datos de entrenamiento. Además, imgaug proporciona muchas ejemplos para que puedas aprender.

(2) Instalación de imgaug

conda config --add channels conda-forge
conda install imgaug

# Instalar imgaug a través de pip
pip install imgaug
# Instalar la versión más reciente directamente desde GitHub
pip install git+https://github.com/aleju/imgaug.git


(3) Uso de imgaug - Procesamiento de Imágenes Únicas

Imgaug sólo proporciona métodos de aumento de datos, pero no operaciones de entrada/salida de imagen. Por lo tanto, necesitamos usar algunas bibliotecas para leer las imágenes. Se recomienda usar imageio para leer las imágenes. Si usas opencv para la lectura de archivos, necesitarás cambiar manualmente el canal, convirtiendo la imagen BGR leída por opencv en RGB. Además, cuando uses PIL.Image para leer las imágenes, porque las imágenes leídas no tienen la propiedad de forma, debes convertir el img leído a np.array() para poder procesarlo. Por lo tanto, en los ejemplos oficiales también se usa imageio para leer las imágenes.

import imageio
import imgaug as ia
%matplotlib inline

# Lectura de la imagen
img = imageio.imread("./Lenna.jpg")

# Usar Image para leer
# img = Image.open("./Lenna.jpg")
# image = np.array(img)
# ia.imshow(image)

# Visualizar la imagen
ia.imshow(img)


from imgaug import augmenters as iaa

# Establecer la semilla aleatoria
ia.seed(4)

# Crear un método
rotar = iaa.Affine(rotate=(-4,45))
img_aug = rotar(image=img)
ia.imshow(img_aug)


Procesamiento de una sola imagen con múltiples técnicas de aumento de datos: Utiliza imgaug.augmenters.Sequential() para construir una cadena de procesamiento de datos aumentados, similar a torchvison.transforms.Compose()

iaa.Sequential(children=None, # Conjunto de Augmenter
               random_order=False, # ¿Usar diferente orden de Augmenter para cada batch?
               nombre=None,
               determinista=False,
               estado_aleatorio=None)

# Construir la secuencia de procesamiento
secuencia_aumento = iaa.Sequential([
    iaa.Affine(rotate=(-25,25)),
    iaa.AdditiveGaussianNoise(scale=(10,60)),
    iaa.Crop(percent=(0,0.2))
])
# Procesar la imagen, image no puede omitirse y no puede escribirse como images
imagen_aumentada = secuencia_aumento(image=img)
ia.imshow(imagen_aumentada)


(4) Uso de imgaug - Procesamiento de Imágenes en Lotes

En el uso real, normalmente necesitamos manejar una gran cantidad de datos de imágenes. En este caso, puedes organizar los datos de imagen en un formato NHWC o una lista de imágenes HWC para procesar un lote de imágenes. Principalmente se divide en dos partes, el procesamiento de imágenes en lotes de la misma manera y el procesamiento de imágenes en lotes de forma parcial.

  • Procesamiento de imágenes en lotes de la misma manera Coloca las imágenes pendientes de procesamiento en una lista y cambia image por images para realizar la operación de aumento de datos. El proceso de operación es el siguiente:
imagenes = [img, img, img, img,]
imagens_aumentadas = rotar(images=imagens)
ia.imshow(np.hstack(imagens_aumentadas))


  • Varias técnicas de aumento de datos Similar al método de una sola imagen, también necesitamos utilizar Sequential para construir la cadena de procesamiento de datos aumentados.
secuencia_aumento = iaa.Sequential([
    iaa.Affine(rotate=(-25, 25)),
    iaa.AdditiveGaussianNoise(scale=(10, 60)),
    iaa.Crop(percent=(0, 0.2))
])

# Pasar por parámetro images
imagens_aumentadas = secuencia_aumento.augment_images(images = imagens)
# imagens_aumentadas = secuencia_aumento(images = imagens) 
ia.imshow(np.hstack(imagens_aumentadas))


  • Procesamiento parcial de imágenes en lotes Puedes usar imgaug.augmenters.Sometimes() para aplicar algunas de las Transformaciones a unas fracciones del lote de imágenes y otras a las restantes.
iaa.Sometimes(p=0.5,  # Representa la proporción de división
              then_list=None,  # Conjunto de Augmenter. Aplicará las Transformaciones con probabilidad p.
              else_list=None,  # Aplicará las Transformaciones con probabilidad 1-p. Ten en cuenta que las imágenes transformadas solo podrán aplicar las Transformaciones presentes en then_list o else_list.
              nombre=None,
              determinista=False,
              estado_aleatorio=None)


  • Procesamiento de imágenes de diferentes tamaños en lotes Para el caso de imágenes de diferentes tamaños, agrupa las imágenes en un lote y luego muestra cada imagen individualmente. El proceso es muy similar al de una sola imagen.
# Construir la cadena de procesamiento
secuencia = iaa.Sequential([
    iaa.CropAndPad(percent=(-0.2, 0.2), pad_mode="edge"),  # Recorta y rellena las imágenes
    iaa.AddToHueAndSaturation((-60, 60)),  # Cambia sus colores
    iaa.ElasticTransformation(alpha=90, sigma=9),  # Efecto de agua
    iaa.Cutout()  # Reemplaza una región cuadrada dentro de la imagen por un valor de intensidad constante
], random_order=True)

# Cargar imágenes de diferentes tamaños
imagenes_distintos_tamaños = [
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/e/ed/BRACHYLAGUS_IDAHOENSIS.jpg"),
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/c/c9/Southern_swamp_rabbit_baby.jpg"),
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/9/9f/Lower_Keys_marsh_rabbit.jpg")
]

# Procesar las imágenes
imagenes_aumentadas = secuencia(images=imagenes_distintos_tamaños)

# Visualizar los resultados
print("Imagen 0 (forma de entrada: %s, forma de salida: %s)" % (imagenes_distintos_tamaños[0].shape, imagenes_aumentadas[0].shape))
ia.imshow(np.hstack([imagenes_distintos_tamaños[0], imagenes_aumentadas[0]]))

print("Imagen 1 (forma de entrada: %s, forma de salida: %s)" % (imagenes_distintos_tamaños[1].shape, imagenes_aumentadas[1].shape))
ia.imshow(np.hstack([imagenes_distintos_tamaños[1], imagenes_aumentadas[1]]))

print("Imagen 2 (forma de entrada: %s, forma de salida: %s)" % (imagenes_distintos_tamaños[2].shape, imagenes_aumentadas[2].shape))
ia.imshow(np.hstack([imagenes_distintos_tamaños[2], imagenes_aumentadas[2]]))


(5) Uso de imgaug en PyTorch

import numpy as np
from imgaug import augmenters as iaa
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

# Construir la cadena de procesamiento
transformaciones = transforms.Compose([
    iaa.Sequential([
        iaa.flip.Fliplr(p=0.5),
        iaa.flip.Flipud(p=0.5),
        iaa.GaussianBlur(sigma=(0.0, 0.1)),
        iaa.MultiplyBrightness(mul=(0.65, 1.35)),
    ]).augment_image,
    # No olvides usar ToTensor()
    transforms.ToTensor()
])

# Definición de un conjunto de datos personalizado
class ConjuntoDatosPersonalizado(Dataset):
    def __init__(self, n_imágenes, n_clases, transform=None):
        # Lectura de imágenes, se recomienda usar imageio
        self.imágenes = np.random.randint(0, 255,
                                        (n_imágenes, 224, 224, 3),
                                        dtype=np.uint8)
        self.etiquetas = np.random.randn(n_imágenes, n_clases)
        self.transform = transform

    def __getitem__(self, índice):
        imagen = self.imágenes[índice]
        etiqueta = self.etiquetas[índice]

        if self.transform:
            imagen = self.transform(imagen)

        return imagen, etiqueta

    def __len__(self):
        return len(self.imágenes)


def funcion_worker_init(worker_id):
    imgaug.seed(np.random.get_state()[1][0] + worker_id)


conjunto_personalizado = ConjuntoDatosPersonalizado(n_imágenes=50, n_clases=10, transform=transformaciones)
cargador_personalizado = DataLoader(conjunto_personalizado, batch_size=64,
                                   num_workers=4, pin_memory=True, 
                                   worker_init_fn=funcion_worker_init)


num_workers en sistemas operativos de Windows solo se puede establecer en 0, pero cuando se usa un servidor remoto de Linux, puede haber diferentes cantidades de num_workers. En este caso, debes tener en cuenta el papel de la función worker_init_fn(). Garantiza que el aumento de datos en num_workers > 0 sea aleatorio.

6. Uso de argparse para Ajustar los Parámetros

En el aprendizaje profundo, el ajuste y el almacenamiento de los hiperparámetros es un paso muy importante, especialmente cuando ejecutamos nuestros modelos en servidores. Python tiene una parte del estándar de bibliotecas que se llama argparse para analizar los parámetros de línea de comandos e introducirlos como hiperparámetros en el modelo.

(1) Introducción a argparse

argparse es un módulo estándar de Python para el análisis de parámetros de línea de comandos, incorporado en Python y no necesita instalación. Este módulo nos permite pasar directamente parámetros a nuestro programa desde la línea de comandos. Usamos python file.py para ejecutar el archivo Python. Y argparse juega el papel de parsear, guardar y usar los parámetros pasados a través de la línea de comandos. Después de usar argparse, podemos establecer los valores de los hiperparámetros comunes en la línea de comandos de la siguiente forma: python file.py --lr 1e-4 --batch_size 32.

(2) Uso de argparse

Podemos resumir el uso de argparse en tres pasos principales.

  • Crear un objeto ArgumentParser()
  • Llamar al método add_argument() para agregar parámetros
  • Usar parse_args() para analizar los parámetros A continuación, aprenderemos a usar argparse a través de ejemplos prácticos.
# demo.py
import argparse

# Crear un objeto ArgumentParser()
analizador = argparse.ArgumentParser()

# Agregar parámetros
analizador.add_argument('-o', '--output', acción='almacenar_true', 
    ayuda="muestra salida")
# acción = `almacenar_true` marcará el parámetro output como verdadero
# type especifica el formato del parámetro
# default especifica el valor predeterminado
analizador.add_argument('--lr', tipo=float, predeterminado=3e-5, ayuda='elige la tasa de aprendizaje, predeterminado=1e-3') 

analizador.add_argument('--batch_size', tipo=int, requerido=True, ayuda='tamaño de lote de entrada')  
# Usar parse_args() para analizar los parámetros
parámetros = analizador.parse_args()

if parámetros.output:
    print("Esta es alguna salida")
    print(f"tasa de aprendizaje: {parámetros.lr} ")


Cuando ejecutes python demo.py --lr 3e-4 --batch_size 32 verás la siguiente salida

Esta es alguna salida
tasa de aprendizaje: 3e-4


Los parámetros de argparse se pueden dividir en parámetros opcionales y obligatorios. Los parámetros opcionales son similares a nuestro parámetro lr, que tendrán el valor predeterminado si no se proporcionan. Los parámetros obligatorios son similares a nuestro parámetro batch_size, que deben proporcionarse al momento de invocar el parámetro, sino que dará error. Cuando se proporcionan parámetros en la línea de comandos, no es necesario usar el prefijo --

# positional.py
import argparse

# Parámetros posicionales
analizador = argparse.ArgumentParser()

analizador.add_argument('nombre')
analizador.add_argument('edad')

parámetros = analizador.parse_args()

print(f'{parámetros.nombre} tiene {parámetros.edad} años')


(3) Uso más eficiente de argparse para modificar los hiperparámetros

Generalmente, para mantener el código más limpio y modular, se suele organizar todo lo relacionado con los hiperparámetros en un archivo llamado config.py, y luego se importa en otros archivos como train.py o cualquier otro.

import argparse  
  
def obtener_opciones(analizador=argparse.ArgumentParser()):  
  
    analizador.add_argument('--workers', tipo=int, predeterminado=0,  
                        ayuda='Número de trabajadores de carga de datos, te sugerimos ponerlo en cuatro veces el número de GPU')  
  
    analizador.add_argument('--batch_size', tipo=int, predeterminado=4, ayuda='tamaño de lote de entrada, predeterminado=64')  
  
    analizador.add_argument('--niter', tipo=int, predeterminado=10, ayuda='número de épocas para entrenar, predeterminado=10')  
  
    analizador.add_argument('--lr', tipo=float, predeterminado=3e-5, ayuda='elige la tasa de aprendizaje, predeterminado=1e-3')  
  
    analizador.add_argument('--seed', tipo=int, predeterminado=118, ayuda="semilla aleatoria")  
  
    analizador.add_argument('--cuda', acción='almacenar_true', predeterminado=True, ayuda='habilita cuda')  
    analizador.add_argument('--ruta_guardado_modelo',tipo=str,predeterminado='',  
                        ayuda='Ruta para cargar un modelo previamente entrenado si no está vacío (por defecto vacío)')  
    analizador.add_argument('--mostrar_salida',accion='almacenar_true',predeterminado=True,help="muestra salida")  
  
    opciones = analizador.parse_args()  
  
    if opciones.mostrar_salida:  
        print(f'número de trabajadores: {opciones.workers}')  
        print(f'tamaño de lote de entrada: {opciones.batch_size}')  
        print(f'épocas (niter): {opciones.niter}')  
        print(f'tasa de aprendizaje: {opciones.lr}')  
        print(f'semilla manual: {opciones.seed}')  
        print(f'habilitar cuda: {opciones.cuda}')  
        print(f'ruta guardado modelo: {opciones.ruta_guardado_modelo}')  
  
    return opciones  
  
if __name__ == '__main__':  
    opciones = obtener_opciones()


Después en train.py o cualquier otro archivo, puedes usar la estructura siguiente para llamar a los parámetros.

# Importar las bibliotecas necesarias
...
import config

opciones = config.obtener_opciones()

semilla_manual = opciones.semilla
numero_trabajadores = opciones.workers
tamano_lote = opciones.batch_size
tasa_aprendizaje = opciones.lr
numero_epocas = opciones.niter
ruta_guardado_modelo = opciones.ruta_guardado_modelo

# Establecer la semilla para reproducibilidad
def establecer_semilla(semilla):
    torch.manual_seed(semilla)
    torch.cuda.manual_seed_all(semilla)
    random.seed(semilla)
    np.random.seed(semilla)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True

...

if __name__ == '__main__':
	setear_semilla(semilla_manual)
	for época in rango(numero_epocas):
		entrenar(modelo,tasa_aprendizaje,tamano_lote,numero_trabajadores,ruta_guardado_modelo)
		validar(modelo,tasa_aprendizaje,tamano_lote,numero_trabajadores,ruta_guardado_modelo)


Etiquetas: PyTorch Deep Learning training techniques custom loss functions learning rate scheduling

Publicado el 10-10 01:33