Como a IA aprende a gerar imagens de gatos



Como a tradução de IA pode aprender a gerar imagens de gatos .

A pesquisa sobre redes adversas generativas (GAN), publicada em 2014, foi uma inovação no campo dos modelos generativos. O pesquisador principal, Yann Lekun, chamou as redes adversárias de "a melhor idéia em aprendizado de máquina nos últimos vinte anos". Hoje, graças a essa arquitetura, podemos criar uma IA que gera imagens realistas de gatos. Legal!


DCGAN durante o treinamento

Todo o código de trabalho está no repositório do Github . Será útil se você tiver alguma experiência em programação em Python, aprendizado profundo, trabalho com Tensorflow e redes neurais convolucionais.

E se você é novo no aprendizado profundo, recomendo que você se familiarize com a excelente série de artigos Machine Learning is Fun!

O que é o DCGAN?


As redes adversas generativas convolucionais profundas (DCGAN) são uma arquitetura de aprendizado profundo que gera dados semelhantes aos dados do conjunto de treinamento.

Este modelo substitui por camadas convolucionais as camadas totalmente conectadas da rede adversária generativa. Para entender como o DCGAN funciona, usamos a metáfora do confronto entre um crítico de arte especialista e um falsificador.

O falsificador ("gerador") está tentando criar uma imagem falsa de Van Gogh e transmiti-la como uma imagem real.



Um crítico de arte ("discriminador") está tentando condenar um falsificador, usando seu conhecimento das telas reais de Van Gogh.



Com o tempo, o crítico de arte está cada vez mais definindo falsificações, e o falsificador as torna mais perfeitas.


Como você pode ver, os DCGANs são compostos por duas redes neurais de aprendizado profundo separadas que competem entre si.

  • O gerador está tentando criar dados confiáveis. Ele não sabe quais são os dados reais, mas aprende com as respostas da rede neural inimiga, alterando os resultados de seu trabalho a cada iteração.
  • O discriminador tenta determinar os dados falsos (comparando com os reais), evitando o máximo possível de falsos positivos em relação aos dados reais. O resultado desse modelo é o feedback para o gerador.


Esquema DCGAN.

  • O gerador pega um vetor de ruído aleatório e gera uma imagem.
  • A imagem é dada ao discriminador, ele a compara com a amostra de treinamento.
  • O discriminador retorna um número - 0 (falso) ou 1 (imagem real).

Vamos criar um DCGAN!


Agora estamos prontos para criar nossa própria IA.

Nesta parte, focaremos nos principais componentes do nosso modelo. Se você quiser ver o código inteiro, clique aqui .

Dados de entrada


Crie stubs para a entrada: inputs_real para o discriminador e inputs_z para o gerador. Observe que teremos duas taxas de aprendizado, separadamente para o gerador e o discriminador.

Os DCGANs são muito sensíveis aos hiperparâmetros, por isso é muito importante ajustá-los.
def model_inputs(real_dim, z_dim):

 """ Create the model inputs :param real_dim: tuple containing width, height and channels :param z_dim: The dimension of Z :return: Tuple of (tensor of real input images, tensor of z data, learning rate G, learning rate D) """ # inputs_real for Discriminator inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='inputs_real') # inputs_z for Generator inputs_z = tf.placeholder(tf.float32, (None, z_dim), name="input_z") # Two different learning rate : one for the generator, one for the discriminator learning_rate_G = tf.placeholder(tf.float32, name="learning_rate_G") learning_rate_D = tf.placeholder(tf.float32, name="learning_rate_D") return inputs_real, inputs_z, learning_rate_G, learning_rate_D 

Discriminador e gerador


Usamos tf.variable_scope por dois motivos.

Primeiro, verifique se todos os nomes de variáveis ​​começam com gerador / discriminador. Mais tarde, isso nos ajudará a treinar duas redes neurais.
Em segundo lugar, reutilizaremos essas redes com diferentes dados de entrada:

  • Treinaremos o gerador e, em seguida, coletaremos uma amostra das imagens geradas por ele.
  • No discriminador, compartilharemos variáveis ​​para imagens de entrada falsas e reais.



Vamos criar um discriminador. Lembre-se de que, como entrada, obtém uma imagem real ou falsa e retorna 0 ou 1 em resposta.

Algumas notas:

  • Precisamos dobrar o tamanho do filtro em cada camada convolucional.
  • O uso de downsampling não é recomendado. Em vez disso, apenas as camadas convolucionais removidas são aplicáveis.
  • Em cada camada, usamos a normalização em lote (com exceção da camada de entrada), pois isso reduz a mudança de covariância. Leia mais neste maravilhoso artigo .
  • Usaremos o Leaky ReLU como uma função de ativação, o que ajudará a evitar o efeito do gradiente “desaparecendo”.

 def discriminator(x, is_reuse=False, alpha = 0.2): ''' Build the discriminator network. Arguments --------- x : Input tensor for the discriminator n_units: Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out, logits: ''' with tf.variable_scope("discriminator", reuse = is_reuse): # Input layer 128*128*3 --> 64x64x64 # Conv --> BatchNorm --> LeakyReLU conv1 = tf.layers.conv2d(inputs = x, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv1') batch_norm1 = tf.layers.batch_normalization(conv1, training = True, epsilon = 1e-5, name = 'batch_norm1') conv1_out = tf.nn.leaky_relu(batch_norm1, alpha=alpha, name="conv1_out") # 64x64x64--> 32x32x128 # Conv --> BatchNorm --> LeakyReLU conv2 = tf.layers.conv2d(inputs = conv1_out, filters = 128, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv2') batch_norm2 = tf.layers.batch_normalization(conv2, training = True, epsilon = 1e-5, name = 'batch_norm2') conv2_out = tf.nn.leaky_relu(batch_norm2, alpha=alpha, name="conv2_out") # 32x32x128 --> 16x16x256 # Conv --> BatchNorm --> LeakyReLU conv3 = tf.layers.conv2d(inputs = conv2_out, filters = 256, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv3') batch_norm3 = tf.layers.batch_normalization(conv3, training = True, epsilon = 1e-5, name = 'batch_norm3') conv3_out = tf.nn.leaky_relu(batch_norm3, alpha=alpha, name="conv3_out") # 16x16x256 --> 16x16x512 # Conv --> BatchNorm --> LeakyReLU conv4 = tf.layers.conv2d(inputs = conv3_out, filters = 512, kernel_size = [5, 5], strides = [1, 1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv4') batch_norm4 = tf.layers.batch_normalization(conv4, training = True, epsilon = 1e-5, name = 'batch_norm4') conv4_out = tf.nn.leaky_relu(batch_norm4, alpha=alpha, name="conv4_out") # 16x16x512 --> 8x8x1024 # Conv --> BatchNorm --> LeakyReLU conv5 = tf.layers.conv2d(inputs = conv4_out, filters = 1024, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv5') batch_norm5 = tf.layers.batch_normalization(conv5, training = True, epsilon = 1e-5, name = 'batch_norm5') conv5_out = tf.nn.leaky_relu(batch_norm5, alpha=alpha, name="conv5_out") # Flatten it flatten = tf.reshape(conv5_out, (-1, 8*8*1024)) # Logits logits = tf.layers.dense(inputs = flatten, units = 1, activation = None) out = tf.sigmoid(logits) return out, logits 



Nós criamos um gerador. Lembre-se de que ele recebe o vetor de ruído (z) como entrada e, graças às camadas de convolução transpostas, cria uma imagem falsa.

Em cada camada, reduzimos pela metade o tamanho do filtro e também dobramos o tamanho da imagem.

O gerador funciona melhor ao usar tanh como função de ativação de saída.

 def generator(z, output_channel_dim, is_train=True): ''' Build the generator network. Arguments --------- z : Input tensor for the generator output_channel_dim : Shape of the generator output n_units : Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out: ''' with tf.variable_scope("generator", reuse= not is_train): # First FC layer --> 8x8x1024 fc1 = tf.layers.dense(z, 8*8*1024) # Reshape it fc1 = tf.reshape(fc1, (-1, 8, 8, 1024)) # Leaky ReLU fc1 = tf.nn.leaky_relu(fc1, alpha=alpha) # Transposed conv 1 --> BatchNorm --> LeakyReLU # 8x8x1024 --> 16x16x512 trans_conv1 = tf.layers.conv2d_transpose(inputs = fc1, filters = 512, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv1") batch_trans_conv1 = tf.layers.batch_normalization(inputs = trans_conv1, training=is_train, epsilon=1e-5, name="batch_trans_conv1") trans_conv1_out = tf.nn.leaky_relu(batch_trans_conv1, alpha=alpha, name="trans_conv1_out") # Transposed conv 2 --> BatchNorm --> LeakyReLU # 16x16x512 --> 32x32x256 trans_conv2 = tf.layers.conv2d_transpose(inputs = trans_conv1_out, filters = 256, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv2") batch_trans_conv2 = tf.layers.batch_normalization(inputs = trans_conv2, training=is_train, epsilon=1e-5, name="batch_trans_conv2") trans_conv2_out = tf.nn.leaky_relu(batch_trans_conv2, alpha=alpha, name="trans_conv2_out") # Transposed conv 3 --> BatchNorm --> LeakyReLU # 32x32x256 --> 64x64x128 trans_conv3 = tf.layers.conv2d_transpose(inputs = trans_conv2_out, filters = 128, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv3") batch_trans_conv3 = tf.layers.batch_normalization(inputs = trans_conv3, training=is_train, epsilon=1e-5, name="batch_trans_conv3") trans_conv3_out = tf.nn.leaky_relu(batch_trans_conv3, alpha=alpha, name="trans_conv3_out") # Transposed conv 4 --> BatchNorm --> LeakyReLU # 64x64x128 --> 128x128x64 trans_conv4 = tf.layers.conv2d_transpose(inputs = trans_conv3_out, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv4") batch_trans_conv4 = tf.layers.batch_normalization(inputs = trans_conv4, training=is_train, epsilon=1e-5, name="batch_trans_conv4") trans_conv4_out = tf.nn.leaky_relu(batch_trans_conv4, alpha=alpha, name="trans_conv4_out") # Transposed conv 5 --> tanh # 128x128x64 --> 128x128x3 logits = tf.layers.conv2d_transpose(inputs = trans_conv4_out, filters = 3, kernel_size = [5,5], strides = [1,1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="logits") out = tf.tanh(logits, name="out") return out 

Perdas no discriminador e gerador


Como treinamos o gerador e o discriminador, precisamos calcular as perdas para as duas redes neurais. O discriminador deve dar 1 quando "considerar" a imagem como real e 0 se a imagem for falsa. De acordo com isso e você precisa configurar a perda. A perda discriminadora é calculada como a soma das perdas para a imagem real e falsa:

d_loss = d_loss_real + d_loss_fake

onde d_loss_real é a perda quando o discriminador considera a imagem falsa, mas na verdade é real. É calculado da seguinte forma:

  • Usamos d_logits_real , todos os rótulos são iguais a 1 (porque todos os dados são reais).
  • labels = tf.ones_like(tensor) * (1 - smooth) . Vamos usar a suavização de etiqueta: abaixe os valores da etiqueta de 1,0 para 0,9 para ajudar o discriminador a generalizar melhor.

d_loss_fake é uma perda quando o discriminador considera a imagem real, mas na verdade é falsa.

  • Usamos d_logits_fake , todos os rótulos são 0.

Para perder o gerador, é usado o d_logits_fake do discriminador. Desta vez, todos os rótulos são 1, porque o gerador deseja enganar o discriminador.

 def model_loss(input_real, input_z, output_channel_dim, alpha): """ Get the loss for the discriminator and generator :param input_real: Images from the real dataset :param input_z: Z input :param out_channel_dim: The number of channels in the output image :return: A tuple of (discriminator loss, generator loss) """ # Generator network here g_model = generator(input_z, output_channel_dim) # g_model is the generator output # Discriminator network here d_model_real, d_logits_real = discriminator(input_real, alpha=alpha) d_model_fake, d_logits_fake = discriminator(g_model,is_reuse=True, alpha=alpha) # Calculate losses d_loss_real = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_model_real))) d_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake))) d_loss = d_loss_real + d_loss_fake g_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake))) return d_loss, g_loss 

Optimizers


Após o cálculo das perdas, o gerador e o discriminador devem ser atualizados individualmente. Para fazer isso, use tf.trainable_variables() criar uma lista de todas as variáveis ​​definidas em nosso gráfico.

 def model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1): """ Get optimization operations :param d_loss: Discriminator loss Tensor :param g_loss: Generator loss Tensor :param learning_rate: Learning Rate Placeholder :param beta1: The exponential decay rate for the 1st moment in the optimizer :return: A tuple of (discriminator training operation, generator training operation) """ # Get the trainable_variables, split into G and D parts t_vars = tf.trainable_variables() g_vars = [var for var in t_vars if var.name.startswith("generator")] d_vars = [var for var in t_vars if var.name.startswith("discriminator")] update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) # Generator update gen_updates = [op for op in update_ops if op.name.startswith('generator')] # Optimizers with tf.control_dependencies(gen_updates): d_train_opt = tf.train.AdamOptimizer(learning_rate=lr_D, beta1=beta1).minimize(d_loss, var_list=d_vars) g_train_opt = tf.train.AdamOptimizer(learning_rate=lr_G, beta1=beta1).minimize(g_loss, var_list=g_vars) return d_train_opt, g_train_opt 

Treinamento


Agora implementamos a função de treinamento. A ideia é bem simples:

  • Salvamos nosso modelo a cada cinco períodos (época).
  • Nós salvamos a imagem na pasta com imagens a cada 10 lotes treinados.
  • A cada 15 períodos, exibimos g_loss , d_loss e a imagem gerada. Isso ocorre porque o notebook Jupyter pode falhar ao exibir muitas fotos.
  • Ou podemos gerar diretamente imagens reais carregando um modelo salvo (isso economizará 20 horas de treinamento).

 def train(epoch_count, batch_size, z_dim, learning_rate_D, learning_rate_G, beta1, get_batches, data_shape, data_image_mode, alpha): """ Train the GAN :param epoch_count: Number of epochs :param batch_size: Batch Size :param z_dim: Z dimension :param learning_rate: Learning Rate :param beta1: The exponential decay rate for the 1st moment in the optimizer :param get_batches: Function to get batches :param data_shape: Shape of the data :param data_image_mode: The image mode to use for images ("RGB" or "L") """ # Create our input placeholders input_images, input_z, lr_G, lr_D = model_inputs(data_shape[1:], z_dim) # Losses d_loss, g_loss = model_loss(input_images, input_z, data_shape[3], alpha) # Optimizers d_opt, g_opt = model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1) i = 0 version = "firstTrain" with tf.Session() as sess: sess.run(tf.global_variables_initializer()) # Saver saver = tf.train.Saver() num_epoch = 0 if from_checkpoint == True: saver.restore(sess, "./models/model.ckpt") show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) else: for epoch_i in range(epoch_count): num_epoch += 1 if num_epoch % 5 == 0: # Save model every 5 epochs #if not os.path.exists("models/" + version): # os.makedirs("models/" + version) save_path = saver.save(sess, "./models/model.ckpt") print("Model saved") for batch_images in get_batches(batch_size): # Random noise batch_z = np.random.uniform(-1, 1, size=(batch_size, z_dim)) i += 1 # Run optimizers _ = sess.run(d_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_D: learning_rate_D}) _ = sess.run(g_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_G: learning_rate_G}) if i % 10 == 0: train_loss_d = d_loss.eval({input_z: batch_z, input_images: batch_images}) train_loss_g = g_loss.eval({input_z: batch_z}) # Save it image_name = str(i) + ".jpg" image_path = "./images/" + image_name show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) # Print every 5 epochs (for stability overwize the jupyter notebook will bug) if i % 1500 == 0: image_name = str(i) + ".jpg" image_path = "./images/" + image_name print("Epoch {}/{}...".format(epoch_i+1, epochs), "Discriminator Loss: {:.4f}...".format(train_loss_d), "Generator Loss: {:.4f}".format(train_loss_g)) show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, False, True) return losses, samples 

Como executar


Tudo isso pode ser executado diretamente no seu computador, se você estiver pronto para aguardar 10 anos, por isso é melhor usar serviços de GPU baseados em nuvem como AWS ou FloydHub. Pessoalmente, treinei esse DCGAN por 20 horas no Microsoft Azure e na Máquina Virtual de Aprendizado Profundo . Não tenho um relacionamento comercial com o Azure, apenas gosto do serviço ao cliente.

Se você tiver alguma dificuldade em executar em uma máquina virtual, consulte este maravilhoso artigo .

Se você melhorar o modelo, sinta-se à vontade para fazer uma solicitação de recebimento.

Source: https://habr.com/ru/post/pt416129/


All Articles