Task01、Task02

本文意在于记录短期学习中同僚总结的知识点,主要学习平台在伯禹https://www.boyuai.com/elites/course/cZu18YmweLv10OeV
Task01:线性回归;Softmax与分类模型、多层感知机(1天)
Task02:文本预处理;语言模型;循环神经网络基础(1天)

Task01:线性回归;Softmax与分类模型、多层感知机

线性回归

实现顺序
1.生成数据集
随机标签,指定参数,计算标准结果添加噪声
2.定义模型
3.定义损失函数
4.定义优化模型
5.训练模型
1)设置超参,初始化模型参数
2)每次迭代中,小批量读取数据,初始化模型计算预测值,损失函数计算插值,反向传播求梯度,优化算法更新参数,参数梯度清零
步骤
第一步,训练集
模型训练的过程其实就是在求【参数】的过程,我们先假定某类【模型】(比如决策树模型),然后用【训练集】来训练,学习到对应的最优的【参数】。但是,我们没有办法保证假设的那个【模型】是最优的,所以我们假设一堆的模型,然后用【训练集】分别对这些模型来进行训练,学习到每一个【模型】中分别对应的参数。
第二步,验证集/调参
通常来说,我们用【超参数】来控制模型的结构(例如正则项系数、神经网络中隐层的节点个数,k值等)。我们就可以找一些数据来训练和学习我们具体的超参数了。直接用【训练集】肯定是不行的,因为我们现在的每一个模型都是用【训练集】来学习出来的,他们在【训练集】上的效果已经很好了,继续用它们来训练超参数不会有太大的效果,所以说我们就选择了使用【验证集】来选择这些超参数。
最后,当我们学习到了【参数】和【非参数】后,我们就确定了我们具体的模型结构,这个时候我们再用一些数据来测试这个模型在新的数据上的效果。因此,我们就不能够使用之前已经使用过的数据了,而要选择一个全新的数据集即【测试集】。这个时候我们就要来看最后的结果怎么样,如果结果很好,那么说明一切顺利,但是如果结果很差,其中可能的一个原因就是我们事先假定的那一类的【模型】(比如我们最先选择的决策树模型)并不是适合来分析这些数据,因此哪怕我们选择出了这一堆决策树模型中最好的一个(超参数的选择过程),它的效果依旧不怎么样。
关于 读取数据部分的程序的解读

  • 程序执行顺序:
    for从函数data_iter中获得可迭代对象,调用next方法。
    data_iter运行至 yield处,并return,此处 yield相当于return
    for将值赋予X,y后,进行下一轮迭代,继续data_iter中的for循环
  • 相关知识:
    生成器: 带yield的函数是一个生成器(生成器是一类特殊的迭代器),而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。
    for item in Iterable循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
    pytorch的函数:
    初始化生成
    torch.ones()/torch.zeros(),与MATLAB的ones/zeros很接近。
    均匀分布
    torch.rand(*sizes, out=None) → Tensor
    返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义。
    标准正态分布
    torch.randn(*sizes, out=None) → Tensor
    返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义。
    torch.mul(a, b)是矩阵a和b对应位相乘,a和b的维度必须相等,比
    如a的维度是(1, 2),b的维度是(1, 2),返回的仍是(1, 2)的矩阵。
    torch.mm(a, b)是矩阵a和b矩阵相乘,比如a的维度是(1, 2),b的维度是(2, 3),返回的就是(1, 3)的矩阵
    torch.Tensor是一种包含单一数据类型元素的多维矩阵,定义了7种CPU tensor和8种GPU tensor类型。
    random.shuffle(a):用于将一个列表中的元素打乱。shuffle() 是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
    backward()是pytorch中提供的函数,配套有require_grad:
    1.所有的tensor都有.requires_grad属性,可以设置这个属性.x = tensor.ones(2,4,requires_grad=True)
    2.如果想改变这个属性,就调用tensor.requires_grad_()方法:   x.requires_grad_(False)

import Timer
timer = Timer()
timer.start()
timer.stop() #直接打印间隔时间

shuffle用来打乱数据集顺序
range函数用法:range(start,end,step)
random.normal用法:
np.random.normal(loc=0.0, scale=1.0, size=None)
作用:生成高斯分布的概率密度随机数
loc:float
—— 此概率分布的均值(对应着整个分布的中心centre)
scale:float
——此概率分布的标准差(对应于分布的宽度,scale越大越矮胖,scale越小,越瘦高)
size:int or tuple of ints
——输出的shape,默认为None,只输出一个值

torch调用神经网络,nn.Sequential(…)与nn.Sequential(OrderedDict([…]))有什么不同?将网络放在一个字典里面了,这个操作有什么好处吗?
如果网络深度上千层时, 前种方法就变得很不实用了 而后种方法在网络很深时也方便拓展;个人理解后者有两种好处:一是可以自定义隐藏层的名字;二是在更深度的情况下支持拓展和方便从外部导入。
pytorch里 读取数据的线程数,是为了并行读取数据,加快拉取数据的速度

提问
  • .view 可以改变tensor的形状, 个人感觉类似于numpy.reshape
    例如 y是(3,4)大小的矩阵 y.view(4,3) 就可以变成大小为(4,3)的矩阵, 当然也可以用(-1,3)和(4,-1)

  • view()里面是可以输入tensor的size()吗
    view可以转换数据维度,而具体如何转换就依据输入的参数(这里的size)
    view的参数类型有view(n)、view(m,n)等 你可以实操一下就明白了

  • yield和index_select(0,j)这两个是什么意思?
    yield与returnl类似,不同处是yield把函数变成生成器,可返回多次值

  • for X, y in data_iter(batch_size, features, labels):
    l = loss(net(X, w, b), y).sum()
    上面的代码中,损失值为什么要求和?感觉和公式推导不是很相同,没有理解,请大家指点一下
    公式推导可以理解为求一个样本的损失,这里是求一个批次的损失,经过net(X, w, b)后得到的是一个batch_size1的向量,得到的损失也是一个batch_size1向量,需要将其表示为一个数值来表示这个batch的损失,所以后面多了一个sum(),用一个批次的损失的和来表示这个批次的损失值

  • .backward()和.grad的用法以及原理?还有为什么要在每次循环时对w.grad以及b.grad进行清零呀?
    清零是为了避免叠加,总的来说,最优化是再所在”点“处找到最优方向优化,如果将w、b叠加,那么就涉及到以前n个点的影响了
    backward()是神经网络反向求导函数,简单的说是求导的链式法则,具体可查阅有关神经网络方向误差、梯度优化的资料
    线性回归的基本要素:训练模型,训练集,优化函数,损失函数。
    线性回归的训练模型为线性模型,训练集即为数据集,视频中的优化函数为小批量的梯度下降,损失函数为均方误差。
    矢量计算比一般的for循环快,所以基本上都是使用矢量计算

  • 3.3.7中:l = loss(output, y.view(-1, 1))这一句是什么意思呢
    output是模型的输出,y是真实的输出,y.view(-1,1)是将y转换成n行一列的数据,和output的size一致,然后调用loss函数计算模型的预测输出和真实输出之间的误差~

题目
真正去实现一个全连接层的时候,如果把b设置成7 * 1,那么网络能接受的批量大小只能是7了,关键还是模型的参数形状不应该和批量大小挂钩
这里的 7 是批量大小,所以这个题目不要看 7 这个数字。 只需要关注输入是8,输出是1就行了

  • 这里的损失函数,为什么没有除以batch_size?·
    首先第一个地方的损失函数是求单个样本的,(y-y_hat)的平方乘上1/2,不需要加batch_size;
    然后第二块在for循环中处理一个批次的损失函数,这里对损失函数得到的向量用sum进行了求和,也不需要加batch_size;
    第三块的求一个周期的损失函数,在输出的时候是对向量用了mean()的方法,也不需要除以batch_size

  • 为什么在数据读取时要将数据打乱处理?
    因为这里的优化函数采用的是随机梯度下降

  • 训练集、验证集和测试集的比例应该怎么去进行分配呢?
    传统上是6:2:2的比例,但是不同的情况下选择应当不同。

损失函数是所有预测值与真实值做插值平方和然后再次平均,但是在简洁实现中的损失函数,loss函数定义的是预测值与真实值的插值取平方除以2,再训练的时候,l对loss函数的结果求和,缺少了平均这一个步骤。为什么没有再次求平均?我对损失函数对batch_size求平均之后得到的结果反而差的可怕,这是什么原因呢?
因为这个求平均的操作对于所有参数的更新影响都是相同的,所以这个操作相当于在调整学习率。
目前代码的学习率是建立在不对batch_size求平均的基础上的,如果你求了平均,相当于学习率小了很多,自然性能下降。
你可以试试把学习率乘以batch_size,应该就能恢复之前的效果了
请问labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
dtype=torch.float32)

为什么构造这个labels时需要再增加一个偏差,b不是已经代表偏差了么?
b代表的偏差是当所有输出的平均值不在零时候的一个固定偏差,构造加入的这个偏差是模拟现实世界中,由未知扰动造成的,与模型不完全相符的误差。

课外学习/参考链接:

迭代器 iter()函数与next()函数 for…in…循环的本质
理解 Python 中的 for 循环

Softmax与分类模型

交叉熵的简易理解: 下面是根据知乎的一篇文章进行的整理,由于平台的latex好像不支持中文,所以直接将自己笔记的截图放上来了,希望对大家的理解有帮助
在这里插入图片描述
在这里插入图片描述
参考链接:https://zhuanlan.zhihu.com/p/35709485

关于softmax函数选择
看完代码,明显softmax和其他模型最与众不同的特点就是softmax函数了,那么为什么选择softmax函数呢?
softmax函数是来自于sigmoid函数在多分类情况下的推广,他们的相同之处:
1.都具有良好的数据压缩能力是实数域R→[ 0 , 1 ]的映射函数,可以将杂乱无序没有实际含义的数字直接转化为每个分类的可能性概率。
2.都具有非常漂亮的导数形式,便于反向传播计算。
3.它们都是 soft version of max ,都可以将数据的差异明显化。
相同的,他们具有着不同的特点,sigmoid函数可以看成softmax函数的特例,softmax函数也可以看作sigmoid函数的推广。
1.sigmoid函数前提假设是样本服从伯努利 (Bernoulli) 分布的假设,而softmax则是基于多项式分布。首先证明多项分布属于指数分布族,这样就可以使用广义线性模型来拟合这个多项分布,由广义线性模型推导出的目标函数即为Softmax回归的分类模型。
2.sigmoid函数用于分辨每一种情况的可能性,所以用sigmoid函数实现多分类问题的时候,概率并不是归一的,反映的是每个情况的发生概率,因此非互斥的问题使用sigmoid函数可以获得比较漂亮的结果;softmax函数最初的设计思路适用于首先数字识别这样的互斥的多分类问题,因此进行了归一化操作,使得最后预测的结果是唯一的。

#softmax基本概念
#分类问题
用来预测种类,图像识别,离散数值来表示不同类别
#权重失量
#神经网络图
单层神经网络,类别与变量间对应关系
#输出问题
1.范围不确定
2.真实为离散值,输出不确定范围误差难以衡量
#softmax值变换为值为正和为1,不改变预测类别输出
#小批量矢量计算
#交叉熵,保证正确预测类别的值大
print(X.sum(dim=0, keepdim=True))#按相同列求和,保留列特征
print(X.sum(dim=1, keepdim=True))#按相同行求和,保留行特征

学习评论中的要素整理:Softmax运算(即数值归一化得到0-1概率,通过exp函数,从直接线性化的hard max变为e指数化的soft max,能够使原有的差异倍数变得更加明显)、交叉熵误差函数(因为2范数太过于严格了,softmax只聚焦于最大概率值,而非全值根据熵增的概念对像素分类)。
补充:反向传递求梯度前一定要梯度清零,以免累增。
首先初始化梯度,计算完一次梯度,更新完之后,清零梯度,进行下一次的计算。

softmax的常数不变性引发的思考:
神经网络中的全部权重同时扩大或减小相同的倍数,softmax分类输出概率不变,交叉熵不变。
因此有必要对神经网络的权重进行正则化,防止权重太大
我对softmax和交叉熵损失函数的理解:
https://www.bilibili.com/video/av86713932?p=3
课件摘自斯坦福大学CS231N计算机视觉公开课
课件里有我的中文批注和讲解

softmax和分类模型
softmax:
分类问题{线性回归:连续值; softmax:离散值的预测}
通常用离散的数值表达不同类别
权重矢量:权重(12个标量)和偏差(3个标量),输出为类别1,2,3的概率
神经网络图——也是一个单层神经网络,softmax回归的输出也是一个全连接层
softmax运算符:通过【y1帽,y2帽,y3帽】= softmax(o1,o2,o3)匠输出值变换为正且和为1的概率分布,softmax运算不改变预测类别输出。
计算效率:单样本矢量计算:可将样本分类通过矢量计算来表达。
小批量矢量计算表达式:进一步提升了计算效率
交叉熵损失函数:
平方损失估计:损失过于严格,损失小很多
交叉熵只关心对正确类别的预测概率,只要其值足够大,就可以确保分类结果正确。
交叉熵损失函数定义:获取Fashion-MNIST训练集和读取数据

交叉熵我是看了下这篇博客
感觉讲得很好https://blog.csdn.net/b1055077005/article/details/100152102
楼上的答案中https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/video/-m1RzLMiaJHiHvnuIWFwc#comment-pZ2avf6zp6ckXO3GQ1zQP 也有相关的内容 可以作为补充,后面关于softmax函数的可以参考下这篇文章https://blog.csdn.net/bitcarmanlee/article/details/82320853

多层感知机

多层感知机中最为重要的自然是“多层”,多层中涉及到的隐藏层的目的是为了将线性的神经网络复杂化,更加有效的逼近满足条件的任何一个函数。
因此文中先证明了一个最常见的思路,即两个线性层复合,是不可行的,无论多少层线性层复合最后得到的结果仍然是等价于线性层。这个结果的逻辑来自与线性代数中,H=XW+b 是一个仿射变换,通过W变换和b平移,而O=HW2+b2 则是通过W2变换和b2平移,最终经过矩阵的乘法和加法的运算法则(分配律)得到最终仍然是对X的仿射变换。
在线性层复合不行的情况下,最容易想到的思路就是将隐藏层变成非线性的,即通过一个“激励函数”将隐藏层的输出改变。
因此这里主要讨论一下,为什么添加激励函数后可以拟合“几乎”任意一个函数。
将函数分成三类:逻辑函数,分类函数,连续函数(分类的原则是输入输出形式)
1.通过一个激励函数可以完成简单的或与非门逻辑,因此通过隐藏层中神经元复合就可以完成任何一个逻辑函数拟合。只需要通过神经网络的拟合将真值表完全表示
2.通过之前使用的线性分类器构成的线性边界进行复合便可以得到任意一个分类函数。
3.通过积分微元法的思想可以拟合任何一个普通的可积函数

在深度学习中主要关注多层模型,线性回归和softmax回归是单层神经网络,多层感知机是多层神经网络。

隐藏层
   多层感知机在单层神经网络的基础上引入了一道多的隐藏层,位于输入层和输出层之间。

其中含有一个隐藏层,该层中有5个隐藏单元。输入层不涉及计算,所以图所示感知机层数为2.隐藏层中的神经元和输入层中的各个输入完全链接,输出层中的神经元和隐藏层中的各个神经元也完全连接,因此,多层感知机中的隐藏层和输出层都是全连接层。
由式子看出,神经网络引入了隐藏层,依然等价于一个单层神经网络:其中输出权重参数为WhWo,偏差参数为bhWo + bo

激活函数

多层感知机
  含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过记过函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例:H =Ø(XWh + bh), O = HWo +bo ,

多层感知机在输出层与输入层之间加入了一个或多个全连接隐藏层,并通过激活函数对隐藏层输出进行变换。
常用的激活函数包括ReLU函数、sigmoid函数和tanh函数。

非线性激活函数也是为了增强神经网络的拟合能力,只不过每种激活函数都有自己的特点,没有绝对完美的选择。
对于非线性激活函数来说,只要是选择的函数满足可导性和单调性等激活函数的基本性质,就可以作为激活函数使用,只不过是选择这个激活函数后网络的和效果好坏罢了。

提问
  • ReLu激活函数为什么没有梯度消失问题,当输入<=0时,梯度不也变成0了么?
    relu有一个变种,就是在x<0时候加上很小很小的数,让他拥有斜率,不会杀死神经元,并且影响很小。 ReLU在x<0部分会有梯度消失问题,但只有一半;所以后续有Leaky ReLU来缓解这个问题,但是相比于sigmoid梯度最大值0.25并且大部分区域都非常小,ReLU只有一半区域还是缓解很多
    sigmoid的梯度消失是指输入值特别大或者特别小的时候求出来的梯度特别小,当网络较深,反向传播时梯度一乘就没有了,这是sigmoid函数的饱和特性导致的。ReLU没有梯度消失是因为用了max函数,对大于0的输入直接给1的梯度,对小于0的输入则不管。
    sigmoid的梯度消失是指输入值特别大或者特别小的时候求出来的梯度特别小,当网络较深,反向传播时梯度一乘就没有了,这是sigmoid函数的饱和特性导致的。ReLU在一定程度上优化了这个问题是因为用了max函数,对大于0的输入直接给1的梯度,对小于0的输入则不管。
    但是ReLU存在将神经元杀死的可能性,这和他输入小于0那部分梯度为0有关,当学习率特别大,对于有的输入在参数更新时可能会让某些神经元直接失活,以后遇到什么样的输入输出都是0,Leaky ReLU输入小于0的部分用很小的斜率,有助于缓解这个问题。

  • torch.max(input=X, other=torch.tensor(0.0)) 是啥子意思呢
    和常规的max(a,b)函数一样–>X就是a, torch.tensor(0.0) 就是b.
    所以这行代码的意思就是将X中的元素逐个与0比较, 将比较大的值返回. 也就是ReLU函数了呗
    X=tensor([1, 0.5, -1]), 得到的结果就是 tensor([1, 0.5, 0])

  • 为什么选择的激活函数普遍具有梯度消失的特点?
    开始的时候我一直好奇为什么选择的激活函数普遍具有梯度消失的特点,这样不就让部分神经元失活使最后结果出问题吗?后来看到一篇文章的描述才发现,正是因为模拟人脑的生物神经网络的方法。在2001年有研究表明生物脑的神经元工作具有稀疏性,这样可以节约尽可能多的能量,据研究,只有大约1%-4%的神经元被激活参与,绝大多数情况下,神经元是处于抑制状态的,因此ReLu函数反而是更加优秀的近似生物激活函数。所以第一个问题,抑制现象是必须发生的,这样能更好的拟合特征。

  • 那么自然也引申出了第二个问题,为什么sigmoid函数这类函数不行?
    1.中间部分梯度值过小(最大只有0.25)因此即使在中间部分也没有办法明显的激活,反而会在多层中失活,表现非常不好。
    2.指数运算在计算中过于复杂,不利于运算,反而ReLu函数用最简单的梯度
    在第二条解决之后,我们来看看ReLu函数所遇到的问题,
    1.在负向部分完全失活,如果选择的超参数不好等情况,可能会出现过多神经元失活,从而整个网络死亡。
    2.ReLu函数不是zero-centered,即激活函数输出的总是非负值,而gradient也是非负值,在back propagate情况下总会得到与输入x相同的结果,同正或者同负,因此收敛会显著受到影响,一些要减小的参数和要增加的参数会受到捆绑限制。
    这两个问题的解决方法分别是
    1.如果出现神经元失活的情况,可以选择调整超参数或者换成Leaky ReLu 但是,没有证据证明任何情况下都是Leaky-ReLu好
    2.针对非zero-centered情况,可以选择用minibatch gradient decent 通过batch里面的正负调整,或者使用ELU(Exponential Linear Units)但是同样具有计算量过大的情况,同样没有证据ELU总是优于ReLU。
    所以绝大多数情况下建议使用ReLu。

Task02:文本预处理;语言模型;循环神经网络基础(1天)

文本预处理;

文本预处理步骤:
1.读取文本 2.分词 3.构建字典,将每个词映射到一个唯一的索引(index)
将文本从词的序列转换为索引的序列,方便输入模型
无论 use_special_token 参数是否为真,都会使用特殊token——,用来表示未登录词。
  句子长度统计与构建字典无关

Vocab字典构建步骤:
1.统计词频,去重筛选掉低频词
2.根据需求添加特殊的token
3.建立字典,将每个token建立映射到唯一的索引
4.建立索引到token的映射

视频的讲解是处理英文文本。
而在中国处理的最多是中文文本,但是中文文本相对英文文本存在了大量的问题。
对于自己所做的短文本的推进系统中,总结了这些中文文本存在的问题(如果大家遇到别中文文本的问题希望补充):

  1. 中文存在词的语义比英文更为复杂
  2. 词汇少
  3. 特征稀释
  4. 分类精确率低
    并且在中文的文本的预处理中,个人觉得还需要做不同停用词表和无关词表。
    其中视频中lines = [re.sub(’[^a-z]+’, ’ ‘, line.strip().lower()) for line in f]可以看做为停用词的处理。
    而无关词即是对场景无关紧要之词,相当于我们日常所说的废话(但并不是人为觉得的)。
    视频中提及的分词工具可以说目前也是相对英文,对于中文的分词工具目前大多数使用的是jieba
    我觉得lines = [re.sub(’[^a-z]+’, ’ ', line.strip().lower()) for line in f]主要还是为了后续的分词,要处理停止词(stop words),恐怕还是得再做专门的处理,就我所知,不管是英文还是中文,都会有一些停止词库,nltk和spacy应该都有。不同的现实场景有不同的预处理方法确实有道理,比如做机翻,应该不会考虑筛掉停止词,但如果是做问答,可能有时候还是需要处理一下停止词。
    它这句话是把非英文的词语去掉,其中就已经包括停用词了。
    只不过是不完整的,停用词包括那些特殊的符号

建立词典:
词典的主要作用是将每一个词映射到一个唯一的索引号,主要构建了一个idx_to_token列表来存储所有的词,一个token_to_idx来存储所有词的索引。
在实现的的流程上是:
对语料进行分词,生成一个token列表,里面包含了语料的分词结果
对分好的词统计词频,然后根据词频来构建词典(统计好的词频完成了去重的操作,同时也保留了词的频率,方便后续的操作)
其中有一些名词的作用是视频里提出来的
pad的作用是在采用批量样本训练时,对于长度不同的样本(句子),对于短的样本采用pad进行填充,使得每个样本的长度是一致的
bos( begin of sentence)和eos(end of sentence)是用来表示一句话的开始和结尾
unk(unknow)的作用是,处理遇到从未出现在预料库的词时都统一认为是unknow ,在代码中还可以将一些频率特别低的词也归为这一类

提问

求教:def getitem(self, tokens):“定义Vacab的索引,从词到索引的映射”,意思是给词返回索引吗?这个函数应该怎么调用啊
我们在构建字典的时候是建立的双映射,如果采取单映射的方式,即只依赖列表的映射机制,会产生什么变化

语言模型

序列模型的采样

随机采样和相邻采样原理图 类似传统数据的随机采样和分层采样

视频中的参数空间过大同样是中文文本处理的一个问题,自己遇到过,但是不知道怎么用专业语言表达。
看到视频的讲解知道这个问题叫做参数空间过大,但遗憾的是视频中并没有说明如果解决这一问题。
自己目前的处理方法是通过关联规则来找出关联度并且词频高的词,往往这些词是无关词即视频所说的停滞词。
关联规则多用于推荐系统,但是目前大部分的推荐都是推荐关联度高的信息。 但是可以反向使用关联规则来帮助我们找出停滞词(无关词)

按照时间序列取样的描述,是不能够取最后一组的,即必须在末尾留下一个字符。但是在相临取样的时候,最后indices…shape[1]-1的操作便预留出了最后一个字符的位置,从而避免取到结尾

gram模型的缺点:
参数系数,参数空间过大,存储和内存吃不消
没有考虑文本中出现过多的废话词,可以用tf-idf优化
没有考虑久远之前出现的词语对现在的影响,可以用bigram、trigram等词袋模型解决

提问
  • 请问文件中
    char_to_idx = {char: i for i,char in enumerate(idx_to_char)}
    vocab_size = len(char_to_idx)
    print(char_to_idx,idx_to_char)
    将每个字符转化为索引,得到一个索引的序列
    corpus_indices = [char_to_idx[char] for char in corpus_char]
    sample = corpus_indices[:20]
    print(‘char:’,’’.join([idx_to_char[idx] for idx in sample]))
    print(‘indices’,sample)
    这段做了什么工作?这句corpus_indices = [char_to_idx[char] for char in corpus_char]
    怎么理解?
    主要是为了形成char_to_idx,idx_to_char 把索引和字符对应起来
    corpus_indices = [char_to_idx[char] for char in corpus_char]
    这段中corpus_char包含了一个char的list
    char_to_idx是一个字典 形式是[(字符,索引号)]
    而for char in corpus_char 表示把 corpus_char中的字符一个一个取出,然后将每个char对应的char_to_idx字典中的索引号取出,构成一段索引叫corpus_indices
    比如这段代码中idx_to_char = [ ‘我’, ‘是’] char_to_idx = [(‘我’,0), (‘是’,1)] 那corpus_char[‘我’] = 0 而corpus_indices = [0,1]

循环神经网络基础

模型参数
W_xh: 状态-输入权重
W_hh: 状态-状态权重
W_hq: 状态-输出权重
b_h: 隐藏层的偏置
b_q: 输出层的偏置
循环神经网络的参数就是上述的三个权重和两个偏置,并且在沿着时间训练(参数的更新),参数的数量没有发生变化,仅仅是上述的参数的值在更新。循环神经网络可以看作是沿着时间维度上的权值共享
在卷积神经网络中,一个卷积核通过在特征图上滑动进行卷积,是空间维度的权值共享。在卷积神经网络中通过控制特征图的数量来控制每一层模型的复杂度,而循环神经网络是通过控制W_xh和W_hh中h的维度来控制模型的复杂度。
一个batch的数据的表示
如何将一个batch的数据转换成时间步数个(批量大小,词典大小)的矩阵?
每个字符都是一个词典大小的向量,每个样本是时间步数个序列,每个batch是批量大小个样本
第一个(批量大小,词典大小)的矩阵:取出一个批量样本中每个序列的第一个字符,并将每个字符展开成词典大小的向量,就形成了第一个时间步所表示的矩阵
第二个(批量大小,词典大小)的矩阵:取出一个批量样本中每个序列的第二个字符,并将每个字符展开成词典大小的向量,就形成了第二个时间步所表示的矩阵
最后就形成了时间步个(批量大小,词典大小)的矩阵,这也就是每个batch最后的形式
隐藏状态的初始化
随机采样时:每次迭代都需要重新初始化隐藏状态(每个epoch有很多词迭代,每次迭代都需要进行初始化,因为对于随机采样的样本中只有一个批量内的数据是连续的)
相邻采样时:如果是相邻采样,则说明前后两个batch的数据是连续的,所以在训练每个batch的时候只需要更新一次(也就是说模型在一个epoch中的迭代不需要重新初始化隐藏状态)

现在来理解一下循环神经网络使用相邻采样的时候为什么要detach参数。
我们知道相邻采样的前后两个批量的数据在在时间步上是连续的,所以模型会使用上一个批量的隐藏
状态初始化当前的隐藏状态,表现形式就是不需要在一个epoch的每次迭代时随机初始化隐藏状态,那么
根据上面所说的。假如没有detach的操作,每次迭代之后的输出是一个叶子节点,并且该叶子节点的
requires_grad = True(从上面的计算图就可以看出),也就意味着两次或者说多次的迭代,计算图一直都是连着
的,因为没有遇到梯度计算的结束位置,这样将会一直持续到下一次隐藏状态的初始化。所以这将会导致
计算图非常的大,进而导致计算开销非常大。而每次将参数detach出来,其实就是相当于每次迭代之后虽然是
使用上一次迭代的隐藏状态,只不过我们希望重新开始,具体的操作就是把上一次的输出节点的参数requires_grad
设置为False的叶子节点。

我们知道循环神经网络的梯度反向传播是沿着时间进行反向传播的,而时间是不会停止的,所以我们会每隔一段时间、
进行一次反向传播,而我们这里的一段时间其实指的就是时间步,我们希望每间隔时间步之后进行一次反向传播,这样
来减小在梯度反向传播时带来的计算开销以及一定程度上缓解梯度消失或者爆炸的问题

以上是我对循环神经网络使用相邻采样为什么要detach参数的理解,可能也有问题,如果有问题希望可以指出来

当我们再训练网络的时候可能希望保持一部分的网络参数不变,只对其中一部分的参数进行调整;或者值训练部分分支网络,并不让其梯度对主网络的梯度造成影响,这时候我们就需要使用detach()函数来切断一些分支的反向传播
detach():
返回一个新的Variable,从当前计算图中分离下来的,但是仍指向原变量的存放位置,不同之处只是requires_grad为false,得到的这个Variable永远不需要计算其梯度,不具有grad。
即使之后重新将它的requires_grad置为true,它也不会具有梯度grad
.这样我们就会继续使用这个新的Variable进行计算,后面当我们进行反向传播时,到该调用detach()的Variable就会停止,不能再继续向前进行传播
detach_()
将一个Variable从创建它的图中分离,并把它设置成叶子variable
.其实就相当于变量之间的关系本来是x -> m -> y,这里的叶子variable是x,但是这个时候对m进行了.detach_()操作,其实就是进行了两个操作:

1.将m的grad_fn的值设置为None,这样m就不会再与前一个节点x关联,这里的关系就会变成x, m -> y,此时的m就变成了叶子结点。
2.然后会将m的requires_grad设置为False,这样对y进行backward()时就不会求m的梯度。

其实detach()和detach_()很像,两个的区别就是detach_()是对本身的更改,detach()则是生成了一个新的variable
。比如x -> m -> y中如果对m进行detach(),后面如果反悔想还是对原来的计算图进行操作还是可以的
。但是如果是进行了detach_(),那么原来的计算图也发生了变化,就不能反悔了。

提问

请问 在相邻采样中 detach掉的是哪个参数的梯度呢
模型所有参数的梯度

相邻采样为什么在每次迭代之前都需要将参数detach
是因为pytorch动态计算图构建的原因,这里主要结合本次的视频内容(循环神经网络使用相邻采样时)

定义模型RNN中的state维护一些状态,都是维护哪些状态,这些状态具体什么作用和效果呢?
对于RNN来讲,就是隐藏状态H,因为RNN在计算H_{t}时会用到H_{t-1},所以需要维护这个状态

如果分离出来意思是不对state更新?
.detach_()会对计算图进行更新而.detach()不会。.detach() 从当前计算图中分离下来的,但是仍指向原变量的存放位置。所以计算图不变。.detach_():将一个Variable从创建它的图中分离,并把它设置成叶子variable.其实就相当于变量之间的关系本来是x -> m -> y,变成了关系x, m -> y,此时的m就变成了叶子结点。这里计算图就改变了。

  • 所有的隐藏层共用相同的参数吗?所有的输出层也共用相同的参数吗?
    是的 所以叫循环神经网络

  • detach了state的参数 Wnh Whh b_h就不计算梯度更新了吗 ?只更新输出层的Whq和b_q吗?
    比如是batch 是10 num_steps是2
    那就是说在t=1初始化h0 h1 = XWnh+h0 x Whh +b_h ,Y1= h1 x Whq + b_q
    在t=2 沿用上一个计算出的h1 那么h2 = h1 x Whh+XWnh +b_h ,Y2= h2 x Whq+b_q

  • 这样一轮batch结束了再更新 Whq和b_q?
    我想知道这里detach了什么 哪一个变量不用计算梯度?
    detach了隐藏状态H。采用相邻采样的时候,当前这个batch的H来自上一个batch,如果没有在这个batch开始的时候把H(也就是H_{0})从计算图当中分离出来,H的梯度的计算依赖于上一个batch的序列,而这一个过程会一直传递到最初的batch,所以随着batch的增加,计算H梯度的时间会越来越长。在batch开始的时候把H从计算图当中分离了,那就相当于是把上一个batch结束时的H的值作为当前batch的H的初始值,这个时候H是一个叶子,最后这个batch结束的时候,对H的梯度只会算到当前这个batch的序列起始处。

课外学习/参考链接

理解的课件
https://www.bilibili.com/video/av86713932?p=8
对多层感知器MLP的理解:
https://www.bilibili.com/video/av86713932?p=4
课件摘自斯坦福大学CS231N计算机视觉公开课,课件里有中文批注和讲解
参考链接:https://zhuanlan.zhihu.com/p/79801410

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值