O recente anúncio dos nomes do tensorflow 2.0 Execução ansiosa como o recurso central número um da nova versão principal. O que isso significa para os usuários R? Conforme demonstrado em nossa recente postagem sobre tradução da máquina neural, você já pode usar a execução ansiosa de R agora, em combinação com os modelos personalizados Keras e a API de dados. É bom conhecer você pode Use -o – mas por que você deveria? E em que casos?
Nesta e algumas postagens futuras, queremos mostrar como a execução ansiosa pode facilitar o desenvolvimento de modelos. O grau de simplicação dependerá da tarefa – e apenas quanto É mais fácil você encontrar a nova maneira de depender da sua experiência usando a API funcional para modelar relacionamentos mais complexos. Mesmo que você pense que Gans, arquiteturas de codificadores-decodificadores ou transferência de estilo neural não apresentaram problemas antes do advento da execução ansiosa, você pode achar que a alternativa é mais adequada à maneira como nós, humanos, imaginamos problemas mentalmente.
Para esta postagem, estamos portando código de um recente Pocket book colaboratório do Google Implementando a arquitetura DCGAN.(Radford, Metz e Chintala 2015)
Não é necessário conhecimento prévio de Gans – manteremos este put up prático (sem matemática) e focarmos em como atingir seu objetivo, mapeando um conceito simples e vívido em um número surpreendentemente pequeno de linhas de código.
Como no put up sobre tradução da máquina com atenção, primeiro precisamos cobrir alguns pré -requisitos. A propósito, não há necessidade de copiar os trechos de código – você encontrará o código completo em anseer_dcgan.r).
Pré -requisitos
O código nesta postagem depende das versões mais recentes de Cran de vários pacotes Tensorflow R. Você pode instalar estes pacotes da seguinte maneira:
set up.packages(c("tensorflow", "keras", "tfdatasets"))
Você também deve ter certeza de que está executando a versão mais recente do TensorFlow (v1.10), que você pode instalar como assim:
library(tensorflow)
install_tensorflow()
Existem requisitos adicionais para usar a execução ansiosa do TensorFlow. Primeiro, precisamos ligar tfe_enable_eager_execution()
Emblem no início do programa. Segundo, precisamos usar a implementação de Keras incluída no Tensorflow, em vez da implementação da base do Keras.
Também usaremos o tfdatasets Pacote para o nosso pipeline de entrada. Então, acabamos com o seguinte preâmbulo para configurar as coisas:
É isso. Vamos começar.
Então, o que é um gan?
Gan significa Rede adversária generativa(Goodfellow et al. 2014). É uma configuração de dois agentes, o gerador e o discriminadorque agem um contra o outro (assim, adversário). Isso é generativo porque o objetivo é gerar saída (em oposição a, digamos, classificação ou regressão).
No aprendizado humano, o suggestions – direto ou indireto – desempenha um papel central. Digamos que queríamos forjar uma nota (desde que ainda existissem). Supondo que possamos fugir de ensaios malsucedidos, ficaríamos cada vez melhores em falsificação ao longo do tempo. Otimizando nossa técnica, acabaríamos ricos. Este conceito de otimização do suggestions é incorporado no primeiro dos dois agentes, o gerador. Recebe seu suggestions do discriminadorde uma maneira de cabeça para baixo: se pode enganar o discriminador, fazendo com que a nota fosse actual, tudo está bem; Se o discriminador perceber o falso, ele terá que fazer as coisas de maneira diferente. Para uma rede neural, isso significa que ele precisa atualizar seus pesos.
Como o discriminador sabe o que é actual e o que é falso? Ele também precisa ser treinado, em notas reais (ou qualquer que seja o tipo de objetos envolvidos) e os falsos produzidos pelo gerador. Portanto, a configuração completa são dois agentes competindo, um se esforçando para gerar objetos falsos de aparência realista e o outro para negar o engano. O objetivo do treinamento é evoluir e melhorar, por sua vez, fazendo com que o outro também melhore.
Nesse sistema, não há um mínimo objetivo para a função de perda: queremos que ambos os componentes aprendam e getter melhor “no bloqueio”, em vez de um vencer o outro. Isso dificulta a otimização. Na prática, portanto, sintonizar um gan pode parecer mais alquimia do que como ciência, e muitas vezes faz sentido se apoiar nas práticas e “truques” relatados por outros.
Neste exemplo, assim como no Pocket book do Google que estamos transportando, o objetivo é gerar dígitos MNIST. Embora isso possa não parecer a tarefa mais emocionante que se pode imaginar, ela nos permite focar na mecânica e nos permite manter os requisitos de computação e memória (comparativamente) baixos.
Vamos carregar os dados (apenas o conjunto de treinamento necessário) e, em seguida, observe o primeiro ator em nosso drama, o gerador.
Dados de treinamento
mnist <- dataset_mnist()
c(train_images, train_labels) %<-% mnist$prepare
train_images <- train_images %>%
k_expand_dims() %>%
k_cast(dtype = "float32")
# normalize pictures to (-1, 1) as a result of the generator makes use of tanh activation
train_images <- (train_images - 127.5) / 127.5
Nosso conjunto completo de treinamento será transmitido uma vez por época:
buffer_size <- 60000
batch_size <- 256
batches_per_epoch <- (buffer_size / batch_size) %>% spherical()
train_dataset <- tensor_slices_dataset(train_images) %>%
dataset_shuffle(buffer_size) %>%
dataset_batch(batch_size)
Essa entrada será alimentada apenas ao discriminador.
Gerador
Tanto o gerador quanto o discriminador são Modelos personalizados Keras. Em contraste com as camadas personalizadas, os modelos personalizados permitem construir modelos como unidades independentes, completas com lógica de passagem para frente personalizada, backprop e otimização. A função de geração de modelos outline as camadas o modelo (self
) deseja atribuir e retorna a função que implementa o passe direto.
Como veremos em breve, o gerador recebe vetores de ruído aleatório para entrada. Esse vetor é transformado em 3D (altura, largura, canais) e, em seguida, aprimorou sucessivamente o tamanho necessário de saída de (28,28,3).
generator <-
operate(title = NULL) {
keras_model_custom(title = title, operate(self) {
self$fc1 <- layer_dense(models = 7 * 7 * 64, use_bias = FALSE)
self$batchnorm1 <- layer_batch_normalization()
self$leaky_relu1 <- layer_activation_leaky_relu()
self$conv1 <-
layer_conv_2d_transpose(
filters = 64,
kernel_size = c(5, 5),
strides = c(1, 1),
padding = "identical",
use_bias = FALSE
)
self$batchnorm2 <- layer_batch_normalization()
self$leaky_relu2 <- layer_activation_leaky_relu()
self$conv2 <-
layer_conv_2d_transpose(
filters = 32,
kernel_size = c(5, 5),
strides = c(2, 2),
padding = "identical",
use_bias = FALSE
)
self$batchnorm3 <- layer_batch_normalization()
self$leaky_relu3 <- layer_activation_leaky_relu()
self$conv3 <-
layer_conv_2d_transpose(
filters = 1,
kernel_size = c(5, 5),
strides = c(2, 2),
padding = "identical",
use_bias = FALSE,
activation = "tanh"
)
operate(inputs, masks = NULL, coaching = TRUE) {
self$fc1(inputs) %>%
self$batchnorm1(coaching = coaching) %>%
self$leaky_relu1() %>%
k_reshape(form = c(-1, 7, 7, 64)) %>%
self$conv1() %>%
self$batchnorm2(coaching = coaching) %>%
self$leaky_relu2() %>%
self$conv2() %>%
self$batchnorm3(coaching = coaching) %>%
self$leaky_relu3() %>%
self$conv3()
}
})
}
Discriminador
O discriminador é apenas uma rede convolucional bastante regular que gera uma pontuação. Aqui, o uso de “pontuação” em vez de “probabilidade” é de propósito: se você olhar para a última camada, está totalmente conectado, do tamanho 1, mas sem a ativação sigmóide traditional. Isso ocorre porque, diferentemente das keras ‘ loss_binary_crossentropy
a função de perda que usaremos aqui – tf$losses$sigmoid_cross_entropy
– Funciona com os logits brutos, não as saídas do sigmóide.
discriminator <-
operate(title = NULL) {
keras_model_custom(title = title, operate(self) {
self$conv1 <- layer_conv_2d(
filters = 64,
kernel_size = c(5, 5),
strides = c(2, 2),
padding = "identical"
)
self$leaky_relu1 <- layer_activation_leaky_relu()
self$dropout <- layer_dropout(price = 0.3)
self$conv2 <-
layer_conv_2d(
filters = 128,
kernel_size = c(5, 5),
strides = c(2, 2),
padding = "identical"
)
self$leaky_relu2 <- layer_activation_leaky_relu()
self$flatten <- layer_flatten()
self$fc1 <- layer_dense(models = 1)
operate(inputs, masks = NULL, coaching = TRUE) {
inputs %>% self$conv1() %>%
self$leaky_relu1() %>%
self$dropout(coaching = coaching) %>%
self$conv2() %>%
self$leaky_relu2() %>%
self$flatten() %>%
self$fc1()
}
})
}
Definindo a cena
Antes de começarmos o treinamento, precisamos criar os componentes usuais de uma configuração profunda de aprendizado: o modelo (ou modelos, neste caso), as funções de perda e o (s) otimizador (s).
A criação de modelos é apenas uma chamada de função, com um pouco mais no topo:
generator <- generator()
discriminator <- discriminator()
# https://www.tensorflow.org/api_docs/python/tf/contrib/keen/defun
generator$name = tf$contrib$keen$defun(generator$name)
discriminator$name = tf$contrib$keen$defun(discriminator$name)
Defun Compila uma função R (uma vez por combinação diferente de formas de argumento e valores de objetos não tensores)) em um gráfico de tensorflow e é usado para acelerar os cálculos. Isso vem com efeitos colaterais e comportamento possivelmente inesperado – consulte a documentação para os detalhes. Aqui, ficamos principalmente curiosos em quanto de uma aceleração podemos notar ao usá -lo de R – em nosso exemplo, isso resultou em uma aceleração de 130%.
Para as perdas. A perda de discriminador consiste em duas partes: identifica corretamente imagens reais como reais e faz corretamente identificar imagens falsas como falsas. Aqui real_output
e generated_output
Contendo as logits retornadas do discriminador – ou seja, seu julgamento sobre se as respectivas imagens são falsas ou reais.
discriminator_loss <- operate(real_output, generated_output) {
real_loss <- tf$losses$sigmoid_cross_entropy(
multi_class_labels = k_ones_like(real_output),
logits = real_output)
generated_loss <- tf$losses$sigmoid_cross_entropy(
multi_class_labels = k_zeros_like(generated_output),
logits = generated_output)
real_loss + generated_loss
}
A perda de gerador depende de como o discriminador julgou suas criações: esperava que todas elas fossem vistas como reais.
generator_loss <- operate(generated_output) {
tf$losses$sigmoid_cross_entropy(
tf$ones_like(generated_output),
generated_output)
}
Agora ainda precisamos definir otimizadores, um para cada modelo.
discriminator_optimizer <- tf$prepare$AdamOptimizer(1e-4)
generator_optimizer <- tf$prepare$AdamOptimizer(1e-4)
Loop de treinamento
Existem dois modelos, duas funções de perda e dois otimizadores, mas há apenas um loop de treinamento, pois os dois modelos dependem um do outro. O loop de treinamento será sobre imagens MNIST transmitidas em lotes, mas ainda precisamos de entrada para o gerador – um vetor aleatório do tamanho 100, neste caso.
Vamos dar o loop de treinamento passo a passo. Haverá um loop externo e interno, um sobre épocas e outro sobre lotes. No início de cada época, criamos um novo iterador sobre o conjunto de dados:
for (epoch in seq_len(num_epochs)) {
<- Sys.time()
begin <- 0
total_loss_gen <- 0
total_loss_disc <- make_iterator_one_shot(train_dataset) iter
Agora, para cada lote que obtemos do iterador, estamos chamando o gerador e gerando imagens a partir de ruído aleatório. Então, estamos chamando o dicriminator em imagens reais, bem como nas imagens falsas que acabaram de gerar. Para o discriminador, suas saídas relativas são alimentadas diretamente na função de perda. Para o gerador, sua perda dependerá de como o discriminador julgou suas criações:
until_out_of_range({
<- iterator_get_next(iter)
batch <- k_random_normal(c(batch_size, noise_dim))
noise with(tf$GradientTape() %as% gen_tape, { with(tf$GradientTape() %as% disc_tape, {
<- generator(noise)
generated_images <- discriminator(batch, coaching = TRUE)
disc_real_output <-
disc_generated_output discriminator(generated_images, coaching = TRUE)
<- generator_loss(disc_generated_output)
gen_loss <- discriminator_loss(disc_real_output, disc_generated_output)
disc_loss }) })
Observe que todas as chamadas modelo acontecem dentro tf$GradientTape
contextos. Isso é para que os passes avançados possam ser gravados e “reproduzidos” para propagar as perdas através da rede.
Obtenha os gradientes das perdas para as variáveis dos respectivos modelos (tape$gradient
) e peça aos otimizadores que os apliquem aos pesos dos modelos (optimizer$apply_gradients
):
gradients_of_generator <-
gen_tape$gradient(gen_loss, generator$variables)
gradients_of_discriminator <-
disc_tape$gradient(disc_loss, discriminator$variables)
generator_optimizer$apply_gradients(purrr::transpose(
checklist(gradients_of_generator, generator$variables)
))
discriminator_optimizer$apply_gradients(purrr::transpose(
checklist(gradients_of_discriminator, discriminator$variables)
))
total_loss_gen <- total_loss_gen + gen_loss
total_loss_disc <- total_loss_disc + disc_loss
Isso termina o loop sobre lotes. Termine o loop sobre as épocas que exibem perdas atuais e salvando algumas das obras de arte do gerador:
cat("Time for epoch ", epoch, ": ", Sys.time() - begin, "n")
cat("Generator loss: ", total_loss_gen$numpy() / batches_per_epoch, "n")
cat("Discriminator loss: ", total_loss_disc$numpy() / batches_per_epoch, "nn")
if (epoch %% 10 == 0)
generate_and_save_images(generator,
epoch,
random_vector_for_generation)
Aqui está o loop de treinamento novamente, mostrado como um todo – mesmo incluindo as linhas para relatar o progresso, é notavelmente conciso e permite uma rápida compreensão do que está acontecendo:
prepare <- operate(dataset, epochs, noise_dim) {
for (epoch in seq_len(num_epochs)) {
begin <- Sys.time()
total_loss_gen <- 0
total_loss_disc <- 0
iter <- make_iterator_one_shot(train_dataset)
until_out_of_range({
batch <- iterator_get_next(iter)
noise <- k_random_normal(c(batch_size, noise_dim))
with(tf$GradientTape() %as% gen_tape, { with(tf$GradientTape() %as% disc_tape, {
generated_images <- generator(noise)
disc_real_output <- discriminator(batch, coaching = TRUE)
disc_generated_output <-
discriminator(generated_images, coaching = TRUE)
gen_loss <- generator_loss(disc_generated_output)
disc_loss <-
discriminator_loss(disc_real_output, disc_generated_output)
}) })
gradients_of_generator <-
gen_tape$gradient(gen_loss, generator$variables)
gradients_of_discriminator <-
disc_tape$gradient(disc_loss, discriminator$variables)
generator_optimizer$apply_gradients(purrr::transpose(
checklist(gradients_of_generator, generator$variables)
))
discriminator_optimizer$apply_gradients(purrr::transpose(
checklist(gradients_of_discriminator, discriminator$variables)
))
total_loss_gen <- total_loss_gen + gen_loss
total_loss_disc <- total_loss_disc + disc_loss
})
cat("Time for epoch ", epoch, ": ", Sys.time() - begin, "n")
cat("Generator loss: ", total_loss_gen$numpy() / batches_per_epoch, "n")
cat("Discriminator loss: ", total_loss_disc$numpy() / batches_per_epoch, "nn")
if (epoch %% 10 == 0)
generate_and_save_images(generator,
epoch,
random_vector_for_generation)
}
}
Aqui está a função para salvar imagens geradas…
generate_and_save_images <- operate(mannequin, epoch, test_input) {
predictions <- mannequin(test_input, coaching = FALSE)
png(paste0("images_epoch_", epoch, ".png"))
par(mfcol = c(5, 5))
par(mar = c(0.5, 0.5, 0.5, 0.5),
xaxs = 'i',
yaxs = 'i')
for (i in 1:25) {
img <- predictions(i, , , 1)
img <- t(apply(img, 2, rev))
picture(
1:28,
1:28,
img * 127.5 + 127.5,
col = grey((0:255) / 255),
xaxt = 'n',
yaxt = 'n'
)
}
dev.off()
}
… E estamos prontos para ir!
num_epochs <- 150
prepare(train_dataset, num_epochs, noise_dim)
Resultados
Aqui estão algumas imagens geradas após o treinamento para 150 épocas:
Como se costuma dizer, seus resultados certamente variam!
Conclusão
Embora certamente ajustar os Gans proceed sendo um desafio, esperamos que pudéssemos mostrar que o mapeamento de conceitos para codificar não é difícil ao usar a execução ansiosa. Caso você tenha brincado com Gans antes, você pode ter descoberto que precisava prestar atenção cuidadosa para configurar as perdas da maneira certa, congelando os pesos do discriminador quando necessário, and so on. Essa necessidade desaparece com uma execução ansiosa. Nas próximas postagens, mostraremos mais exemplos em que o uso facilita o desenvolvimento do modelo.
Goodfellow, Ian J., Jean Pouget-Abadie, Mehdi Mirza, Bing Xu, David Warde-Farley, Sherjil Ozair, Aaron C. Courville e Yoshua Bengio. 2014. “Redes adversárias generativas”. Em Avanços nos Sistemas de Processamento de Informações Neurais 27: Conferência Anual sobre Sistemas de Processamento de Informações Neurais 2014, 8 a 13 de dezembro de 2014, Montreal, Quebec, Canadá2672-80. http://papers.nips.cc/paper/5423-generative-adversarial-nets.
Radford, Alec, Luke Metz e Soumith Chintala. 2015. “Representação não supervisionada Aprendendo com profundas redes adversárias generativas convolucionais”. Corr ABS/1511.06434. http://arxiv.org/abs/1511.06434.