@第一章神经网络
第一步 概论与基本概念
摘要
在这一步中,我们将针对零基础的初学者们,用通俗易懂的语言,讲述神经网络的基本概念。
在概论与基本概念中,首先对人工智能的发展简史、定义、以及科学范式的演化进行介绍,并列举了一些有趣的实例,让大家对人工智能的世界观方法论形成一个基本的认识。
然后讲解一下神经网络基本的训练和工作原理,因为基本上各种教程里都没有提到这一点,以至于笔者在刚开始学习神经网络时一头雾水,不得要领,不知从何处开始下手。
再后面是反向传播和梯度下降,我们先从简单的线性方式说起(只有加法和乘法),而且用代入数值的方式来消除对公式的恐惧心理。然后会说到分层的复杂(非线性)函数的反向传播,同样用数值代入方式手推反向过程。
梯度下降是神经网络的基本学习方法,我们会用单变量和双变量两种方式说明,配以可视化的图解。再多的变量就无法用可视化方式来解释了,所以我们力求用简单的方式理解复杂的事物。
本部分最后是损失函数的讲解,着重说明了神经网络中目前最常用的均方差损失函数(用于回归)和交叉熵损失函数(用于分类)。
2.1 线性反向传播
2.1.1 正向计算的实例
假设有一个函数:
z = x ⋅ y (1) z = x \cdot y \tag{1} z=x⋅y(1)
其中:
x = 2 w + 3 b (2) x = 2w + 3b \tag{2} x=2w+3b(2)
y = 2 b + 1 (3) y = 2b + 1 \tag{3} y=2b+1(3)
计算图如图2-4。
图2-4 简单线性计算的计算图
注意这里 x , y , z x,y,z x,y,z 不是变量,只是中间计算结果; w , b w,b w,b 才是变量。因为在后面要学习的神经网络中,要最终求解的目标是 w w w 和 b b b 的值,所以在这里先预热一下。
当 w = 3 , b = 4 w = 3, b = 4 w=3,b=4 时,会得到图2-5的结果。
图2-5 计算结果
最终的 z z z 值,受到了前面很多因素的影响:变量 w w w,变量 b b b,计算式 x x x,计算式 y y y。
2.1.2 反向传播求解 w w w
求 w w w 的偏导
目前 z = 162 z=162 z=162,如果想让 z z z 变小一些,比如目标是 z = 150 z=150 z=150, w w w 应该如何变化呢?为了简化问题,先只考虑改变 w w w 的值,而令 b b b 值固定为 4 4 4。
如果想解决这个问题,最笨的办法是可以在输入端一点一点的试,把 w w w 变成 3.5 3.5 3.5 试试,再变成 3 3 3 试试…直到满意为止。现在我们将要学习一个更好的解决办法:反向传播。
从 z z z 开始一层一层向回看,图中各节点关于变量 w w w 的偏导计算结果如下:
因为 z = x ⋅ y z = x \cdot y z=x⋅y,其中 x = 2 w + 3 b , y = 2 b + 1 x = 2w + 3b, y = 2b + 1 x=2w+3b,y=2b+1
所以:
∂ z ∂ w = ∂ z ∂ x ⋅ ∂ x ∂ w = y ⋅ 2 = 18 (4) \frac{\partial{z}}{\partial{w}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{w}}=y \cdot 2=18 \tag{4} ∂w∂z=∂x∂z⋅∂w∂x=y⋅2=18(4)
其中:
∂ z ∂ x = ∂ ∂ x ( x ⋅ y ) = y = 9 \frac{\partial{z}}{\partial{x}}=\frac{\partial{}}{\partial{x}}(x \cdot y)=y=9 ∂x∂z=∂x∂(x⋅y)=y=9
∂ x ∂ w = ∂ ∂ w ( 2 w + 3 b ) = 2 \frac{\partial{x}}{\partial{w}}=\frac{\partial{}}{\partial{w}}(2w+3b)=2 ∂w∂x=∂w∂(2w+3b)=2
图2-6 对 w w w 的偏导求解过程
图2-6其实就是链式法则的具体表现, z z z 的误差通过中间的 x x x 传递到 w w w。如果不是用链式法则,而是直接用 z z z 的表达式计算对 w w w 的偏导数,会怎么样呢?我们来试验一下。
根据公式1、2、3,我们有:
z = x ⋅ y = ( 2 w + 3 b ) ( 2 b + 1 ) = 4 w b + 2 w + 6 b 2 + 3 b (5) z=x \cdot y=(2w+3b)(2b+1)=4wb+2w+6b^2+3b \tag{5} z=x⋅y=(2w+3b)(2b+1)=4wb+2w+6b2+3b(5)
对上式求 w w w 的偏导:
∂ z ∂ w = 4 b + 2 = 4 ⋅ 4 + 2 = 18 (6) \frac{\partial z}{\partial w}=4b+2=4 \cdot 4 + 2=18 \tag{6} ∂w∂z=4b+2=4⋅4+2=18(6)
公式4和公式6的结果完全一致!所以,请大家相信链式法则的科学性。
求 w w w 的具体变化值
公式4和公式6的含义是:当 w w w 变化一点点时, z z z 会产生 w w w 的变化值18倍的变化。记住我们的目标是让 z = 150 z=150 z=150,目前在初始状态时是 z = 162 z=162 z=162,所以,问题转化为:当需要 z z z 从 162 162 162 变到 150 150 150 时, w w w 需要变化多少?
既然:
Δ z = 18 ⋅ Δ w \Delta z = 18 \cdot \Delta w Δz=18⋅Δw
则:
Δ w = Δ z 18 = 162 − 150 18 = 0.6667 \Delta w = {\Delta z \over 18}=\frac{162-150}{18}= 0.6667 Δw=18Δz=18162−150=0.6667
所以:
w
=
w
−
0.6667
=
2.3333
w = w - 0.6667=2.3333
w=w−0.6667=2.3333
x
=
2
w
+
3
b
=
16.6667
x=2w+3b=16.6667
x=2w+3b=16.6667
z
=
x
⋅
y
=
16.6667
×
9
=
150.0003
z=x \cdot y=16.6667 \times 9=150.0003
z=x⋅y=16.6667×9=150.0003
我们一下子就成功地让 z z z 值变成了 150.0003 150.0003 150.0003,与 150 150 150 的目标非常地接近,这就是偏导数的威力所在。
2.1.3 反向传播求解 b b b
求 b b b 的偏导
这次我们令 w w w 的值固定为 3 3 3,变化 b b b 的值,目标还是让 z = 150 z=150 z=150。同上一小节一样,先求 b b b 的偏导数。
注意,在上一小节中,求 w w w 的导数只经过了一条路:从 z z z 到 x x x 到 w w w。但是求 b b b 的导数时要经过两条路,如图2-7所示:
- 从 z z z 到 x x x 到 b b b;
- 从 z z z 到 y y y 到 b b b。
图2-7 对b的偏导求解过程
从复合导数公式来看,这两者应该是相加的关系,所以有:
∂ z ∂ b = ∂ z ∂ x ⋅ ∂ x ∂ b + ∂ z ∂ y ⋅ ∂ y ∂ b = y ⋅ 3 + x ⋅ 2 = 63 (7) \frac{\partial{z}}{\partial{b}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{b}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{b}}=y \cdot 3+x \cdot 2=63 \tag{7} ∂b∂z=∂x∂z⋅∂b∂x+∂y∂z⋅∂b∂y=y⋅3+x⋅2=63(7)
其中:
∂
z
∂
x
=
∂
∂
x
(
x
⋅
y
)
=
y
=
9
\frac{\partial{z}}{\partial{x}}=\frac{\partial{}}{\partial{x}}(x \cdot y)=y=9
∂x∂z=∂x∂(x⋅y)=y=9
∂
z
∂
y
=
∂
∂
y
(
x
⋅
y
)
=
x
=
18
\frac{\partial{z}}{\partial{y}}=\frac{\partial{}}{\partial{y}}(x \cdot y)=x=18
∂y∂z=∂y∂(x⋅y)=x=18
∂
x
∂
b
=
∂
∂
b
(
2
w
+
3
b
)
=
3
\frac{\partial{x}}{\partial{b}}=\frac{\partial{}}{\partial{b}}(2w+3b)=3
∂b∂x=∂b∂(2w+3b)=3
∂
y
∂
b
=
∂
∂
b
(
2
b
+
1
)
=
2
\frac{\partial{y}}{\partial{b}}=\frac{\partial{}}{\partial{b}}(2b+1)=2
∂b∂y=∂b∂(2b+1)=2
我们不妨再验证一下链式求导的正确性。把公式5再拿过来:
z = x ⋅ y = ( 2 w + 3 b ) ( 2 b + 1 ) = 4 w b + 2 w + 6 b 2 + 3 b (5) z=x \cdot y=(2w+3b)(2b+1)=4wb+2w+6b^2+3b \tag{5} z=x⋅y=(2w+3b)(2b+1)=4wb+2w+6b2+3b(5)
对上式求b的偏导:
∂ z ∂ b = 4 w + 12 b + 3 = 12 + 48 + 3 = 63 (8) \frac{\partial z}{\partial b}=4w+12b+3=12+48+3=63 \tag{8} ∂b∂z=4w+12b+3=12+48+3=63(8)
结果和公式7的链式法则一样。
求 b b b 的具体变化值
公式7和公式8的含义是:当 b b b 变化一点点时, z z z 会发生 b b b 的变化值 63 63 63 倍的变化。记住我们的目标是让 z = 150 z=150 z=150,目前在初始状态时是 162 162 162,所以,问题转化为:当我们需要 z z z 从 162 162 162 变到 150 150 150 时, b b b 需要变化多少?
既然:
Δ z = 63 ⋅ Δ b \Delta z = 63 \cdot \Delta b Δz=63⋅Δb
则:
Δ b = Δ z 63 = 162 − 150 63 = 0.1905 \Delta b = \frac{\Delta z}{63}=\frac{162-150}{63}=0.1905 Δb=63Δz=63162−150=0.1905
所以:
b
=
b
−
0.1905
=
3.8095
b=b-0.1905=3.8095
b=b−0.1905=3.8095
x
=
2
w
+
3
b
=
17.4285
x=2w+3b=17.4285
x=2w+3b=17.4285
y
=
2
b
+
1
=
8.619
y=2b+1=8.619
y=2b+1=8.619
z
=
x
⋅
y
=
17.4285
×
8.619
=
150.2162
z=x \cdot y=17.4285 \times 8.619=150.2162
z=x⋅y=17.4285×8.619=150.2162
这个结果也是与
150
150
150 很接近了,但是精度还不够。再迭代几次,直到误差不大于 1e-4
时,我们就可以结束迭代了,对于计算机来说,这些运算的执行速度很快。
这个问题用数学公式倒推求解一个二次方程,就能直接得到准确的b值吗?是的!但是我们是要说明机器学习的方法,机器并不会解二次方程,而且很多时候不是用二次方程就能解决实际问题的。而上例所示,是用机器所擅长的迭代计算的方法来不断逼近真实解,这就是机器学习的真谛!而且这种方法是普遍适用的。
2.1.4 同时求解 w w w 和 b b b 的变化值
这次我们要同时改变 w w w 和 b b b,到达最终结果为 z = 150 z=150 z=150 的目的。
已知 Δ z = 12 \Delta z=12 Δz=12,我们不妨把这个误差的一半算在 w w w 的账上,另外一半算在 b b b 的账上:
Δ b = Δ z / 2 63 = 12 / 2 63 = 0.095 \Delta b=\frac{\Delta z / 2}{63} = \frac{12/2}{63}=0.095 Δb=63Δz/2=6312/2=0.095
Δ w = Δ z / 2 18 = 12 / 2 18 = 0.333 \Delta w=\frac{\Delta z / 2}{18} = \frac{12/2}{18}=0.333 Δw=18Δz/2=1812/2=0.333
- w = w − Δ w = 3 − 0.333 = 2.667 w = w-\Delta w=3-0.333=2.667 w=w−Δw=3−0.333=2.667
- b = b − Δ b = 4 − 0.095 = 3.905 b = b - \Delta b=4-0.095=3.905 b=b−Δb=4−0.095=3.905
- x = 2 w + 3 b = 2 × 2.667 + 3 × 3.905 = 17.049 x=2w+3b=2 \times 2.667+3 \times 3.905=17.049 x=2w+3b=2×2.667+3×3.905=17.049
- y = 2 b + 1 = 2 × 3.905 + 1 = 8.81 y=2b+1=2 \times 3.905+1=8.81 y=2b+1=2×3.905+1=8.81
- z = x × y = 17.049 × 8.81 = 150.2 z=x \times y=17.049 \times 8.81=150.2 z=x×y=17.049×8.81=150.2
容易出现的问题:
- 在检查 Δ z \Delta z Δz 时的值时,注意要用绝对值,因为有可能是个负数
- 在计算 Δ b \Delta b Δb 和 Δ w \Delta w Δw 时,第一次时,它们对 z z z 的贡献值分别是 1 / 63 1/63 1/63 和 1 / 18 1/18 1/18,但是第二次时,由于 b , w b,w b,w 值的变化,对 z z z 的贡献值也会有微小变化,所以要重新计算。具体解释如下:
∂
z
∂
b
=
∂
z
∂
x
⋅
∂
x
∂
b
+
∂
z
∂
y
⋅
∂
y
∂
b
=
y
⋅
3
+
x
⋅
2
=
3
y
+
2
x
\frac{\partial{z}}{\partial{b}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{b}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{b}}=y \cdot 3+x \cdot 2=3y+2x
∂b∂z=∂x∂z⋅∂b∂x+∂y∂z⋅∂b∂y=y⋅3+x⋅2=3y+2x
∂
z
∂
w
=
∂
z
∂
x
⋅
∂
x
∂
w
+
∂
z
∂
y
⋅
∂
y
∂
w
=
y
⋅
2
+
x
⋅
0
=
2
y
\frac{\partial{z}}{\partial{w}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{w}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{w}}=y \cdot 2+x \cdot 0 = 2y
∂w∂z=∂x∂z⋅∂w∂x+∂y∂z⋅∂w∂y=y⋅2+x⋅0=2y
所以,在每次迭代中,要重新计算下面两个值:
Δ
b
=
Δ
z
3
y
+
2
x
\Delta b=\frac{\Delta z}{3y+2x}
Δb=3y+2xΔz
Δ
w
=
Δ
z
2
y
\Delta w=\frac{\Delta z}{2y}
Δw=2yΔz
以下是程序的运行结果。
没有在迭代中重新计算 Δ b \Delta b Δb 的贡献值:
single variable: b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
delta_b=0.190476
w=3.000000,b=3.809524,z=150.217687,delta_z=0.217687
delta_b=0.003455
w=3.000000,b=3.806068,z=150.007970,delta_z=0.007970
delta_b=0.000127
w=3.000000,b=3.805942,z=150.000294,delta_z=0.000294
delta_b=0.000005
w=3.000000,b=3.805937,z=150.000011,delta_z=0.000011
delta_b=0.000000
w=3.000000,b=3.805937,z=150.000000,delta_z=0.000000
done!
final b=3.805937
在每次迭代中都重新计算 Δ b \Delta b Δb 的贡献值:
single variable new: b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
factor_b=63.000000, delta_b=0.190476
w=3.000000,b=3.809524,z=150.217687,delta_z=0.217687
factor_b=60.714286, delta_b=0.003585
w=3.000000,b=3.805938,z=150.000077,delta_z=0.000077
factor_b=60.671261, delta_b=0.000001
w=3.000000,b=3.805937,z=150.000000,delta_z=0.000000
done!
final b=3.805937
从以上两个结果对比中,可以看到三点:
factor_b
第一次是63
,以后每次都会略微降低一些- 第二个函数迭代了3次就结束了,而第一个函数迭代了5次,效率不一样
- 最后得到的结果是一样的,因为这个问题只有一个解
对于双变量的迭代,有同样的问题:
没有在迭代中重新计算
Δ
b
,
Δ
w
\Delta b,\Delta w
Δb,Δw 的贡献值(factor_b
和factor_w
每次都保持63
和18
):
double variable: w, b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
delta_b=0.095238, delta_w=0.333333
w=2.666667,b=3.904762,z=150.181406,delta_z=0.181406
delta_b=0.001440, delta_w=0.005039
w=2.661628,b=3.903322,z=150.005526,delta_z=0.005526
delta_b=0.000044, delta_w=0.000154
w=2.661474,b=3.903278,z=150.000170,delta_z=0.000170
delta_b=0.000001, delta_w=0.000005
w=2.661469,b=3.903277,z=150.000005,delta_z=0.000005
done!
final b=3.903277
final w=2.661469
在每次迭代中都重新计算
Δ
b
,
Δ
w
\Delta b,\Delta w
Δb,Δw 的贡献值(factor_b
和factor_w
每次都变化):
double variable new: w, b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
factor_b=63.000000, factor_w=18.000000, delta_b=0.095238, delta_w=0.333333
w=2.666667,b=3.904762,z=150.181406,delta_z=0.181406
factor_b=60.523810, factor_w=17.619048, delta_b=0.001499, delta_w=0.005148
w=2.661519,b=3.903263,z=150.000044,delta_z=0.000044
factor_b=60.485234, factor_w=17.613053, delta_b=0.000000, delta_w=0.000001
w=2.661517,b=3.903263,z=150.000000,delta_z=0.000000
done!
final b=3.903263
final w=2.661517
这个与第一个单变量迭代不同的地方是:这个问题可以有多个解,所以两种方式都可以得到各自的正确解,但是第二种方式效率高,而且满足梯度下降的概念
def target_function(w,b):
x = 2*w+3*b
y=2*b+1
z=x*y
return x,y,z
def single_variable(w,b,t):
print("\nsingle variable: b ----- ")
error = 1e-5
while(True):
x,y,z = target_function(w,b)
delta_z = z - t
print("w=%f,b=%f,z=%f,delta_z=%f"%(w,b,z,delta_z))
if abs(delta_z) < error:
break
delta_b = delta_z /63
print("delta_b=%f"%delta_b)
b = b - delta_b
print("done!")
print("final b=%f"%b)
def single_variable_new(w,b,t):
print("\nsingle variable new: b ----- ")
error = 1e-5
while(True):
x,y,z = target_function(w,b)
delta_z = z - t
print("w=%f,b=%f,z=%f,delta_z=%f"%(w,b,z,delta_z))
if abs(delta_z) < error:
break
factor_b = 2*x+3*y
delta_b = delta_z/factor_b
print("factor_b=%f, delta_b=%f"%(factor_b, delta_b))
b = b - delta_b
print("done!")
print("final b=%f"%b)
# this version has a bug
def double_variable(w,b,t):
print("\ndouble variable: w, b -----")
error = 1e-5
while(True):
x,y,z = target_function(w,b)
delta_z = z - t
print("w=%f,b=%f,z=%f,delta_z=%f"%(w,b,z,delta_z))
if abs(delta_z) < error:
break
delta_b = delta_z/63/2
delta_w = delta_z/18/2
print("delta_b=%f, delta_w=%f"%(delta_b,delta_w))
b = b - delta_b
w = w - delta_w
print("done!")
print("final b=%f"%b)
print("final w=%f"%w)
# this is correct version
def double_variable_new(w,b,t):
print("\ndouble variable new: w, b -----")
error = 1e-5
while(True):
x,y,z = target_function(w,b)
delta_z = z - t
print("w=%f,b=%f,z=%f,delta_z=%f"%(w,b,z,delta_z))
if abs(delta_z) < error:
break
factor_b, factor_w = calculate_wb_factor(x,y)
delta_b = delta_z/factor_b/2
delta_w = delta_z/factor_w/2
print("factor_b=%f, factor_w=%f, delta_b=%f, delta_w=%f"%(factor_b, factor_w, delta_b,delta_w))
b = b - delta_b
w = w - delta_w
print("done!")
print("final b=%f"%b)
print("final w=%f"%w)
def calculate_wb_factor(x,y):
factor_b = 2*x+3*y
factor_w = 2*y
return factor_b, factor_w
if __name__ == '__main__':
w = 3
b = 4
t = 150
single_variable(w,b,t)
single_variable_new(w,b,t)
double_variable(w,b,t)
double_variable_new(w,b,t)