美文网首页
《深度学习入门》第4章 神经网络的学习

《深度学习入门》第4章 神经网络的学习

作者: e9f3ca3721bc | 来源:发表于2023-04-12 23:36 被阅读0次

    1 第4章整体思路

    神经网络的学习:神经网络存在合适的权重(w)和偏置(b),调整权重和偏置以便拟合训练数据的过程叫做“学习”;

    个人理解:使用训练数据进行学习,调整参数,让模型预测得更准确,其中参数就是权重和偏置,准确度通过损失函数观察,该往什么方向调整通过损失函数的梯度决定;

    神经网络学习步骤:mini-batch-->计算梯度-->更新参数;

    2层神经网络代码实现思路:(TwoLayerNet类)
    构造函数:提供初始化方法;
    predict函数:传入x作为第1层input值,计算数组乘积(参数通过构造函数初始化),使用sigmoid激活函数([0,input])计算出中间值;中间值作为第二层的input值,计算数组乘积(参数通过构造函数初始化),使用softmax函数(化为0.0-1.0间的值)作为输出函数计算输出值y;
    loss函数:返回CE损失函数计算后的值;
    accuracy函数:每经过一个epoch,就计算精度
    gradient函数/numerical_gradient函数:计算梯度

    2 详细内容

    导入部分:

    常用的特征量SIFT,SURF,HOG
    常用的分类器SVM,KNN
    机器学习中,一般使用测试数据与训练数据两部分进行学习和实验。首先,使用训练数据学习,寻找最优的参数,然后使用测试数据评价训练得到的模型的实际能力,获得泛化能力(就是处理未被观察的数据的能力);
    训练数据又叫做监督数据;
    过拟合:对某个数据集过度拟合的状态;

    损失函数

    寻找最优参数时,要寻找使损失函数的值尽可能小的参数;寻找的过程通过损失函数的导数实现;如果导数值为负,通过使参数向正方向改变,可以减少Loss的值,若导数值为正,通过使参数向负方向改变,可以减少Loss的值。

    从纯数学的角度是比较好理解的,我们要找某个函数最小值,可以求导数找驻点,然后计算最小值;当有正参数参与计算时,可以求导数,改变参数的值以获得最小值;
    作者从正反两个方向回答了why的问题,即精度是和阶跃函数都不能产生连续的变化(都是离散的,比如100笔中识别成功20笔,永远是离散的数据),所以改参数没什么用,而建立函数,求导数,更能了解数据走向;我们做数学题也是这个方法,看来这是人类解决问题的一个重要方法;
    损失函数-均方误差

    E=½∑(y-t)² #y是output值,t是监督数据,k是维数

    看上去是基于方差的实现思想,方差本来就是度量随机变量 和其数学期望 (即 均值 )之间的偏离程度。代码直接return公式就行;

    损失函数-交叉熵误差CE

    E=-∑tlogy #y是Output,t是正确解标签 就是自然对数取反,因为这样结果是正数

    实际实现时,为了避免溢出,会在Log里加一个微小值参数delta;代码实现也是直接return公式;

    mini-batch学习
    就是求大量数据的Loss,然后求这些loss-value的均值;书中给出了CEloss的批次处理公式;

    E=-1/n∑∑tlogy

    代码实现:

    import numpy as np
    
    def cross_entropy_error(y, t):
        if y.ndim == 1:#y.ndim维度
            t = t.reshape(1, t.size)#转换成1行size列
            y = y.reshape(1, y.size)#转换成1行size列
    
        # 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
        if t.size == y.size:
            t = t.argmax(axis=1)#数组最大索引号
    
        batch_size = y.shape[0]#这种写法是行数
        return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
        #公式照抄 range单参数:0-batchsize....
    

    梯度导入-数值微分-导数
    数值微分 numerical differentiation
    导数表示瞬间变化量,数学中通常是取极限;计算机不能直接求极限,实现方式是选择一个比较小的值,并且将计算方法转化成中心差分的形式计算,有误差是肯定的;

    数值微分有误差,为了减少误差可以计算函数的差分。利用微小的差分求导数的过程叫做数值微分。使用过小的值会造成计算机出现溢出的问题(浮点数都是有位数的),所以微小值要选择合适的值;

    梯度导入-数值微分-偏导数

    有多个变量的函数的导数叫偏导数

    书中给出的是固定一个变量,另一个变量转化成常量用求导数的方法求偏导;
    梯度

    由全部变量的偏导数汇总而成的变量称为梯度;
    实际上,梯度会指向各处的函数值降低的方向。更严格地讲,梯度指示的方向是各点出的函数值减少最多的方向。

    从代码上看,就是return了导数的数组;

    代码实现:

    #梯度
    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]
            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
    
    def function_2(x):
        return x[0]**2+x[1]**2#平方之和
    
    grad = test.numerical_gradient(function_2,np.array([3.0,4.0]))
    print(grad)
    
    D:\software\Anaconda\python.exe D:\workspace_p\study\ch04\test.py 
    [6. 8.]
    [6. 8.]
    
    Process finished with exit code 0
    

    找最优参数是指损失函数最小值的参数。一般损失函数很复杂,使用梯度来寻找函数最小值的方法叫做梯度法;梯度为0的地方不一定是最小资,且当函数复杂并且呈扁平状时,学习可能进入停滞期;
    虽然梯度不一定指向最小值,但是沿着梯度的方向可以最大限度减少函数的值。
    梯度法中,函数的取值从当前位置沿着梯度前进,然后在新的地方重新求梯度,如此反复(类似迭代)。梯度法有梯度上升法和梯度下降法,梯度法主要是指下降法;
    梯度法代码实现:

    #梯度下降法
    def gradient_descent(f,init_x,lr=0.01,step_num=100):#lr 学习率
        x = init_x;
    
        for i in range(step_num):
            grad = numerical_gradient(f,x)
            x -= lr*grad#梯度法公式 x0-lr*偏导 就是让x动起来
    
        return x
    
    #发现函数中可以不写文件名,但是在外部调用必须写
    init_x = np.array([-3.0,4.0])#总是忘加[]
    grad = test.gradient_descent(function_2,init_x=init_x,lr=0.1,step_num=100)
    print(grad)
    
    D:\software\Anaconda\python.exe D:\workspace_p\study\ch04\test.py 
    [-6.11110793e-10  8.14814391e-10]
    [-6.11110793e-10  8.14814391e-10]
    Process finished with exit code 0
    

    学习率过大的话,会发散成很大的值,学习率过小,基本上没有怎么更新就结束了;像学习率这样的参数叫做超参数,是人工设定的;


    代码实现(这章的代码对于我来说有点难以理解了,所以加了些注释):
    两层神经网络:

    # coding: utf-8
    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)#创建1维数组 元素个数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)#cout[0,x]
            a2 = np.dot(z1, W2) + b2
            y = softmax(a2)#cout 0.0-1.0 带e的那个公式
            
            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 
    
    # 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=50, output_size=10)
    
    iters_num = 1000  # 适当设定循环的次数
    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)
    
    #numpy.random.choice(a, size=None, replace=True, p=None)
    #从a(只要是ndarray都可以,但必须是一维的)中随机抽取数字,并组成指定大小(size)的数组
    #replace:True表示可以取相同数字,False表示不可以取相同数字
    #数组p:与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
    
    for i in range(iters_num):#10000
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]#dataset里的
        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: #max(train_size / batch_size, 1) 每经过一个epoch,就计算识别精度
            train_acc = network.accuracy(x_train, t_train)#成功数据百分比
            test_acc = network.accuracy(x_test, t_test)
            train_acc_list.append(train_acc)#放入train_acc数组
            test_acc_list.append(test_acc)#放入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()
    

    end

    相关文章

      网友评论

          本文标题:《深度学习入门》第4章 神经网络的学习

          本文链接:https://www.haomeiwen.com/subject/hzioddtx.html