“学习”是指从训练数据中自动获取最优权重参数的过程。
1. 损失函数
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
在神经网络的学习中,寻找最优参数(权重和偏置)时,要寻找使损失函数的值尽可能小的参数。为了找到使损失函数的值尽可能小的地方,需要计算参数的导数(确切地讲是梯度),然后以这个导数为指引,逐步更新参数的值。
如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值;反过来,如果导数的值为正,则通过使该权重参数向负方向改变,可以减小损失函数的值。不过,当导数的值为0时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此时该权重参数的更新会停在此处。
所以不能用识别精度作为指标,是因为这样一来绝大多数地方的导数都会变为0,导致参数无法更新。
均方误差
这里, y k y_k yk是表示神经网络的输出, t k t_k tk表示监督数据, k k k表示数据的维度。该式计算的是一条数据的均方误差。
交叉熵误差
上面介绍的损失函数的例子中考虑的都是针对单个数据的损失函数。所有训练数据的损失函数的总和,公式为
这里,假设数据有 N N N个, t n k t_{nk} tnk表示第 n n n个数据的第 k k k个元素的值。
import numpy as np
#均方误差
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)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
mini-batch学习
神经网络的学习也是从训练数据中选出一批数据(称为mini-batch,小批量),然后对每个mini-batch进行学习。比如,从60000个训练数据中随机选择100笔,再用这100笔数据进行学习。这种学习方式称为mini-batch学习。
2. 数值微分
导数
导数表示某个瞬间的变化量,定义为:
即x得“微小变化”将导致函数f(x)的值在多大程度上发生变化。
导数实际上对应函数在x处的斜率。而(f(x+h)-f(x))/h对应的是x+h与x之间的斜率。为了减小误差,计算函数f在(x+h)和(x-h)之间的差分,称为中心差分,即(f(x+h)-f(x-h))/2h。
#导数
def numerical_diff(f, x):
h = 1e-4
return (f(x+h) - f(x-h)) / (2*h)
偏导数
函数中含有多个变量,函数对各个变量分别求导。
梯度
梯度是全部变量的偏导数汇总而成的向量。梯度指示的方向是各点处的函数值减小最多的方向。
编程实现时,需要对所有变量进行遍历。这里使用zeros_like生成一个与x形状相同的矩阵,然后使用nditer迭代器进行遍历。
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'])#使用迭代器遍历数组的各个位置
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
梯度法
在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,通过不断地沿着梯度方向前进,逐步减小函数值。
这里需要注意的是,梯度表示的是各点处的函数值减小最多的方向。因此,无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。实际上,在复杂的函数中,梯度指示的方向基本上都不是函数值最小处。
函数的极小值、最小值以及被称为鞍点(saddle point) 的地方,梯度为0。虽然梯度法是要寻找梯度为0的地方,但是那个地方不一定就是最小值(也有可能是极小值或者点)。
梯度法的数学公式表示为:
其中, η \eta η表示更新量,在神经网络的学习中,称为学习率。决定在多大程度上更新参数。
神经网络的梯度
神经网络中用到的梯度是指损失函数关于权重参数的梯度。例如有一个2×3的权重W,损失函数用L表示,则
3. 学习算法的实现
关于神经网络学习的基础知识,到这里就全部介绍完了。“损失函数”“mini-batch”“梯度”“梯度下降法”等关键词已经陆续登场,这里我们来确认一下神经网络的学习步骤,顺便复习一下这些内容。神经网络的学习步骤如下所示。
前提
神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面4个步骤。
步骤1( mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch的损失函数的值。
步骤2(计算梯度)
为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
步骤3(更新参数)
将权重参数沿梯度方向进行微小更新。
步骤4(重复)
重复步骤1、步骤2、步骤3。
神经网络的学习按照上面4个步骤进行。这个方法通过梯度下降法更新参数,不过因为这里使用的数据是随机选择的mini batch数据,所以又称为随机梯度下降法(stochastic gradient descent)。“随机”指的是“随机选择的”的意思,因此,随机梯度下降法是“对随机选择的数据进行的梯度下降法”。
实现一个2层神经网络
#实现一个2层神经网络
from chapter3 import sigmoid
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
''' input_size, hidden_size, output_size分别表示输入层、隐藏层、输出层的神经元数'''
#初始化权重
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):
'''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):
'''t为真实标签'''
y = self.predict(x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y1 = np.argmax(y, axis = 1)
t1 = np.argmax(t, axis = 1)
return np.sum(y1 == t1) / float(x.shape[0])
def 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
#在MNIST数据集上进行mini-batch学习
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(normalize = True, one_hot_label = True)
train_loss_list = []
#超参数设置
iters_num = 500
train_size = x_train.shape[0]
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.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)