从0到1:神经网络实现图像识别(中)


”. . .  we may have knowledge of the past and cannot control it; we may control the future but have no knowledge of it.” — Claude Shannon 1959(往者可知然不可谏,来者可追或未可知)


上篇介绍了神经网络的理论基石 - 感知机(perceptron)模型;感知机模型是一个简洁的二类分类模型,在中篇里,我们把它推广到多类分类问题,不借助计算框架,构建一个全连接神经网络,再应用于MNIST数据集手写数字识别场景。


MNIST数据集

能正确的提出问题,你已经解决了问题的一半。


这里“正确的问题”是MNIST,一个手写数字图片集数据的识别问题。


你可以在 Yann LeCun的官网下载这套数据集,共四个文件包:

train-images-idx3-ubyte.gz:  训练图片集 (9912422 bytes)  

train-labels-idx1-ubyte.gz: 训练图片集的正确标签 (28881 bytes) 


t10k-images-idx3-ubyte.gz:   测试图片 (1648877 


bytes) t10k-labels-idx1-ubyte.gz:   测试图片的正确标签  (4542 bytes)

每张图片包含一个手写数字。

image.png

数据集包含6万张图片用于训练,1万张用于测试验证。


图像数据格式和图向量

image.png

每张图片表达了[0,9]这是10个数字中的一个,有28X28=784个像素,每个像素根据灰度取整数值[0,255];把每张图片看作具有784个特征的图向量,问题就变成:根据D个特征维度,对图像做K分类的问题,这里D=784,K=10。


从二分类到多分类问题

一种思路是把 K 类分类问题,视为 K 个二类分类问题:第一次,把样本数据集的某一个类别,和余下的K-1类(合并成一个大类)做二类分类划分,识别出某一类;第 i 次,划分第i类和余下的K-i类;经过 K 次 二类分类迭代,最终完成 K 类分类。


这里,我们选用一种更直接的思路,回忆感知机二类分类模型,只包含了一个输出节点;现在把输出节点扩展为K个 ;权值向量w扩展为 D x K的权值矩阵W,偏置(bias) b扩展为长度为K的数组;这样一来,一个样本点经过模型处理,会得到这个样本点在所有K个类别上的归类可能性得分 z。


\(\displaystyle p_j =  softmax ( z_j )  = \frac {e^{z_j}}{\sum _{k=1}^Ke^{z_k}}\)


z同样是长度为K的数组,某一个元素z [j]的数值越大,输入样本点属于对应分类类别j的可能性也越高。


Softmax

Softmax方法,用于把输出z进一步标准化(normalize),得到某个样本点,归属于各个类别的概率分布;


例如,归属于类别 j 的概率为:


\(\displaystyle H( p_{ \hat y},p ) = - \sum _{j=1}^{K} p_{\hat y_j} log(p_j)\)


这个结果满足了概率分布的标准化要求:在所有类别上的输出概率都不小0,且所有类别上的输出概率和等于1。


就得到了模型预测输出结果的标准概率分布。


对应的,目标问题MNIST数据集的正确标签,也可以视为一个概率分布;一张手写数字图片,在正确类别上的概率分布视为1,其它类别上为0;数字9的图片,所对应的正确标签为(0,0,0,0,0,0,0,0,0,1),可视为放平之后的分类期望向量


有了预测输出和正确答案的概率分布,就可以刻画两者之间相似度,简便地度量模型预测的损失。


损失函数-交叉熵

经过 Softmax 转换为标准概率分布的预测输出p,与正确类别标签之间的损失,可以用两个概率分布的 交叉熵(cross entropy)来度量:


\(\displaystyle H( p_{ \hat y},p ) = - \sum _{j=1}^{K} p_{\hat y_j} log(p_j) \)


所以,某一样本点使用模型预测的损失函数,可以写为


\(\begin{split} Loss (\hat y) =&  - \sum  _{j=1}^{K}  p_{\hat y_j}  log(p_j)  \\=& -log \ e^{z_j} + log\sum _{k=1}^Ke^{z_k} \end{split}\)


你可以跳过关于交叉熵的展开介绍,从学习算法处继续阅读,不影响方法使用。


再深一点:关于交叉熵

image.png

1948年,Claude E Shannon首次提出信息熵(Entropy)概念。


交叉熵(Cross Entropy)的概念来自信息论 :若离散事件以真实概率p(xi)分布,则以隐概率q(xi)分布对一系列随机事件xi做最短编码,所需要的平均比特(bits)长度。其中,定义q(xi)=1/2li,显然,较短的编码长度li,应当被用于出现频度q(xi)较高的编码片段,以提高传输效率。


\( \begin{split} \displaystyle H(p,q) = & E_p[l_i] \\= & E_p[ log_2( \frac {1} {q(x_i)})] \\=& \sum \limits _{x_i}p(x_i) log_2 \frac {1}{{q(x_i)}} \\= & - \sum \limits _{i}  p_i log_2(q_i) \end{split} \\\)


直观理解,如果有H(p,q')<H(p,q)   ,  则相对于qi,  概率分布qi'同真实概率pi分布 更相似。


交叉熵对两个概率分布的度量结果,不具对称性,所以交叉熵并不是严格意义上的距离。


交叉熵概念的源头,用比特(bits)信息为单位,以2为底做对数计算,那么用作损失函数Loss时,对数计算是否必须以2为底呢?


不是必须的。


机器学习领域,交叉熵被用来衡量两个概率分布的相似度,交叉熵越小,两个概率分布越相似。工程实践中,出于简化公式推导,或优化数值计算效率的考虑,对数的底可以做出其它选择。


例举以e为底的情况,由换底公式:


\(\displaystyle ln(q_i) = \frac {log_2 q_i}{log_2 e}\)


可知,对数的底由2换成e,对Loss的影响是,缩小了常数倍 log2e;上一次,我们提到,优化损失函数使损失极小的场景下,函数取值的数值缩放正倍数不影响优化方法。


所以损失函数也可以写为:

\( Loss (\hat y)  = - z_j + ln\sum \limits_{k=1}^Ke^{z_k}\)


学习算法

用上一次介绍的方法,你可以先为 W, b 设置初始值,W随机初始化为很小的值,b初始化为0;然后用梯度下降法( gradient descent),让参数不断更新梯度▽W 和▽b,来极小化损失函数。

image.png

对包含D维输入特征的K类分类样例点,根据损失函数计算参数更新的梯度:


\(\displaystyle \begin{split}  \nabla _{w_{dj}} Loss =& \frac { \partial Loss}{ \partial {w_{dj}}} \\=& \frac { \partial( - z_j + ln\sum \limits _{k=1}^Ke^{z_k} )}{ \partial w_{dj}}  \end{split}\)


对\(\displaystyle -\frac { \partial(  z_j)}{ \partial w_{dj}}\), 将Zi,内积运算展开,易得:


\(\displaystyle \  -  \frac {\partial( W \cdot \vec x + b)}{\partial w_{dj}} =  \begin{cases} -x^{(d)} & \text {if $j=k$} \\ 0 & \text {if $ j\neq k$} \end{cases} \text {      即:} - p_{\hat y_j} x^{(d)}\)


对后一部分, 应用链式法则:


\(\displaystyle \begin{split}  \frac { \partial( ln\sum \limits_{k=1}^Ke^{z_k} )}{ \partial w_{dj}} =& \frac {1}{\sum \limits _{j=1}^K e^{z_j}} \cdot \frac {\partial \sum  \limits _{j=1}^K e^{z_j}}{\partial w_{dj}}\\ = & \frac {e^{z_j}}{\sum \limits _{j=1}^K e^{z_j}} \cdot \frac {\partial e^{z_j}}{\partial w_{dj}}\\ = & p_j \cdot x^{(d)} \end{split}\)


从而


\(\displaystyle \begin{split}  \nabla _{w_{dj}} Loss = & \frac { \partial Loss}{ \partial {w_{dj}}} = (p_j- p_{\hat y_j}) x^{(d)} \end{split} \)


同样


\( \displaystyle \begin{split}  \nabla _{b_j} Loss = & \frac { \partial Loss}{ \partial {b_j}} = p_j-p_{\hat {y_j}}  \end{split} \)


得到参数更新的梯度:


\(\nabla W = (p-p_{\hat y})\cdot \vec x'  \)

\( \nabla b = \sum \limits _{j=1}^{K}(p-p_{\hat y}) \)


其中


\(\displaystyle  p_{\hat y_j} = \begin{cases}  1& \text { if  $j=k$ }  \\0& \text { if  $j \neq k$} \end{cases}\)


反向传播(back propagation)

反向传播(back propagation)是相对前向推断(inference)过程而言的。


抽取训练数据,得到预测分类结果,再使用训练数据的正确标注,计算样本预测损失,然后根据损失更新神经网络模型参数,这个迭代过程就是反向传播方法。


工程实践中,往往从训练样本集中,抽取一批(batch)训练样本,通过整批数据的矩阵运算,得到这批样本损失的均值,减少更新梯度的次数提高训练效率;每轮训练后,使用该批次的梯度均值更新参数,较快得到接近梯度下降的收敛结果。


\(\displaystyle Param = Param + \eta\frac {  \nabla W}{batch \ capacity}\)


实现-第一个神经网络

上述算法的python实现,不需要安装Tensorflow计算框架,你可以从算法实现层面,了解一个基础的全连接神经网络的基本结构,跟踪训练过程:

image.png

在一千五百次参数更新迭代后,模型参数在验证集上准确率超过90%,五千次迭代后,验证数据集上预测损失(Loss)趋于稳定。

image.png

预测准确率(acc)也在验证数据集上稳定在92%附近。

image.png

上述算法,可以进一步通过正则化处理,来缓解过拟合风险,降低泛化误差。


过拟合与正则化

在训练数据集上,训练迭代次数不足,模型没有学到训练样本的一般特征,称为欠拟合(under-fitting);


反之,有大量参数的复杂模型,经过多轮训练,会将训练数据中,不具一般性的噪声,也学习到模型参数里,结果在训练数据上得到很好的拟合表现,在未知数据上预测效果却不好,这种情况称为过拟合(Over-fitting)。


通过大量参数,在训练数据上拟合复杂模型,可能很好地反映出训练数据集上的细微特征,也会包括不具一般性的样本噪声,过拟合为模型引入“偏见”,增加了模型的泛化误差。


过拟合的缓解方法,是根据模型的复杂程度,增加罚项(penalty term),使模型的结构风险最小化。


\( \displaystyle Loss\_total(w) = \frac {1} {N} \sum \limits _{i=1}^{N} Loss(x_i;w) + \frac {\lambda}{2}  \Vert w \Vert ^2\)


总损失中增加的罚项部分,也称为L2正则化项(regularizer),其中,入是根据模型复杂度设定的罚项系数;乘数0.5用于简化计算;||w||是权值参数w的L2范数,计算方法参见上篇,学习策略部分的介绍。


你可以进一步了解正则化处理,也可以跳到”隐藏层“部分继续阅读,不影响正则化方法的使用。


再深一点:关于正则项

L2正则化的直观效果是:含有相对大分量的权值参数 w,其罚项项也越大;各分量相对小而分散权值w,罚项也较小。

例如:


输入向量 x = [1,1,1,1],和两个权值向量  w1=[1,0,0,0], w2=[0.25,0.25,0.25,0.25]。


做内积运算后结果均为1;然而 W1的L2 罚项为1,W2由于各分量相对小而分散,罚项只有0.25。

L2正则化后的模型,倾向于均衡评估输入样本点上全部各个维度的特征,而不是少数大分量值特征对分类结果的影响,来提高模型在测试数据集上的泛化(generalization)能力,缓解过拟合风险。


偏置项 b 是否也需要做正则化处理呢?


不需要。


上面的例子可以看到:输入样本点各个维度特征分量的数值特征,影响了内积计算结果;而偏置 b, 既不参与内积计算,也不对特征分量的数值特征做倍数放大。所以实践中通常只对权值参数 w 做正则化处理。


隐藏层(Hidden Layer)

感知机线性模型能很好的处理线性可分样本点的类别划分,却无法处理如下非线性可分问题:

image.png

通过引入隐藏层,将模型去线性化,可使模型能处理非线性可分样本的分类识别;


原始输入,先经过隐藏层处理,再传递到输出层;隐藏层中的节点,代表了从输入特征中抽取得到的更高层特征。


把新增隐藏层中的节点看作神经元,这些神经元通过自身的激活函数产生神经元输出。


激活函数

这里使用ReLU(Rectified Linear Units)作为激活函数,形式简洁,有如下效果:


单侧抑制,仅激活输出大于阈值0的信号

宽阔的激活边界

稀疏的激活性


随着神经网络深度(隐藏层数)的增加,ReLU降低信号的激活率的作用愈加显著,有助于重要特征的高效提取。


从图像可以看到,ReLU函数不是处处可导的,但是反向传播梯度仍然可以计算,接下来的算法部分会介绍。

WechatIMG1590.jpeg

以上是ReLU和另一个常用激活函数tanh的图像对比。


下面是不再常用的S型激活函数和softplus激活函数的图像。

WechatIMG1591.jpeg


算法

由于总损失增加了正则化损失部分:


\(\displaystyle \begin{split}Loss\_total(W) = & \frac {1} {N} \sum \limits _{i=1}^{N} Loss(x_i;W) + \frac {\lambda}{2}  \Vert W \Vert ^2 \\=&  \frac {1} {N} \sum \limits _{i=1}^{N} Loss(x_i;W) + \frac {\lambda}{2}  \sum \limits _i\sum \limits _j W_{ij}^2  \end{split}\)


反向传播时,权值矩阵W的也相应增加了正则化部分梯度:


\(\nabla W_{reg} = \displaystyle \frac {d}{d W}(\frac{1}{2}λW^2)=λW\)


增加了隐藏层之后,隐藏层的M个节点和输入层节点之间,有了新的权值矩阵 W1和偏置b1;


\(\displaystyle h  = RelU ( \vec x' \cdot W_{1_{D \times M}} + b_1)= ReLU (z^h)\)


隐藏层原始输出Zh,激活后表达程为h,作为下一层的输入。


隐藏层到输出层的参数仍然保留,名称改为W2,b2,以方便对应。

image.png

隐藏层到输出层参数梯度计算方法不变,以隐藏层输出的M个元素数组h,转置为列向量后,作为输入,


\(\displaystyle z =  \vec {h '}  \cdot W_{2{_{M \times K}} } +  b_2  \qquad(式*)\)


仍然采用交叉熵度量预测损失,W2, b2 反向传播梯度,正则化后,成为:


\(\nabla W_2 = (p-p_{\hat y})\cdot \vec {h'}  + \lambda  W_2\)

\(\nabla b_2 = \sum \limits _{j=1}^{K}(p-p_{\hat y})  \)

\(\displaystyle  p_{\hat y_j} = \begin{cases}  1& \text { if  $j=k$ }  \\0& \text { if  $j \neq k$} \end{cases}\)


对 W1, b1 仍然可以设置初始值,然后用梯度下降法( gradient descent),让参数不断更新梯度▽W1 和▽b1,来极小化损失函数。


首先,误差损失 δ 反向传播到隐藏层:


\( \displaystyle \nabla h = \frac{\partial Loss(W_2,b_2,z,\hat y)}{\partial h} = \frac{\partial Loss(W_2,b_2,z,\hat y)}{\partial z}\frac{\partial z}{\partial h} = \nabla \frac{\partial z}{\partial h}\)


又有


\( \displaystyle \frac{\partial z}{\partial h} =\frac{\partial [( W_2 h + b_2]}{\partial h}  = W_2'  \)

所以 \(\nabla h=  (p-p_{\hat y})\cdot  W_2' \)


再看激活函数 ReLU(x) =max(0,x),不是处处可导的,但是观察函数性质,当输出为负数时,经过ReLU之后被抑制,其梯度降为0;如果输出为非负数,则导数为1,即误差传递到此处是自变量x本身。


\(\displaystyle \frac {\mathrm dRelU(x)}{\mathrm dx} =\begin{cases}  1& \text { if  $x>0$ }  \\0& \text { if  $x <= 0$} \end{cases}\)


所以实际反向梯度▽h为


\(\displaystyle \nabla h =\begin{cases}  \nabla h& \text { if  $h>0$ }  \\0& \text { if  $h <= 0$} \end{cases}\)


得到正则化后,参数更新的梯度:


\(\nabla W_1 = \nabla h\cdot \vec x'  + \lambda  W_1\)

\(\nabla b_1 = \sum \limits _{m=1}^{M}\nabla h\)


得到反向传播的全部参数更新梯度。


实现-加入隐藏层

上述算法的python实现,不借助计算框架,在上一次全连接神经网络的基本结构上,增加了正则化处理,缓解过拟合问题,并添加了一个隐藏层,使模型能处理非线性可分特征,进一步提高识别效果。


以下是设置单隐藏层500个隐藏层节点的训练过程:

image.png

验证集上识别正确率稳定在98以上%。

image.png

以上,介绍了具有一个隐藏层的全连接神经网络,随着深度(隐藏层)增加,神经网络能在复杂的输入样本上提取更多的特征,得以在一些数据集上,超过了人工识别准确率。


增加深度也带来了新挑战:连接节点和参数过多,导致反向传播训练效率降低,且增加了过拟合风险;下一次,将在目前网络结构的基础上,引入卷积核(Convolutional kernel)处理,使之成为一个卷积神经网络(CNN), 通过进一步实践,了解这一挑战的应对方法。


(中篇完)


参考

[1]  斯坦福大学 class CS231n:The Stanford CS class CS231n

[2] de Boer, PT., Kroese, D.P., Mannor, S. et al. A Tutorial on the Cross-Entropy Method. Ann Oper Res (2005) 134: 19.


从0到1:神经网络实现图像识别(中).pdf



微信扫一扫
关注公众号