前言
这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程。
为了使神经网络能进行学习,将导入损失函数这一指标
为了找出尽可能小的损失函数的值,本章我们将介绍利用了函数斜率的梯度法
从数据中学习
本章将介绍神经网络的学习,即利用数据决定参数值的方法,并用Python 实现对MNIST手写数字数据集的学习。
数据驱动
数据是机器学习的核心
机器学习的方法中,由机器从收集到的数据中找出规律性。与从零开始想出算法相比,这种方法可以更高效地解决问题,也能减轻人的负担
将图像转换为向量时使用的特征量仍是由人设计的
对于不同的问题,必须使用合适的特征量(必须设计专门的特征量),才能得到好的结果
神经网络直接学习图像本身,连图像中包含的重要特征量也都是由机器来学习的。
[!IMPORTANT]
深度学习有时也称为端到端机器学习(end-to-end machine learning)。这里所说的端到端是指从一端到另一端的意思,也就是从原始数据(输入)中获得目标结果(输出)的意思
神经网络的优点是对所有的问题都可以用同样的流程来解决:都是通过不断地学习所提供的数据,尝试发现待求解的问题的模式
与待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端”的学习。
训练数据和测试数据
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试数据评价训练得到的模型的实际能力
泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的能力
只对某个数据集过度拟合的状态称为过拟合(over fitting)。避免过拟合也是机器学习的一个重要课题
损失函数
神经网络以某个指标为线索寻找最优权重参数。神经网络的学习中所用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,但一般用均方误差和交叉熵误差等。
均方误差 mean squared error
def mean_squared_error(y, t):
return 0.5 * np.sum((y-t)**2)
交叉熵误差 cross entropy error
式(4.2)实际上只计算对应正确解标签的输出的自然对数
def cross_entropy_error(y, t):
delta = 1e-7 #方式概率为0时 log输出负无穷大 -inf
return -np.sum(t * np.log(y + delta))
mini-batch学习
计算损失函数时必须将所有的训练数据作为对象
如果以全部数据为对象求损失函数的和,则计算过程需要花费较长的时间。
神经网络的学习是从训练数据中选出一批数据(称为mini-batch, 小批量),然后对每个mini-batch 进行学习。比如,从60000 个训练数据中随机选择100 笔,再用这100 笔数据进行学习。这种学习方式称为mini-batch 学习。
随机选择指定个数数据的代码
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)
train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
使用np.random.choice()
可以从指定的数字中随机选择想要的数字
mini-batch 的损失函数是利用一部分样本数据来近似地计算整体,用随机选择的小批量数据(mini-batch)作为全体训练数据的近似值。
mini-batch版交叉熵误差的实现
同时处理单个数据和批量数据(数据作为batch 集中输入)两种情况的函数:
def cross_entropy_error(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(t * np.log(y + 1e-7)) / batch_size
当监督数据是标签形式(非one-hot表示)
def cross_entropy_error(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] + 1e-7)) / batch_size
疑难点
np.log(y[np.arange(batch_size),t])
有些不好理解
如果可以获得神经网络在正确解标签处的输出,就可以计算交叉熵误差
y还是一个二维数组,表示神经网络的输出,并不是一个单纯的标签,[0.1,0.05,...,0.01]
所以y[np.arange(batch_size),t]
可以直接按照y的顺序取出t所指的位置的概率
为何要设定损失函数
“为什么要导入损失函数呢?”
以数字识别任务为例,我们想获得的是能提高识别精度的参数,特意再导入一个损失函数不是有些重复劳动吗?
在进行神经网络的学习时,不能将识别精度作为指标。因为如果以识别精度为指标,则参数的导数在绝大多数地方都会变为0。
识别精度对微小的参数变化基本上没有什么反应,即便有反应,它的值也是不连续地、突然地变化。作为激活函数的阶跃函数也有同样的情况。出于相同的原因,如果使用阶跃函数作为激活函数,神经网络的学习将无法进行。
而sigmoid 函数,如图4-4 所示,不仅函数的输出(竖轴的值)是连续变化的,曲线的斜率(导数)也是连续变化的。sigmoid 函数的导数在任何地方都不为0。这对神经网络的学习非常重要。得益于这个斜率不会为0 的性质,神经网络的学习得以正确进行
数值微分 numerical differentiation
导数
# 不好的实现示例
def numerical_diff(f, x):
h = 10e-50
return (f(x+h) - f(x)) / h
>>> np.float32(1e-50)
0.0
h使用了10e-50这个微小值,会产生舍入误差,因省略小数的精细部分的数值而造成的计算结果上的误差
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
以x 为中心,计算它左右两边的差分,所以也称为中心差分(而(x + h) 和x之间的差分称为
前向差分)
[!IMPORTANT]
利用微小的差分求导数的过程称为数值微分(numerical differentiation)。
而基于数学式的推导求导数的过程,则用“解析性”(analytic)一词,称为“解析性求解”或者“解析性求导”。比如,y = x^2 的导数,可以通过
dy/dx=2x
解析性地求解出来。因此,当x = 2时,y的导数为4。解析性求导得到的导数是不含误差的“真的导数”
数值微分的例子
def function_1(x):
return 0.01*x**2 + 0.1*x
>>> numerical_diff(function_1, 5)
0.1999999999990898
>>> numerical_diff(function_1, 10)
0.2999999999986347
# 与解析解相比,0.2,0.3,误差小到基本上可以认为是相等的
偏导数
def function_2(x):
return x[0]**2 + x[1]**2
# 或者return np.sum(x**2)
把有多个变量的函数的导数称为偏导数
∂
f
∂
x
0
\frac{\partial f}{\partial x_0}
∂x0∂f
偏导数和单变量的导数一样,都是求某个地方的斜率
偏导数需要将多个变量中的某一个变量定为目标变量,并将其他变量固定为某个值
梯度
( ∂ f ∂ x 0 , ∂ f ∂ x 1 ) (\frac{\partial f}{\partial x_0},\frac{\partial f}{\partial x_1}) (∂x0∂f,∂x1∂f)
由全部变量的偏导数汇总而成的向量称为梯度(gradient)
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
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
画是元素值为负梯度的向量
梯度呈现为有向向量(箭头)。观察图4-9,我们发现梯度指向函数f(x0,x1) 的“最低处”(最小值),就像指南针一样,所有的箭头都指向同一点。其次,我们发现离“最低处”越远,箭头越大。
虽然图4-9 中的梯度指向了最低处,但并非任何时候都这样。实际上,梯度会指向各点处的函数值降低的方向。更严格地讲,梯度指示的方向是各点处的函数值减小最多的方向A。这是一个非常重要的性质,请一定牢记!
梯度法
一般而言,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法
梯度表示的是各点处的函数值减小最多的方向,无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。实际上,在复杂的函数中,梯度指示的方向基本上都不是函数值最小处
[!NOTE]
函数的极小值、最小值以及被称为鞍点(saddle point)的地方,梯度为0
虽然梯度法是要寻找梯度为0 的地方,但是那个地方不一定就是最小值(也有可能是极小值或者鞍点)
此外,当函数很复杂且呈扁平状时,学习可能会进入一个(几乎)平坦的地区,陷入被称为“学习高原”的无法前进的停滞期。
通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。
梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用
[!IMPORTANT]
根据目的是寻找最小值还是最大值,梯度法的叫法有所不同
寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。
但是通过反转损失函数的符号,求最小值的问题和求最大值的问题会变成相同的问题,因此“下降”还是“上升”的差异本质上并不重要。
一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法。
η表示更新量,在神经网络的学习中,称为学习率(learning rate)
学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数
每一步都按式(4.7)更新变量的值,通过反复执行此步骤,逐渐减小函数值。
学习率需要事先确定为某个值,比如0.01 或0.001。一般而言,这个值过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会一边改变学习率的值,一边确认学习是否正确进行了。
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
参数f 是要进行最优化的函数,init_x 是初始值,lr 是学习率learningrate,step_num 是梯度法的重复次数。
设定合适的学习率是一个很重要的问题
[!IMPORTANT]
像学习率这样的参数称为超参数
相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。
一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。
神经网络的梯度
这里所说的梯度是指损失函数关于权重参数的梯度
∂
L
∂
w
11
\frac {\partial L}{\partial w_{11}}
∂w11∂L
表示当w11稍微变化时,损失函数L关于发生多大变化,重点是,4.8两式形状相同
以一个简单的神经网络为例,来实现求梯度的代码
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
>>> net = simpleNet()
>>> print(net.W) # 权重参数
[[ 0.47355232 0.9977393 0.84668094],
[ 0.85557411 0.03563661 0.69422093]])
>>>
>>> x = np.array([0.6, 0.9])
>>> p = net.predict(x)
>>> print(p)
[ 1.05414809 0.63071653 1.1328074]
>>> np.argmax(p) # 最大值的索引
2
>>>
>>> t = np.array([0, 0, 1]) # 正确解标签
>>> net.loss(x, t)
0.92806853663411326
#这里的参数W是一个伪参数,因为numerical_gradient(f,x)会在内部执行f(x), 为了与之兼容而定义了f(W)
>>> def f(W):
... return net.loss(x, t)
...
#lambda 写法 f = lambda w: net.loss(x, t)
>>> dW = numerical_gradient(f, net.W)
>>> print(dW)
[[ 0.21924763 0.14356247 -0.36281009]
[ 0.32887144 0.2153437 -0.54421514]]
求出神经网络的梯度后,接下来只需根据梯度法,更新权重参数即可。
[!IMPORTANT]
为了对应形状为多维数组的权重参数W,这里使用的numerical_gradient()和之前的实现稍有不同
学习算法的实现
这里我们来确认一下神经网络的学习步骤,顺便复习一下这些内容
- 前提
- 神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面4 个步骤。
- 步骤1(mini-batch)
- 从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch 的损失函数的值。
- 步骤2(计算梯度)
- 为了减小mini-batch 的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
- 步骤3(更新参数)
- 将权重参数沿梯度方向进行微小更新。
- 步骤4(重复)
- 重复步骤1、步骤2、步骤3。
神经网络的学习按照上面4 个步骤进行,因为这里使用的数据是随机选择的mini batch 数据,所以又称为随机梯度下降法(stochastic gradient descent)。
“对随机选择的数据进行的梯度下降法”
深度学习的很多框架中,随机梯度下降法一般由一个名为SGD的函数来实现。
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):
# 初始化权重
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
# 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)
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
如何设置权重参数的初始值这个问题是关系到神经网络能否成功学习的重要问题。后面我
们会详细讨论权重参数的初始化,这里只需要知道,权重使用符合高斯分布的随机数进行初始化,偏置使用0 进行初始化
[!IMPORTANT]
numerical_gradient(self, x, t)基于数值微分计算参数的梯度。下一章,我们会介绍一个高速计算梯度的方法,称为误差反向传播法
用误差反向传播法求到的梯度和数值微分的结果基本一致,但可以高速地进行处理
mini-batch的实现
import numpy as np
import matplotlib.pyplot as plt
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 = []
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)
基于测试数据的评价
根据图4-11 呈现的结果,我们确认了通过反复学习可以使损失函数的值逐渐减小这一事实
过拟合是指,虽然训练数据中的数字图像能被正确辨别,但是不在训练数据中的数字图像却无法被识别的现象。过拟合需要避免
[!IMPORTANT]
epoch是一个单位
一个epoch表示学习中所有训练数据均被使用过一次时的更新次数
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)
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)
# 超参数
iters_num = 10000
batch_size = 100
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)
# 计算每个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))
小结
• 机器学习中使用的数据集分为训练数据和测试数据。
• 神经网络用训练数据进行学习,并用测试数据评价学习到的模型的泛化能力。
• 神经网络的学习以损失函数为指标,更新权重参数,以使损失函数的值减小。
• 利用某个给定的微小值的差分求导数的过程,称为数值微分。
• 利用数值微分,可以计算权重参数的梯度。
• 数值微分虽然费时间,但是实现起来很简单。下一章中要实现的稍微复杂一些的误差反向传播法可以高速地计算梯度