acgan: Use Generator/Discriminator more closely resembling the ones from the referenced paper, and don't train discriminator to produce class labels for generated images
diff --git a/examples/mnist_acgan.py b/examples/mnist_acgan.py
index 3d7a185d0c3..623c69f15e4 100644
--- a/examples/mnist_acgan.py
+++ b/examples/mnist_acgan.py
@@ -34,7 +34,7 @@
 from keras import layers
 from keras.layers import Input, Dense, Reshape, Flatten, Embedding, Dropout
 from keras.layers.advanced_activations import LeakyReLU
-from keras.layers.convolutional import UpSampling2D, Conv2D
+from keras.layers.convolutional import Conv2DTranspose, Conv2D
 from keras.models import Sequential, Model
 from keras.optimizers import Adam
 from keras.utils.generic_utils import Progbar
@@ -49,26 +49,23 @@ def build_generator(latent_size):
     # label drawn from P_c, to image space (..., 28, 28, 1)
     cnn = Sequential()
 
-    cnn.add(Dense(1024, input_dim=latent_size, activation='relu'))
-    cnn.add(Dense(128 * 7 * 7, activation='relu'))
-    cnn.add(Reshape((7, 7, 128)))
+    cnn.add(Dense(3 * 3 * 384, input_dim=latent_size, activation='relu'))
+    cnn.add(Reshape((3, 3, 384)))
+
+    # upsample to (7, 7, ...)
+    cnn.add(Conv2DTranspose(192, 5, strides=1, padding='valid',
+                            activation='relu',
+                            kernel_initializer='glorot_normal'))
 
     # upsample to (14, 14, ...)
-    cnn.add(UpSampling2D(size=(2, 2)))
-    cnn.add(Conv2D(256, 5, padding='same',
-                   activation='relu',
-                   kernel_initializer='glorot_normal'))
+    cnn.add(Conv2DTranspose(96, 5, strides=2, padding='same',
+                            activation='relu',
+                            kernel_initializer='glorot_normal'))
 
     # upsample to (28, 28, ...)
-    cnn.add(UpSampling2D(size=(2, 2)))
-    cnn.add(Conv2D(128, 5, padding='same',
-                   activation='relu',
-                   kernel_initializer='glorot_normal'))
-
-    # take a channel axis reduction
-    cnn.add(Conv2D(1, 2, padding='same',
-                   activation='tanh',
-                   kernel_initializer='glorot_normal'))
+    cnn.add(Conv2DTranspose(1, 5, strides=2, padding='same',
+                            activation='tanh',
+                            kernel_initializer='glorot_normal'))
 
     # this is the z space commonly refered to in GAN papers
     latent = Input(shape=(latent_size, ))
@@ -94,19 +91,19 @@ def build_discriminator():
 
     cnn.add(Conv2D(32, 3, padding='same', strides=2,
                    input_shape=(28, 28, 1)))
-    cnn.add(LeakyReLU())
+    cnn.add(LeakyReLU(0.2))
     cnn.add(Dropout(0.3))
 
     cnn.add(Conv2D(64, 3, padding='same', strides=1))
-    cnn.add(LeakyReLU())
+    cnn.add(LeakyReLU(0.2))
     cnn.add(Dropout(0.3))
 
     cnn.add(Conv2D(128, 3, padding='same', strides=2))
-    cnn.add(LeakyReLU())
+    cnn.add(LeakyReLU(0.2))
     cnn.add(Dropout(0.3))
 
     cnn.add(Conv2D(256, 3, padding='same', strides=1))
-    cnn.add(LeakyReLU())
+    cnn.add(LeakyReLU(0.2))
     cnn.add(Dropout(0.3))
 
     cnn.add(Flatten())
@@ -185,6 +182,16 @@ def build_discriminator():
         num_batches = int(x_train.shape[0] / batch_size)
         progress_bar = Progbar(target=num_batches)
 
+        # we don't want the discriminator to also maximize the classification
+        # accuracy of the auxilary classifier on generated images, so we
+        # don't train discriminator to produce class labels for generated
+        # images (see https://openreview.net/forum?id=rJXTf9Bxg).
+        # To preserve sum of sample weights for the auxilary classifier,
+        # we assign sample weight of 2 to the real images.
+        disc_sample_weight = [np.ones(2 * batch_size),
+                              np.concatenate((np.ones(batch_size) * 2,
+                                              np.zeros(batch_size)))]
+
         epoch_gen_loss = []
         epoch_disc_loss = []
 
@@ -209,12 +216,13 @@ def build_discriminator():
             x = np.concatenate((image_batch, generated_images))
 
             # use soft real/fake labels
-            soft_zero, soft_one = 0.25, 0.75
+            soft_zero, soft_one = 0.1, 0.9
             y = np.array([soft_one] * batch_size + [soft_zero] * batch_size)
             aux_y = np.concatenate((label_batch, sampled_labels), axis=0)
 
             # see if the discriminator can figure itself out...
-            epoch_disc_loss.append(discriminator.train_on_batch(x, [y, aux_y]))
+            epoch_disc_loss.append(discriminator.train_on_batch(
+                x, [y, aux_y], sample_weight=disc_sample_weight))
 
             # make new noise. we generate 2 * batch size here such that we have
             # the generator optimize over an identical number of images as the
@@ -295,8 +303,9 @@ def build_discriminator():
             'params_discriminator_epoch_{0:03d}.hdf5'.format(epoch), True)
 
         # generate some digits to display
-        num_rows = 10
-        noise = np.random.uniform(-1, 1, (num_rows * num_classes, latent_size))
+        num_rows = 40
+        noise = np.tile(np.random.uniform(-1, 1, (num_rows, latent_size)),
+                        (num_classes, 1))
 
         sampled_labels = np.array([
             [i] * num_rows for i in range(num_classes)

This depends on PR https://github.com/fchollet/keras/pull/8452, and should be merged after it.

Generator:
1. Remove extra embedding layer.
2. Remove extra 1x1 convolutions. 
3. Use transposed convolutions instead of NN upsample+conv (=conv with fractional slides). Note that this corresponds to the paper, but contrary to author's own later advice in [Deconvolution and Checkerboard Artifacts](https://distill.pub/2016/deconv-checkerboard/). Still, I found that paper network works better on MNIST in this example.

Discriminator:
1. Use LeakyReLU slope 0.2, instead of default 0.3

These changes
1. made generated images more diverse (for example, thick and thin are on the same epoch, compare https://github.com/fchollet/keras/pull/8482#issuecomment-344401396 to https://github.com/fchollet/keras/pull/8409#issue-271565775)
2. Can tolerate harder soft_labels (all the way to 1.0 works without generator collapse, but the results are not as good as 0.9).
3. Reduced the number of trainable parameters in generator by 65% from 8,698,356 to 3,182,580.
4. Reduced training time per epoch by 50% from 90 sec to 45 sec on Titan X Maxwell.
