GAN学习笔记(二)——LAPGAN

本文基于2015年的经典论文:Deep Generative Image Models using a Laplacian Pyramid of Adversarial Networks
论文下载地址:https://arxiv.org/abs/1506.05751

LAPGAN是建立在GAN和CGAN基础上的成功尝试。在本文中,LAPGAN依然主要被用于生成图像。

一、基础知识
1 CGAN
有一篇论文单独讨论CGAN,这篇论文内容并不多因此没有单独写文章详解这篇论文。它主要是在GAN的基础上加上了条件,这样可以缓解GAN中生成的样本过于自由的问题。具体方法是:将一个条件变量与噪声和真实样本经过一定数量的层次后分别进行concat。更多的解释可以移步这篇博客

GAN学习笔记(二)——LAPGAN

GAN的公式:
GAN学习笔记(二)——LAPGAN
其中,h是真实样本,z是噪声。
CGAN的公式:
GAN学习笔记(二)——LAPGAN
其中,l是条件变量,它可以是类标,在LAPGAN中,它是图片。

2 图像金字塔
本文中用到了高斯金字塔,用于下采样;
以及拉普拉斯金字塔,用于上采样。
GAN学习笔记(二)——LAPGAN
高斯金字塔的构造过程:
(1) 对图像进行高斯核卷积
(2) 将偶数行除去
用公式表示就是:
GAN学习笔记(二)——LAPGAN
拉普拉斯金字塔的构造过程:
数学定义:GAN学习笔记(二)——LAPGAN
式中的GAN学习笔记(二)——LAPGAN表示第i层的图像。而UP( )操作是将源图像中位置为(x,y)的像素映射到目标图像的(2x+1,2y+1)位置,即在进行向上取样。符号 GAN学习笔记(二)——LAPGAN表示卷积, GAN学习笔记(二)——LAPGAN为5×5的高斯内核。
也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。

GAN学习笔记(二)——LAPGAN
如图所示,上方的人像从左向右是高斯金字塔自底而上的构建过程,下方的人像是拉普拉斯金字塔自顶向下构建的过程。

在论文原文中是这样描述的:
先构建高斯金字塔:
GAN学习笔记(二)——LAPGAN
I0是原图,I1到IK都是由它之前的I经过下采样得到的。K是金字塔的级数,我们希望金字塔的级数能够让IK的像素足够小,<=8*8为宜。
第K层的拉普拉斯金字塔:
GAN学习笔记(二)——LAPGAN
其他层的拉普拉斯金字塔:
GAN学习笔记(二)——LAPGAN
用拉普拉斯金字塔恢复图像:
GAN学习笔记(二)——LAPGAN
需要注意,文中为了简化计算,用的上采样和下采样方法与常规的高斯金字塔和拉普拉斯金字塔生成方式有差异。它取下采样的方式就是互不相交的2*2的区域中像素求均值,生成拉普拉斯金字塔的方式就是在原图上的2*2区域减去刚才算出来的均值。在它提供的代码中,上采样和下采样完全交给了Torch的image包实现(image.scale)。

二、LAPGAN原理:
1 用途:生成高品质的自然图片
2 创新点:利用拉普拉斯金字塔,由粗到精逐级生成越发清楚的图像。
3 突破:GAN只能生成低像素的图片,而LAPGAN可以生成高像素的图片。
4 本质:用CGAN生成拉普拉斯金字塔。
GAN学习笔记(二)——LAPGAN
该图描述了LAPGAN的训练过程,图中的I0是64*64的图像,一共使用了3个GAN,I1为32*32,I2为16*16,I3为8*8。
拉普拉斯金字塔的顶端(也就是像素最低的图像)用来训练普通的GAN ,生成器的输入只有噪声。而后像素更高的图像用来训练CGAN,输入的不光有噪声,还有同级的高斯金字塔的图像经过上采样后得到的图像(在一些情况下,还会加入类标,这个我们在后面的实验中详细介绍)。
GAN学习笔记(二)——LAPGAN
该图描述了训练好了的LAPGAN的使用过程。
GAN学习笔记(二)——LAPGAN
5 LAPGAN的优点:
①与Residual Network有异曲同工之妙,针对残差的逼近和学习相对容易。
②逐级独立训练提高了网络简单记忆输入样本的难度,许多高性能的深度网络都面临着这样的问题。
③减少了每一次 GAN 需要学习的内容,也就从而增大了 GAN 的学习能力。

三、模型和训练
本文使用了三个数据集:
(1) CIFAR10:32*32,10类,共100k训练样本
(2) STL:96*96,10类,共100k训练样本
(3) LSUN:下采样到64*64,10个不同自然场景,共10M训练样本
实验平台:Torch
源代码:https://github.com/facebook/eyescream
https://gist.github.com/soumith/e3f722173ea16c1ea0d9
在所有的模型中,噪声向量zk中的值都服从相同的分布,落在[-1,1]的区间内。
针对CIFAR10和STL:
最初的尺寸:作用域8*8分辨率的图像上,GK和DK都使用有两个隐藏层的全连接神经网络,激活函数为ReLU。DK使用了Dropout,每层有600个神经元。GK每层有1200个神经元。ZK是一个100维的向量。
GAN学习笔记(二)——LAPGAN
后续的尺寸:对CIFAR10而言,通过在原始图像上裁剪28*28的图像扩充数据。金字塔的图像尺寸按照8*8到14*14到28*28扩大。对STL而言,图像尺寸按照8*8到16*16到32*32到64*64到96*96扩大。CIFAR10和STL的神经网络都由拥有三层的Gk和2层的Dk组成。噪声输入作为第四个通道和图像输入的三通道放在一起。
GAN学习笔记(二)——LAPGAN
蓝色箭头指向的图片(也就是下采样后再上采样的模糊图片),在CGAN中的地位就是条件变量。
值得注意的是,在生成器中的卷积层是作者自己定义的SpatialConvolutionUpsample层,因为我们需要最后生成的图片和输入图片的大小一样,因此每次卷积前都会将feature map补零到卷积后恰好仍为原来的尺寸。

在CIFAR10中,作者还做了将类标也作为条件变量的实验。CIFAR10有10类,条件变量是长度为10的独热码向量。如果图片的尺寸为x*x*3(三通道),那么就将条件变量经过一个全连接层得到长度为x*x的向量,将向量重新排列得到x*x的图像,与原先的输入concate,具体如下图所示。
GAN学习笔记(二)——LAPGAN

参数选择:
初始学习率:0.02
学习率下降方式:每完成一个epoch将学习率除以1.000004
初始动量:0.8
动量上升方式:每完成一个epoch将动量增加0.0008

在训练过程中,作者利用Parzen窗估计器检测对数似然函数(具体怎么检测在【第四部分 实验和评估】中详述)。注意:监测只是查看,是在验证集上做的工作,这个结果不会在训练过程中计算,更不会反向传播,训练过程中的损失反向传播与常规的CGAN和GAN相同。

针对LSUN:
LSUN的数据量比较大,因此可以对它的每一个场景分别训练一个LAPGAN模型。这样就可以在评估过程中理解模型学习到的分布的差异。LSUN使用的模型比CIFAR10略大,但内部结构大同小异,这里放上网络结构,不再赘述。
GAN学习笔记(二)——LAPGAN

四、实验和评估
本文使用了3个不同的评估指标:
(1) 对数似然函数
(2) 直观查看生成图片
(3) 人工主观评估
1 对数似然函数
本文对拉普拉斯金字塔上每一个尺度的模型都进行了一次利用高斯Parzen窗的对数似然函数评估。下面介绍如何计算高斯Parzen窗的对数似然函数。
对某一层的图像I而言,它的下采样是d(I),记为l,上采样是u(d(I)),将I-u(d(I))记为h。构建下方的概率密度函数,描述当前的h是真实的I-u(d(I))产生的可能性。
GAN学习笔记(二)——LAPGAN
这事实上是一个条件概率的表达式,q1(l)是真实图像I下采样为l的概率,q0(l,h)是在已知下采样的图片为l的条件下,h为真实的I-u(l)的概率。
这样,我们有:GAN学习笔记(二)——LAPGAN
对每个固定的l,得到:GAN学习笔记(二)——LAPGAN
因此:
GAN学习笔记(二)——LAPGAN
q1和q0都用高斯Parzon窗计算:
GAN学习笔记(二)——LAPGANGAN学习笔记(二)——LAPGAN
GAN学习笔记(二)——LAPGAN
其中,GAN学习笔记(二)——LAPGAN 是用验证集调整的参数。
由于在生成图像时,前一层生成的图像又作为后一层的下采样图像使用,也就是说,p(I)在生成更大尺寸的图片时就处于q1(l)的位置。
因此求最后一层的对数似然函数时有这样的关系:
GAN学习笔记(二)——LAPGAN

实验结果显示,LAPGAN比常规的GAN效果更好。
GAN学习笔记(二)——LAPGAN

2 直观查看生成的图片
下图显示了在CIFAR10上训练的生成模型生成的图像。从中可以看出:
① 我们的GAN做得比以前的论文好,这可能要归功于CIFAR10上的数据扩充(每张图裁了4个28*28);
② LAPGAN优于GAN,边缘更加锐利;
③ 引入类标条件的LAPGAN优于普通LAPGAN,物体结构更加清晰;
④ 最右侧的一列是与生成的图片最相近的真实图片,从中可以生成模型不是简单记忆训练数据。
GAN学习笔记(二)——LAPGAN

下图显示了在STL上训练的生成模型生成的图片。图a中,虽然物体的形状不清晰,但是依然保持着锐利的边缘。图b中可以看到图片逐层产生的过程。
GAN学习笔记(二)——LAPGAN

下图显示了在LSUN上训练的生成模型生成的图片,展示了塔,卧室和教堂前。
GAN学习笔记(二)——LAPGAN
接下来以(a)图举例,a图做了塔的实验,从左到右是4*4扩充到64*64的过程。每一行之间使用的噪声向量是相同的,只有每行最开始生成4*4的图片时,输入的噪声向量是不同的,这些有差异的噪声向量是用插值的方式逐步过渡的。如果网络过拟合了——也就是说,如果网络知识简单记忆训练样本,在生成的图像上我们可以观察到突变。但是实际上,我们可以观察到,生成的图像是平滑变化的,它们看起来也貌似真实,而不是线性地融合两张真实图像。
作者在LSUN还做了很多其他实验,有兴趣可以查阅论文原文。

3 人工主观评估
作者让15位志愿者参与评估实验,看他们是否能区分生成的图片和真实的图片。用户的评估界面如下图所示。呈现给志愿者的图片从以下四个类别的图片中随机选取:三个用CIFAR10训练得到的生成模型生成的图片(LAPGAN,加了类标条件的LAPGAN,标准的GAN)以及真实的CIFAR10图片。由于精确度收到观察时间的影响,因此作者随机选取图片的展示时间(从50ms到2000ms不等),一旦超过了展示时间,图像会被遮挡。在实验开始前,会给志愿者展示CIFAR10中的真实图片。在从志愿者那里收集了10k数量级的样本后绘制了下图的结果图像。结果显示,LAPGAN生成以假乱真的图片的能力优于普通GAN。
GAN学习笔记(二)——LAPGAN
图中,竖线显示的是个体间的差异。

DCGAN及其TensorFlow源码

上一节我们提到G和D由多层感知机定义。深度学习中对图像处理应用最好的模型是CNN,那么如何把CNN与GAN结合?DCGAN是这方面最好的尝试之一。源码:https://github.com/Newmu/dcgan_code 。DCGAN论文作者用theano实现的,他还放上了其他人实现的版本,本文主要讨论tensorflow版本。
TensorFlow版本的源码:https://github.com/carpedm20/DCGAN-tensorflow

DCGAN把上述的G和D换成了两个卷积神经网络(CNN)。但不是直接换就可以了,DCGAN对卷积神经网络的结构做了一些改变,以提高样本的质量和收敛的速度,这些改变有:

  • 取消所有pooling层。G网络中使用转置卷积(transposed convolutional layer)进行上采样,D网络中用加入strided的卷积代替pooling。
  • 在D和G中均使用batch normalization
  • 去掉FC层,使网络变为全卷积网络
  • G网络中使用ReLU作为激活函数,最后一层使用tanh
  • D网络中使用LeakyReLU作为激活函数

这些改变在代码中都可以看到。DCGAN论文中提到对CNN结构有三点重要的改变:

  1. Allconvolutional net (Springenberg et al., 2014) 全卷积网络
    判别模型D:使用带步长的卷积(strided convolutions)取代了的空间池化(spatial pooling),容许网络学习自己的空间下采样(spatial downsampling)。
    Ÿ 生成模型G:使用微步幅卷积(fractional strided),容许它学习自己的空间上采样(spatial upsampling)
  2. 在卷积特征之上消除全连接层。
    Ÿ (Mordvintsev et al.)提出的全局平均池化有助于模型的稳定性,但损害收敛速度。
    GAN的第一层输入:服从均匀分布的噪声向量Z,因为只有矩阵乘法,因此可以被叫做全连接层,但结果会被reshape成4维张量,作为卷积栈的开始。
    对于D,最后的卷积层被flatten(把矩阵变成向量),然后使用sigmoid函数处理输出。
    生成模型:输出层用Tanh函数,其它层用ReLU激活函数。
    判别模型:所有层使用LeakyReLU
  3. Batch Normalization 批标准化。
    解决因糟糕的初始化引起的训练问题,使得梯度能传播更深层次。稳定学习,通过归一化输入的单元,使它们平均值为0,具有单位方差。
    批标准化证明了生成模型初始化的重要性,避免生成模型崩溃:生成的所有样本都在一个点上(样本相同),这是训练GANs经常遇到的失败现象。
    generator:100维的均匀分布Z投影到小的空间范围卷积表示,产生许多特征图。一系列四步卷积将这个表示转换为64×64像素的图像。不用到完全连接或者池化层。

配置

Python
TensorFlow
SciPy
pillow
(可选)moviepy (https://github.com/Zulko/moviepy):用于可视化
(可选)Align&Cropped Images.zip (http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html):人脸数据集

main.py

入口程序,事先定义所需参数的值。
执行程序:
训练一个模型:
$ python main.py --dataset mnist --is_trainTrue
$ python main.py --dataset celebA --is_trainTrue --is_crop True
测试一个已存在模型:
$ python main.py --dataset mnist
$ python main.py --dataset celebA --is_crop True
你也可以使用自己的dataset:
$ mkdir data/DATASET_NAME
添加图片到data/DATASET_NAME …
$ python main.py --dataset DATASET_NAME--is_train True
$ python main.py --dataset DATASET_NAME
训练出多张以假乱真的图片

源码分析

flags配置network的参数,在命令行中可以修改,比如
$python main.py --image_size 96 --output_size 48 --dataset anime --is_crop True--is_train True --epoch 300
该套代码参数主要以mnist数据集为模板,如果要训练别的数据集,可以适当修改一些参数。mnist数据集可以通过download.py下载。
首先初始化model.py中的DCGAN,然后看是否需要训练(is_train)。

FLAGS参数

epoch:训练回合,默认为25
learning_rateAdam的学习率,默认为0.0002
beta1:Adam的动量项(Momentum term of Adam),默认为0.5
train_size:训练图像的个数,默认为np.inf
batch_size:批图像的个数,默认为64。batch_size6436
input_height:所使用的图像的图像高度(将会被center cropped),默认为108
input_width:所使用的图像的图像宽度(将会被center cropped),如果没有特别指定默认和input_height一样
output_height:所产生的图像的图像高度(将会被center cropped),默认为64
output_width:所产生的图像的图像宽度(将会被center cropped),如果没有特别指定默认和output_height一样
dataset:所用数据集的名称,在文件夹data里面,可以选择celebA,mnist,lsun。也可以自己下载图片,把文件夹放到data文件夹里面。
input_fname_pattern:输入的图片类型,默认为*.jpg
checkpoint_dir:存放checkpoint的目录名,默认为checkpoint
sample_dir:存放生成图片的目录名,默认为samples
train:训练为True,测试为False,默认为False
crop:训练为True,测试为False,默认为False
visualize:可视化为True,不可视化为False,默认为False

model.py

初始化参数

model.py定义了DCGAN类,包括9个函数

__init__()

参数初始化,已讲过的input_height, input_width, crop, batch_size, output_height, output_width, dataset_name, input_fname_pattern, checkpoint_dir, sample_dir就不再说了
sample_num:大小和batch_size一样
y_dim:输出通道。mnisty_dim=10,我想可能是因为mnist是图片数字,分为10类。如果不是mnist,则默认为none。
z_dim:噪声z的维度,默认为100
gf_dimG,默认为64
df_dimD,默认为64
gfc_dimGG,默认为1024
dfc_dimDD,默认为1024
c_dim:颜色通道,灰度图像设为1,彩色图像设为3,默认为3
其中self.d_bn1, self.d_bn2, g_bn0, g_bn1, g_bn2是batch标准化,见ops.py的batch_norm(object)。
如果是mnist数据集,d_bn3, g_bn3都要batch_norm。
self.data读取数据集。
然后建立模型(build_model)

build_model()

inputs的形状为[batch_size, input_height, input_width, c_dim]。
如果crop=True,inputs的形状为[batch_size, output_height, output_width, c_dim]。
输入分为样本输入inputs和抽样输入sample_inputs。
噪声z的形状为[None, z_dim],第一个None是batch的大小。
然后取数据:
self.G = self.generator(self.z)#返回[batch_size, output_height, output_width, c_dim]形状的张量,也就是batch_size张图
self.D, self.D_logits = self.discriminator(inputs)#返回的D为是否是真样本的sigmoid概率,D_logits是未经sigmoid处理
self.sampler = self.sampler(self.z)#相当于测试,经过G网络模型,取样,代码和G很像,没有G训练的过程。
self.D_, self.D_logits_ = self.discriminator(self.G, reuse=True)
#D是真实数据,D_是假数据
用交叉熵计算损失,共有:d_loss_real、d_loss_fake、g_loss
self.d_loss_real = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits, tf.ones_like(self.D)))

self.d_loss_fake = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits_, tf.zeros_like(self.D_)))

self.g_loss = tf.reduce_mean(
sigmoid_cross_entropy_with_logits(self.D_logits_, tf.ones_like(self.D_)))

tf.ones_like:新建一个与给定tensor大小一致的tensor,其全部元素为1
d_loss_real是真样本输入的损失,要让D_logits接近于1,也就是D识别出真样本为真的
d_loss_fake是假样本输入的损失,要让D_logits_接近于0,D识别出假样本为假
d_loss = d_loss_real + d_loss_fake是D的目标,要最小化这个损失
g_loss:要让D识别假样本为真样本,G的目标是降低这个损失,D是提高这个损失

summary这几步是关于可视化,就不管了

train()

通过Adam优化器最小化d_loss和g_loss。
sample_z为从-1到1均匀分布的数,大小为[sample_num, z_dim]
从路径中读取原始样本sample,大小为[sample_num, output_height, output_width, c_dim]
接下来进行epoch个训练:
将data总数分为batch_idxs次训练,每次训练batch_size个样本。产生的样本为batch_images。
batch_z为训练的噪声,大小为[batch_num, z_dim]
d_optim = tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) /
.minimize(self.d_loss, var_list=self.d_vars)

g_optim = tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) /
.minimize(self.g_loss, var_list=self.g_vars)

首先输入噪声z和batch_images,通过优化d_optim更新D网络。
然后输入噪声z,优化g_optim来更新G网络。G网络更新两次,以免d_loss为0。这点不同于paper。
这样的训练,每过100个可以生成图片看看效果。
if np.mod(counter, 100) == 1

discriminator()

代码自定义了一个conv2d,对tf.nn.conv2d稍加修改了。下面贴出tf.nn.conv2d解释如下:
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
除去name参数用以指定该操作的name,与方法有关的一共五个参数:
第一个参数input:指需要做卷积的输入图像,它要求是一个Tensor,具有[batch, in_height, in_width, in_channels]这样的shape,具体含义是[训练时一个batch的图片数量, 图片高度, 图片宽度, 图像通道数],注意这是一个4维的Tensor,要求类型为float32和float64其中之一
第二个参数filter:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数input相同,有一个地方需要注意,第三维in_channels,就是参数input的第四维
第三个参数strides:卷积时在图像每一维的步长,这是一个一维的向量,长度4
第四个参数padding:string类型的量,只能是”SAME”,”VALID”其中之一,这个值决定了不同的卷积方式(后面会介绍)
第五个参数:use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认为true
结果返回一个Tensor,这个输出,就是我们常说的feature map
batch_norm(object)
tf.contrib.layers.batch_norm的代码见https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/layers/python/layers/layers.py
batchnormalization来自于http://arxiv.org/abs/1502.03167
加快训练。
DCGAN及其TensorFlow源码
激活函数lrelu见ops.py。四次卷积(其中三次卷积之前先批标准化)和激活之后。然后线性化,返回sigmoid函数处理后的结果。h3到h4的全连接相当于线性化,用一个矩阵将h3和h4连接起来,使h4是一个batch_size维的向量。

generator()

self.h0 = tf.reshape(self.z_, [-1, s_h16, s_w16, self.gf_dim * 8])改变z_的形状。-1代表的含义是不用我们自己指定这一维的大小,函数会自动计算,但列表中只能存在一个-1。(当然如果存在多个-1,就是一个存在多解的方程了)
deconv2d()
引用tf的反卷积函数tf.nn.conv2d_transpose或tf.nn.deconv2d。以tf.nn.conv2d_transpose为例。
defconv2d_transpose(value, filter, output_shape, strides,padding=”SAME”, data_format=”NHWC”, name=None):

  • value: 是一个4维的tensor,格式为[batch, height, width, in_channels] 或者 [batch, in_channels,height, width]。
  • filter: 是一个4维的tensor,格式为[height, width, output_channels, in_channels],过滤器的in_ channels的维度要和这个匹配。
  • output_shape: 一维tensor,表示反卷积操作的输出shapeA
  • strides: 针对每个输入的tensor维度,滑动窗口的步长。
  • padding: “VALID”或者”SAME”,padding算法
  • data_format: “NHWC”或者”NCHW” ,对应value的数据格式。
  • name: 可选,返回的tensor名。

deconv= tf.nn.conv2d_transpose(input_, w, output_shape=output_shape,strides=[1,d_h, d_w, 1])
第一个参数是输入,即上一层的结果,
第二个参数是输出输出的特征图维数,是个4维的参数,
第三个参数卷积核的移动步长,[1, d_h, d_w, 1],其中第一个对应一次跳过batch中的多少图片,第二个d_h对应一次跳过图片中多少行,第三个d_w对应一次跳过图片中多少列,第四个对应一次跳过图像的多少个通道。这里直接设置为[1,2,2,1]。即每次反卷积后,图像的滑动步长为2,特征图会扩大缩小为原来2*2=4倍。
DCGAN及其TensorFlow源码

sampler()

和generator结构一样,用的也是它的参数。存在的意义可能在于共享参数?
self.sampler = self.sampler(self.z, self.y)改为self.sampler = self.generator(self.z, self.y)
报错:
DCGAN及其TensorFlow源码
所以sampler的存在还是有意义的。

load_mnist(), save(), load()
这三个加载保存等就不仔细讲了。

download.py和ops.py好像也没什么好讲的。
utils.py包含可视化等函数

参考:
Springenberg, Jost Tobias, Dosovitskiy, Alexey, Brox, Thomas, and Riedmiller, Martin. Striving for simplicity: The all convolutional net. arXiv preprint arXiv:1412.6806, 2014.
Mordvintsev, Alexander, Olah, Christopher, and Tyka, Mike. Inceptionism : Going deeper into neural networks.http://googleresearch.blogspot.com/2015/06/inceptionism-going-deeper-into-neural.html. Accessed: 2015-06-17.
Radford A, Metz L, Chintala S. UnsupervisedRepresentation Learning with Deep Convolutional Generative AdversarialNetworks[J]. Computer Science, 2015.
http://blog.csdn.net/nongfu_spring/article/details/54342861
http://blog.csdn.net/solomon1558/article/details/52573596

<模型汇总_5>生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

前面介绍了CNN(Convolutional Neural Network)、BNN(Binarized neural network)dual-learning NMTDBN以及深度学习优化算法Batch Normalization和Layer Normalization。感兴趣的同学可以添加微信公众号深度学习与NLP,回复关键词“CNN”、“BNN“dual”DBN”、BN和LN获取对应文章链接。今天介绍GAN及其一些变体模型。

今天主要介绍2016年深度学习最火的模型生成对抗网络(Generative Adversarial Net-GANGAN是由现任谷歌大脑科学家的Ian Goodfellow2014年提出来的一种基于对抗训练(Adversarial training)过程来训练生成模型(Generative Model)的一种新的深度学习框架。GAN是由两个模型组成:一个生成模型G,用于获得输入样本x的分布(表示学习representation learning观点认为,深度学习对输入样本对(xy)关系拟合过程,其实就是在学习输入样本x的分布),一个判别模型(Discriminative ModelD,用于估计一个样本是真实的样本而不是由G生成的样本的概率。

先简单介绍下传统深度学习模型的分类。深度学习产生之初被分为生成模型和判别模型两大类。生成模型典型网络有深度信念网络(Deep Belief NetworkDBN)、堆叠自动编码器(Stacked Auto-EncoderSAE)和深度玻尔兹曼机(Deep Boltzmann Machine,DBM),生成模型认为模型的输出样本y是由模型的输入样本x生成的,一定存在一个最优的输入样本x*使得输出的y的值最大化,通过生成模型可以学习到输入样本x的表示representation。它的最大优势就是直接从输入样本x中进行无监督或半监督的学习,减少了对带标注样本的需求。判别模型认为模型的输入样本x是由输出样本y决定的,典型的网络如卷积神经网络(Convolution Neural NetworkCNN)。个人认为,GAN的产生将二者结合起来,生成模型G根据输入x产生输出y’,并传递给判别模型D判断是否是真实得数据。

 

GAN:Generative Adversarial Nets.Ian J. Goodfellow, Jean Pouget-Abadie.2014.06.10

 NIPS 2016 TutorialGenerative Adversarial Networks.Ian Goodfellow.2017.01.09

GAN的训练:GAN的训练分为两个部分:G的训练目标是最大化D做出错误识别的概率,就是尽可能让D判别出错;D的训练目标则是尽可能把G生成的样本找出来。极限情况下,当G能够完全恢复输入样本X的分布的时候,D已经把G生成的样本识别出来,所以G的输出概率处处为1/2。与传统的生成模型,如DBN相比,通过这种方式来训练生成模型,不需要计算复杂的马尔科夫链或者像CD-K算法那样进行展开的近似推理过程。

目标函数的构造,基于带有噪声的输入变量pz)定义了一个先验得到输入z,然后用GzQ)把输入z映射到生成器输出y’,G是处处可导的且采用一个多层神经网络来表示,用它的参数Q来拟合输入样本x的分布。再用一个多层网络来表示D,输出一个标量,表示来源于x而不是pz)的概率。对于G来说等价于最小化log1-D(G(z)))期望;对于D来说,等价于最大logDx)的期望,类似于一个minimax的游戏。由此可以得到GAN的目标函数VGD):

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

Outer loop训练完一次G之后,inner loop再去训练D,非常耗时,而且一直在一个训练集上训练容易造成过拟合。因此,在实际GAN训练过程中,D训练K次之后再训练G,这样做的目的是使D保持在一个optimum的状态,让G逐步慢慢变化。得到训练算法:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

Ian GoodFellow所提出的生成对抗网络或对抗训练的理论是一个通用的框架,基于GAN也产生了很多变体,适用于各种场景中解决实际问题,展示了GAN家族的强大威力,下面简要介绍其中比较有代表一些模型。

 

CGAN:Conditional generative adversarial nets for convolutional face generation.Jon Gauthier.2015.03

 Conditional Generative Adversarial Nets.Mehdi Mirza.2014.11.06

解决什么问题:图像标注、图像分类和图像生成过程中,存在两类问题:其一、输出图像的label比较多,成千上万类别;其二、对于一个输入x,对应合适输出ylabel)的类别multi-modal(多个),怎么样选择一个合适类别是个问题。CGAN尝试在生成器G和判别器端加入额外的条件信息(additional information)来指导GAN两个模型的训练。

怎么做:条件化(conditionalGAN做法就是直接把额外的信息(y)直接添加到生成器G和判别器D的的目标函数中,与输入ZX中构成条件概率,如下图所示:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

用于指导GD训练的额外信息可以是各种类型(multi-modal)的数据,已图像分类为例,可以是label标签,也可以是关于图像类别或其他信息的text文本描述。

 

DCGANUNSUPERVISED REPRESENTATION LEARNING WITH DEEP CONVOLUTIONAL GENERATIVE ADVERSARIAL NETWORKS.Alec Radford & Luke Metz.2016.01.07

解决什么问题:把有监督学习的CNN与无监督学习的GAN整合到一起提出了Deep Convolutional Generative Adversarial Networks – DCGANs,是生成器和判别器分别学到对输入图像层次化的表示。

本文的最大贡献:1、将CNNGAN结合在一起提出了DCGANs,使用DCGANs从大量的无标记数据(图像、语音)学习到有用的特征,相当于利用无标记数据初始化DCGANs的生成器和判别器的参数,在用于有监督场景,比如,图像分类。2、表示学习representation learning的工作:尝试理解和可视化GAN是如何工作的,多层的GAN的中间表示intermediate representation 是什么。3出了一些稳定训练DCGANsguidelines

DCGAN的网络结构:

生成器构G的造:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

四个堆叠的卷积操作来构成生成器G,没有采用全连接层。

 

InfoGAN: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets.Xi Chen, Yan Duan, Rein Houthooft, John Schulman.2016.06.12

解决什么问题:无监督学习或表示学习(representation learning)可以看做是从大量无标记数据中抽取有价值的特征、或学习一种重要的隐特征(semantic features)表示(representation)的问题。但无监督学习又是ill-posed,因为很多与无监督学习相关的下游的任务在训练时是未知的,而务监督学习也是一种分离/拆解 表示(disentangled representation),有助于下游相关但未知任务的学习,因为disentangled representation可以学习到输入样本的salient attribute。无监督学习中最重要的的模型就是生成模型Generative model,比如,生成对抗网络GAN和变分自动编码器()VAE。本文从disentangled representation角度出发,把信息理论(Information-theoretic)与GAN相结合提出InfoGAN,采用无监督的方式学习到输入样本X的可解释且有意义的表示(representation)。

怎么做:通过最大化隐变量(latent variable)的一个子集与observation之间的互信息

 

SeqGAN: Sequence Generative Adversarial Nets with Policy Gradient.Lantao Yuy, Weinan Zhangy, Jun Wangz, Yong Yuy.2016.12.09

解决什么问题:GAN:用一个判别模型D去指导生成模型G的训练,在generating real-valued打他取得巨大成功,但处理的都是连续可导的数据,比如图像,鲜有涉及离散数据,如文本。原因有两个:其一,梯度从判别器D没有办法反向传递会生成器GG离散不可导;其二,判别器D可以评测一个完整序列的score,但没法评测只生成了一部分的partially sequence现在和未来的score。因此,本文提出SeqGAN解决这两个问题。

怎么做:借鉴了强化学习中的reward的思想,在判别器D端,通过一个完整的sequence序列构造一个reward反馈会生成器G来指导生成器G的训练,通过RL中的策略梯度算法(policy gradient method)来优化G的参数,绕过了上面两个问题。

SeqGAN的结构:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

StackGAN: Text to Photo-realistic Image Synthesis with Stacked Generative Adversarial Networks.Han Zhang, Tao Xu, Hongsheng Li, Shaoting Zhang.2016.12.10

解决什么问题:根据text description生成图像有很多应用场景,比如图片辅助裁剪、计算机辅助设计等。但最大的问题是符合text描述的场景有很多(multi-modle),如何从中选择最佳的场景,生成高清晰的图片是个问题。本文基于GAN来做这个问题,原来方法只能生成64X64low resolution图片,本文使用Stack GAN生成了256X256的高清晰度图片,并且在CUBOxford-102数据集取得了比现有方法分别高28.47%20.30%improvement,这真的很厉害,也展示了GAN的强大功能。

怎么做:提出了一个堆叠的GAN模型用于“text-to-image”中生成高分辨率的图像,stack-1 GAN生成一张包含text文本所描述物体的初级形状和基本颜色的,像素为64X64的低分辨率图片,stack-2 GAN 根据GAN-1输出的低分辨率图片做为输入,加上text文本描述,进一步rectify defects和添加一些细节信息,进行refinement过程后生成一张256X256的高分辨率图片。

Stack GAN的网络结构:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

关键点在于Stack-GAN的两个GAN做么构建,怎么训练

 

WGAN:Wasserstein GAN.Martin Arjovsky, Soumith Chintala, and Lon Bottou.2017.03.09

  Improved Training of Wasserstein GANs.Ishaan Gulrajani1, Faruk Ahmed1, Martin Arjovsky2.2017.03.31

解决什么问题:GAN在训练很麻烦,需要精心设计生成器G和判别器D的网络结构,调整很多的超参数,经常不收敛。为了解决这个问题,让GAN训练起来更容易,本文提出了Wasserstein GANWGAN)。

怎么做:深入分析由GAN所优化的值函数(value function)的收敛特性,指出传统GAN不稳定是因为其基于Jensen-Shannon 差异(divergence)构造的值函数在某一地方不可导,导致生成器G训练不稳定。因此,提出了Earth-Mover距离,又称Wasserstein-1 距离Wq,p),基于Wasserstein distance来构造值函数,代替传统GAN中基于Jensen-Shannon 差异(divergence)的值函数。Wasserstein distance具有更好的特性,Jensen-Shannon divergence可能不连续,在不连续的地方不能提供稳定的梯度用于生成器G的参数优化;相比之下,Earth-Mover距离处处连续,处处可导。

Jensen-Shannon距离与Wassertein距离对比:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

WGAN的训练算法:

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

更多深度学习在NLP方面应用的经典论文、实践经验和最新消息,欢迎关注微信公众号“深度学习与NLPDeepLearning_NLP”或扫描二维码添加关注。

&lt;模型汇总_5&gt;生成对抗网络GAN及其变体SGAN_WGAN_CGAN_DCGAN_InfoGAN_StackGAN

深度学习之生成对抗网络GAN

以下内容将分为如下几个部分:

1. 为什么提出GAN


1.1 提出的背景

  想要对一个事物(某种数据)有更加深刻的理解,就需要利用到生成方法:对数据的分布进行假设和对分布进行参数学习,并能够根据学习而来的模型采样出新的样本。这是什么意思呢,其实可以分为两种玩法:
  (1)密度概率估计:也就是说在不了解时间概率分布的情况下,先假设随机分布(常用高斯分布,也称正态分布),然后通过数据观测来确定真正的概率密度是怎样的,知道了概率密度不就知道数据是怎么分布的了吗?
  (2)生成样本:即本身拥有非常多的训练样本数据,通过训练后的模型来生成类似的样本。也就是说我自己不事先假设随机分布了,依靠训练来让机器自动学得数据的分布。
  在神经网络还未发展如此火热之时,大多数的方法都是基于一个假设的随机分布,然后通过训练真实数据来拟合出模型,而GAN(Generative Adversarial Nets ,生成对抗网络)的目的就是估测数据样本的潜在分布形式并生成新得数据样本。


1.2 大概怎么解决问题

 上面说到生成对抗网络就是为了只通过训练样本数据(真实数据)来估计出真实数据的潜在分布形式,生成新的样本数据。
 基本思想:整个网络由一个生成器G(generator)和一个判别器D(discriminator)构成,介于博弈论中的二人零和博弈:

在严格竞争下,一方的收益必然意味着另一方的损失,博弈各方的收益和损失相加总和永远为“零”,双方不存在合作的可能。

双方争个你死我活,在互相争斗中同时历炼成长,即通过这种生成器G与判别器D对抗学习的方式来对真实数据进行学习来得到一个新的无限接近真实数据分布的数据分布。当然,两个网络并不是一直都在斗争,它们不得不协同合作以达到共同的目标。在整个训练过程中,辨别器不得不教导生成器如何在生成的数据上微做调整。同时它也一直都在学习如何做一个更好的老师。它们共同变强,在理想状态下,会达到一种平衡。

可能有人对这个新的数据分布有啥用还不是太理解,举几个例子吧。

作为一个具有 “无限” 生成能力的模型, GAN 的直接应用就是建模, 生成与真实数据分布一致的 数据样本, 例如可以生成图像、视频等. GAN 可以 用于解决标注数据不足时的学习问题, 例如无监督 学习、半监督学习等. GAN 还可以用于语音和语言 处理, 例如生成对话、由文本生成图像、生成数字、人脸等物体对象,构成各种逼真的室内外场景,,从分割图像恢复原图 像,,给黑白图像上色,从物体轮廓恢复物体图像,从低分辨率图像生成高分辨率图像等。 此外,GAN已经开始被应用到语音和语言处理、电脑病毒监测、棋类比赛程序等问题的研究中。

所以你可能要问了,这个生成器G和判别器D怎么来理解呢?

2. 基本思想及其过程


 生成式对抗网络 GAN (Generative adversarial networks) 是 Goodfellow 等在 2014 年提出的一种生成式模型。系统由一个生成器和一个判别器构成。生成器捕捉真实数据样本的潜在分布, 并生成新的数据样本; 判别器是一个二分类器, 判别输入是真实数据还是生成的样本。生成器和判别器均可以采用目前研究火热的深度神经网络。GAN 的优化过程是一个极小极大博弈 (Minimax game) 问 题, 优化目标是达到纳什均衡, 使生成器估测到数据样本的分布。
 再解释上面两个黑体的概念之前,先放一张GAN的网络框图1.1

深度学习之生成对抗网络GAN
图1.1 生成对抗网络框图

极大极小博弈怎么理解?其实就是由上图生成模型G生成的样本数据G(z)(也称fake data即造假数据),与真实数据x到底有多接近这样一个评判标准,你可以想象成损失函数即标签数据与训练得到数据得到的值之间差异的描述,当损失函数最小时,不就是标签数据与训练数据得到值之间的差异最小化。再说一句,后续大多数对GAN的研究都是对这个GAN的优化过程进行改进。
还有一种我得理解就是当你学无穷小的时候,老师怎么教的呢?就是当这个数比最小的那个数也就是ϵ,怎么样评判这个ϵ就成了定义无穷小的关键。
纳什均衡怎么理解?生成器G通过不断的训练学习来加强自己的造假能力也就是生成G(z),让判别器D无法判别出G(z)和x有啥区别。而判别器D就是通过不断训练学习来提升自己的判别能力,也就是判别出x就是x,G(z)你就是假的!


 下面就详细介绍下生成对抗网络GAN

  1. 输入:
    生成器G:随机噪声分布z(通常为高斯噪声分布)
    判别器D:真实数据x以及生成器生成造假数据G(z)

  2. 输出:
    当判别器D判别概率等于0.5时,输出G(z)=x

  3. 过程:
    目标函数
    深度学习之生成对抗网络GAN
    这个目标函数是一个交叉熵函数(为什么用交叉熵函数?)
    深度学习之生成对抗网络GAN
    接下来我们来理解这个目标函数,根据Ian GoodFellow本人说,max放在里面一是表明先对判别器D 进行训练,二是经过实验表明这样效果比较好,若min在内部容易产生过拟合问题。
    我们知道判别器D的目的就是正确区分出Pdata(x)和Pz(z),因此判别器D努力的增加D(x),并且减小D(G(z))(相当于增大(1-D(G(z)))因为,D()∈[0,1]),因此要maxV(G,D),而生成器G就是要增大D(G(z))(相当于减小(1-D(G(z)))),若C(G)=maxV(D,G),所以要minC(G)。先看看其训练过程。
    深度学习之生成对抗网络GAN
     训练过程就是多次迭代,以其中一次迭代为例,先对判别器D进行k次训练,训练过程为分别从真实样本Pdata和随机噪声Pz中抽样m个,然后利用上述目标函数(1)对参数进行求导,并且按梯度上升的方向求得最大值,然后进行生成器G的训练,从随机噪声z中抽样m个,并按图中的函数(因为只有随机噪声z一个输入)利用随机梯度下降的方法对参数求导求的最小值,从而形成一个极大极小博弈。
     下面来严谨分析下目标函数:
    为了方便分析,先假设生成器G不变,只分析判别器D。
    由(1)有:
    深度学习之生成对抗网络GAN
    要求V(G,D)最大值,就相当于求积分内的方程的最大值,将Pdata(x)和Pg(x)看作常数,对D(x)求导,相信你能很快求得D(x)极值为D*(x):
    深度学习之生成对抗网络GAN
    然后令C(G)=maxV(G,D)
    深度学习之生成对抗网络GAN
    当取得全局极值,也就说所谓的“纳什均衡”状态时,也就是Pdata(x)=Pg(x)时,此时C(G)=-log4,作者为了很好的衡量C(G),引入了JS散度
    深度学习之生成对抗网络GAN
    这里的KL代表KL散度,也就是:
    深度学习之生成对抗网络GAN
    但是这里有个问题就是KL散度只能针对两个分布之间有重叠的情况,分析这个式子就能看出,没有重叠的时候,两个分布相除为0,KL散度就是一个常数,不能进行训练了,因为常数的梯度为0。
    C(G)就变成了
    深度学习之生成对抗网络GAN
    因此C(G)的最小值实际上当Pdata=Pg时就是-log4。
     可能这时就有人问为什么判别器D执行k步时生成器G才执行1步,Ian GoodFellow说这有利于只要当生成器改变的足够慢时,判别器D总是维持在最优解的附近,我理解就是生成器G和判别器D训练时间不一致,为了让两个模型训练达到一个同步的状态,对D做了一定的延时处理,同步的目的是为了避免“Helevtica scenario”,即防止在训练过程中生成器丢失了许多相同于x的z值,因为生成器不就是为了学习到x的近似G(z)分布嘛。
     由于Ian GoodFellow的文章在生成器G和判别器D中都用的是多层感知机,且没有给出结构框图,为了方便理解,上两张DCGAN(deep convolutional generative adversarial network,称为深层卷积生成对抗网络)中的生成器G和判别器D的模型结构图方便理解。
    深度学习之生成对抗网络GAN
    上图为利用CNN作为一个生成器G,输入是噪声分布,通过解卷积升维最终得到一个图片大小和真实样本数据(图片)大小相同都为64x64x3,即长宽都为64的RGB三通道彩色图片。
    解卷积过程:即在原有像素上进行上采样,就是在其周围填充0,白色方块代表0像素,蓝色方块代表原始像素。
    深度学习之生成对抗网络GAN
    下图为判别器D的网络框图,输入为64x64x3的RGB三通道彩色真实样本图片或者造假图片,最后给出结果,1表示真实图片,0表示造假图片。
    深度学习之生成对抗网络GAN
    下图为卷积过程,即提取特征并降维的一个过程:
    深度学习之生成对抗网络GAN
     在DCGAN中,如果你把这些梯度加到生成的图片上,在辨别器看来,图片就会变得更真实一点。但是我们不仅仅把梯度加到图片上。相反,我们进一步反向传播这些图片梯度成为组成生成器的权重,这样一来,生成器就学习到如何生成这幅新图片。技术上来说,通过反向传播辨别器输出的梯度来调整生成图片。以这种方式训练生成器,你将会得到与图片形状一样的梯度向量。


3. GAN的优缺点及其发展

 首先Ian GoodFellow就说过,判别器D中的判别即使到了所谓了“纳什均衡状态”,也就是Pdata=Pz时,其结果并不是恒定,而是一个在其周围振荡的过程,不够稳定,并是不真正意义上的“纳什均衡状态”。

 对于为什么目标函数有Log,那是因为log是的小数据差异明显,根据对数函数的图像可以分析出来。也就是说更偏向生成器G一点点。
GAN的优点:
1. 使用潜在的编码用来表达潜在的维数,控制数据的隐含关系。
2. 数据逐渐统一。
3. 不需要马尔可夫链。
4. 被认为可以生成最好的样本分布。

GAN的缺点:
1. “纳什均衡”就没有那么好得到,因为根据实验观察, “纳什均衡状态”,也就是Pdata=Pz时,其结果并不是恒定,而是一个在其周围振荡的过程,不够稳定,并是不真正意义上的“纳什均衡”。 当博弈双方都由神经网络表示时,在没有实际达到均衡的情况下,让它们永远保持对自己策略的调整是可能的
2. 模式崩溃问题,GAN模型被定义为极小极大问题,没有损失函数,在训练过程中很难区分是否正在取得进展。GAN的学习过程可能发生崩溃问题(collapse problem),生成器开始退化,总是生成同样的样本点,无法继续学习。当生成模型崩溃时,判别模型也会对相似的样本点指向相似的方向,训练无法继续。
3. 不需要预先建模,模型过于自由而不可控。与其他生成式模型相比,GAN这种竞争的方式不再要求一个假设的数据分布,即不需要明确表达p(x),而是使用一种分布直接进行采样,从而真正达到理论上可以完全逼近真实数据,这也是GAN最大的优势。然而,这种不需要预先建模的方法缺点是太过自由了,对于较大的图片,较多的像素的情形,基于简单 GAN 的方式就不太可控了。在GAN中,每次学习参数的更新过程,被设为判别器D更新k回,生成器G才更新1回,也是出于类似的考虑。
4.
  z一般维数会比x高。最大似然估计实际上是KL散度最小化的方法。对KL散度进行简单的分解之后与最大似然估计的形式进行简单的比较即可明白。KL变换不能处理两个不重叠分布的问题,也是之后出现Wassertein GAN的主要原因之一。

 对于训练GAN,Ian GoodFellow也提出了两个小技巧:
1. 将标签进行平滑:也就是0->0.1,1->0.9,但是由于这0.1在D()中的分子项引入了Pg,也就是造假数据的分布,因此改为单边标签平滑,也就是0->0,1->0.9,这样做的好处是让判别函数D()不会给出太大的梯度信号,也防止算法走向极端样本的陷阱,增加了网络抗干扰的能力。
2. 批数据归一化:对输入数据x取其中m个,计算这些x的均值和标准差,归一化x为(x-mean(x))/standard deviation(x)。但由于可能同一批数据太过于相似,对于一个无监督的GAN而言,很容易被偏而误以为它们这些数据都一样,即最终的生成模型的结果会混着同一个batch的好多其他的无关特征。因此最终改进为取一个固定的batch R,然后对x归一化时,把x加入到R中,形成一个新的虚拟batch V,并用这个V的均值和标准差来对x进行归一化,这种方法叫虚拟批处理归一化


UFLDL教程: Exercise:Self-Taught Learning



自我学习


1.先训练稀疏自编码器提取特征,再把特征和label给softmax分类器进行训练,最后用test数据集进行测试。
2.由于实际应用中找到大量有标注的样本是非常困难的,所有采用先用大量无标注样本来进行无监督训练自编码器,再用自编码器来提取特征,配合有标注样本来进行有监督训练softmax分类器。
3.数据预处理方面:例如,如果对未标注数据集进行PCA预处理,就必须将得到的矩阵 U 保存起来,并且应用到有标注训练集和测试集上;而不能使用有标注训练集重新估计出一个不同的矩阵 U (也不能重新计算均值并做均值标准化),否则的话可能得到一个完全不一致的数据预处理操作,导致进入自编码器的数据分布迥异于训练自编码器时的数据分布。
4.自学习(self-taught learning) 不要求未标注数据 x_u 和已标注数据 x_l 来自同样的分布。另外一种带限制性的方式也被称为半监督学习,它要求 x_u和 x_l 服从同样的分布。


流程图


稀疏自编码器学习图像特征(实现自学习)—用到无标签的样本集
softmax回归对样本分类—用到有标签的训练样本集

UFLDL教程: Exercise:Self-Taught Learning

UFLDL教程: Exercise:Self-Taught Learning


步骤


第0步:设置神经网络的结构

该神经网络包括三层:
输入层的神经元个数(数字识别,则设置输入的图像大小)
输出端的神经元个数(也就是类别数)
隐藏层神经元个数
另外一些关于系数编码的参数
sparsityParam、lambda、beta
最大迭代次数:maxIter

第一步:产生无标签样本集和有标签样本集(训练数据集和测试数据集)

1)导入数据集mnistData和mnistLabels
mnistData是一个矩阵,每一列为一个输入样本(也就是一个输入的数字图像所有像素点按列排布)
mnistLabels是一个向量,它存储的数字表示mnistData中每一列样本的类别
(2)将输入的样本集mnistData进行分组
① 首先,将mnistData分为两组:一组为有标签的数据集(数字0-4的样本),另一组为无标签的数据集(数字5-9的样本)
(这两组的指标集分别为labeledSet和unlabeledSet)
② 然后,再将有标签的数据集平均分为两组,一组作为训练集、一组作为测试集;
(这两组的指标集分别为trainSet和testSet)
这里的指标,指在mnistData中的列序号
③ 分别得到上述三组指标集得到相应的数据集,并得到有标签数据集的标签
unlabeledData:无标签数据集,每一列为一个样本
trainData:有标签训练集,每一列为一个样本,相应的标签存放在trainLabels中
testData:有标签测试集,每一列为一个样本,相应的标签存放在testLabels中

用29404个无标注数据unlabeledData(手写数字数据库MNIST Dataset中数字为5-9的数据)来训练稀疏自动编码器,得到其权重参数opttheta。这一步的目的是提取这些数据的特征,虽然我们不知道它提取的究竟是哪些特征(当然,可以通过可视化结果看出来,可假设其提取的特征为Features),但是我们知道它提取到的特征实际上就是已训练好的稀疏自动编码器的隐藏层的激活值(即:第2层激活值)。注意:本节所有训练稀疏自动编码器的算法用的都L-BFGS算法。

第二步:训练稀疏自编码器

利用无标签数据集unlabeledData训练稀疏自编码器
① 初始化化自编码器的参数theta
② 调用minFunc中的最优化函数,计算得到稀疏自编码器的参数
包括设置minFunc函数的一些参数及对minFunc函数的调用,这里要用到稀疏自编码器的代价函数和梯度计算的函数sparseAutoencoderCost

第三步:利用稀疏自编码器对有标签的训练样本集和测试样本集提取特征

在得到稀疏自编码器后,可以利用它从有标签的数据集中提取图像特征,这里需要完成feedForwardAutoencoder.m函数
所谓图像的特征,其实就是指该图像在稀疏自编码器的权值矩阵W1作用下得到的隐藏层的输出
可以得到训练集的特征trainFeatures和测试集的特征testFeatures
它们的每一列分别是由稀疏自编码器提取出的特征

训练样本集提取特征

把15298个已标注数据trainData(手写数字数据库MNIST Dataset中数字为0-4的前一半数据)作为训练数据集通过这个已训练好的稀疏自动编码器(即:权重参数为opttheta的稀疏自动编码器),就可提取出跟上一步一样的相同的特征参数,这里trainData提取的特征表达假设为trainFeatures,它其实也是隐藏层的激活值。如果还不明白,这里打一个比方:假设上一步提取的是一个通信信号A(对应unlabeledData)的特征是一阶累积量,而这一步提取的就是通信信号B(对应trainData)的一阶累积量,它们提取的都是同样的特征,只是对象不同而已。同样地,unlabeledData和trainData提取的是同样的特征Features,只是对象不同而已。

测试样本集提取特征

把15298个已标注数据testData(手写数字数据库MNIST Dataset中数字为0-4的后一半数据)作为测试数据集通过这个已训练好的稀疏自动编码器(即:权重参数为opttheta的稀疏自动编码器),就可提取出跟上一步一样的相同的特征参数,这里testData提取的特征表达假设为testFeatures,它其实也是隐藏层的激活值。


注意:如果上一步对unlabeledData做了预处理,一定要把其各种数据预处理参数(比如PCA中主成份U)保存起来,因为这一步的训练数据集trainData和下一步的测试数据集testData也一定要做相同的预处理。本节练习,因为用的是手写数字数据库MNIST Dataset,已经经过了预处理,所以不用再预处理。
具体见:http://ufldl.stanford.edu/wiki/index.php/%E8%87%AA%E6%88%91%E5%AD%A6%E4%B9%A0


第四步:利用训练样本集训练softmax回归模型

利用训练集的特征集trainFeatures及其标签集trainLabels,训练softmax回归模型
注:softmaxTrain函数的输入参数(特征维数,标签数,惩罚项权值λ,训练数据集的数据,训练数据集的标签,其他参数)

把第三步提取出来的特征trainFeatures和已标注数据trainData的标签trainLabels作为输入来训练softmax分类器,得到其回归模型softmaxModel。

第五步:对测试数据集进行分类
利用得到的softmax回归模型对测试集进行分类

把第三步提取出来的特征testFeatures输入训练好的softmax回归模型softmaxModel,从而预测出已标注数据testData的类别pred,再把pred和已标注数据testData本来的标签testLabels对比,就可得出正确率。

综上,Self-taught learning是利用未标注数据,用无监督学习来提取特征参数,然后用有监督学习和提取的特征参数来训练分类器。

本次实验主要是进行0~4这5个数字的分类,虽然进行无监督训练用的是数字5~9的训练样本,这依然不会影响后面的结果。
5-9的数字进行降维的训练,训练出一般图像到低维空间的表示矩阵W和B,后用W和B,将要分类的图像0-4用低维表示.

%% CS294A/CS294W Self-taught Learning Exercise  %  Instructions %  ------------ %  %  This file contains code that helps you get started on the %  self-taught learning. You will need to complete code in feedForwardAutoencoder.m %  You will also need to have implemented sparseAutoencoderCost.m and  %  softmaxCost.m from previous exercises. % %% ====================================================================== %  STEP 0: Here we provide the relevant parameters values that will %  allow your sparse autoencoder to get good filters; you do not need to  %  change the parameters below. % 该神经网络包括三层: % 输入层的神经元个数(数字识别,则设置输入的图像大小) % 输出端的神经元个数(也就是类别数) % 隐藏层神经元个数  % 另外一些关于系数编码的参数 % sparsityParam、lambda、beta % 最大迭代次数:maxIter  % 设置神经网络的相关参数 inputSize  = 28 * 28;%样本特征维数 numLabels  = 5;%样本类别 hiddenSize = 200;%隐藏层神经元个数 sparsityParam = 0.1; % desired average activation of the hidden units.                      % (This was denoted by the Greek alphabet rho, which looks like a lower-case "p",                      %  in the lecture notes).  lambda = 3e-3;       % weight decay parameter        beta = 3;            % weight of sparsity penalty term    maxIter = 400;       %最大迭代步数  %% ======================================================================  % 第一步:产生无标签样本集和有标签样本集(训练数据集和测试数据集) % (1)导入数据集mnistData和mnistLabels % mnistData是一个矩阵,每一列为一个输入样本(也就是一个输入的数字图像所有像素点按列排布) % mnistLabels是一个向量,它存储的数字表示mnistData中每一列样本的类别 % (2)将输入的样本集mnistData进行分组 % ① 首先,将mnistData分为两组:一组为有标签的数据集(数字0-4的样本),另一组为无标签的数据集(数字5-9的样本) % (这两组的指标集分别为labeledSet和unlabeledSet) % ② 然后,再将有标签的数据集平均分为两组,一组作为训练集、一组作为测试集; % (这两组的指标集分别为trainSet和testSet) % 这里的指标,指在mnistData中的列序号 % ③ 分别得到上述三组指标集得到相应的数据集,并得到有标签数据集的标签 % unlabeledData:无标签数据集,每一列为一个样本 % trainData:有标签训练集,每一列为一个样本,相应的标签存放在trainLabels中 % testData:有标签测试集,每一列为一个样本,相应的标签存放在testLabels中   %  STEP 1: Load data from the MNIST database % %  This loads our training and test data from the MNIST database files. %  We have sorted the data for you in this so that you will not have to %  change it.  % Load MNIST database files addpath mnist/ %MNIST数据集及其相关操作函数均在此文件夹中 mnistData   = loadMNISTImages('mnist/train-images.idx3-ubyte'); mnistLabels = loadMNISTLabels('mnist/train-labels.idx1-ubyte');  % Set Unlabeled Set (All Images)  % 无标签样本集和有标签样本集的指标集(将整个数据集分为无标签样本集和有标签样本集) % Simulate a Labeled and Unlabeled set labeledSet   = find(mnistLabels >= 0 & mnistLabels <= 4);%返回mnistLabels中元素值大于等于0且小于等于4的数字的行号 unlabeledSet = find(mnistLabels >= 5);  % 训练数据集和测试数据集的指标集(有标签数据集再分为两部分:训练数据集和测试数据集) numTrain = round(numel(labeledSet)/2);%训练样本个数 trainSet = labeledSet(1:numTrain);%训练样本集 testSet  = labeledSet(numTrain+1:end);%测试样本集  % 无标记样本集的数据 unlabeledData = mnistData(:, unlabeledSet);  % 训练数据集的数据和标签 trainData   = mnistData(:, trainSet);% mnistData中大于等于0且小于等于4的数字的前一半数字作为有标签的训练数据 trainLabels = mnistLabels(trainSet)' + 1; % Shift Labels to the Range 1-5  % 测试数据集的数据和标签 testData   = mnistData(:, testSet);% mnistData中大于等于0且小于等于4的数字的后一半数字作为有标签的测试数据 testLabels = mnistLabels(testSet)' + 1;   % Shift Labels to the Range 1-5  % Output Some Statistics fprintf('# examples in unlabeled set: %d/n', size(unlabeledData, 2)); fprintf('# examples in supervised training set: %d/n/n', size(trainData, 2)); fprintf('# examples in supervised testing set: %d/n/n', size(testData, 2));  %% ======================================================================  % 第二步:训练稀疏自编码器 % 利用无标签数据集unlabeledData训练稀疏自编码器 % ① 初始化化自编码器的参数theta % ② 调用minFunc中的最优化函数,计算得到稀疏自编码器的参数 % 包括设置minFunc函数的一些参数及对minFunc函数的调用,这里要用到稀疏自编码器的代价函数和梯度计算的函数sparseAutoencoderCost  %  STEP 2: Train the sparse autoencoder %  This trains the sparse autoencoder on the unlabeled training %  images.   % 按均匀分布随机初始化theta参数, 初始化化自编码器的参数theta %  Randomly initialize the parameters theta = initializeParameters(hiddenSize, inputSize);  %% ----------------- YOUR CODE HERE ---------------------- %  Find opttheta by running the sparse autoencoder on %  unlabeledTrainingImages %  利用L-BFGS算法,用无标签数据集来训练稀疏自动编码器  %  利用无标签样本集对稀疏自编码器进行学习 %(利用优化函数,这里要用到minFunc文件夹下的优化函数和sparseAutoencoder文件夹下的sparseAutoencoderCost函数) addpath minFunc/ addpath sparseAutoencoder/ opttheta = theta;    % 优化函数的一些参数设置 options.Method = 'lbfgs'; % Here, we use L-BFGS to optimize our cost                           % function. Generally, for minFunc to work, you                           % need a function pointer with two outputs: the                           % function value and the gradient. In our problem,                           % sparseAutoencoderCost.m satisfies this. options.maxIter = 400;    % Maximum number of iterations of L-BFGS to run  options.display = 'on'; % 调用优化函数,得到opttheta,即为稀疏自编码器的所有权值构成的向量 [opttheta, cost] = minFunc( @(p) sparseAutoencoderCost(p, ...                                    inputSize, hiddenSize, ...                                    lambda, sparsityParam, ...                                    beta, unlabeledData), ...                               theta, options);   %% -----------------------------------------------------  % Visualize weights W1 = reshape(opttheta(1:hiddenSize * inputSize), hiddenSize, inputSize); display_network(W1');  %% ====================================================================== % 第三步:利用稀疏自编码器对有标签的训练样本集和测试样本集提取特征 % 在得到稀疏自编码器后,可以利用它从有标签的数据集中提取图像特征,这里需要完成feedForwardAutoencoder.m函数 % 所谓图像的特征,其实就是指该图像在稀疏自编码器的权值矩阵W1作用下得到的隐藏层的输出 % 可以得到训练集的特征trainFeatures和测试集的特征testFeatures % 它们的每一列分别是由稀疏自编码器提取出的特征  %% STEP 3: Extract Features from the Supervised Dataset %  You need to complete the code in feedForwardAutoencoder.m so that the  %  following command will extract features from the data. % 利用稀疏自编码器提取训练样本集中所有样本的特征 trainFeatures = feedForwardAutoencoder(opttheta, hiddenSize, inputSize, ...                                        trainData); % 利用稀疏自编码器提测试练样本集中所有样本的特征 testFeatures = feedForwardAutoencoder(opttheta, hiddenSize, inputSize, ...                                        testData);  %% ====================================================================== % 第四步:利用训练样本集训练softmax回归模型 % 利用训练集的特征集trainFeatures及其标签集trainLabels,训练softmax回归模型   % 注:softmaxTrain函数的输入参数(特征维数,标签数,惩罚项权值λ,训练数据集的数据,训练数据集的标签,其他参数)  %% STEP 4: Train the softmax classifier  softmaxModel = struct;   %% ----------------- YOUR CODE HERE ---------------------- %  Use softmaxTrain.m from the previous exercise to train a multi-class %  classifier.  %  利用L-BFGS算法,用从有标签训练数据集中提取的特征及其标签,训练softmax回归模型  %  Use lambda = 1e-4 for the weight regularization for softmax  lambda = 1e-4; inputSize = hiddenSize; numClasses = numel(unique(trainLabels));%unique为找出向量中的非重复元素并进行排序  % You need to compute softmaxModel using softmaxTrain on trainFeatures and % trainLabels  addpath Softmax_Regression/ options.maxIter = 100; softmaxModel = softmaxTrain(inputSize, numLabels, lambda, ...                             trainData, trainLabels, options);    

function [activation] = feedForwardAutoencoder(theta, hiddenSize, visibleSize, data) % 该函数的作用是:利用稀疏自编码器从数据中提取特征 % theta: trained weights from the autoencoder % visibleSize: the number of input units (probably 64)  % hiddenSize: the number of hidden units (probably 25)  % data: Our matrix containing the training data as columns.  So, data(:,i) is the i-th training example.   % We first convert theta to the (W1, W2, b1, b2) matrix/vector format, so that this  % follows the notation convention of the lecture notes.   W1 = reshape(theta(1:hiddenSize*visibleSize), hiddenSize, visibleSize); b1 = theta(2*hiddenSize*visibleSize+1:2*hiddenSize*visibleSize+hiddenSize);  %% ---------- YOUR CODE HERE -------------------------------------- %  Instructions: Compute the activation of the hidden layer for the Sparse Autoencoder. activation=sigmoid(W1*data+repmat(b1,1,size(data,2)));  %-------------------------------------------------------------------  end  %------------------------------------------------------------------- % Here's an implementation of the sigmoid function, which you may find useful % in your computation of the costs and the gradients.  This inputs a (row or % column) vector (say (z1, z2, z3)) and returns (f(z1), f(z2), f(z3)).   function sigm = sigmoid(x)     sigm = 1 ./ (1 + exp(-x)); end 

参考文献


UFLDL教程(五)之self-taught learning

Deep Learning 7_深度学习UFLDL教程:Self-Taught Learning_Exercise(斯坦福大学深度学习教程)

自我学习

Deep Learning 6_深度学习UFLDL教程:Softmax Regression_Exercise(斯坦福大学深度学习教程)

吴恩达 Andrew Ng 的公开课

DeepLearning(二) 自编码算法与稀疏性理解与实战

【原创】Liu_LongPo 转载请注明出处
【CSDN】http://blog.csdn.net/llp1992

在有监督学习中,训练样本是有类别标签的。现在假设我们只有一个没有带类别标签的训练样本集合 {x(1),x(2),x(3),...},其中 x(i)R

自编码神经网络是一种无监督学习算法,它使用了反向传播算法,并让目标值等于输入值,比如 y(i)=x(i),示例图如下:

DeepLearning(二) 自编码算法与稀疏性理解与实战

自编码神经网络尝试学习一个 hW,b(x)x 的函数。这看上去好像并不现实,因为我们在信息论中学过,信息经过每一层传递处理都会发生损耗,因此自编码神经网络的输入和输出是不可能相等的吼吼吼。

确实,自编码神经网络的输入和输出是不可能相等的,但是我们就是要强迫它相等,或者说尽可能地相等。(感觉它好不情愿= =)也就是:I ==> Sn ==> O , 且I要尽可能等于O。(I代表输入,Sn代表每一层中间隐藏层的输出a,O代表网络输出)

这么折腾究竟是要干嘛?要I等于O干嘛,好好的,要O去干嘛就直接拿I去不就好了。着实,O确实不重要,但是我们注意,Sn很重要!
Sn为什么重要?我们看上面的模型,输入I有6维,输出O有6维,中间层Sn呢?只有3维!这看出了什么?PCA?白化?差不多,但又有点不同。可以说是降维了,但PCA做的工作是提取了数据的最重要的成分,而这里的Sn是学习了数据更加本质的结构!为什么是这样?因为我们强迫它学习用3维的数据去表示6维的数据,为了完成这个目标,它不得不去寻找输入数据中存在的一些结构。

所以,中间层学习得到的3维输出Sn,就是深度学习网络学习得到的输入数据的更加本质的特征。如果增加中间层的层数,如下图:

DeepLearning(二) 自编码算法与稀疏性理解与实战

也就是:I–>S1–>S2–>…–>Sn–>O,每一个中间层 Sn1 的输出 an1 ,都作为下一层 Sn 的输入 , 于是每一个中间层学习到的特征都得到进一步学习抽象,比如,输入I是人脸的图像,S1学习到了图像的边缘,S2学习到了图像的边缘的组合,比如鼻子,S3学习到了人脸的大概模型等等,因此多层深度学习学习到的最终的特征将能够非常透彻的描述输入数据的本质,从而大大增加了最后面的分类和识别的精确度。

哦哦,这里说一下,深度学习本质:深度学习模型是工具,目的是学习到输入数据的特征。

也就是说,我们最后的分类或者识别之类的,还要加个分类器或者其他的东西。

稀疏性限制

刚才的论述是基于隐藏神经元数量较小的假设。但是即使隐藏神经元的数量较大(可能比输入像素的个数还要多),我们仍然通过给自编码神经网络施加一些其他的限制条件来发现输入数据中的结构。具体来说,如果我们给隐藏神经元加入稀疏性限制,那么自编码神经网络即使在隐藏神经元数量较多的情况下仍然可以发现输入数据中一些有趣的结构。
稀疏性可以被简单地解释如下。如果当神经元的输出接近于1的时候我们认为它被激活,而输出接近于0的时候认为它被抑制,那么使得神经元大部分的时间都是被抑制的限制则被称作稀疏性限制。这里我们假设的神经元的激活函数是sigmoid函数。如果你使用tanh作为激活函数的话,当神经元输出为-1的时候,我们认为神经元是被抑制的。

a(2)j(x) 表示输入为 x 时自编码神经网络隐藏神经元 j 的激活度,可得到

pj=1mi=1m[a(2)jx(i)]

其中 p 表示隐藏神经元 j 的平均活跃度,注意,这里是在训练集上求平均。

然后,我们又要委屈它了,我们加入一个条件:

pj=p

其中,p 为稀疏性参数,是一个比较接近于0的值,比如0.05.为了满足这个条件,我们得让大多数隐藏神经元的活跃度接近0.

何必为难它呢?为什么要让只要少部分中间隐藏神经元的活跃度,也就是输出值大于0,其他的大部分为0.原因就是我们要做的就是模拟我们人脑。神经网络本来就是模型人脑神经元的,深度学习也是。在人脑中有大量的神经元,但是大多数自然图像通过我们视觉进入人脑时,只会刺激到少部分神经元,而大部分神经元都是出于抑制状态的。而且,大多数自然图像,都可以被表示为少量基本元素(面或者线)的叠加。又或者说,这样更加有助于我们用少量的神经元提取出自然图像更加本质的特征。

为了实现这一限制,我们将会在我们的优化目标函数中加入一个额外的惩罚因子,而这一惩罚因子将惩罚那些 pjp 显著不同的情况,惩罚因子如下:

j=1s2plogppj+(1p)log1p1pj

其中, s2 表示隐藏神经元的数量。

基于相对熵的话,上述惩罚因子也可以表示为:

j=1s2KL(p||pj)

假设 p=0.2 ,则 s2j=1KL(p||pj) 随着 pj 的变化如下图:

DeepLearning(二) 自编码算法与稀疏性理解与实战

由上图可以看到,当 pj=p 的时候,s2j=1KL(p||pj) 的值为0,而当 pj 远离 p 的时候,s2j=1KL(p||pj) 的值快速增大。因此,很明显,这个惩罚因子的作用就是让 pj 尽可能靠近 p ,从而达到我们的稀疏性限制。更加具体的计算,请参考ULFDL。

Matlab 实战

这里的输入数据是 8×8 的一个小图片,转换为 64×1 的矩阵,总共10000个样本进行训练,学习图片中的特征,其实结果就是图片的边缘。效果如下图:

DeepLearning(二) 自编码算法与稀疏性理解与实战

代码如下:sparseAutoencoderCost.m

function [cost,grad] = sparseAutoencoderCost(theta, visibleSize, hiddenSize, ...                                              lambda, sparsityParam, beta, data) %lambda = 0; %beta = 0; % visibleSize: the number of input units (probably 64)  % hiddenSize: the number of hidden units (probably 25)  % lambda: weight decay parameter % sparsityParam: The desired average activation for the hidden units (denoted in the lecture %                           notes by the greek alphabet rho, which looks like a lower-case "p"). % beta: weight of sparsity penalty term % data: Our 64x10000 matrix containing the training data.  So, data(:,i) is the i-th training example.   % The input theta is a vector (because minFunc expects the parameters to be a vector).   % We first convert theta to the (W1, W2, b1, b2) matrix/vector format, so that this  % follows the notation convention of the lecture notes.   % 学习率 自己定义的 alpha = 0.01;  % 隐藏神经元的个数是   25   = hiddenSize  % 计算隐藏层神经元的激活度 p = zeros(hiddenSize,1);  % 25x64 W1 = reshape(theta(1:hiddenSize*visibleSize), hiddenSize, visibleSize); % 64 X 25 W2 = reshape(theta(hiddenSize*visibleSize+1:2*hiddenSize*visibleSize), visibleSize, hiddenSize); % 25 X1 b1 = theta(2*hiddenSize*visibleSize+1:2*hiddenSize*visibleSize+hiddenSize); % 64 x 1 b2 = theta(2*hiddenSize*visibleSize+hiddenSize+1:end);  % Cost and gradient variables (your code needs to compute these values).  % Here, we initialize them to zeros.   % costFunction 的第一项 %{ J_sparse = 0; W1grad = zeros(size(W1));  W2grad = zeros(size(W2)); b1grad = zeros(size(b1));  b2grad = zeros(size(b2)); %} %% ---------- YOUR CODE HERE -------------------------------------- %  Instructions: Compute the cost/optimization objective J_sparse(W,b) for the Sparse Autoencoder, %                and the corresponding gradients W1grad, W2grad, b1grad, b2grad. % % W1grad, W2grad, b1grad and b2grad should be computed using backpropagation. % Note that W1grad has the same dimensions as W1, b1grad has the same dimensions % as b1, etc.  Your code should set W1grad to be the partial derivative of J_sparse(W,b) with % respect to W1.  I.e., W1grad(i,j) should be the partial derivative of J_sparse(W,b)  % with respect to the input parameter W1(i,j).  Thus, W1grad should be equal to the term  % [(1/m) /Delta W^{(1)} + /lambda W^{(1)}] in the last block of pseudo-code in Section 2.2  % of the lecture notes (and similarly for W2grad, b1grad, b2grad). %  % Stated differently, if we were using batch gradient descent to optimize the parameters, % the gradient descent update to W1 would be W1 := W1 - alpha * W1grad, and similarly for W2, b1, b2.  %    % 批量梯度下降法的一次迭代  data 64x10000 numPatches = size(data,2); KLdist = 0;  % 25x10000 %a2 = zeros(size(W1,1),numPatches); % 64x10000 %a3 = zeros(size(W2,1),numPatches);  %% 向前传输 % 25x10000  25x64 64x10000  a2 = sigmoid(W1*data+repmat(b1,[1,numPatches])); p = sum(a2,2); a3 = sigmoid(W2 * a2 + repmat(b2,[1,numPatches])); J_sparse = 0.5 * sum(sum((a3-data).^2));  %{ for curPatch = 1:numPatches      % 计算激活值        % 25 X1 第二层的激活值   25x64  64x1     a2(:,curPatch) = sigmoid(W1 * data(:,curPatch) + b1);     % 计算隐藏层神经元的总激活值     p = p + a2(:,curPatch);      % 64 x1 第三层的激活值     a3(:,curPatch) = sigmoid(W2 * a2(:,curPatch) +b2);         %  计算costFunction的第一项     J_sparse = J_sparse + 0.5 * (a3(:,curPatch)-data(:,curPatch))' * (a3(:,curPatch)-data(:,curPatch)) ; end %}  %% 计算 隐藏层的平均激活度 p = p /  numPatches ;  %% 向后传输    %64x10000     residual3 = -(data-a3).*a3.*(1-a3);     %25x10000     tmp = beta * ( - sparsityParam ./ p + (1-sparsityParam) ./ (1-p));     %  25x10000   25x64 64x10000       residual2 = (W2' * residual3 + repmat(tmp,[1,numPatches])) .* a2.*(1-a2);     W2grad = residual3 * a2' / numPatches + lambda * W2 ;     W1grad = residual2 * data'  / numPatches + lambda * W1 ;     b2grad = sum(residual3,2) / numPatches;      b1grad = sum(residual2,2) / numPatches;       %{ for curPatch = 1:numPatches      %  计算残差  64x1        % residual3 = -( data(:,curPatch) - a3(:,curPatch)) .* (a3 - a3.^2);     residual3 = -(data(:,curPatch) - a3(:,curPatch)).* (a3(:,curPatch) - (a3(:,curPatch).^2));     %  25x1         25x 64  *  64X1   ==>  25X1  .*   25X1     residual2 = (W2' * residual3 + beta * (- sparsityParam ./ p + (1-sparsityParam) ./ (1-p))) .* (a2(:,curPatch) - (a2(:,curPatch)).^2);   %  residual2 = (W2' * residual3 ) .* (a2(:,curPatch) - (a2(:,curPatch)).^2);     % 计算偏导数值     %   64 x25   =  64x1    1x25     W2grad = W2grad + residual3 * a2(:,curPatch)';     % 64 x1 = 64x1     b2grad = b2grad + residual3;     % 25x64  =  25x1  * 1x64     W1grad = W1grad + residual2 *   data(:,curPatch)';     % 25x1 = 25x1     b1grad = b1grad + residual2;     %J_sparse = J_sparse + (a3 - data(:,curPatch))' * (a3 - data(:,curPatch));  end  W2grad = W2grad / numPatches + lambda * W2; W1grad = W1grad / numPatches + lambda * W1; b2grad = b2grad / numPatches; b1grad = b1grad / numPatches;   %}  %% 更新权重参数   加上 lambda  权重衰减 W2 = W2 - alpha * ( W2grad  ); W1 = W1 - alpha * ( W1grad );  b2 = b2 - alpha * (b2grad ); b1 = b1 - alpha * (b1grad );  %% 计算KL相对熵 for j = 1:hiddenSize     KLdist = KLdist + sparsityParam *log( sparsityParam / p(j) )   +   (1 - sparsityParam) * log((1-sparsityParam) / (1 - p(j))); end  %% costFunction 加上 lambda 权重衰减 cost = J_sparse / numPatches + (sum(sum(W1.^2)) + sum(sum(W2.^2))) * lambda / 2  + beta * KLdist;  %cost = J_sparse / numPatches + (sum(sum(W1.^2)) + sum(sum(W2.^2))) * lambda / 2;   %------------------------------------------------------------------- % After computing the cost and gradient, we will convert the gradients back % to a vector format (suitable for minFunc).  Specifically, we will unroll % your gradient matrices into a vector.  grad = [W1grad(:) ; W2grad(:) ; b1grad(:) ; b2grad(:)];  end  %------------------------------------------------------------------- % Here's an implementation of the sigmoid function, which you may find useful % in your computation of the costs and the gradients.  This inputs a (row or % column) vector (say (z1, z2, z3)) and returns (f(z1), f(z2), f(z3)).   function sigm = sigmoid(x)     sigm = 1 ./ (1 + exp(-x)); end

代码中包含了向量方式实现和非向量方式实现。向量方式实现代码量少,运行速度也很快。代码中注释写得很清楚了,就不说了。

接下来是实现第二个例子,我们将从如下的图像中学习其中包含的特征,这里的输入图像是 28×28 ,隐藏层单元是 196 个,算法使用向量化编程,不然又得跑很久了吼吼吼。

原始图像如下:

DeepLearning(二) 自编码算法与稀疏性理解与实战

最终学习得到的图像如下:

DeepLearning(二) 自编码算法与稀疏性理解与实战

代码如下:

function [cost,grad] = sparseAutoencoderCost(theta, visibleSize, hiddenSize, ...                                              lambda, sparsityParam, beta, data) %lambda = 0; %beta = 0; % visibleSize: the number of input units (probably 64)  % hiddenSize: the number of hidden units (probably 25)  % lambda: weight decay parameter % sparsityParam: The desired average activation for the hidden units (denoted in the lecture %                           notes by the greek alphabet rho, which looks like a lower-case "p"). % beta: weight of sparsity penalty term % data: Our 64x10000 matrix containing the training data.  So, data(:,i) is the i-th training example.   % The input theta is a vector (because minFunc expects the parameters to be a vector).   % We first convert theta to the (W1, W2, b1, b2) matrix/vector format, so that this  % follows the notation convention of the lecture notes.   % 学习率 自己定义的 alpha = 0.03;  % 计算隐藏层神经元的激活度 p = zeros(hiddenSize,1);  W1 = reshape(theta(1:hiddenSize*visibleSize), hiddenSize, visibleSize); W2 = reshape(theta(hiddenSize*visibleSize+1:2*hiddenSize*visibleSize), visibleSize, hiddenSize); b1 = theta(2*hiddenSize*visibleSize+1:2*hiddenSize*visibleSize+hiddenSize); b2 = theta(2*hiddenSize*visibleSize+hiddenSize+1:end);  % Cost and gradient variables (your code needs to compute these values).  % Here, we initialize them to zeros.   %% ---------- YOUR CODE HERE -------------------------------------- %  Instructions: Compute the cost/optimization objective J_sparse(W,b) for the Sparse Autoencoder, %                and the corresponding gradients W1grad, W2grad, b1grad, b2grad. % % W1grad, W2grad, b1grad and b2grad should be computed using backpropagation. % Note that W1grad has the same dimensions as W1, b1grad has the same dimensions % as b1, etc.  Your code should set W1grad to be the partial derivative of J_sparse(W,b) with % respect to W1.  I.e., W1grad(i,j) should be the partial derivative of J_sparse(W,b)  % with respect to the input parameter W1(i,j).  Thus, W1grad should be equal to the term  % [(1/m) /Delta W^{(1)} + /lambda W^{(1)}] in the last block of pseudo-code in Section 2.2  % of the lecture notes (and similarly for W2grad, b1grad, b2grad). %  % Stated differently, if we were using batch gradient descent to optimize the parameters, % the gradient descent update to W1 would be W1 := W1 - alpha * W1grad, and similarly for W2, b1, b2.  %  numPatches = size(data,2); KLdist = 0;  %% 向前传输  a2 = sigmoid(W1*data+repmat(b1,[1,numPatches])); p = sum(a2,2); a3 = sigmoid(W2 * a2 + repmat(b2,[1,numPatches])); J_sparse = 0.5 * sum(sum((a3-data).^2));  %% 计算 隐藏层的平均激活度 p = p /  numPatches ;  %% 向后传输       residual3 = -(data-a3).*a3.*(1-a3);     tmp = beta * ( - sparsityParam ./ p + (1-sparsityParam) ./ (1-p));     residual2 = (W2' * residual3 + repmat(tmp,[1,numPatches])) .* a2.*(1-a2);      W2grad = residual3 * a2' / numPatches + lambda * W2 ;     W1grad = residual2 * data'  / numPatches + lambda * W1 ;     b2grad = sum(residual3,2) / numPatches;      b1grad = sum(residual2,2) / numPatches;   %% 更新权重参数   加上 lambda  权重衰减 W2 = W2 - alpha * ( W2grad  ); W1 = W1 - alpha * ( W1grad );  b2 = b2 - alpha * (b2grad ); b1 = b1 - alpha * (b1grad );  %% 计算KL相对熵 for j = 1:hiddenSize     KLdist = KLdist + sparsityParam *log( sparsityParam / p(j) )   +   (1 - sparsityParam) * log((1-sparsityParam) / (1 - p(j))); end  %% costFunction 加上 lambda 权重衰减 cost = J_sparse / numPatches + (sum(sum(W1.^2)) + sum(sum(W2.^2))) * lambda / 2  + beta * KLdist;  %------------------------------------------------------------------- % After computing the cost and gradient, we will convert the gradients back % to a vector format (suitable for minFunc).  Specifically, we will unroll % your gradient matrices into a vector.  grad = [W1grad(:) ; W2grad(:) ; b1grad(:) ; b2grad(:)];  end  %------------------------------------------------------------------- % Here's an implementation of the sigmoid function, which you may find useful % in your computation of the costs and the gradients.  This inputs a (row or % column) vector (say (z1, z2, z3)) and returns (f(z1), f(z2), f(z3)).   function sigm = sigmoid(x)      sigm = 1 ./ (1 + exp(-x)); end  

稀疏编码(Sparse Coding)的前世今生(二)

       为了更进一步的清晰理解大脑皮层对信号编码的工作机制(策略),需要把他们转成数学语言,因为数学语言作为一种严谨的语言,可以利用它推导出期望和要寻找的程式。本节就使用概率推理(bayes views)的方式把稀疏编码扩展到随时间变化的图像上,因为人类或者哺乳动物在日常活动中通过眼睛获取的信号是随时间变化而变化的,对于此类信号仍然有一些稀疏系数和基可以描述他们,同类型的处理方式也有慢特征分析(slow features analysis)。废话不多说了,进入正题:

       我们把图像流(图像序列)稀疏编码(Sparse Coding)的前世今生(二)看成时空基函数稀疏编码(Sparse Coding)的前世今生(二)的线性组合再加上一些噪声稀疏编码(Sparse Coding)的前世今生(二),当然时空基函数可以想象成是时空不变的,类似于行为识别里的3D-SIFT,这点貌似又和慢特征分析扯上关系咯。同样时空基函数仍然有一些系数稀疏编码(Sparse Coding)的前世今生(二),用表示,则图像流则可以看成时空基和系数的卷积加上一些噪声,其模型如(公式一)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(公式一)

     整个模型可以形象的用(图一)展示,注意系数是一种单峰类似刺突的东东哦,(图一)上:

稀疏编码(Sparse Coding)的前世今生(二)

(图一)

      当然对于(图一)中的时空基函数稀疏编码(Sparse Coding)的前世今生(二)应尽可能的稀疏,便于减少运算量,不然对图像序列的运算量太大了。对于模型的参数求解,先假设系数独立且满足稀疏,bruno基于这些假设,给出了系数的先验公式,如(公式二)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(公式二)

      因为系数之间独立,所以他们的联合分布分解成单个分布的乘积形式,而且每个系数满足稀疏假设,S是个非凸函数控制着系数alpha的稀疏。有了这些先验知识,给定图像序列后的系数alpha的后验概率如(公式三)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(公式三)

     通过最大化此后验概率,然后利用其梯度下降法求解,求的系数alpha,全部求解步骤如(公式四)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(公式四)

      公式尽管这么多,但扔不足以说明求解系数的详细步骤,因为(公式三)的后两项仍然不清楚,再次对这二项再做个假设,如(公式五)所示:

稀疏编码(Sparse Coding)的前世今生(二)

稀疏编码(Sparse Coding)的前世今生(二)

(公式五)

      尽管做了如此假设,但是P(I|alpha,theta)仍然不能直接计算,需要对此项采样方能完成,这个地方是需要改进的地方,尽管如此,我们还是硬着头皮把学习基函数步骤一并贴出来,为后续改进打下铺垫。学习过程如(图二)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(图二)

      系数alpha通过梯度下降完成,基函数更新则通过Hebbian learning学习完成,Hebbian(海扁,又译赫布)学习就是加强同时激活的细胞之间的连接("Cells that firetogether, wire together."),这点可以稍微解释了“读书百遍”背后的大脑皮层可塑的工作机制。学习到的基函数如(图三)所示:

稀疏编码(Sparse Coding)的前世今生(二)

(图三)

      好咯,稀疏编码的生命科学的解释到此就差不多了,可以看到思想不错,但是手工假设的太多,学习方法也不友好,随着代数学以及LASSO的引入,稀疏码逐渐开始成熟,并开始走上应用的道路,到了DeepLearning时代,手工成分也越来越少,威力貌似也越来越大。(好吧,我承认这节写的很恶心稀疏编码(Sparse Coding)的前世今生(二),但是这节最大的亮点就是在空时域上编码,这对行为识别、语言识别啥的都有些帮助哦)

 

参考文献:

    Probabilistic Models of the Brain: Perception and Neural Function. MIT Press


转载请注明链接:http://blog.csdn.net/cuoqu/article/details/8989233

理解sparse coding

稀疏编码系列:

—————————————————————————

       

        本文的内容主要来自余凯老师在CVPR2012上给的Tutorial。前面在总结ScSPM和LLC的时候,引用了很多Tutorial上的图片。其实这个Tutorial感觉写的挺好的,所以这次把它大致用自己的语言描述一下。不过稀疏编码是前两年比较火的东西,现在火的是deep learning了。

1、What is sparse coding?

       1988年,神经稀疏编码的概念由Mitchison提出,由牛津大学的Rolls等正式引用。灵长目动物颚叶视觉皮层和猫视觉皮层的电生理实验报告和一些相关模型的研究结果都说明了视觉皮层复杂刺激的表达是采用稀疏编码原则的。研究表明:初级视觉皮层V1区第四层有5000万个(相当于基函数),而负责视觉感知的视网膜和外侧膝状体的神经细胞只有100万个左右(理解为输出神经元)。说明稀疏编码是神经信息群体分布式表达的一种有效策略。1996年,加州大学伯克利分校的Olshausen等在Nature杂志发表论文指出:自然图像经过稀疏编码后得到的基函数类似V1区简单细胞感受野的反应特性(空间局部性、空间方向性、信息选择性)。

       典型的sparse coding的过程分为训练和测试。

       Training:给定一些训练样本(training samples)[ x1, x2, …, xm(in Rd)],学习一本字典的基(bases)[Φ1,Φ2……(also in Rd)]。可是用k-means等无监督的方法,也可以用优化的方法(这时training完了同时也得到了这些training samples的codes,这是一个LASSO和QP问题的循环迭代);

       Coding:用优化的方法求解测试样本的codes(此时字典已经学得)。经典的方法是求解LASSO:

理解sparse coding                  (1)

        自我学习就是在Training的时候采用大量无标注的自然图像训练字典,然后对带标注的图像进行编码得到特征codes。

 

2、Connections to RBMs, autoencoders

      (1)式(经典的稀疏编码)有几个特点:

            ——系数a是稀疏的;

            ——a的维数一般比x的维数大;

            ——编码过程a=f(x)是一个非线性的关于x的隐函数(即我们没有f(x)的显示表达,因为求解LASSO没有解析解);

            ——重建过程x’=g(a)是一个线性的显示的关于a的函数(X’=ΣaiΦi)。

理解sparse coding

         而RBM和自编码的特点则是:

           ——有显示的f(x);

           ——不会必然得到稀疏的a,但是如果我们增加稀疏的约束(如稀疏自编码,稀疏RBM),通常能得到更好的效果(进一步说明sparse helps learning)。

         从广义上说,满足这么几个条件的编码方式a=f(x)都可以叫稀疏编码:

           1) a是稀疏的,且通常具有比x更高的维数;

           2) f(x)是一个非线性的映射;(jiang1st2010注:该条要求存疑,见下面解释。

           3) 重建的过程x’=g(a),使得重建后的x’与x相似。

          因此,sparse RBM,sparse auto-encoder,甚至VQ都可以算是一种sparse coding。(jiang1st2010注:第二条要求称f(x)是一个非线性映射,然而SPM中用到的VQ是一个线性映射,原因可以参见这里这里。余凯老师也是LLC论文的作者,似乎存在矛盾?不过这是个小问题了,没必要深究

 

3、Sparse activations vs. sparse models

         现在可以用a=f(x)表示稀疏编码的问题了。它可以分解成两种情况:

         1)sparse model:f(x)的参数是稀疏的

                  –例如:LASSO f(x)=<w,x>,其中w要求是稀疏的。(jiang1st2010注:这个例子中f(x)也是线性的!)

                  –这是一个特征选择的问题:所有的x都挑选相同的特征子集。

                  –hot topic.

         2)sparse activation:f(x)的输出是稀疏的

                  –就是说a是稀疏的。

                  –这是特征学习的问题:不同的x会激活不懂的特征子集。

                         理解sparse coding                  理解sparse coding

 

4、Sparsity vs. locality

       其实这个问题在这里已经谈过了。简单的说就是sparsity不一定导致locality,而locality肯定是sparse的。sparse不比locality好,因为locality具有smooth的特性(即相邻的x编码后的f(x)也是相邻的),而仅仅sparse不能保证smooth。smooth的特性对classification会具有更好的效果,并且设计f(x)时,应尽量保证相似的x在它们的codes中有相似的非0的维度。

理解sparse coding

 

         Tutorial上展示了(1)中取不同的λ,字典中各项呈现的效果:

理解sparse coding   理解sparse coding   

理解sparse coding   理解sparse coding

        作者想说明的问题是分类效果越好的情况下,basis会更清晰地表现出属于某几个特定的类。但是我没太看明白。

 

5、Hierarchical sparse coding

        这里图3曾说明了SIFT本身就是一个Coding+Pooling的过程,所以SPM是一个两层的Coding+Pooling。而Hierarchical sparse coding就是两层的coding都是sparse coding,如下图:

理解sparse coding

         整个HSC的第一层就从pixel层级开始(不需要手动设计SIFT特征了),经过两层SC后,形成codes。这个过程可以从无标注的数据中学习,就是self-taught learning。从pixel层级开始,这点和DNN啥的很像了。

理解sparse coding

          从结果来看,HSC的性能会比SIFT+SC稍微好些。

        

           Tutorial的最后列举了关于SC的其他主题,我也不懂,这里就不废话了。

 

—————–

作者:jiang1st2010

转载请注明出处:http://blog.csdn.net/jwh_bupt/article/details/9902949

TensorFlow简要教程系列(五)TensorFlow实现卷积神经网络(CNN)

这节我们利用TensorFlow建立卷积神经网络(CNN)进行图像识别,数据集还是利用MNIST图像识别数据集。在建立CNN之前我们会跑一下Softmax进行对比,有助于大家体会不同函数的用法,代码如下,具体说明见注释:

# -*- coding: utf-8 -*- """ Created on Sat Apr  1 14:57:40 2017  @author: chenbin """  import input_data mnist = input_data.read_data_sets('MNIST_data', one_hot=True)  import tensorflow as tf sess = tf.InteractiveSession()   x = tf.placeholder("float", shape=[None, 784]) y_ = tf.placeholder("float", shape=[None, 10])  W = tf.Variable(tf.zeros([784,10])) b = tf.Variable(tf.zeros([10]))  sess.run(tf.initialize_all_variables()) #一次性初始化所有变量  y = tf.nn.softmax(tf.matmul(x,W) + b)  cross_entropy = -tf.reduce_sum(y_*tf.log(y))  train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)  for i in range(1000):   batch = mnist.train.next_batch(50)   train_step.run(feed_dict={x: batch[0], y_: batch[1]})    correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))  print (accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))   #CNN  #我们会建立大量权重和偏置项,为了方便,定义初始函数   def weight_variable(shape):   initial = tf.truncated_normal(shape, stddev=0.1) #tf.truncated_normal初始函数将根据所得到的均值和标准差,生成一个随机分布   return tf.Variable(initial)  def bias_variable(shape):   initial = tf.constant(0.1, shape=shape)   return tf.Variable(initial) """ 由于我们使用的是ReLU神经元,因此比较好的做法是用一个较小的正数来初始化偏置项, 以避免神经元节点输出恒为0的问题(dead neurons) """  #卷积池化操作   """ 我们的卷积使用1步长(stride size),0边距(padding size)的模板 保证输出和输入是同一个大小。我们的池化用简单传统的2x2大小的模板做max pooling """  def conv2d(x, W):   return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') """ 1. x是输入的样本,在这里就是图像。x的shape=[batch, height, width, channels]。  - batch是输入样本的数量  - height, width是每张图像的高和宽  - channels是输入的通道,比如初始输入的图像是灰度图,那么channels=1,如果是rgb,那么channels=3。对于第二层卷积层,channels=32。  2. W表示卷积核的参数,shape的含义是[height,width,in_channels,out_channels]。  3. strides参数表示的是卷积核在输入x的各个维度下移动的步长。了解CNN的都知道,在宽和高方向stride的大小决定了卷积后图像的size。这里为什么有4个维度呢?因为strides对应的是输入x的维度,所以strides第一个参数表示在batch方向移动的步长,第四个参数表示在channels上移动的步长,这两个参数都设置为1就好。重点就是第二个,第三个参数的意义,也就是在height于width方向上的步长,这里也都设置为1。  4. padding参数用来控制图片的边距,’SAME’表示卷积后的图片与原图片大小相同,’VALID’的话卷积以后图像的高为Heightout=Height原图−Height卷积核+1StrideHeight, 宽也同理。 """  def max_pool_2x2(x):   return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],                         strides=[1, 2, 2, 1], padding='SAME') """                        这里用2∗2的max_pool。参数ksize定义pool窗口的大小,每个维度的意义与之前的strides相同 第一个参数value:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是[batch, height, width, channels]这样的shape 第二个参数ksize:池化窗口的大小,取一个四维向量,一般是[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1 第三个参数strides:和卷积类似,窗口在每一个维度上滑动的步长,一般也是[1, stride,stride, 1] 第四个参数padding:和卷积类似,可以取'VALID' 或者'SAME' 返回一个Tensor,类型不变,shape仍然是[batch, height, width, channels]这种形式 这个函数的功能是将整个图片分割成2x2的块, 对每个块提取出最大值输出。可以理解为对整个图片做宽度减小一半,高度减小一半的降采样 """  #卷积一 W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) """ 它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征(理解为做了32次卷积,每次卷积中不同的神经元享有同样参数;但是不同次卷积所用的参数是不同的)。 卷积的权重张量形状是[5, 5, 1, 32],前两个维度是patch的大小,接着是输入的通道数目, 最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量 """   x_image = tf.reshape(x, [-1,28,28,1]) """ 为了用这一层,我们把x变成一个4d向量,其第2、第3维对应图片的宽、高, 最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3) 第一维-1代表将x沿着最后一维进行变形 """  h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) h_pool1 = max_pool_2x2(h_conv1)   #卷积二 """ 为了构建一个更深的网络,我们会把几个类似的层堆叠起来。 第二层中,每个5x5的patch会得到64个特征。 """ W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64])  h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) h_pool2 = max_pool_2x2(h_conv2)   #密集连接层 """ 现在,图片尺寸减小到7x7(pool两次,相当于降采样两次),我们加入一个有1024个神经元的全连接层, 用于处理整个图片。我们把池化层输出的张量reshape成一些向量,乘上权重矩阵,加上偏置, 然后对其使用ReLU """ W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024])  h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)  #droput """ 为了减少过拟合,我们在输出层之前加入dropout。 我们用一个placeholder来代表一个神经元的输出在dropout中保持不变的概率。 这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。  TensorFlow的tf.nn.dropout操作除了可以屏蔽神经元的输出外, 还会自动处理神经元输出值的scale。所以用dropout的时候可以不用考虑scale  Dropout是指在模型训练时随机让网络某些隐含层节点的权重不工作, 不工作的那些节点可以暂时认为不是网络结构的一部分, 但是它的权重得保留下来(只是暂时不更新而已), 因为下次样本输入时它可能又得工作了 """ keep_prob = tf.placeholder("float") h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)  #输出层  W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10])  y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)    cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) sess.run(tf.initialize_all_variables()) for i in range(3000):   batch = mnist.train.next_batch(50)   if i%100 == 0:     train_accuracy = accuracy.eval(feed_dict={         x:batch[0], y_: batch[1], keep_prob: 1.0})     print ("step %d, training accuracy %g"%(i, train_accuracy))   train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})  print ("test accuracy %g"%accuracy.eval(feed_dict={     x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))      

我们只训练了3000次,最后准确率为0.986,大家主要体会建立卷积网络的过程和各个函数的用法,尤其是特征的计算,需要的大家对卷积网络很了解,可以多看看注释及复习相关知识,欢迎讨论~

step 2400, training accuracy 0.98 step 2500, training accuracy 0.96 step 2600, training accuracy 0.98 step 2700, training accuracy 1 step 2800, training accuracy 0.98 step 2900, training accuracy 1 test accuracy 0.986

Deep Learning-TensorFlow (11) CNN卷积神经网络_解读 VGGNet

环境:Win8.1 TensorFlow1.0.1

软件:Anaconda3 (集成Python3及开发环境)

TensorFlow安装:pip install tensorflow (CPU版) pip install tensorflow-gpu (GPU版)

TFLearn安装:pip install tflearn

参考:

1. Very Deep Convolutional Networks for Large-Scale Image Recognition

2. Github: tflearn

3. 独家 |《TensorFlow实战》作者黄文坚:四大经典CNN网络技术原理

1. 前言

VGGNet 是牛津大学计算机视觉组(Visual Geometry Group)和 Google DeepMind 公司的研究员一起研发的的深度卷积神经网络,在 ILSVRC 2014 上取得了第二名的成绩,将 Top-5错误率降到7.3%

VGGNet 探索了卷积神经网络的深度与其性能之间的关系,通过反复堆叠3´3的小型卷积核和2´2的最大池化层,VGGNet 成功地构筑了16~19层深的卷积神经网络。VGGNet 相比之前 state-of-the-art 的网络结构,错误率大幅下降,并取得了 ILSVRC 2014 比赛分类项目的第2名和定位项目的第1名。

同时 VGGNet 的拓展性很强,迁移到其他图片数据上的泛化性非常好。VGGNet 的结构非常简洁,整个网络都使用了同样大小的卷积核尺寸(3´3)和最大池化尺寸(2´2)。到目前为止,VGGNet 依然经常被用来提取图像特征(!!!)。VGGNet 训练后的模型参数在其官方网站上开源了,可用来在 domain specific 的图像分类任务上进行再训练(相当于提供了非常好的初始化权重),因此被用在了很多地方。

2. 网络结构

VGGNet 论文中全部使用了3´3的卷积核和2´2的池化核,通过不断加深网络结构来提升性能。下图所示为 VGGNet 各级别的网络结构图,以及随后的每一级别的参数量,从11层的网络一直到19层的网络都有详尽的性能测试。虽然从A到E每一级网络逐渐变深,但是网络的参数量并没有增长很多,这是因为参数量主要都消耗在最后3个全连接层。前面的卷积部分虽然很深,但是消耗的参数量不大,不过训练比较耗时的部分依然是卷积,因其计算量比较大。这其中的D、E也就是我们常说的 VGGNet-16 和 VGGNet-19。C相比B多了几个1´1的卷积层,1´1卷积的意义主要在于线性变换,而输入通道数和输出通道数不变,没有发生降维

Deep Learning-TensorFlow (11) CNN卷积神经网络_解读 VGGNet

VGGNet 拥有5段卷积,每一段内有2~3个卷积层,同时每段尾部会连接一个最大池化层用来缩小图片尺寸。每段内的卷积核数量一样,越靠后的段的卷积核数量越多:64 – 128 – 256 – 512 – 512。其中经常出现多个完全一样的3´3的卷积层堆叠在一起的情况,这其实是非常有用的设计。如下图所示,两个3´3的卷积层串联相当于1个5´5的卷积层,即一个像素会跟周围5´5的像素产生关联,可以说感受野大小为5´5。而3个3´3的卷积层串联的效果则相当于1个7´7的卷积层。除此之外,3个串联的3´3的卷积层,拥有比1个7´7的卷积层更少的参数量,只有后者的一半。最重要的是,3个3´3的卷积层拥有比1个7´7的卷积层更多的非线性变换(前者可以使用三次 ReLU 激活函数,而后者只有一次),使得 CNN 对特征的学习能力更强。

Deep Learning-TensorFlow (11) CNN卷积神经网络_解读 VGGNet

两个串联3´3的卷积层功能类似于一个5´5的卷积层

VGGNet 在训练时有一个小技巧,先训练级别A的简单网络,再复用A网络的权重来初始化后面的几个复杂模型,这样训练收敛的速度更快。在预测时,VGG 采用 Multi-Scale 的方法,将图像 scale 到一个尺寸 Q,并将图片输入卷积网络计算。然后在最后一个卷积层使用滑窗的方式进行分类预测,将不同窗口的分类结果平均,再将不同尺寸 Q的结果平均得到最后结果,这样可提高图片数据的利用率并提升预测准确率。同时在训练中,VGGNet 还使用了 Multi-Scale 的方法做数据增强,将原始图像缩放到不同尺寸 S,然后再随机裁切224´224的图片,这样能增加很多数据量,对于防止模型过拟合有很不错的效果。实践中,作者令 S 在[256,512]这个区间内取值,使用 Multi-Scale 获得多个版本的数据,并将多个版本的数据合在一起进行训练。

总结:

  1. 在 AlexNet 基础上将单层网络替换为堆叠的3´3的卷积层和2´2的最大池化层,减少卷积层参数,同时加深网络结构提高性能;
  2. 采用 Pre-trained 方法利用浅层网络(A)训练参数初始化深层网络参数(D,E),加速收敛;
  3. 采用 Multi-Scale 方法进行数据增强、训练、测试,提高准确率;
  4. 去掉了 LRN,减少了内存的小消耗和计算时间。

问题:


  • 虽然 VGGNet 减少了卷积层参数,但实际上其参数空间比 AlexNet 大,其中绝大多数的参数都是来自于第一个全连接层,耗费更多计算资源。在随后的 NIN 中发现将这些全连接层替换为全局平均池化,对于性能影响不大,同时显著降低了参数数量。
  • 采用 Pre-trained 方法训练的 VGG model(主要是 D 和 E),相对其他的方法参数空间很大,所以训练一个 VGG 模型通常要花费更长的时间,所幸有公开的 Pre-trained model 让我们很方便的使用。

3. 实例

在 tflearn/examples/images/ 下有使用 TFLearn 搭建的 VGGNet,完成 Oxford 17 类鲜花 数据集分类任务。其结构定义:

# -*- coding: utf-8 -*-  """ Very Deep Convolutional Networks for Large-Scale Visual Recognition.  Applying VGG 16-layers convolutional network to Oxford's 17 Category Flower Dataset classification task.  References:     Very Deep Convolutional Networks for Large-Scale Image Recognition.     K. Simonyan, A. Zisserman. arXiv technical report, 2014.  Links:     http://arxiv.org/pdf/1409.1556  """  from __future__ import division, print_function, absolute_import  import tflearn from tflearn.layers.core import input_data, dropout, fully_connected from tflearn.layers.conv import conv_2d, max_pool_2d from tflearn.layers.estimator import regression  # Data loading and preprocessing import tflearn.datasets.oxflower17 as oxflower17 X, Y = oxflower17.load_data(one_hot=True)  # Building 'VGG Network' network = input_data(shape=[None, 224, 224, 3])  network = conv_2d(network, 64, 3, activation='relu') network = conv_2d(network, 64, 3, activation='relu') network = max_pool_2d(network, 2, strides=2)  network = conv_2d(network, 128, 3, activation='relu') network = conv_2d(network, 128, 3, activation='relu') network = max_pool_2d(network, 2, strides=2)  network = conv_2d(network, 256, 3, activation='relu') network = conv_2d(network, 256, 3, activation='relu') network = conv_2d(network, 256, 3, activation='relu') network = max_pool_2d(network, 2, strides=2)  network = conv_2d(network, 512, 3, activation='relu') network = conv_2d(network, 512, 3, activation='relu') network = conv_2d(network, 512, 3, activation='relu') network = max_pool_2d(network, 2, strides=2)  network = conv_2d(network, 512, 3, activation='relu') network = conv_2d(network, 512, 3, activation='relu') network = conv_2d(network, 512, 3, activation='relu') network = max_pool_2d(network, 2, strides=2)  network = fully_connected(network, 4096, activation='relu') network = dropout(network, 0.5) network = fully_connected(network, 4096, activation='relu') network = dropout(network, 0.5) network = fully_connected(network, 17, activation='softmax')  network = regression(network, optimizer='rmsprop',                      loss='categorical_crossentropy',                      learning_rate=0.0001)  # Training model = tflearn.DNN(network, checkpoint_path='model_vgg',                     max_checkpoints=1, tensorboard_verbose=0) model.fit(X, Y, n_epoch=500, shuffle=True,           show_metric=True, batch_size=32, snapshot_step=500,           snapshot_epoch=False, run_id='vgg_oxflowers17') 

由于 TFLearn 的便捷性,我们只需要在上篇 AlexNet 定义基础上修改每一层卷积层,同时 TFLearn 支持不同的 optimizer:

SGD Stochastic Gradient Descent SGD Optimizer accepts learning rate decay. When training a model, it is often recommended to lower the learning rate as the training progresses.
RMSProp RMSProp Maintain a moving (discounted) average of the square of gradients. Divide gradient by the root of this average.
Adam Adam The default value of 1e-8 for epsilon might not be a good default in general. For example, when training an Inception network on ImageNet a current good choice is 1.0 or 0.1.
Momentum Momentum Momentum Optimizer accepts learning rate decay. When training a model, it is often recommended to lower the learning rate as the training progresses. The function returns the decayed learning rate
AdaGrad AdaGrad Adaptive Subgradient Methods for Online Learning and Stochastic Optimization. J. Duchi, E. Hazan & Y. Singer. Journal of Machine Learning Research 12 (2011) 2121-2159.
Ftrl Ftrl Proximal Ad Click Prediction: a View from the Trenches
AdaDelta AdaDelta ADADELTA: An Adaptive Learning Rate Method, Matthew D. Zeiler, 2012.
ProximalAdaGrad ProximalAdaGrad Efficient Learning using Forward-Backward Splitting. J. Duchi, Yoram Singer, 2009.

同时,TFlearn 下还支持 vgg_network_finetuning.py,利用已经训练的 model 在自己的 dataset 上 finetune。