一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

好吧,只好拆分为上下两篇发布了>_<

终于肝出来了,今天就是除夕夜了,祝大家新快乐!^q^

《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础 (上)(逻辑回归、Softmax回归、信息论基础)(DL笔记整理系列)

3043331995@qq.com

https://fanfansann.blog.csdn.net/

https://github.com/fanfansann/fanfan-deep-learning-note

作者:繁凡

version 1.0 2022-1-20

声明:

1)《繁凡的深度学习笔记》是我自学完成深度学习相关的教材、课程、论文、项目实战等内容之后,自我总结整理创作的学习笔记。写文章就图一乐,大家能看得开心,能学到些许知识,对我而言就已经足够了 ^q^ 。

2)因个人时间、能力和水平有限,本文并非由我个人完全原创,文章部分内容整理自互联网上的各种资源,引用内容标注在每章末的参考资料之中。

3)本文仅供学术交流,非商用。所以每一部分具体的参考资料并没有详细对应。如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除,非常感谢各位为知识传播做出的贡献!

4)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢。

5)本文由我个人( CSDN 博主 「繁凡さん」(博客) , 知乎答主 「繁凡」(专栏), Github 「fanfansann」(全部源码) , 微信公众号 「繁凡的小岛来信」(文章 P D F 下载))整理创作而成,且仅发布于这四个平台,仅做交流学习使用,无任何商业用途。

6)「我希望能够创作出一本清晰易懂、可爱有趣、内容详实的深度学习笔记,而不仅仅只是知识的简单堆砌。」

7)本文《繁凡的深度学习笔记》全汇总链接:《繁凡的深度学习笔记》前言、目录大纲

8)本文的Github 地址:https://github.com/fanfansann/fanfan-deep-learning-note/ 孩子的第一个 『Github』!给我个 ⭐ Starred \boxed{⭐ \,\,\,\text{Starred}}⭐Starred ​ 嘛!谢谢!!o(〃^▽^〃)o

9)此属 version 1.0 ,若有错误,还需继续修正与增删,还望大家多多指点。本文会随着我的深入学习不断地进行完善更新,Github 中的 P D F 版也会尽量每月进行一次更新,所以建议点赞收藏分享加关注,以便经常过来回看!

如果觉得还不错或者能对你有一点点帮助的话,请帮我给本篇文章点个赞,你的支持是我创作的最大动力!^0^

要是没人点赞的话自然就更新慢咯 >_<

文章目录

一文让你完全弄懂 信息论基础 与 softmax 回归实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(下)目录:

3.3 信息论基础

3.3.1 信息论

3.3.2 香农熵

3.3.3 相对熵(KL 散度)

3.3.4 交叉熵

3.3.5 JS 散度

3.3.6 Wasserstein 距离

3.3.7 结构化概率模型

3.4 Softmax 回归

3.4.1 Softmax 回归模型

3.4.2 优化方法

3.4.3 权重衰减

3.4.4 Fashion-MNIST 数据集图片分类实战

3.5 Softmax 回归与逻辑回归的关系

3.5.1 Softmax 回归与逻辑回归的关系

3.5.2 多分类问题的 Softmax 回归与 k 个二元分类器

3.6 面试问题集锦

3.7 参考资料

本章话题 第 3 章(上)(点击即可跳转哟):
话题 1 :什么是分类问题?;

《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础 (上)

话题 1 :什么是分类问题?

分类问题(classification),即找一个函数判断输入数据所属的类别,也即预测预测值属于某一段连续的实数区间的方法。分类问题可以是 二类别问题(是 / 不是),也可以是 多类别问题(在多种类别中判断输入数据具体属于哪一个类别)。与 回归问题(regression)相比,分类问题的输出不再是连续值,而是离散值,用来指定其属于哪个类别。分类问题在现实中应用非常广泛,比如垃圾邮件识别,手写数字识别,人脸识别,语音识别等。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图源:[15]

本章我们先从简单的多分类手写数字图片分类问题实战开始入手,使用 简单神经网络 解决分类问题,带大家切身体会一下分类问题的流程以及神经网络的魅力,我们将在 第 6 章 神经网络与反向传播算法 中详细讲解神经网络,并使用诸如 通用逼近定理(universal approximation theorem)等理论来尝试说明神经网络为何如此强大。在体验完神经网络解决手写数字分类问题以后,我们将详细介绍机器学习中关于分类问题的 线性分类器:解决二分类问题的 逻辑回归 以及由其拓展而来的解决多分类问题的 softmax 回归,理解和掌握这些理论,是机器学习的基础。

; 3.1 手写数字分类问题

3.1.1 手写数字图片数据集

我们前面讲过机器学习是一种需要从数据中间学习的行为,因在解决机器学习问题之前我们需要先采集大量的真实样本数据 ( x , y ) (x,y)(x ,y ) 。我们以手写的数字图片识别为例,如图 3.1 所示,我们需要收集大量的由真人书写的 0 ∼ 9 0\sim 9 0 ∼9​​ 的数字图片,为了便于存储和计算,一般把收集的原始图片缩放到某个固定的大小 (Size 或 Shape),比如图片有 224 224 2 2 4​​ 个像素的行和 224 224 2 2 4​​ 个像素的列,则称图片大小为 224 × 224 224 × 224 2 2 4 ×2 2 4​​。我们将这张图片作为一组输入数据的 x x x​​。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.1 手写数字图片示例

同时,我们需要给每一张图片标注一个标签,也即图片的真实分类 y y y​​​​​ ,表明这张图片属于哪一个具体的类别,我们一般通过映射方式将类别名一一对应到从 0 0 0​​​​​ 开始编号的数字,比如说硬币的正反面,我们可以用 0 0 0​​​​​ 来表示硬币的反面,用 1 1 1​​​​​ 来表示硬币的正面,当然也可以反过来。这种编码方式叫作 数字编码(Number Encoding)。手写数字图片识别问题编码则更为直观,我们直接使用数字的 0 ∼ 9 0\sim 9 0 ∼9​​​​​ 来表示类别名字为 0 ∼ 9 0\sim 9 0 ∼9​​​​​​ 的图片。

如果希望模型能够在新样本上也能具有良好的表现,即 模型泛化能力 (GeneralizationAbility)较好,那么我们应该尽可能多地增加数据集的 规模多样性(Variance),使得我们用于学习的训练数据集与真实的手写数字图片的 分布(Ground-truth Distribution)尽可能的逼近,这样在训练数据集上面学到了模型能够很好的用于未见过的手写数字图片的预测。所以我们一般根据 82 82 8 2 原则也即将数据集按照 8 : 2 8:2 8 :2 的比例拆分为 训练集(Training Set)和 测试集(Test Set)。

机器学习的主要目的就是从训练集上学习到数据的真实模型,从而能够在未见过的测试集上也能够表现良好。为了确保我们量化一下模型在训练集和测试集上的表现,将其分别称为 训练误差 (training error) 和 测试误差 (test error) ,后者也经常称为 泛化误差 (generalization error) 。可以说,理想的模型就是在最小化训练误差的同时,最小化泛化误差。因此为了评估和测试各种算法的性能,不同问题下的统一且质量高的数据集必不可少。在手写数字图片识别问题下,Lecun, Bottou, Bengio, & Haffner 于 1998 年发布了名为 MNIST \text{MNIST}MNIST​​ 的手写数字图片数据集,它包含了 0 ∼ 9 0\sim 9 0 ∼9​​​ 共 10 10 1 0​​​ 种数字的手写图片,每种数字一共有 7000 7000 7 0 0 0​​​ 张图片,采集自不同书写风格的真实手写图片,一共 70000 70000 7 0 0 0 0​​​ 张图片。其中 60000 60000 6 0 0 0 0​​​ 张图片作为训练集 𝔻 t r a i n 𝔻_{\mathrm{train}}D t r a i n ​​​​ ,用来训练模型,剩下 10000 10000 1 0 0 0 0​​​ 张图片作为测试集 𝔻 t e s t 𝔻_{\mathrm{test}}D t e s t ​​​​ ,用来测试模型的效果。训练集和测试集共同组成了整个 MNIST \text{MNIST}MNIST 数据集 。

考虑到手写数字图片包含的信息比较简单,每张数字图片均被缩放到了统一的 28 × 28 28 × 28 2 8 ×2 8 的大小,同时只保留了灰度信息,如图 3.2 所示。这些图片由真人书写,包含了如字体大小、书写风格、粗细等丰富的样式,确保这些图片的分布与真实的手写数字图片的分布尽可能的接近,从而保证了模型的泛化能力。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.2 MNIST手写数字图片示例

每张图片包含了 h ℎh​​​​​ (Height/Row), 𝑤 𝑤w​​​​​ (Width/Column),每个位置保存了 像素(Pixel)值,像素值一般使用 0 ∼ 255 0\sim 255 0 ∼2 5 5​​​​​ 的整形数值来表达颜色的 强度信息,例如可以让 0 0 0​​​​​ 表示强度最低,255 255 2 5 5​​​​​ 表示强度最高。如果是彩色图片,则每个像素点包含了 R、G、B 三个通道的强度信息,分别代表三原色红、绿、蓝的红色通道、绿色通道、蓝色通道的 颜色强度,所以与灰度图片不同,它的每个像素点使用一个 1 1 1​​​​​ 维、长度为 3 3 3​​​​​ 的 向量(Vector) 来表示,向量的 3 3 3​​​​​ 个元素依次代表了当前像素点上面的 R、G、B 颜色强度,因此彩色图片需要保存为形状是 [ h , 𝑤 , 3 ] [ℎ, 𝑤, 3][h ,w ,3 ]​​​​​ 的 张量(Tensor,可以理解为 3 3 3​​ 维数组)。如果是灰度图片,则直接使用一个数值来表示灰度强度即可。如图 3.3 所示,令 0 0 0​​​​​ 表示纯黑,255 255 2 5 5​​​​​ 表示纯白,因此它只需要一个形为 [ h , 𝑤 ] [ℎ, 𝑤][h ,w ]​​​​​ 的二维 矩阵(Matrix)来表示一张图片信息(当然也可以保存为 [ h , 𝑤 , 1 ] [ℎ, 𝑤, 1][h ,w ,1 ]​​​​​​ 形状的张量)。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.3 图的表示示意图

; 3.1.2 构建模型

回顾我们在回归问题讨论的生物神经元结构。我们把一组长度为 d i n d_{in}d i n ​​ 的输入向量 𝒙 = [ 𝑥 1 , 𝑥 2 , … , 𝑥 𝑑 𝑖 𝑛 ] T 𝒙 =[𝑥_1, 𝑥_2, … , 𝑥_{𝑑_{𝑖𝑛}}]^{\mathrm T}x =[x 1 ​,x 2 ​,…,x d i n ​​]T​ 简化为单输入标量 x x x​,模型可以表达成 y = x × w + b y = x\times w + b y =x ×w +b​ 。如果是多输入、单输出的模型结构的话,我们需要借助于向量形式:

y = w T x + b = [ w 1 , w 2 , w 3 , … , w d i n ] ⋅ [ x 1 x 2 x 3 ⋯ x d i n ] + b (3.1) y=\boldsymbol{w}^{T} \boldsymbol{x}+b=\left[w_{1}, w_{2}, w_{3}, \ldots, w_{d_\mathrm{in}}\right] \cdot\left[\begin{array}{c}x_{1} \x_{2} \x_{3} \\cdots \x_{d_\mathrm{in}}\end{array}\right]+b\tag{3.1}y =w T x +b =[w 1 ​,w 2 ​,w 3 ​,…,w d i n ​​]⋅⎣⎢⎢⎢⎢⎡​x 1 ​x 2 ​x 3 ​⋯x d i n ​​​⎦⎥⎥⎥⎥⎤​+b (3 .1 )

更一般地,通过组合多个多输入、单输出的神经元模型,可以拼成一个多输入、多输出的模型:
y = w ⋅ x + b (3.2) y=\boldsymbol{w} \cdot \boldsymbol{x}+b\tag{3.2}y =w ⋅x +b (3 .2 )

其中,x ∈ R d i n , b ∈ R d o u t , y ∈ R d o u t , W ∈ R d o u t × d i n \boldsymbol{x} \in R^{d_{\mathrm{i n}}}, \boldsymbol{b} \in R^{d_{\mathrm{out}}}, \boldsymbol{y} \in R^{d_{\mathrm{out}}}, W \in R^{d_{\mathrm{out}} \times d_{\mathrm{i n}}}x ∈R d i n ​,b ∈R d o u t ​,y ∈R d o u t ​,W ∈R d o u t ​×d i n ​。

对于 多输出节点批量训练方式,我们将模型写成张量形式:
Y = X @ W + b (3.3) Y=X @ W+b\tag{3.3}Y =X @W +b (3 .3 )

其中 X ∈ R b × d i n , b ∈ R d o u t , Y ∈ R b × d o u t , W ∈ R d i n × d o u t X \in R^{b \times d_{\mathrm{in}}}, \boldsymbol{b} \in R^{d_{\mathrm{out}}}, \quad Y \in R^{b \times d_{\mathrm{out}}}, \quad W \in R^{d_{\mathrm{in}} \times d_{\mathrm{out}}}X ∈R b ×d i n ​,b ∈R d o u t ​,Y ∈R b ×d o u t ​,W ∈R d i n ​×d o u t ​​。d i n d_\mathrm{in}d i n ​​ 表示输入节点数,d o u t d_{\mathrm{out}}d o u t ​​ 表示输出节点数。张量 X X X​ shape 为 [ 𝑏 , d i n ] [𝑏, d_{\mathrm{in}} ][b ,d i n ​]​ ,表示 𝑏 𝑏b​ 个样本的输入数据,每个样本的特征长度为 d i n d_{\mathrm{in}}d i n ​​ ;张量 W W W​ 的 shape 为 [ d i n , d o u t ] [ d_{\mathrm{in}} , d_{\mathrm{out}}][d i n ​,d o u t ​]​,共包含了 d i n × d o u t d_{\mathrm{in}} \times d_{\mathrm{out}}d i n ​×d o u t ​​ 个网络参数;偏置向量 𝒃 𝒃b​ 的 shape 为 d o u t d_{\mathrm{out}}d o u t ​​ ,每个输出节点上均添加一个偏置值;@符号表示 矩阵相乘(Matrix Multiplication,matmul)。

考虑两个样本,输入特征长度 d i n = 3 d_\mathrm{in}=3 d i n ​=3​ ,输出特征长度 d o u t = 2 d_{\mathrm{out}}=2 d o u t ​=2​ 的模型,公式展开为
[ o 1 1 o 2 1 o 1 2 o 2 2 ] = [ x 1 1 x 2 1 x 3 1 x 1 2 x 2 2 x 3 2 ] [ w 11 w 12 w 21 w 22 w 31 w 32 ] + [ b 1 b 2 ] (3.4) \left[\begin{array}{ll}o_{1}^{1} & o_{2}^{1} \o_{1}^{2} & o_{2}^{2}\end{array}\right]=\left[\begin{array}{lll}x_{1}^{1} & x_{2}^{1} & x_{3}^{1} \x_{1}^{2} & x_{2}^{2} & x_{3}^{2}\end{array}\right]\left[\begin{array}{ll}w_{11} & w_{12} \w_{21} & w_{22} \w_{31} & w_{32}\end{array}\right]+\left[\begin{array}{l}b_{1} \b_{2}\end{array}\right]\tag{3.4}[o 1 1 ​o 1 2 ​​o 2 1 ​o 2 2 ​​]=[x 1 1 ​x 1 2 ​​x 2 1 ​x 2 2 ​​x 3 1 ​x 3 2 ​​]⎣⎡​w 1 1 ​w 2 1 ​w 3 1 ​​w 1 2 ​w 2 2 ​w 3 2 ​​⎦⎤​+b 1 ​b 2 ​​

其中 x 1 1 x_1^1 x 1 1 ​​ 等符号的上标表示样本索引号,下标表示样本向量的元素。对应模型结构图如图 3.4 所示:

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.4 3 输入 2 输出模型

可以看到,通过张量形式表达网络结构,更加简洁清晰,同时也可充分利用张量计算的并行加速能力。那么怎么将图片识别任务的输入和输出转变为满足格式要求的张量形式呢?

考虑输入格式,一张图片 𝒙 𝒙x 使用矩阵方式存储,shape 为:[ h , 𝑤 ] [ℎ, 𝑤][h ,w ] ,𝑏 𝑏b 张图片使用 shape 为 [ 𝑏 , h , 𝑤 ] [𝑏, ℎ, 𝑤][b ,h ,w ] 的张量 X X X 存储。而我们模型只能接受向量形式的输入特征向量,因此需要将 [ h , 𝑤 ] [ℎ, 𝑤][h ,w ] 的矩阵形式图片特征按顺序平铺成 [ h × 𝑤 ] [ℎ\times𝑤][h ×w ] 长度的向量 [ b , h × w ] [b, h\times w][b ,h ×w ],如图 3.5 所示,其中输入特征的长度 d i n = h × 𝑤 d_{\mathrm{in}} = ℎ \times 𝑤d i n ​=h ×w。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.5 打平

对于输出标签,前面我们已经介绍了数字编码,它可以用一个数字来表示便签信息。但是数字编码一个最大的问题是,数字之间存在天然的大小关系,比如 1 < 2 < 3 1 < 2 < 3 1 <2 <3,如果 1、2、3 分别对应的标签是猫、狗、鱼,他们之间并没有大小关系,所以采用数字编码的时候会迫使模型去学习到这种不必要的约束。

那么怎么解决这个问题呢?可以将输出设置为 d o u t d_{\mathrm{out}}d o u t ​​ 个输出节点的向量,d o u t d_{\mathrm{out}}d o u t ​​ 与类别数相同,让第 i ( i ∈ d o u t ) i\ (i\in d_{\mathrm{out}})i (i ∈d o u t ​)​个输出值表示当前样本属于类别 𝑖 𝑖i​ 的概率 𝑃 i 𝑃_i P i ​​。我们只考虑输入图片只输入一个类别的情况,此时输入图片的真实的标注已经明确:如果物体属于第 𝑖 𝑖i​ 类的话,那么索引为 𝑖 𝑖i​ 的位置上设置为 1 1 1​,其他位置设置为 0 0 0​,我们把这种编码方式叫做 one-hot 编码。

手写数字图片的总类别数有 10 种,即输出节点数d o u t = 10 d_{\mathrm{out}}=10 d o u t ​=1 0 ,那么对于某个样本,假设它属于类别 𝑖 𝑖i ,即图片的中数字为 𝑖 𝑖i ,只需要一个长度为 10 10 1 0 的向量 𝐲 𝐲y,向量 𝐲 𝐲y 的索引号为 𝑖 𝑖i 的元素设置为 1 1 1,其他位为 0 0 0。

比如图片 0 0 0​ 的 One-hot 编码为 [1,0,0,&#x2026; ,0],图片 2 2 2​ 的 Onehot 编码为 [0,0,1,&#x2026; ,0],图片 9 9 9​ 的One-hot 编码为 [0,0,0, &#x2026; ,1]。One-hot 编码是非常稀疏(Sparse)的,相对于数字编码来说,占用较多的存储空间,所以一般在存储时还是采用数字编码,在计算时,根据需要来把数字编码转换成 One-hot 编码,通过 tf.one_hot 即可实现。

In [ 2 ] : \text { In }[2]:In [2 ]:

y = tf.constant([0, 1, 2, 3, 9])
y = tf.one_hot(y, depth = 10)
y

Out [ 2 ] : \text { Out }[2]:Out [2 ]:

tf.Tensor(
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]], shape=(5, 10), dtype=float32)

PyTorch 中的 one_hot 实现如下:
In [ 3 ] : \text { In }[3]:In [3 ]:​

def one_hot(label, depth = 10):

    out = torch.zeros(label.size(0), depth)
    idx = torch.LongTensor(label).view(-1, 1)
    out.scatter_(dim = 1, index = idx, value = 1)
    return out

y = torch.tensor([0, 1, 2, 3])
y = one_hot(y, depth = 10)
print(y)

Out [ 3 ] : \text { Out }[3]:Out [3 ]:

tensor(
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]])

对于手写数字图片识别任务而言,输入是一张打平后的图片向量 𝒙 ∈ R 28 ∗ 28 𝒙 ∈ R ^{28∗28}x ∈R 2 8 ∗2 8 ,输出是一个长度为 10 10 1 0 的向量 R 10 R^{10}R 1 0 ,图片的真实标签 𝒚 𝒚y 经过 one-hot 编码后变成长度为 10 10 1 0​ 的稀疏向量。

预测模型采用多输入、多输出的线性模型 o = W T x + b o=\mathbf{W}^{\mathrm{T}} \mathbf x+b o =W T x +b,其中模型的输出记为输入的预测值 o o o ,我们希望 o o o 越接近真实标签 𝒚 𝒚y 越好。我们一般把输入经过一次 (线性) 变换叫做 一层网络

3.1.3 误差计算

对于分类问题来说,我们的目标是最大化某个性能指标,比如准确度 a c c = 分类正确的个数 测试集的总数 \mathrm{acc}=\dfrac{\text{分类正确的个数}}{\text{测试集的总数}}a c c =测试集的总数分类正确的个数​​,但是当我们把准确度当做损失函数去优化时,会发现 ∂ a c c ∂ θ \dfrac{\partial \mathrm{a c c}}{\partial \theta}∂θ∂a c c ​​ 是不可导的,因此无法利用梯度下降算法优化网络参数 θ \theta θ​。

一般的解决方法是:设立一个平滑可导的代理目标函数,比如优化模型的输出与 Onehot 编码后的真实标签 𝒚 𝒚y 之间的 距离(Distance),通过优化代理目标函数得到的模型,一般在测试性能上也能有良好的表现。

因此,相对回归问题而言,分类问题的优化目标函数和评价目标函数是不一致的。模型的训练目标是通过优化损失函数 L \mathcal L L 来找到最优数值解 :
W ∗ , b ∗ = argmin ⁡ W , b L ( o , y ) ⏟ (3.5) \mathrm{W}^{}, \boldsymbol{b}^{}=\underbrace{\underset{W, \boldsymbol{b}}{\operatorname{argmin}} \mathcal{L}(\boldsymbol{o}, \boldsymbol{y})}\tag{3.5}W ∗,b ∗=W ,b a r g m i n ​L (o ,y )​(3 .5 )

对于分类问题的误差计算来说,更常见的是采用 交叉熵(Cross entropy)损失函数,而不是采用回归问题中介绍的均方差损失函数。我们将在 3.3信息论基础 中介绍交叉熵损失函数,这里先继续采用 MSE 损失函数来求解手写数字识别问题。对于 𝑁 𝑁N​​ 个样本的均方差损失函数可以表达为:
L ( o , y ) = 1 N ∑ i = 1 N ∑ j = 1 10 ( o j i − y j i ) 2 (3.6) \mathcal{L}(\boldsymbol{o}, \boldsymbol{y})=\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{10}\left(o_{j}^{i}-y_{j}^{i}\right)^{2}\tag{3.6}L (o ,y )=N 1 ​i =1 ∑N ​j =1 ∑1 0 ​(o j i ​−y j i ​)2 (3 .6 )

现在我们只需要采用梯度下降算法来优化损失函数得到 W , 𝒃 \boldsymbol W,𝒃W ,b 的最优解,利用求得的模型去预测未知的手写数字图片 𝒙 ∈ 𝔻 t e s t 𝒙 ∈ 𝔻_\mathrm{test}x ∈D t e s t ​​​ ,即可得到预测的准确度。

3.1.4 非线性模型

我们目前为止使用的仍然是线性模型,显然是不能够胜任分类问题这一任务的。我们在 2.3.1 非线性模型 中讲解了线性模型转换为非线性模型进行拟合以及表达能力的相关概念,考虑转换为非线性模型并且增加模型的表达能力。

❑ ❑❑ 非线性模型

显然我们只需要使用激活函数即可转换为非线性模型。我们将在 第 6 章 神经网络与反向传播算法 6.4 激活函数 中对激活函数进行详细的讲解,这里选择 ReLU 函数作为激活函数,使得线性模型的输出 W x + b \boldsymbol{W} \boldsymbol{x}+\boldsymbol{b}W x +b 通过激活函数,即可得到非线性模型输出 o \boldsymbol o o :
o = ReLU ⁡ ( W x + b ) (3.7) \boldsymbol{o}=\operatorname{ReLU}(\boldsymbol{W} \boldsymbol{x}+\boldsymbol{b})\tag{3.7}o =R e L U (W x +b )(3 .7 )

❑ ❑❑ 表达能力

针对于模型的表达能力偏弱的问题,我们通过重复堆叠多次变换来增加其表达能力:

h 1 = ReLU ⁡ ( W 1 x + b 1 ) (3.8) \boldsymbol{h}{1}=\operatorname{ReLU}\left(\boldsymbol{W}{1} \boldsymbol{x}+\boldsymbol{b}_{1}\right)\tag{3.8}h 1 ​=R e L U (W 1 ​x +b 1 ​)(3 .8 )

h 2 = ReLU ⁡ ( W 2 h 1 + b 2 ) (3.9) \boldsymbol{h}{2}=\operatorname{ReLU}\left(\boldsymbol{W}{2} \boldsymbol{h}{1}+\boldsymbol{b}{2}\right)\tag{3.9}h 2 ​=R e L U (W 2 ​h 1 ​+b 2 ​)(3 .9 )

o = W 3 h 2 + b 3 (3.10) \boldsymbol{o}=\boldsymbol{W}{3} \boldsymbol{h}{2}+\boldsymbol{b}_{3}\tag{3.10}o =W 3 ​h 2 ​+b 3 ​(3 .1 0 )

把第一层神经元的输出值 𝒉 𝟏 𝒉_𝟏h 1 ​ 作为第二层神经元模型的输入,把第二层神经元的输出 𝒉 𝟐 𝒉_𝟐h 2 ​ 作为第三层神经元的输入,最后一层神经元的输出作为模型的输出即可。

从网络结构上看,如图 3.6 所示,函数的嵌套表现为网络层的前后相连,每堆叠一个(非)线性环节,网络层数增加一层。

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

图 3.6 神经网络

我们把数据节点所在的层叫做输入层,每一个非线性模块的输出 𝒉 𝒊 𝒉_𝒊h i ​​ 连同它的网络层参数 𝑾 𝒊 𝑾_𝒊W i ​​ 和 𝒃 𝒊 𝒃_𝒊b i ​​ 称为一层网络层,特别地,对于网络中间的层,叫做隐藏层,最后一层叫做输出层。这种由大量神经元模型连接形成的网络结构称为(前馈) 神经网络(Neural Network)。

现在我们的网络模型已经升级为为 3 3 3 层的神经网络,具有较好的非线性表达能力,接下来我们讨论怎么优化网络。

❑ ❑❑ 优化方法

对于仅一层的网络模型,如线性回归的模型,我们可以直接推导出 ∂ L ∂ w \dfrac{\partial L}{\partial w}∂w ∂L ​ 和 ∂ L ∂ b \dfrac{\partial L}{\partial b}∂b ∂L ​ 的表达式,然后直接计算每一步的梯度,根据梯度更新法则循环更新 𝑤 , 𝑏 𝑤, 𝑏w ,b 参数即可。

但是,当网络层数增加、数据特征长度增大、添加复杂的非线性函数之后,模型的表达式将变得非常复杂,很难手动推导出梯度的计算公式;而且一旦网络结构发生变动,网络的函数模型也随之发生改变,依赖人工去计算梯度的方式显然不可行。

这个时候就是深度学习框架发明的意义所在,所有深度学习框架都至少要支持一种技术,也就是 自动求导(Autograd)技术。借助于自动求导,深度学习框架在计算函数的损失函数的过程中,会记录模型的计算图模型,并自动完成任意参数 θ \theta θ​ 的偏导分 ∂ L ∂ θ \dfrac{\partial L}{\partial \theta}∂θ∂L ​​ 的计算,用户只需要搭建出网络结构,梯度将自动完成计算和更新,使用起来非常便捷高效。

; 3.1.4 手写数字图片识别实战

我们这里分别使用 TensorFlow2.0 与 PyTorch 对手写数字图片识别进行实战。

3.1.4.1 TensorFlow2.0 实现

❑ ❑\, \,❑​ Step 0 引入文件并设置图像参数

In [ 1 ] : \text { In }[1]:In [1 ]:

%matplotlib inline

import os
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras.datasets as datasets

plt.rcParams['font.size'] = 16
plt.rcParams['font.family'] = ['STKaiti']
plt.rcParams['axes.unicode_minus'] = False

❑ ❑\, \,❑ Step 1 导入数据并对数据进行处理

利用 TensorFlow 自动在线下载 MNIST 数据集,并转换为 Numpy 数组格式。

In [ 2 ] : \text { In }[2]:In [2 ]:

def load_data() :

    (x, y), (x_val, y_val) = datasets.mnist.load_data()

    x = tf.convert_to_tensor(x, dtype = tf.float32) / 255. - 1

    y = tf.convert_to_tensor(y, dtype = tf.int32)

    y = tf.one_hot(y, depth = 10)

    x = tf.reshape(x, (-1, 28 * 28))

    train_dataset = tf.data.Dataset.from_tensor_slices((x, y))

    train_dataset = train_dataset.batch(200)
    return train_dataset

TensorFlow 中的 load_data() 函数返回两个 元组 (tuple) 对象,第一个是训练集,第二个是测试集,每个 tuple 的第一个元素是多个训练图片数据 X X X ,第二个元素是训练图片对应的类别数字 Y Y Y。其中训练集 X X X 的大小为 ( 60000 , 28 , 28 ) (60000,28,28)(6 0 0 0 0 ,2 8 ,2 8 ) ,代表了 60000 60000 6 0 0 0 0 个样本,每个样本由 28 28 2 8 行、28 28 2 8 列构成,由于是灰度图片,故没有 RGB 通道;训练集 Y Y Y 的大小为 ( 60000 ) (60000)(6 0 0 0 0 ),代表了这 60000 60000 6 0 0 0 0 个样本的标签数字,每个样本标签用一个 0 ∼ 9 0\sim 9 0 ∼9 的数字表示,测试集同理。

从 TensorFlow 中加载的 MNIST 数据图片,数值的范围在 [ 0 , 255 ] [0,255][0 ,2 5 5 ] 之间。在机器学习中间,一般希望数据的范围在 0 0 0 周围小范围内分布。我们可以通过预处理步骤,我们把 [ 0 , 255 ] [0,255][0 ,2 5 5 ] 像素范围 归一化(Normalize)到 [ 0 , 1. ] [0,1.][0 ,1 .] 区间,再缩放到 [ − 1 , 1 ] [−1,1][−1 ,1 ] 区间,从而有利于模型的训练。

每一张图片的计算流程是通用的,我们在计算的过程中可以一次进行多张图片的计算,充分利用 CPU 或 GPU 的并行计算能力。一张图片我们用 shape 为 [ h , w ] [h, w][h ,w ] 的矩阵来表示,对于多张图片来说,我们在前面添加一个 数量维度 (Dimension),使用 shape 为 [ b , h , w ] [b, h, w][b ,h ,w ] 的张量来表示,其中的 b b b 代表了 batch size(批量。多张彩色图片可以使用 shape 为 [ b , h , w , c ] [b, h, w, c][b ,h ,w ,c ] 的张量来表示,其中的 c c c 表示通道数量(Channel),彩色图片c = 3 c = 3 c =3(R、G、B)。通过 TensorFlow 的Dataset 对象可以方便完成模型的批量训练,只需要调用 batch() 函数即可构建带 batch 功能的数据集对象。

❑ ❑\, \,❑ Step 2 网络搭建

对于第一层模型来说,他接受的输入 𝒙 ∈ R 784 𝒙 ∈ \mathbb R^{784}x ∈R 7 8 4 ,输出𝒉𝟏 ∈ R 256 \mathbb R^{256}R 2 5 6 设计为长度为 256 256 2 5 6 的向量,我们不需要显式地编写 h 1 = ReLU ⁡ ( W 1 x + b 1 ) \boldsymbol{h}{1}=\operatorname{ReLU}\left(\boldsymbol{W}{1} \boldsymbol{x}+\boldsymbol{b}_{1}\right)h 1 ​=R e L U (W 1 ​x +b 1 ​) 的计算逻辑,在 TensorFlow 中通过一行代码即可实现:

layers.Dense(256, activation = 'relu')

使用 TensorFlow 的 Sequential 容器可以非常方便地搭建多层的网络。对于 3 层网络,我们可以通过

keras.sequential([
    layers.Dense(256, activation = 'relu'),
    layers.Dense(128, activation = 'relu'),
    layers.Dense(10)])

快速完成 3 3 3 层网络的搭建,第 1 1 1 层的输出节点数设计为 256 256 2 5 6,第 2 2 2 层设计为 128 128 1 2 8,输出层节点数设计为 10 10 1 0。直接调用这个模型对象 model(x) 就可以返回模型最后一层的输出 。

为了能让大家理解更多的细节,我们这里不使用上面的框架,手动实现经过 3 层神经网络。

对神经网络参数初始化:

In [ 3 ] : \text { In }[3]:In [3 ]:

def init_paramaters() :

    w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev = 0.1))
    b1 = tf.Variable(tf.zeros([256]))

    w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev = 0.1))
    b2 = tf.Variable(tf.zeros([128]))

    w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev = 0.1))
    b3 = tf.Variable(tf.zeros([10]))
    return w1, b1, w2, b2, w3, b3

❑ ❑\, \,❑ Step 3 模型训练

得到模型输出 o \boldsymbol{o}o 后,通过 MSE 损失函数计算当前的误差 L \mathcal L L:

with tf.GradientTape() as tape:

    x=tf.reshape(x,(-1,28*28))

    out=model(x)

手动实现代码:

    with tf.GradientTape() as tape :

            h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))

            h1 = tf.nn.relu(h1)

            h2 = h1 @ w2 + b2
            h2 = tf.nn.relu(h2)

            out = h2 @ w3 + b3

❑ ❑\, \,❑ Step 4 梯度优化

利用 TensorFlow 提供的自动求导函数 tape.gradient(loss, model.trainable_variables) 求出模型中所有的梯度信息 ∂ L ∂ θ \dfrac{\partial L}{\partial \theta}∂θ∂L ​ , θ ∈ { W 1 , 𝒃 𝟏 , W 2 , 𝒃 𝟐 , W 3 , 𝒃 𝟑 } \theta ∈ {\boldsymbol W_1, 𝒃_𝟏,\boldsymbol W_2, 𝒃_𝟐,\boldsymbol W_3, 𝒃_𝟑}θ∈{W 1 ​,b 1 ​,W 2 ​,b 2 ​,W 3 ​,b 3 ​}:

 grads = tape.gradient(loss, model.trainable_variables)

计算获得的梯度结果使用 grads 变量保存。再使用 optimizers 对象自动按着梯度更新法则
θ ′ = θ − η × ∂ L ∂ θ (3.11) \theta^{\prime}=\theta-\eta \times \frac{\partial \mathcal{L}}{\partial \theta}\tag{3.11}θ′=θ−η×∂θ∂L ​(3 .1 1 )

去更新模型的参数 θ \theta θ。

grads = tape.gradient(loss, model.trainable_variables)

optimizer.apply_gradients(zip(grads, model.trainable_variables))

循环迭代多次后,就可以利用学好的模型 𝑓 θ 𝑓_{\theta}f θ​ 去预测未知的图片的类别概率分布。

手动实现梯度更新代码如下:

In [ 4 ] : \text { In }[4]:In [4 ]:​

def train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr = 0.001) :
    for step, (x, y) in enumerate(train_dataset) :
        with tf.GradientTape() as tape :

            h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))

            h1 = tf.nn.relu(h1)

            h2 = h1 @ w2 + b2
            h2 = tf.nn.relu(h2)

            out = h2 @ w3 + b3

            loss = tf.square(y - out)

            loss = tf.reduce_mean(loss)

            grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])

        w1.assign_sub(lr * grads[0])
        b1.assign_sub(lr * grads[1])
        w2.assign_sub(lr * grads[2])
        b2.assign_sub(lr * grads[3])
        w3.assign_sub(lr * grads[4])
        b3.assign_sub(lr * grads[5])

        if step % 100 == 0 :
            print(epoch, step, 'loss:', loss.numpy())

    return loss.numpy()

整体的代码思路如下:

我们首先创建每个非线性层的 𝑾 𝑾W 和 𝒃 𝒃b 张量参数,然后把 x \boldsymbol x x 的 shape=[b, 28, 28] 转成向量 [b, 784] ,然后计算三层神经网络,每层使用 ReLU 激活函数,然后与 one_hot 编码的 y \boldsymbol y y 一起计算均方差,利用 tape.gradient() 函数自动求梯度

θ ′ = θ − η ⋅ ∂ L ∂ θ (3.12) \theta^{\prime}=\theta-\eta \cdot \frac{\partial \mathcal{L}}{\partial \theta}\tag{3.12}θ′=θ−η⋅∂θ∂L ​(3 .1 2 )

使用 assign_sub() 函数按照上述梯度下降算法更新网络参数(assign_sub()将自身减去给定的参数值,实现参数的原地 (In-place) 更新操作),最后使用 matplotlib 绘制图像输出即可。

In [ 5 ] : \text { In }[5]:In [5 ]:

def train(epochs) :
    losses = []
    train_dataset = load_data()
    w1, b1, w2, b2, w3, b3 = init_paramaters()
    for epoch in range(epochs) :
        loss = train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr = 0.01)
        losses.append(loss)
    x = [i for i in range(0, epochs)]

    plt.plot(x, losses, color = 'cornflowerblue', marker = 's', label = '训练', markersize='3')
    plt.xlabel('Epoch')
    plt.ylabel('MSE')
    plt.legend()
    plt.savefig('MNIST数据集的前向传播训练误差曲线.png')
    plt.show()
    plt.close()

if __name__ == '__main__' :

    train(epochs = 50)

Out [ 5 ] : \text { Out }[5]:Out [5 ]:

epoch:0, iteration:0, loss:2.8238346576690674
epoch:0, iteration:100, loss:0.12282778322696686
epoch:0, iteration:200, loss:0.09311732649803162
epoch:1, iteration:0, loss:0.08501865714788437
epoch:1, iteration:100, loss:0.08542951196432114
epoch:1, iteration:200, loss:0.0726892277598381
epoch:2, iteration:0, loss:0.0702851265668869
epoch:2, iteration:100, loss:0.07322590798139572
epoch:2, iteration:200, loss:0.06422527879476547
epoch:3, iteration:0, loss:0.06337807327508926
epoch:3, iteration:100, loss:0.06658075749874115
epoch:3, iteration:200, loss:0.05931859463453293
......

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

MNIST数据集的前向传播训练误差曲线

手写数字图片 MNIST 数据集的训练误差曲线如上图所示,由于 3 3 3​ 层的神经网络表达能力较强,手写数字图片识别任务简单,误差值可以较快速、稳定地下降,其中对数据集的所有图片迭代一遍叫做一个 Epoch,我们可以在间隔数个 Epoch 后测试模型的准确率等指标,方便监控模型的训练效果。

3.1.4.2 PyTorch 实现

完整代码详见:Github( Python 文件及 Jupyter Notebook 文件)

In [ 1 ] : \text { In }[1]:In [1 ]:​

%matplotlib inline
import os
import sys
import time
import torch
import sklearn
import torchvision
import numpy as np
import pandas as pd
from torch import nn
from torch import optim
from torchsummary import summary
from torch.nn import functional as F
from matplotlib import pyplot as plt
from utils import one_hot, plot_curve, plot_image

print(sys.version_info)
for module in torch, torchvision, np, pd, sklearn:
    print(module.__name__, module.__version__)

Out [ 1 ] : \text { Out }[1]:Out [1 ]:​

sys.version_info(major=3, minor=8, micro=5, releaselevel='final', serial=0)
torch 1.10.2
torchvision 0.11.3
numpy 1.19.3
pandas 1.3.3
sklearn 0.22.2.post1

In [ 2 ] : \text { In }[2]:In [2 ]:​​


''''   超参数   '''
batch_size = 512
n_epochs = 3

learning_rate = 0.01

momentum = 0.9
'''  Hyperparameters   '''

In [ 3 ] : \text { In }[3]:In [3 ]:​​

'''  Step 1 下载训练集和测试集,对数据进行预处理  '''

train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('mnist_data', train = True, download = True,

                                     transform = torchvision.transforms.Compose([

                                         torchvision.transforms.ToTensor(),

                                         torchvision.transforms.Normalize(
                                         (0.1307,), (0.3081,))
                                     ])),
    batch_size = batch_size, shuffle = True)

test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('mnist_data/', train = False, download = True,
                                     transform = torchvision.transforms.Compose([
                                         torchvision.transforms.ToTensor(),
                                         torchvision.transforms.Normalize(
                                         (0.1307,), (0.3081,))
                                     ])),
    batch_size = batch_size, shuffle = False)

In [ 4 ] : \text { In }[4]:In [4 ]:

'''   Step 2。 展示样本数据   '''

def show_sample_image():

    x, y = next(iter(train_loader))

    print(x.shape, y.shape, x.min(), x.max())

    plot_image(x, y, 'image sample')

show_sample_image()

Out [ 4 ] : \text { Out }[4]:Out [4 ]:​

torch.Size([512, 1, 28, 28]) torch.Size([512]) tensor(-0.4242) tensor(2.8215)

一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

In [ 5 ] : \text { In }[5]:In [5 ]:

'''   Step 3。 搭建网络模型   '''
class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()

        self.fc1 = nn.Linear(28 * 28, 256)
        self.fc2 = nn.Linear(256, 64)
        self.fc3 = nn.Linear(64, 10)

    def forward(self, x):

        x = F.relu(self.fc1(x))

        x = F.relu(self.fc2(x))

        x = self.fc3(x)

        return x
        '''
        也可直接将向量经过 softmax 函数得到分类预测值
        return F.log_softmax(x, dim = 1)
        '''

device = torch.device("cuda" if torch.cuda.is_available() else "gpu")
print(torch.cuda.is_available())
net = Net().to(device)

print(net)
summary(net, (1, 28 * 28))

Out [ 5 ] : \text { Out }[5]:Out [5 ]:​

`python
True
Net(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(fc2): Linear(in_features=256, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=10, bias=True)
)
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.83
Estimated Total Size (MB): 0.84

Original: https://blog.csdn.net/weixin_45697774/article/details/120801390
Author: 繁凡さん
Title: 一文让你完全弄懂逻辑回归和分类问题实战《繁凡的深度学习笔记》第 3 章 分类问题与信息论基础(上)(DL笔记整理系列)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/663575/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球