深度学习入门第四章-----神经网络的学习

41. 从数据中学习

4.1.1 数据驱动

        以实现数字5的识别为例,首先从图像中提取特征量,再用机器学习技术来学习这些特征量的模式。特征量是指可以从输入数据(输入图像)中准确地提取本质数据(重要的数据)的转换器。图像的特征量通常表示为向量的形式。 在计算机视觉领域,常用的特征量包括SIFT、SURF和HOG等。使用这些特征量将图像数据转换成向量。然后对转换后的向量使用机器学习中的SVM、KNN等分类器进行学习

        需要注意的是,将图像转换成向量使用的特征量仍是由人设计的,对于不同的问题需要使用合适的特征量。但是神经网络直接学习图像本身,图像中包含的重要特征量也都是由机器来学习的。

4.1.2 训练数据和测试数据 

        在机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和实验等。首先使用训练数据进行学习寻找最优的参数;然后使用测试数据评价得到的模型的实际能力。将数据分为两部分原因是我们追求的是模型的泛化能力。训练数据也称为监督数据

        泛化能力是指处理未被观察过的数据的能力。获得泛化能力是机器学习的最终目标。仅仅使用一个数据集去学习和评价参数,是无法正确评价的,会导致可以顺利处理某个数据集,但是无法处理其他数据集的情况。只对某个数据集过度拟合的状态称为过拟合

4.2 损失函数

        神经网络以某个指标为线索寻找最优权重参数。神经网络的学习中使用的指标是损失函数,一般使用均方误差交叉熵误差。损失函数表示神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。

4.2.1 均方误差

        表达式如下

        yk表示神经网络的输出,tk表示监督数据,k表示数据的维数。手写数字识别中的例子如下

        此处神经网络的输出y是softmax函数的输出,即可以理解为概率。 于是在上例表示0的概率是0.1.......t是监督数据,将正确解的标签表示为1,其他标签表示为0的方法即one-hot表示。现在使用python来实现均方误差。

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

        需要注意,此处的y与t是NumPy数组。现使用该函数来进行计算。

        结果表明第一个例子的损失函数值更小,和监督数据之间的误差较小。即第一个例子的输出结果与监督数据更加吻合。 

4.2.2 交叉熵误差

        公式如下

         tk中只有正确解标签的索引为1,其余为0,所以实际上只计算正确解标签对应的输出的自然对数。比如正确解标签的索引是2,与之对应的神经网络的输出是0.6,则交叉熵误差是-log0.6=0.51。也就是说,交叉熵误差的值是由正确解标签对应的输出结果决定的。因为对数函数的性质,所以正确解标签对应的输出越大,交叉熵误差的值越接近0。输出为1时,交叉熵误差为0。现在使用python实现交叉熵误差。

def cross_entropy_error(y, t):
    delta = le - 7
    return -np.sum(t * np.log(y + delta))

        其中,函数内部计算np.log时,加上了一个微小值delta,这是因为,当出现np.log(0)时,会变为负无限大的-inf,会导致后续计算无法完成,加上一个微小值可以防止负无限大的发生。现进行实际计算。

4.2.3 mini-batch学习

        机器学习使用训练数据进行学习就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。因此,计算损失函数时必须把所有的训练数据作为对象,即如果有100个训练数据的话,就要把这100个损失函数的总和作为学习的指标。现在如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可以写出下式

        假设有N个数据,tnk表示第n个数据的第k个元素值,通过除以N,可以求单个数据平均损失函数。 通过这样的平均化,可以获得和训练数据的数量无关的统一指标。神经网络的学习从训练数据中选出一批数据,称为mini-batch,对每个mini-batch进行学习,称为mini-batch学习。

        现在以MNIST数据集为例说明

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.minst import load_minst

(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)

        现使用NumPy的np.random.choice()从训练数据中随机抽取10笔数据。 

train_size = x_train.shape[0] # 60000,shape[1]=784
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(60000, 10)会从0到59999之间随机选择10个数字。之后指定这些随机选出的索引,取出mini-batch,使用这个mini-batch计算损失函数即可。

4.2.4 mini-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 + le -7)) / batch_size

        y的维度为1时,即求单个数据的交叉熵误差,需要改变数据的形状。

4.2.5 为何要设定损失函数

        在神经网络的学习中,寻找最优参数时,要寻找使损失函数的值尽可能小的参数,为了找到使损失函数值尽可能小的地方,需要计算参数的导数(梯度),然后以该导数为指引,逐步更新参数的值。

        假设有一个神经网络,现关注这个神经网络中的某个权重参数。此时,对该权重参数的损失函数求导,表示的是“如果稍微改变这个权重参数的值,损失函数的值会如何变化”。如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值,反之,如果导数的值为正,通过使该权重参数向负方向改变,可以减小损失函数的值。但是,如果导数为0时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此时,该权重参数的更新会停在此处。

        不能使用识别精度作为指标,因为如果使用识别精度作为指标,则参数的导数在绝大多数地方都会变为0

4.3 数值微分

        梯度法使用梯度的信息决定前进的方向,本章介绍梯度。

4.3.1 导数

        导数是表示某个瞬间的变化量,定义为

        上式表示导数的含义是x的微小变化将导致函数f(x)的值在多大程度上发生变化。现在来实现求导数的程序。

def numerical_diff(f, x):
    h = 10e - 50
    return (f(x + h) - f(x)) / h

        在上述实现中,因为想把尽可能小的数赋给h,使用了h=10e-5,但是这样却产生了舍入误差。 即因省略小数的精细部分而造成最终计算结果上的误差。另一个需要改进的地方是数值微分含有误差,为了减小误差,可以计算f在(x+h)和(x-h)之间的差分。即以x为中心,计算它左右两边的差分,称为中心差分,之前的(x+h)和x之间的差分称为前向差分。改进后的程序如下

def numerical_diff(y, x):
    h = le - 4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)

4.3.2 数值微分的例子

y = 0.01x^2 + 0.1x

        使用python来实现

def function_1(x):
    return 0.01*x**2 + 0.1x

        该函数的图像如下

        计算这个函数在x=5和x=10处的导数

         此处的导数即f(x)相对于x的变化量,对应函数的斜率

4.3.3 偏导数

        现有函数

f(x_{0},x_{1}) = x_{0}^{2} + x_{1}^{2}

         使用python来实现

def function_2(x):
    return x[0]**2 + x[1]**2

        绘制出函数的图像如下,是一个三维图像。

        上式有两个变量,需要区分对哪个变量求导。

4.4 梯度

        现在来考虑求x0=3,x1=4时(x0,x1)的偏导数\left ( \frac{\partial f}{\partial x_{0}} ,\frac{\partial f}{\partial x_{1}}\right ),像这样由全部变量的偏导数汇总而成的向量称为梯度。梯度使用python表示如下

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

        其中,np.zeros_like(x)会生成一个形状和x相同、所有元素都为0的数组。 现在可以计算(x0,x1)在各点处的梯度,将f(x_{0},x_{1}) = x_{0}^{2} + x_{1}^{2}的梯度画在图上如下所示,这里画的时元素值的负梯度的向量。

        如上图所示,函数梯度呈现为有向向量(箭头),且梯度指向f(x0,x1)的最低处(最小值)。严格地讲,梯度指示的方向是各点处的函数值减少最多的方向

4.4.1 梯度法

        梯度表示的是各点处的函数值减少最多的地方。因此,不能保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。函数的最小值、极小值以及鞍点梯度为0。虽然梯度法是要寻找梯度为0的地方,但是该处不一定就是最小值。

        虽然梯度的方向不一定指向最小值,但沿着它的方向能够最大限度地减小函数的值。因此,在寻找函数最小值的位置的任务中,要以梯度的信息为线索,决定前进的方向

        在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,反复地沿梯度方向前进,逐渐减小函数值。这样的过程就是梯度法

        现在尝试用数学式来表示梯度法

x_{0} = x_{0} - \eta \frac{\partial f}{\partial x_{0}}

x_{1} = x_{1} - \eta \frac{\partial f}{\partial x_{1}}

        其中,η表示更新量,在机器学习中,称为学习率。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。上式是更新一次的式子,这个步骤会反复执行,即每一步都按上式更新变量的值,通过反复执行逐渐减小函数值。 学习率需要事先确定为某个值,这个值过大或过小都无法抵达一个好的位置。在神经网络的学习中,一般会一边改变学习率的值,一边确认学习是否正确进行了。

        现在使用python实现梯度下降法

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是学习率,step_num是梯度法的重复次数,numerical_gradient会求函数的梯度,用该梯度乘以学习率得到的值更新操作。使用该函数可以求函数的极小值,如果顺利的话还可以求函数的最小值。

        现在用梯度法求f(x_{0},x_{1}) = x_{0}^{2} + x_{1}^{2}的最小值

        最终的结果非常接近0,实际上,真的最小值就是(0,0)。用图来表示梯度法的更新过程如下

        其中,虚线是函数的等高线。可以发现,原点处是最低的地方,函数的取值一点点在向其靠近。 

        学习率过大会发散成一个很大的值,过小则基本上没怎么更新就结束了。

4.4.2 神经网络的梯度

        神经网络的梯度是指损失函数关于权重参数的梯度,比如有一个只有一个形状为2×3的权重W的神经网络,损失函数用L表示。此时,梯度可以用\frac{\partial L}{\partial W}表示,用数学式表示如下

        偏导中的元素由各个元素关于W的偏导数构成,比如\frac{\partial L}{\partial w_{11}}表示当w_{11}稍微变化时,损失函数L会发生多大的变化。 

        现在以一个简单的神经网络为例,来实现求梯度的代码

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

         现在尝试用以下这个类

        接下来求梯度

        其中numerical_gradient(f,net.w)返回的结果是dw,一个形状为2×3的二维数组。 \frac{\partial L}{\partial W}中的\frac{\partial L}{\partial w_{11}}值大约是0.2,这表示如果将w_{11}增大h,那么损失函数的值会增大0.2h\frac{\partial L}{\partial w_{23}}的值大约是-0.5,这表示如果将w_{23}增大h,那么损失函数的值会减小0.5h,从减小损失函数值的观点来看,w_{11}应向负方向更新,w_{23}应该向正方向更新。更新的程度,w_{23}要比w_{11}贡献要大

4.5 学习算法的实现

        神经网络的学习步骤如下所示

        前提:神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习,分成下面4个步骤

        步骤1(mini-batch):目标是减小mini-batch损失函数的值。

        步骤2(计算梯度):为了减小mini-batch损失函数的值,需要求出各个权重参数的梯度,梯度表示损失函数的值减小最多的方向

        步骤3(更新参数):将权重参数沿着梯度方向进行微小更新。

        步骤4(重复):重复以上3步。

        神经网络的学习按照上面的步骤进行,通过梯度下降法更新参数,由于使用的数据是mini-batch数据,故又称为随机梯度下降法SGD。即对随机选择的数据进行的梯度下降法

4.5.1 2层神经网络的类

        首先将这个2层网络实现为一个名为TwoLayerNet的类,代码如下

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

4.5.2 mini-batch的实现

        以TwoLayerNet类为对象,使用MNIST数据集进行学习

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_
laobel = True)
train_loss_list = []
 # 超参数
iters_num = 10000
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.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)

        此处的mini-batch大小为100,需要每次从60000个训练数据中随机取出100个数据(图像数据和正确解标签数据),然后对这个包含100笔数据的mini-batch求梯度,使用随机梯度下降法SGD更新参数。这里,梯度法的更新次数(循环的次数)为10000。每更新一次,都对训练数据计算损 失函数的值,并把该值添加到数组中。用图像来表示这个损失函数的值的推移,如图所示。

        可以看出,随着学习的进行,损失函数的值在不断减小,表示神经网络的权重参数在逐渐拟合数据。也就是说神经网络的确在学习。

4.5.3 基于测试数据的评价

        神经网络的学习中,必须确认是否能够正确识别训练数据以外的其他数据,即确认是否会发生过拟合。神经网络学习的最初目标是掌握泛化能力,因此,要评价神经网络的泛化能力,就必须使用不包含在训练数据中的数据。下面的代码在进行学习的过程中,会定期地对训练数据测试数据记录识别精度。这里,每经过一个 epoch,都会记录下训练数据和测试数据的识别精度。

        epoch是一个单位,一个epoch表示所有训练数据均被使用过一次时的更新次数。比如,对于10000笔训练数据,用大小为100笔数据的mini-batch进行学习时,重复随机梯度下降法100次,所 有的训练数据就都被“看过”了。此时,100次就是一个epoch。

        代码不再演示,将代码得到的结果用图表示如下

4.6 小结 

        神经网络用训练数据进行学习,并用测试数据评价学习到的模型的泛化能力。

        利用某个给定的微小值的差分求导数的过程,称为数值微分。

        利用数值微分,可以计算权重参数的梯度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值