文章目录
四、神经网络的学习
这一章通过两层神经网络实现对mnist手写数据集的识别,本文是源于《深度学习入门》的学习笔记
若理解困难,参考上一章笔记:深度学习入门-从朴素感知机到神经网络
本文部分数学公式函数用代码实现了,另一些代码在此:数学公式函数
1.损失函数
表示神经网络性能的“恶劣程度”的指标。
1.1均方误差:可以用作损失函数如下图
这里的yk表示神经网络的输出,tk表示监督数据,也就是正确的数据,k表示数据的维度。
1.2 交叉熵误差:也被用作损失函数
注意tk用的时one-hot数组,正确解索引为1其他都为0。
接下来我们用代码实现两个损失函数
def mean_squared_error(y,t): #均方误差
return 0.5*np.sum((y-t)**2)
def cross_entropy_error(y, t):#交叉熵误差
if y.ndim == 1: #变为二维
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size: #将数组变为存储正确索引的一维数组,每一个元素都是
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
#y[np.arange(batch_size),t] 是正确索引对应的y数组值所构成的1维数组,每个元素都是一个数据的正确索引对应的值
#举例
a = np.array([[1,2,3,4,5,6,7,8,9,10],[11,12,13,14,15,16,17,18,19,20]])
b = a[[0,1,0,1,0],[5,6,7,8,9]]
print(b)
#输出结果是[ 6 17 8 19 10]
交叉熵误差看起来有点复杂,因为我们使用了批处理。
使用训练数据进行学习,严格来说, 就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。因此, 计算损失函数时必须将所有的训练数据作为对象。也就是说,如果训练数据 有100个的话,我们就要把这100个损失函数的总和作为学习的指标。
神经网络的学习也是从训练数据中选出一批数据(称为mini-batch,小 批量),然后对每个mini-batch进行学习。比如,从60000个训练数据中随机 选择100笔,再用这100笔数据进行学习。这种学习方式称为mini-batch学习。
2.损失函数的意义
如果我们将精度作为优化参数的目标,那诸多的参数发生的微小改变并不能影响到精度,假如参数变化零点几,精度可能不会发生变化,而损失函数的值却不然。损失函数的值会随着参数变化产生连续的变化,而不是像精度一样离散的变化,我们通过改变参数降低损失函数的值,方法就是寻找某一参数作为自变量求损失函数的导数也就是梯度。不能用阶跃函数做激活函数也是这个道理,阶跃函数会使大部分参数的导数为0,没办法进行优化。之后会详细说明,此处简单了解。
3.数值微分
3.1 导数 :导数公式我们都知道,我们用代码实现一下导数公式
def numerical_diff(f,x):
h = 1e-4 #0.0001 计算机所能正确表示的微小值
return (f(x+h)-f(x-h))/2*h #与正常的前向差分不同,我们采取中心差分求导数,h不可能真的趋近去穷小,实验中 中心差分更近似
3.2 我们来实践一下求导数y=0.01x**2+0.1x
def numerical_diff(f,x):
h = 1e-4
return (f(x+h)-f(x-h))/(2*h)
def function_1(x):
return 0.01*x**2+0.1*x
print(numerical_diff(function_1,5))#0.1999999999990898
print(numerical_diff(function_1,10))#0.2999999999986347
3.3 偏导数:我们来看这样一个函数如下图
和上面的函数不同,它有两个参数,用代码实现如下
def function_2(x):
return x[0]**2+x[1]**2
该函数图像如下
如果我们来求该函数在x0=3,x1=4时的偏导数,那就需要重新定义下函数
def function_4(x0):
return x0**2+4.0**2
print(numerical_diff(function_4,3.0))
#结果是6.00000000000378
3.4 梯度
刚才我们求了x0=3时的偏导数,现在我们考虑求x0=3,x1=4时(x0,x1)的偏导数(x0偏导数,x1偏导数),像这样由全部变量的偏导数汇总而成的向量称为梯度。
接下来我们用代码来实现一下
def numerical_gradient(f,x):
h = 1e-4
grad = np.zeros_like(x)
for idx in range(x.size):
tem_val = x[idx]
x[idx] = tem_val + h
fxh1 = f(x)
x[idx] = tem_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tem_val
return grad
这段求梯度的函数代码看似复杂,其实很简单,然后我们求一下梯度
def function_2(x):
return np.sum(x**2)
print(numerical_gradient(function_2,np.array([3.0,4.0])))
print(numerical_gradient(function_2,np.array([0.0,2.0])))
print(numerical_gradient(function_2,np.array([3.0,0.0])))
#结果是 [6. 8.]
# [0. 4.]
# [6. 0.]
然后我们画出这个函数的梯度图,注意画的是元素值为负梯度的向量,代码较复杂,暂时不必掌握
虽然图中的梯度指向了最低处,但并非任何时候都这样。实际上, 梯度会指向各点处的函数值降低的方向。更严格地讲,梯度指示的方向 是各点处的函数值减小最多的方向 A。这是一个非常重要的性质,请一定 牢记
4.梯度法
4.1梯度法
机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必 须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必 须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数。
通过巧妙地使用梯度来寻找函数最小值 (或者尽可能小的值)的方法就是梯度法。
这里需要注意的是,梯度表示的是各点处的函数值减小最多的方向。因此, 无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。实际 上,在复杂的函数中,梯度指示的方向基本上都不是函数值最小处。
虽然梯度的方向并不一定指向最小值,但沿着它的方向能够最大限度地 减小函数的值。因此,在寻找函数的最小值(或者尽可能小的值)的位置的 任务中,要以梯度的信息为线索,决定前进的方向
通过不断地沿梯度方向前进, 逐渐减小函数值的过程就是梯度法。梯度法是解决机器 学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用。
我们用数学式子来表示梯度法
η表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。一般确定为某个值比如0.01或0.001。
下面我们来实现以下梯度下降法
#梯度下降法 --- 我们如果用二维函数就会很容易理解梯度下降法例如y=x
#虽然y = x永远找不到最小值
def gradient_descent(f,init_x,lr = 0.01,step_num = 100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x = x-lr*grad
return x
然后我们来尝试解决一个问题,求f(x0,x1) = x0* *2+x1 * *2的最小值
def function_2(x):
return np.sum(x**2)
print(gradient_descent(function_2,init_x = np.array([-3.0,4.0]),lr = 0.1,step_num = 100))
# 结果是 [-6.11110793e-10 8.14814391e-10]
结果很接近(0,0),实际上(0,0)就是最小值。用途来表示下梯度法的更新过程如下,代码较为复杂暂时不需要掌握
接下来我们做个实验,将学习率lr改变一下
print(gradient_descent(function_2,init_x = np.array([-3.0,4.0]),lr = 10.0,step_num = 100))
#结果是[-2.58983747e+13 -1.29524862e+12]
print(gradient_descent(function_2,init_x = np.array([-3.0,4.0]),lr = 1e-10,step_num = 100))
#结果是[-2.99999994 3.99999992]
实验结果表明,学习率过大的话,会发散成一个很大的值;反过来,学 习率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率 是一个很重要的问题。
4.2神经网络的梯度
神经网络的学习也要求梯度。这里所说的梯度是指损失函数关于权重参 数的梯度。比如,有一个只有一个形状为2 × 3的权重W的神经网络,损失 函数用L表示。此时,梯度可以用 表示。用数学式表示的话,如下所示。
下面我们用代码实现求神经网络的梯度
from fuction import softmax,cross_entropy_error,numerical_gradient
import numpy as np
class simpleNet:
def __init__(self):
#self.W = np.random.randn(2,3)# 用高斯分布进行初始化 为了保证数据一致,我们自定义一个参数W
self.W = np.array([[0.47355232,0.9977393,0.84668094],
[0.85557411,0.03563661,0.69422093]])
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
net = simpleNet()
print(net.W)#随机生成的权重参数
x = np.array([0.6,0.9]) #输入数据
p = net.predict(x) #用当前权重进行推理 得到结果
print(p)
print(np.argmax(p))#获取结果索引
#结果如下
"""[[0.47355232 0.9977393 0.84668094]
[0.85557411 0.03563661 0.69422093]]
[1.05414809 0.63071653 1.1328074 ]
2
"""
我们设置正确解标签为[0,0,1]来求一下损失函数
t = np.array([0,0,1])
print(net.loss(x,t))
#结果是
#0.9280682857864075
然后我们来求一下这个损失函数在值为x时的各个元素的偏导数,也就是梯度。在求梯度时我们需要改变一下求梯度函数,因为参数数组不是一维了,遍历元素的方式需要改变一下。
# 求梯度
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
#it是获得的迭代器,在下面循环中会便利每一个元素 idx是元素的索引例如(0,1)
#这样每一个元素都求偏导数就是求梯度
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
def f(W): #这里定义的函数f(W)的参数W是一个伪参数。因为numerical_gradient(f,x)会在内部执行f(x),为了与之兼容而定义了f(W)
return net.loss(x,t)
# 其实我们不用管f(W),只需要知道它是一个函数就行,其他的是为了适应python代码而做出的修改。
dW = numerical_gradient(f,net.W)
print(dW)
"""
结果
[[ 0.21924757 0.14356243 -0.36281 ]
[ 0.32887136 0.21534364 -0.544215 ]]
"""
求梯度之后我们就可以”学习“了。
5.学习算法的实现
神经网络的学习 步骤如下所示。
前提 :神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的 过程称为“学习”。神经网络的学习分成下面4个步骤。
步骤1(mini-batch): 从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们 的目标是减小mini-batch的损失函数的值。
步骤2(计算梯度) :为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。 梯度表示损失函数的值减小最多的方向。
步骤3(更新参数): 将权重参数沿梯度方向进行微小更新
步骤4(重复): 重复步骤1、步骤2、步骤3。
5.1下面我们以两层神经网络为对象,使用mnist数据集进行学习。
另外:如何设置权重参数 的初始值这个问题是关系到神经网络能否成功学习的重要问题。后面我 们会详细讨论权重参数的初始化,这里只需要知道,权重使用符合高斯 分布的随机数进行初始化,偏置使用0进行初始化。
5.2 mini-batch的实现
首先我们实现一个两层神经网络的类
import numpy as np
from deeplearning.fuction import softmax,sigmoid,cross_entropy_error,numerical_gradient,sigmoid_grad
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)
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
#计算损失函数
def loss(self,x,t):
y = self.predict(x)
return cross_entropy_error(y,t)
#计算正确率
def accuracy(self,x,t):
y = self.predict(x)
y = np.argmax(y,axis=1)
t = np.argmax(t,axis=2)
accuracy = np.sum((y==t)/float(x.shape[0]))
return accuracy
#数值微分法求梯度,但是速度太慢,不建议用
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):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
然后我们来用数据训练这个神经网络,并将损失函数的值随训练次数的变化画出来
import matplotlib.pyplot as plt
import numpy as np
from deeplearning.mnist import load_mnist
from tow_layer_net import TwoLayerNet
(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True,one_hot_label=True)
#这一步是从mnist数据集中读取数据,读取数据的代码暂时不用掌握,只需要知道x_train是数据集,t_train是正确标签集
#关于测试数据集和测试标签集本段代码并没有用到
train_loss_list = []
# 超参数
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
count = 0
# 创建网络
network = TwoLayerNet(input_size=784,hidden_size=50,output_size=10)
print("数据正在训练中请稍后...")
for i in range(iters_num): #该循环进行一万次mini_batch数据量的学习
# 获取mini_batch
if (i % 1000 == 0):
print("已经完成"+str(i)+"次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)#实在太慢,跑了将近两个多小时没有完成1000mini_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)
print("数据训练结束,开始画图")
x = np.arange(len(train_loss_list))
print(len(train_loss_list))
plt.xlabel("mini_batch学习次数")
plt.ylabel("损失函数")
plt.title("梯度下降法")
print("..")
plt.plot(x,train_loss_list)
plt.show()
图像如下所示:最终结果损失函数值已经很低了,但以后我们还可以更低。
5.3 基于测试数据的评价
神经网络学习的最初目标是掌握泛化能力,因此,要评价神经网络的泛 化能力,就必须使用不包含在训练数据中的数据。下面的代码在进行学习的 过程中,会定期地对训练数据和测试数据记录识别精度。这里,每经过一个 epoch,我们都会记录下训练数据和测试数据的识别精度。一个epoch指所有数据均被学习一次时的更新次数。
实际上,一般做法是事先将所有训练数据随机打乱,然后按指定的批次大小,按序生成mini-batch。 这样每个mini-batch均有一个索引号,比如此例可以是0, 1, 2, … , 99,然后用索引号可以遍历所有 的mini-batch。遍历一次所有数据,就称为一个epoch。请注意,本节中的mini-batch每次都是随机 选择的,所以不一定每个数据都会被看到。
我们稍微修改一下训练代码如下
import matplotlib.pyplot as plt
import numpy as np
from deeplearning.mnist import load_mnist
from tow_layer_net import TwoLayerNet
(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True,one_hot_label=True)
# 超参数
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
count = 0
train_loss_list = []
"""
增加两个列表,一个参数
该参数指一个epoch需要多少次mini_batch
"""
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size/batch_size,1)
# 超参数
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
count = 0
# 创建网络
network = TwoLayerNet(input_size=784,hidden_size=50,output_size=10)
print("数据正在训练中请稍后...")
for i in range(iters_num): #该循环进行一万次mini_batch数据量的学习
# 获取mini_batch
if (i % 1000 == 0):
print("已经完成"+str(i)+"次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)
#计算每个epoch的识别精度
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 | "+str(train_acc)+","+str(test_acc))
print("数据训练结束,开始画图")
x = np.arange(iters_num/iter_per_epoch)
y1 = train_acc_list
y2 = test_acc_list
plt.plot(x,y1,label="train")
plt.plot(x,y2,linestyle="--",label="test")
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.legend()
plt.show()
"""
我们将损失函数的图注释
x = np.arange(len(train_loss_list))
print(len(train_loss_list))
plt.xlabel("mini_batch学习次数")
plt.ylabel("损失函数")
plt.title("梯度下降法")
print("..")
plt.plot(x,train_loss_list)
plt.show()
"""
运行结果如下:
数据正在训练中请稍后…
已经完成0次mini_batch…
train acc, test_acc | 0.11236666666666668,0.1135
train acc, test_acc | 0.7795333333333336,0.7851000000000004
已经完成1000次mini_batch…
train acc, test_acc | 0.8770833333333335,0.8816000000000004
train acc, test_acc | 0.9000333333333336,0.9017000000000004
已经完成2000次mini_batch…
train acc, test_acc | 0.9088500000000003,0.9111000000000004
已经完成3000次mini_batch…
train acc, test_acc | 0.9145666666666667,0.9167000000000003
train acc, test_acc | 0.9203166666666669,0.9212000000000002
已经完成4000次mini_batch…
train acc, test_acc | 0.9244666666666668,0.9254000000000002
train acc, test_acc | 0.927666666666667,0.9285000000000002
已经完成5000次mini_batch…
train acc, test_acc | 0.9312000000000002,0.9317000000000002
已经完成6000次mini_batch…
train acc, test_acc | 0.9332500000000002,0.9340000000000003
train acc, test_acc | 0.9358000000000002,0.9356000000000003
已经完成7000次mini_batch…
train acc, test_acc | 0.9382333333333335,0.9387000000000003
train acc, test_acc | 0.9406000000000003,0.9404000000000003
已经完成8000次mini_batch…
train acc, test_acc | 0.9427000000000003,0.9418000000000004
已经完成9000次mini_batch…
train acc, test_acc | 0.9446833333333337,0.9433000000000002
train acc, test_acc | 0.946866666666667,0.9436000000000002
数据训练结束,开始画图
实线表示训练数据的识别精度,虚线表示测试数据的识别精 度。如图所示,随着epoch的前进(学习的进行),我们发现使用训练数据和 测试数据评价的识别精度都提高了,并且,这两个识别精度基本上没有差异(两 条线基本重叠在一起)。因此,可以说这次的学习中没有发生过拟合的现象。
如此,我们通过mnist数据集识别图像的小实践已经完成了,不过我们对代码中用的误差反向传播法还不怎么了解,接下来会了解一下误差反向传播。