神经网络的学习 《深度学习 基于Python的理论与实现》第四章

本文深入探讨了神经网络的学习过程,包括数据驱动的学习、损失函数(如均方误差和交叉熵误差)、批处理学习(mini-batch)以及数值微分和梯度法。文章还解释了为何要设定损失函数,并展示了如何通过梯度下降法更新参数,以最小化损失函数。此外,介绍了使用测试数据评估模型的泛化能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


layout: post
title: 深度学习入门 基于Python的理论实现
subtitle: 第四章 神经网络的学习
tags: [Machine learning, Reading]


第四章 神经网络的学习

上一章我们讲前向传播,主要说的数据的传递和计算,没有涉及到参数的训练和选择,本章的主题是神经网络的学习。为了使神经网络可以学习,引入了损失函数这个指标,学习的目的是以该损失函数为基准,找出使它的值最小的权重参数。

4.1 从数据中学习

神经网络的特征就是可以从数据中学习。所谓从数据中学习,指的就是可以由税局自动决定权重参数的值。

4.1.1 数据驱动

所谓数据驱动,强调了数据对机器学习的重要性。几乎所有的事情都和数据有关。以下列出三种解决问题的方法。

  • 人面对问题时,研究问题,想出算法,最终解决问题。
  • 从图中提取特征,再学习模式,最终解决问题。
  • 直接学习数据,得到答案。

深度学习有时也被称为端到端机器学习(end-to-end machine learning)。

4.1.2 训练数据和测试数据

在机器学习中一般将数据集分为测试集和训练集。

4.2 损失函数

神经网络用来寻找最优权重参数的指标被称作损失函数。这个损失函数可以使用任意函数,但一半用均方误差和交叉熵误差等。

4.2.1 均方误差

用作损失函数的函数很多,其中最有名的是均方误差。(mean squared error MSE),表达形式如下:

E=12∑k(yk−tk)2 E = \frac{1}{2}\sum_{k}(y_k-t_k)^2 E=21k(yktk)2

这里的yky_kyk代表神经网络的输出,tkt_ktk代表监督数据,kkk代表维数。下来我们进行实现:

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

观察数学意义可以知道,MSE的大小直接反映了预测结果和真实值之间的差异。

4.2.2 交叉熵误差

E=−∑ktklog yk E= -\sum_kt_klog\ y_k E=ktklog yk

公式里的logloglog代表以eee为底的自然对数(logelog_eloge)。yky_kyk是神经网络的输出,tkt_ktk是监督值,也就是标准值。tkt_ktk使用one-hot表示。

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

因为np.log(0)会计算成负无穷,所以这里delta的作用是为了应对这个情况。

4.2.3 mini-batch学习

上一章我们讲了批处理,对应实际中使用的批处理,损失函数的写法也要做相应改变。之前介绍的损失函数考虑的都是单个数据的损失函数,如果要求所有的训练数据的损失函数的总和,以交叉熵为例,可以写成如下的形式:

E=−1N∑n∑ktnklog ynk E = -\frac{1}{N}\sum_n\sum_kt_{nk}log\ y_{nk} E=N1nktnklog ynk

这里假设有NNN个数据,tnkt_nktnk代表第nnn个数据的第kkk个元素的值。另外,MNIST有60000个数据,假设以全部数据为对象求损失函数的和,计算过程需要花费较长的时间。实际中的数据集比这个量更大,因此,我们从全部数据中选出一批数据,然后对每个mini-batch进行学习。这种方式叫做mini-batch学习。

之前我们已经有了读取数据集的代码,接下来我们要从中随机选取10个数据。随机选取可以使用np.random.choice()。

import sys,os
import numpy as np

sys.path.append(os.pardir)
from dataset.mnist import load_mnist

(x_train, t_train),(x_test, t_test) = load_mnist(flatten=True, normalize=True)


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

    delta = 1e-7
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + delta))/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)

    delta = 1e-7
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
4.2.5 为何要设定损失函数

原因主要有以下几点:

  • 不能关注某一个特定的参数
  • 不能以识别精度为指标
  • 识别精度对微小的参数变化不敏感

4.3 数值微分

4.3.1 导数
4.3.2 数值微分的例子

下面的例子是一个求数值微分的例子。使用的中心差分。求的是f(x)=0.01x2+0.1xf(x) = 0.01x^2+0.1xf(x)=0.01x2+0.1xf(x+h)f(x+h)f(x+h)f(x−h)f(x-h)f(xh)之间的差分。

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

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0.0,20.0,0.1)
y = function_x(x)

plt.plot(x,y)
plt.show()

def numerical_diff(f,x):
    h = 1e-4
    return (f(x+h) - f(x-h))/(2*h)

print(numerical_diff(function_x,5))

print(numerical_diff(function_x,10))

在这里插入图片描述

4.3.3 偏导数

这里以一个函数为例:

f(x0,x1)=x02+x12 f(x_0,x_1) = x_0^2 + x_1^2 f(x0,x1)=x02+x12

因为这里有两个变量,所以求偏导数时,固定一个值,对另一个变量求导数。

4.4 梯度

学习了导数和偏导数,那么什么是梯度呢。形如
(∂f∂x0,∂f∂x1) (\frac{\partial f}{\partial x_0},\frac{\partial f}{\partial x_1}) x0f,x1f
的由全部变量的偏导数汇总而成形成的向量被称为梯度

下面实际看一个例子:

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

def numerical_gradient(f,x):
    h = 1e-4
    grad = np.zeros(x.size)

    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x)

        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val

    return grad


print(numerical_gradient(function_2,np.array([3.0,4.0])))

在这里插入图片描述

这里提供一个梯度的可视化方法。使用了matplotlib中的quiver函数。quiver函数使用方法很简单,前面四个参数分别代表了箭头起点,XY,也就是每一个点的梯度位置,后两个参数就是计算出的梯度,代表箭头在两个方向上的矢量。

x0 = np.arange(-2, 2.5, 0.25)
x1 = np.arange(-2, 2.5, 0.25)
X, Y = np.meshgrid(x0, x1)

X = X.flatten()
Y = Y.flatten()

grad = numerical_gradient(function_2, np.array([X, Y]) )

plt.figure()
plt.quiver(X, Y, -grad[0], -grad[1],  angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
plt.xlim([-2, 2])
plt.ylim([-2, 2])
plt.xlabel('x0')
plt.ylabel('x1')
plt.grid()
plt.legend()
plt.draw()
plt.show()

在这里插入图片描述

4.4.1 梯度法

讲解了梯度的意义,现在要讲的是具体的使用,也就是梯度法对于寻找最优参数的意义。这里的最优参数就是损失函数取最小值时的参数。

但是这个最小值通常是很难找的,因为函数通常比较复杂,并且可能找到的仅仅是局部最优解,但是不可否认的是,梯度的下降方向是可以使损失函数减小的方向。

简而言之,梯度法就是通过不断的沿着梯度方向前进,逐渐减小损失函数的值的过程就叫做梯度法。寻找最小值的方法乘坐梯度下降法,反之,则称为梯度上升法

接下来用数学方式表示梯度法。

x0=x0−η∂f∂x0 x_0= x_0-\eta\frac{\partial{f}}{\partial{x_0}} x0=x0ηx0f

x1=x0−η∂f∂x1 x_1= x_0-\eta\frac{\partial{f}}{\partial{x_1}} x1=x0ηx1f

在上面的式子里,η\etaη称为学习率(learning rate)。这个类似于步长的概念。学习率需要事先确定,过大或者过小都不利于找到好的位置。

接下来实现一下梯度下降法。

def gradient_decent(f,init_x,lr=0.01,step_num=100):
    x = init_x

    for i in range(step_num):
        grad = numerical_gradient(f,x)
        x = x - lr*grad

    return x

拥有了所有的工具,接下来尝试解决问题。

问题:用梯度法求解f(x0,x1)=x02+x12f(x_0,x_1) = x_0^2+x_1^2f(x0,x1)=x02+x12的最小值。

思路:使用梯度下降,指定一个初始值,学习率和步长即可。下面是代码和图示。

# coding: utf-8
import numpy as np
import matplotlib.pylab as plt


def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)

        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)

        return grad

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    x_history = []

    for i in range(step_num):
        x_history.append( x.copy() )

        grad = numerical_gradient(f, x)
        x -= lr * grad

    return x, np.array(x_history)


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

init_x = np.array([-3.0, 4.0])

lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)

plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')

plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.savefig('gradient_decent_visualization.png')
plt.show()

在这里插入图片描述

像学习率这样的参数,取大取小都不好,他和诸如权重和偏置这样通过神经网络学习获得的参数不同,它需要人为进行设定,这一类参数通常被称为超参数

4.4.2 神经网络的梯度

神经网络的学习过程也需要求梯度,这里的损失函数就是基于权重参数的梯度。如有一个形状为2×32 \times 32×3的权重为WWW的神经网络,损失函数用LLL表示,此时梯度可以用 ∂L∂W\frac{\partial{L}}{\partial{W}}WL 表示。

W=(ω11ω12ω13ω21ω22ω23) W = \begin{pmatrix} \omega_{11}&\omega_{12}&\omega_{13} \\ \omega_{21}&\omega_{22}&\omega_{23} \end{pmatrix} W=(ω11ω21ω12ω22ω13ω23)

∂L∂W=(∂L∂ω11∂L∂ω12∂L∂ω13∂L∂ω21∂L∂ω22∂L∂ω23) \frac{\partial{L}}{\partial{W}} = \begin{pmatrix} \frac{\partial{L}}{\partial{\omega_{11}}}&\frac{\partial{L}}{\partial{\omega_{12}}}&\frac{\partial{L}}{\partial{\omega_{13}}} \\ \frac{\partial{L}}{\partial{\omega_{21}}}&\frac{\partial{L}}{\partial{\omega_{22}}}&\frac{\partial{L}}{\partial{\omega_{23}}} \end{pmatrix} WL=(ω11Lω21Lω12Lω22Lω13Lω23L)

∂L∂W\frac{\partial{L}}{\partial{W}}WL 的元素是关于WWW的偏导数,例如第一行第一列的元素 ∂L∂ω11\frac{\partial{L}}{\partial{\omega_{11}}}ω11L 表示当 ω11\omega_{11}ω11 稍微变化时,损失函数LLL会发生多大变化。接下来看每一个简单神经网络求梯度的例子:

import numpy as np


def softmax(x):
    exp_a = np.exp(x-np.max(x))
    sum_exp_a = np.sum(exp_a)

    return exp_a/sum_exp_a

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

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

x = np.array([0.6,0.9])

p = net.predict(x)

print(p)

print(np.argmax(p))

t = np.array([1,0,0])

loss = net.loss(x,t)

print(loss)

上面构建了简单的神经网络,使用交叉熵作为损失函数。求得了误差之后,接下来使用梯度下降,更新参数后看误差的变化。

4.5 学习算法的实现

截至目前,神经网络学习的所有基础知识已经全部讲解完毕。接下来确认一下神经网络的学习步骤。

前提

神经网络存在合适的权重的偏置,调整权重和偏置的过程称为学习。

步骤1(mini-batch)
从训练数据中选取一部分数据,使用这部分数据来减小损失函数的值。

步骤2(计算梯度)

为了减小损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。

步骤3(更新参数)
将权重参数做微小的更新。

步骤4(重复)

重复上面的步骤

神经网络的学习按照上面的步骤进行,这里的数据是随机选择的mini batch数据,所以称为随机梯度下降法(stochastic gradient descent)。因此随机梯度下降法也叫做“对随机选择的数据进行的梯度下降法”,在很多框架中,随机梯度下降法一般由一个名为SGD的函数实现。

下面以手写数字识别为例做实现。

4.5.1 2层神经网络的类

我们将这个类命名为TwoLayerNet的类。这个类用于构建我们所用的神经网络,使用这个类可以根据需要创建。

def sigmoid(x):
    return 1/(1+np.exp(-x))


def softmax(x):
    exp_x = np.exp(x-np.max(x))
    sum_exp_x = np.sum(exp_x)

    return exp_x/sum_exp_x

def cross_entropy_error(y,t):
  delta = 1e-7

  return -np.sum(t * np.log/(y + delta))



class TwoLayerNet:
    def __init__(self,input_size,hidden_size,output_size):
        self.params = {}
        self.params['W1'] = np.random.randn(input_size,hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = 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

    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

    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['W1'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

这个类的实现稍稍有点长,但是并没有什么新鲜的内容。TwoLayerNet类有params和grads两个字典实例变量,在实现的时候,调用了构造函数,init(self,input_size,hidden_size,output_size)第一个参数代表输入层神经元个数,第二个参数代表隐藏层的神经元个数,第三个参数代表输出层神经元个数,在这里,输入层的神经元个数为 28×28=78428 \times 28 = 78428×28=784,输出为10个类别,隐藏层设置一个合适的参数即可。
在后面将会详细讨论参数初始化问题,这里我们使用符合高斯分布的随机数进行初始化,偏置使用0进行初始化。predict(self, x)和 accuracy(self, x, t)的实现和上一章的神经网络的推理处理基本一样。如 果仍有不明白的地方,请再回顾一下上一章的内容。另外,loss(self, x, t)是计算损失函数值的方法。这个方法会基于predict()的结果和正确解标签,计算交叉熵误差。剩下的numerical_gradient(self, x, t)方法会计算各个参数的梯度。根 据数值微分,计算各个参数相对于损失函数的梯度。另外,gradient(self, x, t) 是下一章要实现的方法,该方法使用误差反向传播法高效地计算梯度。

4.5.2 mini-batch的实现

神经网络的学习使用的是前面介绍过的mini batch学习,所谓mini batch学习指的是从训练数据中随机选取一部分数据,再使用这些数据用梯度法更新参数。

import numpy as np
from mnist import load_mnist

def numerical_gradient(f, X):
  if X.ndim == 1:
      return _numerical_gradient_no_batch(f, X)
  else:
      grad = np.zeros_like(X)

      for idx, x in enumerate(X):
          grad[idx] = _numerical_gradient_no_batch(f, x)

  return grad

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

def _numerical_gradient_no_batch(f, x):
  h = 1e-4 # 0.0001
  grad = np.zeros_like(x)

  for idx in range(x.size):
      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 # 还原值

  return grad


(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
learning_rate = 0.1


network = TwoLayerNet(input_size=x_train.shape[1],hidden_size=50,output_size=10)

for i in range(iters_num):
  print(i)
  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)


  for key in ('W1','b1','W2','b2'):
      print(key)
      network.params[key] -= learning_rate * grad[key]

      loss = network(x_batch,t_batch)
      train_loss_list.append(loss)
      print(loss)

这样的计算是十分缓慢的。以至于,在计算的时候。完成整个循环需要很久很久。

4.5.3 基于测试数据的评价

从上面的代码,可以看出,使用梯度下降法,实现了损失函数减小。
在神经网络的学习中,必须首先确认是否发生过拟合,即能否识别除训练数据以外的数据。接下来要确认的是神经网络是否掌握泛化能力。

4.6 小结

本章介绍了神经网络的学习。首先,为了顺利进行神经网络的学习,我们导入损失函数这个指标。以这个损失函数为基准,找出使它的值达到最小的权重参数,这就是神经网络的学习目标。为了找到尽可能小的损失函数值,我们介绍了使用函数斜率的梯度法。

  • 机器学习中使用的数据集分为训练数据和测试数据
  • 神经网络用训练数据进行学习,并用测试数据评价学习到的模型的泛化能力。
  • 神经网络的学习以损失函数为指标,更新权重参数,以使损失函数的值减小
  • 利用某个给定的微小值的差分求倒数的过程,称为数值微分
  • 利用数值微分,可以计算权重参数的梯度
  • 数值为分虽然费时间,但是实现起来很简单。下一章中要实现的是复杂一些的误差反向传播法,可以高速的计算梯度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值