当前位置:   article > 正文

Neural Network-神经网络算法本质_sequential neural networks

sequential neural networks

BP Essence 

前馈:forward propagation,本质上是非线性变换 + 加权求和\sum \sigma(wx)

BP: backward propagation,本质上是loss求导得梯度(误差error在wi上的偏导)+更新权重w(梯度下降)

  • 误差e是通过损失函数loss function对神经元输出(输出层输出,或隐层输出)求偏导(这也叫梯度下降,梯度就是导数,最终的梯度=最终的导数=误差e),再对权重元素w_{ij}求导得到的。
  • 损失函数是根据前馈条件概率p(w_{output}|w_{input})(或称为似然概率)真实向量分量tj得到的,比如,多元交叉熵损失函数-log\sum t_{j} * p_{j}
  • 训练模型的目的是最大化似然概率函数,而最大化似然函数p 等价于 最大化logp 等价于 最小化 -logp 推出 多元交叉熵损失函数

目录

BP Essence 

Background

word2vec主要实现方式有:skip-gram和CBOW。

1. Word2vec~single CBOW算法推导BP

基本定义

extension:前馈神经网络

CBOW

one-word context

输入层到隐藏层

extension:Lookup table查表操作

隐藏层到输出层 

softmax激活

loss function

1.3.2 another explanation for 损失函数

1.3 梯度下降和BP反向传播 

1.3.3 输出层 -> 隐藏层:更新输出权重矩阵W'

1) 先求Error对输出层输出分量uj的偏导数 -> 以便求得误差error。

2) 再求E关于权重矩阵W'的元素w'ij的导数。

3) 使用SGD更新权重w'ij

1.3.4 extension:梯度

1.3.5 隐藏层 -> 输入层:更新隐藏权重矩阵W: 先求w误差e + 再更新w权重

1) 先求E对隐藏层输出hi的偏导,hi和所有输出神经元j都有连接,故求和计算收集到所有误差。

2) 再求E对隐藏层权重矩阵W元素wki的偏导,hi和wki (k=1,2,3...V)权重相连。

3) 更新输入词向量​编辑

2. pytorch实现神经网络

2.1 pytorch普通实现方式

1) 输入packages

2) 创建神经网络class和__init__函数

3) 构建forward()函数,将神经元连接起来

4) 构建模型对象model,启动

5) BP反向传播和梯度下降

BP方式(一)

BP方式(二)

BP error(1): RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed)

2.2 torch.nn.ModuleList 实现神经网络

2.3 torch.nn.Sequential()函数

2.4 torch.Module package

2.4.1 torch.Module.parameters()函数

2.4.2 model.train()函数

2.4.3 model.eval()函数

2.5 extension: loss function为啥需要mask掩码

3. TensorFlow搭建神经网络

3. BP优化器算法 optimizer:SGD to Adam

3.1 Background

3.2 本质Essence

3.3 BP optimization algorithms

3.3.1 SGD

3.3.2 SGDM,SGM with Momentum,动量

3.3.3 SGD with Nesterov Acceleration

3.3.4 AdaGrad

3.3.5 AdaDelta/RMSProp

3.3.6 Adam

3.3.7 Nadam 

3.4 Why有人一直黑Adam,而喜欢SGD?

3.5 优化算法常用的tricks

4. cuda GPU并行计算流程

4.1 选择GPU显卡

4.2 上传模型model

4.3 训练阶段training--上传数据data

4.4 验证阶段eval--上传数据data

4.5  run model

参考


Background

NLP是将人类语言当成文本语言来处理,以使计算机和人类更相似。

Problem:但人类语言没有固定的规则和条理,机器往往很难理解原始文本。

Solution:要使机器能从原始文本中学习,就需要将数据转换成计算机易于处理的向量格式,这个过程叫词表示法word embedding(AI最复杂领域之一)

  • 词表示法word embedding

在向量空间内表达词语,如果词向量之间较近,这就意味着这些词是相互关联的。

  • Unsupervised learning

没有标注训练集,对样本间的规律进行统计,可以独立学习词语的上下文,e.g.task clustering。

word2vec主要实现方式有:skip-gram和CBOW。

  • CBOW的目的是根据上下文contextual words来预测当前中心词的概率,且上下文所有单词对当前中心词出现的概率影响权重是一样的,如在袋子中取词,取出足够数量的词就可以了,与取出词的先后顺序无关。
  • skip-gram是根据中心词来预测上下文语境词概率的。

skip-gram就是一种无监督学习技术,常用于查找给定词语上下文位置最相关的word<-语序

skip-gram基于one-hot向量预测给定中心词上下文词contextual words,它与连续词袋模型CBOW算法相反。在skip-gram算法中,中心词是输入词input words,上下文词是输出词output words。

输入:only one中心词,v-dimensional one-hot向量。

输出:2*c上下文词的one-hot vectors

其实single CBOW模型与单个skip-gram模型本质上是一样的,可以依据single CBOW理解skip-gram算法

1. 最小二乘法推导BP

【转载】深度学习数学基础(二)~随机梯度下降(Stochastic Gradient Descent, SGD)_天狼啸月1990的博客-CSDN博客

1. Word2vec~single CBOW算法推导BP

基本定义

  • 所有单词构成的词典大小为V
  • 要学习的单词的词向量维度为N
  • 一个单词最原始的表示,使用one-hot方式。(x_{1}, x_{2},...,x_{k},...,x_{V}),其中,x_{k}=0/1, \forall k = 1, 2, ..., V。假设某个单词w在字典中对应的下标为k,则x_{k}=1, x_{k'}\neq 1, for k' \neq k
  • 一个训练样本实例(training instance)为(w_{I}, w_{O}),即单词pair对,one-hot向量对w_{I}成为输入单词,可理解成机器学习中的特征,w_{O}称为输出单词,可理解成学习中的标签,我们的目标是用w_{I}来预测w_{O}在CBOW模型中,w_{I}为若干个上下文词,w_{O}为中心词(或称为目标词),因此CBOW要利用上下文词来预测中心词。在Skip-gram模型中,w_{I}为中心词,w_{O}为若干个上下文词,因此Skip-gram要利用中心词来预测上下文词。
  • 输入向量Input Vector:输入单词w_{I}的输入向量表示为v_{w_{I}}
  • 输出向量Output Vector:输出单词w_{O}的输出向量表示为v_{w_{O}}^{'}。对于给定的输入单词,可能的输出单词有V种,不妨令可能的输出单词下标为j(0\leq j < V),则输出单词w_{j}的输出向量表示为v_{w_{j}}^{'}。我们通常用j表示可能的输出单词的下标,因此通常要计算p(w_{j}|w_{I})。
  • 对于一个单词w,有一个输入向量v_{w},还有一个输出向量v_{w}^{'}。因为这个单词既可以作为中心词出现,也可以作为上下文词出现。通常最后我们只采用学习到的输入词向量,输出词向量不用。

extension:前馈神经网络

  • 权重矩阵W每一列表示一个神经元
  • 隐层大小N表示神经元个数 = 输出维度

CBOW

CBOW最早是MIklov等大牛在2013年提出的。CBOW要利用若干个上下文词来预测中心词,称为continuous bag-of-word,即连续词袋,是因为在考虑上下文单词时,这些上下文单词的顺序是忽略的。

one-word context

single-CBOW,即只利用一个上下文单词w_{I}来预测中心词w_{O}

词典大小为V,隐藏层大小为N(每层神经网络的输出维度),相邻层的神经元是全连接的(w每列表示一个神经元,w列数=每层神经元个数=每层神经网络输出维度)。输入层是一个用one-hot方式编码的单词向量x=(x_{1},...,x_{k},...,x_{V}),其中只有一个x_{k}为1,其余均为0。

输入层到隐藏层

从输入层到隐藏层的连接权重可以使用矩阵W_{V*N}表示:

 W_{V*N}每行是相应单词的输入词向量表示,即第一行为为词典中第一个单词的输入词向量表示,第k行为词典中第k个词的输入词向量表示,解释如下:

h = W^{T}x = W^{T}_{(k,\cdot )} : = v_{w_{I}}^{T}    (1)

x是某个单词的one-hot向量表示,且该单词在词典中的下标为k,即x_{k}=1x_{k'}=0k'\neq k。因此只用到W^{T}的第k列,也即W的第k行。因此,W的第k行即为第k个单词的输入词向量,相当于直接将W的第k行拷贝到隐藏层单元h上。  

extension:Lookup table查表操作

因为one-hot向量只有一个元素i为1,其余为0。所以,one-hot vector与权重矩阵w相乘只是得到了W矩阵的第i行,这也叫查表操作lookup table。

隐藏层到输出层 

从隐藏层到输出层,同样有连接权重矩阵W_{N*V}^{'}=w_{ij}^{'}来表示。注意,WW'不是转置的关系,是不同的两个矩阵。W'用于预测输出词向量。

计算输出层每个单元j的未激活值,这个j就是基本定义中的输出单词(标签)在词典中的下标。

u_{j} = v_{w_{j}}^{'T}h    (2)

v_{w_{j}}^{'}是W'的第j列,h实际上就是某个样本对(w_{I}, w_{O})中的w_{I}的输入词向量(CBOW中为上下文词的输入词向量)。

softmax激活

计算输出层每个单元j的激活值,使用softmax激活函数,这个激活函数就是用来近似输出单词的后验概率分布

 p(w_{j}|w_{I}) = y_{j} = softmax(u_{j}) = \frac{exp(u_{j})}{\sum_{j'=1}^{V}exp(u_{j'})}    (3)

 y_{j}是第j个输出神经元的激活值。

(1)、(2)代入(3)得到:

p(w_{j}|w_{I}) = \frac{exp(v_{w_{j}}^{'T}v_{w_{I}})}{\sum _{j'=1}^{V}exp(v_{w_{j'}}^{'T}v_{w_{I}})}    (4)

我们的优化目标是,对于j=O,p(w_{j}|w_{I}) -> 1,对于j≠O,p(w_{j}|w_{I}) -> 0,接近输出向量one-hot形式

但是这个式子是优化难点,分母上需要计算每个输出单词的未激活值,计算复杂度太高。

对于某个单词,v_{w}v_{w}^{'}是单词的两种向量表示形式。其中v_{w}实际上是权重矩阵W(input -> hidden)的某一行向量,v_{w}^{'}则是权重矩阵W'(hidden -> output)的某一列向量。前者称为输入向量,后者称作输出向量

下面将推导上述神经网络版本的优化方法。实践中该方法肯定是行不通的,因为复杂度太高。此处优化推导主要为了加深理解

该模型训练的目标是最大化公式(4)的对数似然函数。公式(4)代表的就是给定上下文单词w_{I}以及其权重矩阵的情况下,预测其实际输出中心词w_{O}的条件概率。

loss function

这里使用的损失函数实际上是交叉熵损失函数E = - \sum_{j=1}^{V}t_{j}log p(x_{j})

  • x_{j}理解为输入one-hot样本,
  • p理解为整个神经网络,因此p(x_{j})在该问题中就是最终的输出神经元激活值(概率)y_{j}
  • t_{j}是样本x_{j}的真实标签。

对于某个样本实例,在输出神经元上,只有一个分量t_{j}=1,其余为0,不妨令这个分量为j*。化简即:E = - logp(w_{O}|w_{I})为本问题的交叉熵损失函数。

推导:对于单样本而言,最大化似然概率p = max log(9) = 最小化似然概率-log(p)

 max p(w_{O}|w_{I}) = max y_{j*} \quad (5)\\ = max log y_{j*} \quad(6)\\ = max log \frac{exp(u_{j*})}{\sum _{j'=1}^{V}exp(u_{j'})} \quad(7)\\ = u_{j*} - log \sum _{j'=1}^{V}exp(u_{j'}) = -E 

即:

E = - u_{j*} + log \sum_{j'=1}^{V}exp(u_{j}^{'})

1.3.2 another explanation for 损失函数

xj为输入one-hot向量第j个单元的值,yj为最终输入向量第j个单元的softmax概率值,tj是真实向量第j个元素的值。对于某个真实样本实例,在输出神经元上,只有一个分量tj=1,其余为0,不妨令这个分量为j*

假设输出词是第j*个词(这里可以说成是第j个单词,因为j这个分量对应的不仅是softmax输出向量的概率,还是词汇表索引值为j的单词,这个单词的one-hot向量在j位置为1,那么softmax输出向量第j单元的概率=one-hot向量j分量为1的概率=第j个词的概率),交叉熵损失函数为:

 

1.3 梯度下降和BP反向传播 

1.3.3 输出层 -> 隐藏层:更新输出权重矩阵W'

1) 先求Error对输出层输出分量uj的偏导数 -> 以便求得梯度。

要注意这里的uj是U向量的第j个分量,对其进行求偏导,要注意与其他uj'区分开,不要在一块求导

当j=j*时,tj=1,否则tj=0。(只对j*这一分量求偏导,其他分量作为常量

  (8-1)

 ej表示第j个词的error。

同理,当 j != j*时,tj=0。(此时,是对uj这一分量求导,uj*和其他u1等作为常量

  (8-2)

2) 再求E关于权重矩阵W'的元素w'ij的导数

一个输出层权重矩阵元素w'ij只和隐藏层神经元hi、输出层为激活神经元uj相连接。

w'ij中i -> 对应着隐层输出h的i个分量;j -> 对应着输出层第j个神经元。

因为u的第j个单元分量u_{j}=v_{wj}^{'T} * h

所以,

​​

uj是由hi乘以w'ij元素得到的。

3) 使用SGD更新权重w'ij

 

 这是一个概率元素一个概率元素的BP

  • 当j对应着j*时,ej<0,w'ij越来越大,即j单词为1的概率越来越大。
  • 当j对应着其他0元素时,ej>0,w'ij越来越小,即j单词为1的概率越来越趋向于0。

如果一次性更新输出神经元j的单词wj的输出词向量v_{wj}^{'},也即w'的第j列(输出神经元j的误差ej传播到和它向量的权重向量v_{wj}^{'})

 

从上式中可以看出,在更新权重参数的过程中,我们需要检查词汇表中的每一个单词,计算出它的激活输出概率yi(源于多分类softmax分母),并与期望输出tj(取值为0或1)进行比较。tj实际上就是真实标签,对于某个样本,若某个输出词为该样本的中心词,则为1,否则为0。 

1.3.4 extension:梯度

梯度:即导数方向最大的那个导数,即函数增长最快的方向,可以简单理解为“梯度就是导数”。

梯度下降,即逆梯度方向下降

  • convergence,收敛,梯度
  • divergence,发散,散度
  • gradient,梯度

导数正副代表了导数的方向,+表示梯度方向角度< 90°;-表示梯度方向角度> 90°。

Loss function计算梯度,梯度方向为损失函数J(θ)增长最大方向,梯度有正有负,其代表梯度方向。梯度下降即逆梯度方向下降,如果在B点梯度下降不能跨过山脊,参数θ更新会始终陷入局部最优。

        因为supposed to be上升更新θ,但短暂的山脊会让损失函数J(θ)增大,即△J(x)为负。"此时从数学公式上理解,最优选择上往下更新,降低J(θ)误差",但向下更新又会回到A点,这样反复重复更新,即陷入局部最优。

        所以增加了二阶动量概念,保持全局梯度下降方向。SGD是一阶动量(梯度), Adam=SGD + 二阶动量(梯度),避免陷入局部最优。

        从物理矢量图像上理解,根据三角形法则,加入二阶动量,使合成向量的箭头始终往下,不会因为中途出现的干扰向上而影响整体最优梯度方向。

        从数学上理解,加入二阶动量,使得previous accumulated矢量梯度方向一直为负数,只要一直逆梯度方向更新即可,即便中途出现梯度为正的“山脊”,也会被累积的负数梯度抵消掉-5 + 2=-3,维持整体梯度下降的方向。

 

【转载】深度学习数学基础(二)~随机梯度下降(Stochastic Gradient Descent, SGD)_天狼啸月1990的博客-CSDN博客

1.3.5 隐藏层 -> 输入层:更新隐藏权重矩阵W先求w梯度(误差e) + 再更新w权重

1) 先求E对隐藏层输出hi的偏导,hi和所有输出神经元j都有连接,故求和计算收集到所有误差

证明:hi和所有输出神经元j都有连接

h2 * w'12 + ... = u21

h2 * w'22 + ... = u22

h2 * w'32 + ... = u23

...

w'ij中i对应着隐层第i个分量hi,j对应着第j个输出神经元v_{w_{j}}^{'}

EHi是一个数值。标量EHi合并成一个向量EH,其含义就是输出权重每一列的加权和,而权重是error

EH是一阶N维向量,

2) 再求E对隐藏层权重矩阵W元素wki的偏导,hi和wki (k=1,2,3...V)权重相连

证明:

h1 = x1*w11 + x2 * w21 + ...+ x_{v}*w_{k1} =\sum_{k=1}^{V} x_{k} * w_{k1},由此得到上式hi。

一个神经元i的权重和Σwx得到一个元素hi(隐藏输出N个)

所以,

    (14)

 对于某个样本x而言,只有一个分量xk=1,其余为0。所以,hi实际上只对第k个x_{k}w_{k}求偏导

隐层权重矩阵W维度V * N,因此一个样本实际上只更新一个元素w_{ki}这里k对应着W的行,i对应着W的列=隐层单元hi,i=1,2,...,N

    (15)

 上式梯度下降过程中,只有输入词Wi那一行的梯度会被更新,其他梯度因为x_{i}!= k保持不变。

3) 更新输入词向量V_{WI}

一个w元素更新:w_{ki}^{(new)} = w_{ki}^{(old)} - \beta \cdot EH_{i} \cdot x_{k}

W整体更新(整体也是只更新一行):V_{WI}^{(new)} = V_{WI}^{(old)} - \eta * EH^{T}    (16)

对于某个样本而言,上述x1, x2, ...,xv只有一个值为1。那么在\frac{\partial E}{\partial w_{ki}} 更新中,只有该行非零,其他行全为0。因此,我们只更新输入上下文词WI对应行的词向量。

该梯度更新过程可以使用同样的解释方法,EH = w_{N*V}^{'} * e_{V*1},意味着:权重矩阵W的更新只是EHi的更新(因为xk=1)。

uj大于0或小于0的误差分解到w'ij的N个分量上

  • 如果过高地估计了某个单词wj(softmax输出概率分量)作为最终输出中心词的概率(即yj>tj),相应e分量ej大于0,则上式Vw更新相当于将V_{WI}输入上下文词向量第j个分量减去输出中心词向量V_{WI}^{'}的第j个分量,是的二者远离。
  • 如果过低地估计了某个单词wj(softmax输出概率分量)作为最终输出中心词的概率(即yj<tj),相应e分量ej小于0,则上式Vw更新相当于将V_{WI}输入上下文词向量第j个分量加上输出中心词向量V_{WI}^{'}的第j个分量,是的二者接近。

因此,上下文单词WI(contextual word)输入向量的更新取决于词汇表中所有单词预测为中心词的误差e。

2. pytorch实现神经网络

深入浅出PyTorch: pytorch教程_天狼啸月1990的博客-CSDN博客

2.1 pytorch普通实现方式

1) 输入packages

  1. import torch
  2. import torch.nn.functional as F
  3. from torch.optim import SGD

2) 创建神经网络class和__init__函数

 def __init__(self):
        super(MyNet, self).__init__()  #
创建所有神经网络__init__函数的第一句话,继承父类的构造函数

__init__()函数用于声明初始化变量、层layer 和 模型models

深度学习模型由一层一层layer堆叠而成的

Note that forward function 用于接收输入数据input和输出结果output。

  1. class MyNet(torch.nn.Module):
  2. def __init__(self):
  3. super(MyNet, self).__init__() # 创建所有神经网络__init__函数的第一句话,继承父类的构造函数
  4. # 构建第一层神经网络
  5. self.conv1 = torch.nn.Conv2d(3,32,3,1,1)
  6. self.relu1 = torch.nn.ReLU()
  7. self.max_pooling1 = torch.nn.MaxPool2d(2,1)
  8. # 构建第二层神经网络
  9. self.conv2 = torch.nn.Conv2d(3,32,3,1,1)
  10. self.relu2 = torch.nn.ReLU()
  11. self.max_pooling2 = torch.nn.MaxPool2d(2,1)
  12. # 构建第三层神经网络
  13. self.dense1 = torch.nn.Linear(32*3*3, 128)
  14. self.dense2 = torch.nn.Linear(128, 10)

3) 构建forward()函数,将神经元连接起来

forward函数,用于接受输入数据input,输出结果output。 

  1. def forwad(self, x):
  2. x = self.conv1(x)
  3. x = self.relu1(x)
  4. x = self.max_pooling1(x)
  5. x = self.conv2(x)
  6. x = self.relu2(x)
  7. x = self.max_pooling2(x)
  8. x = self.dense1(x)
  9. x = self.dense2(x)
  10. return x

4) 构建模型对象model,启动

model = MyNet()

5) BP反向传播和梯度下降

  • BP方式(一)

1)设置迭代更新次数

2)生成输入one-hot embedding

3)输入神经网络对象model(x),前馈计算输出向量output embedding

4)计算损失函数loss

5)损失函数随机梯度下降,计算误差error

6)更新权重参数w

  1. # 设置迭代更新次数
  2. for j in range(200):
  3. # generate one-hot input vector
  4. one_hot = torch.zeros(size_vertex)
  5. one_hot[wvi[j]] = 1 # 中心词输入one-hot向量
  6. # 输入神经网络对象,前馈计算输出向量
  7. out = model(one_hot)
  8. # 计算损失函数
  9. loss = 0
  10. # for c-th panel。确定中心点j左右范围,k遍历中心点j的左右位置
  11. for k in range(max(0, j - w), min(j + w + 1, len(wvi))):
  12. # 损失函数e=每个panel的第j个位置误差之和
  13. error = torch.log(torch.sum(torch.exp(out))) - out[wvi[k]]
  14. loss += error
  15. # 损失函数随机梯度下降,计算误差error
  16. loss.backward()
  17. # 更新权重参数w
  18. for param in model.parameters():
  19. param.data.sub_(lr * param.grad)
  20. # param.grad就是每次迭代计算的误差error,需要每次重置为0
  21. param.grad.data.zero_()
  • BP方式(二)

 1)创建神经网络模型对象model = Model()

Input vector X: 输入向量

Output vector target: 输出标签

创建神经网络模型对象model:net = GCN(feat_dim, num_classes, num_features_nonzero)

2)设置损失函数评价指标

3)设置优化器optimizer

因为torch.optimizer.step()方法会自动更新所有的参数

4)设置迭代更新次数;计算损失函数: predited y和true y;loss随机梯度下降;梯度更新参数

  1. # 定义损失函数评价指标
  2. criterion = torch.nn.CrossEntropyLoss(ignore_index=-1)
  3. # 更新权重w针对的是整个神经网络Net,而不是单层卷积Conv
  4. optimizer = optim.SGD(T.parameters(), lr=0.01, momentum=0.9)
  5. # 设置迭代更新次数:200次
  6. for i in range(200):
  7. # 重置梯度为0。梯度就是导数,就是误差error
  8. optimizer.zero_grad()
  9. # 计算loss函数
  10. loss=criterion(T(X), target)
  11. # BP反向传播,计算梯度误差error。
  12. loss.backward()
  13. # 更新权重,即权重参数w
  14. optimizer.step()
  15. l = (T(X));

BP error(1): RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed)

原因:

Pytorch的机制是每次调用loss.backward()都会free掉所有buffers,模型中可能有多次backward(),而前一次backward()会free掉存储在buffer中的梯度,后一次调用backward()就会报错backward through the graph a second time.

解决:

- 添加retain_graph=True这个参数。

  loss.backward(retain_graph=True)这样backward后就不会free掉buffer中的梯度。

- 第二次循环开始后,重新计算一次输入one-hot 向量和forward输出向量。

2.2 torch.nn.ModuleList 实现神经网络

nn.ModuleList()是一个无序性的序列,并没有实现forward()方法

nn.ModuleList()方法:

  • ModuleList可以存储多个model,传统的方法一个model就要写一个forward,但如果将它们存到一个ModuleList的话,就可以使用一个forward。
  • ModuleList是Module的子类,当Module使用它的时候,就能自动识别为子module,所以nn.ModuleList内部的nn.Module参数也被添加到我们网络的parameter中
  • ModuleList使用网络结构具有灵活性。
  1. import torch
  2. import torch.nn as nn
  3. class testNet(nn.Module):
  4. def __init__(self):
  5. super(testNet, self).__init__()
  6. self.combine = nn.Sequential(
  7. nn.Linear(100,50),
  8. nn.Linear(50,25),
  9. )
  10. def forward(self, x):
  11. x = self.combine(x)
  12. return x
  13. testnet = testNet()
  14. input_x = torch.ones(100)
  15. output_x = testnet(input_x)
  16. print(output_x)

2.3 torch.nn.Sequential()函数

用于快速搭建神经网络的方法。 

nn.sequential是一个sequential容器,模块将按照构造函数中传递的顺序添加到模块中。

nn.Sequential()定义的网络中各层会按照定义的顺序进行级联,需要保证各层的输入和输出之间要衔接,并且nn.Sequential实现了forward方法。

  1. import torch
  2. import torch.nn as nn
  3. class testNet(nn.Module):
  4. def __init__(self):
  5. super(testNet, self).__init__()
  6. self.combine = nn.Sequential(
  7. nn.Linear(100,50),
  8. nn.Linear(50,25),
  9. )
  10. def forward(self, x):
  11. x = self.combine(x)
  12. return x
  13. testnet = testNet()
  14. input_x = torch.ones(100)
  15. output_x = testnet(input_x)
  16. print(output_x)

2.4 torch.Module package

2.4.1 torch.Module.parameters()函数

返回模型所有参数的迭代器 

2.4.2 model.train()函数

训练时启动batch normalization和dropout

如果模型中有BN和dropout,需要在训练时添加model.train(),保证BN层能够用到每一批数据的均值和方差。对于dropout,model.train()是随机取一部分网络连接来训练更新参数。

2.4.3 model.eval()函数

测试时不启动batch normalization和dropout

如果模型中有BN和dropout,在测试时添加model.eval()。Model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。对于dropout,model.eval()是利用了所有网络连接,即不进行随机舍弃神经元。

2.5 extension: loss function为啥需要mask掩码

https://chat.openai.com/chat

目的: 降低缺失数据和噪声数据对loss function的影响

损失函数需要mask是因为在某些任务中,inputs可能包含缺失值或者噪声,而这些缺失值或噪声会对loss function计算产生影响。掩码可以用来指示那些位置是有效的数据,哪些位置是缺失值或噪音,从而使得损失函数只考虑有效数据。这样可以避免缺失值或噪音对loss function计算产生干扰,提高模型鲁棒性和性能

比如,文本分类模型中出现单词缺失的情况。

3. TensorFlow搭建神经网络

tensorflow通过计算图的方式控制计算流程 -》先规划好计算图

with tf.Graph.as_default():

tensorflow搭建神经网络主要有3步:

占位符placeholder提前声明好分配内存的变量。 

1) 数据集准备

2) 前向传播过程设计

3) 损失函数及反向传播过程设计

4) 调用tf.session().run()运行代码。

with tf.Session() as sess:

        sess.run()

tf.Session.close(),关闭会话后,占用的资源 及其关联的内存将会被释放

run(fetches, feed_dict=None, options=None, run_metadata=None),session对fetches list中的张量tensor进行评估和计算

  • fetches,必选参数,执行具体的函数/变量。e.g. sess.run([train_step, loss_mes], feed_dict)
  • feed_dict,传入字典数据,可选参数。feed_dict允许调用者覆盖图中张量的值,including, tf.Tensor, tf.placeholder, tf.SparseTensorValue, tf.SparseTensor。
  • return: run()返回值与fetches具有相同的形状。

3. BP优化器算法 optimizer:SGD to Adam

Adam那么棒,为什么还对SGD念念不忘?一个框架看懂深度学习优化算法

神经网络BP优化算法发展历程: SGD -> SGDM -> NAG -> AdaGrad -> AdaDelta -> Adam -> Nadam。

3.1 Background

神经网络模型效果不好的原因:

1) 数据问题:特征问题。影响约为80%

2) 模型问题:模型设计。

3) 模型优化算法问题。影响约为5%

3.2 本质Essence

BP: 求误差e在当前w的梯度 + 下降更新权重。w_{ij}^{new} = w_{ij}^{old} - \alpha * g_{t}学习率α和梯度gt。

首先定义:待优化参数w,目标函数f(w),初始化学习率α

而后开始进行迭代优化,在每个epoch t:

1) 计算目标函数关于当前参数的梯度:g_{t}=\bigtriangledown f(w_{t})

2) 根据历史梯度计算一阶动量和二阶动量:

m_{t} = \Phi (g_{1}, g_{2},...,g_{t})

V_{t} = \Psi (g_{1}, g_{2}, ..., g_{t})

3) 计算当前时刻的下降梯度:

\eta _{t} = \alpha * m_{t} /\sqrt{V_{t}} 

4) 根据下降梯度进行更新: w_{t+1} = w_{t} - \eta _{t}

3.3 BP optimization algorithms

3.3.1 SGD

最简单的随机梯度下降:m_{t} = g_{t}; V_{t} = I^{2}

problem: 下降速度慢,可能会在沟壑两边持续震荡,停留在一个局部最优点。

3.3.2 SGDM,SGM with Momentum,动量

solution:为了抑制SGD震荡,SGDM认为梯度下降过程可以加入惯性

一阶动量:m_{t} = \beta _{1} m_{t-1} + (1-\beta _{1})g_{t}

β1经验值为0.9,这意味着下降方向主要是此前积累的下降方向,并略微偏向当前时刻的下降方向。

3.3.3 SGD with Nesterov Acceleration

在SGD和SGDM基础上,进一步改进困在局部最优沟壑里面的震荡

NAG,Nesterov Accelerated Gradient

NAG不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,到那个时候的下降方向。

g_{t} = \bigtriangledown f(w_{t} - \alpha *m_{t-1}/\sqrt{V_{t-1}})

3.3.4 AdaGrad

二阶动量意味着“自适应学习率”优化算法时代的到来

Problem: SGD及其变种以同样的学习率更新每个参数

Idea:深度神经网络包含大量的参数,这些参数并不是总会用得到的(比如大规模embedding)

  • 对于经常更新的参数,我们已经积累了大量关于它的知识,不希望它被单个样本影响太大,希望学习速率慢一些。
  • 对于偶尔更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本身上多学一些,即学习率大一些。

二阶动量V_{t} = \sum_{\tau =1}^{t} g_{\tau }^{2}

 -> 下降速度:\eta _{t} = \alpha * m_{t}/\sqrt{V_{t}},此时学习率由α变成了\alpha /\sqrt{V_{t}}

优点:这一方法在稀疏数据场景下表现非常好。

Problem:\sqrt{V_{t}}是单调递增的,会使得学习率单调递减至0,可能会使得训练过程提前结束,即便后续还有数据也无法学到必要的知识。

即AdaGrad单调递减的学习率过于激进!

3.3.5 AdaDelta/RMSProp

Solution: 改变二阶动量计算方法,不积累全部历史梯度,而只关注过去一段时间窗口(即Delta)的下降梯度

V_{t} = \beta _{2} V_{t-1} + (1-\beta _{2})g_{t}^{2}

3.3.6 Adam

 Adam和Nadam是前述方法的集大成者。

  • SGDM在SGD基础上增加了一阶动量。
  • AdaGrad和AdaDelta在SGD基础上增加了二阶动量。

-> 一阶动量+二阶动量 -> Adam, Adaptive + Momentum

SGD的一阶动量:m_{t} = \beta _{1} m_{t-1} + (1-\beta _{1})g_{t}

AdaDelta的二阶动量:V_{t} = \beta _{2} V_{t-1} + (1-\beta _{2})g_{t}^{2}

根据经验值,β1=0.9,β2=0.999。

3.3.7 Nadam 

在Adam基础上g_{t} = \bigtriangledown f(w_{t} - \alpha * m_{t-1}/\sqrt{V_{t-1}})

3.4 Why有人一直黑Adam,而喜欢SGD?

如果不想做精细的调整,那么Adam显然最便于直接拿来上手。

但这样的傻瓜方式并不一定能够适应所有的场合。

1) Adam可能不收敛。

2) 可能错过全局最优解。在一个维度极高的空间内,非凸的目标函数往往起起伏伏

        solution:前期Adam快速收敛,后期SGD慢慢寻找最优解

        ①when:前期每次迭代完都计算一下SGD的学习率,当SGD学习率基本不变的时候切换。

        ②切换后的学习率:SGD在Adam下降方向上进行正交投影,应该正好等于Adam的下降方向(含步长)。

3.5 优化算法常用的tricks

1)刚入门,用Adam

2)熟悉你的算法

3)了解数据,if稀疏 -> 自适应学习率算法

4)根据需求来选择,先Adam快速收敛,再SGD极致优化。

5)小数据集实验因为SGD收敛速度和数据集大小无关

6)不同算法组合。e.g. Adam + SGD,etc。

7)数据集一定充分打散(shuffle)

8)训练过程中持续监控训练数据集和验证数据集上的目标函数值以及精度,或AUC等指标的变化情况

  • 对训练数据的监控主要是保证模型得到充分的训练 -> 下降方向正确,且学习率足够高。
  • 对验证数据监控主要是为了避免出现过拟合。

9)制定一个合适的学习率衰减策略:定期衰减、AUC指标衰减、测试集指标不变时衰减。

4. cuda GPU并行计算流程

 参考: Huggingface:https://www.cnblogs.com/chenhuabin/p/16997607.html 

CUDA,Compute Unified Device Architecture 

本质:CUDA是用于深度学习模型,在cpu和GPU之间交换数据的并行计算架构

在pytorch中,即使是有GPU的机器,它也不会自动使用GPU,而是需要在程序中显示指定。调用model.cuda(),可以将模型加载到GPU上去。这种方法不被提倡,而建议使用model.to(device)的方式,这样可以显示指定需要使用的计算资源,特别是有多个GPU的情况下

4.1 选择GPU显卡

device = set_device(cuda_index=1)  # 选择GPU

4.2 上传模型model

  1. class CustomBERTModel(nn.Module):
  2. def __init__(self, n_classes):
  3. super(CustomBERTModel, self).__init__()
  4. self.bert = AutoModel.from_pretrained("./models/bert-base-chinese")
  5. self.drop = nn.Dropout(p=0.3)
  6. self.out = nn.Linear(self.bert.config.hidden_size, n_classes)
  7. def forward(self, input_ids, attention_mask):
  8. _, pooled_output = self.bert(
  9. input_ids=input_ids,
  10. attention_mask=attention_mask,
  11. return_dict = False
  12. )
  13. output = self.drop(pooled_output) # dropout
  14. return self.out(output)
  15. n_classes = len(label2id)
  16. model = CustomBERTModel(n_classes)
  17. model = model.to(device)

4.3 训练阶段training--上传数据data

  • input_ids、attention_mask、targets上传到GPU
  • model已经上传过去了,直接在GPU上运行得到outputs
  1. def train_epoch(model, data_loader,loss_fn,optimizer,device,scheduler, n_examples):
  2. model.train()
  3. losses = []
  4. correct_predictions = 0
  5. for i, d in bar(data_loader):
  6. input_ids = d["input_ids"].to(device)
  7. attention_mask = d["attention_mask"].to(device)
  8. targets = d["labels"].to(device)
  9. outputs = model(
  10. input_ids=input_ids,
  11. attention_mask=attention_mask
  12. )
  13. _, preds = torch.max(outputs, dim=1)
  14. loss = loss_fn(outputs, targets)
  15. correct_predictions += torch.sum(preds == targets)
  16. losses.append(loss.item())
  17. loss.backward()
  18. nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
  19. optimizer.step()
  20. scheduler.step()
  21. optimizer.zero_grad()
  22. return correct_predictions.double() / n_examples, np.mean(losses)

4.4 验证阶段eval--上传数据data

  1. def eval_model(model, data_loader, loss_fn, device, n_examples):
  2. model.eval() # 验证预测模式
  3. losses = []
  4. correct_predictions = 0
  5. with torch.no_grad():
  6. for d in data_loader:
  7. input_ids = d["input_ids"].to(device)
  8. attention_mask = d["attention_mask"].to(device)
  9. targets = d["labels"].to(device)
  10. outputs = model(
  11. input_ids=input_ids,
  12. attention_mask=attention_mask
  13. )
  14. _, preds = torch.max(outputs, dim=1)
  15. loss = loss_fn(outputs, targets)
  16. correct_predictions += torch.sum(preds == targets)
  17. losses.append(loss.item())
  18. return correct_predictions.double() / n_examples, np.mean(losses)

4.5  run model

  • 定义scheduler

        scheduler.step()是pytorch用来更新优化器学习率的,一般按照epoch为单位进行更换。

  • 定义损失函数,上传GPU
  1. EPOCHS = 5 # 训练轮数
  2. optimizer = AdamW(model.parameters(), lr=2e-5)
  3. total_steps = len(train_dataloader) * EPOCHS
  4. scheduler = get_linear_schedule_with_warmup(
  5. optimizer,
  6. num_warmup_steps=0,
  7. num_training_steps=total_steps
  8. )
  9. loss_fn = nn.CrossEntropyLoss().to(device)

参考

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/240617
推荐阅读
相关标签
  

闽ICP备14008679号