一个人在不接触对方的情况下,通过一种特殊的方式,和对方进行一系列的问答.如果在相当长时间内,他无法根据这些问题判断对方是人还是计算机,那么就可以认为这个计算机是智能的。
——阿兰·图灵(Alan Turing)
《Computing Machinery and Intelligence 》
知识体系
深度学习DL是机器学习的分支,是一种以人工神经网络为架构,对数据进行特征学习的算法
ML | DL |
人工特征提取 | 神经网络自动特征提取 |
大量的数据集 |
我们使用找女友的例子来看深度学习,先将女生的各个特征获取,随后进行隐层的第一次判断,再进行二次判断能不能做女朋友 。
这体现出DL的思想:输入原始数据,通过模型如Bert来进行特征工程,随后进行更加高级的特征学习,不断确定合适的参数,让DL去拟合数据
深度学习为什么主要使用神经网络呢?其主要原因是神经网络模型可以使用误差反向传播BP算法,从而可以比较好地解决贡献度分配问题
神经网络需要学习的内容:各个神经元之间连接的权重参数
神经网路是由一个个神经元组成的,每个神经元的结构如下
为什么要使用激活函数?如果不使用激活函数,那么每一层的输出为上一层输入的线性函数,那么最终导致整个网络是线性的,即最原始的感知机(Perceptron),其表示能力较差。此外还有缓解梯度消失和加速模型收敛的作用
其中f为激活函数,常见的激活函数图像如下
Sigmoid函数
ReLu函数
Swish函数
注意点
1 小型模型一般使用ReLU函数,因为它求导较简单。但是不使用sigmoid因为它在取值很大很小的时候,导数非常小,参数更新较慢
激活函数:对神经网络中的上一层的输入进行非线性的转变。主要用于拟合函数
损失函数:度量神经网络的输出预测值与实际值之间差距的一种方式
优化函数:如何把损失值从神经网络的最外层传递到最前面
常见的有三种网络结构,前馈网络FNN类似于一个有向无环图,信息只能朝一个方向传播,较为简单。记忆网络MN类似一个有向循环图,信息可以单向也可以双向传播,较为复杂。图网络GN是前馈网络和记忆网络的泛化,其中每个方格为一个或一组神经元
各种神经网络图
损失函数指的是我们的预测值与真实值之间的偏离程度,一般来说分类问题用交叉熵,回归问题用平方误差。
本层和前一层的每个神经元相互连接,我们称本层为全连接层
可以看到全连接层前后的特征数量没有发生变化,所以本质上来说就是将一个特征空间线性变换到另一个特征空间。在CNN中一般放在最后几层来进行分类,而在RNN中一般放在中间几层来吧Embedding空间拉到隐层空间或把隐层空间转回lable空间
常常也被叫做多层感知器(Multi-Layer Perceptron, MLP),全连接神经网络(Fully Connected Neural Network,FCNN),是最常见的神经网络,其主要特点是单向传播。工作原理是多层的Logistic回归模型不断进行叠加而成(每层可以看做是一个仿射变换和一个非线性变换)
前馈神经网络模型图
其符号定义如下
若输入的数据为x,则传播公式如下
根据通用近似定理,可以发现FNN具有很强的拟合能力,可以看做是一个万能函数,常见的连续非线性函数都可以拟合。
此外,我们都知道DL是ML的一部分,这里也有一个解释就是每层的参数输入输出可以看做是将原始特征进行映射变换,即特征抽取,经过变换后的特征可以极大提高分类器的性能。
神经网络中的参数学习可以通过梯度下降法来求解的,即求解每个参数的偏导,如此大量使用链式法则进行推导的方式,时间复杂度自然比较大。而BP算法恰巧能够解决这个问题。
根据公式推导可得到如下公式,将公式标注在网络中可以发现就是一个反向传播的网络
主要做法
正常正向传播-->反向计算每一层的误差项-->根据误差项计算每一层的偏导数-->更新参数--> 重新进行正常的正向传播
由于它具有较强的调整能力,因此非常容易出现过拟合的现象,解决的方案就是提前停止策略,即当训练集误差降低的同时验证集的误差在升高就停止传播。
神经网路的参数学习要比线性模型更加困难,主要的有两点 :非凸优化问题和梯度消失
非凸优化问题
比如有一个两层的神经网络,每层的神经元的个数均为1个,即 = (2(1)),若分别使用交叉熵和平方误差作为损失函数,其损失函数值与1 和2的关系如下
可以明显的看到两种损失函数都是关于参数的非凸函数
梯度消失问题
神经网络中的误差反向传播的迭代公式为 () = ′ (()) ⊙ ((+1)) T (+1) ,每传播一层都需要乘以该激活函数的导数,如果该导数非常小,接近于0,那么多个这样的数值进行相乘,自然结果也非常的接近于0了,比如Logistic函数和Tanh函数,其导数分别为 ′ () = ()(1 − ()) ∈ [0, 0.25]和tanh′ () = 1 − (tanh()) 2 ∈ [0, 1]。其图像如下所示
解决梯度消失(梯度弥散)问题的主要方法就是使用导数比较大的激活函数比如ReLU或者将传播公式中的相乘变为相加,即残差网络。
先看看FNN有什么缺点吧
参数太多:因为是全连接的,比如你有一个图像是128*128*3的图像那么每一层的神经元和权重参数w就有128*128*3个,如此巨量的参数其训练效率是非常低的。局部不变性,很多时候我们的操作是不影响对象的意义的,但是FNN如果不进行数据增强的话是很难提取这些信息的。
所以CNN就是为了解决以上的问题的一个改进版FNN。主要由卷积层、汇聚层和全连接层交叉堆叠而成的。具有局部连接、权重共享和汇聚的特点。
主要的应用领域是在计算机图像,不过近年来在NLP上也略有涉猎。
对一个输入如果使用了Receptive Field和Parameter sharing,那这种神经网络的架构就是卷积层。具体介绍看以下内容
首先神经网络的输入一定是一个向量,但是图片明显是一个三维(length、width、RGB)的tensor矩阵,因此最简单的做法是将tensor变成一个如100*100*3的向量。如此的权重w是非常的大的。
需要进行简化。
简化方法一:Receptive field
从图片角度观察,如一只小鸟。我们只需要图片中一小部分得到其特征即可。
因此,可以将图片的大tensor分隔成一个个小的tensor(如何分隔是由你自己决定),叫做Receptive field。
然后每个tensor放入一个neural netwok(Receptive field)之中,每一个neural network有很多的neurons神经元。
当然,不同的tensor可以放在不同的neurons中也可以放在同一个中,一个tensor的部分可能会重叠掉其他的tensor,甚至可以只考虑一个channels(这里指的是RGB),有无数种分隔控制的方法。
因为一张图片中任何位置都有可能我们想要的检测的东西,我们不能漏掉,因此Receptive field要覆盖整个图片,可以有一个stride步幅,来更快的覆盖图片区域。
如果遇到边边角角一个Receptive field大小无法满足,那么可以使用padding补齐,一般是使用全0进行补齐,当然使用整张图片的比如平均值啊这些也都是可以的。
这样就可以。
简化方法二:Parameters sharing
比如我们想检测鸟嘴,它可能出现在图片中的任何地方,如果使用方法一里的receptive field,虽然可以覆盖全图片检测,但是每个receptive field都有检测鸟嘴功能的neurons,nurons的w不是统一的,那算法的复杂度非常的高。
实际上,各个receptive field检测功能是一样的,只是检测的区域不一样罢了
所以,如果各个receptive field可以Parameter sharing共享参数就非常好了,所以只要让他们的w一样就好了,那么这个参数叫做filter
简化方法三:Pooling
比如说我们将图片宽为偶数,长为奇数,或者说RGB里的R,删除之后并不会对一个图像的识别产生影响。
Max Pooling技术
将一个Tensor分隔成很多小的Tensor,然后取其中的最大值合并成一个新的Tensor
但是近年来不怎么使用了,因为Pooling主要是为了提高运行训练速度,代价就是降低精度,而现在计算速度越来越快了。
总结做法:
CNN第一层网络
在第一层结束后,一个低维的Tensor变成了一个高维的Tesnor
CNN第二+层
最后将Tensor矩阵通过Flatten拉成一个向量,放入FNN中
1 先根据对象特征设计好一个个的Filter
2 在图片中取出一个个的Receptive Field,使其与Filter进行相乘,获取一个具体的数值
3 通过2的方式,每使用一个Filter就可以获得一个二维Tensor,将二维Tensor结合起来就是一个高维的Tensor,叫做Feature Map
4 使用Pooling技术将Tensor压缩(一般是做了几次的Convolutional之后做一次Pooling,Pooling可有可无)
5 因此,old Tensor --> Convolutional -->newTensor (--> Pooling --> new Tensor) --> Flatten -->FNN -->Result
不要担心自己的filter只有3*3太小了,实际上你每迭代一层,其范围就会扩大一点,如下图
相比于FNN,它具有短期记忆的功能,不但可以接受其他神经元的信息,而且可以接受自身的信息。其参数学习主要通过时间反向传播算法学习,但是可能存在长程依赖问题(梯度爆炸和消失),可以通过门控机制来有效的解决。RNN也非常容易的扩展到更加宽广的记忆网络模型(递归神经网络和图网络)。
思想:在FNN的基础上,在所有的非输出层上都添加一个延时器,记录神经元最近几次的活性值。
延时器中记录的内容主要是:在t时刻,第l层神经元活性值依赖于第l-1层神经元的最近K个时刻的活性值。
在每个时刻 都有一个外部输入,产生一个输出.NARX通过一个延时器记录 最近 次的外部输入和最近 次的输出,第t个时刻的输出 为
其中(⋅)表示非线性函数,可以是一个前馈网络, 和 为超参数
思想:每次有一个输出的时候,就会将这个输出存储到一个memory当中,在下一次输入的时候,就会读取和使用这个memory,从而实现记忆的效果。
结构图如下
做法:输入X1∶ = (1 , 2 , ⋯ , , ⋯ , ),通过一下公式来更新活性值ht
FNN可以模拟任何的连续函数,而RNN强到可以模拟任何程序,因为它有动力系统(时间+函数)
RNN的简单模拟例子
如这样的神经网络,所有的w均为1
第一次输入的时候,Memory没有数据,正常输出就是[4,4]
第二次输入的时候,Memory的数据为[4,4],因此输出的为2+2+4+4=12
以此类推
在RNN中主要解决的问题为Slot Fillling。槽填充的意思就是如给你一个句子,让你输入它的名字、性别、单位。将这三个输出填充上去,这个过程就是槽填充。其解决过程如下
第一次输入arrive,结果a1会存储到memory中。第二次输入的Taipei会获取Memory中的内容,随后输出a2存储到memory中,后面以此类推。这样一个单词的输出和它自己的输入和前面所有单词都有关系,也就是拥有记忆功能。
再狂野一点就是双向进行传播,然后将其中的输出存储到Memory
还有一个想法就是Memory能否有门控机制决定存取,即LSTM
这里的-放在Short和term之间,你可以理解为它是一个比较长的短期记忆神经网络
有门控机制Output Gate、Input Gate和Memory Gate。决定门是否开启可以由Network自己学习。
例子如下
1 首先是f(zi),如果为其值0,那么f(zi)*g(z)也一定为0,意思就是输入无效
2 将接下去是cf(zf),c为之前的记忆块,如果f(zf)为0,意思是就是将之前的记忆清除掉
3 最后一步是f(zo),如果其值为0,那么h(c')*f(zo)也一定为0,意思就是记忆无法输出
总的来说就是设计一个门控函数,其值要么为1要么为0,1为通过,0为不通过
这里的三个门控的数值也是由原始的Input来决定的
简易版LSTM
复杂版LSTM
复杂的地方就是在输入的地方,不仅看当前的数据,还看上一次输出的数据和记忆块中的数据
在Training的时候,我们会发现其损失函数并不是随着层数的增对而慢慢减少,而是不断的在跳动
从高屋建瓴的角度看,其Loss本身在很多地方会很平坦,很多地方非常的陡峭,如下图
解决方法是:若它的梯度大于一个值的时候,就将其值设置为一个阈值,不要让它变化
为什么会出现这样的问题呢?
因为RNN中如果一个单词出现在前面,其后续会重复的使用该单词,就算是1.01使用了1000次也会变成一个难以想象大小的数值!而若是0.99的值最后都会变成了0。因此会造成梯度消失和梯度爆炸的问题。
为什么我们在使用RNN的时候一般都是直接使用LSTM呢?或者或LSTM有什么优点呢?(面试重点)
It can deal with gradient vanishing梯度消失,因为RNN主要使用的是覆写的方式,最终的形式是连乘的,而LSTM使用了Memory(就是一个保护机制),将连乘变为了连加。但是不能解决梯度爆炸问题。
背景:很多时候,我们的需要输入的数据非常的复杂,难以用统一、固定长度的一维向量来表示。比如NLP句子,我们输入的句子长度都是不一样的。在此基础上,我们输出的数据有时候也会复杂,比如一组向量中每一个向量有一个输出(词性标注),或者一组向量有一个输出,或者输出的数量让机器自己决定(即seq2seq任务,比如中英文翻译)
在第一种情况的时候,我们可以使用Fully-connected,然后每一个向量会有一个输出,但是单独使用一个向量为基础其蕴含的信息太少了,于是可以加上一个Windows想法,一个向量的前后5个也作为输入,那么一个输入就有11个向量拼接在一起。但是这样也有一个缺点,就是我们的句子长度是不一样的,难以用一个统一的windows来,此时,就需要用到我们的Self-Attention
Self-Attention第一步:计算两个输入向量之间的Relevant,计为α
常见的计算方法有Dot-product和Additive
就是将各向量分别乘以一个矩阵,得到两个新的向量。两个向量相乘就是Dot-Product方法,若直接相连接然后使用tanh函数就是Additive方法
对于a1本身来说,也是需要乘以wk的,是为了提高最终的准确率。这里的Soft-Max非固定的,你用ReLu当然也是可以的
Self-Attention第二步:新增一个向量v,与α相乘,将所有的数值进行累加
再乘以一个Wv矩阵得到v向量,然后让每一个v向量乘以它的α,将这些向量所有累加起来成为一个新的向量
Self-Attention第三步:与Fully-Connection结合起来使用
但是这样有一个缺点就是,没有考虑到每个单词在句子中的位置信息。
如果想要考虑的话,很简单,你为每一个位置设计一个位置权重向量ei,每个ei加到ai上面去
最主要是运用在NLP上,在其他应用的时候需要改进
在音频上,对于语音,我们一般的处理是将将一句话每隔一段时间就截取一个声音,随后将这些声音组合成一个矩阵,但是这样的矩阵会非常的大,而Self-Attention处理的时间复杂度是比较高的。可以调整的手段就是截取一小段的音频,至于截取哪里,取决于具体的任务和你的设计了。
在影像上,图片本来就是一个矩阵,当然可以使用Self-Attention,而且在之前的CNN中其实就是运用了Self-Attention,只不过CNN处理图片是一个个小范围的信息,而Self-Attention关注的是一整张图片的信息。而目前的研究表明,在数据量比较少的时候,使用CNN的效果比较好,而数据量比较大的时候,使用Self-Attention会更好。Self-Atterntion加上一些东西就会变成CNN
Self-Attention与RNN 之间的区别
或许你会觉得Self-Attention和RNN都是可以将其他的数据考虑进来,而且都是可以进行双向传播的,没有什么区别,但是区别还是有的
最大的区别是就RNN不能并行运行的,它每一个向量的运行都是需要在前一个向量的基础上进行的,Self-Attention进行修改也就变成了RNN
Self-Attention与Graph
Graph本身就可以看做是一个矩阵,因此当然可以使用Self-Attention
是一个seq2seq方法,即seqenece-to-sequence model,NLP所有的任务都可以看做是QA问题,而所有的QA问题都可以使用seq2seq方法来解决
抽象的来说就是你输入一段文本,然后文本循环进入一个Block,每个Block包括了Self-attention和FC。
residual是传统的Self-attention的输出结果加上原始文本叠加的方法。在Transformer中,每个文本通过一个residual再加上一个norm,然后进入一个FC,再次使用residual+norm
将上面的内容综合起来就是原始论文中的内容:一个文本输入通过Input Embedding得到词向量组,再加上每个词的位置向量。随后不断循环使用Block,至于使用Multi-Head Attention是为了将模型分为多个头,形成多个子空间,从而可以关注文本的不同信息,然后使用Residual+Norm,然后进入一个FC,再使用一次Residual+Norm
Decoder与Encoder区别的在于多了一个Cross Attention:Multi Head--Attention+residual & Norm,以及将传统的Multi Head--Attention换成Mask Multi Head--Attention
Mask Self--Attention:传统的Self--Attention中的每个词都会考虑句子中的所有其他词,而Mask Self--Attention只考虑前面出现过的词
Cross Attention:从Encode和Mask Self--Attention中获取输出,随后使用类Self--Attention的方法 Cross Attention进行输出
在原始论文中,Decoder每次循环使用的都是Encoder的最后一层,那么自然后人也使用了很多其他的方法进行修改
核心思想就是:有开始和结束的标志符。每一个结果的输出是在前一个基础之上进行的(和Mask Mutil--Head Attention的思想类似)
有一个问题就是Output的长度我们是不知道的,比如说语音识别,输入的有很多,输出的很少,因此我们要想办法让机器自己去学习
AT是不断迭代输出w直到输出END,而NAT是直接一次性输出全部结果
NAT最大的好处就是在于平行化输出,只需要进行一次Decoder运算即可,效率非常的高,但是缺点也非常的明显,没有很好的使用前面输出的内容,因此NAT的表现往往比AT要差
参考资料
《神经网络与深度学习》--邱锡鹏教授
《动手学深度学习》Pytorch版开源--李沐
《2022深度学习与机器学习》--吴恩达