彻底理解BP之手写BP图像分类你也行
转自:https://zhuanlan.zhihu.com/p/397963213
第一节:用矩阵的视角,看懂BP的网络图
1.1、什么是BP反向传播算法
- BP(Back Propagation)误差反向传播算法,使用反向传播算法的多层感知器又称为BP神经网络。BP是当前人工智能主要采用的算法,例如你所知道的CNN、GAN、NLP中的Bert、Transformer,都是BP体系下的算法框架。
- 理解BP对于理解网络如何训练很重要
- 在这里我们采用最简单的思路理解BP。确保能够理解并且复现
1.2、矩阵乘法
1.3、感知机
- 感知机模拟了神经元突触的信息传递
1.4、感知机-矩阵表示
- 用矩阵的视角来定义感知机结构
1.5、感知机-多个样本
- 当a、b是第一个,x、y是第二个样本时
1.6、感知机-增加偏置
- 关于偏置的存在,考虑y = kx + b直线公式,若b=0,则退化为y = kx,此时表达的直线必定过0点,无法表达不过0点的直线,所以偏置在这里非常重要
1.7、感知机-多个样本,并增加偏置(样本维度增加)
1.8、感知机-多个输出,同一个样本(输出维度增加)
- 讨论增加一个输出时的样子
1.9、感知机-多个样本,多个输出(样本、输出维度同时增加)
- 当样本维度,和输出维度同时增加时
1.10、关于广播机制
- 对于矩阵A和B的元素操作(点乘、点加、点除等等)。广播机制约定了,假设A是1x5,B是3x5,则约定把A在行方向复制3份后,再与B进行元素操作,同理可以发生在列上,或发生在B上
1.11、以下是动画
- 解释维度增加时,矩阵表示的差异
1.12、锻炼一下
- 注意这里没有考虑激活的存在
- 是否和你想的一样?
- 再回过头看BP的图,你看懂了吗?学会了用矩阵的视角看这种了吗?
- 这种图通常省略了偏置和激活,实际中都存在偏置和激活
第二节:BP在干嘛,到,函数的最小化
- BP到底干了嘛,函数最小化是什么?
2.1 实际例子,理解样本书、特征数
2.2 理解BP的意义
- 当我们明白了,特征可以逐层映射到结论时,输入特征和结论可以收集得到。权重该怎么来呢?对,BP就是在给定输入特征和输出结论后,告诉你中间权重应该取值多少是合适的
2.3 样本1
- 这里分析了单位带来的问题
2.4 样本2
2.5 发生误差
- 这里着重强调输入特征、推测结果、结论之间的关系
- 通过推测结果和真实情况之间的误差,反向传递到模型中,促使模型做出调整,使得推测结果更接近真实情况。用到的方法即误差反向传播算法(BP,Back Propagation)
2.6 定义误差度量方式
- 注意,分类问题二元交叉熵更合适,这里为了简化
2.7 转换为函数最小化问题
- 归根结底是为了知道误差最小时, [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YuRuTYw4-1657805811986)(https://www.zhihu.com/equation?tex=%5Ctheta)] 的取值,如何得到最合适的权重?
- 这里提到,BP告诉我们,采用 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HfClaXwG-1657805811986)(https://www.zhihu.com/equation?tex=%5Ctheta)] 的负梯度方向,关于负梯度方向,请看后面分析
2.8 函数最小化举例1
2.9 函数最小化举例2
- 为什么是适当的很小步长,因为梯度方向告诉我们函数上升最快的方向,但是并没有人告诉我们,距离最小值有多远,那么我们只能走很小的一步。然后再看看梯度方向,继续走很小一步。多次迭代后,找到最小值。如果步长太大,其结果是在中间震荡,无法收敛。所以是超参数,经验值
第三节:矩阵求导的推导和结论
- 既然BP可以用矩阵描述,那么反向求导时,则需要处理矩阵求导。这是简化BP理解的一个关键,一定不要用单个值的方式去理解他,太绕了,还难以实现
3.1 导数定义
3.2 f(x)=ax时的导数
3.3 f(x)=x^2时的导数
3.4 使用梯度下降求解sqrt(a)
- 有了导数值,我们可以使用梯度下降(负梯度方向更新)法,迭代找到误差函数的极小值位置,今儿找到我们想要的解
- 步骤如下:
- 代码部分如下:
3.5 扩展阅读,使用牛顿法求解sqrt(a)
- 牛顿法更新时,采用的是x = x - 一阶导/二阶导,速度比梯度下降法快不少,但是他要求解二阶导很难计算
3.6 矩阵求导,定义操作
- 定义基本操作,模拟误差计算函数,使用矩阵表达
3.7 定义误差函数error
3.8 汇总error的定义
3.9 error对A的偏导数
3.10 error对B的偏导数
3.11 矩阵求导结论
第四节:代码实现
4.1 介绍
- 对于C++
- 既然是基于矩阵操作,则首先要实现基于C++的矩阵类。这里matrix.hpp解决矩阵操作问题(矩阵的元素操作、广播等)
- 由于C++矩阵乘法效率问题,可以考虑引用OpenBLAS库
- 工程实现代码请访问:https://github.com/shouxieai/bp-cpp
- 对于Python
- 直接利用Numpy可以轻易实现矩阵操作、广播、元素乘法
- Python中Numpy的矩阵操作,已经进行了优化
IDE采用VSCode,编译采用Makefile,若要配置Makefile和vscode的开发环境,请访问:使用Makefile配置标准工程环境
4.2 图像矩阵化
4.3 训练逻辑
- 加载mnist数据集为矩阵,分别有:
- 训练集图像:50000 x 784
- 训练集标签:50000 x 1
- 测试集图像:10000 x 784
- 测试集标签:10000 x 1
\2. 预处理数据
-
- 将训练集图像转换为浮点数,并做归一化
- 将训练集标签转换为onehot热独编码,变为50000 x 10
- 将测试集图像转换为浮点数,并做归一化
- 将测试集标签转换为onehot热独编码,变为10000 x 10
- 将训练集图像转换为浮点数,并做归一化
\3. 初始化部分
-
-
初始化超参数,隐藏层数量定为1024,迭代次数10轮,动量0.9,批次大小256
-
定义4个权重,分别是
-
- 输入映射到隐层(input_to_hidden):784 x 1024
- 隐层偏置(hidden_bias):1 x 1024
- 隐层到输出层(hidden_to_output):1024 x 10
- 输出层偏置(output_bias):1 x 10
- 输入映射到隐层(input_to_hidden):784 x 1024
-
初始化权重,使用凯明初始化fan_in + fan_out,偏置初始化为0
-
-
\4. 前向部分 - forward
-
-
从训练集中,随机选择batch个样本记为x(batch x 784)。选择对应的onehot标签记为y(batch x 10)
-
以x乘以映射矩阵(input_to_hidden),然后加上隐层偏置,再对结果做激活。作为隐层输出,这里采用relu函数为激活
-
- hidden_act = (x @ input_to_hidden + hidden_bias).relu()
-
以hidden_act乘以映射矩阵(hidden_to_output),然后加上输出层偏置,再对结果做激活。作为输出层的输出值。这里采用sigmoid函数做激活
-
- probability = (hidden_act @ hidden_to_output + output_bias).sigmoid()
-
使用probability和y计算交叉熵损失,并打印损失
-
-
\5. 反向部分 - backward
-
- 计算loss对所有权重的梯度,例如先计算对括号内的导数,然后链式求导往前递推直至所有权重梯度计算出来,这里利用矩阵求导
- 对所有权重,和其梯度值,执行SGDMomentum算法更新权重。该算法相比前面讲的增加了动量因素。稍微公式不一样
- 计算loss对所有权重的梯度,例如先计算对括号内的导数,然后链式求导往前递推直至所有权重梯度计算出来,这里利用矩阵求导
\6. 循环迭代,直至迭代次数满足定义次数后结束并储存模型
PPT课件下载
原稿地址
视频讲解
崔更,规划中,B站主页地址:https://space.bilibili.com/1413433465/