用numpy 实现最简单的前馈神经网络——神经网络架构篇
神经网络架构
矩阵运算
我们可以把矩阵看作一个特殊的函数,它的作用是将长度为n 的向量(如下图
A
\bold{A}
A)转化为长度为m 的向量(如下图
Z
\bold{Z}
Z)。
将输入看作一个包含n 个元素的向量,就可以通过多次矩阵运算转化为长度为m 的输出了。
虽然此时输入矩阵(
A
\bold{A}
A)在变换矩阵(
W
\bold{W}
W)的右侧,但是这是可以改变的,在mnist学习实例中我就会将
A
\bold{A}
A放在
W
\bold{W}
W左侧(当然此时m 和n 会发生一些变化)。
(
w
11
w
12
?
w
1
n
w
21
w
22
?
w
2
n
?
?
?
?
w
m
1
w
m
2
?
w
m
n
)
?
[
a
1
a
2
?
a
n
]
+
(
b
1
b
2
?
b
m
)
=
[
z
1
z
2
?
z
m
]
\begin{pmatrix} w_{11} & w_{12} & \cdots & w_{1n} \\ w_{21} & w_{22} & \cdots & w_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ w_{m1} & w_{m2} & \cdots & w_{mn} \\ \end{pmatrix} \cdot \begin{bmatrix} a_{1} \\ a_{2} \\ \vdots \\ a_{n} \\ \end{bmatrix} + \begin{pmatrix} b_{1} \\ b_{2} \\ \vdots \\ b_{m} \\ \end{pmatrix} = \begin{bmatrix} z_{1} \\ z_{2} \\ \vdots \\ z_{m} \\ \end{bmatrix}
??????w11?w21??wm1??w12?w22??wm2???????w1n?w2n??wmn???????????????a1?a2??an????????+??????b1?b2??bm????????=??????z1?z2??zm????????
W
m
×
n
?
A
n
+
B
m
=
Z
m
,
F
(
A
n
)
=
Z
m
.
\bold{W}_{m \times n} \cdot \bold{A}_n + \bold{B}_m= \bold{Z}_m, \\ F(\bold{A}_n) = \bold{Z}_m.
Wm×n??An?+Bm?=Zm?,F(An?)=Zm?.
在Python中,使用矩阵进行向量化编程也是加快学习速度的必要手段
拟合——深度学习的目的
最简单的拟合——线性回归
我们在中学中曾经学习的用一系列数据对来得出一条线性拟合曲线,还使用了最小二乘法。事实上,深度学习做的也是类似的事情。
y
^
=
a
x
+
b
.
\hat{y} = ax+b.
y^?=ax+b.
深度学习中的拟合
不同的是深度学习中不仅需要线性的拟合,也需要非线性的拟合。(这很正常因为自然界很多事不是线性的)
因此我们需要引入非线性的函数来变换向量,就需要激活函数(下图
α
\alpha
α就是一种激活函数),因为如果只有线性的函数的话,就不可能获得非线性的拟合。
y
(
i
)
=
α
(
x
1
(
i
)
w
1
+
x
2
(
i
)
w
2
+
?
+
x
n
(
i
)
w
n
+
b
(
i
)
)
,
α
(
z
)
=
1
1
+
e
?
z
.
y^{(i)}=\alpha(x^{(i)}_1 w_1+x^{(i)}_2 w_2 + \dots + x^{(i)}_n w_n + b^{(i)}), \\ \alpha(z) = \frac{1}{1+e^{-z}}.
y(i)=α(x1(i)?w1?+x2(i)?w2?+?+xn(i)?wn?+b(i)),α(z)=1+e?z1?.
provided?f(x)?and?g(x)?is?linear,
it’s?easy?to?get?that?f(g(x))?is?linear
\text{provided f(x) and g(x) is linear,} \\ \text{it's easy to get that f(g(x)) is linear}
provided?f(x)?and?g(x)?is?linear,it’s?easy?to?get?that?f(g(x))?is?linear
同时我们需要一个标准来衡量拟合的贴合度,就得到了损失函数
均方误差函数
?
(
i
)
(
a
,
b
)
=
1
2
(
y
^
(
i
)
?
y
(
i
)
)
2
,
?
(
a
,
b
)
=
1
n
∑
i
=
1
n
?
(
i
)
(
a
,
b
)
=
1
n
∑
i
=
1
n
1
2
(
a
x
(
i
)
+
b
?
y
(
i
)
)
2
.
\ell^{(i)}(a, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2, \\ \ell(a, b) =\frac{1}{n} \sum_{i=1}^n \ell^{(i)}(a, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(ax^{(i)} + b - y^{(i)}\right)^2.
?(i)(a,b)=21?(y^?(i)?y(i))2,?(a,b)=n1?i=1∑n??(i)(a,b)=n1?i=1∑n?21?(ax(i)+b?y(i))2.
交叉熵损失函数
?
(
i
)
=
1
N
L
(
i
)
=
?
1
N
∑
i
[
y
(
i
)
?
log
?
(
y
^
(
i
)
)
+
(
1
?
y
(
i
)
)
?
log
?
(
1
?
y
^
(
i
)
)
]
.
\ell^{(i)} = \frac{1}{N}L_{(i)}=-\frac{1}{N}\sum_i[y^{(i)} \cdot \log(\hat y^{(i)})+(1-y^{(i)} ) \cdot \log(1-\hat y^{(i)})].
?(i)=N1?L(i)?=?N1?i∑?[y(i)?log(y^?(i))+(1?y(i))?log(1?y^?(i))].
平均损失最小——梯度下降法
和线性回归中相同,要使拟合最贴合,就是在给定输入(A)时,调整参数(W、B)使损失函数最小。
w
1
?
,
w
2
?
,
…
,
w
n
?
,
b
?
=
a
r
g
m
i
n
{
?
(
w
1
,
w
2
,
…
,
w
n
,
b
)
}
.
w^?_1,w^?_2,\dots,w^*_n,b^?=argmin\{?(w_1,w_2,\dots,w_n,b)\}.
w1??,w2??,…,wn??,b?=argmin{?(w1?,w2?,…,wn?,b)}.
使用高等数学的知识,要如何使某个函数取得最小值呢?
画图?求极值点再求最小值点?可惜的是这些方法在这里都行不通(想想那复杂的表达式和万亿计的自变量吧)
最小二乘法也是求损失函数最小值的一种方法
因此我们只能退而求其次,求得一个局部最小值,希望也能拟合得不错,这也就是梯度下降法(偏导数求最小值法)。
让自变量不断沿着梯度的反方向移动,就能获得极小值
f
(
x
,
y
)
=
x
2
+
y
2
,
(
Δ
x
,
Δ
y
)
=
?
η
(
?
f
?
x
,
?
f
?
y
)
.
f(x,y)=x^2+y^2, \\ (\Delta x, \Delta y) = ?η (\frac{\partial f}{\partial x},\frac{\partial f}{\partial y}).
f(x,y)=x2+y2,(Δx,Δy)=?η(?x?f?,?y?f?).
在jupyter notebook 中运行如下代码
%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from pprint import pprint
def f(x, y):
"""待求函数"""
return x ** 2 + y ** 2
def grad(func, *args):
"""求梯度"""
h = 1e-6
args = list(args)
grad = [None for _ in range(len(args))]
for i, arg in enumerate(args):
args[i] = arg + h
f1 = f(*args)
args[i] = arg - h
f2 = f(*args)
args[i] = arg
grad[i] = (f1 - f2) / 2 / h
return grad
fig = plt.figure()
ax = Axes3D(fig)
a = np.arange(-50, 50, 0.1)
b = np.arange(-50, 50, 0.1)
X, Y = np.meshgrid(a, b)
Z = f(X, Y)
xs = [50]
ys = [43]
zs = [f(xs[-1], ys[-1])]
step_length = 0.03
step_num = 50
for _ in range(step_num):
x = xs[-1]
y = ys[-1]
dx, dy = grad(f, x, y)
x -= dx * step_length
y -= dy * step_length
xs.append(x)
ys.append(y)
zs.append(f(x, y))
ax.scatter3D(xs, ys, zs, cmap="Blues")
ax.plot_surface(X, Y, Z, cmap="rainbow")
pprint(list(zip(xs, ys, zs)))
可以看到蓝点一步步的走向极小值。
当然,局部最小值就是极值点,这种方法有可能会陷入某个极小值,错过了最小值。(可以尝试修改一下代码来看看这种情况,比如更改代求函数和学习率)
反向传播和链式法则
反向传播法其实就是利用链式法则用更快的方法求偏导数。但和正向一样使用梯度下降来求损失函数最小值
forward?propagation:?
Δ
x
=
?
f
?
x
=
lim
?
h
→
0
f
(
x
+
h
)
?
f
(
x
?
h
)
2
h
,
back-propagation:?
Δ
x
=
?
f
?
x
=
∏
(
?
f
?
y
,
?
y
?
x
)
.
\text{forward propagation: } \Delta x = \frac{?f}{?x} = \lim_{h \to 0} \frac{f(x+h)-f(x-h)}{2h}, \\ \text{back-propagation: } \Delta x = \frac{?f}{?x}=\prod(\frac{?f}{?y},\frac{?y}{?x}).
forward?propagation:?Δx=?x?f?=h→0lim?2hf(x+h)?f(x?h)?,back-propagation:?Δx=?x?f?=∏(?y?f?,?x?y?).
激活函数和损失函数的选择
def sigmoid(x):
"""激活函数"""
return 1 / (1 + np.exp(-x))
def cross_entropy_error(result, labels):
"""损失函数"""
return -(np.sum(np.log(result[np.arange(labels.size), labels] + 1e-5)))
def soft_max(x):
"""输出函数"""
t = np.exp(x - x.max())
y = np.sum(t, axis=1).reshape(t.shape[0], 1)
return t / y
在mnist实例中,我会使用上面三个函数。根据概率论的某个原理,soft_max() 和cross_entropy_error() 是best match
总结
- 明确输入和输出
- 选择合适的各种函数
- 用矩阵和激活函数建立起从输入到输出的拟合函数
- 用正向传播或反向传播获得损失函数的偏导数(注意对一定的数据集来说自变量为
W
\bold{W}
W,
A
\bold{A}
A固定)
- 用梯度下降法努力使损失函数最小
|