机器学习系列 【三】神经网络


参考博客:https://blog.csdn.net/qq_32865355/article/details/80260212
参考博客:https://blog.csdn.net/yato0514/article/details/82261821

一、神经网络简介

神经网络是属于机器学习领域的一种方法,也是所谓深度学习的一个基础。它是一种以人脑为模型的机器学习,简单地说就是创造一个人工神经网络,通过一种算法允许计算机通过合并新的数据来学习。

二、误差反向传播

误差反向传播算法简称反向传播算法(即BP算法)。使用反向传播算法的多层感知机又称为BP神经网络。BP算法是一个迭代算法,它的基本思想为:
(1)先计算每一层的状态和激活值,直到最后一层(即信号是前向传播的);
(2)计算每一层的误差,误差的计算过程是从最后一层向前推进的(这就是反向传播算法名字的由来);
(3)更新参数(目标是误差变小)。迭代前面两个步骤,直到满足停止准则(比如相邻两次迭代的误差的差别很小或者达到预设的迭代次数)。
本文记号说明:
在这里插入图片描述
下面以三层感知器(即只含有一个隐藏层的多层感知器)为例介绍“反向传播算法(BP 算法)”。
在这里插入图片描述

1 信息前向传播

在这里插入图片描述
注意:此处L层的权值矩阵,其行列为(L层节点数,L-1层节点数)

2 误差反向传播

在这里插入图片描述
在这里插入图片描述

2.1 输出层的权重参数更新

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 隐藏层的权重参数更新

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 输出层和隐藏层的偏置参数更新

在这里插入图片描述
在这里插入图片描述

2.4 BP算法四个核心公式

在这里插入图片描述

2.5 BP 算法计算某个训练数据的代价函数对参数的偏导数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.6、梯度下降实现参数更新

在这里插入图片描述
在这里插入图片描述
当然,梯度下降算法也有很多种方式,比如随机梯度下降、批量梯度下降以及小批量梯度下降,这些在下一结将进行具体介绍。

三、梯度下降法

在机器学习中,对于很多监督学习模型,需要对原始的模型构建损失函数,接下来便是通过优化算法对损失函数进行优化,以便找到最优的参数。
梯度下降法作为机器学习中较常使用的优化算法,在其求解过程中,只需要求解损失函数的一阶导数,计算的代价比较小。
基本思想可以理解为:我们从山上的某一点出发,找一个最抖的坡走一步(也就是找梯度方向),到达一个点之后,再找最陡的坡,再走一步,直到不断的走,走到最低点(最小花费函数收敛点)
梯度下降法有三种不同的形式:批量梯度下降(Batch Gradient Descent)、随机梯度下降(Stochastic Gradient Descent)以及小批量梯度下降(Mini-Batch Gradient Descent)。其中小批量梯度下降法也常用在深度学习中进行模型的训练。接下来,我们将对这三种不同的梯度下降法进行理解。

1 批量梯度下降法(BGD)

批量梯度下降法是最原始的形式,它是指在每一次迭代时使用所有样本来进行梯度的更新。从数学上理解如下:
  (1)对目标函数求偏导:
在这里插入图片描述
  其中 i=1,2,…,m 表示样本数, j=0,1 表示特征数,这里我们使用了偏置项 x(i)0=1 。
  (2)每次迭代对参数进行更新:
  在这里插入图片描述
  其中,α代表学习率。批量梯度下降的优缺点为:
  优点:
  (1)一次迭代是对所有样本进行计算,此时利用矩阵进行操作,实现了并行。
  (2)由全数据集确定的方向能够更好地代表样本总体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD一定能够得到全局最优。
  缺点:
  (1)当样本数目 m 很大时,每迭代一步都需要对所有样本计算,训练过程会很慢。

2 随机梯度下降法 (SGD)

随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快。
对于一个样本的目标函数为:
在这里插入图片描述
优点:
  (1)由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数的更新速度大大加快。
缺点:
  (1)准确度下降。由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。
  (2)可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。
  (3)不易于并行实现。
解释一下为什么SGD收敛速度比BGD要快:
  答:这里我们假设有30W个样本,对于BGD而言,每次迭代需要计算30W个样本才能对参数进行一次更新,需要求得最小值可能需要多次迭代(假设这里是10);而对于SGD,每次更新参数只需要一个样本,因此若使用这30W个样本进行参数更新,则参数会被更新(迭代)30W次,而这期间,SGD就能保证能够收敛到一个合适的最小值上了。也就是说,在收敛时,BGD计算了 10×30W 次,而SGD只计算了 1×30W 次。

3 小批量梯度下降法(M-BGD)

小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代 使用batch_size个样本来对参数进行更新。
在这里插入图片描述
优点:
  (1)通过矩阵运算,每次在一个batch上优化神经网络参数并不会比单个数据慢太多。
  (2)每次使用一个batch可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。(比如上例中的30W,设置batch_size=100时,需要迭代3000次,远小于SGD的30W次)
  (3)可实现并行化。
缺点:
  (1)batch_size的不当选择可能会带来一些问题。
  
小批量的梯度下降可以利用矩阵和向量计算进行加速,还可以减少参数更新的方差,得到更稳定的收敛。在MSGD中,学习速率一般设置的比较大, 随着训练不断进行,可以动态的减小学习速率,这样可以保证一开始算法收敛速度较快。实际中如果目标函数平面是局部凹面,传统的SGD往往会在此震荡,因为一个负梯度会使其指向一个陡峭的方向,目标函数的局部最优值附近会出现这种情况,导致收敛很慢,这时候需要给梯度一个动量(momentum),使其能够跳出局部最小值,继续沿着梯度下降的方向优化,使得模型更容易收敛到全局最优值
batcha_size的选择带来的影响:
  (1)在合理地范围内,增大batch_size的好处:
    a. 内存利用率提高了,大矩阵乘法的并行化效率提高。
    b. 跑完一次 epoch(全数据集)所需的迭代次数减少,对于相同数据量的处理速度进一步加快。
    c. 在一定范围内,一般来说 Batch_Size 越大,其确定的下降方向越准,引起训练震荡越小。
  (2)盲目增大batch_size的坏处:
    a. 内存利用率提高了,但是内存容量可能撑不住了。
    b. 跑完一次 epoch(全数据集)所需的迭代次数减少,要想达到相同的精度,其所花费的时间大大增加了,从而对参数的修正也就显得更加缓慢。
    c. Batch_Size 增大到一定程度,其确定的下降方向已经基本不再变化。
 下图显示了三种梯度下降算法的收敛过程:
在这里插入图片描述

四、代码实现

本文仅通过numpy实现神经网络算法,包括模型搭建,反向传播以及梯度下降。其中,梯度下降分别使用了批量梯度下降、随机梯度下降以及小批量梯度下降,分别对应train1()、train2()以及train3().可通过不同调用来尝试这三种优化算法。使用的数据集为.cvs格式的MNIST数据集。

import numpy as np

class NeuralNetwork():

    def __init__(self, input_num, hidden_num, output_num, train_file, test_file, epoch):
        self.input_num = input_num
        self.hidden_num = hidden_num
        self.output_num = output_num
        self.rate = 0.1
        self.feat = []
        self.labels = []
        self.test_feat = []
        self.test_label = []
        self.target_tr_label = 0
        self.target_te_label = 0
        self.train_file = train_file
        self.test_file = test_file
        self.w_hidden = 0
        self.b_hidden = 0
        self.w_output = 0
        self.b_output = 0
        self.epoch = epoch

    def load_dataset(self, num):
        if num == 0:
            file = open(self.train_file, 'r')
            feat = []
            label =[]
            for i, d in enumerate(file):
                d = map(float, d.split(','))
                d = list(d)
                temp = []
                for i in range(1, len(d)):
                    temp.append(float(d[i]))
                feat.append(temp)
                label.append(d[0])
                self.feat = np.array(feat)
                self.feat = (self.feat / 255) * 0.99 + 0.01
                self.labels = np.array(label)

        if num == 1:
            file = open(self.test_file, 'r')
            test_feat = []
            for i, d in enumerate(file):
                d = map(float, d.split(','))
                d = list(d)
                temp = []
                for i in range(1, len(d)):
                    temp.append(float(d[i]))
                test_feat.append(temp)
                self.test_label.append(d[0])
                self.test_feat = np.array(test_feat)
                self.test_feat = (self.test_feat / 255) * 0.99 + 0.01

    def transform_label(self):
        """将标签0-9,转为0-1表示"""
        self.target_tr_label = np.zeros((self.output_num, len(self.labels))) + 0.01
        self.target_te_label = np.zeros((self.output_num, len(self.test_label))) + 0.01
        for i in range(len(self.labels)):
            self.target_tr_label[int(self.labels[i])][i] = 0.99
        for i in range(len(self.test_label)):
            self.target_te_label[int(self.test_label[i])][i] = 0.99

    def init_param(self):
        """初始化权值和偏重"""
        self.w_hidden = np.random.normal(0.0, pow(self.hidden_num, -0.5), (self.hidden_num, self.input_num))
        self.b_hidden = np.random.normal(0.0, pow(self.hidden_num, -0.5), (self.hidden_num,1))
        self.w_output = np.random.normal(0.0, pow(self.output_num, -0.5), (self.output_num, self.hidden_num))
        self.b_output = np.random.normal(0.0, pow(self.output_num, -0.5), (self.output_num,1))

    def signoid(self, x):
        return 1/(1+np.exp(-x))

    def diff_signoid(self, x):
        a = self.signoid(x)
        return a*(1-a)

    def forward(self, in_put):
        hidden_input = np.dot(self.w_hidden, in_put) + self.b_hidden   # 200x100  # hidden_num行,数据个数列。即每一列代表一个输出
        hidden_output = self.signoid(hidden_input)                       # 200x100
        out_input = np.dot(self.w_output, hidden_output) + self.b_output  # 10x100    一列代表一组数据
        out_y = self.signoid(out_input)

        return out_y, out_input, hidden_output, hidden_input

    def train1(self):
        """批量梯度下降  BGD"""
        self.load_dataset(0)     # 加载训练数据
        self.load_dataset(1)     # 加载测试数据
        self.init_param()        # 初始化参数
        self.transform_label()   # 转化标签
        epoch = 0
        while epoch < self.epoch:
            epoch += 1
            out_y, out_input, hidden_output, hidden_input = self.forward(self.feat.T)
            err_out = self.target_tr_label - out_y
            delta_out = -err_out * self.diff_signoid(out_input)     # 10x100
            delta_bout = np.sum(self.rate * delta_out, axis=1)/len(self.labels)
            delta_bout = delta_bout.reshape(10,1)
            variable = (delta_out @ hidden_output.T)/100     # 10x100  100*200  将所有数据的误差求和
            self.w_output = self.w_output - self.rate * variable
            self.b_output = self.b_output - delta_bout
            delta_hidden = self.w_output.T @ delta_out  * self.diff_signoid(hidden_input)
            variable1 = (delta_hidden @ self.feat)/100
            self.w_hidden = self.w_hidden - self.rate * variable1
            delta_b_hidden = np.sum(delta_hidden, axis=1)/len(self.labels)
            delta_b_hidden = delta_b_hidden.reshape(200, 1)
            self.b_hidden = self.b_hidden - 0.1 * delta_b_hidden
        out_y, out_input, hidden_output, hidden_input = self.forward(self.feat.T)
        label = np.argmax(out_y, axis=0)
        score = []
        for i in range(len(self.labels)):
            if int(label[i]) == int(self.labels[i]):
                score.append(1)
            else:
                score.append(0)

        score = np.asarray(score)
        accuracy = np.sum(score) / (len(self.labels))
        print("准确率为:%5f" % accuracy)

    def train2(self):
        """随机梯度下降  SGD"""
        self.load_dataset(0)  # 加载训练数据
        self.load_dataset(1)  # 加载测试数据
        self.init_param()  # 初始化参数
        self.transform_label()  # 转化标签
        epoch = 0
        while epoch < 50000:
            epoch += 1
            for i in range(len(self.feat)):
                x = self.feat[i, :]
                x = np.transpose([x])
                y = self.target_tr_label[:, i]
                y = np.transpose([y])
                out_y, out_input, hidden_output, hidden_input = self.forward(x)
                err_out = y - out_y
                delta_out = -err_out * self.diff_signoid(out_input)  # 10x100
                variable = (delta_out @ hidden_output.T)
                self.w_output = self.w_output - self.rate * variable
                self.b_output = self.b_output - delta_out
                delta_hidden = self.w_output.T @ delta_out * self.diff_signoid(hidden_input)
                variable1 = (delta_hidden @ x.T)
                self.w_hidden = self.w_hidden - self.rate * variable1
                self.b_hidden = self.b_hidden - 0.1 * delta_hidden
                if i == len(self.feat)-1:
                    out_y1, out_input1, hidden_output1, hidden_input1 = self.forward(self.feat.T)
                    label = np.argmax(out_y1, axis=0)
                    score = []
                    for j in range(len(self.labels)):
                        if int(label[j]) == int(self.labels[j]):
                            score.append(1)
                        else:
                            score.append(0)

                    score = np.asarray(score)
                    accuracy = np.sum(score) / (len(self.labels))
                    print("准确率为:%5f" % accuracy)

    def train3(self):
        """批量梯度下降  MBGD"""
        mini_bach = 16
        self.load_dataset(0)  # 加载训练数据
        self.load_dataset(1)  # 加载测试数据
        self.init_param()  # 初始化参数
        self.transform_label()  # 转化标签
        epoch = 0
        while epoch < 5000:
            epoch += 1
            for i in range(0, len(self.feat), mini_bach):
                if i+mini_bach < len(self.feat):
                    x = self.feat[i:i+mini_bach, :]
                    y = self.target_tr_label[:, i:i+mini_bach]
                else:
                    """多余的这些数据如何处理呢"""
                    num = len(self.feat) - 1 - i
                    x = self.feat[i:i + num, :]
                    y = self.target_tr_label[:, i:i + num]
                out_y, out_input, hidden_output, hidden_input = self.forward(x.T)
                err_out = y - out_y
                delta_out = -err_out * self.diff_signoid(out_input)  # 10x100
                delta_bout = np.sum(self.rate * delta_out, axis=1) / len(self.labels)
                delta_bout = delta_bout.reshape(10, 1)
                variable = (delta_out @ hidden_output.T) / 16  # 10x100  100*200  将所有数据的误差求和
                self.w_output = self.w_output - self.rate * variable
                self.b_output = self.b_output - delta_bout
                delta_hidden = self.w_output.T @ delta_out * self.diff_signoid(hidden_input)
                variable1 = (delta_hidden @ x) / 16
                self.w_hidden = self.w_hidden - self.rate * variable1
                delta_b_hidden = np.sum(delta_hidden, axis=1) / len(self.labels)
                delta_b_hidden = delta_b_hidden.reshape(200, 1)
                self.b_hidden = self.b_hidden - 0.1 * delta_b_hidden

                out_y1, out_input1, hidden_output1, hidden_input1 = self.forward(self.feat.T)
                label = np.argmax(out_y1, axis=0)
                score = []
                for j in range(len(self.labels)):
                    if int(label[j]) == int(self.labels[j]):
                        score.append(1)
                    else:
                        score.append(0)

                score = np.asarray(score)
                accuracy = np.sum(score) / (len(self.labels))
                print("准确率为:%5f" % accuracy)


    def predict(self):
        out_y, out_input, hidden_output, hidden_input = self.forward(self.test_feat.T)
        label1 = np.argmax(out_y, axis=0)
        score = []
        for i in range(len(self.test_label)):
            if int(label1[i]) == int(self.test_label[i]):
                score.append(1)
            else:
                score.append(0)

        score = np.asarray(score)
        accuracy = np.sum(score)/(len(self.test_label))
        print("准确率为:%5f" % accuracy)


if __name__ =="__main__":
    train_file = "D:/hwProject/data/mnist_train_100.csv"
    test_file = "D:/hwProject/data/mnist_test_10.csv"
    bp = NeuralNetwork(784, 200, 10, train_file, test_file, 30000)
    bp.train3()
    bp.predict()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值