踏入深度学习的大门

2024-02-29 05:20
文章标签 学习 深度 大门 踏入

本文主要是介绍踏入深度学习的大门,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

人工智能已经发展了很多年了,一直比较火热,尤其是chatGPT等大模型的出现和应用,更是引爆了相关话题。现在好像不懂点相关知识都不够“时尚”了。

本文将介绍基本的深度学习概念和原理,由于主打的是一个浅显易懂,讲解过程中必然会丧失一定的严谨性,不严谨的地方将在文末补充。

本文将从使用深度学习模型识别手写数字这个例子展开,大部分内容参考自视频《什么是神经网络?》系列,因此本文本质是一篇笔记,但为了更容易理解,对一些内容进行了调整。同时,阅读此文需要具备一点基本的微积分和线性代数的知识。让我们开始吧~

问题引入

首先问大家一个问题:下图中的数字各是多少?

这个问题对于大家来说太简单了,6、7、8、9...但如果是机器来识别图中的数字呢?具体的,假设现在需要我们设计一款程序来识别图中的数字,我们该怎么来设计呢?

我们会发现这是不容易的,因为手写的数字不像印刷体,每个数字都规规矩矩的,一模一样的,只要对图片的尺寸进行裁剪后识别特定的像素就可以了。手写的数字在像素分布上简直是千奇百怪,比如下面图中的1、2、3各自的写法就不一样:

所以这个问题对于一般的解决方案来说并不简单。但是对于深度学习算法来说刚刚好,实际上,这就是深度学习领域的一个“Hello World”问题。

一、模型的结构

1. 总体结构

我们希望有这么一个神经网络模型,能够准确识别图片中的手写数字,当我们输入手写数字7的图片时,模型能输出数字7,输入手写数字1的图片时,输出数字1。

图中就是神经网络模型的样子,由一排排神经元组成,每个神经元有多个输入,一个输出,神经元的输出称为激活值。一般来说,使用第一排神经元接收输入数据,因此叫做输入层,最后一排神经元负责输出最终结果,因此叫做输出层,我们要识别的数据是0~9,所以输出层一共10个神经元,分别对应每个数字的输出。还有两排中间的神经元我们称为隐藏层,负责拟合各自中间特征,具体内容接下来说。隐藏层每层16个神经元,设置16个的原因完全是为了好说明,实际上取17个、18个都可以,甚至为了提升模型的预测精度,笔者在实验时设置了100个。一般来说,我们将前一层所有神经元的输出作为下一层每个神经元的输入,所以这种模型也称为全连接神经网络

一下子介绍了好多概念,但这就是模型全部的大致结构了,让我们从输入开始,一点点去了解其中的原理。首先,把什么作为模型的输入?输入层应当多少个神经元呢? 

 2. 输入数据

容易知道,这个模型需要的输入数据是一张张手写数字的图片,我们可以找许多人来每个人写一遍数字1~9,然后把这些图片进行处理后(例如尺寸裁切等)汇总起来,于是就形成了数据集。如果自己弄数据集费时费力,也可以去网上寻找公开数据集。这里使用一个叫MNIST的手写数字数据集,它包含了 70,000 张由0~9的手写数字的灰度图像,每张图像的大小为 28x28 像素,部分图片的展示如下:

现在数据集有了,我们面临的任务是将每张图片输入到模型中。在MNIST数据集中,容易算出每张图片一个784个像素,每个像素代表一个灰度。例如,在这张“3”的图片中,处在笔画范围内的像素灰度值就很高,处在笔画边缘的像素灰度值不为0但也不是特别高,其余地方就为0。我们可以将每个像素的灰度值作为模型的输入数据。

现在确定输入为手写数字图片的像素灰度值,共有784个值,所以输入层一共是784个神经元。由于像素灰度值的取值范围是0~255,输入层神经元的输出值理应也是0~255。但是我们面对的是一个数字识别的问题,本质上说是数字的分类,因此将概率设在0~1之间,1代表极致的肯定,0代表极致的否定。像下图中,输出层对应数字9的神经元激活值为0.97,说明有97%的把握肯定输入图片上的数字为9。因此,为了确保模型能更好地学习,把像素灰度值0~255的范围也映射到0~1之间,消除不同特征之间的量纲差异,这步数据处理的方式就叫数据归一化

现在确定了模型的输入输出,那么隐藏层做了什么呢? 

3. 特征拟合
(1)直观部分

现在我们输入数字“9”的图片,最终模型的输出层对应数字9的9号神经元被激活,那为什么9号神经元被激活了,而其它数字的神经元没被激活呢?

实际上可以假设在前一层中(第二个隐藏层),有两个神经元分别负责识别圆圈和竖线,而正好它们被激活了,识别其它笔画的神经元没被激活,于是将激活结果传输到输出层时,这两个笔画组合起来不能组成除了“9”之外的其它数字,因此只有9号神经元被激活了。

 但似乎识别一个圈与竖线也不是特别容易。所以我们对笔画进一步分解,最后得到一组小短边:

于是现在我们似乎得到了这个神经网络模型的识别过程:首先在输入层输入图片的像素灰度归一化后的值;然后第一个隐藏层识别出小短边,有若干神经元被激活;将第一个隐藏层的神经元激活结果输入到第二个隐藏层,识别出笔画;最后将识别的笔画(第二个隐藏层的神经元激活结果)输入到输出层,输出层对应某个数字的神经元被激活,得到最终识别结果。

(2)细节部分

现在就来到问题的关键点了:第一个隐藏层如何识别小短边呢?

答案是加权求和

设输入的像素灰度值为a,第一个隐藏层的每个神经元接收784个像素灰度值,则对应a_{1}~a_{784},再让每个像素灰度值a对应一个权重\omega,让所有的像素灰度值加权求和,得到最终激活值,得到初步的激活函数y=\omega_{1} a_{1}+\omega_{2} a_{2}+\omega_{3} a_{3}+\omega_{4} a_{4}+\cdot \cdot \cdot +\omega_{n} a_{n},如下图所示。

以手写数字“7”为例,无论7怎么写,总会有一条短横,假设图中的神经元是识别短横的神经元,那么将短横笔画范围内的权重设置得很大,其它地方设置得小,如果输入的像素灰度值可以组成这个短横的特征,则对应神经元输出的激活值就会高,反之就会很低。 

但是该激活函数的值域在(-\infty,\infty )之间,而我们希望激活值在0~1之间,因此使用下图的函数做一个包装,图中名为Sigmoid的函数值域恰好就在0~1之间:

同时,为了不让加权和总是过大使神经元太容易被激活,导致识别准确率降低,我们减去一个常数,于是激活函数改为y=\sigma (\omega_{1} a_{1}+\omega_{2} a_{2}+\omega_{3} a_{3}+\omega_{4} a_{4}+\cdot \cdot \cdot +\omega_{n} a_{n}+b),b代表的常数称为偏置。例如下图中,我们将偏置设为-10,代表当加权和大于10时激发才有意义:

当然了,模型并不是一开始就有我们需要的权重分布,我们需要找到正确的权重与偏置,但是就我们这个小小的模型,权重和偏置就有13002个了,根本不可能人为去调。所以需要模型自己去“学习”,而所谓“学习”,就是让模型自己找到正确的权重与偏置,识别出相应特征。

由于神经元很多,每个神经元的参数也很多,在实际计算是使用循环迭代的方式会很吃算力,所以对参数进行向量化计算:

我们可以看到每一层的神经元的激活函数是一样的,接收的输入数据也是一样的,只是每个神经元内部的权重和偏置不一样而已,因此完全可以使用矩阵来运算,这是一个基础的线代思路,大家应该很熟悉了。

最后确定激活函数如下,此时公式里的变量均为向量。:

a^{n+1}=\sigma(wa^{n}+b)

现在我们把a看作每一层神经元的输出,是一个n维向量,n为这一层神经元的个数;w是一个m\times n权重矩阵,m为前一层神经元个数,n为本层神经元个数,w代表本层每个神经元与前一层所有神经元的连接强弱,权重越大则连接越强。 最后模型的结构如下所示:

二、模型的训练 

现在我们知道了模型的结构,接下来就可以训练模型了,但在训练之前,我们得找到一个评判模型好坏得标准。

1. 代价函数

假设现在是一个未训练过得模型,每个神经元的权重和偏置都是随机生成的,此时我们输入手写数字“3”的图片,可以预见模型并不能准确识别,而是输出一个乱七八糟的结果。

我们将这个乱七八糟的输出结果作为输出值,理想中的结果作为预期值,使用平方差作为输出值和预期值差异程度,可以看见,当输出值和预期值差异很大时,代表模型识别能力差:

 反之,当输出值十分接近预期值时,输出值和预期值差异很小,代表模型识别能力好:

 于是我们得到评判模型好坏的公式如下:

C(\omega ,b)=\frac{1}{2m}\sum_{i=1}^{m}(a^{(3)}_{i}-y_{i})^{2}

y_{i}为预期值,或者说标签,m为总的样本数量这里的系数1/2m是为了后面计算导数方便,目前并无特殊意义。 这个评价模型好坏的公式叫做代价函数,很多论文里也叫损失函数,很多时候二者意思通用。

2. 梯度下降

代价函数的值越低,代表模型越好,所以训练模型的过程就是降低代价函数的过程。那么怎么降低代价函数呢?答案是梯度下降

梯度下降的本质是求代价函数处于极小值时,权重\omega和偏置b的值。梯度下降公式的本质是让变量慢慢移动,移动到代价函数处于极小值。一般来说我们只需要改变权重\omega,偏置b不用改变,因为大量实验的结果表明b取多少其实影响并不大。

我们将模型简化来理解这一过程,这是只有一个权重的情况:

结合上图,梯度下降的具体步骤如下:

  1. 初始化权重\omega的值为一个随机的初始值。

  2. 计算代价函数对权重\omega的梯度(或偏导数)。可表示为\frac{\partial C}{\partial \omega },也可表示为∇C,其中∇是梯度运算符。

  3. 更新\omega的值。通过从当前值中减去一个学习率\alpha(一个非负小系数)乘以梯度的值,学习率是一个超参数,它控制每次更新的步长。

  4. 重复步骤 2 和 3,直到代价函数到达极小值或极小值附近。

学习率的选择很重要,如果学习率过大,可能会导致更新过大而错过极小值;如果学习率过小,可能会导致收敛速度很慢。同时,梯度下降算法只能保证找到局部最小值,而不能保证全局最小值。若初始化时\omega的值不同,完全有可能找到不同的极小值。

这是有两个权重的情况,同样是寻找代价函数的极小值:

 这是扩展到n个权重的情况,虽然画不出形象的图了,但原理是一样的:

现在只剩最后一个问题了:怎么让模型自己调整w来达到降低代价函数的目的呢? 

3. 反向传播

我们将手写数字的图片输入模型得到结果的过程称为前向传播,前向传播完成后,输入、中间、输出向量均已确定,然后代价函数的值计算也可以计算确定,此时就可以使用链式求导法则推出代价函数对各权重\omega的偏导(梯度),最后使用梯度下降算法即可调整\omega的值,代价函数得以降低,模型性能得到提升,完美闭环!

所以,反向传播的根本目的在于计算代价函数对每个神经元内权重的偏导。那这个偏导怎么求呢?

同样,先简化模型,考虑只有一层神经元的情况:

千万不要被上面一连串公式给吓到了,第一行就是神经元的激活函数和代价函数,第二行右边是代价函数对激活函数的求导,通过链式求导法则,就能得到左边的代价函数对权重的偏导,有了这个偏导就可以用梯度下降公式去调整权重了。

那为什么不完成前向传播就不能反向传播呢?因为在完成前向传播之前模型的输出结果a^{(1)}是不知道的,所以代价函数对激活函数的求导的值是算不出来的,同时,如果没有数据输入,a^{(0)}也是不知道的,那激活函数对权重的导数的值也无法求,也就无法进行反向传播了。

下面是有两层神经元的情况,原理是一样的:

方向传播的过程直观理解起来就是我们保持a不变,去调整权重\omega,使对应的神经元激活值提升,不对应的神经元激活值下降,不同数字的图片对权重\omega的调整方向不同,使模型能够识别不同数字图片。

 经过多轮的调整后,一个训练好的神经网络就新鲜出炉了!

补充

1. 随机梯度下降 

还记得代价函数的m代表什么吗?没错,代表总的样本数。观察这个公式我们会发现我们训练时要过一遍所有的样本才能进行反向传播,通常一个数据集有成千上万个样本,这样弄会不会太慢了?同时这计算量也太大了... 

C(\omega ,b)=\frac{1}{2m}\sum_{i=1}^{m}(a^{(3)}_{i}-y_{i})^{2}

所以我们每次只选择一部分样本进行计算,分批次训练: 

这样做确实计算量会小很多,但会不会由于样本不全,导致梯度下降的方向不对?这个确实是会的,但是我们也发现,最后代价函数函数还是能找到极小值的。就像下图所示,使用全部样本计算每次梯度下降的方向都正确但却无比的慢,而使用部分样本虽然每次梯度下降的方向不一定正确,但训练过程却变快了。

 2. 模型的不可解释性

在文章的前面我们用数字“9”举例,假设了模型先是识别了“9”的小短边,然后汇成两个笔画,最后通过笔画组合得知是“9”。然而事实真是这样吗?

我们试着把第一个隐藏层的权重图像化显示,蓝色代表正,红色代表负,并且绝对值越高颜色越鲜艳。最后显示出来的图像好像并没有什么小短边,而是显得“杂乱无章”。

事实上,回顾模型的训练过程,我们并没有让模型按照我们设想的思路去认识数字,我们只是在模型每次输出时告诉它这个输出是否好(代价函数判断),然后任由模型自己去调整(反向传播和梯度下降)。所以,模型最后为什么能识别数字,每个神经元、每个神经元内的权重负责去识别什么特征,我们是不知道的。这也对应了模型的不可解释性。 

3. 代价函数更正 

为了方便说明,我们使用了平方差公式作为代价函数,其实这个公式多用于回归问题。在分类问题中,代价函数一般使用交叉熵损失函数,可以在训练时取得更好的性能:

L(y, \hat{y}) = - (y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})) 

如果大家有兴趣,可以自行去了解公式的含义。 

总结

神经网络和深度学习的相关概念:

  1. 神经网络模型有输入层隐藏层输出层
  2. 每层有若干个神经元,每个神经元有多个输入、一个输出
  3. 每个神经元里有一个激活函数激活函数里有n个权重与一个偏置
  4. 模型训练时,使用代价函数评估模型的准确性,具体为输出结果与预期结果(标签)的差异;
  5. 使用梯度下降来调整权重,调整步长为学习率
  6. 使用反向传播使模型自己计算代价函数对每个权重的偏导,从而使用梯度下降来更新权重值,使代价函数的值减小;
  7. 在训练过程中,代价函数的值会一直减小,最终停留在一个范围内,这时我们说模型收敛了,本质是代价函数的值收敛了。

模型的具体训练过程总结:

  1. 前向传播:
    首先进行前向传播计算。从输入层开始,将输入数据通过网络的每一层进行计算,直到得到网络的输出。在每个神经元中,根据权重和激活函数的定义,计算该神经元的加权输入和激活输出。将前一层的激活输出作为下一层的输入,并一直传播到输出层,得到最终的输出结果。

  2. 计算代价函数:
    在前向传播完成后,将输出结果与真实标签进行比较,计算代价函数的值。

  3. 反向传播梯度计算:
    反向传播的核心是计算代价函数对权重的偏导。从输出层开始,根据代价函数对输出层的输出进行偏导数计算,然后,从输出层向输入层逐层传播。在每层中,根据链式法则,将上一层的梯度与当前层的加权输入进行运算,得到对当前层权重的梯度。

  4. 参数更新:
    在计算完所有权重的梯度后,根据梯度下降算法或其他优化算法,更新模型中的权重。通过将权重的当前值减去学习率乘以对应权重的梯度来更新权重。

  5. 重复训练:
    反向传播的上述步骤迭代进行,直到达到预设的停止条件(如代价函数收敛)为止。通过多次迭代的训练,模型参数逐渐调整,使得模型的输出逼近目标值,从而提高模型的性能。


原视频地址:https://www.youtube.com/watch?v=aircAruvnKk

这篇关于踏入深度学习的大门的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/757692

相关文章

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

Maven中生命周期深度解析与实战指南

《Maven中生命周期深度解析与实战指南》这篇文章主要为大家详细介绍了Maven生命周期实战指南,包含核心概念、阶段详解、SpringBoot特化场景及企业级实践建议,希望对大家有一定的帮助... 目录一、Maven 生命周期哲学二、default生命周期核心阶段详解(高频使用)三、clean生命周期核心阶

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

深度解析Python yfinance的核心功能和高级用法

《深度解析Pythonyfinance的核心功能和高级用法》yfinance是一个功能强大且易于使用的Python库,用于从YahooFinance获取金融数据,本教程将深入探讨yfinance的核... 目录yfinance 深度解析教程 (python)1. 简介与安装1.1 什么是 yfinance?

Python学习笔记之getattr和hasattr用法示例详解

《Python学习笔记之getattr和hasattr用法示例详解》在Python中,hasattr()、getattr()和setattr()是一组内置函数,用于对对象的属性进行操作和查询,这篇文章... 目录1.getattr用法详解1.1 基本作用1.2 示例1.3 原理2.hasattr用法详解2.

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499