接着《深度学习入门:基于Python的理论与实现》学习笔记(1)继续学习笔记的记载。
(此笔记是基于《深度学习入门》这本书的重点知识汇总。)
以下主要记录了:
如有细节处没有写到的,请继续精读《深度学习入门:基于Python的理论与实现》,对小白来说真的是非常好的深度学习的入门书籍,通俗易懂。(书中的例子主要是基于CV的)
为什么要计算梯度?
前文我们已经说过,用损失函数来描述神经网络的好坏。损失函数越小,神经网络越好。
那么如何去寻找这个损失函数的最小值讷?也就是说如何找一个函数的最小值。
这种方法就叫梯度下降法(SGD):寻找最小值的梯度法。
图 数学式表示梯度法
学习率lr (η):梯度下降的速度。需要自己设定,不能太大,不能太小。
Python来实现数值微分计算梯度的梯度下降法:
def gradient_descent(f, init_x, lr=0.01, step_num=100): # step_num为更新次数
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
缺点:
但是通过数值微分计算梯度比较费时间,之后介绍的误差反向传播法会更高效。
即求损失函数L关于权重参数的梯度
图 损失函数L关于权重参数w的数值微分法计算梯度
以下是利用数值微分求梯度,实现简单神经网络的代码:
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 用高斯分布进行初始化(正态分布)
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t)
return loss
也就是梯度下降法逐渐更新函数的过程
神经网络的学习分成下面4个步骤。
步骤1(mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch的损失函数的值。
步骤2(计算梯度)
为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
步骤3(更新参数)
将权重参数沿梯度方向进行微小更新。
步骤4(重复)
重复步骤1、步骤2、步骤3。
2层神经网络的实现代码:
import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,
weight_init_std=0.01):
# 1、初始化权重
self.params = {
}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 2、神经网络的推理过程(已知权重参数,进行前向传播)
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# 3、损失函数的选择
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
# 4、网络的识别精度,判断网络的精确度
def accuracy(self, x, t): #
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# 利用数值微分求梯度
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {
}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
就是从训练数据中随机选择一部分数据(称为mini-batch),再以这些mini-batch为对象,使用梯度法更新参数的过程。
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 加载输入训练数据
(x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_hot_
laobel = True)
train_loss_list = []
# 超参数
iters_num = 10000 # 使用随机梯度下降法(SGD)更新参数、循环10000次
train_size = x_train.shape[0]
batch_size = 100 # 随机选择100笔mini-batch数据
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
for i in range(iters_num):
# 获取mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
grad = network.numerical_gradient(x_batch, t_batch)
# grad = network.gradient(x_batch, t_batch) # 计算梯度的高速版!也就是接下来会说的反向传播法
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 记录学习过程,不断拟合的过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
通过数值微分计算梯度比较费时间,误差反向传播法更高效。
计算图:将计算过程用图形表示出来。就像流程图一样。由节点,输入输出和箭头来表示他们之间的关系。
图 基于计算图来求解买苹果需要多少钱的问题
图中所示就是一个简单的计算图,每个圈圈相当于一个节点,通过加减乘除等运算来表示输入之间的关系;箭头上的数字表示输入的权重。比如输入为“苹果”,价格(权重)为100元一个;还有一条输入是“苹果的个数”,个数(权重)为两个,得到第一个乘法节点的输出为200;以此类推加上消费税输入,经过多个节点计算,得到最终买苹果的价格(输出)。
反向传播,顾名思义,由输出传回输入,也就是说已知输出,求出输入。
上图反向传播的计算顺序:
对于简单层:
Relu层反向传播的代码实现:
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
总结:ReLU层像电路中的开关
已知sigmoid的y=1/(1+exp(-x)),带入式子化简一下得到下式,即为sigmoid层的反向传播:
用Python实现反向传播的Sigmoid层
#(实现的代码在 common/layers.py中)
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out # 正向传播时将输出 y 保存在了实例变量 out中
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine仿射变换: y = xw+b。就是神经网络的正向传播中进行的矩阵的乘积运算
其中:W^T的T表示转置。
这里还要注意矩阵的形状,为什么要注意矩阵的形状呢?因为矩阵的乘积运算要求对应维度的元素个数保持一致,所以也就能推导出上式的乘积顺序为什么一个在前,一个在后了。因为只有这样才能实现矩阵乘法,得到正确的矩阵形状。
Affine层的反向传播的实现:
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
def forward(self, x):
self.x = x
out = np.dot(x, self.W) + self.b
return out
def backward(self, dout): # 反向传播
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
中间的过程推导很长,在书中的附录A中可参考,但是这里我们只需要记住这个看起来特别简单的结论即可。
Softmax-with-Loss层的反向传播代码实现
class SoftmaxWithLoss:
def __init__(self):
self.loss = None # 损失
self.y = None # softmax的输出
self.t = None # 监督数据(one-hot vector)
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]
dx = (self.y - self.t) / batch_size # 反向传播
return dx
通过像组装乐高积木一样组装每一层,可以最终构建神经网络。
接下来可以通过组装已经实现的层来构建神经网络。
神经网络学习的步骤如下所示(又出现了,这个很重要):
前提
神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面4个步骤。
步骤1(mini-batch)
从训练数据中随机选择一部分数据。
步骤2(计算梯度)
计算损失函数关于各个权重参数的梯度。(计算梯度:数值微分法和误差反向传播法)
步骤3(更新参数)
将权重参数沿梯度方向进行微小的更新。
步骤4(重复)
重复步骤1、步骤2、步骤3。
和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度。
对应误差反向传播法的两层神经网络的实现
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,
weight_init_std=0.01): # 进行属性的初始化
# 参数从头开始依次是输入层的神经元数、隐藏层的神经元数、输出层的神经元数、初始化权重时的高斯分布的规模
# 初始化权重
self.params = {
}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 生成所需要的层
self.layers = OrderedDict() # 有序字典
self.layers['Affine1'] = \ # w1*x + b1
Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = \ # w2*x + b2
Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss() #SoftmaxWithLoss层
def predict(self, x): # 前向传播
for layer in self.layers.values():
x = layer.forward(x)
return x
# 计算损失函数的值。
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
# 计算识别精度
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
if t.ndim != 1 : t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
# 数值微分法计算梯度
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {
}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
# 利用误差反向传播法计算关于权重参数的梯度
def gradient(self, x, t):
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse() # 反转列表的顺序,反向传播只需要按照相反的顺序条用各层即可
for layer in layers:
dout = layer.backward(dout)
# 设定
grads = {
}
grads['W1'] = self.layers['Affine1'].dW
grads['b1'] = self.layers['Affine1'].db
grads['W2'] = self.layers['Affine2'].dW
grads['b2'] = self.layers['Affine2'].db
return grads
以上做的的事情仅仅是以正确的顺序连接各层,再按顺序(或者逆序)调用各层。
如果想另外构建一个神经网络(比如5层、10层、20层……的大的神经网络)时,只需像组装乐高积木那样添加必要的层就可以了。
数值微分的实现简单,因此,一般情况下不太容易出错。而误差反向传播法的实现很复杂,容易出错。
如何检查误差反向传播法梯度算对没有?:
梯度确认的代码实现如下所示:
# (源代码在 ch05/gradient_check.py中)。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_
hot_label = True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
# 求各个权重的绝对误差的平均值
for key in grad_numerical.keys():
diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
print(key + ":" + str(diff))
这个学习过程中只是求梯度的方法与之前不同。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 通过误差反向传播法求梯度
grad = network.gradient(x_batch, t_batch)
# 更新
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print(train_acc, test_acc)
总结:
我们学过的层有 ReLU 层、Softmax-with-Loss 层、Affine 层、Softmax 层等,这些层中实现了 forward和 backward方法,通过将数据正向和反向地传播,可
以高效地计算权重参数的梯度。
通过使用层进行模块化,神经网络中可以自由地组装层,轻松构建出自己喜欢的网络。
注:如有细节处没有写到的,请继续精读《深度学习入门》,对小白来说真的是非常通俗易懂的深度学习入门书籍。(书中的例子主要是基于CV的)