Posit AI Weblog: Otimizadores em ação



Posit AI Weblog: Otimizadores em ação

Este é o quarto e último capítulo de uma série que apresenta torch básico. Inicialmente, nós focado em tensores. Para ilustrar seu poder, codificamos uma rede neural completa (do tamanho de um brinquedo) do zero. Não aproveitamos nenhum torchcapacidades de nível superior – nem mesmo autograduaçãoseu recurso de diferenciação automática.

Isso mudou no postagem de acompanhamento. Chega de pensar em derivativos e na regra da cadeia; uma única chamada para backward() fiz tudo.

Na terceira postagemo código novamente sofreu uma grande simplificação. Em vez de montar tediosamente um DAG manualmente, deixamos módulos cuide da lógica.

Com base nesse último estado, há apenas mais duas coisas a fazer. Por um lado, ainda calculamos a perda manualmente. E em segundo lugar, embora tenhamos todos os gradientes bem calculados a partir de autograduaçãoainda faremos um loop nos parâmetros do modelo, atualizando-os nós mesmos. Você não ficará surpreso ao saber que nada disso é necessário.

Perdas e funções de perda

torch vem com todas as funções de perda usuais, como erro quadrático médio, entropia cruzada, divergência de Kullback-Leibler e semelhantes. Em geral, existem dois modos de uso.

Veja o exemplo do cálculo do erro quadrático médio. Uma maneira é ligar nnf_mse_loss() diretamente nos tensores de predição e verdade basic. Por exemplo:

x <- torch_randn(c(3, 2, 3))
y <- torch_zeros(c(3, 2, 3))

nnf_mse_loss(x, y)
torch_tensor 
0.682362
( CPUFloatType{} )

Outras funções de perda projetadas para serem chamadas diretamente começam com nnf_ também: nnf_binary_cross_entropy(), nnf_nll_loss(), nnf_kl_div() … e assim por diante.

A segunda maneira é definir o algoritmo antecipadamente e chamá-lo posteriormente. Aqui, todos os respectivos construtores começam com nn_ e terminar em _loss. Por exemplo: nn_bce_loss(), nn_nll_loss(), nn_kl_div_loss()

loss <- nn_mse_loss()

loss(x, y)
torch_tensor 
0.682362
( CPUFloatType{} )

Este método pode ser preferível quando um mesmo algoritmo deve ser aplicado a mais de um par de tensores.

Otimizadores

Até agora, atualizamos os parâmetros do modelo seguindo uma estratégia simples: os gradientes nos informavam qual direção da curva de perda period descendente; a taxa de aprendizagem nos disse o tamanho do passo a ser dado. O que fizemos foi uma implementação simples de descida gradiente.

No entanto, os algoritmos de otimização usados ​​no aprendizado profundo tornam-se muito mais sofisticados do que isso. Abaixo, veremos como substituir nossas atualizações manuais usando optim_adam(), torchimplementação do algoritmo Adam (Kingma e Ba 2017). Porém, primeiro vamos dar uma olhada rápida em como torch os otimizadores funcionam.

Aqui está uma rede muito simples, composta por apenas uma camada linear, a ser chamada em um único ponto de dados.

information <- torch_randn(1, 3)

mannequin <- nn_linear(3, 1)
mannequin$parameters
$weight
torch_tensor 
-0.0385  0.1412 -0.5436
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.1950
( CPUFloatType{1} )

Quando criamos um otimizador, informamos em quais parâmetros ele deve funcionar.

optimizer <- optim_adam(mannequin$parameters, lr = 0.01)
optimizer

  Inherits from: 
  Public:
    add_param_group: perform (param_group) 
    clone: perform (deep = FALSE) 
    defaults: listing
    initialize: perform (params, lr = 0.001, betas = c(0.9, 0.999), eps = 1e-08, 
    param_groups: listing
    state: listing
    step: perform (closure = NULL) 
    zero_grad: perform () 

A qualquer momento, podemos inspecionar esses parâmetros:

optimizer$param_groups((1))$params
$weight
torch_tensor 
-0.0385  0.1412 -0.5436
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.1950
( CPUFloatType{1} )

Agora realizamos os passes para frente e para trás. A passagem para trás calcula os gradientes, mas não não atualize os parâmetros, como podemos ver tanto no modelo e os objetos do otimizador:

out <- mannequin(information)
out$backward()

optimizer$param_groups((1))$params
mannequin$parameters
$weight
torch_tensor 
-0.0385  0.1412 -0.5436
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.1950
( CPUFloatType{1} )

$weight
torch_tensor 
-0.0385  0.1412 -0.5436
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.1950
( CPUFloatType{1} )

Chamando step() no otimizador, na verdade executa as atualizações. Novamente, vamos verificar se o modelo e o otimizador agora mantêm os valores atualizados:

optimizer$step()

optimizer$param_groups((1))$params
mannequin$parameters
NULL
$weight
torch_tensor 
-0.0285  0.1312 -0.5536
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.2050
( CPUFloatType{1} )

$weight
torch_tensor 
-0.0285  0.1312 -0.5536
( CPUFloatType{1,3} )

$bias
torch_tensor 
-0.2050
( CPUFloatType{1} )

Se realizarmos a otimização em um loop, precisamos chamar optimizer$zero_grad() em cada etapa, caso contrário os gradientes seriam acumulados. Você pode ver isso em nossa versão last da rede.

Rede simples: versão last

library(torch)

### generate coaching information -----------------------------------------------------

# enter dimensionality (variety of enter options)
d_in <- 3
# output dimensionality (variety of predicted options)
d_out <- 1
# variety of observations in coaching set
n <- 100


# create random information
x <- torch_randn(n, d_in)
y <- x(, 1, NULL) * 0.2 - x(, 2, NULL) * 1.3 - x(, 3, NULL) * 0.5 + torch_randn(n, 1)



### outline the community ---------------------------------------------------------

# dimensionality of hidden layer
d_hidden <- 32

mannequin <- nn_sequential(
  nn_linear(d_in, d_hidden),
  nn_relu(),
  nn_linear(d_hidden, d_out)
)

### community parameters ---------------------------------------------------------

# for adam, want to decide on a a lot increased studying charge on this drawback
learning_rate <- 0.08

optimizer <- optim_adam(mannequin$parameters, lr = learning_rate)

### coaching loop --------------------------------------------------------------

for (t in 1:200) {
  
  ### -------- Ahead move -------- 
  
  y_pred <- mannequin(x)
  
  ### -------- compute loss -------- 
  loss <- nnf_mse_loss(y_pred, y, discount = "sum")
  if (t %% 10 == 0)
    cat("Epoch: ", t, "   Loss: ", loss$merchandise(), "n")
  
  ### -------- Backpropagation -------- 
  
  # Nonetheless must zero out the gradients earlier than the backward move, solely this time,
  # on the optimizer object
  optimizer$zero_grad()
  
  # gradients are nonetheless computed on the loss tensor (no change right here)
  loss$backward()
  
  ### -------- Replace weights -------- 
  
  # use the optimizer to replace mannequin parameters
  optimizer$step()
}

E é isso! Vimos todos os principais atores no palco: tensores, autograduaçãomódulos, funções de perda e otimizadores. Em postagens futuras, exploraremos como usar tocha para tarefas padrão de aprendizagem profunda envolvendo imagens, texto, dados tabulares e muito mais. Obrigado por ler!

Kingma, Diederik P. e Jimmy Ba. 2017. “Adam: um método para otimização estocástica.” https://arxiv.org/abs/1412.6980.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *