Wie KI lernt, Katzenbilder zu erzeugen



Wie KI- Übersetzung lernen kann, Bilder von Katzen zu erzeugen .

Die 2014 veröffentlichte GAN-Forschung ( Generative Adversarial Nets ) war ein Durchbruch auf dem Gebiet der generativen Modelle. Der leitende Forscher Yann Lekun bezeichnete gegnerische Netze als "die beste Idee für maschinelles Lernen in den letzten zwanzig Jahren". Dank dieser Architektur können wir heute eine KI erstellen, die realistische Bilder von Katzen erzeugt. Cool!


DCGAN während des Trainings

Der gesamte Arbeitscode befindet sich im Github-Repository . Es ist hilfreich für Sie, wenn Sie Erfahrung in der Python-Programmierung, im Deep Learning, in der Arbeit mit Tensorflow und in Faltungs-Neuronalen Netzen haben.

Und wenn Sie mit Deep Learning noch nicht vertraut sind, empfehle ich Ihnen, sich mit der hervorragenden Artikelserie vertraut zu machen. Maschinelles Lernen macht Spaß!

Was ist DCGAN?


Deep Convolutional Generative Adverserial Networks (DCGAN) ist eine Deep-Learning-Architektur, die Daten generiert, die den Daten aus dem Trainingssatz ähnlich sind.

Dieses Modell ersetzt die vollständig verbundenen Schichten des generativen gegnerischen Netzwerks durch Faltungsschichten. Um zu verstehen, wie DCGAN funktioniert, verwenden wir die Metapher der Konfrontation zwischen einem erfahrenen Kunstkritiker und einem Fälscher.

Der Fälscher („Generator“) versucht, ein gefälschtes Van-Gogh-Bild zu erstellen und es als echtes Bild auszugeben.



Ein Kunstkritiker („Diskriminator“) versucht, einen Fälscher zu verurteilen, indem er sein Wissen über die wirklichen Leinwände von Van Gogh nutzt.



Im Laufe der Zeit definiert der Kunstkritiker zunehmend Fälschungen, und der Fälscher macht sie alle perfekter.


Wie Sie sehen können, bestehen DCGANs aus zwei getrennten neuronalen Deep-Learning-Netzen, die miteinander konkurrieren.

  • Der Generator versucht glaubwürdige Daten zu erstellen. Er weiß nicht, was die realen Daten sind, aber er lernt aus den Reaktionen des feindlichen neuronalen Netzwerks und ändert die Ergebnisse seiner Arbeit mit jeder Iteration.
  • Der Diskriminator versucht, die gefälschten Daten zu bestimmen (im Vergleich zu den realen), wobei Fehlalarme in Bezug auf die realen Daten so weit wie möglich vermieden werden. Das Ergebnis dieses Modells ist eine Rückmeldung für den Generator.


DCGAN-Schema.

  • Der Generator nimmt einen zufälligen Rauschvektor und erzeugt ein Bild.
  • Das Bild wird dem Diskriminator gegeben, er vergleicht es mit dem Trainingsmuster.
  • Der Diskriminator gibt eine Zahl zurück - 0 (Fälschung) oder 1 (reales Bild).

Erstellen wir ein DCGAN!


Jetzt sind wir bereit, unsere eigene KI zu erstellen.

In diesem Teil konzentrieren wir uns auf die Hauptkomponenten unseres Modells. Wenn Sie den gesamten Code sehen möchten, klicken Sie hier .

Daten eingeben


Erstellen Sie Stubs für den Eingang: inputs_real für den Diskriminator und inputs_z für den Generator. Bitte beachten Sie, dass wir zwei Lernraten haben, getrennt für den Generator und den Diskriminator.

DCGANs reagieren sehr empfindlich auf Hyperparameter, daher ist es sehr wichtig, sie zu optimieren.
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 

Diskriminator und Generator


Wir verwenden tf.variable_scope aus zwei Gründen.

Stellen Sie zunächst sicher, dass alle Variablennamen mit Generator / Diskriminator beginnen. Dies wird uns später beim Training von zwei neuronalen Netzen helfen.
Zweitens werden wir diese Netzwerke mit unterschiedlichen Eingabedaten wiederverwenden:

  • Wir werden den Generator trainieren und dann eine Probe der von ihm erzeugten Bilder nehmen.
  • Im Diskriminator teilen wir Variablen für gefälschte und echte Eingabebilder.



Lassen Sie uns einen Diskriminator erstellen. Denken Sie daran, dass als Eingabe ein echtes oder falsches Bild aufgenommen wird und als Antwort 0 oder 1 zurückgegeben wird.

Ein paar Anmerkungen:

  • Wir müssen die Filtergröße in jeder Faltungsschicht verdoppeln.
  • Die Verwendung von Downsampling wird nicht empfohlen. Stattdessen sind nur abgestreifte Faltungsschichten anwendbar.
  • In jeder Schicht verwenden wir die Batch-Normalisierung (mit Ausnahme der Eingabeebene), da dies die Kovarianzverschiebung verringert. Lesen Sie mehr in diesem wunderbaren Artikel .
  • Wir werden Leaky ReLU als Aktivierungsfunktion verwenden, um den Effekt des "Verschwindens" des Gradienten zu vermeiden.

 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 



Wir haben einen Generator erstellt. Denken Sie daran, dass der Rauschvektor (z) als Eingabe verwendet wird und dank der transponierten Faltungsschichten ein falsches Bild erstellt wird.

Auf jeder Ebene halbieren wir die Größe des Filters und verdoppeln auch die Größe des Bildes.

Der Generator funktioniert am besten, wenn tanh als Ausgangsaktivierungsfunktion verwendet wird.

 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 

Verluste im Diskriminator und Generator


Da wir sowohl den Generator als auch den Diskriminator trainieren, müssen wir die Verluste für beide neuronalen Netze berechnen. Der Diskriminator sollte 1 geben, wenn er das Bild als echt betrachtet, und 0, wenn das Bild gefälscht ist. Entsprechend müssen Sie den Verlust konfigurieren. Der Diskriminatorverlust wird als Summe der Verluste für das reale und das falsche Bild berechnet:

d_loss = d_loss_real + d_loss_fake

Dabei ist d_loss_real der Verlust, wenn der Diskriminator das Bild als falsch betrachtet, es aber tatsächlich real ist. Es wird wie folgt berechnet:

  • Wir verwenden d_logits_real , alle Labels sind gleich 1 (da alle Daten real sind).
  • labels = tf.ones_like(tensor) * (1 - smooth) . Verwenden wir die Etikettenglättung: Verringern Sie die Etikettenwerte von 1,0 auf 0,9, damit der Diskriminator besser verallgemeinern kann.

d_loss_fake ist ein Verlust, wenn der Diskriminator das Bild als real betrachtet, es aber tatsächlich gefälscht ist.

  • Wir verwenden d_logits_fake , alle Labels sind 0.

Um den Generator zu verlieren, wird d_logits_fake vom Diskriminator verwendet. Dieses Mal sind alle Bezeichnungen 1, weil der Generator den Diskriminator austricksen will.

 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 

Optimierer


Nach der Berechnung der Verluste müssen Generator und Diskriminator einzeln aktualisiert werden. Verwenden Sie dazu tf.trainable_variables() um eine Liste aller in unserem Diagramm definierten Variablen tf.trainable_variables() erstellen.

 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 

Schulung


Jetzt implementieren wir die Trainingsfunktion. Die Idee ist ziemlich einfach:

  • Wir speichern unser Modell alle fünf Perioden (Epoche).
  • Wir speichern das Bild alle 10 trainierten Stapel im Ordner mit Bildern.
  • Alle 15 Perioden zeigen wir g_loss , d_loss und das generierte Bild an. Dies liegt daran, dass das Jupyter-Notebook abstürzen kann, wenn zu viele Bilder angezeigt werden.
  • Oder wir können direkt echte Bilder erzeugen, indem wir ein gespeichertes Modell laden (dies spart 20 Stunden Training).

 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 

Wie man läuft


All dies kann direkt auf Ihrem Computer ausgeführt werden, wenn Sie bereit sind, 10 Jahre zu warten. Daher ist es besser, Cloud-basierte GPU-Dienste wie AWS oder FloydHub zu verwenden. Persönlich habe ich dieses DCGAN 20 Stunden lang auf Microsoft Azure und ihrer Deep Learning Virtual Machine trainiert. Ich habe keine Geschäftsbeziehung mit Azure, ich mag nur den Kundenservice.

Wenn Sie Probleme beim Ausführen in einer virtuellen Maschine haben, lesen Sie diesen wunderbaren Artikel .

Wenn Sie das Modell verbessern, können Sie eine Pull-Anfrage stellen.

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


All Articles