《生成对抗网络入门指南【2】》

 

 

 

深度卷积生成对抗网络(DCGAN)

介绍:最基本的GAN版本,在实际运用中很少使用,而深度卷积对抗生成网络(DCGAN)由于网络训练状态稳定,并可以有效实现高质量的图片生成以及相关的生成模型应用,在实际工程中有广泛的使用,在它之后的大量的GAN模型是在它基础上改进的。

《Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks》

论文链接:

为了使得GAN能够很好的适应于卷积神经网络,DCGAN提出了以下4点架构设计规则:

1、卷积层代替池化层:为了能够让网络自身去学习空间上采样与下采样,使得判别器和生成器都有效的具备相应的能力。

2、去除全连接层

3、使用批归一化

4、使用恰当的激活函数

下面是对上面四点的详细介绍:

1.1对于判别器,我们使用步长卷积(strided convolution)来代替池化层,对于生成器,使用分数步长卷积(fractional-strided convolutions)代替池化。其中,步长卷积在判别器中进行空间下采样(spatial downsampling)。示例如下:输入为5x5的矩阵,使用3x3的过滤器,步长为2x2,最终输出为3x3.

步长卷积示意图(strided convolution)

下图展示是“分数步长卷积”,其在生成器中进行上采样(spatial upsampling),输入为3x3矩阵,也使用3x3的卷积核,步长为1x1,故在每个输入矩阵的点之间填充一个0,最终输出为5x5.

分数步长卷积示意图

2.1去除全连接层,现在很多研究都在试图去除全连接层,在常规的CNN中,在卷积层的后面添加全连接层以输出最终向量,但由于全连接层参数过多容易使网络过拟合。有研究使用了全局平均池化来代替全连接层,可以使得模型更稳定,但会影响收敛速度

该论文中提出了一种折中方案,将生成器的随机输入直接与卷积层特征输入进行连接,同样,判别器的输出层与卷积层的输出特征连接

3、使用批归一化:由于深度学习的神经网络层数很多,每一层都会使得输出数据的分布发生变化,随着层数的增加,网络的整体偏差会越来越大。批归一化的目标则是为了解决这一问题,通过对每一层的输入进行归一化处理,能够有效的使得数据服从某个固定的数据分布。

4、激活函数的作用是为了在神经网络中进行非线性变换,如sigmoid、tanh、Relu、leakRelu。

在DCGAN中,生成器和判别器使用不同的激活函数。在生成器中使用ReLU函数,但在输出层使用了tanh函数,因为发现使用有边界的激活函数可以让模型更快的学习,并能快速覆盖色彩空间。在判别器中对所有层使用leakyRelu。

代码如下:

from __future__ import print_function, division

from keras.datasets import mnist
from keras.layers import Input, Dense, Reshape, Flatten, Dropout
from keras.layers import BatchNormalization, Activation, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D
from keras.models import Sequential, Model
from keras.optimizers import Adam

import matplotlib.pyplot as plt

import sys

import numpy as np

class DCGAN():
    def __init__(self):
        # Input shape
        self.img_rows = 28
        self.img_cols = 28
        self.channels = 1
        self.img_shape = (self.img_rows, self.img_cols, self.channels)
        self.latent_dim = 100

        optimizer = Adam(0.0002, 0.5)

        # Build and compile the discriminator
        self.discriminator = self.build_discriminator()
        self.discriminator.compile(loss='binary_crossentropy',
            optimizer=optimizer,
            metrics=['accuracy'])

        # Build the generator
        self.generator = self.build_generator()

        # The generator takes noise as input and generates imgs
        z = Input(shape=(self.latent_dim,))
        img = self.generator(z)

        # For the combined model we will only train the generator
        self.discriminator.trainable = False

        # The discriminator takes generated images as input and determines validity
        valid = self.discriminator(img)

        # The combined model  (stacked generator and discriminator)
        # Trains the generator to fool the discriminator
        self.combined = Model(z, valid)
        self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)

    def build_generator(self):

        model = Sequential()
        #输入数据为100维的随机数据z,经过一系列分数步长卷积后,最后形成一幅64x64x3的RGB图片
        #先经过一层全连接层,变成128 * 7 * 7个元素矩阵,然后调整成7, 7, 128
        model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim))
        model.add(Reshape((7, 7, 128)))
        #采用最邻近插值算法进行upsampling 默认strides=(2,2)
        model.add(UpSampling2D())#然后上采样  变成14x14x128

        #对上采样的结果进行卷积操作,padding使用same方式,所以结果仍然为128
        model.add(Conv2D(128, kernel_size=3, padding="same"))#使用128个卷积核 结果仍然是14x14x128
        model.add(BatchNormalization(momentum=0.8))
        model.add(Activation("relu"))
        model.add(UpSampling2D())#然后上采样 变成28x28x128

        # 使用64个卷积核 结果仍然是28x28x64
        model.add(Conv2D(64, kernel_size=3, padding="same"))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Activation("relu"))
        ##使用3个卷积核 结果仍然是28x28x3 最终结果和训练集一样
        model.add(Conv2D(self.channels, kernel_size=3, padding="same"))
        #在输出层使用了tanh函数,因为发现使用有边界的激活函数可以让模型更快的学习,并能快速覆盖色彩空间
        #并且tanh比sigmoid更有优点
        model.add(Activation("tanh"))

        model.summary()#输出模型各层的参数状况
        #上面是搭建模型的过程,下面是使用模型
        noise = Input(shape=(self.latent_dim,))
        img = model(noise)
        #返回一个Model实例,其中包含compile方法。
        return Model(noise, img)

    def build_discriminator(self):
        #判别器更像是传统的判别图像类型的方法,就是卷积、激活、池化、全连接,分类函数
        model = Sequential()
        #输入是28x28x1 padding="same"表示输出结果大小为(n/s)的向上取整,"valid"的输出是(n-f+1)的向上取整
        #所以下面一句话的执行结果为14x14x32
        model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same"))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.25))

        #使用64个卷积核,"same"填充。输出结果为7x7x64
        model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
        model.add(ZeroPadding2D(padding=((0,1),(0,1))))
        model.add(BatchNormalization(momentum=0.8))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.25))

        #4x4x128
        model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
        model.add(BatchNormalization(momentum=0.8))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.25))
        #4x4x256
        model.add(Conv2D(256, kernel_size=3, strides =1, padding="same"))
        model.add(BatchNormalization(momentum=0.8))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.25))

        model.add(Flatten())
        model.add(Dense(1, activation='sigmoid'))

        model.summary()

        img = Input(shape=self.img_shape)
        validity = model(img)

        return Model(img, validity)

    def train(self, epochs, batch_size=128, save_interval=50):

        # Load the dataset
        (X_train, _), (_, _) = mnist.load_data()

        # Rescale -1 to 1
        X_train = X_train / 127.5 - 1.
        X_train = np.expand_dims(X_train, axis=3)

        # Adversarial ground truths
        valid = np.ones((batch_size, 1))
        fake = np.zeros((batch_size, 1))

        for epoch in range(epochs):

            # ---------------------
            #  Train Discriminator
            # ---------------------

            # Select a random half of images
            #随机生成128个[0,59999]之间的数据
            idx = np.random.randint(0, X_train.shape[0], batch_size)
            #从训练数据集中随机选取128张图片作为真实图片
            imgs = X_train[idx]

            # Sample noise and generate a batch of new images
            #生成128x10大小的且元素服从标准正太分布的矩阵
            noise = np.random.normal(0, 1, (batch_size, self.latent_dim))
            #根据矩阵元素,生成128张伪图片
            gen_imgs = self.generator.predict(noise)

            # Train the discriminator (real classified as ones and generated as zeros)
            #为真实图片和伪图片分配标签
            d_loss_real = self.discriminator.train_on_batch(imgs, valid)
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake)
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

            # ---------------------
            #  Train Generator
            # ---------------------

            # Train the generator (wants discriminator to mistake images as real)
            #生成器G希望给出噪声数据,但生成的伪数据标签为1
            g_loss = self.combined.train_on_batch(noise, valid)

            # Plot the progress
            print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

            # If at save interval => save generated image samples
            if epoch % save_interval == 0:
                self.save_imgs(epoch)

    def save_imgs(self, epoch):
        r, c = 5, 5
        #生成大小为25x100的矩阵,矩阵元素服从标准正太分布
        noise = np.random.normal(0, 1, (r * c, self.latent_dim))
        #根据噪声数据产生25张伪图片
        gen_imgs = self.generator.predict(noise)

        # Rescale images 0 - 1
        #gen_imgs是由tanh激活的,所以值在[-1,1]
        gen_imgs = 0.5 * gen_imgs + 0.5
        #将画布分成25个子画布
        fig, axs = plt.subplots(r, c)
        cnt = 0
        for i in range(r):
            for j in range(c):
                axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
                axs[i,j].axis('off')
                cnt += 1
        fig.savefig("images/mnist_%d.png" % epoch)
        plt.close()


if __name__ == '__main__':
    dcgan = DCGAN()
    dcgan.train(epochs=4000, batch_size=32, save_interval=50)
发布于 2019-03-26
生成对抗网络(GAN)
 
 

【知识】CNN,因果卷积,拓展卷积和反卷积

 

CNN


通过上面的动态图片可以很好的理解卷积的过程。图中绿色的大矩阵是我们的输入,黄色的小矩阵是卷积核(kernel,filter),旁边的小矩阵是卷积后的输入,通常称为feature map。
从动态图中,我们可以很明白的看出卷积实际上就是加权叠加。
同时,从这个动态图可以很明显的看出,输出的维度小于输入的维度。如果我们需要输出的维度和输入的维度相等,这就需要填充(padding)。
在tensorflow中,我们一般直接调用API,以二维卷积为例:

tf.layers.conv2d(
    inputs,
    filters,
    kernel_size,
    strides=(1, 1),
    padding='valid',
    data_format='channels_last',
    dilation_rate=(1, 1),
    activation=None,
    use_bias=True,
    kernel_initializer=None,
    bias_initializer=tf.zeros_initializer(),
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    trainable=True,
    name=None,
    reuse=None
)

其中inputs是一个4维的tensor,每维的含义由data_format参数决定。默认的是'channels_last',即老版API中的'NHWC',对应(batch, height, width, channels)。
filters也是一个4维的tensor,也就是我们CNN中的核心——卷积核。其每维与inputs意义不同:[filter_height, filter_width, in_channels, out_channels]。即[卷积核高度,卷积核宽度,输入通道数,输出通道数]。其中输入通道数就是inputs的通道数,表明我们每个卷积核进行in_channels次数次卷积,然后把这些结果都对位相加到一起形成最终结果。out_channels则表示我们有几个卷积核,有几个卷积核就输出几个图像。
所以对应上面的inputs和filter,其结果维度为:[batch,out_height,out_width,out_channels]。其中out_height,out_width由filters.shape,strides,padding方式共同决定。
具体可以参考:TF-卷积函数 tf.nn.conv2d 介绍

因果卷积(causal convolution)

在处理序列问题时,因为要考虑时间,即时刻t只能考虑t时刻及之前的输入,所以不能使用普通的CNN卷积,于是因果卷积随即产生。
我们知道序列问题可以抽象为,根据x1......xt和y1.....yt-1去预测yt,使得yt接近于实际值。
它的样子是这样的:

其中input中每个位置对应一个时间步,时间步内可以是多维的数据。
先按一维数据比较容易理解,则图中,卷积核就是一个2*1的卷积核,然后在第一层依次移动,最左边做padding,得到第二层,然后依层传递下去,即得到因果的效果。
但是这样会带来一个问题:想要感受野(receptive field)增大,要么增大卷积核,要么加深网络。卷积核增大会带来参数量,加深网络则会带来梯度消失,Internal Covariate Shift,训练不稳定难收敛等问题。
于是就有了因果拓展卷积,是因果卷积和拓展卷积和合体。

拓展卷积(dilated convolution)

前面也提到了,dilate convolution 可以使用较少的计算就能 cover 到较大的 receptive field。
其实其原理也很简单:

简单来说, dilate convolution 引入一个新的 hyper-parameter, dilate, 这个 hyper-parameter 的涵义是:每隔 dilate-1 个像素取一个” 像素”, 做卷积操作。

因果拓展卷积


这就可以解决因果卷积带来的问题,在示意图中,卷积感受野扩大了1,2,4,8倍。扩大卷积(dilated convolution)可以使模型在层数不大的情况下有非常大的感受野。
那么这个是怎么实现的呢?
因果拓展卷积出于Google DeepMind 2016论文WaveNet,所以源码参见:WaveNet源码
不过源码可能略为晦涩,有个注释版本:谷歌WaveNet 源码详解
下面则是我个人的理解:
我先摘抄上面知乎大佬的含注释代码,并纠正其中padding方式和shape的错误。

def time_to_batch(value, dilation, name=None):
    with tf.name_scope('time_to_batch'):
        #测试中,传入的value的shape为(1,9,1)
        #dilation=4
        shape = tf.shape(value)
        #pad_elements计算为3
        pad_elements = dilation - 1 - (shape[1] + dilation - 1) % dilation
        #padded后的shape为(1,12,1)即在第二个维度后加3个零
        padded = tf.pad(value, [[0, 0], [0, pad_elements], [0, 0]])
        #reshape后的shape为(3,4,1)
        reshaped = tf.reshape(padded, [-1, dilation, shape[2]])
        #转置后的shape为(4,3,1)
        transposed = tf.transpose(reshaped, perm=[1, 0, 2])
        #最后返回的shape为(4,3,1)
        return tf.reshape(transposed, [shape[0] * dilation, -1, shape[2]])


def batch_to_time(value, dilation, name=None):
    with tf.name_scope('batch_to_time'):
        #(4,2,1)
        shape = tf.shape(value)
        prepared = tf.reshape(value, [dilation, -1, shape[2]])
        transposed = tf.transpose(prepared, perm=[1, 0, 2])
        #最后返回的是前面time_to_batch的最初输入数值的shape
        #(1,8,1)
        return tf.reshape(transposed,
                          [tf.div(shape[0], dilation), -1, shape[2]])

def causal_conv(value, filter_, dilation, name='causal_conv'):
    with tf.name_scope(name):
        # Pad beforehand to preserve causality.
        #测试中,filter_width=2
        filter_width = tf.shape(filter_)[0]
        #测试中,dilation设定为4
        #因此,padding为padding=[[0, 0], [4, 0], [0, 0]]
        padding = [[0, 0], [(filter_width - 1) * dilation, 0], [0, 0]]
        #测试中,value的shape为(1,5,1)
        #测试中,padding为在value的第二维度前面加4个零
        #padded的shape变为(1,9,1)
        padded = tf.pad(value, padding)
        if dilation > 1:
            #见time_to_batch函数测试
            #测试中,最后返回来的shape为(4,3,1)
            transformed = time_to_batch(padded, dilation)
            #(4,2,1)
            conv = tf.nn.conv1d(transformed, filter_, stride=1, padding='VALID')
            #最后返回最开始的shape形式
            restored = batch_to_time(conv, dilation)
        else:
            restored = tf.nn.conv1d(padded, filter_, stride=1, padding='VALID')
        # Remove excess elements at the end.
        result = tf.slice(restored,
                          [0, 0, 0],
                          [-1, tf.shape(value)[1], -1])
        #最后返回的结果形式和padding后的即padded数据shape一样
        return result

这里causal_conv方法就实现了一个因果拓展卷积。另外两个则是辅助方法。
怎么样,看完是不是一脸懵逼,这是啥啊。
其实没那么难,思想很简单。
我们从简单的测试数据出发:
value : [1,5,1],即timewindow设为了5,我们一次只处理5个时间步的数据。然后把它整理成卷积的形式,一个batch,里面有5个时间步,一个通道。
然后对它padding,前面pad4个0,后面pad3个0,前面不应该pad3个0就够了吗?后面又pad3个0什么意思?
其实是这样的,我们无法通过正常的卷积操作来完成隔几个像素点挑一个像素来卷积的操作,所以我们只能通过reshape的方法来完成这个操作。
我们假设原来的5个元素都是1,则开始(忽略batch和channel):
dilation=4 filter=2
value:[1,1,1,1,1]
padded: [0,0,0,0,1,1,1,1,1,0,0,0]
reshape:
0,0,0,0,
1,1,1,1,
1,0,0,0.
transform:
0,1,1,
0,1,0,
0,1,0,
0,0,0
我们去对应上面因果拓展卷积的图,我们是想要得到一个[1,5,1]的第二层结果的。第二层第一个元素就是只对第一层第一个元素和填补元素做卷积,后面第2,3,4个元素类似,第5个元素才是第一层1,5元素的卷积。
那么我们通过reshape+transfrom,做VALID填补方式的卷积,就可以得到满足要求的结果:[4,2,1]。展开就是[1,8,1],最后三个元素纯粹是为了让transform后的数据规整,最后通过tf.slice切片操作去掉即可。
所以可以看出,填补前4后3只是为了让数据规整,这样就可以理解上面的代码了。
至此我们实现了因果卷积,虽然在新版tensorflow API中,已经实现了这个,直接调用就好了。
后面WaveNet还借助这个卷积加入了Residual and Skipped Connection,完成了一个拓展卷积层,让模型更快收敛。该层的网络结构如注释中所示。

    def _create_dilation_layer(self, input_batch, layer_index, dilation):
        '''Creates a single causal dilated convolution layer.

        The layer contains a gated filter that connects to dense output
        and to a skip connection:

               |-> [gate]   -|        |-> 1x1 conv -> skip output
               |             |-> (*) -|
        input -|-> [filter] -|        |-> 1x1 conv -|
               |                                    |-> (+) -> dense output
               |------------------------------------|

        Where `[gate]` and `[filter]` are causal convolutions with a
        non-linear activation at the output.
        '''
        variables = self.variables['dilated_stack'][layer_index]

        weights_filter = variables['filter']
        weights_gate = variables['gate']

        #filter卷积
        conv_filter = causal_conv(input_batch, weights_filter, dilation)
        #gate卷积
        conv_gate = causal_conv(input_batch, weights_gate, dilation)

        #是否使用bias
        if self.use_biases:
            filter_bias = variables['filter_bias']
            gate_bias = variables['gate_bias']
            conv_filter = tf.add(conv_filter, filter_bias)
            conv_gate = tf.add(conv_gate, gate_bias)

        #gate和filter共同输出
        out = tf.tanh(conv_filter) * tf.sigmoid(conv_gate)

        # The 1x1 conv to produce the residual output
        #采用1×1卷积实现残差输出
        weights_dense = variables['dense']
        transformed = tf.nn.conv1d(
            out, weights_dense, stride=1, padding="SAME", name="dense")

        # The 1x1 conv to produce the skip output
        #采用1×1卷积实现skip输出
        weights_skip = variables['skip']
        #skip output
        skip_contribution = tf.nn.conv1d(
            out, weights_skip, stride=1, padding="SAME", name="skip")

        if self.use_biases:
            dense_bias = variables['dense_bias']
            skip_bias = variables['skip_bias']
            transformed = transformed + dense_bias
            skip_contribution = skip_contribution + skip_bias

            
        layer = 'layer{}'.format(layer_index)
        #加入summary
        tf.histogram_summary(layer + '_filter', weights_filter)
        tf.histogram_summary(layer + '_gate', weights_gate)
        tf.histogram_summary(layer + '_dense', weights_dense)
        tf.histogram_summary(layer + '_skip', weights_skip)
        if self.use_biases:
            tf.histogram_summary(layer + '_biases_filter', filter_bias)
            tf.histogram_summary(layer + '_biases_gate', gate_bias)
            tf.histogram_summary(layer + '_biases_dense', dense_bias)
            tf.histogram_summary(layer + '_biases_skip', skip_bias)

        #返回skip output作为本层的输出保存下来,最后所有层的输出sum起来,得到最终结果(见_create_network方法)和(残差+input)作为下层的输入
        return skip_contribution, input_batch + transformed

 #建立模型
    def _create_network(self, input_batch):
        '''Construct the WaveNet network.'''
        outputs = []
        current_layer = input_batch

        # Pre-process the input with a regular convolution
        if self.scalar_input:
            initial_channels = 1
        else:
            initial_channels = self.quantization_channels

        #初始层
        current_layer = self._create_causal_layer(current_layer)

        # Add all defined dilation layers.
        #建立dilated层,总共18层
        with tf.name_scope('dilated_stack'):
            for layer_index, dilation in enumerate(self.dilations):
                with tf.name_scope('layer{}'.format(layer_index)):
                    output, current_layer = self._create_dilation_layer(
                        current_layer, layer_index, dilation)
                    outputs.append(output)

        #postprocess层
        with tf.name_scope('postprocessing'):
            # Perform (+) -> ReLU -> 1x1 conv -> ReLU -> 1x1 conv to
            # postprocess the output.
            #创建后续处理层变量
            w1 = self.variables['postprocessing']['postprocess1']
            w2 = self.variables['postprocessing']['postprocess2']
            if self.use_biases:
                b1 = self.variables['postprocessing']['postprocess1_bias']
                b2 = self.variables['postprocessing']['postprocess2_bias']

            tf.histogram_summary('postprocess1_weights', w1)
            tf.histogram_summary('postprocess2_weights', w2)
            if self.use_biases:
                tf.histogram_summary('postprocess1_biases', b1)
                tf.histogram_summary('postprocess2_biases', b2)

            # We skip connections from the outputs of each layer, adding them
            # all up here.
            #将每一层的skip connection输出累加
            total = sum(outputs)
            transformed1 = tf.nn.relu(total)
            conv1 = tf.nn.conv1d(transformed1, w1, stride=1, padding="SAME")
            if self.use_biases:
                conv1 = tf.add(conv1, b1)
            transformed2 = tf.nn.relu(conv1)
            conv2 = tf.nn.conv1d(transformed2, w2, stride=1, padding="SAME")
            if self.use_biases:
                conv2 = tf.add(conv2, b2)

        return conv2

Reference

http://shuokay.com/2016/10/15/wavenet/
http://www.cnblogs.com/yangperasd/p/7071657.html
https://zhuanlan.zhihu.com/p/24568596
https://blog.csdn.net/a1154761720/article/details/53411365 1*1卷积核的作用

 

一天一 GAN之DCGAN,CGAN

Hi ,my name is Chen Yang ,I am a sophomore in ocean university of China .I do some scientific research in my spare time. Based on the current hot direction of artificial intelligence, I hope to share my research progress with Generative adversarial network

OUCMachineLearning/OUCML​github.com图标

DCGAN

arxiv:

code:(简易实现)

DCGAN,全称叫Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks,顾名思义.就是在生成器和判别器特征提取层用卷积神经网络代替了原始GAN中的多层感知机

代码实现

def build_generator(self):

        model = Sequential()

        model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim))
        model.add(Reshape((7, 7, 128)))
        model.add(UpSampling2D())
        model.add(Conv2D(128, kernel_size=3, padding="same"))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Activation("relu"))
        model.add(UpSampling2D())
        model.add(Conv2D(64, kernel_size=3, padding="same"))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Activation("relu"))
        model.add(Conv2D(self.channels, kernel_size=3, padding="same"))
        model.add(Activation("tanh"))

        model.summary()

        noise = Input(shape=(self.latent_dim,))
        img = model(noise)

        return Model(noise, img)

 

 

我们可以看到,他用的是卷积+上采样的方法来实现将100维的噪声输入$z$经过多层的卷积和上采样后得到的一张$64643$大小的一张图片,因为作者在15年写这篇文章的时候就已经使用了celebA数据集来生成人脸图像.

 

 

具体的公式和原始GAN的几乎也一样,涉及到的原理并没有什么太多的变化,仅仅是是把生成器和判别器里面的多层感知机换成了深度卷积网络,就可以在人脸数据集上做生成了,可以看出来原始的GAN他在原理和公式上是很强大的,

判别器

def build_discriminator(self):

    model = Sequential()

    model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same"))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
    model.add(ZeroPadding2D(padding=((0,1),(0,1))))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Conv2D(256, kernel_size=3, strides=1, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(1, activation='sigmoid'))

    model.summary()

    img = Input(shape=self.img_shape)
    validity = model(img)

    return Model(img, validity)

总结

DCGAN几乎是生成对抗网络的入门模板了,不多赘述.

DAY3:

CGAN

CGAN的全称叫Conditional Generative Adversarial Nets,condition的意思是就是条件,我们其实可以理解成概率统计里一个很基本的概念叫做条件概率分布.举个例子:

假设在桌子上抛掷一枚普通的骰子,则其点数结果的概率分布是集合 {1,2,3,4,5,6}的均匀分布:每个点数出现的概率都是均等的六分之一。然而,如果据某个坐在桌边的人观察,向着他的侧面是6点,那么,在此条件下,向上的一面不可能是6点,也不可能是6点对面的1点。因此,在此条件下,抛骰子的点数结果是集合 {2,3,4,5}的均匀分布:有四分之一的可能性出现 2,3,4,5四种点数中的一种。可以看出,增加的条件或信息量(某个侧面是6点)导致了点数结果的概率分布的变化。这个新的概率分布就是条件概率分布。

那么回过头来看原始的GAN的噪声分布和ground truth的分布构成的核心公式:

 

 

如果我们已知输入的ground truth的label信息,那么我们便可以在这个基础上结合条件概率的公式得到CGAN的目标函数:

 

 

如下图所示,通过将额外信息y输送给判别模型和生成模型,作为输入层的一部分,从而实现条件GAN。在生成模型中,先验输入噪声p(z)和条件信息y联合组成了联合隐层表征。对抗训练框架在隐层表征的组成方式方面相当地灵活。类似地,条件GAN的目标函数是带有条件概率的二人极小极大值博弈(two-player minimax game ):

 

 

核心代码

生成器

def build_generator(self):

        model = Sequential()
#一个由多层感知机构成的生成网络
        model.add(Dense(256, input_dim=self.latent_dim))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(1024))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(np.prod(self.img_shape), activation='tanh'))
        model.add(Reshape(self.img_shape))

        model.summary()
#生成器的输入有两个,一个是高斯噪声noise,一个是由我希望生成的图片的label信息,通过embedding的方法把label调整到和噪声相同的维度,在乘起来这样便使得noise的输入是建立在label作为条件的基础上
        noise = Input(shape=(self.latent_dim,))
        label = Input(shape=(1,), dtype='int32')
        label_embedding = Flatten()(Embedding(self.num_classes, self.latent_dim)(label))

        model_input = multiply([noise, label_embedding])
        img = model(model_input)

        return Model([noise, label], img)

判别器:

def build_discriminator(self):

        model = Sequential()
#一个多层感知机的判别网络
        model.add(Dense(512, input_dim=np.prod(self.img_shape)))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.4))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dropout(0.4))
        model.add(Dense(1, activation='sigmoid'))
        model.summary()

        img = Input(shape=self.img_shape)
        label = Input(shape=(1,), dtype='int32')
# 我们把label通过embedding扩展到和image一样的维度 
        label_embedding = Flatten()(Embedding(self.num_classes, np.prod(self.img_shape))(label))
        flat_img = Flatten()(img)
#判别器的输入包含了图片信息和起对应的标签,我们的判别器不但要判别是否真假,还需要判别是不是图片符合对应的类别信息

        model_input = multiply([flat_img, label_embedding])

        validity = model(model_input)

        return Model([img, label], validity)

训练细节

# 选择要batch个训练的图片和label
idx = np.random.randint(0, X_train.shape[0], batch_size)
imgs, labels = X_train[idx], y_train[idx]

# 生成100维的高斯噪声
noise = np.random.normal(0, 1, (batch_size, 100))

# 生成器根据label和noise生成图片
gen_imgs = self.generator.predict([noise, labels])

# 训练判别器
d_loss_real = self.discriminator.train_on_batch([imgs, labels], valid)
d_loss_fake = self.discriminator.train_on_batch([gen_imgs, labels], fake)
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

# ---------------------
#  生成器训练部分
# ---------------------

# 生成随机的标签
sampled_labels = np.random.randint(0, 10, batch_size).reshape(-1, 1)

# 训练生成器
g_loss = self.combined.train_on_batch([noise, sampled_labels], valid)

实验结果

 

 

我们可以清晰的看出来,生成器实际上是可以根据我们提供的label信息很好的生成对应的图像,虽然CGAN用的是很原始的多层感知机,但是我们仍然可以清晰的看出来,这个多层感知机实际上是可以生成所有label对应的分布的图像的,而且作为一种监督学习,condition GAN告诉我们了,要想GAN训练成功,condition是很重要的,具体在以后会说为什么,看到这里了要是看官老爷觉得interesting,不如麻烦关注转发好看三连一波,你的每一点小小的鼓励都是对作者莫大的鼓舞鸭 .

参考

参考

发布于 2019-04-10
生成对抗网络(GAN)
深度学习(Deep Learning)
人工智能
 

 

 

 

 

 

 

 

 

 

 

 

 

 

高斯金字塔:

拉普拉斯金字塔的构建需要使用高斯金字塔,计算方式如下:

上式中k表示第k级,Lk(I)表示第k级的拉普拉斯金字塔,上式看起来很复杂,实际上想要表达的是拉普拉斯金字塔的第k层Lk(I)=Ik-u(Ik+1),也就是拉普拉斯金字塔第k层等于高斯金字塔第k层减去高斯金字塔第k+1层的上采样。所以,拉普拉斯金字塔表示用高斯金字塔第k+1层重建第k层所需的差值,可视化如下:

可以看出,重建差值的视觉效果一般都是类似图像的轮廓结构图。

 

二、Laplacian Generative Adversarial Networks(LAPGAN)

LAPGAN将条件生成对抗网络CGAN集成到它的拉普拉斯金字塔结构中,符号约定如下:

(1)G0、G1......Gk表示k个卷积网络(即生成器Generator);

(2)z表示噪声数据,符合某种分布,如高斯分布;

下式表示高斯金字塔的重建过程:

高斯金字塔的第k层重建需要用它的第k+1层上采样(即u(Ik+1))加上拉普拉斯金字塔第k层(即hk),其中hk是第k个生成器Gk通过zk和u(Ik+1)生成的。其中Ik+1初始值为0,最高级的Gk仅仅使用噪声矢量z作为输入生成Ik,因为它没有上一级:

除了最高级以外,其余的生成器G0、G1......Gk-1都采用上一级的上采样u(Ik)和噪声zk作为联合输入,其中上采样u(Ik)就是“条件变量”

 

-->  类 DenseNet 多连几层,效果会更好可能~

 https://blog.csdn.net/weixin_42182910/article/details/103216558

 搞得跟Faster-RCNN 既做分类又做回归像了。。。

--> Faster-RCNN 是不是也可以借鉴这个思路?


 

InfoGAN解读

InfoGAN的概念最早来自论文InfoGAN: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets,发表于NIPS 2016。

motivation

在标准的GAN中,生成数据的来源一般是一段连续单一的噪声z,这样带来的一个问题是,Generator往往会将z高度耦合处理,我们无法通过控制z的某些维度来控制生成数据的语义特征,也就是说,z是不可解释的。比如,假设我们打算生成像MNIST那样的手写数字图像,每个手写数字可以分解成多个维度特征:代表的数字、倾斜度、粗细度等等,在标准GAN的框架下,我们无法在上述维度上具体指定Generator生成什么样的手写数字。

为了解决这一问题,文章对GAN的目标函数进行了一些小小的改进,成功让网络学习到了可解释的特征表示(即论文题目中的interpretable representation)。

latent code

这个trick的关键是latent code的应用。

既然原始的噪声是杂乱无章的,那就人为地加上一些限制,于是作者把原来的噪声输入分解成两部分:一是原来的z;二是由若干个latent variables拼接而成的latent code c,这些latent variables会有一个先验的概率分布,且可以是离散的或连续的,用于代表生成数据的不同特征维度,比如MNIST实验的latent variables就可以由一个取值范围为0-9的离散随机变量(用于表示数字)和两个连续的随机变量(分别用于表示倾斜度和粗细度)构成。

但仅有这个设定还不够,因为GAN中Generator的学习具有很高的自由度,它很容易找到一个解,使得

,导致c完全不起作用。

mutual information

作者从信息论中得到启发,提出了基于互信息(mutual information)的正则化项。c的作用是对生成数据的分布施加影响,于是需要对这两者的关系建模,在信息论中,互信息I(X;Y)用来衡量“已知Y的情况下可获取多少有关X的信息”,计算公式为:

 

 

其中H表示计算entropy,H(X|Y)衡量的是“给定Y的情况下,X的不确定性”。从公式可以得知,当XY毫无关联时,H(X|Y) = H(X)I(X;Y) = 0,取得最小值;当XY有明确的关联时,已知Y时,X没有不确定性,因而H(X|Y) = 0,此时I(X;Y)取得最大值。

所以,现在优化目标就很明确了:要让c和Generator分布G(z, c)的互信息达到最大值。现在目标函数可改写为:

 

Variational Mutual Information Maximization

上述的推导看似已经很充足了,然而上面互信息的计算涉及后验概率分布P(c|x),而后者在实际中是很难获取的,所以需要定义一个辅助性的概率分布Q(c|x),采用一种叫作Variational Information Maximization的技巧对互信息进行下界拟合:

再经过一番数学推导,可把这个下界转化成如下的形式:

这里的

很容易在现有的框架中进行优化,当它取得最大值H(c)时,我们也获得了互信息的最大值,达到最优结果。

现在目标函数改为:

网络模型概览

最终的网络模型示意图为:

 

 

网络是基于DC-GAN(Deep Convolutional GAN)的,G和D都由CNN构成。在此基础上,Q和D共享卷积网络,然后分别通过各自的全连接层输出不同的内容:Q输出对应于fake data的c的概率分布,D则仍然判别真伪。

编辑于 2019-03-04
生成对抗网络(GAN)
 
   

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/cx2016/p/13154883.html