深入理解变分推断

问题描述

假设我们有一个包含N个数据点的集合 X=\{x^{(i)},i=1,2,\dots,N\},这些数据点是从连续或离散随机变量中采样得到的。假设这些数据是由某个随机过程产生的,并且涉及到一个不可观测的连续随机变量z。这个随机过程包含两个步骤:

1. z^{(i)}由某个先验分布p_{\theta^\ast}(z)生成;

2. x^{(i)}由某个条件分布p_{\theta^\ast}(x|z)生成。

我们假设先前的p_{\theta^\ast}(z)和似然p_{\theta^\ast}(x|z)来自参数族分布p_{\theta}(z)p_{\theta}(x|z)。不幸的是,这个随机过程是隐藏的:真实的参数\theta^\ast以及隐变量z^{(i)}对于我们来说是不可知的。

为了解决上述问题,我们引入一个识别模型q_\phi(z|x),它被用于逼近真实后验分布p_{\theta}(z|x)

从编码理论的角度来看,不可观测的变量z有一个潜在的表示或编码。在本文中,我们也把识别模型q_\phi(z|x)称为概率编码器,类似的,我们把p_{\theta}(x|z)称为概率解码器,因为给定一个编码z,它产生一个x的分布。

变分下界的推导

变分推断的目标是尽量缩小q_\phi(z|x)p_{\theta}(z|x)的KL散度(详细介绍参见附录2)。

D_{KL} (q_\phi (z|x) \parallel p_\theta (z|x))

= \int {q_\phi (z|x) \ln \frac{q_\phi (z|x)}{p_\theta (z|x)}}dz

= -\int {q_\phi (z|x) \ln \frac{p_\theta (z|x)}{q_\phi (z|x)}}dz

= -\int {q_\phi (z|x) \ln \frac{p_\theta (x, z)}{q_\phi (z|x) p_\theta (x)}}dz

= \int {q_\phi (z|x) \ln p_\theta (x)}dz + \int {q_\phi (z|x) \ln q_\phi (z|x)}dz - \int {q_\phi (z|x) \ln p_\theta (x, z)}dz

= \ln p_\theta (x) + \int {q_\phi (z|x) \ln q_\phi (z|x)}dz - \int {q_\phi (z|x) \ln p_\theta (x, z)}dz

L(q) = \int {q_\phi (z|x) \ln p_\theta (x, z)}dz - \int {q_\phi (z|x) \ln q_\phi (z|x)}dz

那么有:

\ln p_\theta (x) = D_{KL} (q_\phi (z|x) \parallel p_\theta (z|x)) + L(q)

由于KL散度永远大于等于0,因此\ln p_\theta (x) \geqslant L(q)。所以,L(q)被称为变分下界。由于\ln p_\theta (x)不包含z,因此可以把它看成常数。那么最小化KL散度D_{KL} (q_\phi (z|x) \parallel p_\theta (z|x))就等价于最大化变分下界L(q)

L(q) = \int {q_\phi (z|x) \ln p_\theta (x, z)}dz - \int {q_\phi (z|x) \ln q_\phi (z|x)}dz

= \int {q_\phi (z|x) \ln p_\theta (x|z)dz} + \int {q_\phi (z|x) \ln p_\theta (z)}dz - \int {q_\phi (z|x) \ln q_\phi (z|x)}dz

= -D_{KL} (q_\phi (z|x) \parallel p_\theta (z)) + \int {q_\phi (z|x) \ln p_\theta (x|z)dz}

q_\phi (z|x)是我们引入的一个分部函数,上面的等式对任意的q_\phi (z|x)都成立。另外,q_\phi (z|x)也可以是一个高维随机变量,也可以看成是q_\phi (z|x) = q_\phi (z_1, z_2, \dots, z_n|x)多个随机变量的联合分部。

平均场理论

在平均场理论中,q_\phi (z|x)被构造成了:

q_\phi (z|x) = q_\phi (z_1, z_2, \dots, z_n|x) = \prod_{i = 1}^{n} q_\phi (z_i|x)

也就是假设q_\phi (z|x)函数的随机变量之间是相互独立的。

变分约束

在变分自编码器中,p_\theta (z)被限定为服从标准正态分布N(0, 1)q_\phi (z|x)被限定为服从均值为\mu方差为\sigma^2的正态分布,即:

q_\phi (z|x) = N(z;u_z(x,\phi), \sigma_z^2(x,\phi))

其中,高斯函数的均值和方差都是x\phi的函数。

于是,可以得到变分自编码器的变分下界:

L(\phi, \theta; x) = -D_{KL} (q_\phi (z|x) \parallel p_\theta (z)) + \int {q_\phi (z|x) \ln p_\theta (x|z)dz}

= L_1 + L_2

其中,\phi\theta分别表示变分自编码器的编码、解码网络层参数。

变分下界的推导

变分下界第一项的推导

由于p_\theta (z)服从标准正态分布函数N(0, 1),而q_\phi (z|x)服从正态分布N(z;u_z(x,\phi), \sigma_z^2(x,\phi))

于是

L_1 = \int {q_\phi (z|x) \ln p_\theta (z)}dz - \int {q_\phi (z|x) \ln q_\phi (z|x)}dz

分成两项分别求积分,首先求\int {q_\phi (z|x) \ln p_\theta (z)}dz的积分:

\int {q_\phi (z|x) \ln p_\theta (z)}dz

= \int {\frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(z-\mu)^2}{2\sigma^2}} \ln \frac{1}{\sqrt{2\pi}} e^{-\frac{z^2}{2}}}dz

= \frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{(z-\mu)^2}{2\sigma^2}} [-\frac{1}{2} \ln 2\pi - \frac{z^2}{2}]}dz

= -\frac{1}{2} \ln 2\pi [\frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{(z-\mu)^2}{2\sigma^2}}}dz] - \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{(z-\mu)^2}{2\sigma^2}}}z^2dz 

= -\frac{1}{2} \ln 2\pi - \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{(z-\mu)^2}{2\sigma^2}}}z^2dz 

y = z - \mu,有

\frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{(z-\mu)^2}{2\sigma^2}}}z^2dz 

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} \int {e^{-\frac{y^2}{2\sigma^2}}}(y-\mu)^2dy

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} [\int {y^2 e^{-\frac{y^2}{2\sigma^2}}}dy + \int {2y\mu e^{-\frac{y^2}{2\sigma^2}}}dy + \int {\mu^2 e^{-\frac{y^2}{2\sigma^2}}}dy]

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} [\int {y^2 e^{-\frac{y^2}{2\sigma^2}}}dy + \mu^2 \sqrt{2\pi\sigma^2}]

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} [-\sigma^2\int {y d e^{-\frac{y^2}{2\sigma^2}}}dy + \mu^2 \sqrt{2\pi\sigma^2}]

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} [\sigma^2 [\int {e^{-\frac{y^2}{2\sigma^2}}dy - y e^{-\frac{y^2}{2\sigma^2}}{|^{+\infty}_{-\infty}}}] + \mu^2 \sqrt{2\pi\sigma^2}]

= \frac{1}{2} \frac{1}{\sqrt{2\pi\sigma^2}} [\sigma^2 \sqrt{2\pi\sigma^2} + \mu^2 \sqrt{2\pi\sigma^2}]

= \frac{1}{2} (\sigma^2 + \mu^2)

因此

\int {q_\phi (z|x) \ln p_\theta (z)}dz = -\frac{1}{2} \ln 2\pi - \frac{1}{2}(\sigma^2 + \mu^2)

接下来求\int {q_\phi (z|x) \ln q_\phi (z|x)}dz的积分:

\int {q_\phi (z|x) \ln q_\phi (z|x)}dz

= \int {\frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(z-\mu)^2}{2\sigma^2}} \ln \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(z-\mu)^2}{2\sigma^2}}}dz

= -{\frac{1}{\sqrt{2\pi\sigma^2}} \int [\frac{1}{2} \ln 2\pi\sigma^2 e^{-\frac{(z-\mu)^2}{2\sigma^2}} + \frac{(z+\mu)^2}{2\sigma^2} e^{-\frac{(z-\mu)^2}{2\sigma^2}}}]dz

= -\frac{1}{2} \ln 2\pi - \ln\sigma - \frac{1}{2}

因此

L_1= \frac{1}{2}(1 + 2 \ln\sigma - \sigma^2 - \mu^2)

变分下界第二项的推导

变分下界第二项

L_2 = \int {q_\phi (z|x) \ln p_\theta (x|z)dz}

其中:

q_\phi(z|x = N(u(x,\phi),\sigma^2(x,\phi)))

p_\theta(x|z = N(u(z,\theta),\sigma^2(z,\theta)))

如果直接对L_2积分会很复杂。在机器学习领域,对于求解复杂积分问题,可以采用蒙特卡洛方法(详见附录4)。

L_2 = \int {q_\phi (z|x) \ln p_\theta (x|z)dz}

\approx \frac{1}{n}\sum_{i=1}^n \ln p_\theta (x|z^i), z^{(i)} \sim q_\phi (z|x)

也就是说我们可以从q_\phi (z|x)中采样足够多的样本并求取平均值来近似L_2。然而这是一个不可微的过程,因此参数不能够通过反向传播进行更新。

L_2参数变换

因为z \sim q_\phi(z|x) = N(u,\sigma^2),我们可以进行参数变换,上面的分布函数等价于:

z = u + \sigma\varepsilon, \varepsilon \sim N(0,1)

所以

L_2 \approx \frac{1}{n}\sum_{i=1}^n \ln p_\theta (x|z^i), z^{(i)} = u + \sigma\varepsilon, \varepsilon\sim N(0,1)

在MNIST数据集上的实验

作者给出了基于Keras的变分自编码器的实现,并在MNIST数据集上进行了测试,输出如下:

Figure_1.png

==================================================

附录:

1 条件概率与乘法定理

条件概率与乘法定理是理解变分推断的基础。

1.1 条件概率的概念

条件概率是指在事件B已经发生的条件下,事件A发生的概率,记作:P(A|B)。即在计算条件概率P(A|B)时,把样本空间缩小为B所包含的基本事件,有利事件为AB

1.2 条件概率的性质

定理1:设AB是两个随机事件,且P(B) > 0 ,则在事件B已经发生的条件下, 事件A发生的条件概率是

P(A|B)=\frac{P(A, B)}{P(B)}

 

tjgl.jpg

从上图可以看出,黄色区域表示事件AB同时发生的情况,它是符合我们要求的有利事件。而黄色区域加绿色区域表示事件B发生的情况,它是我们的样本空间。

1.3 乘法定理及其推广

定理2:若对任意两事件AB都有P(A) > 0P(B) > 0,则

P(A, B) = P(A)P(B|A) = P(B)P(A|B)

称此式为乘法公式,称此结论为乘法定理。

注:当P(A, B)不容易直接求得时,可考虑利用P(A)P(B|A)的乘积或P(B)P(A|B)的乘积间接求得。

定理3:设A_1, A_2, \dots A_n为任意n个事件,n \geqslant 2P(A_1 A_2 \dots A_{n-1}) > 0,则有

P(A_1 A_2 \dots A_n) = P(A_1)P(A_2|A_1) \dots P(A_n|A_1A_2 \dots A_{n-1})

定理3的证明:

因为A_1 \supset A_1A_2 \supset \dots \supset A_1A_2 \dots A_{n-1}

故当P(A_1 A_2 \dots A_{n-1}) > 0,则有

P(A_1) > P(A_2) > \dots > P(A_1 A_2 \dots A_{n-1}) > 0

根据条件概率的定理1,得

P(A_1)P(A_2|A_1) \dots P(A_n|A_1A_2 \dots A_{n-1})

= P(A_1) \times \frac{P(A_1,A_2)}{P(A_1)} \times \frac{P(A_1,A_2,A_3)}{P(A_1,A_2)} \times \dots \times \frac{P(A_1,A_2, \dots ,A_n)}{P(A_1,A_2, \dots ,A_{n-1})}

= P(A_1 A_2 \dots A_n)

1.4 独立随机事件

如果事件AB是完全独立的,也就是说事件B是否发生对事件A没有任何影响,也就是说:

P(A|B) = P(A)

这意味着根据乘法定理有

P(A, B) = P(B)P(A|B) = P(A)P(B)

推广到一般情况,如果事件A_1, A_2, \dots ,A_n相互独立,则有

P(A_1, A_2, \dots ,A_n) = P(A_1)P(A_2) \dots P(A_n)

2. 相对熵(KL散度)

相对熵(relative entropy)又称为KL散度(Kullback–Leibler divergence),它被用来度量两个随机变量的距离。相对熵的定义为:

D_{KL}(P\parallel Q) = \int^{+\infty}_{-\infty} p(x)\ln\frac{p(x)}{q(x)}dx

其中,p(x)q(x)分别表示分布PQ的概率密度函数。当PQ同分布时,相对熵取最小值0。

3. 分步求积分法则

\int udv = uv - \int vdu

4. 蒙特卡洛方法在积分中的应用

蒙特卡洛方法计算定积分时有一种方法叫平均值法,它的原理很简单。假设随机变量X服从[0,1]上的均匀分布,则Y=f(X)的数学期望为:

E(f(X))=\int^1_0 f(x)dx = J

所以估计J的值就是估计f(X)的数学期望值。可以用f(X)的观察值的均值取估计f(X)的数学期望。具体做法是:先用计算机产生n个服从[0,1]上均匀分布的随机数:x_i,i=1,2,\dots,n。对每一个x_i,计算f(x_i)。那么:

J \approx \frac{1}{n}\sum^n_1 f(x_i)

如果你很细心,你会发现这个方法目前只适用于积分区间[0,1]。那么,对于一般区间[a,b]上的定积分J' = \int^b_a g(x)dx呢?我们只需要将J'Jdx建立代数关系就可以了。

首先,做线性变换,令y=(x-a)/(b-a),此时x=(b-a)y + a。那么

J' = (b-a) \int^1_0 g[(b-a)y+a]dy = (b-a)J

泰坦尼克:机器学习应用

在机器学习挑战平台kaggle中,泰坦尼克灾难是比较经典的问题。因为这个挑战赛的数据集不是非常复杂,特别适合新手入门。接下来让我们一起看看如何解答泰坦尼克灾难问题。

问题描述

泰坦尼克灾难问题的训练集包含一系列乘客的属性值(性别、年龄、姓名、仓位等级等等)。同时,训练集还包含了这些乘客是否获救的信息。训练集的部分数据如下图所示:

Screen Shot 2017-09-29 at 3.02.04 PM.png

这个挑战赛的要求是利用训练集创建一个模型,来预测测试集中的乘客是否获救。

数据分析

要解决这个问题,我们首先要做的就是理解每一个训练集属性的含义。

  • survival:表示乘客是否获救,0表示没有获救,1表示获救;
  • pclass:表示仓位等级,1表示一等舱,2表示二等舱,3表示三等舱;
  • sex:性别;
  • age:年龄;
  • sibsp:一起登船的兄弟姐妹或配偶的数目;
  • parch:一起登船的父母或孩子的数目;
  • ticket:船票号码;
  • fare:个人收入;
  • cabin:仓位号码;
  • embarked:登船港口,C表示Cherbourg,Q表示Queenstown,S表示Southampton。

理解了每一个属性之后,我们就可以对数据进行一些简单的分析,看哪些属性对获救概率影响比较大。

性别与获救概率

Screen Shot 2017-09-29 at 3.30.03 PM

可以看到,获救人群中女性明显多于男性,可见性别是影响获救概率的重要因子。

年龄与获救概率

Screen Shot 2017-09-29 at 3.35.43 PM

可以看到,年龄越小获救的概率越大。因此,年龄也是一个重要的因子。

收入与获救概率

Screen Shot 2017-09-29 at 3.40.29 PM

可以看到,高收入人群的获救概率远大于低收入人群。因此,收入水平也是一个很重要的因子。

特征工程

对属性进行初步分析之后,我们就可以对这些属性进行一些处理,从而方便我们构建模型。

补全缺失的数据

训练集中的某些属性(如年龄)包含缺失的值。我们需要补全这些值。补全的方法很多,对数字类型的属性我们可以选取它的平均值。对字符型属性,我们可以引入一个新的字符串。

One-hot属性

某些属性只包含有限个值,如登船港口(embarked)只包含三个可能的值。对于这样的属性,为了使得我们的模型能够更好的学习它,我们可以对它进行one-hot操作。也就是新建三个属性:embarked_C,embarked_Q和embarked_S。如果embarked_C取值为1,则表示原来的embarked属性值是C,以此类推。然后,我们就可以删除原来的embarked属性。

创建新的属性

有时候我们可以通过组合多个属性,创建一个新的属性。也可以通过提取一个属性的某一部分来得到一个新的属性。例如,训练集中name属性包含了乘客的称谓(title)信息。这个信息可能会影响获救概率。于是,我们可以创建一个新的属性title,从而帮助模型识别乘客。

关于特征工程的具体实现,可以参考我的代码

提交答案

在训练完模型之后,我们可以利用它对测试集作出预测,从而得到我们的答案。在kaggle上上传答案后,系统会自动为我们计算准确率并给出排名,我的最好的准确率是0.78947。

Screen Shot 2017-09-29 at 4.12.30 PM

基于OpenCV的车道分割线提取

车道分割线是自动驾驶中最重要的指示物,可以有效帮助我们确定正确的方向盘偏转角度。这篇博客介绍了一个基于OpenCV的车道分割线提取方法。

色彩过滤

车道线一般是白色或者黄色。因此,在处理图像前,我们可以先剔除非白色或者黄色像素,从而滤除非车道线部分,减少噪音的干扰。

frame_0mask_0

上边的第一幅图片代表原始输入图像,第二幅图片代表经过色彩过滤之后的图像。可以看到,除了黄色和白色的像素,其他像素都被置成了黑色。

边缘检测

接下来我们将使用高斯平滑对图像进行进一步的降噪处理。然后我们还将使用一种称为Canny边缘检测的算法,提取所有对象的边缘,下图是示例输出结果:

can_0

调用高斯平滑和Canny边缘检测的代码如下所示:

Screen Shot 2017-08-13 at 5.09.42 PM

定义有效区域

车道线一般位于图像中间偏下的区域,我们称之为“有效区域”。我们将定义一个有效区域,并且只处理图片中位于有效区域内的像素,而将区域外的所有像素置为黑色。我们定义的有效区域是一个梯形,处理后的示例图片如下所示:

msk_0

可以看到,图像中只留下了车道线的边缘。

拟合车道线

在笛卡尔坐标下,直线方程具有如下形式:

y = mx + b

给定一组车道线的边缘像素点,我们希望在图片空间中找到一条直线,使其能够尽可能多的连接这些像素点。为了得到这样的直线,我们引入了m-b参数空间。它的横轴是直线的斜率m,纵轴是截距b。因此,笛卡尔空间中的一条直线就变成了m-b参数空间中的一个点。下图显示了通过边缘点的各种直线以及它们在参数空间中的表示。在笛卡尔空间中共点的直线将会是m-b参数空间中的曲线的交点。

Screen Shot 2017-08-13 at 6.06.45 PM.png

由于垂直于x轴的直线的斜率是没有定义的,我们需要利用极坐标来表示直线。极坐标下的直线被表示为:

\rho = x cos \theta + y sin \theta

\rho表示从原点到直线的距离,\theta表示从原点到线的角度,如下图所示:

Screen Shot 2017-08-13 at 6.17.43 PM

\rho-\theta参数空间中,我们要寻找的直线也是曲线的交点,如下图所示:

Screen Shot 2017-08-18 at 9.07.34 AM

通过Hough变换,我们就能够基于车道线的边缘像素点拟合出车道线的直线方程。

分割网络的Tensorflow实现

在上一篇博客《利用全卷积网络进行车道识别》中,我们介绍了全卷积网络,并利用它进行了车道识别。在这篇博客中,我们将介绍另外一种网络:分割网络(Segnet),并用它进行道路交通场景的语义分割。

网络架构

分割网络的架构如下图所示:

segnet

从图中可以看到,和全卷积网络类似,分割网络也分为两部分。前半部分称为编码器(Encoder),后半部分称为解码器(Decoder)。

编码器

分割网络的编码器由若干个类似的子结构堆叠而成。每个子结构都包含若干个卷积层,Batch Normalization层和ReLU激活层,并最后连接一个下采样层(max_pool_with_argmax)。值得注意的是,不同于普通的max_pool层,max_pool_with_argmax将同时输出最大值的相应坐标,在解码器中我们将利用这些坐标完成上采样操作。

整个编码器的输出被称为feature map,它标记了被识别出的feature及其在输入图像上的大致位置。

解码器

解码器的目标是将识别出的feature映射到每一个原始输入像素上,这样我们就可以对每个像素进行语义识别。分割网络的解码器是它的创新点所在。不同于全卷积网络利用反卷积(Deconvolution)操作进行上采样(Upsampling),分割网络直接进行max_pool_with_argmax的逆向操作,具体过程如下图所示:

Screen Shot 2017-08-05 at 4.20.42 PM

解码器首先创建一个全零的tensor,然后将输入tensor的值填充到对应的元素中,从而得到了一个上采样后的输出。在每个解码器的子结构中,我们首先进行上采样操作,然后连接若干个卷积层、Batch Normalization层和ReLU激活层。解码器的最终输出的将进行一次Softmax操作,从而得到对应像素属于某个类别的概率。

基于Tensorflow的实现

我们将利用分割网络对KITTI和Camvid数据集进行语义分割,这要求我们实现一个与输入无关的通用模型。因此我们采用了如下架构:

 

Screen Shot 2017-08-05 at 4.51.15 PM

针对不同的数据集合,由于数据的大小和格式不同,我们实现了不同的数据加载器(Data Loader)、训练器(Trainer)和评价器(Evaluator)。但是,它们将共享一个相同的分割网络模型。具体实现代码在这里

实验

我们利用分割网络分别对KITTI和Camvid数据集进行了语义分割。Camvid数据集采集了英国剑桥小镇的城市道路数据,并进行了像素级别的语义标注,共包含32个类别,如下图所示:

Screen Shot 2017-08-05 at 4.57.51 PM.png

分割网络对Camvid数据集的语义分割结果如下图所示:

segnet_camvid_2

而KITTI车道数据集则只包含两个分类:车道或非车道。分割网络对KITTI数据集的语义分割样例结果如下图所示:

frame_0frame_5frame_8frame_11frame_175

 

利用全卷积网络进行车道识别

写在前面的话

长期以来,自动驾驶一直是互联网豪门的专属游戏。这个领域内的中小型公司大多都被收购,因此相关技术一直被掌握在少数公司手中,无法被广泛普及,从而导致了这个领域的进入门槛相当之高。同时。网络上关于自动驾驶的文章少之又少。即便偶而发表,也大多停留在概念和抽象分析层面上,让人读后如隔靴搔痒。因此,我试图写一个以自动驾驶为主题的系列,深入浅出地分析相关技术,作为自己学习成果的记录,也与大家分享。

图像分割与车道识别

图像分割指的是将一副图片从语义上划分成多个部分。可以看到,下图(右侧)包含了许多语义信息:前景中的骑手和马,背景中的汽车,草地和树木等。我们希望通过某种方法,识别出图像中的每一个像素所属的语义元素,这个过程就是图像分割。下图(左侧)就是图像分割的结果,不同的颜色代表不同的语义元素。

Screen Shot 2017-03-19 at 11.17.30 AM

通过图像分割,我们可以清晰地得到语义元素在图像中的大小和位置,这些信息将作为后续图像处理的基础。

而本文要介绍的车道识别实际上就是一类特定的图像分割问题。我们的输入图像是车辆前方道路图像(由固定在前挡风玻璃上的摄像头所拍摄),输出则是一个包含两个颜色的图像,不同的颜色分别代表了车道和背景。如下图所示:

Screen Shot 2017-03-19 at 11.37.00 AM

车道识别的目标

车道识别归根结底是图像识别问题。但是,在传统的图像识别问题中,一张图像只有一个识别结果。比如,在我之前的博客利用卷积神经网络识别CIFAR-10中,每个图像都被归为某一个类别。因此这种识别是图像级别的。然而,在车道识别中,图像识别将是像素级别的。也就是说,我们需要针对图像中的每一个像素,给出它所属的类别。例如,如果输入图像是一个m \times n 的三通道的RGB图像,那么输出就是一个m \times n 的矩阵,矩阵的每个元素表示该像素所属的类别。如下图所示:

Screen Shot 2017-03-19 at 9.15.54 PM

为了解决这个问题,我们很自然地想到了是否可以利用卷积神经网络。答案是肯定的。但是,在应用卷积神经网络的具体方式上,我们却有多个选择。

基于卷积网络的方法

卷积网络(convolutional neural network,CNN)通常在卷积层之后会接上若干个全连接层, 将卷积层产生的特征图(feature map)映射成一个固定长度的特征向量。CNN适合于图像级的分类任务,因为它们最后会得到整个输入图像的一个概率向量,比如识别CIFAR-10图像的CNN会输出一个10维的向量,用于表示输入图像属于每一类的概率(softmax归一化)。

如果我们利用CNN来判断图像中的某个像素是否属于车道时,我们通常需要分析它临近的若干像素,而窗口的大小就决定了哪些临近像素会被考虑。我们将窗口截取的图像输入卷积神经网络,网络的输出是位于窗口中心位置的像素的分类(属于或不属于车道)。当我们滑动这个窗口时,就能够给出图像每个像素的分类,如下图所示。

Screen Shot 2017-03-19 at 9.27.36 PM.png

滑动窗口方法的缺点很明显:

  • 计算量巨大。由于窗口之间存在重叠,因此存在重复计算的问题。导致算法的整体性能较低,无法做到实时分析。
  • 存储开销巨大。例如对每个像素使用的图像块的大小为15 \times 15,然后不断滑动窗口,每次滑动的窗口给CNN进行判别分类,因此则所需的存储空间根据滑动窗口的次数和大小急剧上升。
  • 窗口大小不确定。窗口大小的选取取决于经验估计,而且窗口大小的选择对最终分类准确度有很大影响。
  • 窗口大小限制了感知区域的大小。通常窗口的大小比整幅图像的大小小很多,因此只能提取一些局部的特征,从而导致分类的性能受到限制。

全卷积网络的组成

为了克服卷积网络在图像分割问题中的缺点,我们在卷积网络的基础上发展出了全卷积网络(fully convolutional network,FCN)。FCN可以对图像进行像素级的分类,从而实现了语义级别的图像分割(semantic segmentation)。全卷积网络一般由卷积层(convolutional layer)、池化层(pooling layer)和反卷积(deconvolutional layer)层组成。

卷积层

每个卷积层的输入都是一个三维数组h \times w \times d,其中hwd分别是三维数组的是高度、宽度和深度。 由于输入层的数据是图像,因此hw分别代表图像的高度和宽度,d代表颜色通道。每个卷积层都包含一组可被学习的卷积核。每个卷积核的宽度和高度很小,但是会延伸到输入的整个深度。卷积核中的每一个参数都相当于传统神经网络中的权值参数,但是它只与对应的局部像素相连接,将卷积核的各个参数与对应的局部像素值相乘之和,就可以得到卷积层的输出。

池化层

通过卷积层获得了图像的特征之后,理论上我们可以直接使用这些特征训练分类器(如softmax),但是这样做将面临巨大的计算量的挑战,而且容易产生过拟合(overfitting)的现象。为了进一步降低网络训练参数及模型的过拟合程度,我们在每个卷积层之后连接一个下池化层。池化的方式通常有以下两种:

  • 最大化池化(max pooling):选择池化窗口中的最大值作为采样值;
  • 平均值池化(mean pooling):将池化窗口中的所有值相加取平均,以平均值作为采样值。

Screen Shot 2017-04-30 at 3.13.42 PM.png

反卷积层

全卷积网络通常包含若干个反卷积层,它能够将卷积层产生的特征图映射回原始像素。在介绍反卷积之前,我们先来看看卷积运算和矩阵运算之间的关系。

考虑如下一个简单的卷积层运算,其中输入矩阵的大小为i = 4,卷积核大小为k = 3,步长s = 1,填充p = 0,输出矩阵的大小为o = 2。这个卷积操作的示意图如下所示。

8c2b2f6fjw1f99i1rv4jog206s0770t7

对于上述卷积运算,我们可以把上图所示的3 \times 3的卷积核展开成一个如下所示的4 \times 16的稀疏矩阵C,其中非0元素w_{ij}表示卷积核的第i行、第j列的元素。

\begin{pmatrix} \begin{matrix} w_{0,0} & w_{0,1} & w_{0,2} & 0 \\ 0 & w_{0,0} & w_{0,1} & w_{0,2} \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \end{matrix} & \begin{matrix} w_{1,0} & w_{1,1} & w_{1,2} & 0 \\ 0 & w_{1,0} & w_{1,1} & w_{1,2} \\ w_{0,0} & w_{0,1} & w_{0,2} & 0 \\ 0 & w_{0,0} & w_{0,1} & w_{0,2} \end{matrix} & \begin{matrix} w_{2,0} & w_{2,1} & w_{2,2} & 0 \\ 0 & w_{2,0} & w_{2,1} & w_{2,2} \\ w_{1,0} & w_{1,1} & w_{1,2} & 0 \\ 0 & w_{1,0} & w_{1,1} &w_{1,2} \end{matrix} & \begin{matrix} 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ w_{2,0} & w_{2,1} &w_{2,2} & 0 \\ 0 & w_{2,0} & w_{2,1} &w_{2,2} \end{matrix} \end{pmatrix}

我们再把4 \times 4的输入特征展成16 \times 1的矩阵X

\begin{pmatrix} x_{0,0} \\ x_{0,1} \\ x_{0,2} \\ x_{0,3} \\ x_{1,0} \\ x_{1,1} \\  x_{1,2} \\ x_{1,3} \\ x_{2,0} \\ x_{2,1} \\x_{2,2} \\ x_{2,3} \\ x_{3,0} \\ x_{3,1} \\ x_{3,2} \\ x_{3,3} \end{pmatrix}

那么输出矩阵Y=CX则是一个4 \times 1的输出特征矩阵,把它重新排列成2 \times 2的输出特征就得到最终的结果,通过上述的分析,我们可以看到卷积操作可以表示为和矩阵C相乘,那么反卷积操作就是和矩阵C的转置C^T相乘。因此,反卷积操作也被称为转置卷积操作(transposed convolutional layer)。

下图所示的是参数为i'=2, k'=3, s'=1, p'=2的反卷积操作,其对应的卷积操作参数为
i=4, k=3, s=1, p=0

8c2b2f6fjw1f99j2k89hlg209k0aq41i

我们可以发现对应的卷积和反卷积操作,k=k', s=s'。但是反卷积操作却多了p'=2。通过对比我们可以发现卷积层中左上角的输入只对左上角的输出有贡献,所以反卷积层会出现p'=k-p-1=2。可以发现,反卷积层的输入和输出在k=k',s = s'= 1的情况下的关系为:

o'=i'-k'+2p'+1=i'+(k-1)-2p

基于全卷积网络的方法

与经典的卷积网络在卷积层之后使用全连接层得到固定长度的特征向量进行分类不同,全卷积网络可以接受任意尺寸的输入图像,它利用反卷积层对最后一个卷积层的feature map进行上采样,使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生了一个分类预测,同时保留了原始输入图像中的空间信息,从而实现了逐像素的分类。一个典型的全卷积网络的架构如下图所示。

Screen Shot 2017-04-30 at 4.18.32 PM.png

特种属性的融合

在传统的全卷积网络的基础上,我们还采用了多层级融合的技术,将不同层级上的特征属性进行融合,从而提高了语义分割输出的精度,具体实现方法如下图所示。

Screen Shot 2017-04-30 at 7.04.32 PM.png

可以看到,通过四个卷积层之后,我们分别得到了输入图像的四个不同解析度级别上的特征图(feature map)。接下来,我们把不同层级上的特征图通过反卷积操作放大到相同的大小,然后把它们相加,从而实现了不同层级上的特征属性的融合。

基于VGG16的微调

通常来说,用于图像语义分割的全卷积网络层数较多,包含的参数数量十分庞大。全新训练一个全卷积网络用于道路分割非常耗时。而我们的全卷积网络的前半部分(卷积层和池化层)和用于图像分类的VGG16网络完全一致。因此,我们可以在预先训练好的VGG16网络的基础上进行微调。

实验

KITTI数据集

我们在KITTIRoad/Lane Detection Evaluation数据集上测试这个网络。KITTI数据集包括:立体声、光流、视觉测距、3D物体检测和3D跟踪等多个基准数据集,包括了卡尔斯鲁厄市中心、周边农村地区和高速公路上收集的真实数据。每张图片最多可显示15辆汽车和30名行人。除了以原始格式提供所有数据,KITTI还提供了每个任务的基准。对于我们的每个基准,KITTI还提供了评估指标和评估网站。

网络架构和实现代码

网络的实现代码可以在这里找到。我们的网络结构如下表所示。

Name

Kernel Size Stride Padding Num Output

data

3

conv1_1

3 1 1

64

conv1_2

3 1 1

64

pool1

2 2 0

64

conv2_1

3 1 1

128

conv2_2

3 1 1

128

pool2

2 2 0 128
conv3_1

3

1

1 256

conv3_2

3 1 1

256

conv3_3 3 1 1

256

pool3

2 2 0

256

conv4_1

3 1 1 512
conv4_2 3 1 1

512

conv4_3

3 1 1 512

pool4

2 2 0 512
conv5_1 3 1 1

512

conv5_2

3 1 1 512
conv5_3 3 1 1

512

pool5

2 2 0

512

fc6

4096

fc7

4096

fc8

2

upscore2

2

upscore4

2

upscore32

2

其中,conv1-5表示卷积层,卷积层不会改变输入的大小,只会增加特征输出通道。pool1-5表示池化层,池化层的输出大小是输入的一半,而特征输出通道保持不变。fc6-7表示全连接层,用于综合卷积层和池化层学习到的特征。upscore2、upscore4和upscore32分别表示2倍、4倍和32倍上采样,用于将学习到的特征映射回原始像素。

实验结果

下图显示了该网络的分类精度约为99.85%,召回率约为85%,F1约为0.9。

我们从输出结果中选取了9幅图像。绿色的像素表示被正确分类的道路。红色的像素表示false positive的分类(也就是将不属于车道的像素误分类为车道)。蓝色的像素表示false negative的分类(也就是将属于车道的像素误分类为非车道)。可以看到,对于绝大多数像素,该网络都可以正确分类。

decision_97decision_96decision_95decision_94decision_93decision_92decision_91decision_90decision_89

深度神经网络中的梯度丢失和梯度爆炸

最近在尝试构建一个深度网络进行图像分割。在调试网络的过程中,总是出现把整幅图像归为一类的问题,简单地说就是网络不收敛,最终导致无法得到任何有意义的结果。在尝试了很多方法之后,终于发现问题可能出在两方面:一是学习速率过大,二是产生了梯度爆炸(exploding gradient)。学习速率过大很好解决,但是梯度爆炸还是第一次遇见,因此进行了一些研究。

神经网络的反向传播

要理解梯度丢失(vanishing gradient)和梯度爆炸,首先需要理解神经网络的反向传播算法。

一般来说,训练一个神经网络需要很多个迭代。在每个迭代中,都包含两个步骤。

  • 前馈(feed forward):它指的是从神经网络的输入开始,根据每一层的权重和偏置,逐层计算输出,直到得到神经网络的最终输出。这个输出值可以是对图片的分类,也可以是对数据走势的预测等等。
  • 反向传播(back propagation):它指的是将神经网络的输出值和标准值进行比较,从而得到误差值。然后计算网络的每一层对这个误差值的“贡献”,并对每一层的权重和偏置进行调整的过程。

一个标准的神经网络训练迭代如下图所示:

Screen Shot 2017-03-11 at 9.00.21 AM

链式求导法则

那么,我们如何根据误差值更新每一个权重和偏置呢?这就涉及到我们如何计算误差值对每一个权重和偏置的梯度(gradient)。有了这个梯度,我们就可以利用梯度下降法来更新权重和偏置了。在计算误差对每个权重和偏置的梯度的过程中,我们利用了链式求导法则。下面让我们用一个具体的例子来说明链式求导在反向传播中是如何工作的。

让我们考虑下面这个简单的深度神经网络,它的每一层都只包含一个神经元,一共有三个隐藏层:

Screen Shot 2017-03-07 at 9.21.20 PM

这里的w_1, w_2, \dots表示权重,b_1, b_2, \dots表示偏置,C表示网络的输出,每个神经元的激活函数(activation function)记为\deltaa_j表示第j个神经元的输出,其中a_0等于网络的输入,a_j = \delta(z_j)z_j = w_j * a_{j-1} + b_j

在反向传播过程中,我们需要计算C对所有权重w_j和偏置b_j的偏导数。下面我们以\partial C/\partial b_1为例,说明反向传播是如何工作的。

根据链式求导法则\partial C/\partial b_1可以被写为:

\frac{\partial C}{\partial b_1} =\frac{\partial C}{\partial a_4}\times\frac{\partial a_4}{\partial b_1}

由于a_4 = \delta(z_4) = \delta(w_4 * a_3 + b_4),因此

\frac{\partial C}{\partial b_1} =\frac{\partial C}{\partial a_4}\times w_4\times\delta'(z_4)\times\frac{\partial a_3}{\partial b_1}

反复利用上述求导方法,可以最终得到:

\frac{\partial C}{\partial b_1} =\frac{\partial C}{\partial a_4}\times w_4\times\delta'(z_4)\times w_3\times\delta'(z_3)\times w_2\times\delta'(z_2)

这个等式也反映了反向传播的工作模式:它从输出层开始,逐层计算偏导数,直到输入层为止。然后,利用这些计算出来的偏导数,更新对应的权重和偏置,从而达到反向传播的目的。

梯度不稳定

梯度丢失和梯度爆炸统称为梯度不稳定。它们产生的原因是类似的。让我们首先看看梯度丢失是如何产生的。

梯度丢失

在实际应用中,最常用到的激活函数是Sigmoid函数,它的图像如下:

Screen Shot 2017-03-11 at 10.52.40 AM.png

Sigmoid函数的输出值在0到1之间,它的导函数最大值出现在x = 0的时候,最大值为0.25。它的导函数图像画如下所示:

Screen Shot 2017-03-11 at 10.51.24 AM.png

由于我们初始化权重值的时候一般从标准正态分布中采样,所以w_j的绝对值通常小于1,因此我们可以得到:

|w_j\times\delta'(z_j)| < 1

在深度网络中,为了计算初始层的梯度,我们会累乘多个w_j\times\delta'(z_j)项 ,最终计算结果将会呈指数级变小,这也就是梯度丢失产生的原因。

梯度爆炸

梯度爆炸产生的原因和梯度丢失正好相反。当我们选取的权重值较大时,|w_j\times\delta'(z_j)|将大于1。当累乘这些项的时候,计算结果将呈指数级增长。

解决方法

梯度不稳定会使得网络不收敛,最终导致我们的训练无法得到任何有意义的结果。因此,我们必须找到相应的解决方法。

梯度剪裁

梯度剪裁是用来解决梯度爆炸问题的。具体描述如下:

  • 选取一个梯度剪裁的阈值clip_norm(一般选择1)
  • 在计算完每个权重的梯度之后,我们并不像通常那样直接使用这些梯度进行权重更新,而是先求所有权重梯度的平方和global_norm
  • 最后把每个梯度乘以缩放因子clip_norm / max(global_norm, clip_norm)。

这样就保证了在一次迭代更新中,所有权重的梯度的平方和在一个设定范围以内。在实现过程中我们可以使用Tensorflow提供的库函数tf.clip_by_global_norm

关于梯度丢失也有相应的解决方法,这里就不再赘述了。

浅析生成对抗网络

我的上一篇博客《变分自动编码器》介绍了一种数据生成模型,它能够学习训练数据,并生成与训练数据类似的数据。在这篇博客中,我将介绍另一种数据生成模型:生成对抗网络(Generative Adversarial Networks,GAN)。

GAN是由Ian Goodfellow在2014年提出的,主要思想发表于这篇里程碑式的论文:Generative Adversarial Networks。2016年,GAN热潮席卷AI领域顶级会议,Yann LeCun更是评价GAN是“20年来机器学习领域最酷的想法”。究其原因,主要是因为GAN为无监督学习提供了一个崭新的方法,而无监督学习又是机器学习的发展方向。因此,GAN备受学术界和工业界的关注。

Ian的论文包含许多形式化的论证,初学者难以完全理解。而本文希望用最通俗易懂的语言,给大家带来对GAN的直观理解,为进一步深入学习GAN打下基础。

GAN的基本框架

任何生成模型的训练目标都是要使得生成出来的数据尽量接近真实数据。但是在实际应用中,我们完全无法知道真实数据的分布。我们所能够得到的只是真实数据的一些样本。传统的生成模型,一般都采用数据的似然性来作为优化的目标,但GAN创新性地引入了判别模型,从而提出了一种全新的优化目标。这个目标就是要寻找生成模型和判别模型之间的一个平衡。

GAN所建立的一个学习框架,实际上就是生成模型和判别模型之间的一个模仿游戏。我们可以把生成模型看作一个伪装者,而把判别模型看成一个警察。生成模型通过不断地学习来提高自己的伪装能力,从而使得生成出来的数据能够更好地欺骗判别模型。而判别模型则通过不断的训练来提高自己判别的能力,能够更准确地判断出数据的来源。GAN的架构如下图所示:

screen-shot-2017-02-12-at-8-02-56-pm

生成模型以隐随机变量作为输入,其输出是对真实数据分布的一个估计。生成数据和真实数据的采样都由判别模型进行甄别,并给出真假性的判断和当前的损失。利用反向传播,GAN对生成模型和判别模型进行交替优化。

GAN的优化目标

GAN的两个模型分别对应两个优化目标。

生成模型的优化目标

生成模型的优化目标是最小化下面这个表达式的值:

-\nabla_{\theta_{g}} \frac{1}{m}\sum_{i=1}^m log(D(G(z^{(i)})))

其中,z^{(i)} 是从隐随机变量分布p_g(z) 中抽样的m个样本\{z^{(1)}, z^{(2)}, \ldots, z^{(m)}\},它是生成模型的输入。对机器学习不是很了解的读者可能无法理解这个优化目标是如何形成的,其实这其中的原理很简单。

G(z^{(i)})是指生成模型以隐随机变量为输入,生成一个对真实数据分布的估计。理论上来说,通过不断地学习,生成的数据样本应该越来越接近真实的数据分布。然后,判别模型以生成模型的输出作为输入,并输出一个介于0和1之间的数字。这个输出值越接近0,表示判别模型越认为输入数据是生成的,而不是真实数据。相反,输出值越接近1,表示判别模型越认为输入数据是真实数据的抽样而不是生成的。因此,从生成模型的角度来讲,它希望判别模型的输出值越接近1越好。这样就意味着自己生成出来的数据越来越接近真实数据分布。由于0和1之间的差距很小,为了放大这种差异,加速网络训练的进程,我们对判别模型的输出值取对数。对数函数-log(x)的图像如下图所示:

screen-shot-2017-02-18-at-12-27-12-pm

可以看到,x越接近1,-log(x)的函数值越小。相反,x越接近0,-log(x)的函数值快速增长,目标函数的这种特性可以给不恰当的网络参数巨大的惩罚,也就保证了生成模型的输出将迅速逼近真实数据分布。

判别模型的优化目标

理解了生成模型的优化目标之后,我们就可以很容易地理解判别模型的优化目标了。判别模型的优化目标是最小化下面这个表达式的值:

-\nabla_{\theta_{d}} \frac{1}{m}\sum_{i=1}^m log(D(x^{(i)}))+log(1-D(G(z^{(i)})))

其中,第一项-log(D(x^{(i)}))表示判别模型希望对于真实数据的输出值尽可能接近1,也就是说判别模型可以正确识别出真实数据。第二项-log(1-D(G(z^{(i)})))表示判别模型希望对于生成数据的输出值尽可能接近0,也就是说判别模型可以正确识别出生成的数据。

一维生成对抗网络的实现

为了具体阐释生成对抗网络的细节,接下来我们将实现一个非常简单的生成对抗网络,它能够对一维正态分布进行模拟。

数据生成

首先,我们需要编写两个数据生成器,分别用于生成真实数据分布和隐变量。实现代码如下:

screen-shot-2017-02-18-at-2-28-41-pm

DataDistribution从一个正态分布中抽样,这些数据将作为真实数据。GenerationDistribution则随机产生一个数据分布作为隐变量。

模型构建

在构建生成网络和判别网络的过程中,我使用了Keras库,它的优点是API简洁清晰,使得代码的可读性很高。因为一维正态分布比较简单,在生成网络中我只加入了一个隐藏层。而判别网络则加入了两个隐藏层,如下图所示:

具体实现代码如下:

screen-shot-2017-02-18-at-3-11-40-pm

模型的训练

接下来就是交替训练生成模型和判别模型,并使用反向传播算法更新两个网络的参数,使得它们朝着各自的优化目标前进。

实验结果

我们选择批大小为256,生成网络和判别网络隐藏层节点数目都是16,一共迭代15000次。下图分别展示了两个网络的优化目标函数的输出值变化:

Screen Shot 2017-02-18 at 3.20.36 PM.png

可以清晰地看到,当一个网络的目标函数输出值减小时,另一个网络的目标函数输出值就增大。也就是说,一个网络的能力在增强的时候,另一个网络的能力就在减弱。这表明两个网络在互相“对抗”,并最终达到一个平衡。

每10个迭代完成后,我会把生成网络的输出和真实数据分布绘制出来,由此生成出了如下图所示的优化进程:

gan

其中,红色的线条代表真实数据分布,它是一个均值为4,标准差为0.5的正态分布。绿色的线条代表生成网络的输出。可以看到,随着迭代次数的增加,生成网络的输出在逐步逼近真实数据分布。

本文涉及到的代码在这里

变分自动编码器

变分自动编码器(Variational Auto-Encoder)被广泛应用于机器学习中复杂模型的推断,是机器学习中的一个十分重要的工具。利用变分自动编码器我们可以在学习训练图片之后,自动产生相似的图片。

神经网络与函数模拟

神经网络的一个显著的事实是它可以模拟任意函数。 假如,某个人给你一个十分奇特的函数,f(x)

1

不管这个函数是什么样的,总会有一个神经网络能够对任何可能的输入x,确保网络的输出是f(x)。例如:

2

即使这个函数f = f(x_1, x_2,\ldots,x_m)具有多个输入和输出,这个结论依然是成立的。例如,下图所示的这个网络可以计算一个具有m = 3个输入和n = 2个输出的函数:

3

研究表明,神经网络具有一种普遍性。不论我们想要计算什么样函数,我们都确信存在一个神经网络可以模拟它。

解码网络

为了直观地理解变分自动编码器,让我们从一个简单的例子开始。假设我们有许多猫的图片,而且我们希望通过某种函数来描述这些图片。也就是说,对于给定的输入,这个函数可以输出对应的某张猫的图片。根据上一节的介绍,对于任意函数我们都可以找到一个神经网络来模拟它。于是,我们确信可以找到一个神经网络来近似模拟这个函数。这样一来,我们的神经网络就具备了生成这些图片的能力。我们也可以认为这些图片的信息被保存在了神经网络中。

假设我们有一个由几个反卷积层组成的网络。我们设定这个网络的输入始终是一个向量(vector)。通过这个反卷积网络,我们可以得到一个输出图像。然后,我们可以训练这个网络以缩小输出图像和目标图像之间的像素均方误差。于是,这些图像的信息就被存储在了这个网络的参数中。

Screen Shot 2017-01-22 at 3.20.52 PM.png

我们把这些输入向量称为隐变量,它们可以被认为是这些猫的图片的编码。例如,向量[3.3, 4.5, 2.1, 9.8]可能表示的是一张波斯猫的图片,而[3.4, 2.1, 6.7, 4.2]则可能表示的是一张加菲猫的图片。

编码网络

随机地选择隐变量显然是一个坏主意,因为这些隐变量输入解码网络后可能最终生成出一些根本不存在的图片。可行的解决方案是添加一个编码网络来计算训练图片的隐变量。这个编码网络包含若干个卷积层,它使用训练图像作为输入,输出就是对应图片的隐变量。于是,一个标准的自动编码器的总体架构如下图所示:

5.jpg

变分约束

我们可以使用尽可能多的图像来训练这个网络。如果我们保存图像的隐变量,我们就可以通过解码网络来重建它。这就是标准自动编码器。

然而,我们试图建立的是一个生成模型,而不仅仅是一个能够“记住”图像的数据结构。我们无法通过标准自动编码器生成任意图片,因为除了通过编码训练图片得到隐变量,我们并不知道如何创建隐变量。

为了解决这个问题,我们可以在编码网络上添加一个约束,使得它生成的隐变量大致遵循标准正态分布。正是这种约束将变分自动编码器与标准自动编码器分开。

有了这个约束,生成新图像就很容易了。我们只需要从标准正态分布中采样隐变量并将其输入到解码网络就可以生成图片。一个典型的变分自动编码器的架构如下图所示:

screen-shot-2017-01-24-at-7-38-45-am

从图中可以看到,变分自动编码器既可以利用编码解码网络存储图片,也可以通过采样标准正态分布来生成图片。

训练网络

在实践中,我们通过两个独立的损失项来优化网络,这两个损失项分别是生成损失和KL散度。其中,生成损失指的是生成图片和目标图片之间的像素值均方差,它描述的是网络重建图片的精度。KL散度描述的则是隐变量和标准正态分布之间的匹配程度。示例代码如下:

generation_loss = mean(square(generated_image - real_image))
latent_loss = KL-Divergence(latent_variable, unit_gaussian)
loss = generation_loss + latent_loss

为了优化KL散度,我们需要应用一个技巧:让编码网络生成均值向量和标准差向量,而不是直接生成隐变量,如下图所示:

6.jpg

KL散度可以计算如下:

# z_mean and z_stddev are two vectors generated by encoder network
latent_loss = 0.5 * tf.reduce_sum(tf.square(z_mean) + tf.square(z_stddev) - tf.log(tf.square(z_stddev)) - 1,1)

而隐变量则可以计算如下:

samples = tf.random_normal([batchsize,n_z],0,1,dtype=tf.float32)
sampled_z = z_mean + (z_stddev * samples)

实验

现在,我们测试一下这个变分自动编码器的效果如何。我们使用MNIST数据集进行测试,其中隐变量的维度是128。

328个迭代后生成图片如左图所示(右图是原图):

1355个迭代后生成图片如左图所示(右图是原图):

2849个迭代后生成图片如左图所示(右图是原图):

5000个迭代后生成图片如左图所示(右图是原图):

可见,生成出来的图片很好地保留了原始图片的信息。

实现代码在这里

人类是如何通过视差判断物体距离的

人类通过双眼,可以很轻易的对周围的物体进行三维建模,从而判断物体的打小和距离(也就是“景深”)。虽然我们每天都在使用这些技能,但却不是每个人都能够说明其中的原理。那么今天我就通过这篇小文章,为大家梳理一下其中的原由。

人类的两只眼睛就类似两个光学镜头。如下图所示,这个镜头的镜片就是晶状体,感光板就是视网膜,而视网膜到晶状体之间的距离就是镜头的焦距。

200821104551455

如果我们只有一只眼睛,是无法进行三维建模的。因为,即使是距离不同的物体,透过一只眼睛成像后,可能得到的实际上是同一个像素点,如下图中的P点和Q点。

 

screen-shot-2017-01-06-at-11-14-27-pm

但是,幸运的是人类进化出了两只眼睛,情况就大不一样了。我们可以通过两只眼睛产生的视差来得到物体的距离。

如下图所示,假设某个物体上的一点P在左右两只眼睛上的成像点分别是pp^{\prime}。同时,成像点距左视界的距离分别是X_RX_T。左右眼睛的焦点分别是O_RO_T,焦距都是fZ表示物体到眼睛的距离,B表示两只眼睛的焦点之间的距离。那么,接下来我们就可以计算物体的距离Z了。

screen-shot-2017-01-06-at-11-17-42-pm

我们将直线PQ_T向左平移直到O_RO_T重合,如下图所示。

Screen Shot 2017-01-07 at 10.39.47 AM.png

很容易根据等比定律可以得到:

\frac{X_R - X_T}{B} = \frac{f}{Z}

其中,X_R - X_T是同一物体在左右眼成像时的像差,我们把它记为d,那么物体的距离(或者景深)就是:

Z = \frac{fB}{d}

另外一个实际问题是如何定位同一个点在左右眼中的不同成像点,这关系到X_RX_T是否能被精确计算。实际中我们是如何解决这个问题的呢?

实际上,仅用一个像素是很难正确找到它的对应像素的,多数情况下还需要借助周边的像素信息。一种比较简单且常用的方法是使用一个滑动窗口去寻找像素差异。因为通过一个滑动窗口,可以获得更多的纹理和差异信息。如下图所示,每个红色的方块代表一个滑动窗口。由于两只眼睛基本是平行的,因此我们只需要沿着X轴进行搜索。dmax表示可能的最大像差。

Screen Shot 2017-01-07 at 11.29.13 AM.png

然后最简单的方法就是选择差异最小的窗口所覆盖的像素点作为对应像素点。

Screen Shot 2017-01-07 at 11.41.27 AM.png

在实际中,我们需要根据具体情况调节滑动窗口的大小以达到最好的效果。

通过计算得出的像素点的差异图一般叫Disparity Map,表示的是两张成像图上相同点的位移差异,

利用卷积神经网络识别CIFAR-10

CIFAR-10

CIFAR-10包含60000张32×32像素的彩色图片,这些图片分属于10个不同的类别,其中每个类别包含10000张图片。整个数据集被划分成训练集和测试集,其中训练集包含50000张图片,测试集包含10000张图片。下图展示了部分CIFAR-10图片及其所属类别:

screen-shot-2016-12-30-at-2-28-09-pm

CIFAR-10训练集被分别存放在五个单独的文件中,测试集则存放在一个文件中。每个文件包含10000张图片及其分类信息,并且每个文件的格式都是一样的。可以从这里下载CIFAR-10数据集的Python版本,通过cPickle加载文件后我们可以得到一个Python dictionary对象。它包含两个元素:

  • data:一个10000×3072的numpy数组,它的每一行表示一个32×32像素的彩色图片。其中,前1024个元素表示红色通道,接下来1024个元素表示绿色通道,最后1024个元素表示蓝色通道。
  • label:一个包含10000个数字的列表,其中每个数字都在0到9之间。第i个数字表示data中第i张图片所属的分类。

卷积神经网络

卷积神经网络是对常规神经网络的重大改进,它的出现极大地减小了训练神经网络的计算量,降低了过度拟合的概率。大名鼎鼎的Alpha Go围棋程序就使用了卷积神经网络。可以说,卷积神经网络的出现使得大规模应用神经网络变得可行,极大地推动了人工智能和深度学习的发展。

常规神经网络接收一个向量作为输入,并通过一系列隐藏层进行变换。每个隐藏层由一组神经元组成,其中每个神经元完全连接到前一层中的所有神经元,同一层中的神经元完全独立运行。最后一个完全连接层被称为“输出层”。

screen-shot-2016-12-30-at-3-08-05-pm

由于常规神经网络总是保持全连接,因此一旦输入向量增大,用于网络训练的计算量将呈指数级增长。在CIFAR-10中,图像仅具有尺寸32x32x3(宽32,高32,3个颜色通道),因此常规神经网络的第一隐藏层中的单个完全连接的神经元将具有32x32x3=3072个权重。这个数量仍然是可控的,但是这个完全连接的网络结构无法扩展到更大的图像。同时,过多的连接不仅是不必要的,而且会很快导致过度拟合。

卷积神经网络注意到了输入是图像这一事实,通过提取相邻像素包含的特征信息,极大减小了网络的复杂度。不同于常规神经网络,卷积神经网络在宽度、高度和深度3个维度上排列神经元(这里的深度指的是激活向量的第三维,而不是指整个神经网络的深度)。

screen-shot-2016-12-30-at-3-12-27-pm

如上图所示,红色立方体代表输入图像。因此,这里的宽度和高度就是图片的尺寸,而深度指的是图片的3个颜色通道。我们将很快看到,卷积神经网络中的神经元仅连接到前置层的小区域,而不是以全连接的方式连接到之前的所有神经元。

接下来,我们将介绍卷积神经网络的各个层,它们共同构成了一个完整的卷积神经网络架构。为方便起见,我们假设网络的输入是32×32像素的CIFAR-10图片。

输入层

输入层将保持图像的原始像素值,在这种情况下是宽度32,高度32和具有3个颜色通道RGB的图像。

卷积层

卷积层是卷积神经网络的核心,它用于计算连接到输入图像的局部区域的神经元的输出。

过滤器

卷积层的参数包括一组可被学习的过滤器。每个过滤器的宽度和高度很小,但是会延伸到输入的整个深度。例如,一个典型的过滤器可以具有5x5x3的大小(即,5个像素的宽度和高度,以及3的深度,因为输入图像有3个颜色通道)。在正向传播时,我们在输入的宽度和高度上滑动过滤器,并计算过滤器和对应位置的输入的点积。由此我们将产生一个二维激活图(如下图所示),它给出了该过滤器在每个空间位置的响应。

cc

卷积神经网络将通过反向传播来学习过滤器,使得这些过滤器在看到某些类型的视觉特征时可用被激活。同时,我们将在每个卷积层中使用一系列过滤器(例如12个),它们中的每一个过滤器都将产生单独的2维激活图。我们将沿着深度维度堆叠这些激活图,并把它作为该卷积层的输出。

局部连接

当处理高维输入(如图像)时,将神经元连接到上一层中的所有神经元是不切实际的。相反,我们可以将每个神经元连接到输入的局部区域。这种连接的空间范围被称为神经元的接受场(也就是过滤器的大小)。连接在空间(沿着宽度和高度)上是局部的,但总是贯穿输入的整个深度。

Screen Shot 2016-12-30 at 4.37.25 PM.png

假设输入的大小为[32x32x3],如果接受场(或过滤器的大小)为5×5,则卷积层中的每个神经元将与输入的[5x5x3]的区域连接,总共包含5x5x3=75个权重(和1个偏置参数)。注意,沿深度轴的连通性必须为3,因为这是输入的深度为3。

空间布局

我们已经介绍了卷积层中每个神经元到输入的连接性,接下来我们将讨论输出中包含多少个神经元以及它们是如何排列的。卷积层的输出大小由三个超参数控制:深度,步幅和零填充。

  • 深度:它对应于我们使用的过滤器的数量,每个过滤器寻找输入中不同的特征。例如,第一卷积层采用原始图像作为输入,过滤器可用沿着深度维度寻找各种特定边缘或颜色块。
  • 步幅:当步幅为1时,过滤器每次移动1个像素。当步幅是2时,过滤器每次移动2个像素。步幅越大,输出的空间大小就越小。
  • 零填充:零填充指的是在输入的周围填充零。它允许我们控制输出的空间大小(我们可用使用它来确保输入和输出有相同的宽度和高度)。

如果输入的空间大小为W,转换层神经元的接受场大小为F,步幅为S,以及在边框上使用的零填充为P,那么我们就可以利用公式(W-F + 2P) / S + 1来计算卷积输出的空间大小。例如,对于7×7的输入和具有步幅1和0的零填充的3×3过滤器,我们将得到5×5的输出。如果使用2的步幅,我们将得到一个3×3的输出。

激活层

激活层将对每个元素应用激活函数。激活函数有很多,一个最常用的激活函数是Relu,它的表达式是max(0, x),表示如果输入大于零则激活,否则不激活。激活层不会改变卷集输出的大小。

池化层

池化层将沿着空间维度(宽度,高度)执行下采样操作,从而缩小卷积输出的空间大小。

全连接层

全连接层将计算类别分数,得到大小为[1x1x10]的输出,其中10个数字对应于属于每一个类别的概率。

网络的TensorFlow实现

Google最近开源的机器学习框架TensorFlow可用帮助我们快速地实现一个卷积神经网络来实现对CIFAR-10的识别。接下来我们将一步一步详细说明网络的具体实现。

网络架构

我们的网络包含一个输入层、两个卷积层、两个激活层、两个池化层以及两个全连接层,具体组成如下所示:

  1. 输入层:32×32图片,3个颜色通道RGB,输入空间大小为[128, 32, 32, 3],其中128是batch size,32是图片大小,3表示3个颜色通道
  2. 第一卷积层:64个5×5过滤器,步幅为1,零填充为2,这样可以保证输出的宽和高仍然是32,输出空间大小为[128, 32, 32, 64]
  3. 第一激活层:Relu激活函数
  4. 第一池化层:2×2最大池化,步幅为2,图片大小被缩小为输入的一半,输出空间大小为[128, 16, 16, 64]
  5. 第二卷积层:64个5×5过滤器,步幅为1,零填充为2,这样可以保证输出的宽和高仍然是16,输出空间大小为[128, 16, 16, 64]
  6. 第二激活层:Relu激活函数
  7. 第二池化层:2×2最大池化,步幅为2,图片大小被缩小为输入的一半,输出空间大小为[128, 8, 8, 64]
  8. 第一全连接层:包含384个隐藏神经元,Relu激活函数
  9. 第二全连接层:包含192个隐藏神经元,Relu激活函数
  10. 输出层:包含10个输出神经元,使用交叉墒作为代价函数,并计算softmax来判断图片属于哪个类别

网络的拓扑结构如下所示:

Screen Shot 2016-12-30 at 9.55.04 PM.png

网络性能

我们选定batch size为128,经过36k个epoch之后,对验证集的预测准确度约为77.93%,损失函数的曲线如下图所示:

screen-shot-2016-12-30-at-9-29-02-pm

可视化卷积网络

为了深入理解卷积神经网络的工作方式,我们可以通过可视化的手段输出网络的中间结果,包括卷积层过滤器及其更新梯度,卷积层的输出以及激活层的输出。我们选取最后一个epoch中一张鹿的图片作为例子,原图如下:

screen-shot-2016-12-30-at-9-43-00-pm

可以看到,第一卷积层学习到了如下过滤器:

screen-shot-2016-12-30-at-9-37-23-pm

可以看到,这些过滤器都比较平滑。它们可以被大致分为两类,一类负责检测图片中的特定边缘,一类负责检测图片中的特定颜色块。卷积神经网络通过反向传播来学习过滤器,下图显示了更新这些过滤器时的梯度:

screen-shot-2016-12-30-at-9-41-20-pm

通过上述过滤器的监测之后,我们可以得到卷积层和激活层的输出,如下所示:

Screen Shot 2016-12-30 at 9.45.53 PM.pngScreen Shot 2016-12-30 at 9.48.15 PM.png

可以看到,鹿的边缘和形状特征被过滤器提取出来了,而通过激活层则保留了最强的特征,忽略了次要特征。

通过卷积神经网络的可视化,我们可以加深对它的理解,同时也可以确认网络工作正常。完整的实现代码可以在这里找到。