(2023版)斯坦福CS231n学习笔记:DL与CV教程 (3) | 正则化与最优化

在这里插入图片描述

前言

  • 📚 笔记专栏:斯坦福CS231N:面向视觉识别的卷积神经网络(23)
  • 🔗 课程链接:https://www.bilibili.com/video/BV1xV411R7i5
  • 💻 CS231n: 深度学习计算机视觉(2017)中文笔记:https://zhuxiaoxia.blog.csdn.net/article/details/80155166
  • 🔥 2023最新课程PPT:https://download.csdn.net/download/Julialove102123/88734395

⚠️ 本节重点内容

  1. 正则化Regularization
  2. 最优化Optimization
  3. 梯度下降 Grendient descent
  4. 学习率Learning rate

1. 正则化(Regularization)

1.1 为什么引入正则化

上节讲到了如何选择最合适的超参数W,那有没有可能会出现多个这样的参数W1、W2…都能似的损失函数最小呢,答案是非常有可能!!!本节引入正则化就是确定怎么选最合适的W。在这里插入图片描述
在这里插入图片描述

1.2 正则化损失(regularization loss)

为什么要正则化?

  • 表达对权重的偏好
  • 使模型简单,以便它适用于测试数据
  • 通过添加曲率来改进优化
    在这里插入图片描述
    在这里插入图片描述

1.3 常见正则化损失

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

二、优化(Optimization)

🔥🔥🔥重要推荐:可视化工具 :http://vision.stanford.edu/teaching/cs231n-demos/linear-classify/

在这里插入图片描述
现在我们有了数据集、评分函数、损失函数,那我们怎么找到最好的超参数W呢?答案是优化!

2.1 优化策略(Optimization Strategy)

优化策略的目标是:找到能够最小化损失函数值的权重W。

1) 策略一:随机搜索(Random search)

随机尝试很多不同的权重,然后看其中哪个最好。这是一个差劲的初始方案。验证集上表现最好的权重W跑测试集的准确率是15.5%,而完全随机猜的准确率是10%,效果不好!

思路调整:新的策略是从随机权重W开始,然后迭代取优,每次都让它的损失值变得更小一点,从而获得更低的损失值。想象自己是一个蒙着眼睛的徒步者,正走在山地地形上,目标是要慢慢走到山底。在 CIFAR-10 的例子中,这山是30730维的(因为W是3073X10)。我们在山上踩的每一点都对应一个的损失值,该损失值可以看做该点的海拔高度。

2) 策略二:随机本地搜索

第一个策略可以看做是每走一步都尝试几个随机方向,如果是上山方向就停在原地,如果是下山方向,就向该方向走一步。这次我们从一个随机W开始,然后生成一个随机的扰动aW,只有当 W+aW 的损失值变低,我们才会更新。用上述方式迭代1000次,这个方法可以得到 公式 的分类准确率。

3) 策略三:跟随梯度

前两个策略关键点都是在权重空间中找到合适的方向,使得沿其调整能降低损失函数的损失值。其实不需要随机寻找方向,我们可以直接计算出最好的方向,这个方向就是损失函数的梯度(gradient)。这个方法就好比是感受我们脚下山体的倾斜程度,然后向着最陡峭的下降方向下山。

在一维函数中,斜率是函数在某一点的瞬时变化率。梯度是函数斜率的一般化表达,它是一个向量。

在输入空间中,梯度是各个维度的斜率组成的向量(或者称为导数 derivatives)。对一维函数的求导公式如下:在这里插入图片描述

三、梯度计算

计算梯度有两种方法

1.数值梯度法,缓慢的近似方法,实现相对简单。
2. 分析梯度法,计算迅速,结果精确,但是实现时容易出错,且需要使用微分。

3.1 数值梯度法

数值梯度法是借助于梯度的定义对其进行逼近计算。输入函数 f f f和矩阵 x x x,计算 f f f的梯度的通用函数,它返回函数 f f f在点 x x x处的梯度,利用公式
在这里插入图片描述
代码对 x x x矩阵所有元素进行迭代,在每个元素上产生一个很小的变化 h h h,通过观察函数值变化,计算函数在该元素上的偏导数。最后,所有的梯度存储在变量 grad 。实际中用中心差值公式(centered difference formula) [ f ( x + h ) − f ( x − h ) ] / 2 h [f(x+h)-f(x-h)]/2h [f(x+h)f(xh)]/2h 效果会更好。

① 在梯度负方向上更新

  • 在上面的代码中,为了计算 W_new,要注意我们是向着梯度df的负方向去更新,这是因为我们希望损失函数值是降低而不是升高。(偏导大于0,损失递增,W需要减小;偏导小于0,损失递减,W需要增大。)

② 步长的影响

  • 从某个具体的点W开始计算梯度,梯度指明了函数在哪个方向是变化率最大的,即损失函数下降最陡峭的方向,但是没有指明在这个方向上应该迈多大的步子。
  • 小步长下降稳定但进度慢,大步长进展快但是风险更大,可能导致错过最优点,让损失值上升。在某些点如果步长过大,反而可能越过最低点导致更高的损失值。选择步长(也叫作学习率)将会是神经网络训练中最重要(也是最麻烦)的超参数设定之一。

③ 效率问题

  • 计算数值梯度的复杂性和参数的量线性相关。在本例中有30730个参数,所以损失函数每走一步就需要计算30731次损失函数(计算梯度时计算30730次,最终计算一次更新后的。)
  • 现代神经网络很容易就有上千万的参数,因此这个问题只会越发严峻。显然这个策略不适合大规模数据。

3.2 解析梯度法

数值梯度的计算比较简单,但缺点在于只是近似不够精确,且耗费计算资源太多。

得益于牛顿-莱布尼茨的微积分,我们可以利用微分来分析,得到计算梯度的公式(不是近似),用公式计算梯度速度很快,但在实现的时候容易出错。

为了解决这个问题,在实际操作时常常将分析梯度法的结果和数值梯度法的结果作比较,以此来检查其实现的正确性,这个步骤叫做梯度检查
在这里插入图片描述

四、梯度下降(Gradient Descent)

现在可以利用微分公式计算损失函数梯度了,程序重复地计算梯度然后对参数进行更新,这一过程称为梯度下降。

4.1 Batch Gradient Descent (BGD)(Batch = 全量M)

在这里插入图片描述

Batch梯度下降法 (批梯度下降法) 是最常用的梯度下降形式,它是基于整个训练集的梯度下降算法,在更新参数时使用所有的样本来进行更新。

梯度更新规则:

BGD 采用整个训练集的数据来计算 cost function 对参数的梯度:

缺点:

由于这种方法是在一次更新中,就对整个数据集计算梯度,所以计算起来非常慢,遇到很大量的数据集也会非常棘手,而且不能投入新数据实时更新模型。

Batch gradient descent 对于凸函数可以收敛到全局极小值,对于非凸函数可以收敛到局部极小值。

4.2 Stochastic Gradient Descent (SGD)(Batch = 1 )

在这里插入图片描述

梯度更新规则:

和 BGD 的一次用所有数据计算梯度相比,SGD 每次更新时对每个样本进行梯度更新,对于很大的数据集来说,可能会有相似的样本,这样 BGD 在计算梯度时会出现冗余,而 SGD 一次只进行一次更新,就没有冗余,而且比较快,并且可以新增样本。

随机梯度下降是通过每个样本来迭代更新一次,如果样本量很大的情况,那么可能只用其中部分的样本,就已经将theta迭代到最优解了,对比上面的批量梯度下降,迭代一次需要用到十几万训练样本,一次迭代不可能最优,如果迭代10次的话就需要遍历训练样本10次。缺点是SGD的噪音较BGD要多,使得SGD并不是每次迭代都向着整体最优化方向。所以虽然训练速度快,但是准确度下降,并不是全局最优。虽然包含一定的随机性,但是从期望上来看,它是等于正确的导数的。

缺点:

  1. SGD 因为更新比较频繁,会造成 cost function 有严重的震荡。

  2. BGD 可以收敛到局部极小值,当然 SGD 的震荡可能会跳到更好的局部极小值处。

  3. 当我们稍微减小 learning rate,SGD 和 BGD 的收敛性是一样的。

4.2 Mini-batch gradient descent (MBGD)(Batch =n, 1<n<M)

在这里插入图片描述

梯度更新规则:

MBGD 每一次利用一小批样本,即 n 个样本进行计算,这样它可以降低参数更新时的方差,收敛更稳定,另一方面可以充分地利用深度学习库中高度优化的矩阵操作来进行更有效的梯度计算。
和 SGD 的区别是每一次循环不是作用于每个样本,而是具有 n 个样本的批次。

超参数设定值: n 一般取值在 50~256

缺点

🔥1. 挑战1:不能保证很好的收敛性,learning rate 如果选择的太小,收敛速度会很慢,如果太大,loss function 就会在极小值处不停地震荡甚至偏离。对于非凸函数,还要避免陷于局部极小值处,或者鞍点处,因为鞍点周围的error是一样的,所有维度的梯度都接近于0,SGD 很容易被困在这里。(会在鞍点或者局部最小点震荡跳动,因为在此点处,如果是训练集全集带入即BGD,则优化会停止不动,如果是mini-batch或者SGD,每次找到的梯度都是不同的,就会发生震荡,来回跳动。),解决方法mometum等

🔥2. 挑战2:SGD对所有参数更新时应用同样的 learning rate,如果我们的数据是稀疏的,我们更希望对出现频率低的特征进行大一点的更新。LR会随着更新的次数逐渐变小。解决方法Ada等

鞍点就是:一个光滑函数的鞍点邻域的曲线,曲面,或超曲面,都位于这点的切线的不同边。例如这个二维图形,像个马鞍:在x-轴方向往上曲,在y-轴方向往下曲,鞍点就是(0,0)。

五、梯度下降拓展

⚠️应对挑战1:如何选学习率LR?

5.1 Momentum

在这里插入图片描述
Momentum 通过加入 p v t pv_t pvt ,可以加速 SGD, 并且抑制震荡
当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。加入的这一项,可以使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,这样就可以加快收敛并减小震荡。
在这里插入图片描述
超参数设定值: 一般 p p p 取值 0.9 左右。

缺点:

这种情况相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,就知道需要减速了的话,适应性会更好。

5.2 Nesterov Momentum

在这里插入图片描述

梯度更新规则:

x t + p v t x_t+pv_t xt+pvt来近似当做参数下一步会变成的值,则在计算梯度时,不是在当前位置,而是未来的位置上,超参数设定值: 一般 p p p仍取值 0.9 左右。
在这里插入图片描述
效果比较:
在这里插入图片描述
蓝色是 Momentum 的过程,会先计算当前的梯度,然后在更新后的累积梯度后会有一个大的跳跃。而 NAG 会先在前一步的累积梯度上(brown vector)有一个大的跳跃,然后衡量一下梯度做一下修正(red vector),这种预期的更新可以避免我们走的太快。

NAG 可以使 RNN 在很多任务上有更好的表现。

目前为止,我们可以做到,在更新梯度时顺应 loss function 的梯度来调整速度,并且对 SGD 进行加速。

我们还希望可以根据参数的重要性而对不同的参数进行不同程度的更新。

⚠️应对挑战2:如何更新学习率LR?

5.4 Adagrad (Adaptive gradient algorithm)

Adagrad 可以对低频的参数做较大的更新,对高频的做较小的更新,也因此,对于稀疏的数据它的表现很好,很好地提高了SGD 的鲁棒性,例如识别 Youtube 视频里面的猫,训练 GloVe word embeddings,因为它们都是需要在低频的特征上有更大的更新。

梯度更新规则:
在这里插入图片描述
Adagrad 的优点是减少了学习率的手动调节,超参数设定值:一般η选取0.01

缺点:

它的缺点是分母会不断积累,这样学习率就会收缩并最终会变得非常小。

5.5 RMSProp: Leaky AdaGrad

RMSprop 是 Geoff Hinton 提出的一种自适应学习率方法。为了解决 Adagrad 学习率急剧下降问题的,
在这里插入图片描述

梯度更新规则:

RMSprop 使用的是指数加权平均,旨在消除梯度下降中的摆动,与Momentum的效果一样,某一维度的导数比较大,则指数加权平均就大,某一维度的导数比较小,则其指数加权平均就小,这样就保证了各维度导数都在一个量级,进而减少了摆动。允许使用一个更大的学习率η)
在这里插入图片描述

超参数设定值:Hinton 建议设定 γ 为 0.9, 学习率 η 为 0.001。

5.6 Adam:Adaptive Moment Estimation

这个算法是另一种计算每个参数的自适应学习率的方法。相当于 RMSprop + Momentum
在这里插入图片描述
除了像RMSprop 一样存储了过去梯度的平方 vt 的指数衰减平均值 ,也像 momentum 一样保持了过去梯度 mt 的指数衰减平均值
梯度更新规则:
在这里插入图片描述

超参数设定值:建议 β1 = 0.9,β2 = 0.999,ϵ = 10e−8

实践表明,Adam 比其他适应性学习方法效果要好。

效果和总结

在这里插入图片描述
在这里插入图片描述
上面两种情况都可以看出,Adagrad, Adadelta, RMSprop 几乎很快就找到了正确的方向并前进,收敛速度也相当快,而其它方法要么很慢,要么走了很多弯路才找到。

由图可知自适应学习率方法即 Adagrad, Adadelta, RMSprop, Adam 在这种情景下会更合适而且收敛性更好。

如果数据是稀疏的,就用自适用方法,即 Adagrad, Adadelta, RMSprop, Adam。

RMSprop, Adadelta, Adam 在很多情况下的效果是相似的。

Adam 就是在 RMSprop 的基础上加了 bias-correction 和 momentum,随着梯度变的稀疏,Adam 比 RMSprop 效果会好。

整体来讲,Adam 是最好的选择。

很多论文里都会用 SGD,没有 momentum 等。SGD 虽然能达到极小值,但是比其它算法用的时间长,而且可能会被困在鞍点。如果需要更快的收敛,或者是训练更深更复杂的神经网络,需要用一种自适应的算法。

六、学习率(learning rate)

SGD, SGD+Momentum, Adagrad, RMSProp, Adam 都有超参数学习率。学习率 (learning rate),控制 模型的 学习进度。

6.1 作用和影响

用于权重更新: a a a
在这里插入图片描述
学习率(learning rate,lr)是在神经网络的训练过程中一个很重要的超参数,对神经网络的训练效果与训练时间成本有很大影响

  1. 学习率对训练效果的影响
    (主要体现在对网络的有效容量/参数搜索空间的影响上):

    • 学习率过大:导致参数更新步幅过大,迈过了很多候选参数,有可能会越过最优值。因此,从这个意义上讲,过大的学习率会降低模型的有效容量,缩小了神经网络的参数搜索空间。

    • 学习率过小:因为神经网络的优化是一个非凸过程,损失函数曲线/超平面上存在许多局部极小值,鞍点,平滑点等。过小的学习率容易导致参数搜索过程中,使网络参数停留在一个很高的局部极小值上,不能继续搜索更好的、更偏向于全局的局部极小值。从这个意义上讲,学习率过小也会降低模型的有效容量。

  2. 学习率对训练时间成本的影响:

    • 学习率过大:会导致参数优化过程中损失函数值震荡(或,在最终的极优值两侧来回摆动),导致网络不能收敛。

    • 学习率过小:除了会导致训练速度慢以外,还容易导致模型停留在一个训练误差很高的局部极小值上,不利于寻找一个更低的(或更偏向于全局的)局部极小值。

在神经网络的训练过程中,常采用的一个策略就是使用学习率更新策略,使学习率随着模型训练的迭代次数逐渐衰减,这样既可以兼顾学习效率又能兼顾后期学习的稳定性:前期通过大学习率快速搜索,找到一个较好的(更倾向于全局最小的)局部区域,后期用较小的学习率在这个局部区域进行收敛。

6.2 衰减机制(更新策略)

在这里插入图片描述
不做细讲:https://zhuanlan.zhihu.com/p/525261152

七、资料

  1. 吴恩达老师的讲解,非常之详尽!!!https://www.showmeai.tech/article-detail/217
  2. 优化器的比较:https://link.jianshu.com/?t=https://arxiv.org/pdf/1609.04747.pdf
  3. 优化器算法详解:https://www.cnblogs.com/guoyaohua/p/8542554.html
  4. 学习率:https://zhuanlan.zhihu.com/p/525261152

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/326211.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【产品测试】Bug报告的四个元素,你知道吗?

前言 由于任何由人编写的程序都不可避免的存在着不符合测试需求的错误&#xff0c;也就是bug。因此需要一个方法来跟踪、分析和展示那些测试活动&#xff0c;避免偏离最小。这种方法称之为错误跟踪系统。它主要是有效的管理缺陷&#xff0c;实现以下作用&#xff1a; 1)减少由…

Java--业务场景:在Spring项目启动时加载Java枚举类到Redis中

文章目录 前言实现项目启动时加载枚举值到Redis1. 定义EnumInterface接口2. 创建EnumDTO3. 创建ClassUtils工具类4. 创建EnumService接口5. 创建EnumServiceImpl6. 修改枚举类7. 创建ApplicationInit 测试结果 前言 新的一年即将来到&#xff0c;回首2023年&#xff0c;也是学…

深入解析JavaScript中new Function语法

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 Function是JavaScript中非常重要的内置构造函数,可以用来动态创建函数…

Redis和MySQL如何保持数据一致性

前言 在高并发的场景下&#xff0c;大量的请求直接访问Mysql很容易造成性能问题。所以&#xff0c;我们都会用Redis来做数据的缓存&#xff0c;削减对数据库的请求。但是&#xff0c;Mysql和Redis是两种不同的数据库&#xff0c;如何保证不同数据库之间数据的一致性就非常关键…

Pyside6/PyQt6图标设置必备:窗口图标、软件图标、任务栏图标与系统托盘图标设置详解(含示例源码)

文章目录 📖 介绍 📖🏡 环境 🏡📒 图标设置 📒📝 设置窗口图标📝 设置软件/任务栏图标📝 设置系统托盘图标🎈 添加右键菜单/全局快捷键/隐藏任务栏图标⚓️ 相关链接 ⚓️📖 介绍 📖 在创建图形用户界面(GUI)应用程序时,一个吸引人的图标是必不可少…

day10

1.构造代码块和构造方法的区别 &#xff5b;代码块 &#xff5d; public 类名 () {} 都是实例化一个对象的时候执行的 只不过构造代码块先于构造方法执行的 2.局部变量和成员变量区别局部变量写在方法中&#xff0c;只能在方法体中使用&#xff0c;出了这个方法就不能再使用了成…

AI对决:ChatGPT与文心一言的深度比较

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 引言ChatGPT与文心一言的比较Chatgpt的看法文心一言的看法Copilot的观点chatgpt4.0的回答 模型的自我评价自我评价 ChatGPT的优势在这里插入图片描述 文…

【刷题】leetcode 1 . 两数之和

两数之和 两数之和1 思路一 &#xff08;简单突破&#xff09;2 思路二 &#xff08;进行优化&#xff09;3 思路三 &#xff08;哈希表 我还不会&#xff09; 谢谢阅读Thanks♪(&#xff65;ω&#xff65;)&#xff89;下一篇文章见&#xff01;&#xff01;&#xff01; 两数…

uniapp的nvue是什么

什么是nvue uni-app App 端内置了一个基于 weex 改进的原生渲染引擎&#xff0c;提供了原生渲染能力。 在 App 端&#xff0c;如果使用 vue 页面&#xff0c;则使用 webview 渲染&#xff1b;如果使用 nvue 页面(native vue 的缩写)&#xff0c;则使用原生渲染。一个 App 中可…

USB Redirector本地安装并结合内网穿透实现远程共享和访问USB设备

文章目录 前言1. 安装下载软件1.1 内网安装使用USB Redirector1.2 下载安装cpolar内网穿透 2. 完成USB Redirector服务端和客户端映射连接3. 设置固定的公网地址 前言 USB Redirector是一款方便易用的USB设备共享服务应用程序&#xff0c;它提供了共享和访问本地或互联网上的U…

十五.流程控制与游标

流程控制与游标 1.流程控制1.1分支结构之IF1.2分支结构值CASE1.3循环结构之LOOP1.4循环结构之WHILE1.5循环结构之REPEAT1.6跳转语句之LEAVE语句1.7跳转语句之ITERATE语句 2.游标2.1什么是游标2.2使用游标步骤4.3举例4.5小结 1.流程控制 解决复杂问题不可能通过一个 SQL 语句完…

深度学习基础知识整理

自动编码器 Auto-encoders是一种人工神经网络&#xff0c;用于学习未标记数据的有效编码。它由两个部分组成&#xff1a;编码器和解码器。编码器将输入数据转换为一种更紧凑的表示形式&#xff0c;而解码器则将该表示形式转换回原始数据。这种方法可以用于降维&#xff0c;去噪…

【图形学】直线光栅化算法(DDA算法和Bresenham算法)

在数学上,直线就是由无穷多个点组成的, 在计算机屏幕显示的话, 需要做一些处理,对于光栅显示器&#xff0c;就是用有限多个点去逼近直线, 我们需要知道每一个像素点的坐标(都是整数) 数学上直线的方程如下 y k x b ykxb ykxb&#xff0c;给定直线的起点坐标 P 0 ( x 0 , y…

开源 UI 组件库和开发工具库概览 | 开源专题 No.59

ant-design/ant-design Stars: 87.9k License: MIT Ant Design 是一个企业级 UI 设计语言和 React UI 库。 为 Web 应用程序设计的企业级 UI。提供一套高质量的开箱即用的 React 组件。使用可预测静态类型编写 TypeScript 代码。包含完整的设计资源和开发工具包。支持数十种语…

QT上位机开发(软件的发布和部署)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 我们在读书的时候&#xff0c;如果程序写好了&#xff0c;这个时候一般直接把exe拷贝给老师就可以了。这就是最原始的软件发布。但是&#xff0c;这…

“核弹级“攻击队视角下的监管痛点解决方案

痛点分析及解决方案 一、辖区企业资产分散且不透明 - 传统的监管体系中&#xff0c;政府监管单位往往面临着辖区企业资产分散且不透明的问题。 - 企业无法梳理自身资产&#xff0c;上报的资产台账无法涵盖全部自身资产 - 监管单位精力有限&#xff0c;无法保证辖区企业资产台账…

解决jmeter测试计划无法保存、另存为的问题

问题&#xff1a; 在保存测试计划时直接保存在C:\Windows\System32&#xff0c; 导致执行时报错Couldn’t save test plan to file&#xff1a;C:\Windows\System32 解决方案&#xff1a; 将路径改为 options--------Look And Feel-------windows

Express框架使用全流程

1.目的和使用场景 对于像我这样不常使用 Node.js 进行开发的人来说&#xff0c;每次开始一个新项目都意味着从头开始设置环境&#xff0c;这个过程相当繁琐。因此&#xff0c;我决定自己构建一个开箱即用的项目脚手架。我的目标是创建一个简单易用的基础框架&#xff0c;能让我…

NET Core发布 HTTP Error 500.31 - Failed to load ASP.NET Core runtime

记录一下踩过的坑&#xff1a; 首先&#xff0c;不论是500.31还是500.30 &#xff0c;首先确保安装了三个文件 1.NET Core RunTime 2.NET SDK 3.NET Hosting 其次&#xff0c;确保三个文件的版本一致&#xff0c;如下&#xff1a; 要装就统一装同一个大版本&#xff0c;不要东…

51单片机学习总结(自学)

1、模块化编程 c语言模块化编程实现思路设计代码 具体的程序实现代码如下所示 1&#xff1a;程序的头文件 2&#xff1a;程序的函数文件 3&#xff1a;程序的主文件控制函数的实现 持续更新中......