001__神经网络基础

一、神经网络

神经网络(Neural networks)是一种基于生物神经系统的计算模型,用于模拟人脑的工作机制。它由大量的人工神经元(神经网络单元)和它们之间的连接组成。每个神经元都接收来自其他神经元的输入,并通过一个激活函数来产生输出。这些神经元之间的连接具有不同的权重,这些权重可以调整以改变网络的行为。

神经网络通过训练来学习数据的特征和模式,从而能够进行各种任务,例如图像分类、语音识别、自然语言处理等。在训练过程中,网络通过反向传播算法来调整权重,以最小化预测输出与真实输出之间的差距。这种学习方式使得神经网络可以逐渐优化自己的性能。

现代神经网络通常由多个层级组成,称为深度神经网络(Deep Neural Networks,DNNs)。深度神经网络的隐藏层可以提取更高级别的特征表示,因此具备更强大的表示能力。这使得深度学习成为目前许多领域中最先进的技术之一。

神经网络的训练需要大量的数据和计算资源,并且对于设计合适的网络结构和超参数调整也需要一定的经验和技巧。但是,当正确应用时,神经网络可以在许多复杂任务中取得出色的性能,并且具有广泛的应用前景。

神经元
一个神经元通常具有多个** 树突** ,主要用来接受传入信息;而** 轴突** 只有一条,轴突尾端有许多轴突末梢可以给其他多个神经元传递信息。轴突末梢跟其他神经元的树突产生连接,从而传递信号。这个连接的位置在生物学上叫做“** 突触** ”。

人脑中的神经元形状可以用下图做简单的说明:

神经元模型
从生物书上,我们可以概括出生物神经网络的假定特点:

1. 每个神经元都是一个 多输入 单输出的信息处理单元;
2. 神经元输入分兴奋性输入抑制性输入两种类型;
3. 神经元具有 空间整合特性阈值特性
4. 神经元输入与输出间有固定的 时滞,主要取决于突触延搁

下面是一个人工神经网络的构造图。每一个圆代表着一个神经元,他们连接起来构成了一个网络。

人类大脑神经元细胞的树突用于接收来自外部的多个强度不同的刺激,并在神经元细胞体内进行处理,然后将其转化为一个输出结果。在这里,神经元是神经网络的基本单元,我们学过生物的同学都知道,神经元有两种状态:兴奋和抑制。一般情况下,大多数的神经元是处于抑制状态,但是一旦某个神经元收到刺激,导致它的电位超过一个阈值,那么这个神经元就会被激活,处于“兴奋”状态,进而向其他的神经元传播化学物质(其实就是信息)

人工神经元也有相似的工作原理。如下图所示。

上面的x是神经元的输入,相当于树突接收的多个外部刺激。w是每个输入对应的权重,代表了每个特征的重要程度,它对应于每个输入特征,影响着每个输入x的刺激强度。假设只有3个特征,那么x就可以用(x1,x2,x3)。b表示阈值[yù zhí],用来影响预测结果。z就是预测结果。

所以有:z = (x1 * w1 + x2 * w2 + x3 * w3) - b

上面这个式子在业内称之为逻辑回归。

下面对神经元模型的图进行一些扩展。首先将sum函数与sgn函数合并到一个圆圈里,代表神经元的内部计算。其次,把输入a与输出z写到连接线的左上方,便于后面画复杂的网络。

最后说明,一个神经元可以引出多个代表输出的有向箭头,但****值都是一样的

神经元可以看作一个计算与存储单元。计算是神经元对其的输入进行计算功能。存储是神经元会暂存计算结果,并传递到下一层。

问题分类
分类问题和回归问题是机器学习和统计分析中两种不同类型的问题,它们的主要区别在于其目标和处理方式:

问题类型:

  • 分类问题(Classification):在分类问题中,目标是将数据分为不同的类别或标签。这意味着模型的输出是一个离散值,通常代表预定义的类别。例如,垃圾邮件检测是一个分类问题,它需要将电子邮件分为两个类别:垃圾邮件和非垃圾邮件。
  • 回归问题(Regression):在回归问题中,目标是预测连续的数值结果。回归模型的输出是一个连续的数字,通常用于估计或预测数值。例如,房价预测是一个回归问题,它需要根据房屋的特征来估计价格,价格可以是任何实数值。

输出类型:

  • 分类问题的输出是有限的离散类别,通常表示为标签或类别。模型的目标是将输入数据映射到这些离散类别之一。
  • 回归问题的输出是连续的实数值。模型的目标是根据输入数据来预测一个连续的数值结果。

评估指标:

  • 分类问题的评估通常使用准确性、精确度、召回率、F1分数等指标来衡量模型的性能。
  • 回归问题的评估通常使用均方误差(Mean Squared Error,MSE)、均方根误差(Root Mean Squared Error,RMSE)、平均绝对误差(Mean Absolute Error,MAE)等指标来衡量模型的性能。

示例:

  • 分类问题的示例包括图像分类、垃圾邮件检测、疾病诊断等。
  • 回归问题的示例包括房价预测、股票价格预测、气温预测等。

总的来说,分类问题关注于将数据分为不同的类别,而回归问题关注于预测连续数值。选择合适的问题类型取决于您的数据和目标。

激活函数
根据输出的计算可以发现,其实隐层的每个神经元是由输入特征x的线性组合构成。然而如果仅仅是线性组合,那么** 不管这个神经网络有多少层,结果都将与特征线性相关**。于是我们在每个神经元结果z之后,添加一个激活函数(Activation Function),改变线性规则,比如使用Sigmoid函数。

激活函数非常非常重要,如果没有它,那么神经网络的智商永远高不起来。为什么要使用激活函数:大家可以想像火车站排队的栏杆,激活函数就是这样不断的约束引导输入走向我们想要的目标,这才是它的底层含义,而它的用途就是把线性问题变成非线性,以及把输出映射到概率分布,方便我们进行分类。

从上图M-P神经元模型可以看出,神经元的输出

其中θ为我们之前提到的神经元的**激活阈值**,函数f(⋅)也被称为是激活函数。激活函数不止一种,如上图所示,函数f(⋅)可以用一个阶跃方程表示,大于阈值激活;否则则抑制。

sigmoid函数
但是这样有点太粗暴,因为阶跃函数不光滑,不连续,不可导,因此我们更常用的方法是用 sigmoid函数来表示函数函数f(⋅),它的公式和图像如下。

为什么要关注导数呢?

用户的问题是关于为什么在激活函数中要关注导数。我得先理解激活函数在神经网络中的作用。激活函数是用来引入非线性,让模型能够处理更复杂的数据对吧。那导数的作用是什么呢?可能是为了计算梯度,用于优化权重。

哦,对了,反向传播算法里用到了链式法则,所以导数在这里很重要。那导数具体怎么影响的呢?比如,如果导数太小,梯度下降会变慢,可能导致训练时间增加。这可能就是所谓的“梯度消失”问题,尤其是在深层网络中。

比如sigmoid函数在输入绝对值大的时候导数会很小,这样梯度信号会被削弱,训练深层网络时效果不好。那ReLU呢,它的导数在正区间是1,这样梯度衰减的问题就小很多,训练更快。所以导数影响了学习速度和网络的训练效果。

还有,如果导数太大,可能会导致梯度爆炸,这样训练也不稳定。所以选择合适的导数,能够帮助网络更好地收敛。另外,像LSTM中的激活函数要考虑导数的性质,确保长期依赖的信息不会被丢失。

所以,总结一下,关注导数因为它关系到梯度的传播,影响训练速度、网络性能和稳定性,选择合适的导数形状可以帮助避免梯度消失或爆炸,提高模型效果。

在神经网络中,激活函数的导数非常重要,因为它们直接影响网络的训练过程和性能。以下是关注导数的一些主要原因:

  1. 梯度计算:在反向传播算法中,导数用于计算梯度,这是优化权重和调整模型参数的基础。通过链式法则,导数帮助传播误差信号,更新网络中的权重。
  2. 梯度消失与爆炸:如果激活函数的导数值过小,可能导致梯度消失,特别是在深层网络中,这会减缓训练速度,甚至使模型无法有效学习。反之,如果导数值过大,可能导致梯度爆炸,使训练不稳定。
  3. 训练速度和性能:合适的导数形状,如ReLU的非零导数,可以加快训练过程并提高模型性能。导数为零的区域会导致神经元无法更新,影响网络表现。
  4. 模型稳定性:导数有助于保持训练过程的稳定性,防止权重更新过大或过小,从而确保模型在训练过程中不会发散。
    综上所述,关注激活函数的导数是确保神经网络有效训练和良好性能的关键因素。

我们在这里先只介绍它的一个用途——把z映射到[0,1]之间。上图中的横坐标是z,纵坐标我们用y’来表示,y’就代表了我们最终的预测结果。从图像可以看出,z越大那么y’就越靠近1,z越小那么y’就越靠近0。那为什么要把预测结果映射到[0,1]之间呢?因为这样不仅便于神经网络进行计算,也便于我们人类进行理解。例如在预测是否有猫的例子中,如果y’是0.8,就说明有80%的概率是有猫的。

sigmoid函数的导数图像如下所示。当输入值为0时,sigmoid函数的导数达到最大值0.25;而输入在任一方向上越远离0点时,导数越接近0。

什么情况下适合使用sigmoid?

  1. sigmoid函数的输出范围是0到1。由于输出值在0和1之间,它相当于将每个神经元的输出归一化。
  2. 特别适合用于需要将预测概率作为输出的模型。因为任何概率值的范围是[0,1],而且我们往往希望概率值尽量确定(即概率值远离0.5),所以s型曲线是最理想的选择。
  3. 平滑梯度。显然,sigmoid函数在定义域上处处可导。
  4. sigmoid函数是可微的,这意味着我们可以找到任意两点之间的斜率。
  5. 明确的预测值。也就是说倾向于接近0或1。

sigmoid有哪些缺点?

  1. 倾向于梯度消失。当输入值z zz的绝对值过大时,导数很小(远小于1),这将导致梯度消失以及深度神经网络学习能力变差。
  2. 函数输出不以0为中心,这会降低权值更新的效率。
  3. sigmoid需要指数运算,计算机运算较慢。

补充:可导

设函数y=f(x)在 的邻域U( )内有定义,当自变量x在点取得增量

,且 时,相应的函数增量

,若

存在,则称函数y=f(x)在 处可导,并称这个极限值为函数y=f(x)在点 处的导数,记做

注:(1)若上述极限不存在,则称函数y=f(x)在点处不可导。如果不可导的原因是由于

为了方便起见,也说函数f(x)在点 处的导数为无穷大。

(2) 就是函数y=f(x)在点处的变化率,它反映了函数y=f(x)在点 处随自变量x变化的快慢程度。

定理:函数可导则函数连续;函数连续不一定可导;不连续的函数一定不可导。

接下里我们给出常用的几个激活函数以及其图像:

图上左上角,即为上文所述的sigmoid函数,其导数为a· = a(1 - a)

参考:Sigmoid函数求导(保姆级推导过程)-CSDN博客

导数:微积分的核心是极限(Limit)求导(Derivative)是微积分的重要内容,本质就是求极限。

导数公式是‌微积分学中的基础概念,用于描述函数在某一点的局部性质,特别是函数在该点附近的变化率。导数的计算公式涵盖了各种基本函数,包括常数函数、‌幂函数、‌指数函数、‌对数函数、‌三角函数等。以下是一些常见的导数公式:

  1. 常数函数的导数:y = c (c为常数),y’ = 0。
  2. 幂函数的导数:y = x^n,y’ = n*x^(n-1)。
  3. 指数函数的导数:y = a^x,y’ = a^x * ln(a);y = e^x,y’ = e^x。
  4. 对数函数的导数:y = log_a(x),y’ = 1/(x * ln(a));y = ln(x),y’ = 1/x。
  5. 三角函数的导数:y = sin(x),y’ = cos(x);y = cos(x),y’ = -sin(x)。
  6. 反三角函数的导数:y = arcsin(x),y’ = 1/√(1 - x^2);y = arccos(x),y’ = -1/√(1 - x^2)。

如求原函数与积分是等价的,求导和积分是一对互逆的操作。‌

导数的四则运算法则包括:

链式法则求导: ( f ( g ( x ) ) ) ′ = f ′ ( g ( x ) ) g ′ ( x )

tanh函数
![](https://i-blog.csdnimg.cn/img_convert/bd328c835a9546db2fe5357acd8a238a.png)

tanh函数的图像如下所示,当输入在0附近时,tanh函数接近线形变换。函数的形状类似于sigmoid函数,不同的是tanh函数关于坐标系原点中心对称

tanh函数的导数如下,当输入接近0时,tanh函数的导数接近最大值1。与sigmoid函数图像中看到的类似,输入在任一方向上远离0点,导数越接近0。

tanh函数的函数图像和sigmoid的曲线很像,但tanh有一些优点:

  1. 当输入过大或过小时,输出几乎是平滑的,梯度小,不利于权值的更新。区别在于输出间隔。tanh的输出区间为1,整个函数以0为中心,优于sigmoid。
  2. 其主要优点是负数输入将被映射为接近-1,而零输入将被映射为tanh图中接近零的地方。
ReLU函数
左下角为ReLU(修正线性单元):a = max(0,z)

右下角为Leaky ReLU:a = max(0.01,z)

给定元素x,ReLU函数被定义为该元素与0的最大值

ReLU函数通过将相应的活性值设为0,仅保留正元素并丢弃所有负元素。如下为ReLU函数的曲线图

当输入为负时,reLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。当输入值等于0时,ReLU函数不可导。如下为ReLU函数的导数:

ReLU函数的求导表现的很好:要么让参数消失,要么让参数通过。ReLU减轻了神经网络的梯度消失问题。ReLU函数有很多变体,如LeakyReLU,pReLU等。

ReLU (Rectified Linear Unit) 函数是目前在深度学习中较其他激活函数更受欢迎的激活函数。与sigmoid和tanh相比,ReLU有如下优点:

  1. 当输入值为正时,不存在梯度饱和问题(训练过程中,梯度逐渐接近0,导致权重几乎不更新)。
  2. 计算速度快,没有 sigmoid、tanh 函数的指数运算。

缺点:

  1. Dead ReLU问题(神经元死亡问题)。当输入值为负数时,将直接舍弃该输入值并用0代替,这样就导致使用链式求导法则求导时,求到这就中断了(或者说梯度为0)。这个问题在有些领域是敏感的,有些是不敏感的。但是在反向传播过程中,如果输入一个负数,梯度将完全为零,这与sigmoid函数和tanh函数的问题是一样的。
  2. ReLU函数的输出要么为0,要么为正数,这意味着ReLU函数不是以0为中心的函数。
leaky ReLU 函数
Leaky ReLU:是 ReLU 的一个变体,在输入小于等于 0 时有一个小的非零斜率 α ,从而避免了神经元死亡问题:

α≠1 and α≠0

α 是一个小常数,通常取 0.01。在反向传播过程中,对于LeakyReLU激活函数输入小于 0 的部分,也可以计算得到梯度(ReLU的值为0),这样就避免了梯度方向锯齿问题。

leaky ReLU 函数

优点:

- 解决神经元死亡的问题

缺点:

- 不同区间函数一致性、连续性问题
ELU 函数
即指数线性单元(Exponential Linear Unit),解决神经网络训练中的一些问题,如梯度消失、非连续性以及输出均值偏离零等问题。

理想的激活函数应满足两个条件:

  1. 输出的分布是零均值的,加快训练速度。
    
  2. 激活函数是单侧饱和的,更好的收敛。
    

LeakyReLU满足1不满足2;而ReLU满足2不满足1,ELU 都满足。

ELU:在 x ≤ 0 x \leq 0 x≤0 时有平滑的指数衰减,解决神经元死亡问题,数学表达式为:

特点

  • 缓解梯度消失问题:当 ( x > 0 ),ELU 函数的行为类似于ReLU,允许直接传递输入,从而避免了梯度消失的问题。
  • 对负值的处理更加温和:与ReLU不同的是,当 ( x < 0 ),ELU 不是简单地将它们置为0,而是通过指数函数给出一个非零的输出,这有助于保持网络中的信息流动。
  • 输出的均值更接近于零:由于其在负区间内的特性,ELU 能够帮助神经网络学习到更具有鲁棒性的特征表示,并且倾向于产生更接近于零的输出均值,这对于加速学习过程是有益的。

ELU 的导数在 x > 0 时为1,在 x < 0 时为$ α·e^x $ 。特别地,在 x = 0处,通常认为其导数是连续的,取左侧或右侧极限值之一。

ELU 适用于需要减少偏移量并加快学习速度的任务,但计算上比ReLU稍微复杂一些,因为它涉及到指数运算。因此,在设计深度学习模型时,需权衡这些因素来决定最适合的激活函数。

softmax函数
在二分类任务时,经常使用sigmoid激活函数。而在处理多分类问题的时候,需要使用softmax函数。它的输出有两条规则。
  • 每一项的区间范围的(0,1)
  • 所有项相加的和为1.

假设有一个数组V,Vi代表V中的第i个元素,那么这个元素的softmax值的计算公式为:

那么在搭建神经网络的时候,应该如何选择激活函数?

  1. 如果搭建的神经网络的层数不多的时候,选择sigmoid、tanh、relu都可以,如果搭建的网络层数较多的时候,选择不当不当会造成梯度消失的问题,此时一般不宜选择sigmoid、tanh激活函数,最好选择relu激活函数。
  2. 在二分类问题中,网络的最后一层适合使用sigmoid激活函数;而多分类任务中,网络的最后一层使用softmax激活函数。
损失函数
神经网络** 损失函数**(loss function)是用来** 衡量神经网络预测输出与实际标签之间的差异的函数**。它是神经网络训练过程中的关键组成部分,用于指导网络参数的优化。

在训练神经网络时,我们希望通过调整网络**权重偏置**来最小化损失函数的值,从而使网络的预测结果尽可能接近实际标签。

损失函数的原理
误差反映单个数据点的预测偏差,损失则是整体数据集的预测偏差总和。损失函数运用这两者原理,聚合误差以优化模型,降低总体预测偏差。

(1)误差(Error)

对单个数据点预测结果与真实值之间的差异,用于评估模型在特定数据点上的预测准确性。

定义:误差是指模型在对单个数据点进行预测时,其预测结果与真实值之间的差异或偏离程度。这种差异反映了模型预测的不准确性或偏差。

计算:误差可以通过多种数学公式来计算。其中:

- 绝对误差:预测值与真实值之间差值的绝对值,用于量化预测偏离真实值的实际大小;
- 平方误差:预测值与真实值之间差值的平方,常用于平方损失函数中,以便更显著地突出较大的误差。

误差棒:

    误差棒通常以线条或矩形的形式出现在数据点的上方、下方或两侧,其长度或大小代表了误差的量级。这种可视化方法有助于识别潜在的问题区域,并指导进一步的模型改进或数据分析。

(2)损失(Loss)

损失是衡量机器学习模型在整个数据集上预测不准确性的总体指标,通过**最小化损失**可以优化模型参数并改进预测性能。

  • 定义:损失是衡量机器学习模型在整个数据集上预测的总体不准确性的指标。它反映了模型预测与真实值之间的差异,并将这些差异进行聚合,以提供一个标量值来表示预测的总体不准确性。
  • 计算:损失的具体计算是通过损失函数来完成的。损失函数接受模型的预测值和真实值作为输入,并输出一个标量值,即损失值,表示模型在整个数据集上的总体预测误差。
  • 损失曲线:损失曲线直观地呈现了模型在训练过程中损失值的变化趋势。通过绘制训练损失和验证损失随迭代次数的变化,我们能够洞察模型是否遭遇过拟合或欠拟合等问题,进而调整模型结构和训练策略。

参考:https://blog.csdn.net/u010960155/article/details/104078717

损失函数的算法
常见的神经网络损失函数有以下几种:
  • 均方误差(Mean Squared Error,MSE):均方误差是一种常见的回归问题的损失函数,计算预测值与真实值之间的平均平方差。MSE对于异常值较为敏感。
  • 交叉熵损失(Cross-Entropy Loss):交叉熵损失通常应用于分类问题,特别是多类别分类。它基于真实标签和预测概率分布之间的差异来计算损失。
  • 对数损失(Log Loss):对数损失与交叉熵损失类似,也常用于二分类问题。它基于预测概率和真实标签之间的差异来计算损失。
  • Hinge损失:Hinge损失通常用于支持向量机(SVM)中,也可以应用于神经网络中的二元分类问题。它惩罚了预测结果与真实标签之间的差异。
  • KL散度(KL Divergence):KL散度常用于衡量两个概率分布之间的差异性。在神经网络中,KL散度通常与自编码器等模型中使用,用于衡量重构损失。

选择合适的损失函数取决于具体的任务和数据特点。不同的损失函数可能对模型训练产生不同的效果。因此,在选择损失函数时需要仔细考虑任务的性质和优化目标。

在神经网络的训练中,我们通过损失函数(Loss Function)来衡量这个神经网络的训练是否到位了。

https://blog.csdn.net/leonardotu/article/details/136541350?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172187553916800207043920%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=172187553916800207043920&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-136541350-null-null.142v100pc_search_result_base3&utm_term=%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187

以均方差函数MSE、交叉熵CE为例

https://blog.csdn.net/y601500359/article/details/135262329?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172187510316800182747153%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=172187510316800182747153&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-135262329-null-null.142v100pc_search_result_base3&utm_term=%E5%9D%87%E6%96%B9%E5%B7%AE%E5%92%8C%E5%B9%B3%E6%96%B9%E5%B7%AE&spm=1018.2226.3001.4187

  • 均方差函数MSE

均方误差(Mean Square Error,MSE)是回归损失函数中最常用的误差,它是预测值f(x)与目标值y之间差值平方和的均值,其公式如下所示:

N 是样本数目,o 是神经网络输出,y 是真实值
下图是均方误差值的曲线分布,其中最小值为预测值为目标值的位置。我们可以看到**随着误差的增加损失函数增加的更为迅猛**。

  • 优点:MSE的函数曲线光滑、连续,处处可导,便于使用梯度下降算法,是一种常用的损失函数。 而且,随着误差的减小,梯度也在减小,这有利于收敛,即使使用固定的学习速率,也能较快的收敛到最小值。8

  • 缺点:当真实值y和预测值f(x)的差值大于1时,会放大误差;而当差值小于1时,则会缩小误差,这是平方运算决定的。MSE对于较大的误差(>1)给予较大的惩罚,较小的误差(<1)给予较小的惩罚。也就是说,对离群点比较敏感,受其影响较大。

  • 交叉熵函数CE

交叉熵是信息论中的概念,想要理解交叉熵,首先需要了解一些与之相关的信息论基础。当我们不知道某事物具体状态,却知道它有几种可能性时,显然,可能性种类愈多,不确定性愈大。不确定性愈大的事物,我们最后确定了、知道了,这就是说我们从中得到了愈多的信息,也就是信息量大。所以,熵、不确定性、信息量,这三者是同一个数值。

信息量的基本想法是:一个不太可能发生的事件居然发生了,我们收到的信息要多于一个非常可能发生的事件发生。

用一个例子来理解一下,假设我们收到了以下两条消息:

A:今天早上太阳升起

B:今天早上有日食

我们认为消息A的信息量是如此之少,甚至于没有必要发送,而消息B的信息量就很丰富。利用这个例子,我们来细化一下信息量的基本想法:

①非常可能发生的事件信息量要比较少,在极端情况下,确保能够发生的事件应该没有信息量;

②不太可能发生的事件要具有更高的信息量。事件包含的信息量应与其发生的概率负相关。

假设是一个离散型随机变量,它的取值集合为,定义X=xi的信息量为:

其中,log表示自然对数,底数为e(也有资料使用底数为2的对数)。公式中,为变量取值为的概率,这个概率值应该落在0到1之间,画出上面函数在P为0-1时的取值,图像如下。在概率值趋向于0时,信息量趋向于正无穷,在概率值趋向于1时,信息量趋向于0,这个函数能够满足信息量的基本想法,可以用来描述信息量。

熵:信息量是对于单个事件来说的,但是实际情况一件事有很多种发生的可能,比如掷骰子有可能出现6种情况,明天的天气可能晴、多云或者下雨等等。熵是表示随机变量不确定的度量,是对所有可能发生的事件产生的信息量的期望。如果一个随机变量X的可能取值为X = {x1, x2,…, xk},其概率分布为P(X = xi) = pi(i = 1,2, …, n),则随机变量X的熵定义为:

把最前面的负号放到最后,便成了:


上面两个熵的公式,无论用哪个都行,而且两者等价

相对熵

相对熵又称KL散度,用于衡量对于同一个随机变量x的两个分布p(x)和q(x)之间的差异。在机器学习中,p(x)常用于描述样本的真实分布,例如[1,0,0,0]表示样本属于第一类,而q(x)则常常用于表示预测的分布,例如[0.7,0.1,0.1,0.1]。显然使用q(x)来描述样本不如p(x)准确,q(x)需要不断地学习来拟合准确的分布p(x)。

KL散度的公式如下:

n表示事件可能发生的情况总数

KL散度的值越小表示两个分布越接近。

2.4 交叉熵

我们将KL散度的公式进行变形,得到:

前半部分就是p(x)的熵,后半部分就是我们的交叉熵

机器学习中,我们常常使用KL散度来评估predict和label之间的差别,但是由于KL散度的前半部分是一个常量,所以我们常常将后半部分的交叉熵作为损失函数,其实二者是一样的。

凹凸性判断是:
当一个函数的二阶导数f’’(x)>=0,就是凹函数,类似于y=x^2的图形。
当一个函数的二阶导数f’’(x)<=0,就是凸函数,类似于y=-x^2的图形。

在回归问题中,我们常常使用均方误差(MSE)作为损失函数,其公式如下:

m表示样本个数,loss表示的是m个样本的均值

其实这里也比较好理解,因为回归问题要求拟合实际的值,通过MSE衡量预测值和实际值之间的误差,可以通过梯度下降的方法来优化。而不像分类问题,需要一系列的激活函数(sigmoid、softmax)来将预测值映射到0-1之间,这时候再使用MSE的时候就要好好掂量一下了,为啥这么说,请继续看:

sigmoid加MES的基本公式

gradient推导过程

上面复杂的推导过程,其实结论就是下面一张图:

C就是?的J,sigma就是sigmoid函数,a就是predict

从以上公式可以看出,w和b的梯度跟激活函数的梯度成正比,激活函数的梯度越大,w和b的大小调整得越快,训练收敛得就越快。而我们都知道sigmoid函数长这样:

在上图的绿色部分,初始值是0.98,红色部分初始值是0.82,假如真实值是0。直观来看那么0.82下降的速度明显高于0.98,但是明明0.98的误差更大,这就导致了神经网络不能像人一样,误差越大,学习的越快。

但是如果我们把MSE换成交叉熵会怎么样呢?

x表示样本,n表示样本的总数

重新计算梯度:

推导过程

另外sigmoid有一个很好的性质:

我们从结果可以看出梯度中不再含有sigmoid的导数,有的是sigmoid的值和实际值之间的差,也就满足了我们之前所说的错误越大,下降的越快。

这也就是在分类问题中常用cross entropy 而不是 MSE的原因了。

梯度下降法
[【梯度下降法】详解优化算法之梯度下降法(原理、实现)_梯度下降算法-CSDN博客](https://blog.csdn.net/kevinjin2011/article/details/125299113?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172221881816800207026776%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=172221881816800207026776&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-125299113-null-null.142^v100^control&utm_term=%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E6%B3%95&spm=1018.2226.3001.4187)

梯度下降法(Gradient descent,简称GD)是一阶最优化算法。 要使用梯度下降法找到一个函数的局部极小值,必须向函数上当前点对应梯度(或者是近似梯度)的反方向的规定步长距离点进行迭代搜索。如果相反地向梯度正方向迭代进行搜索,则会接近函数的局部极大值点,这个过程则被称为梯度上升法。

梯度下降法是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降法和最小二乘法是最常采用的方法。在求解损失函数的最小值时,可以通过梯度下降法来迭代求解,得到最小化的损失函数和模型参数值。反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。在机器学习中,基于基本的梯度下降法发展了两种常用梯度下降方法,分别为随机梯度下降法和批量梯度下降法。

2、梯度下降法的原理

举例说明,将下山的人看作,即表示目标函数,目的地是最低点。而中间如何到达最低点则是需要解决的问题。

在当前位置求偏导,即梯度,正常的梯度方向类似于上山的方向,是使值函数增大的,下山最快需使最小,从负梯度求最小值,这就是梯度下降。梯度上升是直接求偏导,梯度下降则是梯度上升的负值。由于不知道怎么下山,于是需要走一步算一步,继续求解当前位置的偏导数。这样一步步的走下去,当走到了最低点,此时我们能得到一个近似最优解。

可以看出梯度下降有时得到的是局部最优解,如果损失函数是凸函数,梯度下降法得到的解就是全局最优解。

如果函数为一元函数,梯度就是该函数的导数:

如果为二元函数,梯度定义为:

梯度下降法公式:

求解步骤:

(1)确定当前位置的损失函数的梯度,对于,其梯度表达式为:

(2)用步长乘以损失函数的梯度,得到当前位置下降的距离,即:

(3)确定是否所有的梯度下降的距离都小于,如果小于则算法终止,当前所有的

即为最终结果。否则进入第(4)步。

(4)更新所有的,更新表达式如下,更新完成后转入步骤(1):

3、Python 代码实现

from sklearn.linear_model import SGDRegressor

from sklearn.preprocessing import StandardScaler

standardScaler = StandardScaler()

X_train_sta = standardScaler.transform(X_train)

X_test_sta = standardScaler.transform(X_test)

sgd_reg = SGDRegressor(n_iter=100000)

sgd_reg.fit(X_train_sta,y_train)

sgd_reg.score(X_test_sta,y_test)

#0.7102320877365798

4、几种常用的梯度下降法

(1)全梯度下降算法(FG)

计算训练集所有样本误差,对其求和再取平均值作为目标函数。权重向量沿其梯度相反的方向移动,从而使当前目标函数减少得最多。因为在执行每次更新时,需要在整个数据集上计算所有的梯度,所以速度会很慢,同时,其在整个训练数据集上计算损失函数关于参数θ的梯度:

(2)随机梯度下降算法(SG)

由于FG每迭代更新一次权重都需要计算所有样本误差,而实际问题中经常有上亿的训练样本,故效率偏低,且容易陷入局部最优解,因此提出了随机梯度下降算法。其每轮计算的目标函数不再是全体样本误差,而仅是单个样本误差,即每次只代入计算一个样本目标函数的梯度来更新权重,再取下一个样本重复此过程,直到损失函数值停止下降或损失函数值小于某个设定的阈值。此过程简单,高效,通常可以较好地避免更新迭代收敛到局部最优解。其迭代形式为:

其中,表示一条训练样本的特征值,表示一条训练样本的标签值。

但是由于,SG每次只使用一个样本迭代,若遇上噪声则容易陷入局部最优解。

(3)小批量梯度下降法

小批量梯度下降算法是FG和SG的折中方案,在一定程度上兼顾了以上两种方法的优点。每次从训练样本集上随机抽取一个小样本集,在抽出来的小样本集上采用FG迭代更新权重。被抽出的小样本集所含样本点的个数称为batch_size,通常设置为2的幂次方,更有利于GPU加速处理。特别的,若batch_size=1,则变成了SG;若batch_size=n,则变成了FG。其迭代形式为:

二、神经网络的发展历程

+ 单个神经元模型(1943年):神经网络的起源可以追溯到1943年,当时神经生理学家沃伦·麦卡洛克和沃尔特·皮茨提出了一个简单的神经元模型,称为麦卡洛克-皮茨模型。它模拟了神经元的基本功能。 + 感知器模型(1957年):弗兰克·罗森布拉特于1957年提出了感知器模型,它是一种最早的人工神经网络模型。感知器模型是一种单层的前馈神经网络,能够解决线性可分问题。 + 多层感知器(1980年代):多层感知器(Multilayer Perceptron,MLP)是一种具有多个隐藏层的前馈神经网络。在1980年代,研究人员发现多层感知器可以通过使用反向传播算法进行训练,从而解决更为复杂的非线性问题。 + 支持向量机(SVM)与核方法(1990年代):1990年代出现了支持向量机(Support Vector Machine,SVM)以及核方法的发展。虽然SVM并非传统意义上的神经网络,但它在机器学习领域的发展对神经网络的发展起到了积极的推动作用。 + 反向传播算法的重要性(1986年):1986年,大卫·鲍姆和保罗·鲍姆提出了反向传播算法,该算法能够有效地训练多层感知器。反向传播算法通过计算网络中每个参数的梯度来进行权重的更新,从而实现了神经网络的有效训练。 + 深度学习的崛起(2000年代):2000年代,随着计算机性能的提升和大规模数据集的可用性增加,深度学习逐渐崭露头角。深度学习使用深层神经网络来进行特征学习和表示学习,取得了在图像识别、语音识别和自然语言处理等领域的重大突破。 + 卷积神经网络(CNN)的突破(2010年代):卷积神经网络(Convolutional Neural Network,CNN)是一种专门用于图像处理的神经网络模型。在2012年,由于深度学习和卷积神经网络惊人的表现,以至于深度学习领域如日中天。
1、感知器模型
![](https://i-blog.csdnimg.cn/img_convert/cf83a949dc528bf93e01ffb847891502.png)

x1、 x2是输入信号,y是输出信号, w1、 w2是权重(w是weight的首字母)。图中的○称为“神经元”或者“节点”。输入信号被送往神经元时,会被分别乘以固定的权重(w1x1、 w2x2)。神经元会计算传送过来的信号的总和,只有当这个总和超过了某个界限值时,才会输出1。这也称为“神经元被激活” 。这里将这个界限值称为阈值,用符号θ表示。

感知机的运行原理只有这些!把上述内容用数学式来表示,就是下面这个式子(1)。

感知机的多个输入信号都有各自固有的权重,这些权重发挥着控制各个信号的重要性的作用。也就是说,权重越大,对应该权重的信号的重要性就越高。

感知机(Perceptron)是最早的人工神经网络模型之一,它主要应用于二分类问题,即判断输入数据属于两个预定义类别中的哪一个。感知机基于一个线性组合的加权输入,通过一个激活函数(通常是阶跃函数或符号函数)产生输出。

下面是一个感知机的实例说明,包括它的基本组成部分和工作原理:

基本组成部分:

  • 输入层:每个输入节点代表一个特征。
  • 权重:每个输入节点到输出节点之间有一个权重,表示该输入特征的重要程度。
  • 偏置(Bias):相当于数学中的截距项,用于调整决策边界的位置。
  • 加权求和:所有输入特征乘以相应的权重后相加,再加上偏置。
  • 激活函数:对加权求和的结果应用激活函数,决定最终的输出。

工作原理:

  • 初始化:权重和偏置通常被随机初始化。
  • 前向传播:输入数据,计算加权求和。
  • 激活:应用激活函数,例如阶跃函数或符号函数,产生输出。
  • 比较与更新:比较输出与实际类别,如果预测错误,则根据误差调整权重和偏置。
  • 迭代:重复上述步骤,直到所有训练数据都被正确分类或达到预定的迭代次数。

Python 实现:

下面是一个简单的感知机模型的Python实现:

# @Author: coderdz
# @File: Perceptron.py
# @Site: github.com/dingzhen7186
# @Ended Time : 2020/9/21

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import load_iris

class Perceptron():
	def __init__(self):
		 # w初始化为全1数组
		self.w = np.ones(len(data[0]) - 1, dtype = np.float32)
		self.b = 0
		self.rate = 0.5 # 初始化学习率
	
	# 感知机训练, 找出最合适的w, b
	def fit(self, x_train, y_train):
		while True:
			flag = True # 标记是否存在误分类数据
			for i in range(len(x_train)): # 遍历训练数据
				xi = x_train[i]
				yi = y_train[i]
				 # 判断 yi * (wx + b) <= 0
				if yi * (np.inner(self.w, xi) + self.b) <= 0:
					flag = False # 找到误分类数据, flag标记为False
					 # 更新w, b值
					self.w += self.rate * np.dot(xi, yi)
					self.b += self.rate * yi
			if flag:
				break
		 # 输出w = ? , b = ?
		print('w = ' + str(self.w) + ', b = ' + str(self.b))

	# 图形显示结果
	def show(self, data):
		x_ = np.linspace(4, 7, 10)
		y_ = -(self.w[0] * x_ + self.b) / self.w[1]
		 # 画出这条直线
		plt.plot(x_, y_)
		 # 画出数据集的散点图
		plt.plot(data[:50, 0], data[:50, 1], 'bo', c = 'blue', label = '0')
		plt.plot(data[50:100, 0], data[50:100, 1], 'bo', c = 'orange', label = '1')
		plt.xlabel('sepal length')
		plt.ylabel('sepal width')
		plt.legend()
		plt.show()

iris = load_iris()
# 通过DataFrmae对象获取iris数据集对象, 列名为该数据集样本的特征名
df = pd.DataFrame(iris.data, columns = iris.feature_names)
# 增加label列为它们的分类标签
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
#print(df.label.value_counts()) 不同标签的样本数量
# 2:50 1:50 0:50

# 画出散点图查看特征点的分布
plt.scatter(df[:50]['sepal length'].values, df[:50]['sepal width'].values, label='0')
plt.scatter(df[50:100]['sepal length'].values, df[50:100]['sepal width'].values, label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()

# 选择数据集
data = np.array(df.iloc[:100, [0, 1, -1]])
# 数据集划分
x, y = data[:, :-1], data[:, -1]
# 将y数据集值变为1和-1
y = np.array([1 if i == 1 else -1 for i in y])

# 开始训练
p = Perceptron()
p.fit(x, y)
p.show(data)

采用sklearn中已经实现好了的Perceptron感知机,然后调用相关方法实现二分类

# @Author: coderdz
# @File: Perceptron.py
# @Site: github.com/dingzhen7186
# @Ended Time : 2020/9/21

import sklearn
from sklearn.linear_model import Perceptron
from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# 数据预处理
iris = load_iris()
df = pd.DataFrame(iris.data, columns = iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data = np.array(df.iloc[:100, [0, 1, -1]])
x, y = data[:, :-1], data[:, -1]
y = np.array([1 if i == 1 else -1 for i in y])

# 模型训练
clf = Perceptron(fit_intercept = True, max_iter = 1000, tol = None, shuffle = True)
clf.fit(x, y)
# 输出参数w, b
print('w = ' + str(clf.coef_) + ', b = ' + str(clf.intercept_))

# 画出图形
plt.figure(figsize = (10, 10)) #设置画布大小
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title('鸢尾花线性数据示例')

# 画出散点图, iris-setosa:山鸢尾, iris-versicolour: 杂色鸢尾
plt.scatter(data[:50, 0], data[:50, 1], c = 'b', label = 'iris-setosa')
plt.scatter(data[50:100, 0], data[50:100, 1], c = 'orange', label = 'iris-versicolor')

# 画出感知机的线
x_ = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ + clf.intercept_) / clf.coef_[0][1]
plt.plot(x_, y_)

plt.legend() # 显示图例
plt.grid(False) # 不显示网络
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()
2、多层感知器
多层感知机,也称为MLP模型,是一种层次网络,包括了三个部分,输入层,输出层,和中间层/隐藏层;每一层都由特定个数的节点(又可称为神经元)构成。

每一个节点上的非线性函数称为激活函数,如果缺少了激活函数,神经网络无论有多少层都失去了意义,都只能描述线性关系。

在深度学习中,神经网络的结构通常包括输入层、隐藏层和输出层。其中,输入层的节点数需要与数据形状匹配,隐藏层的节点数需要根据实验和调整找到最佳的节点数,输出层的节点数通常根据任务的要求来定义。此外,激活函数也是神经网络中的重要组成部分,常见的有Sigmoid、ReLU、Tanh、Softmax等。以下是神经网络的主要结构和构造方法:

输入层输入层的节点数需要与数据形状(维度数量)匹配,否则模型将无法接收正确形状的输入数据。对于每个输入样本,它的特征数量就对应着输入层的节点数。例如,对于图像识别问题,输入层节点数通常等于图像的分辨率乘以通道数。如果图像分辨率为28x28像素,通道数为3(RGB),则输入层的节点数为2352。对于文本处理问题,词嵌入模型通常只需要低维的输入层节点,而词袋模型或one-hot模型就需要词个数那么多的维度/节点数了;
隐藏层中间层的节点数是根据训练数据和模型的复杂度来确定的,这需要进行实验和调整以找到最佳的节点数。一种常见的方法是通过交叉验证来确定中间层的节点数。交叉验证是将数据集分成若干份,其中一部分作为验证集用于验证模型的性能,其他部分作为训练集用于训练模型。然后,多次进行模型训练和验证,并记录模型在验证集上的性能,最终选择最佳的中间层节点数。
输出层输出层的节点数通常根据任务的要求来定义。输出层节点数表示了模型最终需要输出的目标数量,例如分类问题可能需要输出不同类别的概率或标签,回归问题可能需要输出具体的数值等。
激活函数它属于非线性函数,作用于神经元的输出,在神经网络中起到对输入信号进行加工、调整和压缩的作用,将神经元的输出值进行过滤或归一化,使之更符合网络后层的处理需求。常见的有 Sigmoid、ReLU、Tanh、 Softmax等。

对于线性模型:

对于MLP(右图),输入层、输出层之间增加了中间层(隐藏层),输入、输出关系描述:

其中,f(·)是某个非线性函数,叫激活函数。

从数学上来看,如果f(·)是线性的,那么增加了隐藏层的MLP本质上还是一个线性模型。 为了让MLP的学习能力更强大,f(·)需要使用非线性函数,目前应用比较多的有校正非线性(relu)、正切双曲线(tanh)或sigmoid函数。 三个函数的表达式分别为

神经元的计算流程

  • 加权求和:每个神经元将接收到的每个输入乘以相应的权重,所有这些加权输入相加,形成总输入和。
  • 加偏置:将上述加权求和的结果再加上一个偏置值,偏置有助于调整神经元的激活阈值。
  • 激活函数:加权和加偏置的结果通常会通过一个非线性激活函数进行转换,以引入非线性因素,使得网络能够学习和建模更加复杂的关系。常用的激活函数包括ReLU、Sigmoid和Tanh等。

深度学习的核心特性

  • 多层结构:如图所示,深度学习通过增加网络的深度(即多个隐藏层)来增强模型的学习能力,使其能够捕捉更为抽象和复杂的数据特征。
  • 模拟人脑机制:尽管这种模拟相对简化,深度神经网络的设计原理与人脑中神经元的组织方式相似,意在模拟人脑处理信息的复杂机制。

  • 输入。输入是一组原始数据(比如图像的像素值)或前一层神经元的输出,即图中的x;
  • 权重和偏置。每一个输入信号都对应着一个权重(w),它的意义是觉得哪一路信号更重要(高权值),所谓的训练模型就是不断更新这些权值。偏置(b)则是传递函数中一个独立于输入值的参数。
  • 传递函数。简单说就是把所有的输入信号组合称为一个输输出值,计算方式是加权之和,再加上偏置b,即图中的net_i。
  • 激活函数。传递函数计算出来的输出值,还要经过一个激活函数才能成为神经元最终的输出。它的作用是引入非线性关系,使得神经网络能模拟和学习复杂的数据模式。常见的激活函数包括Sigmoid、ReLU、Tanh
  • 输出。经过激活函数处理后的值就是该神经元的输出,它将作为输入被传递给下一个神经层的神经元。

现在已经有了一个神经元,把多个这样的神经元堆叠在一起就构成了一层神经层,而多个神经层沿着纵向继续堆叠下去,就形成了深度神经网络。所谓的“深”其实就是表达神经网络的层数的规模和每层的复杂程度。

一点最典型的神经网络由输入层(1个)、隐藏层(1个或多个)和输出层构成。比如下面这个网络就包含两个隐藏层。

不同的神经网络结构就是如何设计这些神经元的运算、连接方式,不同神经层的安排,以及数据流向等等,以达到处理各种结构数据的目的。

3、支持向量机与核方法
参考:[机器学习:支持向量机(SVM)详解与实战_svm‘-CSDN博客](https://blog.csdn.net/weixin_52390875/article/details/142885040)

支持向量机(Support Vector Machine,简称 SVM)是一种常用于分类和回归任务的监督学习算法。它的核心思想是通过在特征空间中找到一个能够最大化分类间隔的超平面,将不同类别的数据分离开来。

SVM的目标是找到一个最优的决策边界(即超平面),使得分类间隔(即边界到各类别数据点的最小距离)最大化。SVM可以处理线性可分和线性不可分的数据集。

上图中,有离散分布的两类数据点,三条直线分别代表3个分类器.

其中,H3分类器的效果最好,为什么呢?

H1没有把数据点完全分类开;H2虽然可以分类,但是分割线与数据点之间的间隔很小,如果测试数据有“噪声”,就很可能被H2错误的分类(对噪声敏感,泛化能力弱);H3以较大间隔将数据点分类开,对噪声的容忍度较高(泛化能力不错)。

对于支持向量机(SVM)来说,数据点如果是 p 维向量(上图是二维向量),可以用 p-1 维超平面来分开这些点,可能有需要超平面符合要求,但是最佳超平面是 以最大间隔把两个分类分开的超平面。

线性可分SVM:当训练数据线性可分时,通过硬间隔最大化可以学习得到一个线性分类器,即硬间隔SVM,如上图H3;

线性SVM:当训练数据不能线性可分但是可以近似线性可分时,通过软间隔最大化也可以学习到一个线性分类器,即软间隔SVM;

非线性SVM:当训练数据线性不可分时,通过核技巧和软间隔最大化,可以学习到一个非线性SVM。

什么时候用支持向量机?

  •    二分类问题: SVM 主要用于解决二分类问题,即将数据分成两个类别。通过在特征空间中找到一个超平面,SVM可以有效地进行二分类任务。
    
  •     高维空间: 当数据具有大量特征时,SVM仍然能够有效工作。在高维空间中,SVM的性能通常比其他分类算法更好,这使其在处理图像、文本和生物信息学等领域的问题时非常有用。
    
  •     小样本数据集: SVM 在小样本数据集上表现良好,因为它不依赖于整个数据集,而是依赖于支持向量。这使得它对于训练集规模相对较小的问题也适用。
    
  •     非线性关系: SVM 可以通过使用核函数来处理非线性关系。常见的核函数包括多项式核、径向基函数(RBF)核等,使得 SVM 能够处理更加复杂的数据分布。
    
  •     泛化能力强: SVM 在训练后具有较强的泛化能力,这意味着它在面对新的未见数据时能够产生较好的预测性能。
    
  •     异常值敏感: SVM 对于异常值比一些其他算法更为鲁棒。由于它主要关注支持向量,对于远离超平面的离群点的影响相对较小。
    
  •     图像分类和文本分类: SVM 在图像和文本分类等领域中被广泛应用。例如,在文本分类任务中,可以使用 SVM 对文本进行情感分析或主题分类。
    

总结

    当面对复杂的、高维的数据集,并且需要一个能够提供良好泛化性能的分类器时,SVM是一个值得考虑的选择。然而,在处理大规模数据集时,训练时间可能会较长。

线性SVM:

# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
 
# 创建虚拟数据集
X, y = make_classification(n_samples=100, n_features=2, n_informative=2, n_redundant=0, random_state=42)
 
# 划分数据集为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
 
# 创建一个线性核的支持向量机模型
svm_model = svm.SVC(kernel='linear', C=1)
 
# 训练模型
svm_model.fit(X_train, y_train)
 
# 预测测试集
y_pred = svm_model.predict(X_test)
 
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy}")
 
# 绘制决策边界
def plot_decision_boundary(model, X, y):
    h = .02  # 步长
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
 
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
 
    plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm, alpha=0.8)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm)
 
    plt.xlabel('Feature 1')
    plt.ylabel('Feature 2')
    plt.title('SVM Decision Boundary')
    plt.show()
 
# 绘制决策边界
plot_decision_boundary(svm_model, X_test, y_test)
from numpy import *
import random
import matplotlib.pyplot as plt
import numpy
 
def kernelTrans(X,A,kTup):                    # 核函数(此例未使用)
    m,n=shape(X)
    K = mat(zeros((m,1)))
    if kTup[0] =='lin':
        K=X*A.T
    elif kTup[0]=='rbf':
        for j in range(m):
            deltaRow = X[j,:]-A
            K[j]=deltaRow*deltaRow.T           # ||w||^2 = w^T * w
        K =exp(K/(-1*kTup[1]**2))              # K = e^(||x-y||^2 / (-2*sigma^2))
    else:
        raise NameError("Houston we Have a problem --")
    return K
 
class optStruct:
    def __init__(self,dataMain,classLabel,C,toler,kTup):
 
        self.X = dataMain                     # 样本矩阵
        self.labelMat = classLabel
        self.C = C                            # 惩罚因子
        self.tol = toler                      # 容错率
        self.m = shape(dataMain)[0]           # 样本点个数
        self.alphas = mat(zeros((self.m,1)))  # 产生m个拉格郎日乘子,组成一个m×1的矩阵
        self.b =0                             # 决策面的截距
        self.eCache = mat(zeros((self.m,2)))    # 产生m个误差 E=f(x)-y ,设置成m×2的矩阵,矩阵第一列是标志位,标志为1就是E计算好了,第二列是误差E
        # self.K = mat(zeros((self.m,self.m)))
        # for i in range(self.m):               # K[,]保存的是任意样本之间的相似度(用高斯核函数表示的相似度)
        #     self.K[:,i]=kernelTrans(self.X,self.X[i,:],kTup)
 
def loadDataSet(filename):                 # 加载数据
    dataMat = []
    labelMat = []
    fr = open(filename)
    for line in fr.readlines():
        lineArr = line.split()
        dataMat.append([float(lineArr[0]),float(lineArr[1])])
        labelMat.append(float(lineArr[2]))     # 一维列表
    return dataMat, labelMat
 
def selectJrand(i, m):       # 随机选择一个不等于i的下标
    j =i
    while(j==i):
        j = int(random.uniform(0,m))
    return j
 
def clipAlpha(aj, H,L):
    if aj>H:                      # 如果a^new 大于上限值,那么就把上限赋给它
        aj = H
    if L>aj:                      # 如果a^new 小于下限值,那么就把下限赋给它
        aj = L
    return aj
 
def calcEk(oS, k):           # 计算误差E, k代表第k个样本点,它是下标,oS是optStruct类的实例
    # fXk = float(multiply(oS.alphas,oS.labelMat).T * oS.K[:,k] + oS.b)   # 公式f(x)=sum(ai*yi*xi^T*x)+b
    fXk = float(multiply(oS.alphas,oS.labelMat).T * (oS.X*oS.X[k,:].T)) +oS.b
    Ek = fXk - float(oS.labelMat[k])          # 计算误差 E=f(x)-y
    return Ek
 
def selectJ(i, oS, Ei):      # 选择两个拉格郎日乘子,在所有样本点的误差计算完毕之后,寻找误差变化最大的那个样本点及其误差
    maxK = -1                # 最大步长的因子的下标
    maxDeltaE = 0            # 最大步长
    Ej = 0                   # 最大步长的因子的误差
    oS.eCache[i] = [1,Ei]
    valiEcacheList = nonzero(oS.eCache[:,0].A)[0]    # nonzero结果是两个array数组,第一个数组是不为0的元素的x坐标,第二个数组是该位置的y坐标
                                                  # 此处寻找误差矩阵第一列不为0的数的下标
    print("valiEcacheList is {}".format(valiEcacheList))
    if (len(valiEcacheList))>1:
        for k in valiEcacheList:          # 遍历所有计算好的Ei的下标,valiEcacheLIst保存了所有样本点的E,计算好的有效位置是1,没计算好的是0
 
            if k == i:
                continue
            Ek = calcEk(oS,k)
            deltaE = abs(Ei-Ek)          # 距离第一个拉格朗日乘子a1绝对值最远的作为第二个朗格朗日乘子a2
            if deltaE>maxDeltaE:
                maxK = k                 # 记录选中的这个乘子a2的下标
                maxDeltaE = deltaE       # 记录他俩的绝对值
                Ej = Ek                  # 记录a2此时的误差
        return maxK, Ej
    else:                             # 如果是第一次循环,随机选择一个alphas
        j = selectJrand(i, oS.m)
        # j = 72
        Ej = calcEk(oS, j)
    return j,Ej
 
def updateEk(oS, k):
    Ek = calcEk(oS, k)
    oS.eCache[k] = [1,Ek]        # 把第k个样本点的误差计算出来,并存入误差矩阵,有效位置设为1
 
def innerL(i, oS):
    Ei = calcEk(oS, i)           # KKT条件, 若yi*(w^T * x +b)-1<0 则 ai=C  若yi*(w^T * x +b)-1>0 则 ai=0
    print("i is {0},Ei is {1}".format(i,Ei))
    if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
        j,Ej = selectJ(i,oS,Ei)
        print("第二个因子的坐标{}".format(j))
        alphaIold = oS.alphas[i].copy()       # 用了浅拷贝, alphaIold 就是old a1,对应公式
        alphaJold = oS.alphas[j].copy()
        if oS.labelMat[i] != oS.labelMat[j]:  # 也是根据公式来的,y1 不等于 y2时
            L = max(0,oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C+oS.alphas[j]-oS.alphas[i])
        else:
            L = max(0,oS.alphas[j]+oS.alphas[i]-oS.C)
            H = min(oS.C,oS.alphas[j]+oS.alphas[i])
        if L==H:         # 如果这个j让L=H,i和j这两个样本是同一类别,且ai=aj=0或ai=aj=C,或者不同类别,aj=C且ai=0
                         # 当同类别时 ai+aj = 常数 ai是不满足KKT的,假设ai=0,需增大它,那么就得减少aj,aj已经是0了,不能最小了,所以此情况不允许发生
                         # 当不同类别时 ai-aj=常数,ai是不满足KKT的,ai=0,aj=C,ai需增大,它则aj也会变大,但是aj已经是C的不能再大了,故此情况不允许
            print("L=H")
            return 0
        # eta = 2.0*oS.K[i,j]-oS.K[i,i]-oS.K[j,j]   # eta=K11+K22-2*K12
        eta = 2.0*oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T
        if eta >= 0:                 # 这里跟公式正好差了一个负号,所以对应公式里的 K11+K22-2*K12 <=0,即开口向下,或为0成一条直线的情况不考虑
            print("eta>=0")
            return 0
        oS.alphas[j]-=oS.labelMat[j]*(Ei-Ej)/eta     # a2^new = a2^old+y2(E1-E2)/eta
        print("a2 归约之前是{}".format(oS.alphas[j]))
        oS.alphas[j]=clipAlpha(oS.alphas[j],H,L)     # 根据公式,看看得到的a2^new是否在上下限之内
        print("a2 归约之后is {}".format(oS.alphas[j]))
        # updateEk(oS,j)               # 把更新后的a2^new的E更新一下
        if abs(oS.alphas[j]-alphaJold)<0.00001:
            print("j not moving enough")
            return 0
        oS.alphas[i] +=oS.labelMat[j]*oS.labelMat[i]*(alphaJold-oS.alphas[j])   # 根据公式a1^new = a1^old+y1*y2*(a2^old-a2^new)
        print("a1更新之后是{}".format(oS.alphas[i]))
        # updateEk(oS,i)
        # b1^new = b1^old+(a1^old-a1^new)y1*K11+(a2^old-a2^new)y2*K12-E1
        # b1 = oS.b-Ei-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i]-oS.labelMat[j]*\
        #      (oS.alphas[j]-alphaJold)*oS.K[i,j]
 
        b1 = oS.b-Ei-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T-oS.labelMat[j]* \
             (oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
        # b2 = oS.b-Ej-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]-oS.labelMat[j]* \
        #      (oS.alphas[j]-alphaJold)*oS.K[j,j]
 
        b2 = oS.b-Ej-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T-oS.labelMat[j]* \
             (oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
        updateEk(oS,j)          # 个人认为更新误差应在更新b之后,因为公式算出的b的公式使用的是以前的Ei
        updateEk(oS,i)
        # b2^new=b2^old+(a1^old-a1^new)y1*K12+(a2^old-a2^new)y2*K22-E2
        if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
            oS.b = b1.A[0][0]
        elif (0<oS.alphas[j]) and (oS.C > oS.alphas[j]):
            oS.b = b2.A[0][0]
        else:
            oS.b = (b1+b2)/2.0
 
 
        print("b is {}".format(oS.b))
        return 1
    else:
        return 0
 
def smoP(dataMatIn, classLabels, C,toler,maxIter,kTup=('lin',)):
    oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(),C,toler,kTup)
    iter = 0
    entireSet = True              # 两种遍历方式交替
    alphaPairsChanged = 0
    while (iter<maxIter) and ((alphaPairsChanged>0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:
            for i in range(oS.m):
                alphaPairsChanged += innerL(i,oS)
                print("fullSet, iter:%d i: %d pairs changed %d"%(iter,i ,alphaPairsChanged))
 
            iter+=1
            print("第一种遍历alphaRairChanged is {}".format(alphaPairsChanged))
            print("-----------eCache is {}".format(oS.eCache))
            print("***********alphas is {}".format(oS.alphas))
            print("---------------------------------------")
        else:
            nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]  # 这时数组相乘,里面其实是True 和False的数组,得出来的是
                                                                          # 大于0并且小于C的alpha的下标
            for i in nonBoundIs:
                alphaPairsChanged += innerL(i,oS)
                print("non-bound, iter: %d i:%d, pairs changed %d"%(iter,i,alphaPairsChanged))
            print("第二种遍历alphaPairChanged is {}".format(alphaPairsChanged))
            iter+=1
        if entireSet:
            entireSet = False  # 当第二种遍历方式alpha不再变化,那么继续第一种方式扫描,第一种方式不再变化,此时alphachanged为0且entireSet为false,退出循环
        elif (alphaPairsChanged==0):
            entireSet=True
        print("iteration number: %d"%iter)
    return oS.b,oS.alphas
 
def calcWs(alphas,dataArr,classLabels):                # 通过alpha来计算w
    X = mat(dataArr)
    labelMat = mat(classLabels).transpose()
    m,n = shape(X)
    w = zeros((n,1))
    for i in range(m):
        w += multiply(alphas[i]*labelMat[i], X[i,:].T)        # w = sum(ai*yi*xi)
    return w
 
def draw_points(dataArr,classlabel, w,b,alphas):
    myfont = FontProperties(fname='/usr/share/fonts/simhei.ttf')    # 显示中文
    plt.rcParams['axes.unicode_minus'] = False     # 防止坐标轴的‘-’变为方块
    m = len(classlabel)
    red_points_x=[]
    red_points_y =[]
    blue_points_x=[]
    blue_points_y =[]
    svc_points_x =[]
    svc_points_y =[]
    # print(type(alphas))
    svc_point_index = nonzero((alphas.A>0) * (alphas.A <0.8))[0]
    svc_points = array(dataArr)[svc_point_index]
    svc_points_x = [x[0] for x in list(svc_points)]
    svc_points_y = [x[1] for x in list(svc_points)]
    print("svc_points_x",svc_points_x)
    print("svc_points_y",svc_points_y)
 
    for i in range(m):
        if classlabel[i] ==1:
            red_points_x.append(dataArr[i][0])
            red_points_y.append(dataArr[i][1])
        else:
            blue_points_x.append(dataArr[i][0])
            blue_points_y.append(dataArr[i][1])
 
    fig = plt.figure()                     # 创建画布
    ax = fig.add_subplot(111)
    ax.set_title("SVM-Classify")           # 设置图片标题
    ax.set_xlabel("x")                     # 设置坐标名称
    ax.set_ylabel("y")
    ax1=ax.scatter(red_points_x, red_points_y, s=30,c='red', marker='s')   #s是shape大小,c是颜色,marker是形状,'s'代表是正方形,默认'o'是圆圈
    ax2=ax.scatter(blue_points_x, blue_points_y, s=40,c='green')
    # ax.set_ylim([-6,5])
    print("b",b)
    print("w",w)
    x = arange(-4.0, 4.0, 0.1)                   # 分界线x范围,步长为0.1
    # x = arange(-2.0,10.0)
    if isinstance(b,numpy.matrixlib.defmatrix.matrix):
        b = b.A[0][0]
    y = (-b-w[0][0]*x)/w[1][0]    # 直线方程 Ax + By + C = 0
    ax3,=plt.plot(x,y, 'k')
    ax4=plt.scatter(svc_points_x,svc_points_y,s=50,c='orange',marker='p')
    plt.legend([ax1, ax2,ax3,ax4], ["red points","blue points", "decision boundary","support vector"], loc='lower right')         # 标注
    plt.show()
 
dataArr,labelArr = loadDataSet('/home/zhangqingfeng/test/svm_test_data')
b,alphas = smoP(dataArr,labelArr,0.8,0.001,40)
w=calcWs(alphas,dataArr,labelArr)
draw_points(dataArr,labelArr,w,b,alphas)
4、梯度下降算法
+ **梯度就是导数,** + **梯度下降算法就是一种通过求目标函数的导数,来寻求目标函数最小化的方法** + **梯度下降目的是找到目标函数最小化时,对应的自变量的值**

导数和偏导的概念:导数与偏导_导数的偏导-CSDN博客

简单例子:$ f(x)=(x-1)^2+1 $

其对于自变量x求导得到:$ f(x)'=2x-2 $

根据导数的意义,对x求导就是函数曲线对于X轴的斜切率,导数为正数代表随着自变量$ x ∗ ∗ 增大,因变量也随之增大;导数为负数代表随着自变量 ∗ ∗ **增大,因变量也随之增大;导数为负数代表随着自变量** 增大,因变量也随之增大;导数为负数代表随着自变量 x $增大,因变量随之减小。

想要函数达到最小值,就是向着与导数 相反的方向移动自变量x(导数为正,则减小$ x ;导数为负,增大 ;导数为负,增大 ;导数为负,增大 x $)

学习率$ \eta :控制 :控制 :控制 x $更新的幅度

x更新的过程可以表示为:$ x = x - \eta * \frac{df(x)}{dx} $

「初始化:」

# 变量 x 表示当前所在的位置
# 就随机初始在 6 这个位置好了
x = 6
eta = 0.05

「第 1 次更新 x」

# 变量 df 存储当前位置的导数值
df = 2*x-2
x = x - eta*df
# 更新后 x 由 6 变成 
# 6-0.05*10 = 5.5

「第 2 次更新 x」

# 因为 x 的位置发生了变化
# 要重新计算当前位置的导数
df = 2*x-2
x = x - eta*df
# 更新后 x 由 5.5 变成
# 5.5-0.05*9 = 5.05

只要不停地重复刚才的这个过程,那么最终就会收敛到一个 「局部」 的极值点。那么可以想一下这是为什么?因为随着每一次的更新,曲线都会越来越平缓,相应的导数值也会越来越小,当我们接近极值点的时候导数的值会无限地靠近 「0」。由于导数的绝对值越来越小,那么随后更新的幅度也会越来越小,最终就会停留在极值点的位置了。

二元函数的梯度下降
二元函数的图像是在三维坐标系中的,如下图,在这里纵坐标我使用 $ y $ 表示,底面的两个坐标分别使用 $ x_1 $ 和 $ x_2 $ 来表示

通过二元函数凸显可以发现想要让 $ y $的值尽可能地小就要寻找极值点(同样地这个极值点也不一定是最小值点)。梯度是一个由各个自变量的偏导数所组成的一个「向量」。用数学表达式表示如下:

$ (\frac{\partial y}{\partial x_1}, \frac{\partial y}{\partial x_2}) $

算法过程与前面的一元函数类似:

$ x_1 = x_1 - \eta * {\frac{\partial y}{\partial x_1}} $

$ x_2 = x_2 - \eta * {\frac{\partial y}{\partial x_2}} $

同样地,一开始随机初始一个位置,随后让当前位置的坐标值减去学习率乘以当前位置的偏导数。更新自变量 $ x_1 < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 就让 < / f o n t > <font style="color:rgb(25, 27, 31);"> 就让 </font> <fontstyle="color:rgb(25,27,31);">就让</font> x_1 < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 减去学习率乘以 < / f o n t > <font style="color:rgb(25, 27, 31);">减去学习率乘以 </font> <fontstyle="color:rgb(25,27,31);">减去学习率乘以</font> y < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 对 < / f o n t > <font style="color:rgb(25, 27, 31);"> 对 </font> <fontstyle="color:rgb(25,27,31);"></font> x_1 < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 的偏导数,更新自变量 < / f o n t > <font style="color:rgb(25, 27, 31);">的偏导数,更新自变量 </font> <fontstyle="color:rgb(25,27,31);">的偏导数,更新自变量</font> x_2 < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 就让 < / f o n t > <font style="color:rgb(25, 27, 31);">就让 </font> <fontstyle="color:rgb(25,27,31);">就让</font> x_2 < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 减去学习率乘以 < / f o n t > <font style="color:rgb(25, 27, 31);"> 减去学习率乘以</font> <fontstyle="color:rgb(25,27,31);">减去学习率乘以</font> y < f o n t s t y l e = " c o l o r : r g b ( 25 , 27 , 31 ) ; " > 对 < / f o n t > <font style="color:rgb(25, 27, 31);">对 </font> <fontstyle="color:rgb(25,27,31);"></font> x_2 $ 的偏导数。

使用红色箭头表示我们当前所在的位置,随后我使用 「黑色」 箭头代表其中一个轴上的坐标朝向哪个方向变化可以使得函数值 「y」 减小,并使用 「黄色」 箭头代表另外一个轴上的坐标朝向哪个方向变化可以使得函数值 「y」 减小。根据平行色变形法则有了 「黑色」 向量和 「黄色」 向量,就可以知道这两个向量最终达到的效果就是 「蓝色」 向量所达到的效果。

两个变量形成的向量,指向的就是梯度下降的方向,表示如下:

$ \begin{pmatrix}
x_1 \
x_2
\end{pmatrix}
= \begin{pmatrix}
x_1 \
x_2
\end{pmatrix} -
\eta * \begin{pmatrix}
\frac{\partial y}{\partial x_1} \
\frac{\partial y}{\partial x_2}
\end{pmatrix} $

继续推广,对于更多变量,梯度算法可以使用向量表示为:

$ \begin{pmatrix}
x_1 \
x_2 \
… \
x_n
\end{pmatrix}
= \begin{pmatrix}
x_1 \
x_2 \
… \
x_n
\end{pmatrix} -
\eta * \begin{pmatrix}
\frac{\partial y}{\partial x_1} \
\frac{\partial y}{\partial x_2} \
… \
\frac{\partial y}{\partial x_n}
\end{pmatrix} $

5、反向传播算法
参考:[机器学习笔记丨神经网络的反向传播原理及过程(图文并茂+浅显易懂)_神经网络反向传播原理-CSDN博客](https://blog.csdn.net/fsfjdtpzus/article/details/106256925)
(1)神经网络的前向传播原理
以下面全连接三层神经网络为例:

输入:x1、x2

权重:w1、w2

激活函数:f(e)

偏置:b

输出:y

在全连接神经网络中,每一层的每个神经元都会与前一层的所有神经元或者输入数据相连,每一个神经元的输出=使用激活函数激活前一层函数的累加和(加上偏置)


(2)反向传播算法
反向传播(英语:Backpropagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。 该方法对网络中所有权重计算损失函数的梯度。 这个梯度会反馈给最优化方法,用来更新权值以最小化损失函数。(误差的反向传播)

神经网络的反向传播可以分为两个步骤:

步骤一:计算误差

计算神经网络的输出与实际值的误差,这一步就是使用损失函数计算误差,损失函数根据问题类型合理选择均方误差(MSE)、交叉熵等算法。

反向传播是从后向前传播的一种方法。因此计算完误差后,需要将误差向前一层传播。

向前一层传播时,需要考虑前一个神经元的权重系数(因为不同神经元的重要性不同,因此回传时需要考虑权重系数)。例如下图, δ是计算得到的误差


与前向传播时相同,反向传播时后一层的节点会与前一层的多个节点相连,因此需要对所有节点的误差求和。例如图中的神经元f1(e) 同时与f4(e)和f5(e) 相连,因此计算f1(e)的误差时需要考虑后一层f4(e) 和f5(e) 的权重系数,因此δ1 = w14 δ4 + w15 δ5



步骤二:更新权重

图中的η 代表学习率,w′ 是更新后的权重,通过这个式子来更新权重。这个式子具体是怎么来的,请看下面的具体事例,现在只要先保留大概的印象就行了。




(3)实例
总结:前向传播是计算损失,反向传播是回传误差。

下图是一个简单的神经网络:

激活函数为sigmoid,采用均方误差

接下来是反向传播,求误差$ E_{total} $对各个权重的导数

这是一个链式求导过程,若有两个一元函数$ f(x)、g(x) ,把 ,把 ,把 g(x) 作为 作为 作为 f(x) 的输入自变量,得到复合函数: 的输入自变量,得到复合函数: 的输入自变量,得到复合函数: f(g(x)) $

链式求导:$ f(g(x))’ = f’(g(x)) * g’(x) $

更新输出层的权重

误差$ E{total} 对权重 对权重 对权重 W_5 求导计算如下: 求导计算如下: 求导计算如下: \frac{\partial E_{total}}{\partial W_5} =
\frac{\partial E_{total}}{\partial out_{o1}} *
\frac{\partial out_{o1}}{\partial net_{o1}} *
\frac{\partial net_{o1}}{\partial W_5} $

为什么要误差$ E_{total} 对权重 对权重 对权重 W_5 $求导?

$ W_5 方向上, 方向上, 方向上, W_5
怎么变化,才能使误差 怎么变化,才能使误差 怎么变化,才能使误差 E_{total} $最小

得到误差$ E_{total} 对权重 对权重 对权重 W_5 的导数后,更新 的导数后,更新 的导数后,更新 W_5 $权重, $ \eta $是学习率(假设取0.5):

$ W_5 = W_5 - \eta * \frac{\partial E_{total}}{\partial W_5} $

同理更新w6,w7,w8:

更新隐藏层(中间层)权重

在更新隐藏层权重系数w1,使用链式法则时

注意!这个时候神经元o1的求导路径有2条(如蓝色箭头所示)!

因此,计算的时候,需要计算这两个地方传来的误差,并求和

$ E_{total} = \frac{1}{2}\sum(target_{o1} - out_{ox})^2 =
\frac{1}{2} * (target_{o1} - out_{o1})^2 +
\frac{1}{2} * (target_{o2} - out_{o2})^2 $

$ out_{o1} = sigmoid(net_{o1}) =
\frac{1}{1+e^{-net_{o1}}} $

$ out_{o2} = sigmoid(net_{o2}) =
\frac{1}{1+e^{-net_{o2}}} $

$ net_{o1} = out_{h1} * w_5 + out_{h2} * w_6 + b_2 $

$ net_{o2} = out_{h1} * w_7 + out_{h2} * w_8 + b_2 $

$ out_{h1} = sigmoid(net_{h1}) =
\frac{1}{1+e^{-net_{h1}}} =
out_{h1} * (1-out_{h1}) $

$ net_{h1} = w_1 * i_1 + w_2 * i_2 +b_1 $

由上述公式可得:

$ \begin{align}
\frac{\partial E_{total}}{\partial W_1} &=
\frac{\partial E_{total}}{\partial out_{h1}} * \frac{\partial out_{h1}}{\partial net_{h1}} *
\frac{\partial net_{h1}}{\partial W_1} \
&= (\frac{\partial E_{total}}{\partial out_{o1}} * \frac{\partial out_{o1}}{\partial net_{o1}} * \frac{\partial net_{o1}}{\partial out_{h1}} +
\frac{\partial E_{total}}{\partial out_{o2}} * \frac{\partial out_{o2}}{\partial net_{o2}} * \frac{\partial net_{o2}}{\partial out_{h1}} ) *
\frac{\partial out_{h1}}{\partial net_{h1}} *
\frac{\partial net_{h1}}{\partial W_1}
\end{align} $

其中,累加符号表示将不同路径的误差相加,此时的路径有两条(图中的两个蓝色箭头)。同时,将计算输出层的误差时说到,计算时使用δ δδ来表示误差,这里的δ h 1

代表神经元h1的误差。

得到了神经元h1的误差,就可以根据之前的权重系数以及误差来更新权重系数了。

更新h1的权重系数:

至此,1个神经元的权重系数的更新就完成了。其中的η \etaη代表学习率,通常在程序中指定,可以理解为梯度下降法中的步长。

同理,更新w2,w3,w4的权重系数:

三、补充内容

输入层 矩阵 列向量 Xn x 1

n:输入层节点数量

假设中间层有 m 个节点,形成列向量 Hm x 1

偏置矩阵 同样为 m 个元素的列向量 Bm x 1

权重矩阵 Wm x n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值