- 神经网络的复习
- 1.1 数学和Python的复习
- 1.2 神经网络的推理
- 1.2.1 工作流程
- 1.2.2 激活函数
- 1.2.3 两层网络的大致实现
- 1.2.4 层的类化及正向传播的实现
- 1.3 神经网络的学习
- 1.3.1 损失函数
- 1.3.2 导数和梯度计算基础
- 1.3.4 梯度的推导和反向传播的实现
- 1.3.4.1 Sigmoid 层
- 1.3.4.2 Affine 层
- 1.3.4.3 Softmax with Loss 层
- 1.3.4.4 实现神经网络的学习
- 1.3.5 计算的高速化
- 总结
开始《深度学习进阶》的学习了,但是上一本书的内容也不能忘,这一章将会快速回顾一下上一本书学习的东西,温故知新后我们就可以正式开始这本书的学习了。
1.1 数学和Python的复习向量和矩阵的基本概念这里不再继续赘述,但是要明白一个新的概念张量,说是新的概念但是他其实是我们以前就知道的东西,不过以前没有特别强调出来。
将向量和矩阵扩展到 N 维的数据集合,就是张量。
此外,在Numpy中如果将项链最为行向量处理的情况下,它会将向量明确变形为水平方向上的矩阵,比如有N个数据构成的向量,Numpy会将它变成1 x N的矩阵,这有点像行向量:
>>> import numpy as np >>> x = np.array([1, 2, 3]) >>> x.__class__ # 输出类名1.2 神经网络的推理 1.2.1 工作流程>>> x.shape (3,) >>> x.ndim 1
有了Python的基础和矩阵和向量的相关知识,就可以开始从正向上推理神经网络的工作流程了,一下为全连接方式,它需要有输入,权重,偏置。
(
h
1
,
h
2
,
h
3
,
h
4
)
=
(
x
1
,
x
2
)
(
w
11
w
12
w
13
w
14
w
21
w
22
w
23
w
24
)
+
(
b
1
,
b
2
,
b
3
,
b
4
)
(h_1,h_2,h_3,h_4)=(x_1,x_2)begin{pmatrix} w_{11}&w_{12}&w_{13}&w_{14}\ w_{21}&w_{22}&w_{23}&w_{24}\ end{pmatrix}+(b_1,b_2,b_3,b_4)
(h1,h2,h3,h4)=(x1,x2)(w11w21w12w22w13w23w14w24)+(b1,b2,b3,b4)
由于计算结果是线性的,这样多神经元的优势体现不出来,所以需要将结果转化为非线性结果,这时就需要用到激活函数,比如常见的Sigmoid激活函数:
σ
(
x
)
=
1
1
+
e
x
p
(
−
x
)
sigma(x)= {1over 1+exp(-x)}
σ(x)=1+exp(−x)1
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.random.randn(10, 2)
W1 = np.random.randn(2, 4)
b1 = np.random.randn(4)
W2 = np.random.randn(4, 3)
b2 = np.random.randn(3)
h = np.dot(x, W1) + b1
a = sigmoid(h)
s = np.dot(a, W2) + b2
1.2.4 层的类化及正向传播的实现
我们需要将神经网络的层进行类化,这样做的目的是让神经网络的层变成类模块,这样我们在实现一个新的神经网络的过程就像是搭积木一样方便了。
import numpy as np
class Sigmoid:#激活函数层
def __init__(self):
self.params = []
def forward(self, x):
return 1 / (1 + np.exp(-x))
class Affine:#仿射变换层
def __init__(self, W, b):
self.params = [W, b]
def forward(self, x):
W, b = self.params
out = np.dot(x, W) + b
return out
class TwoLayerNet:#实现图中所示的两层神经网络
def __init__(self, input_size, hidden_size, output_size):
I, H, O = input_size, hidden_size, output_size
# 初始化权重和偏置
W1 = np.random.randn(I, H)
b1 = np.random.randn(H)
W2 = np.random.randn(H, O)
b2 = np.random.randn(O)
# 生成层
self.layers = [
Affine(W1, b1),
Sigmoid(),
Affine(W2, b2)
]
# 将所有的权重整理到列表中
self.params = []
for layer in self.layers:
self.params += layer.params
def predict(self, x):
for layer in self.layers:
x = layer.forward(x)
return x
假设我们有10组数据那么进行批处理计算的网络预测代码如下:
x = np.random.randn(10, 2) model = TwoLayerNet(2, 4, 3) s = model.predict(x)
- 所有的层都有 forward() 方法和 backward() 方法
- 所有的层都有 params 和 grads 实例变量
简单说明一下这个代码规范。首先,forward() 方法和 backward() 方法分别对应正向传播和反向传播。其次,params 使用列表保存权重和偏置等参数(参数可能有多个,所以用列表保存)。grads 以与 params 中的参数对应的形式,使用列表保存各个参数的梯度。
1.3 神经网络的学习前面已经介绍了神经网络的工作流程,但是那个网络只是一个模型,因为它并不能进行正确预测,它的权重参数是人为编的,这里就需要进行神经网络的学习,让神经网络通过数据找到正确的参数。
1.3.1 损失函数使用损失函数的目的是由于我们想在调参数的时候让模型的准确率出现一种线性的变化,这样我们能知道自己调参数的正确程度,而不至于出现一动参数,数据回出现离散的变化,这样我们无法确定调参数调的对不对,他的选择根据输出层激活函数而定。
对于一个多分类问题,我们通常选用Softmax作为输出层激活函数,配套选用交叉熵误差作为损失函数,这样做的目的是让反向传播结果成为Softmax层和监督标签的差分,为了得出这样的结果,人们甚至去专门设计损失函数。本网络将用到Softmax with Loss层,这里的损失函数是Softmax:
y
k
=
e
x
p
(
s
k
)
∑
i
=
1
n
e
x
p
(
s
i
)
y_k={exp(s_k)over sum_{i=1}^nexp(s_i)}
yk=∑i=1nexp(si)exp(sk)
交叉熵误差计算公式为:
E
=
−
∑
k
t
k
l
n
y
k
E=- sum_{k} t_klny_k
E=−k∑tklnyk
将两者扩展到mini-batch的情况下:
E
=
−
1
N
∑
n
∑
k
t
n
k
l
n
y
n
k
E=-{1over N} sum_{n}sum_{k} t_{nk}lny_{nk}
E=−N1n∑k∑tnklnynk
他的工作位置是:
这里主要用到了反向传播求梯度具体内容在上一本书提到过,不过这里需要特别强调一个repeat节点,这个节点关系到上一本书说到的,关于偏置的反向传播,在上一本书上写道在反向传播时,偏置会被加到
X
⋅
W
boldsymbol{X}cdotboldsymbol{W}
X⋅W的各个数据上:
>>> X_dot_W = np.array([[0, 0, 0], [10, 10, 10]])
>>> B = np.array([1, 2, 3])
>>>
>>> X_dot_W
array([[ 0, 0, 0],
[ 10, 10, 10]])
>>> X_dot_W + B
array([[ 1, 2, 3],
[11, 12, 13]])
在反向传播时各个数据的反向传播的值需要汇总为偏置的元素:
>>> dY = np.array([[1, 2, 3,], [4, 5, 6]])
>>> dY
array([[1, 2, 3],
[4, 5, 6]])
>>>
>>> dB = np.sum(dY, axis=0)
>>> dB
array([5, 7, 9])
这个情况当时我不理解,但是结合repeat节点的工作原理就好理解了:
节点正向传播时进行复制,反响传播时进行数据的汇总:
>>> import numpy as np >>> D, N = 8, 7 >>> x = np.random.randn(1, D) # 输入 >>> y = np.repeat(x, N, axis=0) # 正向传播 >>> dy = np.random.randn(N, D) # 假设的梯度 >>> dx = np.sum(dy, axis=0, keepdims=True) # 反向传播1.3.4 梯度的推导和反向传播的实现
有了前面的对与倒数和梯度的理解,后面就方便构建各个层的正向和反向传播了。
1.3.4.1 Sigmoid 层Sigmoid函数反向传播推到出计算结果为:
但是这个结果可以化简:
∂
L
∂
y
e
x
p
(
−
x
)
=
∂
L
∂
y
1
(
1
+
e
x
p
(
−
x
)
)
2
e
x
p
(
−
x
)
=
∂
L
∂
y
1
1
+
e
x
p
(
−
x
)
e
x
p
(
−
x
)
1
+
e
x
p
(
−
x
)
=
∂
L
∂
y
y
(
1
−
y
)
{partial Lover partial y}exp(-x)={partial Lover partial y}{1over(1+exp(-x))^2}exp(-x)\={partial Lover partial y}{1over1+exp(-x)}{exp(-x)over1+exp(-x)}\={partial Lover partial y}y(1-y)
∂y∂Lexp(−x)=∂y∂L(1+exp(−x))21exp(−x)=∂y∂L1+exp(−x)11+exp(−x)exp(−x)=∂y∂Ly(1−y)
有了这些数学理论推导的公式,就可以实现了:
class Sigmoid:
def __init__(self):
self.params, self.grads = [], []
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
1.3.4.2 Affine 层
仿射层是完成前面所说的变换:
根据流程进行实现:
class Affine:
def __init__(self, W, b):
self.params = [W, b]
self.grads = [np.zeros_like(W), np.zeros_like(b)]
self.x = None
def forward(self, x):
W, b = self.params
out = np.dot(x, W) + b
self.x = x
return out
def backward(self, dout):
W, b = self.params
dx = np.dot(dout, W.T)
dW = np.dot(self.x.T, dout)
db = np.sum(dout, axis=0)
self.grads[0][...] = dW#这里的省略号是用到了深复制
self.grads[1][...] = db
return dx
1.3.4.3 Softmax with Loss 层
根据前面提到的公式进行该层的实现:
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None # softmax的输出
self.t = None # 监督数据
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况
dx = (self.y - self.t) / batch_size
else:
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx = dx / batch_size
return dx
1.3.4.4 实现神经网络的学习
我们使用随机梯度下降的方法进行学习:
W
⟵
W
−
η
∂
L
∂
W
boldsymbol{W} longleftarrow boldsymbol{W} -eta { partial L over partial boldsymbol{W}}
W⟵W−η∂W∂L
import numpy as np
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size):
I, H, O = input_size, hidden_size, output_size
# 初始化权重和偏置
W1 = 0.01 * np.random.randn(I, H)
b1 = np.zeros(H)
W2 = 0.01 * np.random.randn(H, O)
b2 = np.zeros(O)
# 生成层
self.layers = [
Affine(W1, b1),
Sigmoid(),
Affine(W2, b2)
]
self.loss_layer = SoftmaxWithLoss()
# 将所有的权重和梯度整理到列表中
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def predict(self, x):
for layer in self.layers:
x = layer.forward(x)
return x
def forward(self, x, t):
score = self.predict(x)
loss = self.loss_layer.forward(score, t)
return loss
def backward(self, dout=1):
dout = self.loss_layer.backward(dout)
for layer in reversed(self.layers):
dout = layer.backward(dout)
return dout
#实现代码
class SGD:
def __init__(self, lr=0.01):
self.lr = lr#学习率
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
# ❶ 设定超参数
max_epoch = 300
batch_size = 30
hidden_size = 10
learning_rate = 1.0
# ❷ 读入数据,生成模型和优化器
x, t = spiral.load_data()
model = TwoLayerNet(input_size=2, hidden_size=hidden_size, output_size=3)
optimizer = SGD(lr=learning_rate)
# 学习用的变量
data_size = len(x)
max_iters = data_size // batch_size
total_loss = 0
loss_count = 0
loss_list = []
for epoch in range(max_epoch):
# ❸ 打乱数据
idx = np.random.permutation(data_size)
x = x[idx]
t = t[idx]
for iters in range(max_iters):
batch_x = x[iters*batch_size:(iters+1)*batch_size]
batch_t = t[iters*batch_size:(iters+1)*batch_size]
# ❹ 计算梯度,更新参数
loss = model.forward(batch_x, batch_t)
model.backward()
optimizer.update(model.params, model.grads)
total_loss += loss
loss_count += 1
# ❺ 定期输出学习过程
if (iters+1) % 10 == 0:
avg_loss = total_loss / loss_count
print('| epoch %d | iter %d / %d | loss %.2f'
% (epoch + 1, iters + 1, max_iters, avg_loss))
loss_list.append(avg_loss)
total_loss, loss_count = 0, 0
1.3.5 计算的高速化
这里计算的告诉化基本延续了以前的方式:
- 降低位精度
- 硬件上使用GPU优化
- 分布式学习
本章主要是对上一本书的快学复习,现在可以开始进一步学习了。



