第十二周周报
- 摘要
- Abstract
- 机器学习
- 1. Recurrent Neural Network(下)
- 1.1 RNN的Loss Function怎么求?
- 1.2 RNN奇怪的特性
- 1.3 如何解决 RNN 梯度消失或者爆炸
- 1.4 RNN 其他应用
- Pytorch学习
- 1. 现有的网络模型使用以及其修改
- 1.1 在VGG16模型添加Module
- 1.2 在VGG16模型修改Module
- 拓展学习
- 1. 高斯(正态)分布的推导
- 总结
摘要
本周主要继续对RNN的更深层次内容进行了学习,例如学习了RNN的是如何train的、RNN特殊的性质以及如何解决RNN的特殊性质引发的vanish gradient问题,最后对RNN的应用进行了总结。此外,本周还继续对Pytorch进行了学习,主要学习了如何将一个数据集训练的模型通过调参应用到另一数据集中。最后,还对高斯分布的数学推导过程进行了研究学习。
Abstract
This week, deeper aspects of RNNs were studied, including how RNNs are trained, their unique properties, and methods to address the vanishing gradient problem associated with these properties. Additionally, a summary of RNN applications was provided. Furthermore, learning on PyTorch focused on how models trained on one dataset can be applied to another through parameter tuning. Lastly, the mathematical derivation process of the Gaussian distribution was explored.
机器学习
1. Recurrent Neural Network(下)
本周继续在学习RNN前,先复习一下上一周学习的LSTM
1.1 RNN的Loss Function怎么求?
如果我们要使用RNN进行train的话,就要使用Loss Function来判断参数的好坏,然后不断地优化参数使得其Loss最小。
那在RNN中如何定义它的Loss function呢?
我们可以通过计算每个slot 与 对应input 的 cross entropy 之和来查看其Loss
如下图所示:
比如,x1 中 的输出y1 会与 reference vector 算出一个cross entropy。
如果我们现在丢进去的是arrive,那y1的referenced vector是对应的 other 那个 slot 的 dimension value 是1,其他为0。
reference vector的长度就是slot的数目。
比如说你定的40个slot,那这个reference vector的dimension就是40。
又比如我们把Taipei作为input,就需要和destination 的 slot 对比,此时destination的slot 的 dimension value为1其他为0。
但是我们不能把x2与x1单独分开来,x2必须要在x1之后输入。
按照上述的方法,把y1 到yn与其对应的slot的所有的cross entropy加起来,就得到了我们要去minimize的对象(即得到了我们的Loss Function)
接下来我们还是使用gradient descent中的Back-propagation来优化我们的参数。
不过对于RNN中,对Back Propagation进行了优化,优化为了BPTT(Back-Propagation Through Time)
BPTT(Back-Propagation Through Time)算法是一种用于训练循环神经网络(RNN)的方法
它通过时间维度反向传播误差,以考虑序列数据中的时间相关性。
BPTT算法在每个时间步长内叠加所有对应权重的梯度,与传统的反向传播算法不同,它引入了时间维度,并考虑了序列数据中的时序关系。
这种算法允许RNN更好地捕捉到序列数据中的时间依赖性和上下文信息,从而提高了处理序列数据的性能。
1.2 RNN奇怪的特性
不幸的就是RNN的training是比较困难的
如下图所示:
为什么会出现上图这种情况呢?
我们第一反应肯定是我们的function出现了问题
但是这是一个正常现象
因为有专业人士分析过其loss的error surface对参数的变化是非常的敏感,也就是其图像非常崎跷的。
意思是说error surface,它有一些地方非常平坦,有一些地方非常的陡峭。
以下是这个error surface示意图
纵轴是total loss。
x轴跟y轴代表两个参数w1和w2。
这两个参数对total lost的影响在很多地方非常平坦,然而在某些地方就非常的陡峭。
出现种情况可能会对我们的train造成一些问题
如果在update的时候,下一个点正好在悬崖上就会参数就会直接起飞。
为什么会这样呢?
因为当点踩在悬崖上,因为在悬崖上的gradient会很大。然后之前的gradient都很小,所以会突然打你个措手不及。然后你可能把learning rate调的比较大,但是突然很大的gradient再乘上很大的learning rate结果参数就update很多,然后整个参数就飞出去了。
之后有人通过Clipping(剪裁)的方式去解决了这个难题
Clipping的意思就是当gradient大于某一个threhold(门槛)的时候就让gradient不要让它超过那个值
就比如:当gradient大于15的时候,就等于15结束。
使用了Clipping后,gradient会在一个可控的值,所以就算在悬崖边上,gradient也不会剧增,所以其可以继续的做RNN的train。
那为什么RNN会有这种奇怪的特性呢?
我们拿一个很简单的RNN network举例子
其network具体情况如下:
activation function都是linear的,且没有bias。
输入只有第一时间是1,其余都是0
如下图所示:
当y1000的时候输出的就是w999(因为当y2输出的是w;y3输出的是w2)
那么关键的地方来了:、
当w = 1 时候 ,y1000 = 1
当w = 1.01时候,y1000 ≈ 20000
这突然暴增的gradient(表现在error surface中就比较陡峭)需要我们调一个比较小的η。
但是
当w = 0.99的时候,y1000 ≈ 0
当w = 0.01的时候,y1000 ≈ 0
这时候gradient很小(表现在error surface中就是平坦),需要我们调一个比较大的η。
总结起来就是RNN出现这样的问题就是
RNN从memory接到neuron的那一组weight在不同的时间点被反复使用,从而导致不断叠加。
所以这个w只要一有变化,它有可能完全没有造成任何影响,但像上诉的情况,一旦它可以造成影响,那影响都会是天崩地裂。所以它有时候gradient很大,有时候gradient很小。
1.3 如何解决 RNN 梯度消失或者爆炸
那有什么样的技巧可以帮助我们解决这个问题呢?
其实现在最广泛被使用的技巧就是LSTM
其原理就它可以把那些比较平坦的地方去除掉,留下一些崎岖的地方。
这样下来我们就只有崎岖的地方(即gradient变化比较大),所以我们在LSTM下就可以使用一个很小的learning rate去train。
为什么LSTM可以处理gradient特别小(gradient vanish)的问题呢?
这也是某国际大厂的面试题,我们可以对其进行学习一下
RNN跟LSTM,他们在面对memory的处理的操作其实是不一样的
通过之前的学习,我们可以发现
RNN在处理memory的过程中,在每一个时间点neuron的output都要被放到memory里面去,所以每一个在每一个时间点memory里面的信息都会被覆盖掉。
而LSTM引入了memory cell和一些gate之后,它是把原来memory里面的值乘上一个值,再把input的值加起来,放到里memory里面。
LSTM和RNN不同的地方是,如果weight可以影响到memory里面的值的话,这个影响会永远都存在(除非forget gate被使用,导致memory cell的值清零,否则在memory有改变的时候,每一次都只会有新的东西加进来,而不会把原来存在memory里面的值呢洗掉)。
不像RNN在每一个时间点值都会被forget掉。
所以其不会有gradient vanish的问题
那我们可能有个疑问:
可是有forge gate把过去存的值洗掉啊,但事实上在LSTM的第一个版本,其实就是为了解决gradient vanishing的问题,所以他是没有forget gate的。这是后来才加上去的。
那甚至现在有个传言是你在训练LSTM的时候,你要给特别大的bias,你要确保否forget gate在多数的情况下都是开启的,只有少数的情况它会被forget的。
除此之外,还有另外一个版本用gate操控这个memory的cell
叫做:Gate Recurrent Unit(GRU)
它的这个gate只有两个,所以它需要的参数量是比较少的
所以如果你今天在trian LSTM的时候,你觉得overfitting的情况很严重,你可以试一下用GRU去train。
GRU会把input gate跟for gate联动起来。
也就是说当input gate被打开的时候,forget gate就会自动关闭。
当你forget gate打开的时候就会要format存在memory里面的值。
当forget gate没有要format时候,input gate就会被关起来
(即要把存在memory里面的值清掉,才可以把新的值呢放进来)
其实还有很多其他的technique,是来handle规定vanishing这一个问题。
比如说Clockwise RNN或者是structurally constrained Recurrent Network(SCRN)等等
有很多的vacation在我们前面举的那个slug feeling的例子里面,我们是假设input和output element的数目是一样多的。也就是说if有几个word,我们就给每一个word,一个这个slot的label。
论文“A Simple Way to Initialize Recurrent Networks of Rectified Linear Units”采用了不同的做法。
一般的 RNN 用单位矩阵(identity matrix)来初始化转移权重,与 ReLU 激活函数配合可以得到很好的性能。
如果用一般训练的方法随机初始化权重,ReLU 跟 sigmoid 函数来比的话,sigmoid 性能会比较好。
但是使用了单位矩阵,这时候用 ReLU 性能会比较好。
identity matrices(单位矩阵)
矩阵里面的元素,从左上角到右下角都是1,剩余的部分都是0。
如下所示:
[
1
0
0
0
1
0
0
0
1
]
{\color{Red} \begin{bmatrix}1& 0 &0 \\0& 1 &0 \\ 0& 0 &1\end{bmatrix}}
100010001
1.4 RNN 其他应用
slot filling的例子中,假设输入跟输出的数量是一样的(也就是说输入有几个单词,我们就给
每一个单词槽标签)
但是RNN 可以做到更复杂的事情
一、多对一序列
比如输入是一个序列,输出是一个向量。
1.情感分析(sentiment analysis)是典型的应用
某家公司想要知道,他们的产品在网上的评价是正面的还是负面的。可以用一个机器学习的方法学习一个分类器(classifier)来判断文档的正、负面。
情感分析就是给机器看很多的文章,机器要自动判断哪些文章是正类,哪些文章是负类。
2.用 RNN 来作关键术语抽取(key term extraction)
关键术语抽取:给机器看一个文章,机器要预测出这篇文章有哪些关键单词。
如果能够收集到一些训练数据(比如:一些文档,这些文档都有标签,哪些单词是对应的,那就可以直接训练一个 RNN)
这个 RNN 把文档当做输入,通过嵌入层(embedding layer),用 RNN 把这个文档读一次,把出现在最后一个时间点的输出拿过来做注意力,可以把这样的信息抽出来再丢到前馈神经网络得到最后的输出。
二、多对多序列
RNN 也可以处理多对多的问题,比如输入和输出都是序列,但输出序列比输入序列短。
1.语音识别
输入是声音序列,一句话就是一段声音信号。
一般处理声音信号的方式就是在这个声音信号里面,每隔一小段时间,就把它用vector来表示。(当然这一小段时间是非常短的,可能只有0.01秒)
输出的是字符序列
假如中文的语音识别的话,那输出目标理论上就是这个世界上所有可能中文的单词,常用的可能是8000个,那么RNN 分类器(classifier)的数量可能就是8000个。、
虽然很大,但也是没有办法做。但是充其量只能做到说:每一个向量属于一个字符。
每一个输入对应的时间间隔是很小的(0.01 秒),所以通常是好多个向量对应到同一个字符。
下图中如果不加修改,识别结果为“好好好棒棒棒棒棒”
但上面这不是语音识别的结果。,因为其中会进行修剪(trimming)
(即把重复的东西拿掉,就变成“好棒”(褒义)。这样会有一个严重的问题,因为它没有识别“好棒棒”(贬义))
需要把“好棒”跟“好棒棒”分开来,怎么办,有一招叫做 :
CTC(在输出时候,不只是输出所有中文的字符,还可以输出一个符号”null”,其代表没有任何东西。)
所以输入一段声音的序列,它的输出是“好 null null 棒 null null null null”
然后把“null”的部分拿掉,它就变成“好棒”。
如果我们输入另外一个序列,它的输出是“好 null null 棒 null 棒 nullnull”,然后把“null”拿掉,所以它的输出就是“好棒棒”。
这样就可以解决叠字的问题了。
如下图示:
可能第一个是“好 null 棒null null null”
可能是“好 null null 棒 null null”
可能是“好 null null null 棒 null”
假设以上情况都是正确的
CTC 怎么做训练呢?
CTC 在做训练的时候,手上的训练数据就会告诉我们说,这一串声音特征对应到这一串字符序列,但它不会告诉我们说“好”是对应第几个字符到第几个字符。
简单来说,我们不知道“好”对应到那几个字符,“棒”对应到哪几个字符。
所以我们要穷举所有可能性
假设我们所有的状况都是可能的。
如下图所示:
如果我们看到输出是如下图所示:
最后把“null”的地方拿掉,这句话的识别结果就是“HIS FRIEND’S”。
我们不需要告诉机器说:”HIS”是一个单词,“FRIEND’s”是一个单词, 机器通过训练数据会自己学到。
所有如果用 CTC来做语音识别,就算是有某一个单词在训练数据中从来没有出现过,机器也是有机会把它识别出来。
三、序列到序列(Sequence-to-Sequence,Seq2Seq)
在序列到序列学习里面,RNN 的输入跟输出都是序列 (但是两者的长度是不一样的)
在CTC时输入比较长,输出比较短。
Seq2Seq要考虑的是不确定输入跟输出谁比较长谁比较短。
1.机器翻译
输入英文单词序列把它翻译成中文的字符序列。(因为英文和中文序列的长短是未知的)
假如input为机器学习,然后用 RNN 阅读
在最后一个时间点,这个记忆元里面就存了所有输入序列的信息
我们让机器吐一个字符(“机”),就让它输出下一个字符,把之前的输出出来的字符当做输入
再把记忆元里面的值读进来,它就会输出“器”。下一个时间点输出“学”,然后输出“习”,然后一直输出下去
要怎么阻止让它产生单词呢?
要多加一个符号“断”(类似于停止符号)
所以机器的输出不是只有字符,它还有一个可能输出“断”。
如果“习”后面是符号“===”(断)的话,就停下来了
S2S的序列学习,假设做翻译:原来是输入某种语言的文字,翻译成另外一种语言的文字。
有没有可能直接输入某种语言的声音信号,输出另外一种语言的文字呢?
我们直接把英文的声音信号丢到这个模型里面去,看它能不能输出正确的中文。
然而这是行得通的!
假设要把粤语转成英文只需要收集粤语的声音信号跟它的英文翻译就可以了,不需要粤语语音识别的结果,也不需要知道粤语的文字。
2.句法解析(syntactic parsing)
句法解析:让机器看一个句子,得到句子结构树。
序列到序列的技术也被用到句法解析(syntactic parsing)
如图所示:
只要把树状图描述成一个序列,比如:“John has a dog.”
S2S学习直接学习一个序列到序列模型,其输出直接就是句法解析树,这个是可以训练的起来的。LSTM 的输出的序列也是符合文法结构,左、右括号都有。
要将一个文档表示成一个向量往往会用词袋(Bag-of-Words,BoW)的方法
用词袋这个方法的时候,往往会忽略掉单词顺序信息。
举例来说,有一个单词序列是“white blood cells destroying an infection”
另外一个单词序列是:“an infection destroying white blood cells”
这两句话的意思完全是相反的。但是我们用词袋的方法来描述的话,他们的词袋完全是一样的。
它们里面有完全一摸一样的六个单词,但是单词的顺序是不一样的,所以他们的意思一个变成正面的,一个变成负面的。
可以用序列到序列自编码器这种做法来考虑单词序列顺序的情况下,把一个文档变成一个向量
Pytorch学习
1. 现有的网络模型使用以及其修改
因为之前我们一直使用的都是CIFAR-10数据集,现在我们就来学习其他的数据集——VGG
VGG 模型基于 "用于大规模图像识别的深度卷积网络 "论文。
https://pytorch.org/vision/stable/models/vgg.html
其参数说明如下:
如果我们要使用预训练的ImageNet,就要查看一个ImageNet这个数据集
ImageNet数据集介绍:
由于scipy在PyCharm上下载的很慢,所以我们可以在清华的镜像网站上把其whl文件下载下来,再导入到PyCharm中。
接下来演示一下如何将whl文件导入到PyCharm中:
1、先下载whl文件
scipy的whl下载链接(点击进入)
2、下载完毕后,右键该文件,点击复制文件地址(我的是windows 11)
3、在PyCharm中打开terminal,输入如下代码:
双引号里面的内容就是我们刚刚我们刚刚复制的文件地址
pip install "E:\13101\Documents\Edge Downloads\scipy-1.5.4-cp36-cp36m-win_amd64.whl"
4、安装完成后,输入如下代码验证是否安装成功
pip list
可以看到,scipy已经安装完成
由于ImageNet中,不支持在Pycharm里面的控制台下载,于是我们自己去寻找。
其官网的介绍如下:
但是其数据集太大,足足有147.9个G
所以我们选择使用其预训练好的VGG模型来应用在CIFAR-10数据集中。
接下来我们用以下代码来演示预训练好的VGG与初始化VGG参数的差别
import torchvision
import os
os.environ["TORCH_HOME"] = "E:/DeepLearning/pycharm download"
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print('ok')
在print(‘ok’)进行一个断点,然后进行debug
预训练好(在ImageNet数据集训练好的)的某一层weight参数如下:
初始化的某一层weight参数如下:
很明显,它们之间的参数存在着区别。
VGG的网络架构如下:
import torchvision
import os
os.environ["TORCH_HOME"] = "E:/DeepLearning/pycharm download"
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
我刚刚了解到VGG是基于ImageNet训练的,但是在ImageNet中含有1000个类别,但是我们要将VGG应用到CIFAR-10数据集中,该数据集只有10个类别。
那么我们需要将其用在CIFAR-10中要怎么应用呢?
如下图所示
总体的思路比较类似于调参
通过VGG16前置网络来提取图片的一些特征,然后再通过修改classifier参数来完成一些功能。
1.1 在VGG16模型添加Module
import torchvision
import os
from torch import nn
os.environ["TORCH_HOME"] = "E:/DeepLearning/pycharm download"
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
datasets = torchvision.datasets.CIFAR10(root='./datasets', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
vgg16_true.add_module('add_linear',nn.Linear(in_features=1000, out_features=10))
print(vgg16_true)
可以看到Linear已经添加成功了
如果我们想加入到classifier里面用如下代码
import torchvision
import os
from torch import nn
# 修改VGG16模型参数的下载路径
os.environ["TORCH_HOME"] = "E:/DeepLearning/pycharm download"
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
datasets = torchvision.datasets.CIFAR10(root='./datasets', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
# 为VGG16的classifier中添加一个线性层
vgg16_true.classifier.add_module('add_linear',nn.Linear(in_features=1000, out_features=10))
print(vgg16_true)
1.2 在VGG16模型修改Module
如果我们只是单纯的想要修改VGG16里面的参数,而不想添加要如何操作呢?
import torchvision
import os
from torch import nn
# 修改VGG16模型参数的下载路径
os.environ["TORCH_HOME"] = "E:/DeepLearning/pycharm download"
vgg16_false = torchvision.models.vgg16(pretrained=False)
datasets = torchvision.datasets.CIFAR10(root='./datasets', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
print(vgg16_false)
# 修改VGG16_false的Linear
vgg16_false.classifier[6] = nn.Linear(in_features=1000, out_features=10)
print(vgg16_false)
可以看到,参数已经被修改成功
拓展学习
1. 高斯(正态)分布的推导
总结
本周因为课程繁多,加上刚开学需要处理其他的事情,所以学习的内容和速度明显降低。但是还是按照预期完成了上一周计划定制的内容。
本周在机器学习的理论部分,继续对RNN这个网络模型进行了更加深入的探讨,其中学会了RNN是如何训练的,包括求RNN的Loss function(拿slot filling案例来说明,计算每个slot的word于output的cross entropy之和),此外还了解了RNN的奇怪特性,就是其error surface是崎岖的(忽高忽低),导致我们在使用RNN train的时候,gradient会突然暴增和骤降使我们不好调制η。面对这一情况,我们后续引入了LSTM的计算机制去解决这个问题,即通过解决gradient vanish来使η保持一个比较小的值去训练。最后还学些了RNN的其他应用,分为输入输出分别为多对一序列(情感分析、关键术语提取)、多对多序列(语音识别,其中引入了CTC解决了叠字语义的问题)、序列到序列(机器翻译)的情况来进行实际应用。
此外,在代码实战环节中主要在Pytorch中使用了VGG16模型(使用ImageNet训练)通过修改和添加模型参数处理CIFAR-10数据集。学会了如何修改网络模型
最后,我决定每一周添加一些数学方面的学习模块,学习深度学习中一些数学理论的推导,为以后的论文中为模型进行数学方面的解释做铺垫。本周学习了高斯分布的推导(是classification里面的内容),分别运用了大学中概率论(误差密度函数、似然函数)、高数(积分、微分方程、极限的敛散性)、线代(齐次方程的解)等综合知识进行了推导,受益良多。
下一周因为临近假期,希望可以继续保持学习的进度。由于GNN是视频中的选修内容,所以决定后续作为拓展来学习,下一周计划学习Transformer的内容。然后Pytorch继续进行代码实战,计划快到训练模型阶段了。然后数学理论方面,如果有时间,下一周将对梯度、向量、张量数学方面论文的学习推导。