【DL学习笔记03】深度学习入门——基于Python的理论与实现(ch04: Train Neuralnet)
1. 从数据从中学习:数据驱动、训练数据和测试数据;2. loss function:均方误差、交叉熵误差、mini-batch学习、mini-batch版交叉熵误差的实现;3. 数值微分:梯度、梯度法(gradient method)、神经网络的梯度;4. 学习算法的实现:2层神经网络的类、mini-batch的实现、基于测试数据的评价;5. 小结...
目录
- 导入损失函数指标使神经网络能进行学习
- 学习的目的就是以该损失函数为基准,找出能使它的值达到最小的权重系数
1. 从数据从中学习
是神经网络的特征,是指从训练数据中自动获取最优权重系数的过程
数据驱动
- 特征量:指可以从输入数据中准确地提取本质数据的转换器,图像特征量通常表示为向量的形式
- 机器学习的方法中,由机器从收集到的数据中找到规律性
- 神经网络直接学习图像本身,包括特征量。深度学习有时也称为端到端机器学习,也就是从原始数据(输入)中获得目标结果(输出)
训练数据和测试数据
- 用训练数据进行学习,寻找最优的参数;用测试数据评价训练得到的模型的实际能力
- 因此,为了正确评价模型的泛化能力,就必须划分;另外,训练数据也叫监督数据
- 泛化能力:处理未被观察过的数据的能力;获得泛化能力是机器学习的最终目标
- 过拟合:只对某个数据过度拟合的状态;机器学习因避免过拟合
2. loss function
- 神经网络以损失函数为线索寻找最优权重参数,一般用均方误差和交叉熵误差
- 损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合不一致
均方误差
- 公式:
- 其中,
表示神经网络的输出,
表示监督数据,
表示数据的维数
- 均方误差回计算神经网络的输出和正确解监督数据的各个元素之差的平分
def mean_squared_error(y, t):
return 0.5 * np.sum((y - t)**2)
交叉熵误差
- 公式:
- 其中,
表示神经网络的输出,
表示正确解标签,
表示数据的维数
- 并且,
只有正确解标签的索引为1,其它均为0(noe-hot表示)
- 因此该公式只计算对应正确解标签的输出的自然对数
def cross_entropy_error(y, t):
delta = le-7 # 给y加上一个微小值,防止负无限大
return -np.sum(t * np.log(y + delta))
mini-batch学习
-
使用训练数据进行学习严格来说就是针对训练数据计算损失函数的值,找出尽可能小的参数,因此计算损失函数时必须将所有的训练数据作为对象
- 平均损失函数:
- 平均损失函数:
-
当训练数据较大时,可以从中选出一批数据(称为mini-batch,小批量)
-
实现
# mini-batch train_size = x_train.shape[0] batch_size = 10 # choice(a, b)表示从0~(a-1)个数据中随机选择b个数据 batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask]
mini-batch版交叉熵误差的实现
- one-hot
def cross_entropy_error2(y, t):
"""
1.当y的维度为1时,即求单个数据的交叉熵误差时,需要改变数据的形状
2.举例:当y=[0.3, 0.1, 0.6]时,y.shape=(3,),y.shape[0]=3
3.因此要使得shape[0]能够提取出单个数据和批量数据的个数,需要对单个数据用reshape(1, t.size)
"""
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + le-7)) / batch_size
- 标签形式
def cross_entropy_error3(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + le - 7)) / batch_size
-
对
y[np.arange(batch_size), t]
的解释:- 在one-hot表示中,t为0的元素的交叉熵误差也为0,所以针对这些元素的计算是可以忽略的
- 当监督数据是标签形式时,我们需要找到神经网络在正确解标签处的输出
""" 这里我们我们举例: y = np.array([[0,1,0.1,0,0,0,0,0,0,0],[0,0,0.2,0.8,0,0,0,0,0,0]]) t = np.array([1, 3]) batch_size = y.shape[0] = 2 """ # 1.np.arange(a)会生成一个0到a-1的数组 >>>np.arange(batch_size) array([0, 1]) # 2.用y[np.arange(batch_size), t]抽出各个数据的正确解标签对应的神经网络的输出 >>>k = y[np.arange(batch_size), t] # y[[0, 1], [1, 3]] >>>print(k) [1. 0.8]
3. 数值微分
梯度法使用梯度的信息决定前进的方向,以下介绍梯度是什么,有什么性质等
梯度
像$(\frac{\partial f}{\partial {x_0}},\frac{\partial f}{\partial {x_1}})$ 这样由全部变量的偏导数汇总而成的向量称为梯度
-
实现
# 示例函数 def function_2(x): return x[0]**2 + x[1]**2 def numerical_gradient(f, x): h = 1e-4 # 0.0001 grad = np.zeros_like(x) # 生成和x形状相同,所有元素为0的数组 for idx in range(x.size): tmp_val = x[idx] # f(x+h)的计算 x[idx] = tmp_val + h fxh1 = f(x) # f(x-h)的计算 x[idx] = tmp_val - h fxh2 = f(x) grad[idx] = (fxh1 - fxh2) / (2*h) x[idx] = tmp_val # 还原值 return grad
梯度法(gradient method)
-
上述提到:
- 机器学习的主要任务实在学习是寻找最优参数
- 同样的,神经网络也必须在学习时找到最优参数(权重和偏置)
- 这里说的最优参数也就是指损失函数取最小值的参数
- 一般而言损失函数很复杂,而梯度法就是通过巧妙的使用梯度来寻找函数最小值
-
注意:
- 梯度指示的方向是各点处的函数值减小最多的方向,无法保证是函数的最小值或是真正应该前进的方向,但沿着它的方向能够最大限度地减小函数的值
- 函数的极小值、最小值、鞍点的梯度为0
- 鞍点:从某个方向上看是极大值,另一个方向上看是极小值的点
- 当函数很复杂且呈扁平状时,学习可能会进入一个(几乎)平坦的地区,陷入被称为”学习高原“的无法前进的停滞期
- 梯度指示的方向是各点处的函数值减小最多的方向,无法保证是函数的最小值或是真正应该前进的方向,但沿着它的方向能够最大限度地减小函数的值
-
梯度法:函数的取值从当前位置沿着梯度方向前进一段距离,然后在新的地方重新求梯度,再沿着新梯度方向前进一定距离,如此反复,不断沿着梯度方向前进,逐渐减小函数值。
- 寻找最小值的梯度法称为梯度下降法,寻找最大值称为梯度上升法。
- 但是通过反转损失函数的符号,两者无本质区别
- 一般来说,神经网络(深度学习)中,主要指梯度下降法
-
公式:
-
学习率:公式中的
表示更新率,在神经网络的学习中,称为学习率,决定了在一次学习中,应该学习多少,以及在多大程度上更新参数。
- 学习率过大,会发散成一个很大的值;学习率国小,基本没怎么更新就结束了
- 学习中一般会一遍改变学习率的值,以便确认学习是否正确进行
- 学习率这样的参数称为超参数,是人工设定的
-
代码
# 梯度下降法 """ f是要进行最优化的函数 init_x是初始值,lr是学习率(learning rate) step_num是梯度法的重复次数 """ 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 -= lr * grad return x
神经网络的梯度
神经网络的学习也要求梯度,是指损失函数关于权重参数的梯度
-
公式:
-
实现:
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_error3(y, t) return loss
-
分析:
- 假设:结果
,
- 0.2表示如果将
增加 h,那么损失函数的值会增加 0.2h
- -0.5表示如果将
增加 h,那么损失函数的值会减少 0.5h
- 因此,从减少损失函数值的观点来看,
应向正方向更新,
应向反方向更新,至于更新的程度,
比
大
- 假设:结果
-
求出神经网络的梯度后,接下来只需要根据梯度法,更新权重参数即可
4. 学习算法的实现
首先,复习一下神经网络的学习步骤:
- 前提
- 神经网络存在合适的权重和偏置,调整权重和偏置以便你和训练数据的过程称为”学习“
- 步骤1(mini-batch)
- 从训练数据中随机选出一部分数据,这部分数据称为mini-batch
- 我们的目标是减小mini-batch的损失函数的值
- 步骤2(计算梯度)
- 为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度
- 梯度表示损失函数的值减小最多的方向
- 步骤3(更新参数)
- 将权重参数沿梯度方向进行微小更新
- 步骤4(重复)
- 重复上述3个步骤
以上方法称为随机梯度下降法(stochastic gradient descent, SGD)
下面我们来实现手写数字识别的神经网络
2层神经网络的类
import os
import sys
import numpy as np
from ch3_NeuralNetworks.SigmoidFunction import sigmoid
from ch3_NeuralNetworks.SoftmaxFunction import softmax
from ch4_trainNeuralnet.gradient_method import numerical_gradient
from ch4_trainNeuralnet.loss_function import cross_entropy_error
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
class TwoLayerNet:
# 初始化
def __init__(self, input_size, hidden_size, output_size,weight_init_std=0.01):
# params 保存神经网络的参数
# 权重:通过randn(i, h),得到一组i*h的符合标准正态分布的随机数
# 偏置:通过zeros(h),得到一个全为0的矩阵
self.params = {'W1': weight_init_std * np.random.randn(input_size, hidden_size),
'b1': np.zeros(hidden_size),
'W2': weight_init_std * np.random.randn(hidden_size, output_size),
'b2': np.zeros(output_size)}
# 推理,x:图像数据
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
# 计算损失值,x:输入数据,t:监督数据
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=1)
accurancy = np.sum(y == t) / float(x.shape[0])
return accurancy
# 计算权重参数的梯度,x:输入数据,t:监督数据
def numerical_grandient(self, x, t):
loss_W = lambda W: self.loss(x, t) # def loss_W(x):...
# grads 保存梯度
grads = {'W1': numerical_gradient(loss_W, self.params['W1']),
'b1': numerical_gradient(loss_W, self.params['b1']),
'W2': numerical_gradient(loss_W, self.params['W2']),
'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 = self.sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
def sigmoid_grad(x):
return (1.0 - sigmoid(x)) * sigmoid(x)
- 这里
numerical_grandient(self, x, t)
计算梯度基于数值微分,下一章会介绍高速计算梯度的方法,称为误差反向传播法grandient(self, x, t)
- 注:下面的实现,可以发现,
numerical_grandient(self, x, t)
的计算速度很慢很慢,还要迭代10000次,所以,我们先直接使用误差反向传播法grandient(self, x, t)
- 注:下面的实现,可以发现,
mini-batch的实现
-
以上述实现的TwoLayerNet类为对象,使用MNIST数据集进行学习
import numpy as np from ch4_trainNeuralnet.two_layer_net import TwoLayerNet from mnist import load_mnist # 载入数据集,并将图像的像素值正规化为0.0~1.0,标签采用one-hot表示 # (训练图象,训练标签),(测试图象,测试标签) (x_train, t_train), (x_test, t_test) = \\ load_mnist(normalize=True, one_hot_label=True) # 记录学习过程中的损失值 train_loss_list = [] # 超参数 iters_num = 10000 # 迭代次数 train_size = x_train.shape[0] batch_size = 100 # 批数量 learing_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.gradient(x_batch, t_batch) # 更新参数,正方向更新梯度为负的权重,反方向更新梯度为正的权重 for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learing_rate * grad[key] # 记录学习过程 loss = network.loss(x_batch, t_batch) train_loss_list.append(loss)
-
绘制损失函数值的图像,观察学习情况
x = np.arange(iters_num) plt.plot(x, train_loss_list) plt.xlabel("iters_num") # x轴标签 plt.ylabel("loss") # y轴标签 plt.show()
- 可以发现损失函数的值在不断减小,是学习正常进行的信号,表示神经网络的权重参数在逐渐拟合数据,逐渐向最优参数靠近
基于测试数据的评价
- 上述结果并不能说明该神经网络在其他数据集上也一定能有同等程度的表现
- 神经网络的学习中,必须确认是否能正确识别训练数据外的其他数据,即确认是否会发生过拟合
- 过拟合:训练数据中的数字图像能被正确辨别,数据外的无法被识别的现象
- 神经网络学习的最初目标是掌握泛化能力,因此我们需要使用不包含在训练数据中的数据
- 下面代码在进行学习的过程中,会定期(每经过一个epoch)对训练数据和测试数据记录识别精度
- epoch是一个单位,表示学习中所有训练数据均被使用过一次时的更新次数
- 一般为
max(train_size / batch_size, 1)
,因为在循环语句中一直计算识别精度开销太大,且只要大方向上把握识别精度的推移就够了 - 加粗部分为修改部分
import numpy as np
from matplotlib import pyplot as plt
from ch4_trainNeuralnet.two_layer_net import TwoLayerNet
from mnist import load_mnist
# 载入数据集,并将图像的像素值正规化为0.0~1.0,标签采用one-hot表示
# (训练图象,训练标签),(测试图象,测试标签)
(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 # 批数量
learing_rate = 0.1 # 学习率
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
# 记录学习过程中的损失值、**训练数据的识别精度、测试数据的识别精度**
train_loss_list = []
**train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)**
for i in range(iters_num):
print("第", 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_grandient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新参数,正方向更新梯度为负的权重,反方向更新梯度为正的权重
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learing_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)
plt.plot(x, train_loss_list)
plt.xlabel("iters_num") # x轴标签
plt.ylabel("loss") # y轴标签
plt.show()
5. 小结
- 为了顺利进行神经网络的学习,导入指标——损失函数
- 以损失函数为基准,找出使它的值达到最小的参数(神经网络学习的目标)
- 找到尽可能小的损失函数值,使用梯度法
更多推荐
所有评论(0)