神经网络的基础——Logistic回归中的反向传播算法及其推导

最近在看Andrew Ng的DeepLearning.ai上的深度学习课程(网易云课堂上有中文字幕版),把之前学的logistic回归又复习了一遍,不得不佩服Andrew Ng,大师就是大师,深入浅出,讲得太好了,虽然logistic回归的代价函数很简单,可以直接求梯度,但是logistic回归也可以用所谓的反向传播算法计算梯度,了解了logistic中的back propagation,对后续理解神经网络的back propagation大有帮助。
这篇文章不是单独用来讲logistic回归的,重点放在用backprop来计算logistic回归的梯度上,所以只对logistic回归作一点必要的说明。

关于logistic回归的几点说明

logistic回归是一种参数学习法,单纯的logistic回归只能用来解决二分类问题,当然,可以用OvR或者OVO的方法实现多分类问题。logistic回归的目的就是用一个式子拟合给定的数据,使得用这个式子计算得到的结果和真实结果的误差达到最小,这样来了一组新的数据,我们就可以用这个式子来预测了,输入是x,输出是 y ^ {\rm{\hat y}} y^ y ^ ≥ 0 . 5 {\rm{\hat y}} \ge {\rm{0}}{\rm{.5}} y^0.5时,我们就把其归类为 1,否则,归类为 0。

logistic回归的假设函数

之前提到了,我们需要有一个式子来拟合给定的数据,这个式子就是logistic回归的假设函数。logistic回归的假设函数就是在线性回归的基础上,加了一个sigmoid函数,将其值域压缩到(0,1)之间:
z = w T x + b {\rm{z = }}{{\rm{w}}^T}x + b z=wTx+b
σ ( z ) = 1 1 + e − z \sigma {\rm{(z)}} = \frac{1}{{1 + {e^{ - z}}}} σ(z)=1+ez1
y ^ = a = σ ( z ) {\rm{\hat y = a = }}\sigma {\rm{(z)}} y^=a=σ(z)

公式及符号说明

设x为一个样本数据,样本共有 n x {n_x} nx个特征,x为一个 n x {n_x} nx维列向量,即x的维度是( n x {n_x} nx,1)。w为x的参数,维度和x相同,也是一个 n x {n_x} nx维列向量,b是一个数,也就是所谓的偏置。z和 y ^ {\rm{\hat y}} y^都是一个数,z是中间变量, y ^ {\rm{\hat y}} y^是预测值,a在这里和 y ^ {\rm{\hat y}} y^相等,它在神经网络里表示通过激活函数所得到的值。y是输入值对应的真实值,就是标签,也是一个数。
我们之前肯定还遇到过其他的表示方式:
z = θ T x b z = {\theta ^T}{x_b} z=θTxb
其中 θ \theta θ x b {x_b} xb都是一个 n x {n_x} nx + 1 维的列向量,第一行分别为 θ 0 {\theta_0} θ0 x 0 {x_0} x0,这样, θ {\theta} θ[1:]就相当于w, θ 0 {\theta_0} θ0 x 0 {x_0} x0就等于b。
这两种表示方法是等价的,只不过在神经网络里,我们更习惯将偏置b单独拿出来,所以在logistic回归中,我也这么做。

损失函数和代价函数

我们在逻辑函数用到的损失函数为:
L ( y ^ , y ) = − y log ⁡ ( y ^ ) − ( 1 − y ) log ⁡ ( 1 − y ^ ) {\rm{L}}(\hat y,y) = - y\log (\hat y) - (1 - y)\log (1 - \hat y) L(y^,y)=ylog(y^)(1y)log(1y^)
这里的损失函数是针对一个训练样本来说的。而代价函数就是对所有样本的损失函数求平均。假设有m个样本,则代价函数为:
J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) = − 1 m ∑ i = 1 m [ y ( i ) log ⁡ ( y ^ ( i ) ) + ( 1 − y ( i ) ) log ⁡ ( 1 − y ^ ( i ) ) ] J(w,b) = \frac{1}{m}\sum\limits_{i = 1}^m {{\rm{L}}({{\hat y}^{(i)}},{y^{(i)}}) = - \frac{1}{m}\sum\limits_{i = 1}^m {[{y^{(i)}}\log ({{\hat y}^{(i)}}) + (1 - {y^{(i)}})\log (1 - {{\hat y}^{(i)}})} } ] J(w,b)=m1i=1mL(y^(i),y(i))=m1i=1m[y(i)log(y^(i))+(1y(i))log(1y^(i))]
我们最终的目的就是要找到这样一组w和b,使得 J ( w , b ) J(w,b) J(w,b)最小。用到的方法就是大名鼎鼎的梯度下降法。对于logistic的代价函数,它的梯度是非常容易求的,我们可以直接对w中的每个元素和b求偏导,但是为了与神经网络联系起来,这里我们采用反向传播的思想来求解梯度。

正向传播

先来看看logistic回归的正向传播过程,假设这里 n x {n_x} nx=2,也就是输入样本只有两个特征。

神经网络的基础——Logistic回归中的反向传播算法及其推导_第1张图片
z = w T x + b {\rm{z = }}{{\rm{w}}^T}x + b z=wTx+b
y ^ = a = σ ( z ) {\rm{\hat y = a = }}\sigma {\rm{(z)}} y^=a=σ(z)
L ( a , y ) = − y log ⁡ ( a ) − ( 1 − y ) log ⁡ ( 1 − a ) {\rm{L}}(a,y) = - y\log (a) - (1 - y)\log (1 - a) L(a,y)=ylog(a)(1y)log(1a)
根据上述公式,我们知道,logistic回归的正向传播过程,就是给定输入x,先经过一个线性加法器,得到z,随后送入激活函数(这里logistic回归的激活函数就是sigmoid函数),得到a,这里的a也就是预测值 y ^ {\rm{\hat y}} y^,之后可以计算损失函数。

梯度下降与反向传播过程

单个样本

梯度下降过程我们都很熟悉。这里我们需要学习得到的参数就是w和b,所以需要计算w和b的梯度。我们还是先以一个训练样本为例,稍后再推广到m个训练样本的情况。
计算梯度,对w而言,就是求 L ( a , y ) {\rm{L}}(a,y) L(a,y)关于 w 1 {w_1} w1 w 2 {w_2} w2的偏导,对b而言,就是计算 L ( a , y ) {\rm{L}}(a,y) L(a,y)对b的偏导。
根据链式法则,有:
∂ L ( a , y ) ∂ w 1 = d L ( a , y ) d a ⋅ d a d z ⋅ ∂ z ∂ w 1 \frac{{\partial {\rm{L}}(a,y)}}{{\partial {w_1}}} = \frac{{{\rm{dL}}(a,y)}}{{da}} \cdot \frac{{da}}{{dz}} \cdot \frac{{\partial z}}{{\partial {w_1}}} w1L(a,y)=dadL(a,y)dzdaw1z

∂ L ( a , y ) ∂ w 2 = d L ( a , y ) d a ⋅ d a d z ⋅ ∂ z ∂ w 2 \frac{{\partial {\rm{L}}(a,y)}}{{\partial {w_2}}} = \frac{{{\rm{dL}}(a,y)}}{{da}} \cdot \frac{{da}}{{dz}} \cdot \frac{{\partial z}}{{\partial {w_2}}} w2L(a,y)=dadL(a,y)dzdaw2z

∂ L ( a , y ) ∂ b = d L ( a , y ) d a ⋅ d a d z ⋅ ∂ z ∂ b \frac{{\partial {\rm{L}}(a,y)}}{{\partial b}} = \frac{{{\rm{dL}}(a,y)}}{{da}} \cdot \frac{{da}}{{dz}} \cdot \frac{{\partial z}}{{\partial b}} bL(a,y)=dadL(a,y)dzdabz

损失函数是已知的,我们很容易可以得到:

d L ( a , y ) d a = − y a + 1 − y 1 − a \frac{{{\rm{dL}}(a,y)}}{{da}} = - \frac{y}{a} + \frac{{1 - y}}{{1 - a}} dadL(a,y)=ay+1a1y

d a d z = a ( 1 − a ) \frac{{da}}{{dz}} = a(1 - a) dzda=a(1a)

所以:

d L ( a , y ) d z = d L ( a , y ) d a ⋅ d a d z = a − y \frac{{{\rm{dL}}(a,y)}}{{dz}} = \frac{{{\rm{dL}}(a,y)}}{{da}} \cdot \frac{{da}}{{dz}} = a - y dzdL(a,y)=dadL(a,y)dzda=ay

再把它带入到最上面的三个式子,就得到了三个偏导:

∂ L ( a , y ) ∂ w 1 = ( a − y ) x 1 = d L ( a , y ) d z x 1 \frac{{\partial {\rm{L}}(a,y)}}{{\partial {w_1}}} = (a - y){x_1}=\frac{{{\rm{dL}}(a,y)}}{{dz}}x_1 w1L(a,y)=(ay)x1=dzdL(a,y)x1

∂ L ( a , y ) ∂ w 2 = ( a − y ) x 2 = d L ( a , y ) d z x 2 \frac{{\partial {\rm{L}}(a,y)}}{{\partial {w_2}}} = (a - y){x_2}=\frac{{{\rm{dL}}(a,y)}}{{dz}}x_2 w2L(a,y)=(ay)x2=dzdL(a,y)x2

∂ L ( a , y ) ∂ b = a − y = ∂ L ( a , y ) ∂ z \frac{{\partial {\rm{L}}(a,y)}}{{\partial b}} = a - y = \frac{{\partial {\rm{L}}(a,y)}}{{\partial z}} bL(a,y)=ay=zL(a,y)

为了方便表示,我们用dz,da,dw,db分别表示损失函数L对z,a,w,b的偏导,也就是说我们用d Var来表示损失函数L对变量Var的偏导,这仅仅是方便表示而已,这里的d并不是微分的意思。

现在,我们来看看究竟什么是反向传播。
其实,刚刚计算偏导时已经用到了反向传播的方法。我们来看看反向传播的流程图。
神经网络的基础——Logistic回归中的反向传播算法及其推导_第2张图片
在正向传播输出后,我们可以先求出da,进而根据 d z = g ′ ( z ) d a dz = g'(z)da dz=g(z)da求出dz,其中 g ′ ( z ) g'(z) g(z)为激活函数的导数,具体到logistic回归,就是sigmoid函数的导数。从da求出dz的过程,就像是从输出单元反向传播到上一个单元一样,同理,db=dz,又可以求得db,dw=xdz,可以求得相应的dw,这就是反向传播运用在logistic回归中的过程。

我们总结一下整个过程:
先根据正向传播的两个公式求出z和a,然后开始反向传播过程,先求出da,再求出dz,然后是dw和db,这里的dw是一个列向量 ( d w 1 , d w 2 ) T {(dw_1,dw_2)^T} (dw1,dw2)T,这就是一次求梯度的过程,然后根据:
w = w − α ⋅ d w w = w - \alpha \cdot dw w=wαdw
b = b − α ⋅ d b b = b - \alpha \cdot db b=bαdb
更新w和b,随后进行新一轮迭代。

多个样本

前面讲的梯度下降和反向传播只是针对一个训练样本来说的,现在我们将其推广到m个样本中,其中每个样本都有 n x n_x nx个特征第i个样本记为 x ( i ) x^{(i)} x(i),对应的标签为 y ( i ) y^{(i)} y(i),其中,把一个样本记为一个列向量,每个标签的值是0或1。
对于m个样本,我们要最小化的代价函数 J ( w , b ) = 1 m ∑ i = 1 m L ( a ( i ) , y ( i ) ) J(w,b) = \frac{1}{m}\sum\limits_{i = 1}^m {L({a^{(i)}},{y^{(i)}})} J(w,b)=m1i=1mL(a(i),y(i)),也就是说,我们应该对每个样本都单独求一次梯度,随后取m个样本得到的梯度的平均值,就是最后需要的梯度。
因此:
d w = 1 m ∑ i = 1 m d w ( i ) dw = \frac{1}{m}\sum\limits_{i = 1}^m {d{w^{(i)}}} dw=m1i=1mdw(i)

d b = 1 m ∑ i = 1 m d b ( i ) db = \frac{1}{m}\sum\limits_{i = 1}^m {d{b^{(i)}}} db=m1i=1mdb(i)
(dw是 n x n_x nx维列向量,b是一个数)

我们可以用和单个样本同样的方法求每次的梯度,最后加起来求平均即可。但是这样必须要使用循环才可以完成,我们可以使用向量化的方法,去掉循环。

对于单个样本,可以求得 z ( i ) = w T x ( i ) + b {z^{(i)}} = {w^T}{x^{(i)}} + b z(i)=wTx(i)+b,我们将m个样本横向排列,也就是令 X = ( x ( 1 ) , x ( 2 ) , . . . . . . , x ( m ) ) X = ({x^{(1)}},{x^{(2)}},......,{x^{(m)}}) X=(x(1),x(2),......,x(m)),很显然,这里的 X X X是一个维度是 ( n x , m ) (n_x,m) (nx,m)的矩阵,这样对于每一个 x ( i ) x^{(i)} x(i)求得的 z ( i ) z^{(i)} z(i),也将其横向排列起来,就可以得到 Z = ( z ( 1 ) , z ( 2 ) , . . . . . . , z ( m ) ) Z = ({z^{(1)}},{z^{(2)}},......,{z^{(m)}}) Z=(z(1),z(2),......,z(m)),Z的维度是 ( 1 , m ) (1,m) (1,m)。这样我们可以得到 Z = w T X + b Z=w^TX+b Z=wTX+b
这里我们需要做一点说明: Z Z Z的维度是 ( 1 , m ) (1,m) (1,m) X X X的维度是 ( n x , m ) (n_x,m) (nx,m) w w w的维度是 ( n x , 1 ) (n_x,1) (nx,1) w T w^T wT的维度就是 ( 1 , n x ) (1,n_x) (1,nx),这样 w T X w^TX wTX的维度就是(1,m),和 Z Z Z相同。但是这里b只是一个数,这里 w T X + b w^TX+b wTX+b就是对 w T X w^TX wTX的每一个元素都加上b,因为每一个输入进去的样本来说,它们的偏置都是相同的,都是b。在Python中,numpy的广播功能又恰好允许我们这么写,因此,这里就不再把b也扩展成 ( 1 , m ) (1,m) (1,m)的向量了。
在Python中,我们就可以这么写:

import numpy as np
Z=np.dot(W.T,X) + b

同样的道理,对于每一个 a ( i ) a^{(i)} a(i),将其横向排列起来,就有A= ( a ( 1 ) , a ( 2 ) , . . . . . . , a ( m ) ) ({a^{(1)}},{a^{(2)}},......,{a^{(m)}}) (a(1),a(2),......,a(m)),也就是 A = σ ( Z ) A=\sigma(Z) A=σ(Z),可以这样来写:

A = 1 / (1 + np.exp(-Z))

前面我们已经得到 d z ( i ) = a ( i ) − y ( i ) dz^{(i)}=a^{(i)}-y^{(i)} dz(i)=a(i)y(i),我们已经将 a ( i ) a^{(i)} a(i)排列起来得到 A A A,很容易想到,我们应该将 y ( i ) y^{(i)} y(i)也相同地排列起来,即令 Y = ( y ( 1 ) , y ( 2 ) , . . . . . . , y ( m ) ) Y=({y^{(1)}},{y^{(2)}},......,{y^{(m)}}) Y=(y(1),y(2),......,y(m)),这样就可以得到 d Z = A − Y dZ=A-Y dZ=AY,可以这么写:

dZ = A - Y

现在还剩下 d w dw dw d b db db需要计算,先看 d b db db,因为 d b ( i ) = d z ( i ) db^{(i)}=dz^{(i)} db(i)=dz(i) d b = 1 m ∑ i = 1 m d b ( i ) db = \frac{1}{m}\sum\limits_{i = 1}^m {d{b^{(i)}}} db=m1i=1mdb(i),所以可以这么写:

db = 1 / m * np.sum(dZ)

最后来看 d w dw dw。对于单个样本,有 d w ( i ) = x ( i ) d z ( i ) d{w^{(i)}} = {x^{(i)}}d{z^{(i)}} dw(i)=x(i)dz(i),而 d w = 1 m ∑ i = 1 m d w ( i ) = 1 m ∑ i = 1 m x ( i ) d z ( i ) dw = \frac{1}{m}\sum\limits_{i = 1}^m {d{w^{(i)}}} = \frac{1}{m}\sum\limits_{i = 1}^m {{x^{(i)}}d{z^{(i)}}} dw=m1i=1mdw(i)=m1i=1mx(i)dz(i),其中, d w dw dw是一个维度是 ( n x , 1 ) (n_x,1) (nx,1)的列向量,
∑ i = 1 m x ( i ) d z ( i ) = ( x ( 1 ) , x ( 2 ) , . . . . . . , x ( m ) ) ⋅ ( d z ( 1 ) , d z ( 2 ) , . . . . . . , d z ( m ) ) T = X ⋅ d Z T \sum\limits_{i = 1}^m {{x^{(i)}}d{z^{(i)}}} = ({x^{(1)}},{x^{(2)}},......,{x^{(m)}}) \cdot {(d{z^{(1)}},d{z^{(2)}},......,d{z^{(m)}})^T} = X \cdot d{Z^T} i=1mx(i)dz(i)=(x(1),x(2),......,x(m))(dz(1),dz(2),......,dz(m))T=XdZT,所以 d w = 1 m X d Z T dw= \frac{1}{m}XdZ^T dw=m1XdZT。在Python中可以这么写:

dw = 1 / m * np.dot(X, dZ.T)

至此,我们就向量化了全部的梯度。
完整的梯度下降过程如下(这只是一个简化的代码,把backprop的思想体现一下而已,假装w和b已经随机初始化过):

import numpy as np
def gradient_descent(alpha=0.01,iters=1000):
	for _ in range(iters):
		Z = np.dot(W.T,X) + b
		A = 1 / (1 + np.exp(-Z))
		dZ = A - Y
		dw = 1 / m * np.dot(X, dZ.T)
		db = 1 / m * np.sum(dZ)
		dw -= alpha * dw
		db -= alpha * db

总结

终于写完了,反向传播算法是神经网络求解梯度的基础,logistic回归可以看做是没有隐藏层的,只有输入层和输出层且输出层只有一个单元的神经网络,将反向传播算法应用在logistic回归中的确是大材小用了,但是理解了logistic回归中的反向传播,对理解神经网络中的反向传播是大有帮助的,因为神经网络每一层之间的连接都可以看做是若干个logistic回归。
下一篇就先写一写浅层神经网络中的BP算法。

你可能感兴趣的:(机器学习和深度学习)