CS229 Lecture notes
原作者 | 翻译 | 校对 |
---|---|---|
Andrew Ng 吴恩达,Kian Katanforoosh | CycleUser | XiaoDong_Wang |
相关链接 |
---|
Github 地址 |
知乎专栏 |
斯坦福大学 CS229 课程网站 |
网易公开课中文字幕视频 |
回忆一下,给出一个输入特征$x$的时候,我们定义了$a^{[0]}=x$。然后对于层(layer)$l=1,2,3,\dots,N$,其中的$N$是网络中的层数,则有:
$z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}$ $a^{[l]}=g^{[l]}(z^{[l]})$
在讲义中都是假设了非线性特征$g^{[l]}$对除了第$N$层以外的所有层都相同。这是因为我们可能要在输出层进行回归(regression) [因此就可能使用$g(x)=x$],或者进行二值化分类(binary classification) [这时候用$g(x)=sigmoid(x)$],或者是多类分类(multiclass classification) [这就要用$g(x)=softmax(x)$]。因此要将$g^{[N]}$和$g$相区分开,然后假设$g$使用在除了第$N$层外的其他所有层。
最终,给网络$a^{[N]}$的输出,为了简单记作$\hat y$,就可以衡量损失函数$J(W,b)=\mathcal{L}(a^{[N]},y)=\mathcal{L}(\hat y,y)$。例如,对于实数值的回归可以使用下面的平方损失函数(squared loss):
对使用逻辑回归(logistic regression)的二值化分类(binary classification),我们可以使用下面的损失函数:
或者也可以使用负对数似然函数(negative log-likelihood)。最后是对于有$k$类的柔性最大回归(softmax regression),使用交叉熵损失函数(cross entropy loss):
上面这个其实就是将负对数似然函数简单扩展到多类情景而已。要注意这时候的$\hat y$是一个$k$维度向量。如果使用$y$来表示这个$k$维度向量,其中除了第$l$个位置是$1$,其他地方都是$0$。也就是为真的分类标签(label)就是$l$,这时候也可以将交叉熵损失函数以如下方式表达:
然后咱们再定义更多的记号用于反向传播。$^1$首先定义:
1 这部分的讲义内容基于 斯坦福大学的监督学习中关于多层神经网络算法的内容 Scribe: Ziang Xie
然后可以按照后续内容所示来定义一个计算每个$W^{[l]},b^{[l]}$所对应的梯度的三个步骤:
-
对于输出层$N$有:
$$ \delta ^{[N]} =\nabla _{z^{[N]}}\mathcal{L}(\hat y,y) $$
有时候我们可能要直接计算$\nabla _{z^{[N]}}L(\hat y,y)$(比如$g^{[N]}$是柔性最大函数(softmax function)),而其他时候(比如$g^{[N]}$是S型函数(sigmoid function)$\sigma$)则可以应用链式法则(chain rule):
$$ \nabla _{z^{[N]}}L(\hat y,y)= \nabla _{\hat y}L(\hat y,y) \circ (g^{[N]})'(z^{[N]}) $$
注意$(g^{[N]})'(z^{[N]})$表示的是关于$z^{[N]}$的按元素的导数(elementwise derivative)。
-
对$l=N-1,N-1,\dots,1$则有:
$$ \delta^{[l]} = ( {W^{[l+1]}}^T \delta^{[l+1]} ) \circ g' ( z^{[l]} ) $$
-
最终就可以计算第$l$层的梯度(gradies):
$$ \begin{aligned} \nabla_{W^{[l]}}J(W,b)&= \delta^{[l]}{a^{[l-1]} }^T \ \nabla_{b^{[l]}}J(W,b)&= \delta^{[l]} \ \end{aligned} $$
上文中的小圆圈负号$\circ$表示元素积(elementwise product)。要注意上面的过程是对应单个训练样本的。
你可以将上面的算法用到逻辑回归(logistic regression)里面($N=1,g^{[1]}$是S型函数(sigmoid function)$\sigma$)来测试步骤$1$和$3$。还记得$\sigma'(z)=\sigma(z)\circ (1-\sigma(z))$以及$\sigma(z^{[1]})$就是$a^{[1]}$。注意对于逻辑回归的情景,如果$x$是一个实数域内$R^{n\times 1}$的列向量(column vector),然后$W^{[1]}\in R^{1\times n}$,因此则有$\nabla_{W^{[1]}}J(W,b)\in R^{1\times n}$。代码样本如http://cs229.stanford.edu/notes/backprop.py所示。
(译者注:为了方便我直接把上面链接中的代码贴到下面了。)
#http://cs229.stanford.edu/notes/backprop.py
import numpy as np
from copy import copy
# Example backpropagation code for binary classification with 2-layer
# neural network (single hidden layer)
sigmoid = lambda x: 1 / (1 + np.exp(-x))
def fprop(x, y, params):
# Follows procedure given in notes
W1, b1, W2, b2 = [params[key] for key in ('W1', 'b1', 'W2', 'b2')]
z1 = np.dot(W1, x) + b1
a1 = sigmoid(z1)
z2 = np.dot(W2, a1) + b2
a2 = sigmoid(z2)
loss = -(y * np.log(a2) + (1-y) * np.log(1-a2))
ret = {'x': x, 'y': y, 'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2, 'loss': loss}
for key in params:
ret[key] = params[key]
return ret
def bprop(fprop_cache):
# Follows procedure given in notes
x, y, z1, a1, z2, a2, loss = [fprop_cache[key] for key in ('x', 'y', 'z1', 'a1', 'z2', 'a2', 'loss')]
dz2 = (a2 - y)
dW2 = np.dot(dz2, a1.T)
db2 = dz2
dz1 = np.dot(fprop_cache['W2'].T, dz2) * sigmoid(z1) * (1-sigmoid(z1))
dW1 = np.dot(dz1, x.T)
db1 = dz1
return {'b1': db1, 'W1': dW1, 'b2': db2, 'W2': dW2}
# Gradient checking
if __name__ == '__main__':
# Initialize random parameters and inputs
W1 = np.random.rand(2,2)
b1 = np.random.rand(2, 1)
W2 = np.random.rand(1, 2)
b2 = np.random.rand(1, 1)
params = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
x = np.random.rand(2, 1)
y = np.random.randint(0, 2) # Returns 0/1
fprop_cache = fprop(x, y, params)
bprop_cache = bprop(fprop_cache)
# Numerical gradient checking
# Note how slow this is! Thus we want to use the backpropagation algorithm instead.
eps = 1e-6
ng_cache = {}
# For every single parameter (W, b)
for key in params:
param = params[key]
# This will be our numerical gradient
ng = np.zeros(param.shape)
for j in range(ng.shape[0]):
for k in xrange(ng.shape[1]):
# For every element of parameter matrix, compute gradient of loss wrt
# that element numerically using finite differences
add_eps = np.copy(param)
min_eps = np.copy(param)
add_eps[j, k] += eps
min_eps[j, k] -= eps
add_params = copy(params)
min_params = copy(params)
add_params[key] = add_eps
min_params[key] = min_eps
ng[j, k] = (fprop(x, y, add_params)['loss'] - fprop(x, y, min_params)['loss']) / (2 * eps)
ng_cache[key] = ng
# Compare numerical gradients to those computed using backpropagation algorithm
for key in params:
print key
# These should be the same
print(bprop_cache[key])
print(ng_cache[key])