神经网络学习

导语

神经网络中的学习指从训练数据中自动获取最优权重参数的过程,这个“最优”的定义在不同的应用场景下各有不同。

数据驱动

机器学习最关键的部分即如何对待数据,从数据当中找到模式,找到规律,找到答案。

驱动方法

机器学习的思路是,先从图像中提取特征量(一般由人设计),再用机器学习学习特征量的模式,神经网络的思路是,直接学习数据本身,特征量也是自主学习,区别图如下(来自书):
在这里插入图片描述神经网络对所有的问题都用同样的流程解决,有时也被称为端到端机器学习(从输入直接获得输出)。

训练/测试数据

训练数据用来学习和找到最优的参数,测试数据用来评价模型的泛化能力,判断已训练的模型效果如何。

泛化能力即处理未被观察过的数据(简单说就是训练数据之外的数据)的能力,机器学习的目标就是获得这种能力。

为了提高泛化能力,对一个模型往往使用多个数据集进行考察,如果只使用一个数据集的话,可能会导致模型在这个数据集上表现很好,但是直接拿来检测新的数据时表达很差,这种只对某个数据集过度拟合的状态就是过拟合。

损失函数

为了评估每次训练的结果,神经网络采用了损失函数这一指标,这个损失函数有多种取法,书上给了几种函数及其实现方式。

均方误差

均方误差是常用的损失函数,表达式为(仅两个数据比对): E = 1 2 ∑ k ( y k − t k ) 2 E=\frac{1}{2}\mathop{\sum}\limits_k(y_k-t_k)^2 E=21k(yktk)2

y k y_k yk为神经网络输出, t k t_k tk为监督数据, k k k为数据的维数,一般用one-hot表示(正解标签为1,其他为0)。

python实现:

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

交叉熵误差

交叉熵误差表达式为: E = − ∑ k t k l n y k E=-\mathop{\sum}\limits_kt_klny_k E=ktklnyk,也用one-hot标签,python的实现很简单,如下:

def cross_entropy_error(y,t):
	return -np.sum(t*np.log(y))

这个实现方法看起来没什么问题,但是如果熟悉 l n x lnx lnx曲线的话就会知道,当 y y y存在0项时, l n 0 ln0 ln0的值是无穷大,是无法运算的,因此需要添加一个微小值防止负无穷大,更改之后的函数实现如下:

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

mini-batch

上述给出的实现和式子都是只针对只有一个值的情况,实际上,神经网络是一批一批地处理数据的,当要求处理大量数据时,书上以交叉熵误差为例,式子表达为: E = − 1 N ∑ n ∑ k t n k l n y n k E=-\frac{1}{N}\mathop{\sum}\limits_n\mathop{\sum}\limits_kt_{nk}lny_{nk} E=N1nktnklnynk
其中, t n k t_{nk} tnk表示第 n n n个数据的第 k k k个元素值, y n k y_{nk} ynk是输出, t n k t_{nk} tnk是监督数据。
一般来说,由于数据集的数据量很大,用全部数据来计算损失函数是不适合的,因此mini-batch应运而生(从全部数据随机选出一部分,作为整体的近似),可以理解为现实中的抽样调查。

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+1e-8))/batch_size#one-hot表示
	#return -np.sum(np.log(y[np.arange(batch_size),t]+1e-8))/batchsize
	#非one-hot表示

数值微分

书上在这里介绍了导数(用割线来代替)、求导、偏导等概念,如果有高等数学基础,应该很容易能理解这些,因此跳过,直接从梯度开始。

梯度

梯度是建立在偏导数的基础上的,假设有变量 x 0 , x 1 , . . . . . . x n x_0,x_1,......x_n x0,x1,......xn,然后有偏导数 ∂ f ∂ x 0 , ∂ f ∂ x 1 . . . . . . , ∂ f ∂ x n \frac{\partial f}{\partial x_0},\frac{\partial f}{\partial x_1}......,\frac{\partial f}{\partial x_n} x0f,x1f......,xnf,由全部变量的偏导数汇成的向量就是梯度,书上python的实现如下:

def numerical_gradient(f,x):
    h=1e-4
    grad=np.zeros_like(x)#先生成一个全0数组

    for idx in range(x.size):#遍历输入
        tmp_val=x[idx]
        x[idx]=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 = x − η ∂ f ∂ x x=x-η\frac{\partial f}{\partial x} x=xηxf, η \eta η是更新量,也就是学习率,决定了每次移动的步长。

像学习率这种参数被称为超参数,因为它并不是神经网络自动学习获得的,在实际的训练过程中,往往需要尝试多个值,学习率过小,则迭代次数过多,训练的时间被无意义浪费,学习率过大,可能步子迈大了扯着蛋,越过了极值或最值。

书上将梯度法式子用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

神经网络梯度

在理解了梯度的概念和用法之后,在神经网络中运用梯度就变得很容易了,将结果矩阵中的每个值对权重秋偏导即可,以一个 2 × 2 2×2 2×2的矩阵为例,表达式如下:
W = ( w 11 w 12 w 21 w 22 ) W= \begin{pmatrix} w_{11}&w_{12}\\ w_{21}&w_{22} \end{pmatrix} W=(w11w21w12w22)

∂ L ∂ W = ( ∂ L ∂ w 11 ∂ L ∂ w 12 ∂ L ∂ w 21 ∂ L ∂ w 22 ) \frac{\partial L}{\partial W}= \begin{pmatrix} \frac{\partial L}{\partial w_{11}}&\frac{\partial L}{\partial w_{12}}\\ & &\\ \frac{\partial L}{\partial w_{21}}&\frac{\partial L}{\partial w_{22}}\\ \end{pmatrix} WL= w11Lw21Lw12Lw22L

python的实现如下:

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)#初始化一个全0数组
    
    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

学习算法的实现

神经网络的学习过程大致包括这几个部分:选出mini-batch,计算梯度,更新参数,循环往复,理解起来很简单,最关键的部分就是前面提到的梯度及其更新的部分。

随机梯度下降

随机梯度下降的概述很简单,在原数据中随机选择mini batch的数据,然后再计算梯度,再根据梯度下降的方向移动,进行下一次运算,循环往复,一般将该函数命名为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)#偏置初始化为0
        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

mini-batch实现

书上以MNIST数据集为基础,用两层神经网络进行了学习,修改和加上注释后,代码和运行结果如下:

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
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=100, output_size=10)#创造一个神经网络类,隐藏层数据可以设置其他合理值

iters_num = 10000  # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100#抽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):
    batch_mask = np.random.choice(train_size, batch_size)#选100个随机数
    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'):
        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()

在这里插入图片描述在这里插入图片描述
代码循环次数为10000,每一次都随机抽100个进行学习,计算损失函数值,输出一次学习结果,记录准确率,可以明显的看到准确率在逐渐增加,一般来说,准确率的增加也有可能意味着过拟合的出现,但是可以从图中看到,随着学习进行,训练数据和测试数据的精度几乎同步上升,且基本重合,因此可以说没有出现过拟合。

总结

神经网络的学习中有许多重要的概念,如梯度、损失函数、梯度下降等,在弄清楚了这些之后就能更好的理解神经网络的学习过程。

参考文献

  1. 《深度学习——基于Python的理论实现》
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值