赞
踩
前馈:forward propagation,本质上是非线性变换 + 加权求和。
BP: backward propagation,本质上是loss求导得梯度(误差error在wi上的偏导)+更新权重w(梯度下降)。
目录
word2vec主要实现方式有:skip-gram和CBOW。
1.3.2 another explanation for 损失函数
1) 先求Error对输出层输出分量uj的偏导数 -> 以便求得误差error。
1.3.5 隐藏层 -> 输入层:更新隐藏权重矩阵W: 先求w误差e + 再更新w权重
1) 先求E对隐藏层输出hi的偏导,hi和所有输出神经元j都有连接,故求和计算收集到所有误差。
2) 再求E对隐藏层权重矩阵W元素wki的偏导,hi和wki (k=1,2,3...V)权重相连。
2.2 torch.nn.ModuleList 实现神经网络
2.4.1 torch.Module.parameters()函数
2.5 extension: loss function为啥需要mask掩码
3. BP优化器算法 optimizer:SGD to Adam
3.3 BP optimization algorithms
3.3.2 SGDM,SGM with Momentum,动量
3.3.3 SGD with Nesterov Acceleration
NLP是将人类语言当成文本语言来处理,以使计算机和人类更相似。
Problem:但人类语言没有固定的规则和条理,机器往往很难理解原始文本。
Solution:要使机器能从原始文本中学习,就需要将数据转换成计算机易于处理的向量格式,这个过程叫词表示法word embedding(AI最复杂领域之一)
在向量空间内表达词语,如果词向量之间较近,这就意味着这些词是相互关联的。
没有标注训练集,对样本间的规律进行统计,可以独立学习词语的上下文,e.g.task clustering。
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算法。
【转载】深度学习数学基础(二)~随机梯度下降(Stochastic Gradient Descent, SGD)_天狼啸月1990的博客-CSDN博客
- 权重矩阵W每一列表示一个神经元
- 隐层大小N表示神经元个数 = 输出维度
CBOW最早是MIklov等大牛在2013年提出的。CBOW要利用若干个上下文词来预测中心词,称为continuous bag-of-word,即连续词袋,是因为在考虑上下文单词时,这些上下文单词的顺序是忽略的。
single-CBOW,即只利用一个上下文单词来预测中心词。
词典大小为V,隐藏层大小为N(每层神经网络的输出维度),相邻层的神经元是全连接的(w每列表示一个神经元,w列数=每层神经元个数=每层神经网络输出维度)。输入层是一个用one-hot方式编码的单词向量,其中只有一个为1,其余均为0。
从输入层到隐藏层的连接权重可以使用矩阵表示:
每行是相应单词的输入词向量表示,即第一行为为词典中第一个单词的输入词向量表示,第k行为词典中第k个词的输入词向量表示,解释如下:
(1)
x是某个单词的one-hot向量表示,且该单词在词典中的下标为k,即,,。因此只用到的第k列,也即W的第k行。因此,W的第k行即为第k个单词的输入词向量,相当于直接将W的第k行拷贝到隐藏层单元h上。
因为one-hot向量只有一个元素i为1,其余为0。所以,one-hot vector与权重矩阵w相乘只是得到了W矩阵的第i行,这也叫查表操作lookup table。
从隐藏层到输出层,同样有连接权重矩阵来表示。注意,W和W'不是转置的关系,是不同的两个矩阵。W'用于预测输出词向量。
计算输出层每个单元j的未激活值,这个j就是基本定义中的输出单词(标签)在词典中的下标。
(2)
是W'的第j列,h实际上就是某个样本对()中的的输入词向量(CBOW中为上下文词的输入词向量)。
计算输出层每个单元j的激活值,使用softmax激活函数,这个激活函数就是用来近似输出单词的后验概率分布。
(3)
是第j个输出神经元的激活值。
(1)、(2)代入(3)得到:
(4)
我们的优化目标是,对于j=O,p() -> 1,对于j≠O,p() -> 0,接近输出向量one-hot形式。
但是这个式子是优化难点,分母上需要计算每个输出单词的未激活值,计算复杂度太高。
对于某个单词,和是单词的两种向量表示形式。其中实际上是权重矩阵W(input -> hidden)的某一行向量,则是权重矩阵W'(hidden -> output)的某一列向量。前者称为输入向量,后者称作输出向量。
下面将推导上述神经网络版本的优化方法。实践中该方法肯定是行不通的,因为复杂度太高。此处优化推导主要为了加深理解。
该模型训练的目标是最大化公式(4)的对数似然函数。公式(4)代表的就是给定上下文单词以及其权重矩阵的情况下,预测其实际输出中心词的条件概率。
这里使用的损失函数实际上是交叉熵损失函数,
对于某个样本实例,在输出神经元上,只有一个分量=1,其余为0,不妨令这个分量为j*。化简即:为本问题的交叉熵损失函数。
推导:对于单样本而言,最大化似然概率p = max log(9) = 最小化似然概率-log(p):
即:
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个词的概率),交叉熵损失函数为:
要注意这里的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)
一个输出层权重矩阵元素w'ij只和隐藏层神经元hi、输出层为激活神经元uj相连接。
w'ij中i -> 对应着隐层输出h的i个分量;j -> 对应着输出层第j个神经元。
因为u的第j个单元分量,
所以,
uj是由hi乘以w'ij元素得到的。
这是一个概率元素一个概率元素的BP。
如果一次性更新输出神经元j的单词wj的输出词向量,也即w'的第j列(输出神经元j的误差ej传播到和它向量的权重向量)
从上式中可以看出,在更新权重参数的过程中,我们需要检查词汇表中的每一个单词,计算出它的激活输出概率yi(源于多分类softmax分母),并与期望输出tj(取值为0或1)进行比较。tj实际上就是真实标签,对于某个样本,若某个输出词为该样本的中心词,则为1,否则为0。
梯度:即导数方向最大的那个导数,即函数增长最快的方向,可以简单理解为“梯度就是导数”。
梯度下降,即逆梯度方向下降。
- 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博客
证明:hi和所有输出神经元j都有连接
h2 * w'12 + ... = u21
h2 * w'22 + ... = u22
h2 * w'32 + ... = u23
...
w'ij中i对应着隐层第i个分量hi,j对应着第j个输出神经元。
EHi是一个数值。标量EHi合并成一个向量EH,其含义就是输出权重每一列的加权和,而权重是error。
EH是一阶N维向量,
证明:
,由此得到上式hi。
一个神经元i的权重和Σwx得到一个元素hi(隐藏输出N个)
所以,
(14)
对于某个样本x而言,只有一个分量xk=1,其余为0。所以,hi实际上只对第k个求偏导
隐层权重矩阵W维度V * N,因此一个样本实际上只更新一个元素,这里k对应着W的行,i对应着W的列=隐层单元hi,i=1,2,...,N。
(15)
上式梯度下降过程中,只有输入词Wi那一行的梯度会被更新,其他梯度因为保持不变。
一个w元素更新:
W整体更新(整体也是只更新一行): (16)
对于某个样本而言,上述x1, x2, ...,xv只有一个值为1。那么在 更新中,只有该行非零,其他行全为0。因此,我们只更新输入上下文词WI对应行的词向量。
该梯度更新过程可以使用同样的解释方法,,意味着:权重矩阵W的更新只是EHi的更新(因为xk=1)。
uj大于0或小于0的误差分解到w'ij的N个分量上
如果过低地估计了某个单词wj(softmax输出概率分量)作为最终输出中心词的概率(即yj<tj),相应e分量ej小于0,则上式Vw更新相当于将输入上下文词向量第j个分量加上输出中心词向量的第j个分量,是的二者接近。
因此,上下文单词WI(contextual word)输入向量的更新取决于词汇表中所有单词预测为中心词的误差e。
深入浅出PyTorch: pytorch教程_天狼啸月1990的博客-CSDN博客
- import torch
- import torch.nn.functional as F
- from torch.optim import SGD
def __init__(self):
super(MyNet, self).__init__() # 创建所有神经网络__init__函数的第一句话,继承父类的构造函数
__init__()函数用于声明初始化变量、层layer 和 模型models。
深度学习模型由一层一层layer堆叠而成的。
Note that forward function 用于接收输入数据input和输出结果output。
- class MyNet(torch.nn.Module):
- def __init__(self):
- super(MyNet, self).__init__() # 创建所有神经网络__init__函数的第一句话,继承父类的构造函数
- # 构建第一层神经网络
- self.conv1 = torch.nn.Conv2d(3,32,3,1,1)
- self.relu1 = torch.nn.ReLU()
- self.max_pooling1 = torch.nn.MaxPool2d(2,1)
-
- # 构建第二层神经网络
- self.conv2 = torch.nn.Conv2d(3,32,3,1,1)
- self.relu2 = torch.nn.ReLU()
- self.max_pooling2 = torch.nn.MaxPool2d(2,1)
-
- # 构建第三层神经网络
- self.dense1 = torch.nn.Linear(32*3*3, 128)
- self.dense2 = torch.nn.Linear(128, 10)
forward函数,用于接受输入数据input,输出结果output。
- def forwad(self, x):
- x = self.conv1(x)
- x = self.relu1(x)
- x = self.max_pooling1(x)
-
- x = self.conv2(x)
- x = self.relu2(x)
- x = self.max_pooling2(x)
-
- x = self.dense1(x)
- x = self.dense2(x)
-
- return x
model = MyNet()
1)设置迭代更新次数
2)生成输入one-hot embedding
3)输入神经网络对象model(x),前馈计算输出向量output embedding
4)计算损失函数loss
5)损失函数随机梯度下降,计算误差error
6)更新权重参数w
- # 设置迭代更新次数
- for j in range(200):
- # generate one-hot input vector
- one_hot = torch.zeros(size_vertex)
- one_hot[wvi[j]] = 1 # 中心词输入one-hot向量
-
- # 输入神经网络对象,前馈计算输出向量
- out = model(one_hot)
-
- # 计算损失函数
- loss = 0
- # for c-th panel。确定中心点j左右范围,k遍历中心点j的左右位置
- for k in range(max(0, j - w), min(j + w + 1, len(wvi))):
- # 损失函数e=每个panel的第j个位置误差之和
- error = torch.log(torch.sum(torch.exp(out))) - out[wvi[k]]
- loss += error
-
- # 损失函数随机梯度下降,计算误差error
- loss.backward()
-
- # 更新权重参数w
- for param in model.parameters():
- param.data.sub_(lr * param.grad)
- # param.grad就是每次迭代计算的误差error,需要每次重置为0
- param.grad.data.zero_()
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随机梯度下降;梯度更新参数
- # 定义损失函数评价指标
- criterion = torch.nn.CrossEntropyLoss(ignore_index=-1)
-
- # 更新权重w针对的是整个神经网络Net,而不是单层卷积Conv
- optimizer = optim.SGD(T.parameters(), lr=0.01, momentum=0.9)
-
- # 设置迭代更新次数:200次
- for i in range(200):
- # 重置梯度为0。梯度就是导数,就是误差error
- optimizer.zero_grad()
- # 计算loss函数
- loss=criterion(T(X), target)
- # BP反向传播,计算梯度误差error。
- loss.backward()
- # 更新权重,即权重参数w
- optimizer.step()
- l = (T(X));
原因:
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输出向量。
nn.ModuleList()是一个无序性的序列,并没有实现forward()方法
nn.ModuleList()方法:
- import torch
- import torch.nn as nn
-
- class testNet(nn.Module):
- def __init__(self):
- super(testNet, self).__init__()
- self.combine = nn.Sequential(
- nn.Linear(100,50),
- nn.Linear(50,25),
- )
-
- def forward(self, x):
- x = self.combine(x)
-
- return x
-
- testnet = testNet()
- input_x = torch.ones(100)
- output_x = testnet(input_x)
- print(output_x)
用于快速搭建神经网络的方法。
nn.sequential是一个sequential容器,模块将按照构造函数中传递的顺序添加到模块中。
nn.Sequential()定义的网络中各层会按照定义的顺序进行级联,需要保证各层的输入和输出之间要衔接,并且nn.Sequential实现了forward方法。
- import torch
- import torch.nn as nn
-
- class testNet(nn.Module):
- def __init__(self):
- super(testNet, self).__init__()
- self.combine = nn.Sequential(
- nn.Linear(100,50),
- nn.Linear(50,25),
- )
-
- def forward(self, x):
- x = self.combine(x)
-
- return x
-
- testnet = testNet()
- input_x = torch.ones(100)
- output_x = testnet(input_x)
- print(output_x)
返回模型所有参数的迭代器
训练时启动batch normalization和dropout
如果模型中有BN和dropout,需要在训练时添加model.train(),保证BN层能够用到每一批数据的均值和方差。对于dropout,model.train()是随机取一部分网络连接来训练更新参数。
测试时不启动batch normalization和dropout
如果模型中有BN和dropout,在测试时添加model.eval()。Model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。对于dropout,model.eval()是利用了所有网络连接,即不进行随机舍弃神经元。
目的: 降低缺失数据和噪声数据对loss function的影响。
损失函数需要mask是因为在某些任务中,inputs可能包含缺失值或者噪声,而这些缺失值或噪声会对loss function计算产生影响。掩码可以用来指示那些位置是有效的数据,哪些位置是缺失值或噪音,从而使得损失函数只考虑有效数据。这样可以避免缺失值或噪音对loss function计算产生干扰,提高模型鲁棒性和性能。
比如,文本分类模型中出现单词缺失的情况。
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进行评估和计算
Adam那么棒,为什么还对SGD念念不忘?一个框架看懂深度学习优化算法
神经网络BP优化算法发展历程: SGD -> SGDM -> NAG -> AdaGrad -> AdaDelta -> Adam -> Nadam。
神经网络模型效果不好的原因:
1) 数据问题:特征问题。影响约为80%。
2) 模型问题:模型设计。
3) 模型优化算法问题。影响约为5%。
BP: 求误差e在当前w的梯度 + 下降更新权重。,学习率α和梯度gt。
首先定义:待优化参数w,目标函数f(w),初始化学习率α。
而后开始进行迭代优化,在每个epoch t:
1) 计算目标函数关于当前参数的梯度:
2) 根据历史梯度计算一阶动量和二阶动量:
3) 计算当前时刻的下降梯度:
4) 根据下降梯度进行更新:
最简单的随机梯度下降:
problem: 下降速度慢,可能会在沟壑两边持续震荡,停留在一个局部最优点。
solution:为了抑制SGD震荡,SGDM认为梯度下降过程可以加入惯性。
一阶动量:
β1经验值为0.9,这意味着下降方向主要是此前积累的下降方向,并略微偏向当前时刻的下降方向。
在SGD和SGDM基础上,进一步改进困在局部最优沟壑里面的震荡。
NAG,Nesterov Accelerated Gradient
NAG不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,到那个时候的下降方向。
二阶动量意味着“自适应学习率”优化算法时代的到来。
Problem: SGD及其变种以同样的学习率更新每个参数。
Idea:深度神经网络包含大量的参数,这些参数并不是总会用得到的(比如大规模embedding)
二阶动量
-> 下降速度:,此时学习率由α变成了
优点:这一方法在稀疏数据场景下表现非常好。
Problem:是单调递增的,会使得学习率单调递减至0,可能会使得训练过程提前结束,即便后续还有数据也无法学到必要的知识。
即AdaGrad单调递减的学习率过于激进!
Solution: 改变二阶动量计算方法,不积累全部历史梯度,而只关注过去一段时间窗口(即Delta)的下降梯度。
Adam和Nadam是前述方法的集大成者。
-> 一阶动量+二阶动量 -> Adam, Adaptive + Momentum
SGD的一阶动量:
AdaDelta的二阶动量:
根据经验值,β1=0.9,β2=0.999。
在Adam基础上
如果不想做精细的调整,那么Adam显然最便于直接拿来上手。
但这样的傻瓜方式并不一定能够适应所有的场合。
1) Adam可能不收敛。
2) 可能错过全局最优解。在一个维度极高的空间内,非凸的目标函数往往起起伏伏
solution:前期Adam快速收敛,后期SGD慢慢寻找最优解。
①when:前期每次迭代完都计算一下SGD的学习率,当SGD学习率基本不变的时候切换。
②切换后的学习率:SGD在Adam下降方向上进行正交投影,应该正好等于Adam的下降方向(含步长)。
1)刚入门,用Adam
2)熟悉你的算法
3)了解数据,if稀疏 -> 自适应学习率算法
4)根据需求来选择,先Adam快速收敛,再SGD极致优化。
5)小数据集实验。因为SGD收敛速度和数据集大小无关。
6)不同算法组合。e.g. Adam + SGD,etc。
7)数据集一定充分打散(shuffle)
8)训练过程中持续监控训练数据集和验证数据集上的目标函数值以及精度,或AUC等指标的变化情况
9)制定一个合适的学习率衰减策略:定期衰减、AUC指标衰减、测试集指标不变时衰减。
参考: 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的情况下。
device = set_device(cuda_index=1) # 选择GPU
- class CustomBERTModel(nn.Module):
- def __init__(self, n_classes):
- super(CustomBERTModel, self).__init__()
- self.bert = AutoModel.from_pretrained("./models/bert-base-chinese")
- self.drop = nn.Dropout(p=0.3)
- self.out = nn.Linear(self.bert.config.hidden_size, n_classes)
- def forward(self, input_ids, attention_mask):
- _, pooled_output = self.bert(
- input_ids=input_ids,
- attention_mask=attention_mask,
- return_dict = False
- )
- output = self.drop(pooled_output) # dropout
- return self.out(output)
-
- n_classes = len(label2id)
- model = CustomBERTModel(n_classes)
- model = model.to(device)
- def train_epoch(model, data_loader,loss_fn,optimizer,device,scheduler, n_examples):
- model.train()
- losses = []
- correct_predictions = 0
-
- for i, d in bar(data_loader):
- input_ids = d["input_ids"].to(device)
- attention_mask = d["attention_mask"].to(device)
- targets = d["labels"].to(device)
- outputs = model(
- input_ids=input_ids,
- attention_mask=attention_mask
- )
- _, preds = torch.max(outputs, dim=1)
- loss = loss_fn(outputs, targets)
- correct_predictions += torch.sum(preds == targets)
- losses.append(loss.item())
- loss.backward()
- nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
- optimizer.step()
- scheduler.step()
- optimizer.zero_grad()
- return correct_predictions.double() / n_examples, np.mean(losses)
- def eval_model(model, data_loader, loss_fn, device, n_examples):
- model.eval() # 验证预测模式
- losses = []
- correct_predictions = 0
-
- with torch.no_grad():
- for d in data_loader:
- input_ids = d["input_ids"].to(device)
- attention_mask = d["attention_mask"].to(device)
- targets = d["labels"].to(device)
-
- outputs = model(
- input_ids=input_ids,
- attention_mask=attention_mask
- )
- _, preds = torch.max(outputs, dim=1)
-
- loss = loss_fn(outputs, targets)
-
- correct_predictions += torch.sum(preds == targets)
- losses.append(loss.item())
-
- return correct_predictions.double() / n_examples, np.mean(losses)
scheduler.step()是pytorch用来更新优化器学习率的,一般按照epoch为单位进行更换。
- EPOCHS = 5 # 训练轮数
-
- optimizer = AdamW(model.parameters(), lr=2e-5)
- total_steps = len(train_dataloader) * EPOCHS
-
- scheduler = get_linear_schedule_with_warmup(
- optimizer,
- num_warmup_steps=0,
- num_training_steps=total_steps
- )
-
- loss_fn = nn.CrossEntropyLoss().to(device)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。