关于神经网络与深度学习

小时候,每个人都会鼓励不断成长

变成一个心智成熟,不在耍小孩子脾气的人

但是,很少有人鼓励继续成长

变成一个怀疑和抵制社会错误潮流的人

——保罗•格雷厄姆《黑客与画家》

目录

[TOC]

参考资料

课程

b站[中英字幕]吴恩达机器学习系列课程

Coursera机器学习

Coursera深度学习

主成分数据选择那里有一节课没有字幕,建议移步Coursera

黄海广笔记

Coursera深度学习教程中文笔记

斯坦福大学2014(吴恩达)机器学习教程中文笔记

其他

我的机器学习笔记

神经网络与深度学习邱席鹏

27个小时,集中时间的话三天左右看完。估计分散在一周时间内吧。

第一门课 神经网络和深度学习(Neural Networks and Deep Learning)

第一周:深度学习引言(Introduction to Deep Learning)

第二周:神经网络的编程基础(Basics of Neural Network programming)

2.1 二分类(Binary Classification)

2.2 逻辑回归(Logistic Regression)

2.3 逻辑回归的代价函数(Logistic Regress)

损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function

练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数cost function

2.4 梯度下降法(Gradient Descent)

2.9 逻辑回归中的梯度下降(Logistic Regression Gradient Descent)

2.10 m 个样本的梯度下降(Gradient Descent on m Examples)

2.11 向量化(Vectorization)

2.13 向量化逻辑回归(Vectorizing Logistic Regression)

2.14 向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression’s Gradient)

第三周:浅层神经网络(Shallow neural networks)

3.1 神经网络概述(Neural Network Overview)

3.2 神经网络的表示(Neural Network Representation)

3.3 计算一个神经网络的输出(Computing a Neural Network’s output)

3.4 多样本向量化(Vectorizing across multiple examples)

3.5 向量化实现的解释(Justification for vectorized implementation)

3.6 激活函数(Activation functions)

tanh函数或者双曲正切函数是总体上都优于sigmoid函数的激活函数。

sigmoid函数和tanh函数两者共同的缺点是,在$z$特别大或者特别小的情况下,导数的梯度或者函数的斜率会变得特别小,最后就会接近于0,导致降低梯度下降的速度。

在机器学习另一个很流行的函数是:修正线性单元的函数(ReLu

如果输出是0、1值(二分类问题),则输出层选择sigmoid函数,然后其它的所有单元都选择Relu函数。

这是很多激活函数的默认选择,如果在隐藏层上不确定使用哪个激活函数,那么通常会使用Relu激活函数。有时,也会使用tanh激活函数,但Relu的一个优点是:当$z$是负值的时候,导数等于0。

这里也有另一个版本的Relu被称为Leaky Relu

当$z$是负值时,这个函数的值不是等于0,而是轻微的倾斜。

如图。(图在心中)

两者的优点是:

第一,在$z$的区间变动很大的情况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个if-else语句,而sigmoid函数需要进行浮点四则运算,在实践中,使用ReLu激活函数神经网络通常会比使用sigmoid或者tanh激活函数学习的更快。

第二,sigmoidtanh函数的导数在正负饱和区的梯度都会接近于0,这会造成梯度弥散,而ReluLeaky ReLu函数大于0部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu进入负半区的时候,梯度为0,神经元此时不会训练,产生所谓的稀疏性,而Leaky ReLu不会有这问题)

$z$在ReLu的梯度一半都是0,但是,有足够的隐藏层使得z值大于0,所以对大多数的训练数据来说学习过程仍然可以很快。

快速概括一下不同激活函数的过程和结论。

sigmoid激活函数:除了输出层是一个二分类问题基本不会用它。

tanh激活函数:tanh是非常优秀的,几乎适合所有场合。

ReLu激活函数:最常用的默认函数,,如果不确定用哪个激活函数,就使用ReLu或者Leaky ReLu

3.7 为什么需要非线性激活函数?(why need a nonlinear activation function?)

3.8 激活函数的导数(Derivatives of activation functions)

3.9 神经网络的梯度下降(Gradient descent for neural networks)

3.10(选修)直观理解反向传播(Backpropagation intuition)

看懂逻辑回归就清楚了,主要是梯度下降用到loss function对某参数的导数来更新,求导就是链式法则,神经网络就是矩阵求导。

3.11 随机初始化(Random+Initialization)

如果$W$很大,$z$就会很大或者很小,因此这种情况下你很可能停在tanh/sigmoid函数的平坦的地方(见图3.8.2),这些地方梯度很小也就意味着梯度下降会很慢,因此学习也就很慢。

事实上有时有比0.01更好的常数,当你训练一个只有一层隐藏层的网络时(这是相对浅的神经网络,没有太多的隐藏层),设为0.01可能也可以。但当你训练一个非常非常深的神经网络,你可能要试试0.01以外的常数。下一节课我们会讨论怎么并且何时去选择一个不同于0.01的常数,但是无论如何它通常都会是个相对小的数。

第四周:深层神经网络(Deep Neural Networks)

4.1 深层神经网络(Deep L-layer neural network)

4.2 前向传播和反向传播(Forward and backward propagation)

4.3 深层网络中的前向传播(Forward propagation in a Deep Network)

4.4 核对矩阵的维数(Getting your matrix dimensions right)

4.5 为什么使用深层表示?(Why deep representations?)

4.6 搭建神经网络块(Building blocks of deep neural networks)

4.7 参数VS超参数(Parameters vs Hyperparameters)

4.8 深度学习和大脑的关联性(What does this have to do with the brain?)

第二门课 改善深层神经网络:超参数调试、正则化以及优化(Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)

第一周:深度学习的实践层面(Practical aspects of Deep Learning)

1.1 训练,验证,测试集(Train / Dev / Test sets)

比如我们有100万条数据,那么取1万条数据便足以进行评估,找出其中表现最好的1-2种算法。同样地,根据最终选择的分类器,测试集的主要目的是正确评估分类器的性能,所以,如果拥有百万数据,我们只需要1000条数据,便足以评估单个分类器,并且准确评估该分类器的性能。假设我们有100万条数据,其中1万条作为验证集,1万条作为测试集,100万里取1万,比例是1%,即:训练集占98%,验证集和测试集各占1%。对于数据量过百万的应用,训练集可以占到99.5%,验证和测试集各占0.25%,或者验证集占0.4%,测试集占0.1%。

根据经验,我建议大家要确保验证集和测试集的数据来自同一分布,关于这个问题我也会多讲一些。因为你们要用验证集来评估不同的模型,尽可能地优化性能。如果验证集和测试集来自同一个分布就会很好。

1.2 偏差,方差(Bias /Variance)

高偏差(high bias)的情况,我们称为“欠拟合”(underfitting)。

方差较高(high variance),数据过度拟合(overfitting)。

理解偏差和方差的两个关键数据是训练集误差(Train set error)和验证集误差(Dev set error

假定训练集误差是1%,为了方便论证,假定验证集误差是11%,可以看出训练集设置得非常好,而验证集设置相对较差,我们可能过度拟合了训练集,在某种程度上,验证集并没有充分利用交叉验证集的作用,像这种情况,我们称之为“高方差”。

通过查看训练集误差和验证集误差,我们便可以诊断算法是否具有高方差。也就是说衡量训练集和验证集误差就可以得出不同结论。

假设训练集误差是15%,我们把训练集误差写在首行,验证集误差是16%,假设该案例中人的错误率几乎为0%,人们浏览这些图片,分辨出是不是猫。算法并没有在训练集中得到很好训练,如果训练数据的拟合度不高,就是数据欠拟合,就可以说这种算法偏差比较高。相反,它对于验证集产生的结果却是合理的,验证集中的错误率只比训练集的多了1%,所以这种算法偏差高,因为它甚至不能拟合训练集

再举一个例子,训练集误差是15%,偏差相当高,但是,验证集的评估结果更糟糕,错误率达到30%,在这种情况下,我会认为这种算法偏差高,因为它在训练集上结果不理想,而且方差也很高,这是方差偏差都很糟糕的情况。

1.3 机器学习基础(Basic Recipe for Machine Learning)

1.4 正则化(Regularization)

深度学习可能存在过拟合问题——高方差,有两个解决方法,一个是正则化,另一个是准备更多的数据,这是非常可靠的方法,但你可能无法时时刻刻准备足够多的训练数据或者获取更多数据的成本很高,但正则化通常有助于避免过拟合或减少你的网络误差。

今天上午起晚了,进度拖慢了,然后又改了一手cnn模型的代码。明天上午再看。今天晚上休息一会。

1.5 为什么正则化有利于预防过拟合呢?(Why regularization reduces overfitting?)

1.6 dropout 正则化(Dropout Regularization)

除了$L2$正则化,还有一个非常实用的正则化方法——“Dropout(随机失活)”

1.7 理解 dropout(Understanding Dropout)

Dropout可以随机删除网络中的神经单元,他为什么可以通过正则化发挥如此大的作用呢?

直观上理解:不要依赖于任何一个特征,因为该单元的输入可能随时被清除,因此该单元通过这种方式传播下去,并为单元的四个输入增加一点权重,通过传播所有权重,dropout将产生收缩权重的平方范数的效果,和之前讲的$L2$正则化类似;实施dropout的结果实它会压缩权重,并完成一些预防过拟合的外层正则化;$L2$对不同权重的衰减是不同的,它取决于激活函数倍增的大小。

1.8 其他正则化方法(Other regularization methods)

1.9 归一化输入(Normalizing inputs)

1.10 梯度消失/梯度爆炸(Vanishing / Exploding gradients)

1.11 神经网络的权重初始化(Weight Initialization for Deep NetworksVanishing / Exploding gradients)

1.12 梯度的数值逼近(Numerical approximation of gradients)

1.13 梯度检验(Gradient checking)

1.14 梯度检验应用的注意事项(Gradient Checking Implementation Notes)

第二周:优化算法 (Optimization algorithms)

2.1 Mini-batch 梯度下降(Mini-batch gradient descent)

首先,如果训练集较小,直接使用batch梯度下降法,样本集较小就没必要使用mini-batch梯度下降法,你可以快速处理整个训练集,所以使用batch梯度下降法也很好,这里的少是说小于2000个样本,这样比较适合使用batch梯度下降法。不然,样本数目较大的话,一般的mini-batch大小为64到512,考虑到电脑内存设置和使用的方式,如果mini-batch大小是2的$n$次方,代码会运行地快一些,64就是2的6次方,以此类推,128是2的7次方,256是2的8次方,512是2的9次方。所以我经常把mini-batch大小设成2的次方。

mini-batch梯度下降法比batch梯度下降法运行地更快。

2.2 理解mini-batch梯度下降法(Understanding mini-batch gradient descent)

使用batch梯度下降法时,每次迭代你都需要历遍整个训练集,可以预期每次迭代成本都会下降,所以如果成本函数$J$是迭代次数的一个函数,它应该会随着每次迭代而减少,如果$J$在某次迭代中增加了,那肯定出了问题,也许你的学习率太大。

使用mini-batch梯度下降法,如果你作出成本函数在整个过程中的图,则并不是每次迭代都是下降的

另一个极端情况,假设mini-batch大小为1,就有了新的算法,叫做随机梯度下降法,每个样本都是独立的mini-batch

2.3 指数加权平均数(Exponentially weighted averages)

指数加权平均数经常被使用,再说一次,它在统计学中被称为指数加权移动平均值,我们就简称为指数加权平均数。通过调整这个参数($\beta$),或者说后面的算法学习,你会发现这是一个很重要的参数,可以取得稍微不同的效果,往往中间有某个值效果最好

2.4 理解指数加权平均数(Understanding exponentially weighted averages)

指数加权平均数公式的好处之一在于,它占用极少内存,电脑内存中只占用一行数字而已,然后把最新数据代入公式,不断覆盖就可以了,正因为这个原因,其效率,它基本上只占用一行代码,计算指数加权平均数也只占用单行数字的存储和内存,当然它并不是最好的,也不是最精准的计算平均数的方法。如果你要计算移动窗,你直接算出过去10天的总和,过去50天的总和,除以10和50就好,如此往往会得到更好的估测。但缺点是,如果保存所有最近的温度数据,和过去10天的总和,必须占用更多的内存,执行更加复杂,计算成本也更加高昂。

2.5 指数加权平均的偏差修正(Bias correction in exponentially weighted averages)

有个办法可以修改这一估测,让估测变得更好,更准确,特别是在估测初期,也就是不用$v_{t}$,而是用$\frac{v_{t}}{1- \beta^{t}}$,t就是现在的天数。

在机器学习中,在计算指数加权平均数的大部分时候,大家不在乎执行偏差修正,因为大部分人宁愿熬过初始时期,拿到具有偏差的估测,然后继续计算下去。如果你关心初始时期的偏差,在刚开始计算指数加权移动平均数的时候,偏差修正能帮助你在早期获取更好的估测。

2.6 动量梯度下降法(Gradient descent with Momentum)

还有一种算法叫做Momentum,或者叫做动量梯度下降法,运行速度几乎总是快于标准的梯度下降算法,简而言之,基本的想法就是计算梯度的指数加权平均数,并利用该梯度更新你的权重

另一个看待问题的角度是,在纵轴上,你希望学习慢一点,因为你不想要这些摆动,但是在横轴上,你希望加快学习,你希望快速从左向右移,移向最小值,移向红点。

想象你有一个碗,你拿一个球,微分项给了这个球一个加速度,此时球正向山下滚,球因为加速度越滚越快,而因为$\beta$ 稍小于1,表现出一些摩擦力,所以球不会无限加速下去,所以不像梯度下降法,每一步都独立于之前的步骤,你的球可以向下滚,获得动量,可以从碗向下加速获得动量。我发现这个球从碗滚下的比喻,物理能力强的人接受得比较好,但不是所有人都能接受,如果球从碗中滚下这个比喻,你理解不了,别担心。

最后我们来看具体如何计算,算法在此。

你有两个超参数,学习率$a$以及参数$\beta$,$\beta$控制着指数加权平均数。$\beta$最常用的值是0.9,我们之前平均了过去十天的温度,所以现在平均了前十次迭代的梯度。实际上$\beta$为0.9时,效果不错,你可以尝试不同的值,可以做一些超参数的研究,不过0.9是很棒的鲁棒数。

2.7 RMSprop

你们知道了动量(Momentum)可以加快梯度下降,还有一个叫做RMSprop的算法,全称是root mean square prop算法,它也可以加速梯度下降,我们来看看它是如何运作的。

该算法会照常计算当下mini-batch的微分$dW$,$db$,所以我会保留这个指数加权平均数,我们用到新符号$S_{dW}$,而不是$v_{dW}$,因此$S_{dW}= \beta S_{dW} + (1 -\beta) {dW}^{2}$,澄清一下,这个平方的操作是针对这一整个符号的,这样做能够保留微分平方的加权平均数,同样$S_{db}= \beta S_{db} + (1 - \beta){db}^{2}$,再说一次,平方是针对整个符号的操作。

接着RMSprop会这样更新参数值,$W:= W -a\frac{dW}{\sqrt{S_{dW}}}$,$b:=b -\alpha\frac{db}{\sqrt{S_{db}}}$,我们来理解一下其原理。记得在横轴方向或者在例子中的$W$方向,我们希望学习速度快,而在垂直方向,也就是例子中的$b$方向,我们希望减缓纵轴上的摆动,所以有了$S_{dW}$和$S_{db}$,我们希望$S_{dW}$会相对较小,所以我们要除以一个较小的数,而希望$S_{db}$又较大,所以这里我们要除以较大的数字,这样就可以减缓纵轴上的变化。你看这些微分,垂直方向的要比水平方向的大得多,所以斜率在$b$方向特别大,所以这些微分中,$db$较大,$dW$较小,因为函数的倾斜程度,在纵轴上,也就是b方向上要大于在横轴上,也就是$W$方向上。$db$的平方较大,所以$S_{db}$也会较大,而相比之下,$dW$会小一些,亦或$dW$平方会小一些,因此$S_{dW}$会小一些,结果就是纵轴上的更新要被一个较大的数相除,就能消除摆动,而水平方向的更新则被较小的数相除。

所以RMSpropMomentum有很相似的一点,可以消除梯度下降中的摆动,包括mini-batch梯度下降,并允许你使用一个更大的学习率$a$,从而加快你的算法学习速度。

2.8 Adam 优化算法(Adam optimization algorithm)

在深度学习的历史上,包括许多知名研究者在内,提出了优化算法,并很好地解决了一些问题,但随后这些优化算法被指出并不能一般化,并不适用于多种神经网络,时间久了,深度学习圈子里的人开始多少有些质疑全新的优化算法,很多人都觉得动量(Momentum)梯度下降法很好用,很难再想出更好的优化算法。所以RMSprop以及Adam优化算法(Adam优化算法也是本视频的内容),就是少有的经受住人们考验的两种算法,已被证明适用于不同的深度学习结构,这个算法我会毫不犹豫地推荐给你,因为很多人都试过,并且用它很好地解决了许多问题。

2.9 学习率衰减(Learning rate decay)

2.10 局部最优的问题(The problem of local optima)

首先,你不太可能困在极差的局部最优中,条件是你在训练较大的神经网络,存在大量参数,并且成本函数$J$被定义在较高的维度空间。

第二点,平稳段是一个问题,这样使得学习十分缓慢,这也是像Momentum或是RMSpropAdam这样的算法,能够加速学习算法的地方。在这些情况下,更成熟的优化算法,如Adam算法,能够加快速度,让你尽早往下走出平稳段。

第三周 超参数调试、Batch正则化和程序框架(Hyperparameter tuning)

3.1 调试处理(Tuning process)

3.2 为超参数选择合适的范围(Using an appropriate scale to pick hyperparameters)

3.3 超参数调试的实践:Pandas VS Caviar(Hyperparameters tuning in practice: Pandas vs. Caviar)

3.4 归一化网络的激活函数(Normalizing activations in a network)

3.5 将 Batch Norm 拟合进神经网络(Fitting Batch Norm into a neural network)

3.6 Batch Norm 为什么奏效(Why does Batch Norm work?)

3.7 测试时的 Batch Norm(Batch Norm at test time)

3.8 Softmax 回归(Softmax regression)

3.9 训练一个 Softmax 分类器(Training a Softmax classifier)

3.10 深度学习框架(Deep Learning frameworks)

3.11 TensorFlow

今天晚上多花了点时间,把进度提前推一点,BN是重点,明天回过头来再看看。明天再刷一天就进入cv了。

第三门课 结构化机器学习项目(Structuring Machine Learning Projects)

第一周 机器学习(ML)策略(1)(ML strategy(1))

1.1 为什么是ML策略?(Why ML Strategy?)

1.2 正交化(Orthogonalization)

所以正交化的概念是指,你可以想出一个维度,这个维度你想做的是控制转向角,还有另一个维度来控制你的速度,那么你就需要一个旋钮尽量只控制转向角,另一个旋钮,在这个开车的例子里其实是油门和刹车控制了你的速度。但如果你有一个控制旋钮将两者混在一起,比如说这样一个控制装置同时影响你的转向角和速度,同时改变了两个性质,那么就很难令你的车子以想要的速度和角度前进。然而正交化之后,正交意味着互成90度。设计出正交化的控制装置,最理想的情况是和你实际想控制的性质一致,这样你调整参数时就容易得多。可以单独调整转向角,还有你的油门和刹车,令车子以你想要的方式运动。

1.3 单一数字评估指标(Single number evaluation metric)

1.4 满足和优化指标(Satisficing and optimizing metrics)

总结一下,如果你需要顾及多个指标,比如说,有一个优化指标,你想尽可能优化的,然后还有一个或多个满足指标,需要满足的,需要达到一定的门槛。现在你就有一个全自动的方法,在观察多个成本大小时,选出”最好的”那个。现在这些评估指标必须是在训练集或开发集或测试集上计算或求出来的。所以你还需要做一件事,就是设立训练集、开发集,还有测试集。

1.5 训练/开发/测试集划分(Train/dev/test distributions)

1.6 开发集和测试集的大小(Size of dev and test sets)

1.7 什么时候该改变开发/测试集和指标?(When to change dev/test sets and metrics)

所以方针是,如果你在指标上表现很好,在当前开发集或者开发集和测试集分布中表现很好,但你的实际应用程序,你真正关注的地方表现不好,那么就需要修改指标或者你的开发测试集。换句话说,如果你发现你的开发测试集都是这些高质量图像,但在开发测试集上做的评估无法预测你的应用实际的表现。因为你的应用处理的是低质量图像,那么就应该改变你的开发测试集,让你的数据更能反映你实际需要处理好的数据。

但总体方针就是,如果你当前的指标和当前用来评估的数据和你真正关心必须做好的事情关系不大,那就应该更改你的指标或者你的开发测试集,让它们能更够好地反映你的算法需要处理好的数据。

1.8 为什么是人的表现?(Why human-level performance?)

1.9 可避免偏差(Avoidable bias)

这个差值,贝叶斯错误率或者对贝叶斯错误率的估计和训练错误率之间的差值称为可避免偏差,你可能希望一直提高训练集表现,直到你接近贝叶斯错误率,但实际上你也不希望做到比贝叶斯错误率更好,这理论上是不可能超过贝叶斯错误率的,除非过拟合。而这个训练错误率和开发错误率之前的差值,就大概说明你的算法在方差问题上还有多少改善空间。

1.10 理解人的表现(Understanding human-level performance)

1.11 超过人的表现(Surpassing human- level performance)

1.12 改善你的模型的表现(Improving your model performance)

所以我想要让一个监督学习算法达到实用,基本上希望或者假设你可以完成两件事情。首先,你的算法对训练集的拟合很好,这可以看成是你能做到可避免偏差很低。还有第二件事你可以做好的是,在训练集中做得很好,然后推广到开发集和测试集也很好,这就是说方差不是太大。

可以修正可避免偏差问题,比如训练更大的网络或者训练更久。还有一套独立的技巧可以用来处理方差问题,比如正则化或者收集更多训练数据。

总结一下前几段视频我们见到的步骤,如果你想提升机器学习系统的性能,我建议你们看看训练错误率和贝叶斯错误率估计值之间的距离,让你知道可避免偏差有多大。换句话说,就是你觉得还能做多好,你对训练集的优化还有多少空间。然后看看你的开发错误率和训练错误率之间的距离,就知道你的方差问题有多大。换句话说,你应该做多少努力让你的算法表现能够从训练集推广到开发集,算法是没有在开发集上训练的。

如果你想用尽一切办法减少可避免偏差,我建议试试这样的策略:比如使用规模更大的模型,这样算法在训练集上的表现会更好,或者训练更久。使用更好的优化算法,比如说加入momentum或者RMSprop,或者使用更好的算法,比如Adam。你还可以试试寻找更好的新神经网络架构,或者说更好的超参数。这些手段包罗万有,你可以改变激活函数,改变层数或者隐藏单位数,虽然你这么做可能会让模型规模变大。或者试用其他模型,其他架构,如循环神经网络和卷积神经网络。在之后的课程里我们会详细介绍的,新的神经网络架构能否更好地拟合你的训练集,有时也很难预先判断,但有时换架构可能会得到好得多的结果。

另外当你发现方差是个问题时,你可以试用很多技巧,包括以下这些:你可以收集更多数据,因为收集更多数据去训练可以帮你更好地推广到系统看不到的开发集数据。你可以尝试正则化,包括$L2$正则化,dropout正则化或者我们在之前课程中提到的数据增强。同时你也可以试用不同的神经网络架构,超参数搜索,看看能不能帮助你,找到一个更适合你的问题的神经网络架构。

第二周:机器学习策略(2)(ML Strategy (2))

2.1 进行误差分析(Carrying out error analysis)

总结一下,进行错误分析,你应该找一组错误样本,可能在你的开发集里或者测试集里,观察错误标记的样本,看看假阳性(false positives)和假阴性(false negatives),统计属于不同错误类型的错误数量。在这个过程中,你可能会得到启发,归纳出新的错误类型,就像我们看到的那样。如果你过了一遍错误样本,然后说,天,有这么多Instagram滤镜或Snapchat滤镜,这些滤镜干扰了我的分类器,你就可以在途中新建一个错误类型。总之,通过统计不同错误标记类型占总数的百分比,可以帮你发现哪些问题需要优先解决,或者给你构思新优化方向的灵感。在做错误分析的时候,有时你会注意到开发集里有些样本被错误标记了,这时应该怎么做呢?

2.2 清除标注错误的数据(Cleaning up Incorrectly labeled data)

如果你还记得设立开发集的目标的话,开发集的主要目的是,你希望用它来从两个分类器$A$和$B$中选择一个。所以当你测试两个分类器$A$和$B$时,在开发集上一个有2.1%错误率,另一个有1.9%错误率,但是你不能再信任开发集了,因为它无法告诉你这个分类器是否比这个好,因为0.6%的错误率是标记出错导致的。那么现在你就有很好的理由去修正开发集里的错误标签,因为在右边这个样本中,标记出错对算法错误的整体评估标准有严重的影响。而左边的样本中,标记出错对你算法影响的百分比还是相对较小的。

现在如果你决定要去修正开发集数据,手动重新检查标签,并尝试修正一些标签,这里还有一些额外的方针和原则需要考虑。首先,我鼓励你不管用什么修正手段,都要同时作用到开发集和测试集上,我们之前讨论过为什么,开发和测试集必须来自相同的分布。开发集确定了你的目标,当你击中目标后,你希望算法能够推广到测试集上,这样你的团队能够更高效的在来自同一分布的开发集和测试集上迭代。如果你打算修正开发集上的部分数据,那么最好也对测试集做同样的修正以确保它们继续来自相同的分布。所以我们雇佣了一个人来仔细检查这些标签,但必须同时检查开发集和测试集。

2.3 快速搭建你的第一个系统,并进行迭代(Build your first system quickly, then iterate)

如果你想搭建全新的机器学习程序,就是快速搭好你的第一个系统,然后开始迭代。我的意思是我建议你快速设立开发集和测试集还有指标,这样就决定了你的目标所在,如果你的目标定错了,之后改也是可以的。但一定要设立某个目标,然后我建议你马上搭好一个机器学习系统原型,然后找到训练集,训练一下,看看效果,开始理解你的算法表现如何,在开发集测试集,你的评估指标上表现如何。当你建立第一个系统后,你就可以马上用到之前说的偏差方差分析

建立这个初始系统的所有意义在于,它可以是一个快速和粗糙的实现(quick and dirty implementation),你知道的,别想太多。初始系统的全部意义在于,有一个学习过的系统,有一个训练过的系统,让你确定偏差方差的范围,就可以知道下一步应该优先做什么,让你能够进行错误分析,可以观察一些错误,然后想出所有能走的方向,哪些是实际上最有希望的方向。

你的主要目标是弄出能用的系统,你的主要目标并不是发明全新的机器学习算法,这是完全不同的目标,那时你的目标应该是想出某种效果非常好的算法。

2.4 使用来自不同分布的数据,进行训练和测试(Training and testing on different distributions)

将两组数据合并在一起,这样你就有21万张照片,你可以把这21万张照片随机分配到训练、开发和测试集中。

2.5 数据分布不匹配时,偏差与方差的分析(Bias and Variance with mismatched data distributions)

我们继续用猫分类器为例,我们说人类在这个任务上能做到几乎完美,所以贝叶斯错误率或者说贝叶斯最优错误率,我们知道这个问题里几乎是0%。所以要进行错误率分析,你通常需要看训练误差,也要看看开发集的误差。比如说,在这个样本中,你的训练集误差是1%,你的开发集误差是10%,如果你的开发集来自和训练集一样的分布,你可能会说,这里存在很大的方差问题,你的算法不能很好的从训练集出发泛化,它处理训练集很好,但处理开发集就突然间效果很差了。

但如果你的训练数据和开发数据来自不同的分布,你就不能再放心下这个结论了。特别是,也许算法在开发集上做得不错,可能因为训练集很容易识别,因为训练集都是高分辨率图片,很清晰的图像,但开发集要难以识别得多。所以也许软件没有方差问题,这只不过反映了开发集包含更难准确分类的图片。所以这个分析的问题在于,当你看训练误差,再看开发误差,有两件事变了。首先算法只见过训练集数据,没见过开发集数据。第二,开发集数据来自不同的分布。而且因为你同时改变了两件事情,很难确认这增加的9%误差率有多少是因为算法没看到开发集中的数据导致的,这是问题方差的部分,有多少是因为开发集数据就是不一样。

为了弄清楚哪个因素影响更大,如果你完全不懂这两种影响到底是什么,别担心我们马上会再讲一遍。但为了分辨清楚两个因素的影响,定义一组新的数据是有意义的,我们称之为训练-开发集,所以这是一个新的数据子集。我们应该从训练集的分布里挖出来,但你不会用来训练你的网络。

我们说人类水平错误率是4%的话,你的训练错误率是7%,而你的训练-开发错误率是10%,而开发错误率是12%,这样你就大概知道可避免偏差有多大。因为你知道,你希望你的算法至少要在训练集上的表现接近人类。而这大概表明了方差大小,所以你从训练集泛化推广到训练-开发集时效果如何?而这告诉你数据不匹配的问题大概有多大。技术上你还可以再加入一个数字,就是测试集表现,我们写成测试集错误率,你不应该在测试集上开发,因为你不希望对测试集过拟合。但如果你看看这个,那么这里的差距就说明你对开发集过拟合的程度。所以如果开发集表现和测试集表现有很大差距,那么你可能对开发集过拟合了,所以也许你需要一个更大的开发集,对吧?要记住,你的开发集和测试集来自同一分布,所以这里存在很大差距的话。如果算法在开发集上做的很好,比测试集好得多,那么你就可能对开发集过拟合了。如果是这种情况,那么你可能要往回退一步,然后收集更多开发集数据。

2.6 处理数据不匹配问题(Addressing data mismatch)

如果您的训练集来自和开发测试集不同的分布,如果错误分析显示你有一个数据不匹配的问题该怎么办?这个问题没有完全系统的解决方案,但我们可以看看一些可以尝试的事情。如果我发现有严重的数据不匹配问题,我通常会亲自做错误分析,尝试了解训练集和开发测试集的具体差异。技术上,为了避免对测试集过拟合,要做错误分析,你应该人工去看开发集而不是测试集。

通过人工数据合成,你可以快速制造更多的训练数据,就像真的在车里录的那样,那就不需要花时间实际出去收集数据,比如说在实际行驶中的车子,录下上万小时的音频。所以,如果错误分析显示你应该尝试让你的数据听起来更像在车里录的,那么人工合成那种音频,然后喂给你的机器学习算法,这样做是合理的。

我们谈到其中一种办法是人工数据合成,人工数据合成确实有效。在语音识别中。我已经看到人工数据合成显著提升了已经非常好的语音识别系统的表现,所以这是可行的。但当你使用人工数据合成时,一定要谨慎,要记住你有可能从所有可能性的空间只选了很小一部分去模拟数据。

2.7 迁移学习(Transfer learning)

深度学习中,最强大的理念之一就是,有的时候神经网络可以从一个任务中习得知识,并将这些知识应用到另一个独立的任务中。所以例如,也许你已经训练好一个神经网络,能够识别像猫这样的对象,然后使用那些知识,或者部分习得的知识去帮助您更好地阅读x射线扫描图,这就是所谓的迁移学习。

那么迁移学习什么时候是有意义的呢?迁移学习起作用的场合是,在迁移来源问题中你有很多数据,但迁移目标问题你没有那么多数据。例如,假设图像识别任务中你有1百万个样本,所以这里数据相当多。可以学习低层次特征,可以在神经网络的前面几层学到如何识别很多有用的特征。但是对于放射科任务,也许你只有一百个样本,所以你的放射学诊断问题数据很少,也许只有100次$X$射线扫描,所以你从图像识别训练中学到的很多知识可以迁移,并且真正帮你加强放射科识别任务的性能,即使你的放射科数据很少。

2.8 多任务学习(Multi-task learning)

最后多任务学习往往在以下场合更有意义,当你可以训练一个足够大的神经网络,同时做好所有的工作,所以多任务学习的替代方法是为每个任务训练一个单独的神经网络。所以不是训练单个神经网络同时处理行人、汽车、停车标志和交通灯检测。你可以训练一个用于行人检测的神经网络,一个用于汽车检测的神经网络,一个用于停车标志检测的神经网络和一个用于交通信号灯检测的神经网络。那么研究员Rich Carona几年前发现的是什么呢?多任务学习会降低性能的唯一情况,和训练单个神经网络相比性能更低的情况就是你的神经网络还不够大。但如果你可以训练一个足够大的神经网络,那么多任务学习肯定不会或者很少会降低性能,我们都希望它可以提升性能,比单独训练神经网络来单独完成各个任务性能要更好。

2.9 什么是端到端的深度学习?(What is end-to-end deep learning?)

研究人员发现,比起一步到位,一步学习,把这个问题分解成两个更简单的步骤。首先,是弄清楚脸在哪里。第二步是看着脸,弄清楚这是谁。这第二种方法让学习算法,或者说两个学习算法分别解决两个更简单的任务,并在整体上得到更好的表现。

为什么两步法更好呢?实际上有两个原因。一是,你解决的两个问题,每个问题实际上要简单得多。但第二,两个子任务的训练数据都很多。具体来说,有很多数据可以用于人脸识别训练,对于这里的任务1来说,任务就是观察一张图,找出人脸所在的位置,把人脸图像框出来,所以有很多数据,有很多标签数据$(x,y)$,其中$x$是图片,$y$是表示人脸的位置,你可以建立一个神经网络,可以很好地处理任务1。然后任务2,也有很多数据可用,今天,业界领先的公司拥有,比如说数百万张人脸照片,所以输入一张裁剪得很紧凑的照片,比如这张红色照片,下面这个,今天业界领先的人脸识别团队有至少数亿的图像,他们可以用来观察两张图片,并试图判断照片里人的身份,确定是否同一个人,所以任务2还有很多数据。相比之下,如果你想一步到位,这样$(x,y)$的数据对就少得多,其中$x$是门禁系统拍摄的图像,$y$是那人的身份,因为你没有足够多的数据去解决这个端到端学习问题,但你却有足够多的数据来解决子问题1和子问题2。

实际上,把这个分成两个子问题,比纯粹的端到端深度学习方法,达到更好的表现。不过如果你有足够多的数据来做端到端学习,也许端到端方法效果更好。但在今天的实践中,并不是最好的方法。

2.10 是否要使用端到端的深度学习?(Whether to use end-to-end learning?)

这里是应用端到端学习的一些好处,首先端到端学习真的只是让数据说话。所以如果你有足够多的$(x,y)$数据,那么不管从$x$到$y$最适合的函数映射是什么,如果你训练一个足够大的神经网络,希望这个神经网络能自己搞清楚,而使用纯机器学习方法,直接从$x$到$y$输入去训练的神经网络,可能更能够捕获数据中的任何统计信息,而不是被迫引入人类的成见。

端到端深度学习的第二个好处就是这样,所需手工设计的组件更少,所以这也许能够简化你的设计工作流程,你不需要花太多时间去手工设计功能,手工设计这些中间表示方式。

那么缺点呢?这里有一些缺点,首先,它可能需要大量的数据。要直接学到这个$x$到$y$的映射,你可能需要大量$(x,y)$数据。我们在以前的视频里看过一个例子,其中你可以收集大量子任务数据,比如人脸识别,我们可以收集很多数据用来分辨图像中的人脸,当你找到一张脸后,也可以找得到很多人脸识别数据。但是对于整个端到端任务,可能只有更少的数据可用。所以$x$这是端到端学习的输入端,$y$是输出端,所以你需要很多这样的$(x,y)$数据,在输入端和输出端都有数据,这样可以训练这些系统。这就是为什么我们称之为端到端学习,因为你直接学习出从系统的一端到系统的另一端。

另一个缺点是,它排除了可能有用的手工设计组件。机器学习研究人员一般都很鄙视手工设计的东西,但如果你没有很多数据,你的学习算法就没办法从很小的训练集数据中获得洞察力。所以手工设计组件在这种情况,可能是把人类知识直接注入算法的途径,这总不是一件坏事。我觉得学习算法有两个主要的知识来源,一个是数据,另一个是你手工设计的任何东西,可能是组件,功能,或者其他东西。所以当你有大量数据时,手工设计的东西就不太重要了,但是当你没有太多的数据时,构造一个精心设计的系统,实际上可以将人类对这个问题的很多认识直接注入到问题里,进入算法里应该挺有帮助的。

如果你在构建一个新的机器学习系统,而你在尝试决定是否使用端到端深度学习,我认为关键的问题是,你有足够的数据能够直接学到从$x$映射到$y$足够复杂的函数吗?我还没有正式定义过这个词“必要复杂度(complexity needed)”。但直觉上,如果你想从$x$到$y$的数据学习出一个函数,就是看着这样的图像识别出图像中所有骨头的位置,那么也许这像是识别图中骨头这样相对简单的问题,也许系统不需要那么多数据来学会处理这个任务。或给出一张人物照片,也许在图中把人脸找出来不是什么难事,所以你也许不需要太多数据去找到人脸,或者至少你可以找到足够数据去解决这个问题。相对来说,把手的X射线照片直接映射到孩子的年龄,直接去找这种函数,直觉上似乎是更为复杂的问题。如果你用纯端到端方法,需要很多数据去学习。

所以这个例子就表明了,如果你想使用机器学习或者深度学习来学习某些单独的组件,那么当你应用监督学习时,你应该仔细选择要学习的$x$到$y$映射类型,这取决于那些任务你可以收集数据。

6.25 刚好到周五结束前三门课程,周末可以再把后续的cv的CNN模型和nlp序列模型看完,然后就可以马上自己动手实践了。其实这样看一遍只相当于是简单预习,完全谈不上学完,只有自己一边实践一边学才能真正学会。后两门才是重头,前面都是机器学习课程里曾经涉及过的或者相关的基础内容,这样复习一边前面知识一边继续前进,这个进度很舒服。

第四门课 卷积神经网络(Convolutional Neural Networks)

起晚了。

第一周 卷积神经网络(Foundations of Convolutional Neural Networks)

1.2 边缘检测示例(Edge detection example)

卷积运算是卷积神经网络最基本的组成部分,使用边缘检测作为入门样例。

在编程练习中,你会使用一个叫conv_forward的函数。如果在tensorflow下,这个函数叫tf.conv2d。在其他深度学习框架中,在后面的课程中,你将会看到Keras这个框架,在这个框架下用Conv2D实现卷积运算。所有的编程框架都有一些函数来实现卷积运算。

1.3 更多边缘检测内容(More edge detection)

你已经见识到用卷积运算实现垂直边缘检测,在本视频中,你将学习如何区分正边和负边,这实际就是由亮到暗与由暗到亮的区别,也就是边缘的过渡。你还能了解到其他类型的边缘检测以及如何去实现这些算法,而不要总想着去自己编写一个边缘检测程序,让我们开始吧。

1.4 Padding

为了构建深度神经网络,你需要学会使用的一个基本的卷积操作就是padding,让我们来看看它是如何工作的。

第一个缺点是每次做卷积操作,你的图像就会缩小,从6×6缩小到4×4,你可能做了几次之后,你的图像就会变得很小了,可能会缩小到只有1×1的大小。你可不想让你的图像在每次识别边缘或其他特征时都缩小,这就是第一个缺点。

第二个缺点时,如果你注意角落边缘的像素,这个像素点(绿色阴影标记)只被一个输出所触碰或者使用,因为它位于这个3×3的区域的一角。但如果是在中间的像素点,比如这个(红色方框标记),就会有许多3×3的区域与之重叠。所以那些在角落或者边缘区域的像素点在输出中采用较少,意味着你丢掉了图像边缘位置的许多信息。

为了解决这些问题,你可以在卷积操作之前填充这幅图像。在这个案例中,你可以沿着图像边缘再填充一层像素。如果你这样操作了,那么6×6的图像就被你填充成了一个8×8的图像。

至于选择填充多少像素,通常有两个选择,分别叫做Valid卷积和Same卷积。

因此如果你有一个$n×n$的图像,用$p$个像素填充边缘,输出的大小就是这样的$(n+2p-f+1)×(n+2p-f+1)$。如果你想让$n+2p-f+1=n$的话,使得输出和输入大小相等,如果你用这个等式求解$p$,那么$p=(f-1)/2$。所以当$f$是一个奇数的时候,只要选择相应的填充尺寸,你就能确保得到和输入相同尺寸的输出。

1.5 卷积步长(Strided convolutions)

卷积中的步幅是另一个构建卷积神经网络的基本操作,让我向你展示一个例子。

输入和输出的维度是由下面的公式决定的。如果你用一个$f×f$的过滤器卷积一个$n×n$的图像,你的padding为$p$,步幅为$s$,在这个例子中$s=2$,你会得到一个输出,因为现在你不是一次移动一个步子,而是一次移动$s$个步子,输出于是变为$\frac{n+2p - f}{s} + 1 \times \frac{n+2p - f}{s} + 1$

现在只剩下最后的一个细节了,如果商不是一个整数怎么办?在这种情况下,我们向下取整。$⌊ ⌋$这是向下取整的符号,这也叫做对$z$进行地板除(floor),这意味着$z$向下取整到最近的整数。这个原则实现的方式是,你只在蓝框完全包括在图像或填充完的图像内部时,才对它进行运算。如果有任意一个蓝框移动到了外面,那你就不要进行相乘操作,这是一个惯例。你的3×3的过滤器必须完全处于图像中或者填充之后的图像区域内才输出相应结果,这就是惯例。因此正确计算输出维度的方法是向下取整,以免$\frac{n + 2p - f}{s}$不是整数。

1.6 三维卷积(Convolutions over volumes)

你已经知道如何对二维图像做卷积了,现在看看如何执行卷积不仅仅在二维图像上,而是三维立体上。

1.7 单层卷积网络(One layer of a convolutional network)

今天我们要讲的是如何构建卷积神经网络的卷积层,下面来看个例子。

1.8 简单卷积网络示例(A simple convolution network example)

上节课,我们讲了如何为卷积网络构建一个卷积层。今天我们看一个深度卷积神经网络的具体示例,顺便练习一下我们上节课所学的标记法。

1.9 池化层(Pooling layers)

除了卷积层,卷积网络也经常使用池化层来缩减模型的大小,提高计算速度,同时提高所提取特征的鲁棒性,我们来看一下。

最大池化算法。

另外还有一种类型的池化,平均池化,它不太常用。目前来说,最大池化比平均池化更常用。但也有例外,就是深度很深的神经网络,你可以用平均池化来分解规模为7×7×1000的网络的表示层,在整个空间内求平均值,得到1×1×1000,一会我们看个例子。但在神经网络中,最大池化要比平均池化用得更多。

总结一下,池化的超级参数包括过滤器大小$f$和步幅$s$,常用的参数值为$f=2$,$s=2$,应用频率非常高,其效果相当于高度和宽度缩减一半。也有使用$f=3$,$s=2$的情况。至于其它超级参数就要看你用的是最大池化还是平均池化了。你也可以根据自己意愿增加表示padding的其他超级参数,虽然很少这么用。最大池化时,往往很少用到超参数padding,当然也有例外的情况,我们下周会讲。大部分情况下,最大池化很少用padding。目前$p$最常用的值是0,即$p=0$。最大池化的输入就是$n_{H} \times n_{W} \times n_{c}$,假设没有padding,则输出$\lfloor\frac{n_{H} - f}{s} +1\rfloor \times \lfloor\frac{n_{w} - f}{s} + 1\rfloor \times n_{c}$。输入通道与输出通道个数相同,因为我们对每个通道都做了池化。需要注意的一点是,池化过程中没有需要学习的参数。执行反向传播时,反向传播没有参数适用于最大池化。只有这些设置过的超参数,可能是手动设置的,也可能是通过交叉验证设置的。

1.10 卷积神经网络示例(Convolutional neural network example)

构建全卷积神经网络的构造模块我们已经掌握得差不多了,下面来看个例子。

人们发现在卷积神经网络文献中,卷积有两种分类,这与所谓层的划分存在一致性。一类卷积是一个卷积层和一个池化层一起作为一层,这就是神经网络的Layer1。另一类卷积是把卷积层作为一层,而池化层单独作为一层。人们在计算神经网络有多少层时,通常只统计具有权重和参数的层。因为池化层没有权重和参数,只有一些超参数。这里,我们把CONV1POOL1共同作为一个卷积,并标记为Layer1。虽然你在阅读网络文章或研究报告时,你可能会看到卷积层和池化层各为一层的情况,这只是两种不同的标记术语。一般我在统计网络层数时,只计算具有权重的层,也就是把CONV1POOL1作为Layer1。这里我们用CONV1POOL1来标记,两者都是神经网络Layer1的一部分,POOL1也被划分在Layer1中,因为它没有权重

1.11 为什么使用卷积?(Why convolutions?)

这是本周最后一节课,我们来分析一下卷积在神经网络中如此受用的原因,然后对如何整合这些卷积,如何通过一个标注过的训练集训练卷积神经网络做个简单概括。和只用全连接层相比,卷积层的两个主要优势在于参数共享和稀疏连接,举例说明一下。

第二周 深度卷积网络:实例探究(Deep convolutional models: case studies)

2.1 为什么要进行实例探究?(Why look at case studies?)

2.2 经典网络(Classic networks)

这节课,我们来学习几个经典的神经网络结构,分别是LeNet-5AlexNetVGGNet

2.3 残差网络(ResNets)(Residual Networks (ResNets))

2.4 残差网络为什么有用?(Why ResNets work?)

之所以能实现跳跃连接是因为same卷积保留了维度,所以很容易得出这个捷径连接,并输出这两个相同维度的向量。

结果表明,残差块学习这个恒等式函数并不难,跳跃连接使我们很容易得出$ a^{\left\lbrack l + 2 \right\rbrack} = a^{\left\lbrack l\right\rbrack}$。这意味着,即使给神经网络增加了这两层,它的效率也并不逊色于更简单的神经网络,因为学习恒等函数对它来说很简单。尽管它多了两层,也只把$a^{[l]}$的值赋值给$a^{\left\lbrack l + 2 \right\rbrack}$。所以给大型神经网络增加两层,不论是把残差块添加到神经网络的中间还是末端位置,都不会影响网络的表现。

残差网络起作用的主要原因就是这些残差块学习恒等函数非常容易,你能确定网络性能不会受到影响,很多时候甚至可以提高效率,或者说至少不会降低网络的效率,因此创建类似残差网络可以提升网络性能。

2.5 网络中的网络以及 1×1 卷积(Network in Network and 1×1 convolutions)

2.6 谷歌 Inception 网络简介(Inception network motivation)

2.7 Inception 网络(Inception network)

2.8 使用开源的实现方案(Using open-source implementations)

2.9 迁移学习(Transfer Learning)

2.10 数据增强(Data augmentation)

常用的实现数据扩充的方法是使用一个线程或者是多线程,这些可以用来加载数据,实现变形失真,然后传给其他的线程或者其他进程,来训练这个(编号2)和这个(编号1),可以并行实现。

这就是数据扩充,与训练深度神经网络的其他部分类似,在数据扩充过程中也有一些超参数,比如说颜色变化了多少,以及随机裁剪的时候使用的参数。与计算机视觉其他部分类似,一个好的开始可能是使用别人的开源实现,了解他们如何实现数据扩充。当然如果你想获得更多的不变特性,而其他人的开源实现并没有实现这个,你也可以去调整这些参数。因此,我希望你们可以使用数据扩充使你的计算机视觉应用效果更好。

2.11 计算机视觉现状(The state of computer vision)

下面是一些有助于在基准测试中表现出色的小技巧

其中一个是集成,这就意味着在你想好了你想要的神经网络之后,可以独立训练几个神经网络,并平均它们的输出。比如说随机初始化三个、五个或者七个神经网络,然后训练所有这些网络,然后平均它们的输出。另外对他们的输出$\hat y$进行平均计算是很重要的,不要平均他们的权重,这是行不通的。看看你的7个神经网络,它们有7个不同的预测,然后平均他们,这可能会让你在基准上提高1%,2%或者更好。这会让你做得更好,也许有时会达到1%或2%,这真的能帮助你赢得比赛。但因为集成意味着要对每张图片进行测试,你可能需要在从3到15个不同的网络中运行一个图像,这是很典型的,因为这3到15个网络可能会让你的运行时间变慢,甚至更多时间,所以技巧之一的集成是人们在基准测试中表现出色和赢得比赛的利器,但我认为这几乎不用于生产服务于客户的,我想除非你有一个巨大的计算预算而且不介意在每个用户图像数据上花费大量的计算。

你在论文中可以看到在测试时,对进准测试有帮助的另一个技巧就是Multi-crop at test time,我的意思是你已经看到了如何进行数据扩充,Multi-crop是一种将数据扩充应用到你的测试图像中的一种形式。

集成的一个大问题是你需要保持所有这些不同的神经网络,这就占用了更多的计算机内存。对于multi-crop,我想你只保留一个网络,所以它不会占用太多的内存,但它仍然会让你的运行时间变慢。

这些是你看到的小技巧,研究论文也可以参考这些,但我个人并不倾向于在构建生产系统时使用这些方法,尽管它们在基准测试和竞赛上做得很好。

不知道什么时候会用到机器视觉

第三周 目标检测(Object detection)

3.1 目标定位(Object localization)

3.2 特征点检测(Landmark detection)

3.3 目标检测(Object detection)

3.4 滑动窗口的卷积实现(Convolutional implementation of sliding windows)

为了构建滑动窗口的卷积应用,首先要知道如何把神经网络的全连接层转化成卷积层。

3.5 Bounding Box预测(Bounding box predictions)

在上一个视频中,你们学到了滑动窗口法的卷积实现,这个算法效率更高,但仍然存在问题,不能输出最精准的边界框。在这个视频中,我们看看如何得到更精准的边界框。

其中一个能得到更精准边界框的算法是YOLO算法,YOLO(You only look once)意思是你只看一次,这是由Joseph RedmonSantosh DivvalaRoss GirshickAli Farhadi提出的算法。

是这么做的,比如你的输入图像是100×100的,然后在图像上放一个网格。为了介绍起来简单一些,我用3×3网格,实际实现时会用更精细的网格,可能是19×19。基本思路是使用图像分类和定位算法,前几个视频介绍过的,然后将算法应用到9个格子上。(基本思路是,采用图像分类和定位算法,本周第一个视频中介绍过的,逐一应用在图像的9个格子中。)更具体一点,你需要这样定义训练标签,所以对于9个格子中的每一个指定一个标签$y$,$y$是8维的,和你之前看到的一样,$y= \ \begin{bmatrix} p_{c} \\ b_{x} \\ b_{y} \\ b_{h} \\ b_{w} \\ c_{1} \\ c_{2}\\ c_{3} \\\end{bmatrix}$,$p_{c}$等于0或1取决于这个绿色格子中是否有图像。然后$b_{x}$、$b_{y}$、$b_{h}$和$b_{w}$作用就是,如果那个格子里有对象,那么就给出边界框坐标。然后$c_{1}$、$c_{2}$和$c_{3}$就是你想要识别的三个类别,背景类别不算,所以你尝试在背景类别中识别行人、汽车和摩托车,那么$c_{1}$、$c_{2}$和$c_{3}$可以是行人、汽车和摩托车类别。这张图里有9个格子,所以对于每个格子都有这么一个向量。

事实上YOLO算法有一个好处,也是它受欢迎的原因,因为这是一个卷积实现,实际上它的运行速度非常快,可以达到实时识别。

3.6 交并比(Intersection over union)

在对象检测任务中,你希望能够同时定位对象,所以如果实际边界框是这样的,你的算法给出这个紫色的边界框,那么这个结果是好还是坏?所以交并比(loU)函数做的是计算两个边界框交集和并集之比。两个边界框的并集是这个区域,就是属于包含两个边界框区域(绿色阴影表示区域),而交集就是这个比较小的区域(橙色阴影表示区域),那么交并比就是交集的大小,这个橙色阴影面积,然后除以绿色阴影的并集面积。

一般约定,在计算机检测任务中,如果$loU≥0.5$,就说检测正确,如果预测器和实际边界框完美重叠,loU就是1,因为交集就等于并集。但一般来说只要$loU≥0.5$,那么结果是可以接受的,看起来还可以。一般约定,0.5是阈值,用来判断预测的边界框是否正确。一般是这么约定,但如果你希望更严格一点,你可以将loU定得更高,比如说大于0.6或者更大的数字,但loU越高,边界框越精确。

所以这是衡量定位精确度的一种方式,你只需要统计算法正确检测和定位对象的次数,你就可以用这样的定义判断对象定位是否准确。再次,0.5是人为约定,没有特别深的理论依据,如果你想更严格一点,可以把阈值定为0.6。有时我看到更严格的标准,比如0.6甚至0.7,但很少见到有人将阈值降到0.5以下。

人们定义loU这个概念是为了评价你的对象定位算法是否精准,但更一般地说,loU衡量了两个边界框重叠地相对大小。如果你有两个边界框,你可以计算交集,计算并集,然后求两个数值的比值,所以这也可以判断两个边界框是否相似,我们将在下一个视频中再次用到这个函数,当我们讨论非最大值抑制时再次用到。

3.7 非极大值抑制(Non-max suppression)

到目前为止你们学到的对象检测中的一个问题是,你的算法可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次。非极大值抑制这个方法可以确保你的算法对每个对象只检测一次

所以这就是非极大值抑制,非最大值意味着你只输出概率最大的分类结果,但抑制很接近,但不是最大的其他预测结果,所以这方法叫做非极大值抑制。

3.8 Anchor Boxes

到目前为止,对象检测中存在的一个问题是每个格子只能检测出一个对象,如果你想让一个格子检测出多个对象,你可以这么做,就是使用anchor box这个概念

这就是anchor box的概念,我们建立anchor box这个概念,是为了处理两个对象出现在同一个格子的情况,实践中这种情况很少发生,特别是如果你用的是19×19网格而不是3×3的网格,两个对象中点处于361个格子中同一个格子的概率很低,确实会出现,但出现频率不高。也许设立anchor box的好处在于anchor box能让你的学习算法能够更有征对性,特别是如果你的数据集有一些很高很瘦的对象,比如说行人,还有像汽车这样很宽的对象,这样你的算法就能更有针对性的处理,这样有一些输出单元可以针对检测很宽很胖的对象,比如说车子,然后输出一些单元,可以针对检测很高很瘦的对象,比如说行人。

最后,你应该怎么选择anchor box呢?人们一般手工指定anchor box形状,你可以选择5到10个anchor box形状,覆盖到多种不同的形状,可以涵盖你想要检测的对象的各种形状。还有一个更高级的版本,我就简单说一句,你们如果接触过一些机器学习,可能知道后期YOLO论文中有更好的做法,就是所谓的k-平均算法,可以将两类对象形状聚类,如果我们用它来选择一组anchor box,选择最具有代表性的一组anchor box,可以代表你试图检测的十几个对象类别,但这其实是自动选择anchor box的高级方法。如果你就人工选择一些形状,合理的考虑到所有对象的形状,你预计会检测的很高很瘦或者很宽很胖的对象,这应该也不难做。

3.9 YOLO 算法(Putting it together: YOLO algorithm)

你们已经学到对象检测算法的大部分组件了,在这个视频里,我们会把所有组件组装在一起构成YOLO对象检测算法。

我们先看看如何构造你的训练集,假设你要训练一个算法去检测三种对象,行人、汽车和摩托车,你还需要显式指定完整的背景类别。这里有3个类别标签,如果你要用两个anchor box,那么输出 $y$ 就是3×3×2×8,其中3×3表示3×3个网格,2是anchor box的数量,8是向量维度,8实际上先是5($p_{c},b_{x},b_{y},b_{h},b_{w}$)再加上类别的数量($c_{1},c_{2},c_{3}$)。你可以将它看成是3×3×2×8,或者3×3×16。要构造训练集,你需要遍历9个格子,然后构成对应的目标向量$y$。

所以先看看第一个格子(编号1),里面没什么有价值的东西,行人、车子和摩托车,三个类别都没有出现在左上格子中,所以对应那个格子目标$y$就是这样的,$y= \begin{bmatrix} 0 & ? & ? & ? & ? & ? & ? & ? & 0 & ? & ? & ? & ? & ? & ? & ?\\ \end{bmatrix}^{T}$,第一个anchor box的 $p_{c}$ 是0,因为没什么和第一个anchor box有关的,第二个anchor box的 $p_{c}$ 也是0,剩下这些值是don’t care-s

现在网格中大多数格子都是空的,但那里的格子(编号2)会有这个目标向量$y$,$y =\begin{bmatrix} 0 & ? & ? & ? & ? & ? & ? & ? & 1 & b_{x} & b_{y} & b_{h} &b_{w} & 0 & 1 & 0 \\\end{bmatrix}^{T}$,所以假设你的训练集中,对于车子有这样一个边界框(编号3),水平方向更长一点。所以如果这是你的anchor box,这是anchor box 1(编号4),这是anchor box 2(编号5),然后红框和anchor box 2的交并比更高,那么车子就和向量的下半部分相关。要注意,这里和anchor box 1有关的 $p_{c}$ 是0,剩下这些分量都是don’t care-s,然后你的第二个 $p_{c} =1$,然后你要用这些($b_{x},b_{y},b_{h},b_{w}$)来指定红边界框的位置,然后指定它的正确类别是2($c_{1}= 0 ,c_{2} = 1,c_{3} = 0$),对吧,这是一辆汽车。

所以你这样遍历9个格子,遍历3×3网格的所有位置,你会得到这样一个向量,得到一个16维向量,所以最终输出尺寸就是3×3×16。和之前一样,简单起见,我在这里用的是3×3网格,实践中用的可能是19×19×16,或者需要用到更多的anchor box,可能是19×19×5×8,即19×19×40,用了5个anchor box。这就是训练集,然后你训练一个卷积网络,输入是图片,可能是100×100×3,然后你的卷积网络最后输出尺寸是,在我们例子中是3×3×16或者3×3×2×8。

接下来我们看看你的算法是怎样做出预测的,输入图像,你的神经网络的输出尺寸是这个3×3×2×8,对于9个格子,每个都有对应的向量。对于左上的格子(编号1),那里没有任何对象,那么我们希望你的神经网络在那里(第一个$p_{c}$)输出的是0,这里(第二个$p_{c}$)是0,然后我们输出一些值,你的神经网络不能输出问号,不能输出don’t care-s,剩下的我输入一些数字,但这些数字基本上会被忽略,因为神经网络告诉你,那里没有任何东西,所以输出是不是对应一个类别的边界框无关紧要,所以基本上是一组数字,多多少少都是噪音(输出 $y$ 如编号3所示)。

和这里的边界框不大一样,希望$y$的值,那个左下格子(编号2)的输出$y$(编号4所示),形式是,对于边界框1来说($p_{c}$)是0,然后就是一组数字,就是噪音(anchor box 1对应行人,此格子中无行人,$p_{c} = 0,b_{x} = ?,b_{y} = ?,b_{h} = ?,b_{w} = ?,c_{1} = ?c_{2} = ?,c_{3} =?$)。希望你的算法能输出一些数字,可以对车子指定一个相当准确的边界框(anchor box 2对应汽车,此格子中有车,$ p_{c} = 1,b_{x},b_{y},b_{h},b_{w},c_{1} = 0,c_{2}= 1,c_{3} = 0$),这就是神经网络做出预测的过程。

最后你要运行一下这个非极大值抑制,为了让内容更有趣一些,我们看看一张新的测试图像,这就是运行非极大值抑制的过程。如果你使用两个anchor box,那么对于9个格子中任何一个都会有两个预测的边界框,其中一个的概率$p_{c}$很低。但9个格子中,每个都有两个预测的边界框,比如说我们得到的边界框是是这样的,注意有一些边界框可以超出所在格子的高度和宽度(编号1所示)。接下来你抛弃概率很低的预测,去掉这些连神经网络都说,这里很可能什么都没有,所以你需要抛弃这些(编号2所示)。

最后,如果你有三个对象检测类别,你希望检测行人,汽车和摩托车,那么你要做的是,对于每个类别单独运行非极大值抑制,处理预测结果所属类别的边界框,用非极大值抑制来处理行人类别,用非极大值抑制处理车子类别,然后对摩托车类别进行非极大值抑制,运行三次来得到最终的预测结果。所以算法的输出最好能够检测出图像里所有的车子,还有所有的行人(编号3所示)。

这就是YOLO对象检测算法,这实际上是最有效的对象检测算法之一,包含了整个计算机视觉对象检测领域文献中很多最精妙的思路。

3.10 候选区域(选修)(Region proposals (Optional))

如果你们阅读一下对象检测的文献,可能会看到一组概念,所谓的候选区域,这在计算机视觉领域是非常有影响力的概念。我把这个视频定为可选视频是因为我用到候选区域这一系列算法的频率没有那么高,但当然了,这些工作是很有影响力的,你们在工作中也可能会碰到,我们来看看。

你们还记得滑动窗法吧,你使用训练过的分类器,在这些窗口中全部运行一遍,然后运行一个检测器,看看里面是否有车辆,行人和摩托车。现在你也可以运行一下卷积算法,这个算法的其中一个缺点是,它在显然没有任何对象的区域浪费时间,对吧。

所以这里这个矩形区域(编号1)基本是空的,显然没有什么需要分类的东西。也许算法会在这个矩形上(编号2)运行,而你知道上面没有什么有趣的东西。

[Girshick R, Donahue J, Darrell T, et al. Rich feature hierarchies for accurate object detection and semantic segmentation[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2014: 580-587.]

所以Ross GirshickJeff DonahueTrevor DarrellJitendra Malik,在本幻灯片底部引用到的论文中提出一种叫做R-CNN的算法,意思是带区域的卷积网络,或者说带区域的CNN。这个算法尝试选出一些区域,在这些区域上运行卷积网络分类器是有意义的,所以这里不再针对每个滑动窗运行检测算法,而是只选择一些窗口,在少数窗口上运行卷积网络分类器。

选出候选区域的方法是运行图像分割算法,分割的结果是下边的图像,为了找出可能存在对象的区域。比如说,分割算法在这里得到一个色块,所以你可能会选择这样的边界框(编号1),然后在这个色块上运行分类器,就像这个绿色的东西(编号2),在这里找到一个色块,接下来我们还会在那个矩形上(编号2)运行一次分类器,看看有没有东西。在这种情况下,如果在蓝色色块上(编号3)运行分类器,希望你能检测出一个行人,如果你在青色色块(编号4)上运行算法,也许你可以发现一辆车,我也不确定。

所以这个细节就是所谓的分割算法,你先找出可能2000多个色块,然后在这2000个色块上放置边界框,然后在这2000个色块上运行分类器,这样需要处理的位置可能要少的多,可以减少卷积网络分类器运行时间,比在图像所有位置运行一遍分类器要快。特别是这种情况,现在不仅是在方形区域(编号5)中运行卷积网络,我们还会在高高瘦瘦(编号6)的区域运行,尝试检测出行人,然后我们在很宽很胖的区域(编号7)运行,尝试检测出车辆,同时在各种尺度运行分类器。

这就是R-CNN或者区域CNN的特色概念,现在看来R-CNN算法还是很慢的。所以有一系列的研究工作去改进这个算法,所以基本的R-CNN算法是使用某种算法求出候选区域,然后对每个候选区域运行一下分类器,每个区域会输出一个标签,有没有车子?有没有行人?有没有摩托车?并输出一个边界框,这样你就能在确实存在对象的区域得到一个精确的边界框。

澄清一下,R-CNN算法不会直接信任输入的边界框,它也会输出一个边界框$b_{x}$,$b_{y}$,$b_{h}$和$b_{w}$,这样得到的边界框比较精确,比单纯使用图像分割算法给出的色块边界要好,所以它可以得到相当精确的边界框。

现在R-CNN算法的一个缺点是太慢了,所以这些年来有一些对R-CNN算法的改进工作,Ross Girshik提出了快速的R-CNN算法,它基本上是R-CNN算法,不过用卷积实现了滑动窗法。最初的算法是逐一对区域分类的,所以快速R-CNN用的是滑动窗法的一个卷积实现,这和你在本周第四个视频(3.4 卷积的滑动窗口实现)中看到的大致相似,这显著提升了R-CNN的速度。

事实证明,Fast R-CNN算法的其中一个问题是得到候选区域的聚类步骤仍然非常缓慢,所以另一个研究组,任少卿Shaoqing Ren)、何凯明Kaiming He)、Ross Girshick孙剑Jiangxi Sun)提出了更快的R-CNN算法(Faster R-CNN),使用的是卷积神经网络,而不是更传统的分割算法来获得候选区域色块,结果比Fast R-CNN算法快得多。不过我认为大多数更快R-CNN的算法实现还是比YOLO算法慢很多。

候选区域的概念在计算机视觉领域的影响力相当大,所以我希望你们能了解一下这些算法,因为你可以看到还有人在用这些概念。对我个人来说,这是我的个人看法而不是整个计算机视觉研究界的看法,我觉得候选区域是一个有趣的想法,但这个方法需要两步,首先得到候选区域,然后再分类,相比之下,能够一步做完,类似于YOLO或者你只看一次(You only look once)这个算法,在我看来,是长远而言更有希望的方向。但这是我的个人看法,而不是整个计算机视觉研究界的看法,所以你们最好批判接受。但我想这个R-CNN概念,你可能会想到,或者碰到其他人在用,所以这也是值得了解的,这样你可以更好地理解别人的算法。

第四周 特殊应用:人脸识别和神经风格转换(Special applications: Face recognition &Neural style transfer)

4.1 什么是人脸识别?

4.2 One-Shot学习(One-shot learning)

所以要让人脸识别能够做到一次学习,为了能有更好的效果,你现在要做的应该是学习Similarity函数。详细地说,你想要神经网络学习这样一个用$d$表示的函数,$d(img1,img2) = degree\ of\ difference\ between\ images$,它以两张图片作为输入,然后输出这两张图片的差异值。如果你放进同一个人的两张照片,你希望它能输出一个很小的值,如果放进两个长相差别很大的人的照片,它就输出一个很大的值。所以在识别过程中,如果这两张图片的差异值小于某个阈值$\tau$,它是一个超参数,那么这时就能预测这两张图片是同一个人,如果差异值大于τ,就能预测这是不同的两个人,这就是解决人脸验证问题的一个可行办法。

4.3 Siamese 网络(Siamese network)

对于两个不同的输入,运行相同的卷积神经网络,然后比较它们,这一般叫做Siamese网络架构。这里提到的很多观点,都来自于Yaniv TaigmanMing YangMarc’ Aurelio RanzatoLior Wolf的这篇论文,他们开发的系统叫做DeepFace

怎么训练这个Siamese神经网络呢?不要忘了这两个网络有相同的参数,所以你实际要做的就是训练一个网络,它计算得到的编码可以用于函数$d$,它可以告诉你两张图片是否是同一个人。更准确地说,神经网络的参数定义了一个编码函数$f(x^{(i)})$,如果给定输入图像$x^{(i)}$,这个网络会输出$x^{(i)}$的128维的编码。你要做的就是学习参数,使得如果两个图片$x^{( i)}$和$x^{( j)}$是同一个人,那么你得到的两个编码的距离就小。前面几个幻灯片我都用的是$x^{(1)}$和$x^{( 2)}$,其实训练集里任意一对$x^{(i)}$和$x^{(j)}$都可以。相反,如果$x^{(i)}$和$x^{(j)}$是不同的人,那么你会想让它们之间的编码距离大一点。

4.4 Triplet 损失

想通过学习神经网络的参数来得到优质的人脸图片编码,方法之一就是定义三元组损失函数然后应用梯度下降。

用三元组损失的术语来说,你要做的通常是看一个 Anchor 图片,你想让Anchor图片和Positive图片(Positive意味着是同一个人)的距离很接近。然而,当Anchor图片与Negative图片(Negative意味着是非同一个人)对比时,你会想让他们的距离离得更远一点。

这就是为什么叫做三元组损失,它代表你通常会同时看三张图片,你需要看Anchor图片、Postive图片,还有Negative图片,我要把Anchor图片、Positive图片和Negative图片简写成$A$、$P$、$N$。

把这些写成公式的话,你想要的是网络的参数或者编码能够满足以下特性,也就是说你想要$|| f(A) - f(P) ||^{2}$,你希望这个数值很小,准确地说,你想让它小于等$f(A)$和$f(N)$之间的距离,或者说是它们的范数的平方(即:$|| f(A) - f(P)||^{2} \leq ||f(A) - f(N)||^{2}$)。($|| f(A) - f(P) ||^{2}$)当然这就是$d(A,P)$,($|| f(A) - f(N) ||^{2}$)这是$d(A,N)$,你可以把$d$ 看作是距离(distance)函数,这也是为什么我们把它命名为$d$。

现在如果我把方程右边项移到左边,最终就得到:

$|| f(A) - f(P)||^{2} \leq ||f(A) - f(N)||^{2}$

现在我要对这个表达式做一些小的改变,有一种情况满足这个表达式,但是没有用处,就是把所有的东西都学成0,如果$f$总是输出0,即0-0≤0,这就是0减去0还等于0,如果所有图像的$f$都是一个零向量,那么总能满足这个方程。所以为了确保网络对于所有的编码不会总是输出0,也为了确保它不会把所有的编码都设成互相相等的。另一种方法能让网络得到这种没用的输出,就是如果每个图片的编码和其他图片一样,这种情况,你还是得到0-0。

为了阻止网络出现这种情况,我们需要修改这个目标,也就是,这个不能是刚好小于等于0,应该是比0还要小,所以这个应该小于一个$-a$值(即$|| f(A) - f(P)||^{2} -||f(A) - f(N)||^{2} \leq -a$),这里的$a$是另一个超参数,这个就可以阻止网络输出无用的结果。按照惯例,我们习惯写$+a$(即$|| f(A) - f(P)||^{2} -||f(A) - f(N)||^{2} +a\leq0$),而不是把$-a$写在后面,它也叫做间隔(margin),这个术语你会很熟悉,如果你看过关于支持向量机 (SVM)的文献,没看过也不用担心。我们可以把上面这个方程($|| f(A) - f(P)||^{2} -||f(A) - f(N)||^{2}$)也修改一下,加上这个间隔参数。

接下来我们定义损失函数,这个例子的损失函数,它的定义基于三元图片组,我先从前一张幻灯片复制过来一些式子,就是$|| f( A) - f( P)||^{2} -||f( A) - f( N)||^{2} +a \leq0$。所以为了定义这个损失函数,我们取这个和0的最大值:

$L( A,P,N) = max(|| f( A) - f( P)||^{2} -|| f( A) - f( N)||^{2} + a,0)$

这个$max$函数的作用就是,只要这个$|| f( A) - f( P)||^{2} -|| f( A) - f( N)||^{2} + a\leq0$,那么损失函数就是0。只要你能使画绿色下划线部分小于等于0,只要你能达到这个目标,那么这个例子的损失就是0。

另一方面如果这个$|| f( A) - f( P)||^{2} -|| f( A) - f( N)||^{2} + a\leq0$,然后你取它们的最大值,最终你会得到绿色下划线部分(即$|| f(A) - f( P)||^{2} -|| f( A) - f( N)||^{2} +a$)是最大值,这样你会得到一个正的损失值。通过最小化这个损失函数达到的效果就是使这部分$|| f( A) - f( P)||^{2} -||f( A) - f( N)||^{2} +a$成为0,或者小于等于0。只要这个损失函数小于等于0,网络不会关心它负值有多大。

这是一个三元组定义的损失,整个网络的代价函数应该是训练集中这些单个三元组损失的总和。假如你有一个10000个图片的训练集,里面是1000个不同的人的照片,你要做的就是取这10000个图片,然后生成这样的三元组,然后训练你的学习算法,对这种代价函数用梯度下降,这个代价函数就是定义在你数据集里的这样的三元组图片上。

注意,为了定义三元组的数据集你需要成对的$A$和$P$,即同一个人的成对的图片,为了训练你的系统你确实需要一个数据集,里面有同一个人的多个照片。这是为什么在这个例子中,我说假设你有1000个不同的人的10000张照片,也许是这1000个人平均每个人10张照片,组成了你整个数据集。如果你只有每个人一张照片,那么根本没法训练这个系统。当然,训练完这个系统之后,你可以应用到你的一次学习问题上,对于你的人脸识别系统,可能你只有想要识别的某个人的一张照片。但对于训练集,你需要确保有同一个人的多个图片,至少是你训练集里的一部分人,这样就有成对的AnchorPositive图片了。

所以为了构建一个数据集,你要做的就是尽可能选择难训练的三元组$A$、$P$和$N$。具体而言,你想要所有的三元组都满足这个条件($d(A,P) + a \leq d(A,N)$),难训练的三元组就是,你的$A$、$P$和$N$的选择使得$d(A,P)$很接近$d(A,N)$,即$d(A,P) \approx d(A,N)$,这样你的学习算法会竭尽全力使右边这个式子变大($d(A,N)$),或者使左边这个式子($d(A,P)$)变小,这样左右两边至少有一个$a$的间隔。并且选择这样的三元组还可以增加你的学习算法的计算效率,如果随机的选择这些三元组,其中有太多会很简单,梯度算法不会有什么效果,因为网络总是很轻松就能得到正确的结果,只有选择难的三元组梯度下降法才能发挥作用,使得这两边离得尽可能远。

4.5 人脸验证与二分类(Face verification and binary classification)

总结一下,把人脸验证当作一个监督学习,创建一个只有成对图片的训练集,不是三个一组,而是成对的图片,目标标签是1表示一对图片是一个人,目标标签是0表示图片中是不同的人。利用不同的成对图片,使用反向传播算法去训练神经网络,训练Siamese神经网络。

4.6 什么是神经风格迁移?(What is neural style transfer?)

4.7 CNN特征可视化(What are deep ConvNets learning?)

4.8 代价函数(Cost function)

记住我们的问题,给你一个内容图像$C$,给定一个风格图片$S$,而你的目标是生成一个新图片$G$。为了实现神经风格迁移,你要做的是定义一个关于$G$的代价函数$J$用来评判某个生成图像的好坏,我们将使用梯度下降法去最小化$J(G)$,以便于生成这个图像。

怎么判断生成图像的好坏呢?我们把这个代价函数定义为两个部分。

$J_{\text{content}}(C,G)$

第一部分被称作内容代价,这是一个关于内容图片和生成图片的函数,它是用来度量生成图片$G$的内容与内容图片$C$的内容有多相似。

$J_{\text{style}}(S,G)$

然后我们会把结果加上一个风格代价函数,也就是关于$S$和$G$的函数,用来度量图片$G$的风格和图片$S$的风格的相似度。

$J( G) = a J_{\text{content}}( C,G) + \beta J_{\text{style}}(S,G)$

最后我们用两个超参数$a$和$\beta$来来确定内容代价和风格代价,两者之间的权重用两个超参数来确定。两个代价的权重似乎是多余的,我觉得一个超参数似乎就够了,但提出神经风格迁移的原始作者使用了两个不同的超参数,我准备保持一致。

关于神经风格迁移算法我将在接下来几段视频中展示的,是基于Leon GatysAlexandra EckerMatthias Bethge的这篇论文。这篇论文并不是很难读懂,如果你愿意,看完这些视频,我也非常推荐你去看看他们的论文。

Leon A. Gatys, Alexander S. Ecker, Matthias Bethge, (2015). A Neural Algorithm of Artistic Style (https://arxiv.org/abs/1508.06576)

4.9 内容代价函数(Content cost function)

现在你需要衡量假如有一个内容图片和一个生成图片他们在内容上的相似度,我们令这个$a^{[l][C]}$和$a^{[l][G]}$,代表这两个图片$C$和$G$的$l$层的激活函数值。如果这两个激活值相似,那么就意味着两个图片的内容相似。

我们定义这个

$J_{\text{content}}( C,G) = \frac{1}{2}|| a^{[l][C]} - a^{[l][G]}||^{2}$

4.10 风格代价函数(Style cost function)

比如你有这样一张图片,你可能已经对这个计算很熟悉了,它能算出这里是否含有不同隐藏层。现在你选择了某一层$l$(编号1),比如这一层去为图片的风格定义一个深度测量,现在我们要做的就是将图片的风格定义为$l$层中各个通道之间激活项的相关系数。

4.11 一维到三维推广(1D and 3D generalizations of models)

一下就快到了晚上十点了,晚安早点睡。

第五门课 序列模型(Sequence Models)

第一周 循环序列模型(Recurrent Neural Networks)

1.1 为什么选择序列模型?(Why Sequence Models?)

要想了解到这个模型学到了什么,一种非正式的方法就是进行一次新序列采样

1.2 数学符号(Notation)

如果你遇到了一个不在你词表中的单词,答案就是创建一个新的标记,也就是一个叫做Unknow Word的伪造单词,用\<UNK>作为标记,来表示不在词表中的单词

1.3 循环神经网络模型(Recurrent Neural Network Model)

140529e4d7531babb5ba21778cd88bc3

循环神经网络用的激活函数经常是tanh,不过有时候也会用ReLU,但是tanh是更通常的选择,我们有其他方法来避免梯度消失问题,我们将在之后进行讲述。选用哪个激活函数是取决于你的输出$y$,如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,如果是$k$类别分类问题的话,那么可以选用softmax作为激活函数。不过这里激活函数的类型取决于你有什么样类型的输出$y$,对于命名实体识别来说$y$只可能是0或者1,那我猜这里第二个激活函数$g$可以是sigmoid激活函数。

1.4 通过时间的反向传播(Backpropagation through time)

在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,这也就是为什么这个算法有一个很别致的名字,叫做“通过(穿越)时间反向传播backpropagation through time)”。取这个名字的原因是对于前向传播,你需要从左到右进行计算,在这个过程中,时刻$t$不断增加。而对于反向传播,你需要从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样。

nn_cell_backpro

1.5 不同类型的循环神经网络(Different types of RNNs)

这个视频的内容参考了Andrej Karpathy的博客,一篇叫做《循环神经网络的非理性效果》(“The Unreasonable Effectiveness of Recurrent Neural Networks”)的文章,我们看一些例子。

多对多”、“多对一”、“一对一”和“一对多”的结构

1.6 语言模型和序列生成(Language model and sequence generation)

所以语言模型所做的就是,它会告诉你某个特定的句子它出现的概率是多少,根据我所说的这个概率,假设你随机拿起一张报纸,打开任意邮件,或者任意网页或者听某人说下一句话,并且这个人是你的朋友,这个你即将从世界上的某个地方得到的句子会是某个特定句子的概率是多少,例如“the apple and pear salad”。它是两种系统的基本组成部分,一个刚才所说的语音识别系统,还有机器翻译系统,它要能正确输出最接近的句子。而语言模型做的最基本工作就是输入一个句子,准确地说是一个文本序列,$y^{<1>}$,$y^{<2>}$一直到$y^{<T_{y}>}$。对于语言模型来说,用$y$来表示这些序列比用$x$来表示要更好,然后语言模型会估计某个句子序列中各个单词出现的可能性。

那么如何建立一个语言模型呢?为了使用RNN建立出这样的模型,你首先需要一个训练集,包含一个很大的英文文本语料库(corpus)或者其它的语言,你想用于构建模型的语言的语料库。语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的英文句子组成的文本。

1.7 对新序列采样(Sampling novel sequences)

这些就是基础的RNN结构和如何去建立一个语言模型并使用它,对于训练出的语言模型进行采样。在之后的视频中,我想探讨在训练RNN时一些更加深入的挑战以及如何适应这些挑战,特别是梯度消失问题来建立更加强大的RNN模型。下节课,我们将谈到梯度消失并且会开始谈到GRU,也就是门控循环单元和LSTM长期记忆网络模型。

1.8 循环神经网络的梯度消失(Vanishing gradients with RNNs)

你应该还记得之前讨论的训练很深的网络,我们讨论了梯度消失的问题。比如说一个很深很深的网络(上图编号4所示),100层,甚至更深,对这个网络从左到右做前向传播然后再反向传播。我们知道如果这是个很深的神经网络,从输出$\hat y$得到的梯度很难传播回去,很难影响靠前层的权重,很难影响前面层(编号5所示的层)的计算。

对于有同样问题的RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困难,因为同样的梯度消失的问题,后面层的输出误差(上图编号6所示)很难影响前面层(上图编号7所示的层)的计算。这就意味着,实际上很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的was或者were。而且在英语里面,这中间的内容(上图编号8所示)可以任意长,对吧?所以你需要长时间记住单词是单数还是复数,这样后面的句子才能用到这些信息。也正是这个原因,所以基本的RNN模型会有很多局部影响,意味着这个输出$\hat y^{<3>}$(上图编号9所示)主要受$\hat y^{<3>}$附近的值(上图编号10所示)的影响,上图编号11所示的一个数值主要与附近的输入(上图编号12所示)有关,上图编号6所示的输出,基本上很难受到序列靠前的输入(上图编号10所示)的影响,这是因为不管输出是什么,不管是对的,还是错的,这个区域都很难反向传播到序列的前面部分,也因此网络很难调整序列前面的计算。这是基本的RNN算法的一个缺点,我们会在下几节视频里处理这个问题。如果不管的话,RNN会不擅长处理长期依赖的问题。

尽管我们一直在讨论梯度消失问题,但是,你应该记得我们在讲很深的神经网络时,我们也提到了梯度爆炸,我们在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练RNN时是首要的问题,尽管梯度爆炸也是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法。然而梯度消失更难解决,这也是我们下几节视频的主题。

1.9 GRU单元(Gated Recurrent Unit(GRU))

许多GRU的想法都来分别自于Yu Young Chang, Kagawa,Gaza Hera, Chang Hung Chu
Jose Banjo的两篇论文。我再引用上个视频中你已经见过的这个句子,“The cat, which already ate……, was full.”,你需要记得猫是单数的,为了确保你已经理解了为什么这里是was而不是were,“The cat was full.”或者是“The cats were full”。当我们从左到右读这个句子,GRU单元将会有个新的变量称为$c$,代表细胞(cell),即记忆细胞(下图编号1所示)。记忆细胞的作用是提供了记忆的能力,比如说一只猫是单数还是复数,所以当它看到之后的句子的时候,它仍能够判断句子的主语是单数还是复数。于是在时间$t$处,有记忆细胞$c^{}$,然后我们看的是,GRU实际上输出了激活值$a^{}$,$c^{} = a^{}$(下图编号2所示)。于是我们想要使用不同的符号$c$和$a$来表示记忆细胞的值和输出的激活值,即使它们是一样的。我现在使用这个标记是因为当我们等会说到LSTMs的时候,这两个会是不同的值,但是现在对于GRU,$c^{}$的值等于$a^{}$的激活值。

所以这些等式表示了GRU单元的计算,在每个时间步,我们将用一个候选值重写记忆细胞,即${\tilde{c}}^{}$的值,所以它就是个候选值,替代了$c^{}$的值。然后我们用tanh激活函数来计算,${\tilde{c}}^{} =tanh(W_{c}\left\lbrack c^{},x^{} \right\rbrack +b_{c})$,所以${\tilde{c}}^{}$的值就是个替代值,代替表示$c^{}$的值(下图编号3所示)。

重点来了,在GRU中真正重要的思想是我们有一个门,我先把这个门叫做$\Gamma_{u}$(上图编号4所示),这是个下标为$u$的大写希腊字母$\Gamma$,$u$代表更新门,这是一个0到1之间的值。为了让你直观思考GRU的工作机制,先思考$\Gamma_{u}$,这个一直在0到1之间的门值,实际上这个值是把这个式子带入sigmoid函数得到的,$\Gamma_{u}= \sigma(W_{u}\left\lbrack c^{},x^{} \right\rbrack +b_{u})$。我们还记得sigmoid函数是上图编号5所示这样的,它的输出值总是在0到1之间,对于大多数可能的输入,sigmoid函数的输出总是非常接近0或者非常接近1。在这样的直觉下,可以想到$\Gamma_{u}$在大多数的情况下非常接近0或1。然后这个字母u表示“update”,我选了字母$\Gamma$是因为它看起来像门。还有希腊字母GG是门的首字母,所以G表示门。

然后GRU的关键部分就是上图编号3所示的等式,我们刚才写出来的用$\tilde{c}$更新$c$的等式。然后门决定是否要真的更新它。于是我们这么看待它,记忆细胞$c^{}$将被设定为0或者1,这取决于你考虑的单词在句子中是单数还是复数,因为这里是单数情况,所以我们先假定它被设为了1,或者如果是复数的情况我们就把它设为0。然后GRU单元将会一直记住$c^{}$的值,直到上图编号7所示的位置,$c^{}$的值还是1,这就告诉它,噢,这是单数,所以我们用was。于是门,即$\Gamma_{u}$的作用就是决定什么时候你会更新这个值,特别是当你看到词组the cat,即句子的主语猫,这就是一个好时机去更新这个值。然后当你使用完它的时候,“The cat, which already ate……, was full.”,然后你就知道,我不需要记住它了,我可以忘记它了。

所以我们接下来要给GRU用的式子就是$c^{} = \Gamma_{u}{\tilde{c}}^{} +\left( 1- \Gamma_{u} \right)c^{}$(上图编号1所示)。你应该注意到了,如果这个更新值$\Gamma_{u} =1$,也就是说把这个新值,即$c^{}$设为候选值($\Gamma_{u} =1$时简化上式,$c^{} = {\tilde{c}}^{}$)。将门值设为1(上图编号2所示),然后往前再更新这个值。对于所有在这中间的值,你应该把门的值设为0,即$\Gamma_{u}= 0$,意思就是说不更新它,就用旧的值。因为如果$\Gamma_{u} = 0$,则$c^{} =c^{}$,$c^{}$等于旧的值。甚至你从左到右扫描这个句子,当门值为0的时候(上图编号3所示,中间$\Gamma_{u}=0$一直为0,表示一直不更新),就是说不更新它的时候,不要更新它,就用旧的值,也不要忘记这个值是什么,这样即使你一直处理句子到上图编号4所示,$c^{}$应该会一直等$c^{}$,于是它仍然记得猫是单数的。

让我再画个图来(下图所示)解释一下GRU单元,顺便说一下,当你在看网络上的博客或者教科书或者教程之类的,这些图对于解释GRU和我们稍后会讲的LSTM是相当流行的,我个人感觉式子在图片中比较容易理解,那么即使看不懂图片也没关系,我就画画,万一能帮得上忙就最好了。

GRU单元输入$c^{}$(下图编号1所示),对于上一个时间步,先假设它正好等于$a^{}$,所以把这个作为输入。然后$x^{}$也作为输入(下图编号2所示),然后把这两个用合适权重结合在一起,再用tanh计算,算出${\tilde{c}}^{}$,${\tilde{c}}^{} =tanh(W_{c}\left\lbrack c^{},x^{} \right\rbrack +b_{c})$,即$c^{}$的替代值。

再用一个不同的参数集,通过sigmoid激活函数算出$\Gamma_{u}$,$\Gamma_{u}= \sigma(W_{u}\left\lbrack c^{},x^{} \right\rbrack +b_{u})$,即更新门。最后所有的值通过另一个运算符结合,我并不会写出公式,但是我用紫色阴影标注的这个方框(下图编号5所示,其所代表的运算过程即下图编号13所示的等式),代表了这个式子。所以这就是紫色运算符所表示的是,它输入一个门值(下图编号6所示),新的候选值(下图编号7所示),这再有一个门值(下图编号8所示)和$c^{}$的旧值(下图编号9所示),所以它把这个(下图编号1所示)、这个(下图编号3所示)和这个(下图编号4所示)作为输入一起产生记忆细胞的新值$c^{}$,所以$c^{}$等于$a^{}$。如果你想,你也可以也把这个代入softmax或者其他预测$y^{}$的东西。

这就是GRU单元或者说是一个简化过的GRU单元,它的优点就是通过门决定,当你从左(上图编号10所示)到右扫描一个句子的时候,这个时机是要更新某个记忆细胞,还是不更新,不更新(上图编号11所示,中间$\Gamma_{u}=0$一直为0,表示一直不更新)直到你到你真的需要使用记忆细胞的时候(上图编号12所示),这可能在句子之前就决定了。因为sigmoid的值,现在因为门很容易取到0值,只要这个值是一个很大的负数,再由于数值上的四舍五入,上面这些门大体上就是0,或者说非常非常非常接近0。所以在这样的情况下,这个更新式子(上图编号13所示的等式)就会变成$c^{} = c^{}$,这非常有利于维持细胞的值。因为$\Gamma_{u}$很接近0,可能是0.000001或者更小,这就不会有梯度消失的问题了。因为$\Gamma_{u}$很接近0,这就是说$c^{}$几乎就等于$c^{}$,而且$c^{}$的值也很好地被维持了,即使经过很多很多的时间步(上图编号14所示)。这就是缓解梯度消失问题的关键,因此允许神经网络运行在非常庞大的依赖词上,比如说catwas单词即使被中间的很多单词分割开。

现在我想说下一些实现的细节,在这个我写下的式子中$c^{}$可以是一个向量(上图编号1所示),如果你有100维的隐藏的激活值,那么$c^{}$也是100维的,${\tilde{c}}^{}$也是相同的维度(${\tilde{c}}^{} =tanh(W_{c}\left\lbrack c^{},x^{} \right\rbrack +b_{c})$),$\Gamma_{u}$也是相同的维度($\Gamma_{u}= \sigma(W_{u}\left\lbrack c^{},x^{} \right\rbrack +b_{u})$),还有画在框中的其他值。这样的话“*”实际上就是元素对应的乘积($c^{} = \Gamma_{u}{\tilde{c}}^{} +\left( 1- \Gamma_{u} \right)c^{}$),所以这里的$\Gamma_{u}$:($\Gamma_{u}= \sigma(W_{u}\left\lbrack c^{},x^{} \right\rbrack +b_{u})$),即如果门是一个100维的向量,$\Gamma_{u}$也就100维的向量,里面的值几乎都是0或者1,就是说这100维的记忆细胞$c^{}$($c^{}=a^{}$上图编号1所示)就是你要更新的比特。

当然在实际应用中$\Gamma_{u}$不会真的等于0或者1,有时候它是0到1的一个中间值(上图编号5所示),但是这对于直观思考是很方便的,就把它当成确切的0,完全确切的0或者就是确切的1。元素对应的乘积做的就是告诉GRU单元哪个记忆细胞的向量维度在每个时间步要做更新,所以你可以选择保存一些比特不变,而去更新其他的比特。比如说你可能需要一个比特来记忆猫是单数还是复数,其他比特来理解你正在谈论食物,因为你在谈论吃饭或者食物,然后你稍后可能就会谈论“The cat was full.”,你可以每个时间点只改变一些比特。

你现在已经理解GRU最重要的思想了,幻灯片中展示的实际上只是简化过的GRU单元,现在来描述一下完整的GRU单元。

对于完整的GRU单元我要做的一个改变就是在我们计算的第一个式子中给记忆细胞的新候选值加上一个新的项,我要添加一个门$\Gamma_{r}$(下图编号1所示),你可以认为$r$代表相关性(relevance)。这个$\Gamma_{r}$门告诉你计算出的下一个$c^{}$的候选值${\tilde{c}}^{}$跟$c^{}$有多大的相关性。计算这个门$\Gamma_{r}$需要参数,正如你看到的这个,一个新的参数矩阵$W_{r}$,$\Gamma_{r}= \sigma(W_{r}\left\lbrack c^{},x^{} \right\rbrack + b_{r})$。

正如你所见,有很多方法可以来设计这些类型的神经网络,然后我们为什么有$\Gamma_{r}$?为什么不用上一张幻灯片里的简单的版本?这是因为多年来研究者们试验过很多很多不同可能的方法来设计这些单元,去尝试让神经网络有更深层的连接,去尝试产生更大范围的影响,还有解决梯度消失的问题,GRU就是其中一个研究者们最常使用的版本,也被发现在很多不同的问题上也是非常健壮和实用的。你可以尝试发明新版本的单元,只要你愿意。但是GRU是一个标准版本,也就是最常使用的。你可以想象到研究者们也尝试了很多其他版本,类似这样的但不完全是,比如我这里写的这个。然后另一个常用的版本被称为LSTM,表示长短时记忆网络,这个我们会在下节视频中讲到,但是GRULSTM是在神经网络结构中最常用的两个具体实例。

还有在符号上的一点,我尝试去定义固定的符号让这些概念容易理解,如果你看学术文章的话,你有的时候会看到有些人使用另一种符号$\tilde{x}$,$u$,$r$和$h$表示这些量。但我试着在GRULSTM之间用一种更固定的符号,比如使用更固定的符号$\Gamma$来表示门,所以希望这能让这些概念更好理解。

所以这就是GRU,即门控循环单元,这是RNN的其中之一。这个结构可以更好捕捉非常长范围的依赖,让RNN更加有效。然后我简单提一下其他常用的神经网络,比较经典的是这个叫做LSTM,即长短时记忆网络,我们在下节视频中讲解。

1.10 长短期记忆(LSTM(long short term memory)unit)

在上一个视频中你已经学了GRU(门控循环单元)。它能够让你可以在序列中学习非常深的连接。其他类型的单元也可以让你做到这个,比如LSTM即长短时记忆网络,甚至比GRU更加有效,让我们看看。

这里是上个视频中的式子,对于GRU我们有$a^{< t >} = c^{}$。

还有两个门:

更新门$\Gamma_{u}$(the update gate

相关门$\Gamma_{r}$(the relevance gate

${\tilde{c}}^{}$,这是代替记忆细胞的候选值,然后我们使用更新门$\Gamma_{u}$来决定是否要用${\tilde{c}}^{}$ 更新$c^{}$。

LSTM是一个比GRU更加强大和通用的版本,这多亏了 Sepp HochreiterJurgen Schmidhuber,感谢那篇开创性的论文,它在序列模型上有着巨大影响。我感觉这篇论文是挺难读懂的,虽然我认为这篇论文在深度学习社群有着重大的影响,它深入讨论了梯度消失的理论,我感觉大部分的人学到LSTM的细节是在其他的地方,而不是这篇论文。

这就是LSTM主要的式子(上图编号2所示),我们继续回到记忆细胞c上面来,使用${\tilde{c}}^{} = tanh(W_{c}\left\lbrack a^{},x^{} \right\rbrack +b_{c}$来更新它的候选值${\tilde{c}}^{}$(上图编号3所示)。注意了,在LSTM中我们不再有$a^{} = c^{}$的情况,这是现在我们用的是类似于左边这个式子(上图编号4所示),但是有一些改变,现在我们专门使用$a^{}$或者$a^{}$,而不是用$c^{}$,我们也不用$\Gamma_{r}$,即相关门。虽然你可以使用LSTM的变体,然后把这些东西(左边所示的GRU公式)都放回来,但是在更加典型的LSTM里面,我们先不那样做。

我们像以前那样有一个更新门$\Gamma_{u}$和表示更新的参数$W_{u}$,$\Gamma_{u}= \sigma(W_{u}\left\lbrack a^{},x^{} \right\rbrack +b_{u})$(上图编号5所示)。一个LSTM的新特性是不只有一个更新门控制,这里的这两项(上图编号6,7所示),我们将用不同的项来代替它们,要用别的项来取代$\Gamma_{u}$和$1-\Gamma_{u}$,这里(上图编号6所示)我们用$\Gamma_{u}$。

然后这里(上图编号7所示)用遗忘门(the forget gate),我们叫它$\Gamma_{f}$,所以这个$\Gamma_{f} =\sigma(W_{f}\left\lbrack a^{},x^{} \right\rbrack +b_{f})$(上图编号8所示);

然后我们有一个新的输出门,$\Gamma_{o} =\sigma(W_{o}\left\lbrack a^{},x^{} \right\rbrack +>b_{o})$(上图编号9所示);

于是记忆细胞的更新值$c^{} =\Gamma_{u}{\tilde{c}}^{} + \Gamma_{f}c^{}$(上图编号10所示);

所以这给了记忆细胞选择权去维持旧的值$c^{}$或者就加上新的值${\tilde{c}}^{}$,所以这里用了单独的更新门$\Gamma_{u}$和遗忘门$\Gamma_{f}$,

然后这个表示更新门($\Gamma_{u}= \sigma(W_{u}\left\lbrack a^{},x^{} \right\rbrack +b_{u})$上图编号5所示);

遗忘门($\Gamma_{f} =\sigma(W_{f}\left\lbrack a^{},x^{} \right\rbrack +b_{f})$上图编号8所示)和输出门(上图编号9所示)。

最后$a^{} = c^{}$的式子会变成$a^{} = \Gamma_{o}c^{}$。这就是*LSTM主要的式子了,然后这里(上图编号11所示)有三个门而不是两个,这有点复杂,它把门放到了和之前有点不同的地方。

再提一下,这些式子就是控制LSTM行为的主要的式子了(上图编号1所示)。像之前一样用图片稍微解释一下,先让我把图画在这里(上图编号2所示)。如果图片过于复杂,别担心,我个人感觉式子比图片好理解,但是我画图只是因为它比较直观。这个右上角的图的灵感来自于Chris Ola的一篇博客,标题是《理解LSTM网络》(Understanding LSTM Network),这里的这张图跟他博客上的图是很相似的,但关键的不同可能是这里的这张图用了$a^{}$和$x^{}$来计算所有门值(上图编号3,4所示),在这张图里是用$a^{}$, $x^{}$一起来计算遗忘门$\Gamma_{f}$的值,还有更新门$\Gamma_{u}$以及输出门$\Gamma_{o}$(上图编号4所示)。然后它们也经过tanh函数来计算${\tilde{c}}^{}$(上图编号5所示),这些值被用复杂的方式组合在一起,比如说元素对应的乘积或者其他的方式来从之前的$c^{}$(上图编号6所示)中获得$c^{}$(上图编号7所示)。

这里其中一个元素很有意思,如你在这一堆图(上图编号8所示的一系列图片)中看到的,这是其中一个,再把他们连起来,就是把它们按时间次序连起来,这里(上图编号9所示)输入$x^{<1>}$,然后$x^{<2>}$,$x^{<3>}$,然后你可以把这些单元依次连起来,这里输出了上一个时间的$a$,$a$会作为下一个时间步的输入,$c$同理。在下面这一块,我把图简化了一下(相对上图编号2所示的图有所简化)。然后这有个有意思的事情,你会注意到上面这里有条线(上图编号10所示的线),这条线显示了只要你正确地设置了遗忘门和更新门,LSTM是相当容易把$c^{<0>}$的值(上图编号11所示)一直往下传递到右边,比如$c^{<3>} = c^{<0>}$(上图编号12所示)。这就是为什么LSTMGRU非常擅长于长时间记忆某个值,对于存在记忆细胞中的某个值,即使经过很长很长的时间步。

这就是LSTM,你可能会想到这里和一般使用的版本会有些不同,最常用的版本可能是门值不仅取决于$a^{}$和$x^{}$,有时候也可以偷窥一下$c^{}$的值(上图编号13所示),这叫做“窥视孔连接”(peephole connection)。虽然不是个好听的名字,但是你想,“偷窥孔连接”其实意思就是门值不仅取决于$a^{}$和$x^{}$,也取决于上一个记忆细胞的值($c^{}$),然后“偷窥孔连接”就可以结合这三个门($\Gamma_{u}$、$\Gamma_{f}$、$\Gamma_{o}$)来计算了。

如你所见LSTM主要的区别在于一个技术上的细节,比如这(上图编号13所示)有一个100维的向量,你有一个100维的隐藏的记忆细胞单元,然后比如第50个$c^{}$的元素只会影响第50个元素对应的那个门,所以关系是一对一的,于是并不是任意这100维的$c^{}$可以影响所有的门元素。相反的,第一个$c^{}$的元素只能影响门的第一个元素,第二个元素影响对应的第二个元素,如此类推。但如果你读过论文,见人讨论“偷窥孔连接”,那就是在说$c^{}$也能影响门值。

LSTM前向传播图:

ST

STM_rn

LSTM反向传播计算:

门求偏导:

$d \Gamma_o^{\langle t \rangle} = da_{next}\tanh(c_{next}) \Gamma_o^{\langle t \rangle}*(1-\Gamma_o^{\langle t \rangle})\tag{1}$

$d\tilde c^{\langle t \rangle} = dc_{next}\Gamma_i^{\langle t \rangle}+ \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) i_t da_{next} \tilde c^{\langle t \rangle} * (1-\tanh(\tilde c)^2) \tag{2}$

$d\Gamma_u^{\langle t \rangle} = dc_{next}\tilde c^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) \tilde c^{\langle t \rangle} da_{next}\Gamma_u^{\langle t \rangle}*(1-\Gamma_u^{\langle t \rangle})\tag{3}$

$d\Gamma_f^{\langle t \rangle} = dc_{next}\tilde c_{prev} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) c_{prev} da_{next}\Gamma_f^{\langle t \rangle}*(1-\Gamma_f^{\langle t \rangle})\tag{4}$

参数求偏导 :

$ dW_f = d\Gamma_f^{\langle t \rangle} \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{5} $
$ dW_u = d\Gamma_u^{\langle t \rangle}
\begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{6} $
$ dW_c = d\tilde c^{\langle t \rangle} \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{7} $
$ dW_o = d\Gamma_o^{\langle t \rangle}
\begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{8}$

为了计算$db_f, db_u, db_c, db_o$ 需要各自对$d\Gamma_f^{\langle t \rangle}, d\Gamma_u^{\langle t \rangle}, d\tilde c^{\langle t \rangle}, d\Gamma_o^{\langle t \rangle}$ 求和。

最后,计算隐藏状态、记忆状态和输入的偏导数:

$ da_{prev} = W_f^Td\Gamma_f^{\langle t \rangle} + W_u^T d\Gamma_u^{\langle t \rangle}+ W_c^T d\tilde c^{\langle t \rangle} + W_o^T d\Gamma_o^{\langle t \rangle} \tag{9}$

$ dc_{prev} = dc_{next}\Gamma_f^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} (1- \tanh(c_{next})^2)\Gamma_f^{\langle t \rangle}da_{next} \tag{10}$
$ dx^{\langle t \rangle} = W_f^T
d\Gamma_f^{\langle t \rangle} + W_u^T d\Gamma_u^{\langle t \rangle}+ W_c^T d\tilde c_t + W_o^T * d\Gamma_o^{\langle t \rangle}\tag{11} $

这就是LSTM,我们什么时候应该用GRU?什么时候用LSTM?这里没有统一的准则。而且即使我先讲解了GRU,在深度学习的历史上,LSTM也是更早出现的,而GRU是最近才发明出来的,它可能源于Pavia在更加复杂的LSTM模型中做出的简化。研究者们在很多不同问题上尝试了这两种模型,看看在不同的问题不同的算法中哪个模型更好,所以这不是个学术和高深的算法,我才想要把这两个模型展示给你。

GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。

但是LSTM更加强大和灵活,因为它有三个门而不是两个。如果你想选一个使用,我认为LSTM在历史进程上是个更优先的选择,所以如果你必须选一个,我感觉今天大部分的人还是会把LSTM作为默认的选择来尝试。虽然我认为最近几年GRU获得了很多支持,而且我感觉越来越多的团队也正在使用GRU,因为它更加简单,而且还效果还不错,它更容易适应规模更加大的问题。

所以这就是LSTM,无论是GRU还是LSTM,你都可以用它们来构建捕获更加深层连接的神经网络。

Hochreiter S, Schmidhuber J. Long Short-Term Memory[J]. Neural Computation, 1997, 9(8):1735-1780.

1.11 双向循环神经网络(Bidirectional RNN

1.12 深层循环神经网络(Deep RNNs)

对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层,很少会看到这种网络堆叠到100层。但有一种会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉(上图编号1所示),然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测$y^{<1>}$。同样这里(上图编号2所示)也加上一个深层网络,然后预测$y^{<2>}$。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单元,在时间上连接,接着一个网络在后面接一个网络,当然$y^{<3>}$和$y^{<4>}$也一样,这是一个深层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。通常这些单元(上图编号3所示)没必要非是标准的RNN,最简单的RNN模型,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层,这个也就是在时间上连接了三个深层的循环层,你看不到多少深层的循环层,不像卷积神经网络一样有大量的隐含层。

这就是深层RNN的内容,从基本的RNN网络,基本的循环单元到GRULSTM,再到双向RNN,还有深层版的模型。这节课后,你已经可以构建很不错的学习序列的模型了。

第二周 自然语言处理与词嵌入(Natural Language Processing and Word Embeddings)

2.1 词汇表征(Word Representation)

2.2 使用词嵌入(Using Word Embeddings)

尽管one-hot向量很快计算,而学到的用于词嵌入的300维的向量会更加紧凑。

2.3 词嵌入的特性(Properties of Word Embeddings)

这是一系列你希望词嵌入可以捕捉的单词的特征表示,假如我提出一个问题,man如果对应woman,那么king应该对应什么?你们应该都能猜到king应该对应queen。能否有一种算法来自动推导出这种关系,下面就是实现的方法。

在之前我们谈到过用t-SNE算法来将单词可视化。t-SNE算法所做的就是把这些300维的数据用一种非线性的方式映射到2维平面上,可以得知t-SNE中这种映射很复杂而且很非线性。在进行t-SNE映射之后,你不能总是期望使等式成立的关系,会像左边那样成一个平行四边形,尽管在这个例子最初的300维的空间内你可以依赖这种平行四边形的关系来找到使等式成立的一对类比,通过t-SNE算法映射出的图像可能是正确的。但在大多数情况下,由于t-SNE的非线性映射,你就没法再指望这种平行四边形了,很多这种平行四边形的类比关系在t-SNE映射中都会失去原貌。

现在,再继续之前,我想再快速地列举一个最常用的相似度函数,这个最常用的相似度函数叫做余弦相似度。这是我们上个幻灯片所得到的等式(下图编号1所示),在余弦相似度中,假如在向量$u$和$v$之间定义相似度

在本节视频中,你见到了词嵌入是如何被用于类比推理的,可能你不会自己动手构建一个类比推理系统作为一项应用,不过希望在这些可学习的类特征的表示方式能够给你一些直观的感受。你还看知道了余弦相似度可以作为一种衡量两个词嵌入向量间相似度的办法,我们谈了许多有关这些嵌入的特性,以及如何使用它们。下节视频中,我们来讨论如何真正的学习这些词嵌入。

2.4 嵌入矩阵(Embedding Matrix)

,要记住的一件事就是我们的目标是学习一个嵌入矩阵$E$。在下节视频中你将会随机地初始化矩阵$E$,然后使用梯度下降法来学习这个300×10,000的矩阵中的各个参数,$E$乘以这个one-hot向量(上图编号1所示)会得到嵌入向量。再多说一点,当我们写这个等式(上图编号2所示)的时候,写出这些符号是很方便的,代表用矩阵$E$乘以one-hot向量$O_{j}$。但当你动手实现时,用大量的矩阵和向量相乘来计算它,效率是很低下的,因为one-hot向量是一个维度非常高的向量,并且几乎所有元素都是0,所以矩阵向量相乘效率太低,因为我们要乘以一大堆的0。所以在实践中你会使用一个专门的函数来单独查找矩阵$E$的某列,而不是用通常的矩阵乘法来做,但是在画示意图时(上图所示,即矩阵$E$乘以one-hot向量示意图),这样写比较方便。但是例如在Keras中就有一个嵌入层,然后我们用这个嵌入层更有效地从嵌入矩阵中提取出你需要的列,而不是对矩阵进行很慢很复杂的乘法运算。

2.5 学习词嵌入(Learning Word Embeddings)

研究者发现,如果你真想建立一个语言模型,用目标词的前几个单词作为上下文是常见做法(上图编号9所示)。但如果你的目标是学习词嵌入,那么你就可以用这些其他类型的上下文(上图编号10所示),它们也能得到很好的词嵌入。

2.6 Word2Vec

在本视频中你会见到 Word2Vec算法,这是一种简单而且计算时更加高效的方式来学习这种类型的嵌入,让我们来看看。

本视频中的大多数的想法来源于Tomas MikolovKai ChenGreg CorradoJeff Dean

Mikolov T, Chen K, Corrado G, et al. Efficient Estimation of Word Representations in Vector Space[J]. Computer Science, 2013.

假设在训练集中给定了一个这样的句子:“I want a glass of orange juice to go along with my cereal.”,在Skip-Gram模型中,我们要做的是抽取上下文和目标词配对,来构造一个监督学习问题。上下文不一定总是目标单词之前离得最近的四个单词,或最近的$n$个单词。我们要的做的是随机选一个词作为上下文词,比如选orange这个词,然后我们要做的是随机在一定词距内选另一个词,比如在上下文词前后5个词内或者前后10个词内,我们就在这个范围内选择目标词。可能你正好选到了juice作为目标词,正好是下一个词(表示orange的下一个词),也有可能你选到了前面第二个词,所以另一种配对目标词可以是glass,还可能正好选到了单词my作为目标词。

于是我们将构造一个监督学习问题,它给定上下文词,要求你预测在这个词正负10个词距或者正负5个词距内随机选择的某个目标词。显然,这不是个非常简单的学习问题,因为在单词orange的正负10个词距之间,可能会有很多不同的单词。但是构造这个监督学习问题的目标并不是想要解决这个监督学习问题本身,而是想要使用这个学习问题来学到一个好的词嵌入模型。

接下来说说模型的细节,我们继续假设使用一个10,000词的词汇表,有时训练使用的词汇表会超过一百万词。但我们要解决的基本的监督学习问题是学习一种映射关系,从上下文c,比如单词orange,到某个目标词,记为t,可能是单词juice或者单词glass或者单词my。延续上一张幻灯片的例子,在我们的词汇表中,orange是第6257个单词,juice是10,000个单词中的第4834个,这就是你想要的映射到输出$y$的输入$x$。

为了表示输入,比如单词orange,你可以先从one-hot向量开始,我们将其写作$O_{c}$,这就是上下文词的one-hot向量(上图编号1所示)。然后和你在上节视频中看到的类似,你可以拿嵌入矩阵$E$乘以向量$O_{c}$,然后得到了输入的上下文词的嵌入向量,于是这里$e_{c}=EO_{c}$。在这个神经网络中(上图编号2所示),我们将把向量$e_{c}$喂入一个softmax单元。我通常把softmax单元画成神经网络中的一个节点(上图编号3所示),这不是字母O,而是softmax单元,softmax单元要做的就是输出$\hat y$。然后我们再写出模型的细节,这是softmax模型(上图编号4所示),预测不同目标词的概率:

$Softmax:p\left( t \middle| c \right) = \frac{e^{\theta_{t}^{T}e_{c}}}{\sum_{j = 1}^{10,000}e^{\theta_{j}^{T}e_{c}}}$

这里$\theta_{t}$是一个与输出$t$有关的参数,即某个词$t$和标签相符的概率是多少。我省略了softmax中的偏差项,想要加上的话也可以加上。

最终softmax的损失函数就会像之前一样,我们用$y$表示目标词,我们这里用的$y$和$\hat y$都是用one-hot表示的,于是损失函数就会是:

$L\left( \hat y,y \right) = - \sum_{i = 1}^{10,000}{y_{i}\log \hat y_{i}}$

这是常用的softmax损失函数,$y$ 就是只有一个1其他都是0的one-hot向量,如果目标词是juice,那么第4834个元素就是1,其余是0(上图编号5所示)。类似的$\hat y$是一个从softmax单元输出的10,000维的向量,这个向量是所有可能目标词的概率。

总结一下,这大体上就是一个可以找到词嵌入的简化模型和神经网络(上图编号2所示),其实就是个softmax单元。矩阵$E$将会有很多参数,所以矩阵$E$有对应所有嵌入向量$e_{c}$的参数(上图编号6所示),softmax单元也有$\theta_{t}$的参数(上图编号3所示)。如果优化这个关于所有这些参数的损失函数,你就会得到一个较好的嵌入向量集,这个就叫做Skip-Gram模型。它把一个像orange这样的词作为输入,并预测这个输入词,从左数或从右数的某个词,预测上下文词的前面一些或者后面一些是什么词。

实际上使用这个算法会遇到一些问题,首要的问题就是计算速度。尤其是在softmax模型中,每次你想要计算这个概率,你需要对你词汇表中的所有10,000个词做求和计算,可能10,000个词的情况还不算太差。如果你用了一个大小为100,000或1,000,000的词汇表,那么这个分母的求和操作是相当慢的,实际上10,000已经是相当慢的了,所以扩大词汇表就更加困难了。

这里有一些解决方案,如分级(hierarchical)的softmax分类器和负采样Negative Sampling)。

在文献中你会看到的方法是使用一个分级(hierarchical)的softmax分类器,意思就是说不是一下子就确定到底是属于10,000类中的哪一类。想象如果你有一个分类器(上图编号1所示),它告诉你目标词是在词汇表的前5000个中还是在词汇表的后5000个词中,假如这个二分类器告诉你这个词在前5000个词中(上图编号2所示),然后第二个分类器会告诉你这个词在词汇表的前2500个词中,或者在词汇表的第二组2500个词中,诸如此类,直到最终你找到一个词准确所在的分类器(上图编号3所示),那么就是这棵树的一个叶子节点。像这样有一个树形的分类器,意味着树上内部的每一个节点都可以是一个二分类器,比如逻辑回归分类器,所以你不需要再为单次分类,对词汇表中所有的10,000个词求和了。实际上用这样的分类树,计算成本与词汇表大小的对数成正比(上图编号4所示),而不是词汇表大小的线性函数,这个就叫做分级softmax分类器。

我要提一下,在实践中分级softmax分类器不会使用一棵完美平衡的分类树或者说一棵左边和右边分支的词数相同的对称树(上图编号1所示的分类树)。实际上,分级的softmax分类器会被构造成常用词在顶部,然而不常用的词像durian会在树的更深处(上图编号2所示的分类树),因为你想更常见的词会更频繁,所以你可能只需要少量检索就可以获得常用单词像theof。然而你更少见到的词比如durian就更合适在树的较深处,因为你一般不需要到那样的深处,所以有不同的经验法则可以帮助构造分类树形成分级softmax分类器。所以这是你能在文献中见到的一个加速softmax分类的方法,但是我不会再花太多时间在这上面了,你可以从我在第一张幻灯片中提到的Tomas Mikolov等人的论文中参阅更多的细节,所以我不会再花更多时间讲这个了。因为在下个视频中,我们会讲到另一个方法叫做负采样,我感觉这个会更简单一点,对于加速softmax和解决需要在分母中对整个词汇表求和的问题也很有作用,下个视频中你会看到更多的细节。

但是在进入下个视频前,我想要你理解一个东西,那就是怎么对上下文c进行采样,一旦你对上下文c进行采样,那么目标词t就会在上下文c的正负10个词距内进行采样。但是你要如何选择上下文c?一种选择是你可以就对语料库均匀且随机地采样,如果你那么做,你会发现有一些词,像theofaandto诸如此类是出现得相当频繁的,于是你那么做的话,你会发现你的上下文到目标词的映射会相当频繁地得到这些种类的词,但是其他词,像orangeappledurian就不会那么频繁地出现了。你可能不会想要你的训练集都是这些出现得很频繁的词,因为这会导致你花大部分的力气来更新这些频繁出现的单词的$e_{c}$(上图编号1所示),但你想要的是花时间来更新像durian这些更少出现的词的嵌入,即$e_{\text{durian}}$。实际上词$p(c)$的分布并不是单纯的在训练集语料库上均匀且随机的采样得到的,而是采用了不同的分级来平衡更常见的词和不那么常见的词。

这就是Word2VecSkip-Gram模型,如果你读过我之前提到的论文原文,你会发现那篇论文实际上有两个不同版本的Word2Vec模型,Skip-Gram只是其中的一个,另一个叫做CBOW,即连续词袋模型(Continuous
Bag-Of-Words Model),它获得中间词两边的的上下文,然后用周围的词去预测中间的词,这个模型也很有效,也有一些优点和缺点。

总结下:CBOW是从原始语句推测目标字词;而Skip-Gram正好相反,是从目标字词推测出原始语句。CBOW对小型数据库比较合适,而Skip-Gram在大型语料中表现更好。 (下图左边为CBOW,右边为Skip-Gram

bo kipgra

而刚才讲的Skip-Gram模型,关键问题在于softmax这个步骤的计算成本非常昂贵,因为它需要在分母里对词汇表中所有词求和。通常情况下,Skip-Gram模型用到更多点。在下个视频中,我会展示给你一个算法,它修改了训练目标使其可以运行得更有效,因此它可以让你应用在一个更大的训练集上面,也可以学到更好的词嵌入。

2.7 负采样(Negative Sampling)

在上个视频中,你见到了Skip-Gram模型如何帮助你构造一个监督学习任务,把上下文映射到了目标词上,它如何让你学到一个实用的词嵌入。但是它的缺点就在于softmax计算起来很慢。在本视频中,你会看到一个改善过的学习问题叫做负采样,它能做到与你刚才看到的Skip-Gram模型相似的事情,但是用了一个更加有效的学习算法,让我们来看看这是怎么做到的。

我们在这个算法中要做的是构造一个新的监督学习问题,那么问题就是给定一对单词,比如orangejuice,我们要去预测这是否是一对上下文词-目标词(context-target)。

总结一下,生成这些数据的方式是我们选择一个上下文词(上图编号2所示),再选一个目标词(上图编号3所示),这(上图编号4所示)就是表的第一行,它给了一个正样本,上下文,目标词,并给定标签为1。然后我们要做的是给定几次,比如$K$次(上图编号5所示),我们将用相同的上下文词,再从字典中选取随机的词,kingbooktheof等,从词典中任意选取的词,并标记0,这些就会成为负样本(上图编号6所示)。出现以下情况也没关系,就是如果我们从字典中随机选到的词,正好出现在了词距内,比如说在上下文词orange正负10个词之内。

接下来我们将构造一个监督学习问题,其中学习算法输入$x$,输入这对词(上图编号7所示),要去预测目标的标签(上图编号8所示),即预测输出$y$。因此问题就是给定一对词,像orangejuice,你觉得它们会一起出现么?你觉得这两个词是通过对靠近的两个词采样获得的吗?或者你觉得我是分别在文本和字典中随机选取得到的?这个算法就是要分辨这两种不同的采样方式,这就是如何生成训练集的方法。

这个算法有一个重要的细节就是如何选取负样本,即在选取了上下文词orange之后,你如何对这些词进行采样生成负样本?一个办法是对中间的这些词进行采样,即候选的目标词,你可以根据其在语料中的经验频率进行采样,就是通过词出现的频率对其进行采样。但问题是这会导致你在liketheofand诸如此类的词上有很高的频率。另一个极端就是用1除以词汇表总词数,即$\frac{1}{\left|v\right|}$,均匀且随机地抽取负样本,这对于英文单词的分布是非常没有代表性的。所以论文的作者Mikolov等人根据经验,他们发现这个经验值的效果最好,它位于这两个极端的采样方法之间,既不用经验频率,也就是实际观察到的英文文本的分布,也不用均匀分布,他们采用以下方式:

$P\left( w_{i} \right) = \frac{f\left( w_{i} \right)^{\frac{3}{4}}}{\sum_{j = 1}^{10,000}{f\left( w_{j} \right)^{\frac{3}{4}}}}$

进行采样,所以如果$f(w_{i})$是观测到的在语料库中的某个英文词的词频,通过$\frac{3}{4}$次方的计算,使其处于完全独立的分布和训练集的观测分布两个极端之间。我并不确定这是否有理论证明,但是很多研究者现在使用这个方法,似乎也效果不错。

总结一下,你已经知道了在softmax分类器中如何学到词向量,但是计算成本很高。在这个视频中,你见到了如何通过将其转化为一系列二分类问题使你可以非常有效的学习词向量。如果你使用这个算法,你将可以学到相当好的词向量。当然和深度学习的其他领域一样,有很多开源的实现,当然也有预训练过的词向量,就是其他人训练过的然后授权许可发布在网上的,所以如果你想要在NLP问题上取得进展,去下载其他人的词向量是很好的方法,在此基础上改进。

2.8 GloVe 词向量(GloVe Word Vectors)

GloVe代表用词表示的全局变量(global vectors for word representation)。在此之前,我们曾通过挑选语料库中位置相近的两个词,列举出词对,即上下文和目标词,GloVe算法做的就是使其关系开始明确化。假定$X_$是单词$i$在单词$j$上下文中出现的次数,那么这里$i$和$j$就和$t$和$c$的功能一样,所以你可以认为$X_$等同于$X_$。你也可以遍历你的训练集,然后数出单词$i$在不同单词$j$上下文中出现的个数,单词$t$在不同单词$c$的上下文中共出现多少次。根据上下文和目标词的定义,你大概会得出$X_$等于$X_{ji}$这个结论。事实上,如果你将上下文和目标词的范围定义为出现于左右各10词以内的话,那么就会有一种对称关系。如果你对上下文的选择是,上下文总是目标词前一个单词的话,那么$X_$和$X_{ji}$就不会像这样对称了。不过对于GloVe算法,我们可以定义上下文和目标词为任意两个位置相近的单词,假设是左右各10词的距离,那么$X_$就是一个能够获取单词$i$和单词$j$出现位置相近时或是彼此接近的频率的计数器。

GloVe模型做的就是进行优化,我们将他们之间的差距进行最小化处理:

$\text{mini}\text{mize}\sum_{i = 1}^{10,000}{\sum_{j = 1}^{10,000}{f\left( X_ \right)\left( \theta_{i}^{T}e_{j} + b_{i} + b_{j}^{‘} - logX_ \right)^{2}}}$

其中$\theta_{i}^{T}e_{j}$,想一下$i$和$j$与$t$和$c$的功能一样,因此这就和你之前看的有些类似了,即$\theta_{t}^{T}e_{c}$。同时对于这个($\theta_{t}^{T}e_{c}$,下图编号1所示)来说,你想要知道的是告诉你这两个单词之间有多少联系,$t$和$c$之间有多紧密,$i$和$j$之间联系程度如何,换句话说就是他们同时出现的频率是多少,这是由这个$X_$影响的。然后,我们要做的是解决参数$\theta$和$e$的问题,然后准备用梯度下降来最小化上面的公式,你只想要学习一些向量,这样他们的输出能够对这两个单词同时出现的频率进行良好的预测。

现在一些附加的细节是如果$X_$是等于0的话,那么$log0$就是未定义的,是负无穷大的,所以我们想要对$X_$为0时进行求和,因此要做的就是添加一个额外的加权项$f\left(X_\right)$(上图编号2所示)。如果$X_$等于0的话,同时我们会用一个约定,即$0log0= 0$,这个的意思是如果$X_ =0$,先不要进行求和,所以这个$log0$项就是不相关项。上面的求和公式表明,这个和仅是一个上下文和目标词关系里连续出现至少一次的词对的和。$f\left(X_\right)$的另一个作用是,有些词在英语里出现十分频繁,比如说thisisofa等等,有些情况,这叫做停止词,但是在频繁词和不常用词之间也会有一个连续统(continuum)。不过也有一些不常用的词,比如durion,你还是想将其考虑在内,但又不像那些常用词这样频繁。因此,这个加权因子$f\left(X_\right)$就可以是一个函数,即使是像durion这样不常用的词,它也能给予大量有意义的运算,同时也能够给像thisisofa这样在英语里出现更频繁的词更大但不至于过分的权重。因此有一些对加权函数f的选择有着启发性的原则,就是既不给这些词(thisisofa)过分的权重,也不给这些不常用词(durion)太小的权值。如果你想要知道f是怎么能够启发性地完成这个功能的话,你可以看一下我之前的幻灯片里引用的GloVe算法论文。

最后,一件有关这个算法有趣的事是$\theta$和$e$现在是完全对称的,所以那里的$\theta_{i}$和$e_{j}$就是对称的。如果你只看数学式的话,他们($\theta_{i}$和$e_{j}$)的功能其实很相近,你可以将它们颠倒或者将它们进行排序,实际上他们都输出了最佳结果。因此一种训练算法的方法是一致地初始化$\theta$和$e$,然后使用梯度下降来最小化输出,当每个词都处理完之后取平均值,所以,给定一个词$w$,你就会有$e_{w}^{(final)}= \frac{e_{w} +\theta_{w}}{2}$。因为$\theta$和$e$在这个特定的公式里是对称的,而不像之前视频里我们了解的模型,$\theta$和$e$功能不一样,因此也不能像那样取平均。

这就是GloVe算法的内容,我认为这个算法的一个疑惑之处是如果你看着这个等式,它实在是太简单了,对吧?仅仅是最小化,像这样的一个二次代价函数(上图编号3所示)是怎么能够让你学习有意义的词嵌入的呢?但是结果证明它确实有效,发明者们发明这个算法的过程是他们以历史上更为复杂的算法,像是newer language模型,以及之后的Word2VecSkip-Gram模型等等为基础,同时希望能够简化所有之前的算法才发明的。

在我们总结词嵌入学习算法之前,有一件更优先的事,我们会简单讨论一下。就是说,我们以这个特制的表格作为例子来开始学习词向量,我们说,第一行的嵌入向量是来表示Gender的,第二行是来表示Royal的,然后是是Age,在之后是Food等等。但是当你在使用我们了解过的算法的一种来学习一个词嵌入时,例如我们之前的幻灯片里提到的GloVe算法,会发生一件事就是你不能保证嵌入向量的独立组成部分是能够理解的,为什么呢?

假设说有个空间,里面的第一个轴(上图编号1所示)是Gender,第二个轴(上图编号2所示)是Royal,你能够保证的是第一个嵌入向量对应的轴(上图编号3所示)是和这个轴(上面提到的第一和第二基轴,编号1,2所示)有联系的,它的意思可能是GenderRoyalAgeFood。具体而言,这个学习算法会选择这个(上图编号3所示)作为第一维的轴,所以给定一些上下文词,第一维可能是这个轴(上图编号3所示),第二维也许是这个(上图编号4所示),或者它可能不是正交的,它也可能是第二个非正交轴(上图编号5所示),它可以是你学习到的词嵌入中的第二部分。当我们看到这个(上图编号6所示)的时候,如果有某个可逆矩阵$A$,那么这项(上图编号6所示)就可以简单地替换成$\left(A\theta_{i} \right)^{T}(A^{- T}e_{j})$,因为我们将其展开:

$\left( A\theta_{i} \right)^{T}\left( A^{- T}e_{j} \right) = \theta_{i}^{T}A^{T}A^{- T}e_{j} = \theta_{i}^{T}e_{j}$

不必担心,如果你没有学过线性代数的话会,和这个算法一样有一个简单证明过程。你不能保证这些用来表示特征的轴能够等同于人类可能简单理解的轴,具体而言,第一个特征可能是个GenderRoyaAgeFood CostSize的组合,它也许是名词或是一个行为动词和其他所有特征的组合,所以很难看出独立组成部分,即这个嵌入矩阵的单行部分,然后解释出它的意思。尽管有这种类型的线性变换,这个平行四边形映射也说明了我们解决了这个问题,当你在类比其他问题时,这个方法也是行得通的。因此尽管存在特征量潜在的任意线性变换,你最终还是能学习出解决类似问题的平行四边形映射。

2.9 情感分类(Sentiment Classification)

2.10 词嵌入除偏(Debiasing Word Embeddings)

第三周 序列模型和注意力机制(Sequence models & Attention mechanism

在这一周,你将会学习seq2seqsequence to sequence)模型,从机器翻译到语音识别,它们都能起到很大的作用,从最基本的模型开始。之后你还会学习集束搜索(Beam search)和注意力模型(Attention Model),一直到最后的音频模型,比如语音。

还有一个与此类似的结构被用来做图像描述,给出一张图片,比如这张猫的图片(上图编号1所示),它能自动地输出该图片的描述,一只猫坐在椅子上,那么你如何训练出这样的网络?通过输入图像来输出描述,像这个句子一样。

方法如下,在之前的卷积网络课程中,你已经知道了如何将图片输入到卷积神经网络中,比如一个预训练的AlexNet结构(上图编号2方框所示),然后让其学习图片的编码,或者学习图片的一系列特征。现在幻灯片所展示的就是AlexNet结构,我们去掉最后的softmax单元(上图编号3所示),这个预训练的AlexNet结构会给你一个4096维的特征向量,向量表示的就是这只猫的图片,所以这个预训练网络可以是图像的编码网络。现在你得到了一个4096维的向量来表示这张图片,接着你可以把这个向量输入到RNN中(上图编号4方框所示),RNN要做的就是生成图像的描述,每次生成一个单词,这和我们在之前将法语译为英语的机器翻译中看到的结构很像,现在你输入一个描述输入的特征向量,然后让网络生成一个输出序列,或者说一个一个地输出单词序列。

3.2 选择最可能的句子(Picking the most likely sentence)

条件语言模型(conditional language model)。相比语言模型,输出任意句子的概率,翻译模型会输出句子的英文翻译(上图编号5所示),这取决于输入的法语句子(上图编号6所示)。换句话说,你将估计一个英文翻译的概率,比如估计这句英语翻译的概率,”Jane is visiting Africa in September.“,这句翻译是取决于法语句子,”Jane visite I’Afrique en septembre.“,这就是英语句子相对于输入的法语句子的可能性,所以它是一个条件语言模型。

不过在了解束搜索之前,你可能会问一个问题,为什么不用贪心搜索(Greedy Search)呢?贪心搜索是一种来自计算机科学的算法,生成第一个词的分布以后,它将会根据你的条件语言模型挑选出最有可能的第一个词进入你的机器翻译模型中,在挑选出第一个词之后它将会继续挑选出最有可能的第二个词,然后继续挑选第三个最有可能的词,这种算法就叫做贪心搜索,但是你真正需要的是一次性挑选出整个单词序列,从$y^{<1>}$、$y^{<2>}$到$y^{<T_{y}>}$来使得整体的概率最大化。所以这种贪心算法先挑出最好的第一个词,在这之后再挑最好的第二词,然后再挑第三个,这种方法其实并不管用,为了证明这个观点,我们来考虑下面两种翻译。

3.3 集束搜索(Beam Search)

贪婪算法只会挑出最可能的那一个单词,然后继续。而集束搜索则会考虑多个选择,集束搜索算法会有一个参数B,叫做集束宽(beam width)。在这个例子中我把这个集束宽设成3,这样就意味着集束搜索不会只考虑一个可能结果,而是一次会考虑3个,比如对第一个单词有不同选择的可能性,最后找到injaneseptember,是英语输出的第一个单词的最可能的三个选项,然后集束搜索算法会把结果存到计算机内存里以便后面尝试用这三个词。如果集束宽设的不一样,如果集束宽这个参数是10的话,那么我们跟踪的不仅仅3个,而是10个第一个单词的最可能的选择。所以要明白,为了执行集束搜索的第一步,你需要输入法语句子到编码网络,然后会解码这个网络,这个softmax层(上图编号3所示)会输出10,000个概率值,得到这10,000个输出的概率值,取前三个存起来。

3.4 改进集束搜索(Refinements to Beam Search)

长度归一化(Length normalization)就是对束搜索算法稍作调整的一种方式,帮助你得到更好的结果,下面介绍一下它。

这些符号看起来可能比实际上吓人,但这就是我们之前见到的乘积概率(the product probabilities)。如果计算这些,其实这些概率值都是小于1的,通常远小于1。很多小于1的数乘起来,会得到很小很小的数字,会造成数值下溢(numerical underflow)。数值下溢就是数值太小了,导致电脑的浮点表示不能精确地储存,因此在实践中,我们不会最大化这个乘积,而是取$log$值。如果在这加上一个$log$,最大化这个$log$求和的概率值,在选择最可能的句子$y$时,你会得到同样的结果。所以通过取$log$,我们会得到一个数值上更稳定的算法,不容易出现四舍五入的误差,数值的舍入误差(rounding errors)或者说数值下溢(numerical underflow)。因为$log$函数它是严格单调递增的函数,最大化$P(y)$,因为对数函数,这就是$log$函数,是严格单调递增的函数,所以最大化$logP(y|x)$和最大化$P(y|x)$结果一样。如果一个$y$值能够使前者最大,就肯定能使后者也取最大。所以实际工作中,我们总是记录概率的对数和(the sum of logs of the probabilities),而不是概率的乘积(the production of probabilities)。

对于目标函数(this objective function),还可以做一些改变,可以使得机器翻译表现的更好。如果参照原来的目标函数(this original objective),如果有一个很长的句子,那么这个句子的概率会很低,因为乘了很多项小于1的数字来估计句子的概率。所以如果乘起来很多小于1的数字,那么就会得到一个更小的概率值,所以这个目标函数有一个缺点,它可能不自然地倾向于简短的翻译结果,它更偏向短的输出,因为短句子的概率是由更少数量的小于1的数字乘积得到的,所以这个乘积不会那么小。顺便说一下,这里也有同样的问题,概率的$log$值通常小于等于1,实际上在$log$的这个范围内,所以加起来的项越多,得到的结果越负,所以对这个算法另一个改变也可以使它表现的更好,也就是我们不再最大化这个目标函数了,我们可以把它归一化,通过除以翻译结果的单词数量(normalize this by the number of words in your translation)。这样就是取每个单词的概率对数值的平均了,这样很明显地减少了对输出长的结果的惩罚(this significantly reduces the penalty for outputting longer translations.)。

在实践中,有个探索性的方法,相比于直接除$T_{y}$,也就是输出句子的单词总数,我们有时会用一个更柔和的方法(a softer approach),在$T_{y}$上加上指数$a$,$a$可以等于0.7。如果$a$等于1,就相当于完全用长度来归一化,如果$a$等于0,$T_{y}$的0次幂就是1,就相当于完全没有归一化,这就是在完全归一化和没有归一化之间。$a$就是算法另一个超参数(hyper parameter),需要调整大小来得到最好的结果。不得不承认,这样用$a$实际上是试探性的,它并没有理论验证。但是大家都发现效果很好,大家都发现实践中效果不错,所以很多人都会这么做。你可以尝试不同的$a$值,看看哪一个能够得到最好的结果。

总结一下如何运行束搜索算法。当你运行束搜索时,你会看到很多长度等于1的句子,很多长度等于2的句子,很多长度等于3的句子,等等。可能运行束搜索30步,考虑输出的句子可能达到,比如长度30。因为束宽为3,你会记录所有这些可能的句子长度,长度为1、2、 3、 4 等等一直到30的三个最可能的选择。然后针对这些所有的可能的输出句子,用这个式子(上图编号1所示)给它们打分,取概率最大的几个句子,然后对这些束搜索得到的句子,计算这个目标函数。最后从经过评估的这些句子中,挑选出在归一化的$log$ 概率目标函数上得分最高的一个(you pick the one that achieves the highest value on this normalized log probability objective.),有时这个也叫作归一化的对数似然目标函数a normalized log likelihood objective)。这就是最终输出的翻译结果,这就是如何实现束搜索。

最后还有一些实现的细节,如何选择束宽BB越大,你考虑的选择越多,你找到的句子可能越好,但是B越大,你的算法的计算代价越大,因为你要把很多的可能选择保存起来。最后我们总结一下关于如何选择束宽B的一些想法。接下来是针对或大或小的B各自的优缺点。如果束宽很大,你会考虑很多的可能,你会得到一个更好的结果,因为你要考虑很多的选择,但是算法会运行的慢一些,内存占用也会增大,计算起来会慢一点。而如果你用小的束宽,结果会没那么好,因为你在算法运行中,保存的选择更少,但是你的算法运行的更快,内存占用也小。在前面视频里,我们例子中用了束宽为3,所以会保存3个可能选择,在实践中这个值有点偏小在产品中,经常可以看到把束宽设到10,我认为束宽为100对于产品系统来说有点大了,这也取决于不同应用。但是对科研而言,人们想压榨出全部性能,这样有个最好的结果用来发论文,也经常看到大家用束宽为1000或者3000,这也是取决于特定的应用和特定的领域。在你实现你的应用时,尝试不同的束宽的值,当B很大的时候,性能提高会越来越少。对于很多应用来说,从束宽1,也就是贪心算法,到束宽为3、到10,你会看到一个很大的改善。但是当束宽从1000增加到3000时,效果就没那么明显了。对于之前上过计算机科学课程的同学来说,如果你熟悉计算机科学里的搜索算法(computer science search algorithms), 比如广度优先搜索(BFS, Breadth First Search algorithms),或者深度优先搜索(DFS, Depth First Search),你可以这样想束搜索,不像其他你在计算机科学算法课程中学到的算法一样。如果你没听说过这些算法也不要紧,但是如果你听说过广度优先搜索和深度优先搜索,不同于这些算法,这些都是精确的搜索算法(exact search algorithms),束搜索运行的更快,但是不能保证一定能找到argmax的准确的最大值。如果你没听说过广度优先搜索和深度优先搜索,也不用担心,这些对于我们的目标也不重要,如果你听说过,这就是束搜索和其他算法的关系。

好,这就是束搜索。这个算法广泛应用在多产品系统或者许多商业系统上,在深度学习系列课程中的第三门课中,我们讨论了很多关于误差分析(error analysis)的问题。事实上在束搜索上做误差分析是我发现的最有用的工具之一。有时你想知道是否应该增大束宽,我的束宽是否足够好,你可以计算一些简单的东西来指导你需要做什么,来改进你的搜索算法。

3.5 集束搜索的误差分析(Error analysis in beam search)

束搜索算法是一种近似搜索算法(an approximate search algorithm),也被称作启发式搜索算法(a heuristic search algorithm),它不总是输出可能性最大的句子,它仅记录着B为前3或者10或是100种可能。那么如果束搜索算法出现错误会怎样呢?

记住,我们是要计算$P(y^*|x)$和$P(\hat y|x)$,然后比较这两个哪个更大,所以就会有两种情况。

第一种情况,RNN模型的输出结果$P(y^|x)$ 大于$P(\hat y|x)$,这意味着什么呢?
束搜索算法选择了$\hat y$ ,对吧?
你得到$\hat y$的方式是,你用一个RNN模型来计算$P(y|x)$,然后束搜索算法做的就是尝试寻找使$P(y|x)$最大的$y$,不过在这种情况下,相比于$\hat y$,$y^
$的值更$P(y|x)$大,因此你能够得出束搜索算法实际上不能够给你一个能使$P(y|x)$最大化的$y$值,因为束搜索算法的任务就是寻找一个$y$的值来使这项更大,但是它却选择了$\hat y$,而$y^*$实际上能得到更大的值。因此这种情况下你能够得出是束搜索算法出错了。那另一种情况是怎样的呢?

第二种情况是$P(y^|x)$小于或等于$P(\hat y|x)$对吧?这两者之中总有一个是真的。情况1或是情况2总有一个为真。情况2你能够总结出什么呢?
在我们的例子中,$y^
$ 是比 $\hat y$更好的翻译结果,不过根据RNN模型的结果,$P(y^)$ 是小于$P(\hat y)$的,也就是说,相比于$\hat y$,$y^$成为输出的可能更小。因此在这种情况下,看来是RNN模型出了问题。同时可能值得在RNN模型上花更多时间。这里我少讲了一些有关长度归一化(length normalizations)的细节。这里我略过了有关长度归一化的细节,如果你用了某种长度归一化,那么你要做的就不是比较这两种可能性大小,而是比较长度归一化后的最优化目标函数值。不过现在先忽略这种复杂的情况。第二种情况表明虽然$y^$是一个更好的翻译结果,RNN模型却赋予它更低的可能性,是*RNN模型出现了问题。

所以误差分析过程看起来就像下面这样。你先遍历开发集,然后在其中找出算法产生的错误,这个例子中,假如说$P(y^|x)$的值为2 x 10-10,而$P(\hat y|x)$的值为 1 x10-10,根据上页幻灯片中的逻辑关系,这种情况下我们得知束搜索算法实际上选择了比$y^$可能性更低的$\hat y$,因此我会说束搜索算法出错了。我将它缩写为B。接着你继续遍历第二个错误,再来看这些可能性。也许对于第二个例子来说,你认为是RNN模型出现了问题,我会用缩写R来代表RNN。再接着你遍历了更多的例子,有时是束搜索算法出现了问题,有时是模型出现了问题,等等。通过这个过程,你就能够执行误差分析,得出束搜索算法和RNN模型出错的比例是多少。有了这样的误差分析过程,你就可以对开发集中每一个错误例子,即算法输出了比人工翻译更差的结果的情况,尝试确定这些错误,是搜索算法出了问题,还是生成目标函数(束搜索算法使之最大化)的RNN模型出了问题。并且通过这个过程,你能够发现这两个部分中哪个是产生更多错误的原因,并且只有当你发现是束搜索算法造成了大部分错误时,才值得花费努力增大集束宽度。相反地,如果你发现是RNN模型出了更多错,那么你可以进行更深层次的分析,来决定是需要增加正则化还是获取更多的训练数据,抑或是尝试一个不同的网络结构,或是其他方案。你在第三门课中,了解到各种技巧都能够应用在这里。

这就是束搜索算法中的误差分析,我认为这个特定的误差分析过程是十分有用的,它可以用于分析近似最佳算法(如束搜索算法),这些算法被用来优化学习算法(例如序列到序列模型/RNN)输出的目标函数。

3.6 Bleu 得分(选修)(Bleu Score (optional))

BLEU得分对于机器翻译来说,具有革命性的原因是因为它有一个相当不错的虽然不是完美的但是非常好的单一实数评估指标,因此它加快了整个机器翻译领域的进程,我希望这节视频能够让你了解BLEU得分是如何操作的。实践中,很少人会从零实现一个BLEU得分(implement a BLEU score from scratch),有很多开源的实现结果,你可以下载下来然后直接用来评估你的系统。不过今天,BLEU得分被用来评估许多生成文本的系统(systems that generate text),比如说机器翻译系统(machine translation systems),也有我之前简单提到的图像描述系统(image captioning systems)。也就是说你会用神经网络来生成图像描述,然后使用BLEU得分来看一下,结果在多大程度上与参考描述或是多个人工完成的参考描述内容相符。BLEU得分是一个有用的单一实数评估指标,用于评估生成文本的算法,判断输出的结果是否与人工写出的参考文本的含义相似。不过它并没有用于语音识别(speech recognition)。因为在语音识别当中,通常只有一个答案,你可以用其他的评估方法,来看一下你的语音识别结果,是否十分相近或是字字正确(pretty much, exactly word for word correct)。不过在图像描述应用中,对于同一图片的不同描述,可能是同样好的。或者对于机器翻译来说,有多个一样好的翻译结果,BLEU得分就给了你一个能够自动评估的方法,帮助加快算法开发进程。说了这么多,希望你明白了BLEU得分是怎么运行的。

3.7 注意力模型直观理解(Attention Model Intuition)

3.8注意力模型(Attention Model)

注意力模型如何让一个神经网络只注意到一部分的输入句子。当它在生成句子的时候,更像人类翻译

跟上个视频一样,我们先假定有一个输入句子,并使用双向的RNN,或者双向的GRU或者双向的LSTM,去计算每个词的特征。实际上GRULSTM经常应用于这个,可能LSTM更经常一点。对于前向传播(the forward occurrence),你有第一个时间步的前向传播的激活值(a forward occurrence first time step),第一个时间步后向传播的激活值,后向的激活值,以此类推。他们一共向前了五个时间步,也向后了五个时间步,技术上我们把这里设置为0。我们也可以后向传播6次,设一个都是0的因子,实际上就是个都是0的因子。为了简化每个时间步的记号,即使你在双向RNN已经计算了前向的特征值和后向的特征值,我就用$a^{}$来一起表示这些联系。所以$a^{}$就是时间步$t$上的特征向量。但是为了保持记号的一致性,我们用第二个,也就是$t’$,实际上我将用$t’$来索引法语句子里面的词。接下来我们只进行前向计算,就是说这是个单向的RNN,用状态$S$表示生成翻译。所以第一个时间步,它应该生成$y^{<1>}$,当你输入上下文$C$的时候就会这样,如果你想用时间来索引它,你可以写$C^{<1>}$,但有时候我就写个$C$,就是没有上标的$C$,这个会取决于注意力参数,即$a^{<1,1>}$,$a^{<1,2>}$以此类推,告诉我们应该花多少注意力。同样的,这个$a$参数告诉我们上下文有多少取决于我们得到的特征,或者我们从不同时间步中得到的激活值。所以我们定义上下文的方式实际上来源于被注意力权重加权的不同时间步中的特征值。于是更公式化的注意力权重将会满足非负的条件,所以这就是个0或正数,它们加起来等于1。我们等会会见到我们如何确保这个成立,我们将会有上下文,或者说在$t=1$时的上下文,我会经常省略上标,这就会变成对$t’$的求和。这个权重的所有的$t’$值,加上这些激活值。所以这里的这项(上图编号1所示)就是注意力权重,这里的这项(上图编号2)来自于这里(上图编号3),于是$a^{<t,t’>}$就是$y^{}$应该在$t’$时花在$a$上注意力的数量。换句话来说,当你在$t$处生成输出词,你应该花多少注意力在第$t’$个输入词上面,这是生成输出的其中一步。然后下一个时间步,你会生成第二个输出。于是相似的,你现在有了一个新的注意力权重集,再找到一个新的方式将它们相加,这就产生了一个新的上下文,这个也是输入,且允许你生成第二个词。只有现在才用这种方式相加,它会变成第二个时间步的上下文。即对$t’$的$a^{<2,t’>}$进行求和,于是使用这些上下文向量,$C^{<1>}$写到这里,$C^{<2>}$也同理。这里的神经网络看起来很像相当标准的RNN序列,这里有着上下文向量作为输出,我们可以一次一个词地生成翻译,我们也定义了如何通过这些注意力权重和输入句子的特征值来计算上下文向量。剩下唯一要做的事情就是定义如何计算这些注意力权重。让我们下张幻灯片看看。

回忆一下$a^{<t,t’>}$,是你应该花费在$a^{<t’>}$上的注意力的数量,当你尝试去生成第$t$个输出的翻译词,让我们先把式子写下来,再讨论它是怎么来的。这个式子你可以用来计算$a^{<t,t’>}$,在此之前我们要先计算$e^{<t,t’>}$,关键要用softmax,来确保这些权重加起来等于1。如果你对$t’$求和,比如每一个固定的$t$值,这些加起来等于1。如果你对$t’$求和,然后优先使用softmax,确保这些值加起来等于1。

现在我们如何计算这些$e$项,一种我们可以用的方式是用下面这样的小的神经网络,于是$s^{}$就是神经网络在上个时间步的状态,于是这里我们有一个神经网络,如果你想要生成$y^{}$,那么$s^{}$就是上一时间步的隐藏状态,即$s^{}$。这是给小神经网络的其中一个输入,也就是在神经网络中的一个隐藏层,因为你需要经常计算它们,然后$a^{<t’>}$,即上个时间步的的特征是另一个输入。直观来想就是,如果你想要决定要花多少注意力在$t’$的激活值上。于是,似乎它会很大程度上取决于你上一个时间步的的隐藏状态的激活值。你还没有当前状态的激活值,因为上下文会输入到这里,所以你还没计算出来,但是看看你生成上一个翻译的RNN的隐藏状态,然后对于每一个位置,每一个词都看向他们的特征值,这看起来很自然,即$a^{<t,t’>}$和$e^{<t,t’>}$应该取决于这两个量。但是我们不知道具体函数是什么,所以我们可以做的事情就是训练一个很小的神经网络,去学习这个函数到底是什么。相信反向传播算法,相信梯度下降算法学到一个正确的函数。这表示,如果你应用这整个的模型,然后用梯度下降来训练它,这是可行的。这个小型的神经网络做了一件相当棒的事情,告诉你$y^{}$应该花多少注意力在$a^{}$上面,然后这个式子确保注意力权重加起来等于1,于是当你持续地一次生成一个词,这个神经网络实际上会花注意力在右边的这个输入句子上,它会完全自动的通过梯度下降来学习。

这个算法的一个缺点就是它要花费三次方的时间,就是说这个算法的复杂是$O(n3)$的,如果你有$T_x$个输入单词和$T_y$个输出单词,于是注意力参数的总数就会是$T_x\times T_y$,所以这个算法有着三次方的消耗。但是在机器翻译的应用上,输入和输出的句子一般不会太长,可能三次方的消耗是可以接受,但也有很多研究工作,尝试去减少这样的消耗。那么讲解注意想法在机器翻译中的应用,就到此为止了。虽然没有讲到太多的细节,但这个想法也被应用到了其他的很多问题中去了,比如图片加标题(image captioning),图片加标题就是看一张图,写下这张图的标题。底下的这篇论文来源于Kevin ChuJimmy Barr, Ryan Kiros, Kelvin Shaw, Aaron Korver, Russell Zarkutnov, Virta Zemo, 和 Andrew Benjo。他们也显示了你可以有一个很相似的结构看图片,然后,当你在写图片标题的时候,一次只花注意力在一部分的图片上面。如果你感兴趣,那么我鼓励你,也去看看这篇论文,做一些编程练习。

因为机器翻译是一个非常复杂的问题,在之前的练习中,你应用了注意力,在日期标准化的问题(the date normalization problem)上面,问题输入了像这样的一个日期,这个日期实际上是阿波罗登月的日期,把它标准化成标准的形式,或者这样的日期。用一个序列的神经网络,即序列模型去标准化到这样的形式,这个日期实际上是威廉·莎士比亚的生日。一般认为是这个日期正如你之前联系中见到的,你可以训练一个神经网络,输入任何形式的日期,生成标准化的日期形式。其他可以做的有意思的事情是看看可视化的注意力权重(the visualizations of the attention weights)。这个一个机器翻译的例子,这里被画上了不同的颜色,不同注意力权重的大小,我不想在这上面花太多时间,但是你可以发现,对应的输入输出词,你会发现注意力权重,会变高,因此这显示了当它生成特定的输出词时通常会花注意力在输入的正确的词上面,包括学习花注意在哪。
在注意力模型中,使用反向传播时, 什么时候学习完成。

3.9语音识别(Speech recognition)

现今,最令人振奋的发展之一,就是seq2seq模型(sequence-to-sequence models)在语音识别方面准确性有了很大的提升。这门课程已经接近尾声,现在我想通过剩下几节视频,来告诉你们,seq2seq模型是如何应用于音频数据的(audio data),比如语音(the speech)。

什么是语音视频问题呢?现在你有一个音频片段$x$(an audio clip,x),你的任务是自动地生成文本$y$。现在有一个音频片段,画出来是这样,该图的横轴是时间。一个麦克风的作用是测量出微小的气压变化,现在你之所以能听到我的声音,是因为你的耳朵能够探测到这些微小的气压变化,它可能是由你的扬声器或者耳机产生的,也就是像图上这样的音频片段,气压随着时间而变化。假如这个我说的音频片段的内容是:”the quick brown fox“(敏捷的棕色狐狸),这时我们希望一个语音识别算法(a speech recognition algorithm),通过输入这段音频,然后输出音频的文本内容。考虑到人的耳朵并不会处理声音的原始波形,而是通过一种特殊的物理结构来测量这些,不同频率和强度的声波。音频数据的常见预处理步骤,就是运行这个原始的音频片段,然后生成一个声谱图(a spectrogram),就像这样。同样地,横轴是时间,纵轴是声音的频率(frequencies),而图中不同的颜色,显示了声波能量的大小(the amount of energy),也就是在不同的时间和频率上这些声音有多大。通过这样的声谱图,或者你可能还听过人们谈到过伪空白输出(the false blank outputs),也经常应用于预处理步骤,也就是在音频被输入到学习算法之前,而人耳所做的计算和这个预处理过程非常相似。语音识别方面,最令人振奋的趋势之一就是曾经有一段时间,语音识别系统是用音位(phonemes)来构建的,也就是人工设计的基本单元(hand-engineered basic units of cells),如果用音位来表示”the quick brown fox“,我这里稍微简化一些,”the“含有”th“和”e“的音,而”quick“有”k“ “w“ “i“ “k“的音,语音学家过去把这些音作为声音的基本单元写下来,把这些语音分解成这些基本的声音单元,而”brown“不是一个很正式的音位,因为它的音写起来比较复杂,不过语音学家(linguists)们认为用这些基本的音位单元(basic units of sound called phonemes)来表示音频(audio),是做语音识别最好的办法。不过在end-to-end模型中,我们发现这种音位表示法(phonemes representations)已经不再必要了,而是可以构建一个系统,通过向系统中输入音频片段(audio clip),然后直接输出音频的文本(a transcript),而不需要使用这种人工设计的表示方法。使这种方法成为可能的一件事就是用一个很大的数据集,所以语音识别的研究数据集可能长达300个小时,在学术界,甚至3000小时的文本音频数据集,都被认为是合理的大小。大量的研究,大量的论文所使用的数据集中,有几千种不同的声音,而且,最好的商业系统现在已经训练了超过1万个小时的数据,甚至10万个小时,并且它还会继续变得更大。在文本音频数据集中(Transcribe audio data sets)同时包含$x$和$y$,通过深度学习算法大大推进了语音识别的进程。那么,如何建立一个语音识别系统呢?

在上一节视频中,我们谈到了注意力模型,所以,一件你能做的事就是在横轴上,也就是在输入音频的不同时间帧上,你可以用一个注意力模型,来输出文本描述,如”the quick brown fox“,或者其他语音内容。

还有一种效果也不错的方法,就是用CTC损失函数(CTC cost)来做语音识别。CTC就是Connectionist Temporal Classification,它是由Alex GravesSantiago Fernandes, Faustino Gomez、和Jürgen Schmidhuber提出的。(Graves A, Gomez F. Connectionist temporal classification:labelling unsegmented sequence data with recurrent neural networks[C]// International Conference on Machine Learning. ACM, 2006:369-376.

算法思想如下:

假设语音片段内容是某人说:”the quick brown fox“,这时我们使用一个新的网络,结构像这个样子,这里输入$x$和输出$y$的数量都是一样的,因为我在这里画的,只是一个简单的单向RNN结构。然而在实际中,它有可能是双向的LSTM结构,或者双向的GIU结构,并且通常是很深的模型。但注意一下这里时间步的数量,它非常地大。在语音识别中,通常输入的时间步数量(the number of input time steps)要比输出的时间步的数量(the number of output time steps)多出很多。举个例子,比如你有一段10秒的音频,并且特征(features)是100赫兹的,即每秒有100个样本,于是这段10秒的音频片段就会有1000个输入,就是简单地用100赫兹乘上10秒。所以有1000个输入,但可能你的输出就没有1000个字母了,或者说没有1000个字符。这时要怎么办呢?CTC损失函数允许RNN生成这样的输出:ttt,这是一个特殊的字符,叫做空白符,我们这里用下划线表示,这句话开头的音可表示为h_eee_ _ _,然后这里可能有个空格,我们用这个来表示空格,之后是_ _ _qqq__,这样的输出也被看做是正确的输出。下面这段输出对应的是”the q“。CTC损失函数的一个基本规则是将空白符之间的重复的字符折叠起来,再说清楚一些,我这里用下划线来表示这个特殊的空白符(a special blank character),它和空格(the space character)是不一样的。所以thequick之间有一个空格符,所以我要输出一个空格,通过把用空白符所分割的重复的字符折叠起来,然后我们就可以把这段序列折叠成”the q“。这样一来你的神经网络因为有很多这种重复的字符,和很多插入在其中的空白符(blank characters),所以最后我们得到的文本会短上很多。于是这句”the quick brown fox“包括空格一共有19个字符,在这样的情况下,通过允许神经网络有重复的字符和插入空白符使得它能强制输出1000个字符,甚至你可以输出1000个$y$值来表示这段19个字符长的输出。这篇论文来自于Alex Grace以及刚才提到的那些人。我所参与的深度语音识别系统项目就使用这种思想来构建有效的语音识别系统。

希望这能给你一个粗略的理解,理解语音识别模型是如何工作的:注意力模型是如何工作的,以及CTC模型是如何工作的,以及这两种不同的构建这些系统的方法。现今,在生产技术中,构建一个有效语音识别系统,是一项相当重要的工作,并且它需要很大的数据集

3.10触发字检测(Trigger Word Detection)

现在你已经学习了很多关于深度学习和序列模型的内容,于是我们可以真正去简便地描绘出一个触发字系统(a trigger word system),就像上节视频中你看到的那样。随着语音识别的发展,越来越多的设备可以通过你的声音来唤醒,这有时被叫做触发字检测系统(rigger word detection systems)。我们来看一看如何建立一个触发字系统。

有关于触发字检测系统的文献,还处于发展阶段。对于触发字检测,最好的算法是什么,目前还没有一个广泛的定论。我这里就简单向你介绍一个你能够使用的算法好了。现在有一个这样的RNN结构,我们要做的就是把一个音频片段(an audio clip)计算出它的声谱图特征(spectrogram features)得到特征向量$x^{<1>}$, $x^{<2>}$, $x^{<3>}$..,然后把它放到RNN中,最后要做的,就是定义我们的目标标签$y$。假如音频片段中的这一点是某人刚刚说完一个触发字,比如”Alexa“,或者”小度你好” 或者”Okay Google“,那么在这一点之前,你就可以在训练集中把目标标签都设为0,然后在这个点之后把目标标签设为1。假如在一段时间之后,触发字又被说了一次,比如是在这个点说的,那么就可以再次在这个点之后把目标标签设为1。这样的标签方案对于RNN来说是可行的,并且确实运行得非常不错。不过该算法一个明显的缺点就是它构建了一个很不平衡的训练集(a very imbalanced training set),0的数量比1多太多了。

这里还有一个解决方法,虽然听起来有点简单粗暴,但确实能使其变得更容易训练。比起只在一个时间步上去输出1,其实你可以在输出变回0之前,多次输出1,或说在固定的一段时间内输出多个1。这样的话,就稍微提高了1与0的比例,这确实有些简单粗暴。在音频片段中,触发字刚被说完之后,就把多个目标标签设为1,这里触发字又被说了一次。说完以后,又让RNN去输出1。在之后的编程练习中,你可以进行更多这样的操作,我想你应该会对自己学会了这么多东西而感到自豪。我们仅仅用了一张幻灯片来描述这种复杂的触发字检测系统。在这个基础上,希望你能够实现一个能有效地让你能够检测出触发字的算法,不过在编程练习中你可以看到更多的学习内容。这就是触发字检测,希望你能对自己感到自豪。因为你已经学了这么多深度学习的内容,现在你可以只用几分钟时间,就能用一张幻灯片来描述触发字能够实现它,并让它发挥作用。你甚至可能在你的家里用触发字系统做一些有趣的事情,比如打开或关闭电器,或者可以改造你的电脑,使得你或者其他人可以用触发字来操作它。

3.11结论和致谢(Conclusion and thank you)

我们一起经历了一段很长的旅程,如果你已经学完了整个专业的课程,那么现在你已经学会了神经网络和深度学习,如何改进深度神经网络,如何结构化机器学习项目,和卷积神经网络。在最近的课程中还学了序列模型,我知道你为此非常努力,也希望你能对自己感到自豪,为你的努力,为你所做的这一切。

我想向你传达一个对你来说可能很重要的想法。就是我觉得深度学习是一种超能力,通过深度学习算法,你可以让计算机拥有”视觉”(make a computer see),可以让计算机自己合成小说(synthesize novel art),或者合成音乐(synthesized music),可以让计算机将一种语言翻译成另一种(translate from one language to another),或者对放射影像进行定位然后进行医疗诊断(Maybe have it located radiology image and render a medical diagnosis),或者构建自动驾驶系统(build pieces of a car that can drive itself)。如果说这还不是超能力的话,那还能是什么呢?当我们结束这一系列课程的时候,结束整个专业的时候,我希望你能够使用这些思想来发展你的事业,追逐你的梦想,但最重要的是,去做你认为最合适的能对人类有贡献的事。这个世界现在面临着诸多挑战,但是在这种力量下,在人工智能和深度学习的力量下,我觉得我们可以让世界变得更美好。现在这种超能力就掌握在你的手中,去突破障碍,让生活变得更好,这不单单是为自己,也是为了其他人。当然我也希望你能够对自己取得的成就以及你学到的一切感到自豪。当你完成这一系列课程的学习后,我想你可以把课程分享到社交媒体上,比如TwitterFacebook,让你的朋友也能知道这门课程。

最后,我想告诉你的最后一件事,就是恭喜你完成了这门课程的学习,为自己所取得的成就欢呼吧!同时也非常感谢你们,因为我知道大家都很忙,即便如此你们还是花了很多时间来学习这些视频,可能还花了很多时间来做课堂测验还有编程练习,希望你们能够乐在其中,并学到很多算法流程。我很荣幸你们能够花费时间,付诸精力,来学习这些东西,非常感谢大家!


   转载规则


《关于神经网络与深度学习》 Henry-Avery 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录