导航

线性神经网络

Posted on 2022-06-21 12:47  rossxp  阅读(173)  评论(0编辑  收藏  举报

1, 线性模型

线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子:

price=warea⋅area+wage⋅age+b.

wareawage称为权重weight),权重决定了每个特征对我们预测值的影响。b称为偏置bias)、偏移量offset)或截距intercept)。偏置是指当所有特征都取值为0时,预测值应该为多少

给定一个数据集,我们的目标是寻找模型的权重和偏置, 使得根据模型做出的预测大体符合数据里的真实价格。 输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入包含d个特征时,我们将预测结果y^(通常使用尖角符号表示y的估计值)表示为:

y^=w1x1+...+wdxd+b

将所有特征放到向量x∈Rd中,并将所有权重放到向量w∈Rd中, 我们可以用点积形式来简洁地表达模型:

 

 

 

 

 

2, 损失函数

在我们开始考虑如何用模型拟合(fit)数据之前,我们需要确定一个拟合程度的度量。损失函数(loss function)能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。 回归问题中最常用的损失函数是平方误差函数。平方误差可以定义为以下公式:

 

 

 

为了度量模型在整个数据集上的质量,我们需计算在训练集个样本上的损失均值(也等价于求和):

 

在训练模型时,我们希望寻找一组参数(w∗,b∗), 这组参数能最小化在所有训练样本上的总损失

 

 

3, 优化方法——随机梯度下降SGD

首先,我们有一个可微分的函数。这个函数就代表着一座山。我们的目标就是找到这个函数的最小值,也就是山底。根据之前的场景假设,最快的下山的方式就是找到当前位置最陡峭的方向,然后沿着此方向向下走,对应到函数中,就是找到给定点的梯度 ,然后朝着梯度相反的方向,就能让函数值下降的最快!因为梯度的方向就是函数之变化最快的方向(在后面会详细解释)

所以,我们重复利用这个方法,反复求取梯度,最后就能到达局部的最小值,这就类似于我们下山的过程。而求取梯度就确定了最陡峭的方向,也就是场景中测量方向的手段。

 

 

 

 

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。比如BGD批量梯度下降法,它是最原始的形式,它是指在每一次迭代时使用所有样本来进行梯度的更新。为了加快训练速度,采用的随机梯度下降法SGD不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。使得训练速度加快。由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数的更新速度大大加快。但是SGD也有缺点,可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。

因此,介于SGDBGD之间我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descentMBGD)。

总结一下,算法的步骤如下:

1)初始化模型参数的值,如随机初始化;(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。

批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。这些可以调整但不在训练过程中更新的参数称为超参数hyperparameter)。调参hyperparameter tuning)是选择超参数的过程。超参数通常是我们根据训练迭代结果来调整的,而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

 

 

 

4, 实现线性回归的第一个完整例子

我们已经准备好了模型训练所有需要的要素,可以实现主要的训练过程部分了。理解这段代码至关重要,因为从事深度学习后,你会一遍又一遍地看到几乎相同的训练过程。在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测计算完损失后,我们开始反向传播,存储每个参数的梯度。最后,我们调用优化算法SGD来更新模型参数。

 

 

 

 

 

 

 

下面便是训练过程:

 

 

 

 

 

 

 

5, softmax回归分类

softmax函数将未规范化的预测变换为非负并且总和为1,同时要求模型保持可导。我们首先对每个未规范化的预测求幂,这样可以确保输出非负。为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。如下式:o是未规范化的预测值,经过softmax之后就满足了上面的要求)

 

 

 

 

上面是表达式

 

 

 

 

下面用一个例子说明:

 

 

 

 

交叉熵求损失函数如下:

 

 

 

 

6, 多层感知机MLP

多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。激活函数的目的是在模型中加入非线性。

 

 

要加入非线性激活函数,不能使用线性的,原因如下:

线性意味着单调假设:任何特征的增大都会导致模型输出的增大(如果对应的权重为正),或者导致模型输出的减小(如果对应的权重为负)。有时这是有道理的。例如,如果我们试图预测一个人是否会偿还贷款。我们可以认为,在其他条件不变的情况下,收入较高的申请人比收入较低的申请人更有可能偿还贷款。但是,虽然收入与还款概率存在单调性,但它们不是线性相关的。收入从0增加到5万,可能比从100万增加到105万带来更大的还款可能性。我们可以很容易找出违反单调性的例子。例如,我们想要根据体温预测死亡率。对于体温高于37摄氏度的人来说,温度越高风险越大。然而,对于体温低于37摄氏度的人来说,温度越高风险就越低。

7, 那么非线性激活函数有哪些?

7.1sigmoid

对于一个定义域在ℝR中的输入,sigmoid函数将输入变换为区间(0, 1)上的输出

 

 

 

 

 

当人们逐渐关注到到基于梯度的学习时, sigmoid函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似。 当我们想要将输出视作二元分类问题的概率时, sigmoid仍然被广泛用作输出单元上的激活函数 (你可以将sigmoid视为softmax的特例)。 然而,sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代。

7.2tanh

sigmoid函数类似,tanh(双曲正切)函数能将其输入压缩转换到区间(-1, 1)

 

 

 

 

 

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

7.2 ReLU

最受欢迎的激活函数是修正线性单元Rectified linear unitReLU),因为它实现简单,同时在各种预测任务中表现良好。ReLU提供了一种非常简单的非线性变换。

 

 

 

 

 

使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题

而且很简单,算的很快,不同于sigmoidtanh要指数运算,一次指数运算差不多相当于一百次乘法运算。

8, 模型的选择

训练误差training error)是指,模型在训练数据集上计算得到的误差。泛化误差generalization error)是指,模型应用在同样从原始样本的分布中抽取的无限多数据样本时,模型误差的期望。举个例子:假设一个大学生正在努力准备期末考试。一个勤奋的学生会努力做好练习,并利用往年的考试题目来测试自己的能力。尽管如此,在过去的考试题目上取得好成绩并不能保证他会在真正考试时发挥出色。例如,学生可能试图通过死记硬背考题的答案来做准备。他甚至可以完全记住过去考试的答案。另一名学生可能会通过试图理解给出某些答案的原因来做准备。在大多数情况下,后者会考得更好。

几个倾向于影响模型泛化的因素

!可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。

!参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。

!训练样本的数量。即使你的模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。

在机器学习中,我们通常在评估几个候选模型后选择最终的模型。这个过程叫做模型选择。有时,需要进行比较的模型在本质上是完全不同的(比如,决策树与线性模型)。又有时,我们需要比较不同的超参数设置下的同一类模型。我们可能希望比较具有不同数量的隐藏层、不同数量的隐藏单元以及不同的的激活函数组合的模型。为了确定候选模型中的最佳模型,我们通常会使用验证集

 

 

 

几个错误理解验证集和测试集的例子:

!直接谷歌上面爬图片当作imagenet测试集(因为imagenet上面的图片大多数也是从谷歌上面爬的,所以会导致说测试集精度偏高。)

!用测试集作为验证集来进行调参(相当于作弊了),测试集只能用一次。

K折交叉验证:

当训练数据稀缺时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。这个问题的一个流行的解决方案是采用折交叉验证。这里,原始训练数据被分成个不重叠的子集。然后执行次模型训练和验证,每次在个子集上进行训练,并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。最后,通过对次实验的结果取平均来估计训练和验证误差

K常常取5或者10。如何数据集很大,可以取小一点,比如23;如果数据集比较小,那么就取K相对大一点。

 

 

 

 

欠拟合和过拟合:

首先,我们要注意这样的情况:训练误差和验证误差都很严重,但它们之间仅有一点差距。如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足),无法捕获试图学习的模式。此外,由于我们的训练和验证误差之间的泛化误差很小,我们有理由相信可以用一个更复杂的模型降低训练误差。这种现象被称为欠拟合(underfitting)。

另一方面,当我们的训练误差明显低于验证误差时要小心,这表明严重的过拟合(overfitting,过拟合可能意味着过多关注无关紧要的点比如一些噪声是否过拟合或欠拟合可能取决于模型复杂性和可用训练数据集的大小。

 

 

 

9控制过拟合的方法——L2正则化

前面提到参数过多和参数可选择范围过大可能导致过拟合。那么我们可以通过简化模型的方式减少参数来避免过拟合;也可以通过限制参数值的范围来控制模型容量。

正则化就是减少机器学习中过拟合的过程。正则化最常见的方式就是对模型的权重进行L1L2正则化(针对参数的正则化,不同于dropout等等其他正则化)。正则化的定义是凡是可以减少泛化误差而不是训练误差的都叫做正则化方法。

正则化项是对系数做了处理(限制)。L1正则化和L2正则化的说明如下:

L1正则化是指权值向量w中各个元素的绝对值之和

L2正则化是指权值向量w中各个元素的平方和然后再求平方根

那添加L1L2正则化有什么用? 

L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择

L2正则化可以防止模型过拟合一定程度上,L1也可以防止过拟合

 

 

 

现在单独看L2正则化。L2正则化和用拉格朗日乘数法给参数W加上一个约束范围是等价的。相当于是给w的选值加上了一个可行域的范围。比如上面的红色部分是原模型的损失函数的等高线,绿色部分就是可行域范围。那么我们就可以找到可行域范围内的最值了。

 

 

 

 

 

 

所以可以通过控制lambda来控制W, lambda越大,W越小。

 

 

 

右上角显示L2损失函数在优化点附近和稍远地方相同距离对损失函数的影响不一样,优化点附近的更大,那么对于原来模型的损失函数以及加上的正则项来说,原本模型的最优点可以稍微往正则化的圈靠近,这时对于已经加上正则化项的模型损失函数来说是在优化的,直到到达某一点,达到了最优,就达到了平衡。同时由于最优解往原点走了,参数的范围变小了,也达到了减小模型复杂度的目的。

 

 

 

所以因为有lambda的引入,使得每次权重更新时,其实学习率乘上梯度的部分不会有什么变化,就是前面的Wt由之前单纯的Wt变成再另外减去一部分,使得每次更新前其实参数以及衰退了。

有一个很好的问题,为什么要把w往小了拉,如果最优解的w就是比较大的数,那么权重衰减是不是有反作用?

因为我们的数据客观上会有噪音的存在,算法也会去记住噪音,往往会是最优解往往是偏大的,正则化就是为了拉回来。因为我们的数据客观上会有噪音的存在。

10过拟合的方法——丢弃法dopout正则

一则很有意思的评论:

为什么dropout会有用?

直接的想法是这样子:

  在training的时候,会丢掉一些neuron,就好像是你要练轻功的时候,会在脚上绑一些重物;然后,你在实际战斗的时候,就是实际testing的时候,是没有dropout的,就相当于把重物拿下来,所以你就会变得很强

  过拟合是很多机器学习的通病。如果模型过拟合,那么得到的模型几乎不能用。为了解决过拟合问题,一般会采用模型集成的方法,即训练多个模型进行组合。此时,训练模型费时就成为一个很大的问题,不仅训练多个模型费时,测试多个模型也是很费时。

Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征

 

 

 

dropout总结

!丢弃法将一些输入项随机置0来控制模型复杂度(它很少用在CNN之类的模型上面)。

!常作用在多层感知机的隐藏层输出上。

!丢弃概率是控制模型复杂度的超参数(通常设置为0.1、0.5、0.9)。

!可能效果比L2正则化效果要好一点点。

!Dropout是目前最主流的多层感知机的模型控制方法。

!对深度学习来讲,可以把模型弄复杂一点点,再通过正则化来控制模型复杂度。

11,前向传播和反向传播

前向传播(forward propagation或forward pass)指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。

 

 

 

 

 

 

 

反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法。简言之,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。该算法存储了计算某些参数梯度时所需的任何中间变量(偏导数)。

 

 

 

在训练神经网络时,在初始化模型参数后, 我们交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数。 注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。 带来的影响之一是我们需要保留中间值,直到反向传播完成。 这也是训练比单纯的预测需要更多的内存(显存)的原因之一。 此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。 因此,使用更大的批量来训练更深层次的网络更容易导致内存不足(out of memory)错误。

小结:

前向传播在神经网络定义的计算图中按顺序计算和存储中间变量,它的顺序是从输入层到输出层。

反向传播按相反的顺序(从输出层到输入层)计算和存储神经网络的中间变量和参数的梯度。

在训练深度学习模型时,前向传播和反向传播是相互依赖的。

训练比预测需要更多的内存

 

12,梯度消失和梯度爆炸

 

 

 

那么当梯度都是比1大一点的数,网络模型有100层,如下面的例子,就会发生梯度爆炸,当方向传播梯度回来,发现梯度已经太大了。

 

 

 

相反,如果当梯度都是比1小一点的数比如0.8,网络模型有100层,如下面的例子,就会发生梯度消失,当方向传播梯度回来,发现梯度已经很小了,参数就会更新不动。

 

梯度爆炸的问题:

 

 

 

怎么理解对学习率比较敏感?

学习率大的话参数更新的时候参数变化大,那么会带来更大的梯度,而更大的梯度又会导致更大的参数更新,迭代几次整个梯度就变得无穷大。学习率过小有可能导致权重参数更新太小,跑不动。

 

梯度消失的问题:

 

 

 

经过激活函数的输入只要稍微大一点,sigmoid输出求导会变得很小接近0

 

 

 

(底部的层指的是靠近输入的层。因为参数更新是通过反向传播的,顶部的求导即梯度太小,累计反向回到底部的时候已经变得很小了,就会训练不动。)

13,梯度消失和梯度爆炸的问题在于使得训练很不稳定,那么有什么办法可以让梯度值保持在合理得范围内呢?

 

 

14,一些补充:

模型的灵活构造——块nn.Sequential

可以理解为一个列表list,里面的元素是各个层。

!一个块可以由许多层组成;一个块可以由许多块组成。

!块可以包含代码。

!块负责大量的内部处理,包括参数初始化和反向传播。

!层和块的顺序连接由Sequential块处理。

 

 

 

 

 

参数的初始化和调整

在选择了架构并设置了超参数后,我们就进入了训练阶段。此时,我们的目标是找到使损失函数最小化的模型参数值。经过训练后,我们将需要使用这些参数来做出未来的预测。此外,有时我们希望提取参数,以便在其他环境中复用它们,将模型保存下来,以便它可以在其他软件中执行,或者为了获得科学的理解而进行检查。

 

print(net[2].state_dict())可以访问某一层网络的参数状态

 

 

 

Print(net)可以打印出网络模型的结构信息。

 

 

 

可以直接调用loadsave函数分别加载和保存张量,字典甚至模型。

但是由于pytorch不像tensorflow那样可以直接存网络的定义,所以可以使用.state_dic()先存下来网络每一层的参数。

 

 

 

之后再使用load_state_dict()来恢复

 

 

 

15,GPUCPU

查看自己GPU的状态!nvidia-smi

 

 

 

在PyTorch中,每个数组都有一个设备(device), 我们通常将其称为上下文(context)。默认情况下,所有变量和相关的计算都分配给CPU。有时上下文可能是GPU。当我们跨多个服务器部署作业时,事情会变得更加棘手。通过智能地将数组分配给上下文,我们可以最大限度地减少在设备之间传输数据的时间。例如,当在带有GPU的服务器上训练神经网络时,我们通常希望模型的参数在GPU上。运行此部分中的程序,至少需要两个GPU。注意,对于大多数桌面计算机来说,这可能是奢侈的,但在云中很容易获得。

查看拥有GPU的个数 torch.cuda.device_count()

 

 

 

我们可以查询张量所在的设备。默认情况下,张量是在CPU上创建的。

 

 

 

需要注意的是,无论何时我们要对多个项进行操作,它们都必须在同一个设备上。例如,如果我们对两个张量求和,我们需要确保两个张量都位于同一个设备上,否则框架将不知道在哪里存储结果,甚至不知道在哪里执行计算,实际上是可以的,但是需要在不同设备之间传数据,非常慢,而且会造成奇怪的debug

CPU看起来比较快,但是计算的时候要进行访存,所有实际上花费时间不少。

 

 

 

GPU的核远远比CPU的要多,所以GPU总的计算速度要比CPU快很多。