30.7 C
Colombia
lunes, julio 7, 2025

Posit AI Weblog: Que haja luz: ¡Más luz para la antorcha!



Posit AI Weblog: Que haja luz: ¡Más luz para la antorcha!

… Antes de comenzar, mis disculpas a nuestros lectores de habla hispana… Tuve que elegir entre “haja” y “haya”y al closing todo quedó en tirar una moneda al aire…

Mientras escribo esto, estamos más que contentos con la rápida adopción que hemos visto de torch – no sólo para uso inmediato, sino también, en paquetes que se basan en él, haciendo uso de su funcionalidad principal.

Sin embargo, en un escenario aplicado (un escenario que implica entrenar y validar al mismo tiempo, calcular métricas y actuar sobre ellas, y cambiar dinámicamente hiperparámetros durante el proceso) a veces puede parecer que hay una cantidad nada despreciable de código repetitivo involucrado. Por un lado, está el bucle principal sobre épocas y, en el inside, los bucles sobre lotes de entrenamiento y validación. Además, pasos como actualizar el modelo modo (entrenamiento o validación, respectivamente), la puesta a cero y el cálculo de gradientes, y la propagación de las actualizaciones del modelo deben realizarse en el orden correcto. Por último, hay que tener cuidado de que en cualquier momento los tensores se encuentren en la posición esperada. dispositivo.

¿No sería un sueño sicomo solía decir la in style serie “Head First…” de principios de la década de 2000, ¿había una manera de eliminar esos pasos manuales, manteniendo la flexibilidad? Con luzhay.

En esta publicación, nos centramos en dos cosas: en primer lugar, el flujo de trabajo optimizado en sí; y segundo, mecanismos genéricos que permitan la personalización. Para obtener ejemplos más detallados de este último, además de instrucciones de codificación concretas, nos vincularemos a la (ya extensa) documentación.

Entrene y valide, luego pruebe: un flujo de trabajo básico de aprendizaje profundo con luz

Para demostrar el flujo de trabajo esencial, utilizamos un conjunto de datos que está fácilmente disponible y no nos distraerá demasiado, en cuanto al preprocesamiento: a saber, el Perros contra gatos colección que viene con torchdatasets. torchvision será necesario para transformaciones de imágenes; Aparte de esos dos paquetes, todo lo que necesitamos son torch y luz.

Datos

El conjunto de datos se descarga de Kaggle; Deberá editar la ruta a continuación para reflejar la ubicación de su propio token de Kaggle.

dir <- "~/Downloads/dogs-vs-cats" 

ds <- torchdatasets::dogs_vs_cats_dataset(
  dir,
  token = "~/.kaggle/kaggle.json",
  rework = . %>%
    torchvision::transform_to_tensor() %>%
    torchvision::transform_resize(measurement = c(224, 224)) %>% 
    torchvision::transform_normalize(rep(0.5, 3), rep(0.5, 3)),
  target_transform = operate(x) as.double(x) - 1
)

Convenientemente podemos utilizar dataset_subset() para dividir los datos en conjuntos de entrenamiento, validación y prueba.

train_ids <- pattern(1:size(ds), measurement = 0.6 * size(ds))
valid_ids <- pattern(setdiff(1:size(ds), train_ids), measurement = 0.2 * size(ds))
test_ids <- setdiff(1:size(ds), union(train_ids, valid_ids))

train_ds <- dataset_subset(ds, indices = train_ids)
valid_ds <- dataset_subset(ds, indices = valid_ids)
test_ds <- dataset_subset(ds, indices = test_ids)

A continuación, creamos una instancia del respectivo dataloaders.

train_dl <- dataloader(train_ds, batch_size = 64, shuffle = TRUE, num_workers = 4)
valid_dl <- dataloader(valid_ds, batch_size = 64, num_workers = 4)
test_dl <- dataloader(test_ds, batch_size = 64, num_workers = 4)

Eso es todo en cuanto a los datos: hasta el momento no hay cambios en el flujo de trabajo. Tampoco hay diferencia en cómo definimos el modelo.

Modelo

Para acelerar el entrenamiento, nos basamos en AlexNet previamente entrenado ( krizhevski (2014)).

internet <- torch::nn_module(
  
  initialize = operate(output_size) {
    self$mannequin <- model_alexnet(pretrained = TRUE)

    for (par in self$parameters) {
      par$requires_grad_(FALSE)
    }

    self$mannequin$classifier <- nn_sequential(
      nn_dropout(0.5),
      nn_linear(9216, 512),
      nn_relu(),
      nn_linear(512, 256),
      nn_relu(),
      nn_linear(256, output_size)
    )
  },
  ahead = operate(x) {
    self$mannequin(x)[,1]
  }
  
)

Si miras de cerca, verás que todo lo que hemos hecho hasta ahora es definir el modelo. A diferencia de en un torch-solo flujo de trabajo, no vamos a crear una instancia del mismo, ni tampoco lo vamos a mover a una eventual GPU.

Ampliando esto último, podemos decir más: Todo del manejo del dispositivo es gestionado por luz. Busca la existencia de una GPU suitable con CUDA y, si la encuentra, se asegura de que tanto los pesos del modelo como los tensores de datos se muevan allí de forma transparente cuando sea necesario. Lo mismo ocurre en la dirección opuesta: las predicciones calculadas en el conjunto de prueba, por ejemplo, se transfieren silenciosamente a la CPU, listas para que el usuario las manipule aún más en R. Pero en cuanto a las predicciones, todavía no hemos llegado a ese punto: modelar la formación, donde la diferencia hecha por luz salta directo al ojo.

Capacitación

A continuación, verá cuatro llamadas a luzdos de los cuales son necesarios en cada entorno y dos dependen del caso. Los que siempre se necesitan son setup() y match() :

  • En setup()le dices luz cuál debería ser la pérdida y qué optimizador utilizar. Opcionalmente, más allá de la pérdida en sí (la métrica principal, en cierto sentido, en el sentido de que informa la actualización del peso), puede tener luz calcular otros adicionales. Aquí, por ejemplo, pedimos precisión en la clasificación. (Para un ser humano que observa una barra de progreso, una precisión de dos clases de 0,91 es mucho más indicativa que una pérdida de entropía cruzada de 1,26).

  • En match()se pasan referencias a la formación y validación dataloaders. Aunque existe un valor predeterminado para la cantidad de épocas para entrenar, normalmente también querrás pasar un valor personalizado para este parámetro.

Las llamadas dependientes del caso aquí, entonces, son aquellas para set_hparams() y set_opt_hparams(). Aquí,

  • set_hparams() aparece porque, en la definición del modelo, teníamos initialize() tomar un parámetro, output_size. Cualquier argumento esperado por initialize() debe pasarse a través de este método.

  • set_opt_hparams() está ahí porque queremos utilizar una tasa de aprendizaje no predeterminada con optim_adam(). Si estuviéramos contentos con el valor predeterminado, tal decisión no sería necesaria.

fitted <- internet %>%
  setup(
    loss = nn_bce_with_logits_loss(),
    optimizer = optim_adam,
    metrics = record(
      luz_metric_binary_accuracy_with_logits()
    )
  ) %>%
  set_hparams(output_size = 1) %>%
  set_opt_hparams(lr = 0.01) %>%
  match(train_dl, epochs = 3, valid_data = valid_dl)

Así es como me quedó el resultado:

predict(fitted, test_dl)

probs <- torch_sigmoid(preds)
print(probs, n = 5)
torch_tensor
 1.2959e-01
 1.3032e-03
 6.1966e-05
 5.9575e-01
 4.5577e-03
... [the output was truncated (use n=-1 to disable)]
[ CPUFloatType{5000} ]

Y eso es todo para un flujo de trabajo completo. En caso de que tenga experiencia previa con Keras, esto le resultará bastante acquainted. Lo mismo puede decirse de la técnica de personalización más versátil pero estandarizada implementada en luz.

Cómo hacer (casi) cualquier cosa (casi) en cualquier momento

Como Keras, luz tiene el concepto de devoluciones de llamada que puede “conectarse” al proceso de capacitación y ejecutar código R arbitrario. Específicamente, el código se puede programar para ejecutarse en cualquiera de los siguientes momentos:

  • cuando comienza o finaliza el proceso basic de formación (on_fit_begin() / on_fit_end());

  • cuando comienza o finaliza una época de entrenamiento más validación (on_epoch_begin() / on_epoch_end());

  • cuando durante una época, la mitad del entrenamiento (validación, respectivamente) comienza o termina (on_train_begin() / on_train_end(); on_valid_begin() / on_valid_end());

  • cuando durante el entrenamiento (validación, respectivamente) un nuevo lote está a punto de procesarse o ya ha sido procesado (on_train_batch_begin() / on_train_batch_end(); on_valid_batch_begin() / on_valid_batch_end());

  • e incluso en puntos de referencia específicos dentro de la lógica de entrenamiento/validación “más interna”, como “después del cálculo de la pérdida”, “después del retroceso” o “después del paso”.

Si bien puedes implementar cualquier lógica que desees usando esta técnica, luz ya viene equipado con un conjunto muy útil de devoluciones de llamada.

Por ejemplo:

  • luz_callback_model_checkpoint() Guarda periódicamente los pesos del modelo.

  • luz_callback_lr_scheduler() permite activar uno de torch‘s programadores de tasa de aprendizaje. Existen diferentes programadores, cada uno de los cuales sigue su propia lógica en cuanto a cómo ajustan dinámicamente la tasa de aprendizaje.

  • luz_callback_early_stopping() finaliza el entrenamiento una vez que el rendimiento del modelo deja de mejorar.

Las devoluciones de llamada se pasan a match() en una lista. Aquí adaptamos nuestro ejemplo anterior, asegurándonos de que (1) los pesos del modelo se guarden después de cada época y (2), el entrenamiento finalice si la pérdida de validación no mejora durante dos épocas seguidas.

fitted <- internet %>%
  setup(
    loss = nn_bce_with_logits_loss(),
    optimizer = optim_adam,
    metrics = record(
      luz_metric_binary_accuracy_with_logits()
    )
  ) %>%
  set_hparams(output_size = 1) %>%
  set_opt_hparams(lr = 0.01) %>%
  match(train_dl,
      epochs = 10,
      valid_data = valid_dl,
      callbacks = record(luz_callback_model_checkpoint(path = "./fashions"),
                       luz_callback_early_stopping(endurance = 2)))

¿Qué pasa con otros tipos de requisitos de flexibilidad, como en el escenario de múltiples modelos que interactúan, equipados, cada uno, con sus propias funciones de pérdida y optimizadores? En tales casos, el código será un poco más largo que lo que hemos estado viendo aquí, pero luz todavía puede ayudar considerablemente a optimizar el flujo de trabajo.

Para concluir, utilizando luzno pierdes nada de la flexibilidad que viene con torchal tiempo que gana mucho en simplicidad, modularidad y mantenibilidad del código. ¡Nos encantaría saber que lo probarás!

¡Gracias por leer!

Foto por JD Rincs en desempaquetar

Krizhevsky, Alex. 2014. “Un truco extraño para paralelizar redes neuronales convolucionales”. CORR abs/1404.5997. http://arxiv.org/abs/1404.5997.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles