神经网络的特征就是可以从数据中学习。所谓“从数据中学习”,是指可以由数据自动决定权重参数的值。利用特征量和机器学习的方法中,特征量仍是由人工设计的,而在神经网络中,连图像中包含的重要特征量也都是由机器来学习的。
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试数据评价训练得到的模型的实际能力。为什么需要将数据分为训练数据和测试数据呢?因为我们追求的是模型的泛化能力。为了正确评价模型的泛化能力,就必须划分训练数据和测试数据。另外,训练数据也可以称为监督数据。
泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的能力。获得泛化能力是机器学习的最终目标。只对某个数据集过度拟合的状态称为过拟合(overfittingover\ fittingover fitting)。避免过拟合也是机器学习的一个重要课题。
神经网络的学习通过某个指标表示现在的状态。然后,以这个指标为基准,寻找最优权重参数。神经网络的学习中所用的指标称为损失函数(lossfunctionloss\ functionloss function)。这个损失函数可以使用任意函数,但一般用均方误差和交叉熵误差等。
均方误差如下式所示,其中,yky_kyk表示神经网络的输出,tkt_ktk表示监督数据,kkk表示数据的维数,均方误差会计算神经网络的输出和正确解监督数据的各个元素之差的平方,再求总和:
使用Python实现代码如下:
def mean_squared_error(y, t):return 0.5 * np.sum((y - t) ** 2)
举个例子计算一下:
# 设2为正确解
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]# 例1:2的概率最高的情况(0.6)
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
mean_squared_error(np.array(y), np.array(t)) # 0.097500000000000031# 例2:7的概率最高的情况(0.6)
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
mean_squared_error(np.array(y), np.array(t)) # 0.59750000000000003
交叉熵误差如下式所示,其中,yky_kyk表示神经网络的输出,tkt_ktk表示正确解标签,且tkt_ktk中只有正确解标签的索引为111,其他均为000(one−hotone-hotone−hot表示):
该式实际上只计算对应正确解标签的输出的自然对数。比如,假设正确解标签的索引是“2”“2”“2”,与之对应的神经网络的输出是0.60.60.6,则交叉熵误差是−log0.6=0.51-log\ 0.6=0.51−log 0.6=0.51;若“2”“2”“2”对应的输出是0.10.10.1,则交叉熵误差为−log0.1=2.30-log\ 0.1=2.30−log 0.1=2.30。也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。
使用Python实现代码如下:
def cross_entropy_error(y, t):delta = 1e-7return -np.sum(t * np.log(y + delta))
函数内部在计算np.log
时,加上了一个微小值delta
。这是因为,当出现np.log(0)
时,结果会变为负无穷大的,这样一来就会导致后续计算无法进行。
举个例子计算一下:
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t)) # 0.51082545709933802y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t)) # 2.3025840929945458
使用训练数据进行学习,严格来说,就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。计算损失函数时必须将所有的训练数据作为对象。计算损失函数时必须将所有的训练数据作为对象,如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可以写成下面的式子:
假设数据有NNN个,tnkt_{nk}tnk表示第nnn个数据的第kkk个元素的值(ynky_{nk}ynk是神经网络的输出,tnkt_{nk}tnk是监督数据)。
由于MNIST数据集的训练数据有600006000060000个,如果以全部数据为对象求损失函数的和,则计算过程需要花费较长的时间。因此,我们从全部数据中选出一部分,作为全部数据的“近似”。神经网络的学习也是从训练数据中选出一批数据(称为mini−batchmini-batchmini−batch,小
批量),然后对每个mini−batchmini-batchmini−batch进行学习。
假设要从这个训练数据中随机抽取10笔数据,可以使用如下代码,使用np.random.choice()
可以从指定的数字中随机选择想要的数字。比如,np.random.choice(60000, 10)
会从000到599995999959999之间随机选择101010个数字:
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]
改良交叉熵误差代码,实现一个可以同时处理单个数据和批量数据(数据作为batchbatchbatch集中输入)两种情况的函数:
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−hotone-hotone−hot表示,而是像“2”,“7”“2”,“7”“2”,“7”这样的标签)时,交叉熵误差可通过如下代码实现:
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# 假设batch_size = 5, t = [2, 7, 0, 9, 4]
# 则y[np.arange(batch_size), t] = [y[0, 2], y[1, 7], y[2, 0], y[3, 9], y[4, 4]]
导数就是表示某个瞬间的变化量,它可以定义成下面的式子:
但是该式子计算时存在误差,因为真的导数是对应函数在xxx处的斜率(称为切线),但上述实现中计算的导数对应的是(x+h)(x+h)(x+h)和xxx之间的斜率(如下图所示)。为了减小这个误差,我们可以计算函数fff在(x+h)(x+h)(x+h)和(x−h)(x-h)(x−h)之间的差分。因为这种计算方法以xxx为中心,计算它左右两边的差分,所以也称为中心差分(而(x+h)(x+h)(x+h)和xxx之间的差分称为前向差分)。
使用Python实现代码如下:
def numerical_diff(f, x):h = 1e-4 # 0.0001return (f(x + h) - f(x - h)) / (2 * h)
现在假设我们有一个一元二次函数:y=0.01x2+0.1xy=0.01x^2+0.1xy=0.01x2+0.1x,使用Python实现该式如下:
def function_1(x):return 0.01 * x ** 2 + 0.1 * x
我们来计算一下这个函数在x=5x=5x=5和x=10和x=10和x=10处的导数:
numerical_diff(function_1, 5) # 0.1999999999990898
numerical_diff(function_1, 10) # 0.2999999999986347
接下来我们来看另一个函数:f(x0,x1)=x02+x12f(x_0,x_1)=x_0^2+x_1^2f(x0,x1)=x02+x12,和上例不同的是,这里有两个变量,使用Python实现该式如下:
def function_2(x):return x[0] ** 2 + x[1] ** 2# 或者return np.sum(x ** 2)
因为该式有两个变量,因此有必要区分对哪个变量求导数,有多个变量的函数的导数称为偏导数。用数学式表示的话,可以写成∂f∂x0,∂f∂x1\frac {\partial f}{\partial x_0},\frac {\partial f}{\partial x_1}∂x0∂f,∂x1∂f。偏导数需要将多个变量中的某一个变量定为目标变量,并将其他变量固定为某个值(即常数)。
例如求x0=3,x1=4x_0=3,x_1=4x0=3,x1=4时,关于x0x_0x0的偏导数∂f∂x0\frac {\partial f}{\partial x_0}∂x0∂f:
def function_tmp1(x0):return x0 * x0 + 4.0 ** 2.0numerical_diff(function_tmp1, 3.0) # 6.00000000000378
像(∂f∂x0,∂f∂x1)(\frac {\partial f}{\partial x_0},\frac {\partial f}{\partial x_1})(∂x0∂f,∂x1∂f)这样的由全部变量的偏导数汇总而成的向量称为梯度(gradientgradientgradient),梯度指示的方向是各点处的函数值减小最多的方向。梯度可以像下面这样来实现:
def numerical_gradient(f, x):h = 1e-4 # 0.0001grad = np.zeros_like(x) # 会生成一个形状和x相同,所有元素都为0的数组it = np.nditer(x, flags = ['multi_index'], op_flags = ['readwrite'])while not it.finished:idx = it.multi_indextmp_val = x[idx]x[idx] = float(tmp_val) + hfxh1 = f(x) # f(x + h)x[idx] = float(tmp_val) - hfxh2 = f(x) # f(x - h)grad[idx] = (fxh1 - fxh2) / (2 * h)x[idx] = tmp_val # 还原值it.iternext()return grad
神经网络必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数,通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法。由于梯度表示的是各点处的函数值减小最多的方向,无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。虽然梯度的方向并不一定指向最小值,但沿着它的方向能够最大限度地减小函数的值。
现在,我们尝试用数学式来表示梯度法,如下式所示:
式中的η\etaη表示更新量,在神经网络的学习中,称为学习率(learningratelearning\ ratelearning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。
学习率需要事先确定为某个值,比如0.010.010.01或0.0010.0010.001。一般而言,这个值过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会一边改变学习率的值,一边确认学习是否正确进行了。
使用Python实现梯度下降法如下,其中参数fff是要进行最优化的函数,init_xinit\_xinit_x是初始值,lrlrlr是学习率learningratelearning\ ratelearning rate,step_numstep\_numstep_num是梯度法的重复次数:
def gradient_descent(f, init_x, lr=0.01, step_num=100):x = init_xfor i in range(step_num):grad = numerical_gradient(f, x)x -= lr * gradreturn x
实验结果表明,学习率过大的话,会发散成一个很大的值;反过来,学习率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率是一个很重要的问题。
神经网络中的梯度是指损失函数关于权重参数的梯度:
我们以一个简单的神经网络为例,来实现求梯度的代码。为此,我们要实现一个名为simpleNet
的类:
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
试着用一下simpleNet
:
net = simpleNet()
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
接下来求梯度:
def f(W):return net.loss(x, t)
dW = numerical_gradient(f, net.W)
Python中如果定义的是简单的函数,可以使用lambdalambdalambda表示法。使用lambdalambdalambda的情况下,上述代码可以如下实现:
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
我们先来确认一下神经网络的学习步骤,顺便复习一下这些内容:
神经网络的学习按照上面444个步骤进行。这个方法通过梯度下降法更新参数,不过因为这里使用的数据是随机选择的mini−batchmini-batchmini−batch数据,所以又称为随机梯度下降法。
下面,我们来实现手写数字识别的222层神经网络(隐藏层为111层)。我们将这个222层神经网络实现为一个名为TwoLayerNet
的类,实现过程如下所示:
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) + b1z1 = sigmoid(a1)a2 = np.dot(z1, W2) + b2y = softmax(a2)return ydef 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 accuracydef 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
接下来,我们就以TwoLayerNet
类为对象,使用MNIST数据集进行学习:
import sys
sys.path.append('D:\VS Code Project\Deep Learning')
import numpy as np
import matplotlib.pylab 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 = []
train_acc_list = []
test_acc_list = []iter_per_epoch = max(train_size / batch_size, 1)for i in range(iters_num):# 获取mini-batchbatch_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)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))# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label = 'train acc')
plt.plot(x, test_acc_list, label = 'test acc', linestyle = '--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc = 'lower right')
plt.show()
神经网络学习的最初目标是掌握泛化能力,因此,要评价神经网络的泛化能力,就必须使用不包含在训练数据中的数据。上述的代码在进行学习的过程中,会定期地对训练数据和测试数据记录识别精度。这里,每经过一个epochepochepoch,我们都会记录下训练数据和测试数据的识别精度。epochepochepoch是一个单位。一个epochepochepoch表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于100001000010000笔训练数据,用大小为100100100笔数据的mini−batchmini-batchmini−batch进行学习时,重复随机梯度下降法100100100次,就相当于所有的训练数据就都被“看过”了。
绘图结果如下图所示:
如上图所示,随着epochepochepoch的前进(学习的进行),我们发现使用训练数据和测试数据评价的识别精度都提高了,并且,这两个识别精度基本上没有差异(两条线基本重叠在一起)。因此,可以说这次的学习中没有发生过拟合的现象。
下一节:【学习笔记】深度学习入门:基于Python的理论与实现-误差反向传播法。
上一篇:健壮性测试是什么?
下一篇:二.maven常用功能点