Additional Deep Learning Topics: Transfer Learning, Model Compression, and Anomaly Detection
迁移学习
实际应用中很多任务的数据的标注成本很高,无法获得充足的训练数据,这种情况可以使用迁移学习(transfer learning)。假设 A、B 是两个相关的任务,A 任务有很多训练数据,就可以把从 A 任务中学习到的某些可以泛化知识迁移到 B 任务。
领域偏移
目前为止,我们学习了很多深度学习的模型,所以训练一个分类器对于目前来说不是什么很难的事情。比如经典的MINIST数据集上的手写数字识别问题。但测试数据和训练数据的分布不一样时会导致一些问题。假设训练时数字是黑白的,但测试时数字是彩色的,正确率会非常低。这种问题称为领域偏移(domain shift)。
领域偏移其实有很多种不同的类型,模型输入的数据分布有变化的状况是一种类型。另外一种类型是,输出的分布也可能有变化。比如在训练数据上面,可能每一个数字出现的概率都是一样的,但是在测试数据上面,可能每一个数字输出的概率是不一样的,有可能某一个数字它输出的概率特别大,这也是有可能的。还有一种比较罕见状况的类型是,输入跟输出虽然分布可能是一样的,但它们之间的关系变了。比如同一张图片在训练数据里的标签为“0”,但是在测试数据的标签为“1”。
接下来我们专注于输入数据不同的领域偏移。接下来我们称测试数据来自目标领域(target domain),训练数据来自源领域(source domain)。在基准数据集上学习时,很多时候无视领域偏移问题,假设训练数据跟测试数据往往有一样的分布。但在实际应用时,当训练数据跟测试数据有一点差异时,机器的表现可能会比较差,因此需要领域自适应来提升机器的性能。
对于领域自适应,训练数据是一个领域,测试数据是另外一个领域,要把某一个领域上学到的信息用到另外一个领域,领域自适应侧重于解决特征空间与类别空间一致,但特征分布不一致的问题。
领域自适应
接下来介绍下领域自适应,以手写数字识别为例。比如有一堆有标注的训练数据,这些数据来自源领域,用这些数据训练出一个模型,这个模型可以用在不一样的领域。在训练的时候,我们必须要对测试数据所在的目标领域有一些了解。
随着了解的程度不同,领域自适应的方法也不同。如果目标领域上有一大堆有标签的数据,这种情况其实不需要做领域自适应,直接用目标领域的数据训练。如果目标领域上有一点有标签的数据,这种情况可以用领域自适应,使用目标领域的少量数据进行微调。
在这一种情况下,需要注意的问题是,因为目标领域的数据量非常少,所以要小心不要过拟合,不要在目标领域的数据上迭代太多次。
下面主要介绍下在目标领域上有大量未标注的数据的这种情况。这种情况其实是很符合实际会发生的情况。最基本的想法是训练一个特征提取器(feature extractor)。把源领域和目标领域的差异去除,让他们具有相同的分布,这样就可以实现领域自适应了。一般采用领域对抗学习的方式实现。
领域泛化
对目标领域一无所知,并不是要适应到某一个特定的领域上的问题通常称为领域泛化。领域泛化可又分成两种情况。
一种情况是训练数据非常丰富,包含了各种不同的领域,测试数据只有一个领域。此时我们期待期待因为训练数据有多个领域,模型可以学到如何弥平领域间的差异。
另外一种情况是训练数据只有一个领域,而测试数据有多种不同的领域。虽然只有一个领域的数据,但可以使用数据增强的方法去产生多个领域的数据
深度学习中的基本迁移学习方法
深度学习中最基本的迁移学习方法就是参数共享,我们在具有较多数据的A领域中训练模型,将其中的部分参数下载并传输给另一个模型。这个模型处理和原始领域近似的问题,在部分架构上类似。比如共享卷积神经网络的前几个隐层中的参数,这样一般会获得比随机初始化更好的结果。
这也可以称为监督的预训练与监督微调,和LLM领域近似但是不同。这个预训练需要完全同类型的数据集上进行,保证参数的类似性。我们通过下载其他人的参数的方式代替预训练步骤。
终身学习
灾难性遗忘
我们先训练机器做任务一语音识别,再教它做任务二图像识别,接着再 教它做第三个任务翻译,这样一来它就一起学会了这三个任务。我们不断去教会机器学习新的技能,等它学会成百上千个技能之后,它就会变得越来越厉害,以至于人类无法企及,这就是我们所说的终身学习(LifeLong Learning,LLL)。 也可以称为增量学习。
这种学习的意义是什么? 增量学习对于那种需要通过上线来收集更多数据辅助下一波训练的模型很有意义,如果每次都要从头开始训练,其训练开销无疑是很大的,我们以前进行的训练全部白费了。
终身学习有什么样的难点呢?看上去不断更新它的数据和对应网络的参数就能实现终身学习,但实际上没有那么容易。比如我们想要训练两个不同领域的图像识别任务,我们先训一个比较简单的网络来做第一个手写数字识别任务,然后再做任务二,任务一上的准确率是 90%,此时就算没有训过任务二,在任务二上也已经有了 96% 的准确率,可以说迁移得非常好。那么接下来我们用同一个的模型,即在任务一中训练好的模型再去训练任务二,结果我们发现在任务二上的准确率变得更好了(97%)。但是比较糟糕的事情是此时机器已经忘了怎么去做任务一了,即在任务一上的准确率从 90% 降到了 80%。
可能会想是不是网络设置的太过简单然后导致出现这样的现象,但实际上我们把任务一和任务二的数据放在一起让这个网络同时去学的时候,会发现机器是能够同时学好这两个任务的,也就是说两个独立的数据集因为没有进行合适的打乱,对模型最后的结果产生了很大的影响。不断增加新的很接近的任务,依旧导致模型对前面已经学会的任务产生了极强的遗忘。
这种情况称为灾难性遗忘(catastrophic forgetting)。我们知道即使是人类也可能会有遗忘的时候,而这里在遗忘前面我们加上了灾难性这个形容词,意在强调模型的这个遗忘不是一般的遗忘,而是特别严重程度的遗忘。
终身学习评估方法
现在我们可以先看看怎么评判终身学习技术做不做得好的一些标准。在做终身学习之前,得先有一系列任务让模型去学习,其实通常都是比较简单的任务。
具体的评估方式如下,首先有一排任务,并且有一个随机初始化的参数,用在这 T 个任务上,得到对应的准确率。然后让模型先学第一个任务,然后在所有任务上分别测一次准确率,即 ,依此类推。直到学完所有的任务,得到一个准确率的表格,来评估终身学习的结果。
具体的最终准确率公式表示为
另一种评估方法叫做反向迁移,即
终身学习的主要解法
解决终身学习问题,即主要解决灾难性遗忘的问题,通常来讲学术界有几种主要的研究思路。我们首先将第一种主要的解法,即选择性的突触可塑性(selective synaptic plasticity)。顾名思义,就是只让神经网络中的某些神经元之间的连接具有可塑性,其余的必须被固化,这类方法又叫做基于正则的方法。
对于这种方式,基本的思想就是每一个参数对我们过去学过的任务的重要性程度是不同的,因此在学习新的任务时,我们尽量不要动那些对过去任务很重要的参数,而是去学一些其他的对新任务比较重要的参数。
这种方法会给每一个参数赋予一个系数,表示他对过去的任务到底重不重要,现在我们在新的损失函数上惩罚参数的修改,并使用刚才的重要性程度赋予惩罚正则化程度。这样就可以抑制对全部的原始参数都进行大刀阔斧的修改而产生的灾难性遗忘的问题。
接下来比较关键的是 要怎么设定的问题,也就是某个参数到底对任务的重要性有多少。其实有一种简单的控制变量的方法,就是移动或改变某个参数,如果损失基本不怎么变换,就意味着这个参数在一定范围可变,相应的重要性参数 就可以很小,反之参数就要更大。
其实在基于正则的方法出现之前,还有一类方法,叫做梯度回合记忆(gradient episodic memory,MMD),它不是在参数上做限制,而是在梯度更新的方向上做限制,因此又被称为基于梯度的方法。它在计算当前任务梯度方向的同时,也会回去算历史任务对应的梯度方向,然后把两个梯度进行向量求和,得出实际的梯度方向,这样持续更新就能尽可能接近一个不会陷入灾难性遗忘的最优解了。当然新的梯度方向需要满足大于等于0 的条件,否则很难朝最优解方向优化。由于这里只是存储梯度这些少量的信息,因此我们还可以接受其是一种终身学习方法。
网络压缩
网络压缩(network compression)是一个很重要的方向,Bert 或 GPT 之类的模型很大,能不能够把这些硕大无朋的模型它缩小,让它有比较少量的参数,但是跟原来的性能其实是差不多的呢,这就是网络压缩想要做的事情。网络压缩的主要目的就是让我们可以在端侧运转庞大的模型,而无须依赖云服务器。
网络剪枝
第一个技术是网络剪枝(network pruning)。网络剪枝就是要把网络里面的一些参数剪掉。和曾经的树模型剪枝是一个思路,把那些对模型没有什么意义的参数与分支完全去除。
首先,先训练一个大的网络。接下来去衡量这个大的网络里面每一个参数或者是每一个神经元的重要性,去评估一下有没有哪些参数是没在做事的,或有没有哪些神经元是没在做事的。
最简单的方法评估参数重要性的方法就是看它的绝对值。如果这个参数的绝对值越大,它可能越能对整个网络的影响越大。或者如果它的绝对值越接近零。也许对整个网络的影响越小,也许对我们任务的影响越小。
常用的评估神经元重要性的方法是计算这个神经元输出不为零的次数,如果经常输出0就意味着神经元没那么重要,我们有很多的办法来衡量重要性,这里介绍的仅仅是一个思路。
把不重要的神经元或是不重要的参数就剪掉,就把它从模型里面移出,就得到一个比较小的网络。但是做完这个修剪以后,通常模型的性能就会掉一点。现在可以把这个比较小的网络,把剩余没有被剪掉的参数,再重新做微调。把训练数据拿出来,把这个比较小的网络再重新训练一下。这个步骤是一个不断重复的,反复进行多次直到我们在性能和大小上实现平衡。
在实验上,如果一次剪掉大量的参数,可能对网络的伤害太大了,可能会大到用微调也没有办法复原。所以一次去除的参数量不能太多。
修剪的单位可以以参数为单位,也可以以神经元来当作单位,这两者作为单位,在实现上会是有显著不同。当我们以参数为修剪单位,那后续得到的网络的形状可能会是不规则的。这对于Pytorch的运算非常不利,会导致无法正常的调用加速。因此在实际操作上,参数修剪往往是把对应位置的参数变为0,没法直接缩小模型。现有的论文证明参数修剪多数情况下无法起到合适的加速作用,并不实用。
神经元剪枝,即以神经元为单位来做剪枝也许是一个比较有效的方法。实际上神经元修剪也可以理解为把现有的大网络的参数放到另一个网络中复用,因此其自由程度是相对较高的。实现的时候,只要改那个每一个层输入、输出的那个维度就好了,也比较好用 GPU 来加速。
为什么要研究大网络修剪到小网络 ,不直接训练一个小网络,实验验证了我们可以先训练一个大的网络,再把它变小,正确率没有掉太多。但直接训练小的网络,得不到大的网络剪枝完变得小的网络一样的正确率。目前还没有什么清晰的理论来解释这个问题。彩票假说是一个可能的思路,他认为大网络本质是一堆小网络的集合,修剪是获取那个优秀的网络的方法,因此其会比直接训练小网络更好。
知识蒸馏
接下来讲可以让网络变小的方法——知识蒸馏(knowledge distillation)。先训练一个大的网络,这个大的网络在知识蒸馏里面称为教师网络(teacher network)。我们要训练的是真正想要的小的网络,即学生网络(student network)。先训练一个大的网络称为教师网络。再根据这个大的网络来制造学生网络。
在网络剪枝里面,直接把那个大的网络做一些修剪,把大的网络里面一些参数拿掉,就把它变成小的网络。在知识蒸馏里面是不一样的,这个小的网络(学生网络)是去根据教师网络来学习。
假设要做手写数字识别,就把训练数据都丢到教师里面,教师就产生输出,因为这是一个分类的问题,所以教师的输出其实是一个分布。比如教师的输出可能是看到这张图片 1 的分数是 0.7,7 的分数是 0.2,9 这个数字的分数是 0.1 等等。接下来给学生一模一样的图片,但是学生不是去看这个图片的正确答案来学习,它把老师的输出就当做正确答案,也就是老师输出 1 要 0.7,7 要 0.2,9 要 0.1。
知识蒸馏的思路和前面网络剪枝的核心思路是有接近之处的,他们都认为直接训练一个小网络没有大网络的效果好。为什么知识蒸馏会有帮助呢?一个比较直觉的解释是教师网络会提供学生网络额外的信息,从而实际上辅助了其的学习效果。
教师网络不一定要是单一的巨大网络,它甚至可以是多个网络的集成,训练多个模型,输出的结果就是多个模型,投票的结果就结束了。或者是把多个模型的输出平均起来的结果当做是最终的答案。
虽然在比赛里面,常常会使用到集成的方法。但是在实用上,集成的,计算量也未免太大了,因此我们需要研究知识的蒸馏。
在使用知识蒸馏的时候有一个小技巧。这个小技巧是稍微改一下 Softmax 函数,会在Softmax 函数上面加一个温度(temperature)。原始的Softmax为
增加了温度的Softmax为
其中温度是一个超参数,温度 的作用就是把本来比较集中的分布变得比较平滑一点。当的时候,这样的Softmax会将原本的集中变得分散,大小关系不变,这样就可以让教师网络为学生网络提供而外的信息,从而帮助学习。
温度太大,模型会会改变很多。假设温度接近无穷大,这样所有的类别的分数就变得差不多,学生网络也学不到东西了,因此 又是另外一个超参数,它就跟学习率一样,这个是我们在做知识蒸馏的时候要调的参数
参数量化
接下来介绍下一个技巧:参数量化(parameter quantization)。参数量化是说能否只用比较少的空间来储存一个参数。
举个例子,现在存一个参数的时候可能是用 64 位或 32 位。可能不需要这么高的精度,用 16 或 8 位就够了。所以参数量化最简单的做法就是,本来如果存网络的时候,举例来说,我们是 16 个位存一个数值,现在改成 8 个位存一个数值。储存空间,网络的大小直接就变成原来的一半,而且性能不会掉很多,甚至有时候把储存参数的精度变低,结果还会稍微更好一点。一般情况下在训练的时候我们会考虑使用较高的精度,在推理的时候考虑进行参数量化,从而减少存储空间。
还有一个再更进一步压缩参数的方法,即权重聚类(weight clustering)。举个例子,先对网络的参数做聚类,按照这个参数的数值来分群。数值接近的放在一群,要分的群数会先事先设定好,此时每一群都只拿一个数值来表示它。这样记录参数就只需要记录他属于哪个群就好了。当然这种方法对于优化计算性能就没有什么帮助了。
权重聚类可以直接在训练的时候考虑,让模型自动学习比较接近的参数即可。一般权重聚类里群的值就是其中所有参数的均值。
动态计算
现在我们讨论动态计算(dynamic computation),动态计算跟前几个方法想 要达成的目标不太一样。前几个方法就是单纯的把网络变小,而动态计算希望网络可以自由地调整它需要的计算量。从而自动适应其运行的平台。动态计算有一种简单的替代方法,就是训练一堆模型,针对不同的平台设计参数量与计算开销。
怎么让网络自由地调整其对计算资源的需求呢?一个可能的方向是让网络自由地调整它的深度。可以在这个层和层中间再加上一个额外的层。这个额外的层的工作是根据每一个隐藏层的输出决定现在分类的结果。当计算资源比较充足的时候,可以让这张图片去跑过所有的层,得到最终的分类结果。当计算资源不充足的时候,可以让网络决定它要在哪一个层自行做输出。
另外还可以让网络自由地决定它的宽度,怎么让网络自由决定它的宽度。设定好几个不同的宽度,同一张图片丢进去。在训练的时候,同一张图片丢进去,每个不同宽度的网络会有不同的输出。我们在希望每一个输出都跟正确答案越接近越好就结束了,把所有的输出跟标准答案的距离加起来得到一个损失,最小化这个损失就结束了。此时我们在计算性能不足的使用少使用一些神经元。
以上介绍的方法都不是独立存在的,我们可以结合多种方法来追求更好的效果。
优化网络架构设计
优化网络架构设计是最好的方法,但是其高度依赖于其研究背景,需要深厚的研究功底才能对网络架构进行优化,这也是算法团队应该完成的工作。
ChatGPT与LLM
本章节我们介绍如今最火的深度学习应用之一 大语言模型,我们主要讨论其中的基本原理以及它背后的关键技术——预训练。
首先我们需要知道这一类模型的特点,第一,ChatGPT 每次的输出都不一样,所以如果你问一模一样的问题,可能会得到非常不一样的答案。每次的答案都是重新生成的。ChatGPT 的另外一个特点是你可以再继续追问,在同一个对话里面可以有多轮的互动。他知道过去的输入与输出。不是输出开发者预先准备好的信息,也不是在互联网上搜索信息再一模一样的输出出来。
那 ChatGPT 真正在做的事情是什么呢?和我们在Transformer中介绍的一样,ChatGPT 简单来将其本身就是一个函数,输入一些东西,就输出一些东西。可以以一个句子作为输入,它输出这个句子后面应该接的词汇的概率。在获取这个概率后我们进行采样,由于采样的随机性,每次输出的答案都不尽相同。
和我们在Transformer中介绍的一样,GPT采用一次输出一个token的方式,不断将新的输出加入的解码器的输入中,直到结束符号被输出,最后形成一段完整的对话。至于其上下文能力,则是因为每次都将前文作为编码器输入的一部分。
在了解了基本的模型工作方法以后,我们就需要知道其中的关键技术——预训练。由于模型的参数量非常庞大,我们无法接受收集如此多的训练数据来进行监督训练,因此我们需要利用自监督学习技术,也就是预训练。模型通过大量的公开数据集,进行生成式的预训练。这就是GPT模型。
在GPT模型之后,下一步就是人工监督学习,接下来 GPT 就透过人类老师提供的数据,继续去做学习。那在进行监督式学习之前,通过大量网络数据学习的这个过程,我们称之为预训练。这个监督学习的过程我们称为微调。
另外,我们知道 ChatGPT 中不只是有监督式的学习,还有加上强化学习,其使用的是强化学习中常见的 PPO 算法。在强化学习中,人不是直接给机器答案,而是告诉机器,现在你的答案是好还是不好。强化学习的好处是,相较于监督式学习,监督式学习的人类老师是比较辛苦的,而在强化学习中,人类老师可以偷懒,只需要指导大的方向。强化学习可以用于人类也无法给出准确的答案但可以判断答案的好坏的时候,比如写诗。
综上,ChatGPT 的学习基本上就是三个步骤——先做预训练,再做监督学习,然后做强化学习。
- Title: Additional Deep Learning Topics: Transfer Learning, Model Compression, and Anomaly Detection
- Author: Hyacehila
- Created at : 2024-11-14 14:53:20
- Link: https://hyacehila.github.io//blog/2024/11/14/additional-deep-learning-topics/
- License: This work is licensed under CC BY-NC-SA 4.0.