半监督学习综述

半监督学习(Semi-Supervised Learning,SSL) 使用标记和未标记的数据来执行有监督的学习或无监督的学习任务。

半监督学习可进一步划分为纯(pure)半监督学习直推学习(transductive learning)。前者假定训练数据中的未标记样本并非待预测的数据,而后者则假定学习过程中所考虑的未标记样本恰是待预测数据。纯半监督学习是基于“开放世界”假设,希望学得模型能适用于训练过程中未观察到的数据,而直推学习是基于“封闭世界”假设,仅试图对学习过程中观察到的未标记数据进行预测。下图直观的表现出主动学习纯半监督学习直推学习的区别:

虽然训练数据中含有大量无标签数据,但其实在很多半监督学习算法中用的训练数据还有挺多要求的,一般默认的有:无标签数据一般是有标签数据中的某一个类别的(不要不属于的,也不要属于多个类别的);有标签数据的标签应该都是对的;无标签数据一般是类别平衡的(即每一类的样本数差不多);无标签数据的分布应该和有标签的相同或类似等等。

一般,半监督学习算法可分为:self-training(自训练算法)、Graph-based Semi-supervised Learning(基于图的半监督算法)、Semi-supervised supported vector machine(半监督支持向量机,S3VM)。简单介绍如下:

1.简单自训练(simple self-training):用有标签数据训练一个分类器,然后用这个分类器对无标签数据进行分类,这样就会产生伪标签(pseudo label)或软标签(soft label),挑选你认为分类正确的无标签样本(此处应该有一个挑选准则),把选出来的无标签样本用来训练分类器。

2.协同训练(co-training):其实也是 self-training 的一种,但其思想是好的。假设每个数据可以从不同的角度(view)进行分类,不同角度可以训练出不同的分类器,然后用这些从不同角度训练出来的分类器对无标签样本进行分类,再选出认为可信的无标签样本加入训练集中。由于这些分类器从不同角度训练出来的,可以形成一种互补,而提高分类精度;就如同从不同角度可以更好地理解事物一样。

3.半监督字典学习:其实也是 self-training 的一种,先是用有标签数据作为字典,对无标签数据进行分类,挑选出你认为分类正确的无标签样本,加入字典中(此时的字典就变成了半监督字典了)

4.标签传播算法(Label Propagation Algorithm):是一种基于图的半监督算法,通过构造图结构(数据点为顶点,点之间的相似性为边)来寻找训练数据中有标签数据和无标签数据的关系。是的,只是训练数据中,这是一种直推式的半监督算法,即只对训练集中的无标签数据进行分类,这其实感觉很像一个有监督分类算法…,但其实并不是,因为其标签传播的过程,会流经无标签数据,即有些无标签数据的标签的信息,是从另一些无标签数据中流过来的,这就用到了无标签数据之间的联系

5.半监督支持向量机:监督支持向量机是利用了结构风险最小化来分类的,半监督支持向量机还用上了无标签数据的空间分布信息,即决策超平面应该与无标签数据的分布一致(应该经过无标签数据密度低的地方)(这其实是一种假设,不满足的话这种无标签数据的空间分布信息会误导决策超平面,导致性能比只用有标签数据时还差)

其实,半监督学习的方法大都建立在对数据的某种假设上,只有满足这些假设,半监督算法才能有性能的保证,这也是限制了半监督学习应用的一大障碍。

半监督深度学习

终于来到正题——半监督深度学习,深度学习需要用到大量有标签数据,即使在大数据时代,干净能用的有标签数据也是不多的,由此引发深度学习与半监督学习的结合。

如果要给半监督深度学习下个定义,大概就是,在有标签数据+无标签数据混合成的训练数据中使用的深度学习算法吧…orz.

半监督深度学习算法个人总结为三类:无标签数据预训练网络后有标签数据微调(fine-tune);有标签数据训练网络,利用从网络中得到的深度特征来做半监督算法;让网络 work in semi-supervised fashion。

1.无标签数据预训练,有标签数据微调

对于神经网络来说,一个好的初始化可以使得结果更稳定,迭代次数更少。因此如何利用无标签数据让网络有一个好的初始化就成为一个研究点了。

目前我见过的初始化方式有两种:无监督预训练,和伪有监督预训练

无监督预训练:一是用所有数据逐层重构预训练,对网络的每一层,都做重构自编码,得到参数后用有标签数据微调;二是用所有数据训练重构自编码网络,然后把自编码网络的参数,作为初始参数,用有标签数据微调。

伪有监督预训练:通过某种方式/算法(如半监督算法,聚类算法等),给无标签数据附上伪标签信息,先用这些伪标签信息来预训练网络,然后在用有标签数据来微调。(MAE: mask 编码器)

2.利用从网络得到的深度特征来做半监督算法

神经网络不是需要有标签数据吗?我给你造一些有标签数据出来!这就是第二类的思想了,相当于一种间接的 self-training 吧。一般流程是:

先用有标签数据训练网络(此时网络一般过拟合…),从该网络中提取所有数据的特征,以这些特征来用某种分类算法对无标签数据进行分类,挑选你认为分类正确的无标签数据加入到训练集,再训练网络;如此循环。

由于网络得到新的数据(挑选出来分类后的无标签数据)会更新提升,使得后续提出来的特征更好,后面对无标签数据分类就更精确,挑选后加入到训练集中又继续提升网络,感觉想法很好,但总有哪里不对…orz

个人猜测这个想法不能很好地 work 的原因可能是噪声,你挑选加入到训练无标签数据一般都带有标签噪声(就是某些无标签数据被分类错误),这种噪声会误导网络且被网络学习记忆。

3.让网络 work in semi-supervised fashion

前面的1.和2.虽然是都用了有标签数据和无标签数据,但就神经网络本身而言,其实还是运行在一种有监督的方式上。

哪能不能让深度学习真正地成为一种半监督算法呢,当然是可以啊。譬如下面这些方法:

Pseudo-Label : The Simple and Efficient Semi-Supervised Learning Method for Deep Neural Networks

这是一篇发表在 ICML 2013 的文章,是一个相当简单的让网络 work in semi-supervised fashion 的方法。就是把网络对无标签数据的预测,作为无标签数据的标签(即 Pseudo label),用来对网络进行训练,其思想就是一种简单自训练。但方法虽然简单,但是效果很好,比单纯用有标签数据有不少的提升。

网络使用的代价函数如下:

L=∑m=1n∑i=1CL(yim,fim)+α(t)∑m=1n′∑i=1CL(y′im,f′im)

代价函数的前面是有标签数据的代价,后面的无标签数据的代价,在无标签数据的代价中,y′无标签数据的 pseudo label,是直接取网络对无标签数据的预测的最大值为标签。

虽然思想简单,但是还是有些东西需要注意的,就是这个α(t),其决定着无标签数据的代价在网络更新的作用,选择合适的α(t)很重要,太大性能退化,太小提升有限。在网络初始时,网络的预测时不太准确的,因此生成的 pseudo label 的准确性也不高。在初始训练时,α(t)要设为 0,然后再慢慢增加,论文中给出其增长函数。在后面的介绍中,有两篇论文都使用了一种高斯型的爬升函数。

感觉这种无标签数据代价达到一种正则化的效果,其减少了网络在有限有标签数据下的过拟合,使得网络泛化地更好。

Semi-Supervised Learning with Ladder Networks

2015年诞生半监督 ladderNet,ladderNet是其他文章中先提出来的想法,但这篇文章使它 work in semi-supervised fashion,而且效果非常好,达到了当时的 state-of-the-art 性能。

ladderNet 是有监督算法和无监督算法的有机结合。前面提到,很多半监督深度学习算法是用无监督预训练这种方式对无标签数据进行利用的,但事实上,这种把无监督学习强加在有监督学习上的方式有缺点:两种学习的目的不一致,其实并不能很好兼容。

无监督预训练一般是用重构样本进行训练,其编码(学习特征)的目的是尽可能地保留样本的信息;而有监督学习是用于分类,希望只保留其本质特征,去除不必要的特征。

ladderNet 通过 skip connection 解决这个问题,通过在每层的编码器和解码器之间添加跳跃连接(skip connection),减轻模型较高层表示细节的压力,使得无监督学习和有监督学习能结合在一起,并在最高层添加分类器,ladderNet 就变身成一个半监督模型。

ladderNet 有机地结合了无监督学习和有监督学习,解决兼容性问题,发展出一个端对端的半监督深度模型。

PS:论文有给出代码

Temporal Ensembling for Semi-supervised Learning

Temporal ensembling 是 Pseudo label 的发展,目的是构造更好的 pseudo label(文中称为 target,我认为是一致的)。

多个独立训练的网络的集成可取得更好的预测,论文扩展了这个观点,提出自集成(self-ensembling),通过同一个模型在不同的迭代期,不同的数据增强和正则化的条件下进行集成,来构造更好的 target。

论文提出了两种不同的实现: Π model 和 temporal ensembling

两个模型的代价函数都是一样的,与 Pseudo Label 的代价函数类似,一个有监督 loss,一个无监督 loss,中间有个权系数函数,与 Pseudo Label 的区别在于,Pseudo Label 的第二项是无标签 loss,是只针对无标签数据的(如果我没理解错..orz),而 Temporal ensembling 的第二项是 无监督 loss,是面向全部数据的。

Π model 的无监督代价是对同一个输入在不同的正则和数据增强条件下的一致性。即要求在不同的条件下,模型的估计要一致,以鼓励网络学习数据内在的不变性。

缺点也是相当明显,每个迭代期要对同一个输入在不同的正则和数据增强的条件下预测两次,相对耗时。还好不同的正则可以使用 dropout 来实现,不然也很麻烦。

temporal ensembling 模型是对每一次迭代期的预测进行移动平均来构造更好的 target,然后用这个 target 来计算无监督 loss,继而更新网络。

缺点也有,记录移动平均的 target 需要较多空间。但 temporal ensembling 的潜力也更大,可以收集更多的信息,如二阶原始矩,可基于这些信息对不同的预测加权等。

Temporal ensembling 还对标签噪声具有鲁棒性,即使有标签数据的标签有误的话,无监督 loss 可以平滑这种错误标签的影响。

Mean teachers are better role models: Weight-averaged consistency targets improve semi-supervised deep learning results

Mean Teacher 这篇文章一上来就说“模型成功的关键在于 target 的质量”,一语道破天机啊。而提高 target 的质量的方法目前有两:1.精心选择样本噪声;2. 找到一个更好的 Teacher model。而论文采用了第二种方法。

Mean teacher 也是坚信“平均得就是最好的”(不知道是不是平均可以去噪的原因…orz),但是时序上的平均已经被 temporal ensembling 做了,因此 Mean teacher 提出了一个大胆的想法,我们对模型的参数进行移动平均(weight-averaged),使用这个移动平均模型参数的就是 teacher model 了,然后用 teacher model 来构造高质量 target。

一思索就觉得这想法好,对模型的参数进行平均,每次更新的网络的时候就能更新 teacher model,就能得到 target,不用像 temporal ensembling 那样等一个迭代期这么久,这对 online model 是致命的。

知识蒸馏(KD)综述

https://cloud.tencent.com/developer/article/1763873

https://www.cvmart.net/community/detail/5865

知识蒸馏总的思路:通过采用与训练好的复杂模型(teacher model)的输出作为监督信号,同label标签一起去做监督训练,训练一个简单的模型(student model)

摘要

近年来,深度神经网络在工业界和学术界都取得了成功,尤其是在计算机视觉任务方面。深度学习的巨大成功主要归因于其可扩展性以编码大规模数据并操纵数十亿个模型参数。但是,将这些繁琐的深度模型部署在资源有限的设备(例如,移动电话和嵌入式设备)上是一个挑战,这不仅是因为计算复杂性高,而且还有庞大的存储需求。为此,已经开发了多种模型压缩和加速技术。作为模型压缩和加速的代表类型,知识蒸馏有效地从大型教师模型中学习小型学生模型。它已迅速受到业界的关注。本文从知识类别,训练框架,师生架构,蒸馏算法,性能比较和应用的角度对知识蒸馏进行了全面的调查。此外,简要概述了知识蒸馏中的挑战,并讨论和转发了对未来研究的评论。

知识蒸馏简介

知识蒸馏,已经受到业界越来越多的关注。大型深度模型在实践中往往会获得良好的性能,因为当考虑新数据时,过度参数化会提高泛化性能。在知识蒸馏中,小模型(学生模型)通常是由一个大模型(教师模型)监督,算法的关键问题是如何从老师模型转换的知识传授给学生模型。一个知识蒸馏系统由三个主要部分组成:知识,蒸馏算法,和师生架构

知识蒸馏框架

用于模型压缩的知识蒸馏类似于人类学习的方式。受此启发,最近的知识蒸馏方法已扩展到师生学习,相互学习,辅助教学,终身学习和自学。知识蒸馏的大多数扩展都集中在压缩深度神经网络上。由此产生的轻量级学生网络可以轻松部署在视觉识别,语音识别和自然语言处理(NLP)等应用程序中。此外,知识蒸馏中的知识从一种模型到另一种模型的转移可以扩展到其他任务,例如对抗攻击,数据增强,数据隐私和安全性。通过知识蒸馏的动机进行模型压缩,知识转移的思想已被进一步用于压缩训练数据,即数据集蒸馏,这将知识从大型数据集转移到小型数据集以减轻深度模型的训练负担

早期知识蒸馏框架通常包含一个或多个大型的预训练教师模型和小型的学生模型。教师模型通常比学生模型大得多。主要思想是在教师模型的指导下训练高效的学生模型以获得相当的准确性。来自教师模型的监督信号(通常称为教师模型学到的“知识”)可以帮助学生模型模仿教师模型的行为。

在典型的图像分类任务中,logit(例如深层神经网络中最后一层的输出)被用作教师模型中知识的载体,而训练数据样本未明确提供该模型。例如,猫的图像被错误地归类为狗的可能性非常低,但是这种错误的可能性仍然比将猫误认为汽车的可能性高很多倍。另一个示例是,手写数字2的图像与数字3相比,与数字7更相似。这种由教师模型学习的知识也称为暗知识(“dark knowledge”)

早期的知识蒸馏中转移 dark knowledge 的方法如下。给定对数向量 z作为深度模型的最后一个全连接层的输出,则zi是第 i 类的对数,则输入属于第 i 类的概率 pi可以为 由softmax 函数估算:

因此,通过教师模型获得的软目标的预测包含暗知识,并且可以用作监督者,以将知识从教师模型转移到学生模型。同样,one-hot 标签也称为硬目标。关于软目标和硬目标的直观示例如图3所示。此外,引入温度因子T来控制每个软目标的重要性

较高的温度会在各个类别上产生较弱的概率分布。具体来说,当 T→∞时,所有类别都具有相同的概率。当 T→0时,软目标变为 one-hot 标记,即硬目标。教师模型提供的软目标(distillation loss)和ground-truth label提供的硬目标(student loss)对于提高学生模型的绩效都非常重要。

定义蒸馏损失以匹配教师模型和学生模型之间的 logits ,即:

其中 zt和 zs分别是教师和学生模型的logits。教师模型的logits通过交叉熵梯度与学生模型的 logits 匹配, 然后可以将相对于 logit zsi的梯度评估为:

如果温度 T 比 logits 高得多,

则可以根据其泰勒级数近似得出:

如果进一步假设每个转移训练样本的 logits 为零 (比如

则上式可以简化为:

因此,根据上式,在高温和零均值 logits 的情况下,蒸馏损失等于匹配教师模型和学生模型之间的 logit ,即最小化:(zsi−zti)

因此,通过与高温匹配的 logit 进行蒸馏可以传达非常有用的知识信息,这些信息是由教师模型学到的以训练学生模型。

学生损失(student loss)定义为 ground truth 标签和学生模型的软对数之间的交叉熵:

代表交叉熵损失,y 是一个 ground truth 向量,其中只有一个元素为1,它表示转移训练样本的 ground truth 标签,其他元素为0。在蒸馏和学生损失中,两者均使用学生模型的相同 logit,但温度不同。温度在学生损失中为T = 1,在蒸馏损失中为T = t。最后,传统知识蒸馏的基准模型是蒸馏和学生损失的结合:

其中 x 是转移集上的训练输入,W是学生模型的参数,并且是调节参数。为了轻松理解知识蒸馏,下图显示了传统知识蒸馏与教师和学生模型联合的特定体系结构。在下图所示的知识蒸馏中,始终首先对教师模型进行预训练,然后再进行训练。仅使用来自预训练教师模型的软目标的知识来训练学生模型。实际上,这就是离线知识提炼与基于响应的知识。

he specific architecture of the benchmark knowledge distillation(Hinton et al., 2015)

知识

知识的三种形式

Response-Based Knowledge

基于响应的知识通常是指教师模型最后输出层的神经响应。主要思想是直接模仿教师模型的最终预测。基于响应的知识蒸馏简单但有效地进行了模型压缩,已被广泛用于不同的任务和应用中。最流行的基于响应的图像分类知识被称为软目标。基于响应的知识的蒸馏损失可以表示为

其中LKL表示Kullback-Leibler(KL)散度损失。典型的基于响应的KD模型如下图所示。基于响应的知识可用于不同类型的模型预测。例如,对象检测任务中的响应可能包含logit以及边界框的偏移量。在语义地标定位任务中,例如人体姿态估计,教师模型的响应可能包括每个地标的热图。最近,基于响应的知识得到了进一步的探索,以解决将地面标签信息作为条件目标的问题。

基于响应的知识

基于响应的知识的概念是简单易懂的,尤其是在“黑暗知识(dark knowledge)”的情况下。从另一个角度看,软目标的有效性类似于标签平滑或正则化器。但是,基于响应的知识通常依赖于最后一层的输出(例如,软目标),因此无法解决教师模型在监督,这对于使用非常深层神经网络的表示学习非常重要。由于 soft logits 实际上是类概率分布,因此基于响应的知识蒸馏也仅限于监督学习。

Feature-Based Knowledge

深度神经网络擅长通过增加抽象来学习多个级别的特征表示。这就是代表性学习。因此,最后一层的输出和中间层的输出,即特征图,都可以用作监督学生模型训练的知识。具体来说,来自中间层的基于特征的知识是基于响应的知识的良好扩展,尤其是对于更薄和更深的网络的训练而言。

中间表示法首先在 Fitnets 中引入,通过提供 hints,以改善学生模型的训练。主要思想是直接匹配老师和学生的特征激活。受此启发,已经提出了多种其他方法来间接匹配特征从原始特征图中得出了一个“注意图”来表达知识。Huang和Wang(2017)使用神经元选择性转移对注意力图进行了概括。Passalis和Tefas(2018)通过匹配特征空间中的概率分布来传递知识。为了更容易地转移教师知识,Kim等人。(2018年)引入了所谓的“因素”,作为一种更易于理解的中间表示形式。为了缩小师生之间的绩效差距,Jin等人。(2019)提出了路线约束式提示学习,该方法通过教师提示层的输出来监督学生。最近,Heo等。(2019c)建议使用隐藏神经元的激活边界进行知识转移。有趣的是,教师模型中间层的参数共享以及基于响应的知识也可以被用作教师知识(Zhou et al。,2018)。

通常,基于特征的知识转移的蒸馏损失可以用公式表达为:

其中 ft(x),fs(x) 分别是教师模型和学生模型的中间层的特征图。转换函数Φt(ft(x)),Φs(fs(x)),通常在教师和学生模型的特征图不是同一形状时应用。LF(.)表示用于匹配老师和学生模型的特征图的相似度函数。一个通用的基于特征的KD模型如下图所示。

本文还从特征类型,源层和蒸馏损失的角度总结了不同类型的基于特征的知识,如下表所示。

具体地说,L2(.),L1(.),LCE(.),LMMD(.) 分别表示l2-范数距离,l1-范数距离,交叉熵损失和最大平均差异损失。尽管基于特征的知识转移为学生模型的学习提供了有利的信息,但是如何有效地从教师模型中选择提示层和从学生模型中选择引导层仍然有待进一步研究。由于 hint 层和 guided 层的大小之间存在显着差异,因此还需要探索如何正确匹配教师和学生的特征表示

Relation-Based Knowledge

基于响应的知识和基于特征的知识都使用教师模型中特定层的输出。基于关系的知识进一步探索了不同层或数据样本之间的关系

为了探索不同特征图之间的关系,Yim等人。(2017)提出了一种解决方案流程(FSP),该流程由两层之间的Gram矩阵定义。FSP 矩阵总结了特征图对之间的关系。它是使用两层要素之间的内积来计算的。利用特征图之间的相关性作为蒸馏的知识,(Lee et al。,2018)提出了通过奇异值分解的知识蒸馏来提取特征图中的关键信息。为了利用多位教师的知识,Zhang和Peng(2018)分别以每个教师模型的 logits 和特征为节点,形成了两个图。具体来说,在知识转移之前,不同的教师的重要性和关系通过 logits 和表示图进行建模(Zhang and Peng,2018)。Lee and Song(2019)提出了基于多头图的知识蒸馏。图知识是通过多头注意力网络在任意两个特征图之间的内部数据关系。为了探索成对的提示信息,学生模型还模拟了教师模型的成对的提示层之间的互信息(Passalis等,2020b)。通常,基于特征图的关系的知识的蒸馏损失可以表示为:

其中 ft和 fs分别是老师和学生模型的特征图。教师模型选取的成对特征图表达为:^ft,ˇft,学生模型选择的成对特征图表达为:^fs,ˇfs。Ψt(.)和Ψs(.)是来自教师和学生模型的成对特征图的相似性函数。LR1(.)

表示教师和学生特征图之间的相关函数。

传统的知识转移方法通常涉及个人知识的提炼。老师的软目标直接提炼给学生。实际上,提炼的知识不仅包含特征信息,还包含数据样本的相互关系。具体来说,刘等。(2019g)通过实例关系图提出了一种鲁棒而有效的知识提炼方法。实例关系图中传递的知识包含实例特征,实例关系和特征空间转换跨层。Park等。(2019)提出了一种关系知识蒸馏,该知识蒸馏了实例关系中的知识。基于流形学习的思想,通过特征嵌入来学习学生网络,这保留了教师网络中间层中样本的特征相似性(Chen等人,2020b)。使用数据的特征表示将数据样本之间的关系建模为概率分布(Passalis和Tefas,2018; Passalis等,2020a)。师生的概率分布与知识转移相匹配。(Tung and Mori,2019)提出了一种保留相似性的知识提炼方法。尤其是,将教师网络中输入对的相似激活所产生的保持相似性的知识转移到学生网络中,并保持成对相似性。Peng等。(2019a)提出了一种基于相关一致性的知识蒸馏方法,其中蒸馏的知识既包含实例级信息,又包含实例之间的相关性。使用关联一致性进行蒸馏,学生网络可以了解实例之间的关联。

典型的基于实例关系的KD模型如下图所示。

可以将提取的知识从不同的角度进行分类,例如数据的结构化知识,有关输入功能的特权信息。下表显示了基于关系的知识的不同网络类别的摘要。

尽管最近提供了一些类型的基于关系的知识,但是如何根据特征图或数据样本对关系信息进行建模(作为知识)仍然值得进一步研究

蒸馏

蒸馏的几种形式:

离线蒸馏(Offline Distillation)

大多数以前的知识蒸馏方法都可以脱机工作。在常见的知识蒸馏中,知识从预先训练的教师模型转移到学生模型。因此,整个训练过程有两个阶段,即:

  • 大型教师模型是在蒸馏之前首先在一组训练样本上训练的。
  • 教师模型用于提取logit或中间特征形式的知识,然后用于指导蒸馏过程中学生模型的训练。

离线蒸馏的第一阶段通常不作为知识蒸馏的一部分进行讨论,即,假定教师模型是预先定义的。很少关注教师模型结构及其与学生模型的关系。因此,离线方法主要集中于改进知识转移的不同部分,包括知识的设计以及用于匹配特征或分布匹配的损失函数。离线方法的主要优点在于它们简单易行。例如,教师模型可以包含使用可能位于不同机器上的不同软件包训练的一组模型。可以提取知识并将其存储在缓存中。

离线蒸馏方法通常采用单向知识转移和两阶段训练程序。然而,不可避免的是,复杂的高容量教师模型具有很长的训练时间,而离线蒸馏中对学生模型的训练通常在教师模型的指导下是有效的。此外,大型教师和小型学生之间的能力差距始终存在,而学生在很大程度上依赖于教师。

在线蒸馏(Online Distillation)

尽管离线蒸馏方法简单有效,但离线蒸馏中的一些问题已引起研究界的越来越多的关注。为了克服离线蒸馏的局限性,提出了在线蒸馏以进一步改善学生模型的性能,特别是在没有大容量高性能教师模型的情况下。在在线蒸馏中,教师模型和学生模型同时更新,并且整个知识蒸馏框架是端到端可训练的。

在最近三年中,已经提出了多种在线知识蒸馏方法。具体来说,在深度相互学习中(Zhang等人,2018b),多个神经网络以协作方式工作。在训练过程中,任何一个网络都可以作为学生模型,其他模型可以作为老师。为了提高泛化能力,通过使用 soft Logits 的集合来扩展深度相互学习(Guo等,2020)。Chen等。(2020a)进一步将辅助同伴(auxiliary peers)和小组负责人(group leader)引入深度相互学习中,以形成一套多样化的同伴模型。为了降低计算成本,Zhu和Gong(2018)提出了一种多分支架构,其中每个分支表示一个学生模型,不同分支共享相同的骨干网络。Kim等人(2019b)没有使用Logits,引入了特征融合模块来构建教师分类器。谢等。(2019)用便宜的卷积运算代替了卷积层以形成学生模型。Anil等。(2018)使用在线蒸馏来训练大规模分布式神经网络,并提出了在线蒸馏的一种变体,称为共蒸馏。并行共蒸馏以相同的架构训练多个模型,并且通过从其他模型转移知识来训练任何一个模型。最近,提出了一种在线对抗知识蒸馏方法,以利用来自类别概率和特征图的知识,同时由鉴别者训练多个网络(Chung等,2020)。

在线蒸馏是一种具有高效并行计算功能的单阶段端到端训练方案。然而,现有的在线方法(例如,相互学习)通常不能解决在线设置中的高能力教师,这使得在在线设置中进一步探索教师与学生模型之间的关系成为一个有趣的话题。

自我蒸馏(Self-Distillation)

在自我蒸馏中,教师和学生模型采用相同的网络。这可以视为在线蒸馏的特殊情况。具体来说,Zhang等。(2019b)提出了一种新的自蒸馏方法,其中将来自网络较深部分的知识蒸馏为浅层部分。与(Zhang et al。,2019b)中的自蒸馏相似,有人提出了一种自注意蒸馏方法进行车道检测(Hou et al。,2019)。该网络利用其自身层的注意力图作为其较低层的蒸馏目标。快照蒸馏(Yang et al。,2019b)是自我蒸馏的一种特殊变体,其中网络早期(教师)的知识被转移到其后期(学生)以支持在同一时期内的监督训练过程网络。为了进一步减少通过提前退出的推理时间,Phuong和Lampert(2019b)提出了基于蒸馏的训练方案,其中提前退出层尝试在训练过程中模仿后续退出层的输出。

另外,最近提出了一些有趣的自蒸馏方法。具体来说,袁等。提出了一种基于标签平滑规则化(label smoothing regularization)分析的无教师知识蒸馏方法(Yuan et al。,2020)。Hahn和Choi提出了一种新颖的自我知识蒸馏方法,其中自我知识由预测概率而不是传统的软概率组成(Hahn和Choi,2019)。这些预测的概率由训练模型的特征表示来定义。它们反映了特征嵌入空间中数据的相似性。Yun等。提出了分类自知识蒸馏,以匹配同一模型中同一来源内的类内样本和扩充样本之间的训练模型的输出分布(Yun et al。,2020)。此外,采用Lee等人(2019a)提出的自蒸馏进行数据增强,并将增强的自知性蒸馏为模型本身。还采用自我蒸馏中以一对一地优化具有相同架构的深度模型(教师或学生网络)(Furlanello等,2018; Bagherinezhad等,2018)。每个网络都使用教师优化来蒸馏先前网络的知识。

此外,还可以从人类师生学习的角度直观地了解离线,在线和自我蒸馏中。离线蒸馏是指知识渊博的老师向学生传授知识;在线蒸馏是指老师和学生互相学习;自我蒸馏是指学生自己学习知识。而且,就像人类学习一样,这三种蒸馏由于自身的优势可以结合起来互相补充。

师生架构

在知识蒸馏中,师生架构是形成知识转移的通用载体。换句话说,从老师到学生的知识获取和蒸馏的质量也取决于如何设计老师和学生的网络。在人类学习习惯方面,我们希望学生能够找到合适的老师。因此,如何在知识蒸馏中完成知识的提取和提取,如何选择或设计合适的师生结构是非常重要而又困难的问题。最近,在蒸馏过程中,教师和学生的模型设置几乎都预先设置了不变的大小和结构,从而容易造成模型容量差距。但是,几乎不存在如何特别设计教师和学生的体系结构以及为什么由这些模型设置确定其体系结构的方法。在本节中,将讨论下图所示的教师模型和学生模型的结构之间的关系。

师生架构关系

知识蒸馏以前曾被设计为压缩深度神经网络的方法之一。深度神经网络的复杂性主要来自两个维度:深度和宽度。通常需要将知识从更深和更广的神经网络转移到更浅和更薄的神经网络。学生网络通常选择为:

  • 教师网络的简化版本,每层中的层数更少且通道更少。
  • 教师网络的量化版本,其中保留了网络的结构。
  • 具有高效基本操作的小型网络。
  • 具有优化的全局网络结构的小型网络。
  • 与教师使用同一网络。

大型深层神经网络和小型学生神经网络之间的模型能力差距会降低知识转移的速度。为了有效地将知识转移到学生网络,已提出了多种方法来控制模型复杂度的可控降低。具体来说,Mirzadeh等。(2020)引入了助教来减轻教师模型和学生模型之间的训练差距。(Gao et al。,2020)通过残差学习进一步缩小了差距,即使用辅助结构来学习残差。另一方面,最近的几种方法也集中在最小化学生模型和教师模型的结构差异上。例如,Polino等。(2018)将网络量化与知识蒸馏相结合,即学生模型很小,是教师模型的量化版本。Nowak和Corso(2018)提出了一种结构压缩方法,该方法涉及将多层学习的知识转移到单层。Wang等。(2018a)逐步执行从教师网络到学生网络的块状知识转移,同时保留接受领域。在在线环境中,教师网络通常是学生网络的集合,其中学生模型彼此共享相似的结构(或相同的结构)。

最近,深度可分离卷积已被广泛用于为移动或嵌入式设备设计有效的神经网络。受神经架构搜索(或NAS)成功的启发,通过基于有效元操作或块的全局结构搜索,小型神经网络的性能得到了进一步改善。此外,动态搜索知识转移机制的想法也出现在知识蒸馏中,例如,使用强化学习以数据驱动的方式自动删除冗余层,并在给定教师网络条件下搜索最佳学生网络

以前的大多数工作都着重于设计教师和学生模型的结构或它们之间的知识转移方案。为了使小型学生模型与大型教师模型很好地匹配,以提高知识蒸馏的绩效,自适应的师生学习体系结构是必要的。最近,在知识蒸馏中进行神经体系结构搜索(NAS)的想法,即在教师模型的指导下联合搜索学生结构和知识转移,将是未来研究的一个有趣课题。

蒸馏算法

对抗蒸馏(Adversarial Distillation)

多教师蒸馏(Multi-Teacher Distillation)

跨模态蒸馏(Cross-Modal Distillation)

图蒸馏(Graph-Based Distillation)

注意力蒸馏(Attention-Based Distillation)

由于注意力可以很好地反映卷积神经网络的神经元激活,因此在知识蒸馏中使用了一些注意力机制来改善学生网络的性能。在这些基于注意力的KD方法中,定义了不同的注意力转移机制,用于从教师网络向学生蒸馏知识网络。注意转移的核心是定义用于特征嵌入神经网络各层的关注图。也就是说,使用关注图功能来传递关于特征嵌入的知识

无数据蒸馏(Data-Free Distillation)

量化蒸馏(Quantized Distillation)

网络量化通过将高精度网络(例如32位浮点)转换为低精度网络(例如2位和8位)来降低神经网络的计算复杂度。同时,知识蒸馏的目的是训练小型模型以产生与复杂模型相当的性能。目前已经有多篇文章提出了在量化过程使用教师-学生框架中的一些KD方法。量化蒸馏方法的框架如下图所示。

具体来说,Polino等。(2018)提出了一种量化蒸馏方法,将知识转移到权重量化的学生网络中。在(Mishra和Marr,2018年)中,提出的量化KD被称为“学徒”。高精度教师网络将知识转移到小型的低精度学生网络。为了确保小型学生网络准确地模仿大型教师网络,首先在特征图上对高精度教师网络进行量化,然后将知识从量化教师转移到量化学生网络(Wei等人,2018年) )。Kim等。(2019a)提出了基于量化学生网络的自学,以及基于师生网络与知识转移的共同研究的量化意识知识蒸馏。此外,Shin等。(2019)使用蒸馏和量化进行了深度神经网络的经验分析,同时考虑了知识蒸馏的超参数,例如教师网络的大小和蒸馏温度。

终身蒸馏(Lifelong Distillation)

终身学习,包括持续学习和元学习,旨在以与人类相似的方式进行学习。它积累了以前学到的知识,还将学到的知识转移到未来的学习中。知识蒸馏提供了一种有效的方法来保存和转移所学知识,而不会造成灾难性的遗忘。最近,基于终生学习的KD变体数量不断增加。

关于元学习:Jang等。(2019)设计了元转移网络,可以确定在师生架构中转移的内容和地点。Flennerhag等。(2019)提出了一个轻量级的框架,称为Leap,用于通过将知识从一种学习过程转移到另一种学习过程来对任务流形进行元学习。Peng等。(2019b)设计了一种用于少拍图像识别的新知识转移网络架构。该体系结构同时合并了来自图像和先验知识的视觉信息。刘等。(2019e)提出了一种用于图像检索的语义感知知识保存方法。从图像模态和语义信息中获得的教师知识将得到保存和转移。

此外,为了解决终身学习中的灾难性遗忘问题,全局蒸馏(Lee等人,2019b),基于知识蒸馏的终身GAN(Zhai等人,2019),多模型蒸馏(Zhou等人,2020) )和其他基于KD的方法(Li and Hoiem,2017; Shmelkov et al。,2017)已经开发出来,以提取学习到的知识并在新任务上教给学生网络。

NAS蒸馏(NAS-Based Distillation)

神经体系结构搜索(NAS)是最流行的自动机器学习(或AutoML)技术之一,旨在自动识别深度神经模型并自适应地学习适当的深度神经结构。在知识蒸馏中,知识转移的成功不仅取决于老师的知识,还取决于学生的架构。但是,大型教师模型和小型学生模型之间可能存在能力差距,从而使学生难以向老师学习。为了解决这个问题,已经有工作采用 NAS 来找到 oracle-based 和 architecture-aware 的合适的学生架构实现知识蒸馏。此外,知识蒸馏被用于提高神经架构搜索的效率,例如,具有蒸馏架构知识的 NAS(AdaNAS)以及教师指导的架构搜索(TGSA)。在TGSA中,指导每个体系结构搜索步骤以模仿教师网络的中间特征表示,通过有效搜索学生的可能结构,老师可以有效地监督特征转移。

性能对比

知识蒸馏是用于模型压缩的出色技术。通过捕获教师的知识并在教师学习中使用蒸馏策略,它可以提高轻量级学生模型的性能。近来,许多知识蒸馏方法致力于改善性能,尤其是在图像分类任务中。在本节中,为了清楚地证明知识蒸馏的有效性,总结了一些典型的KD方法在两个流行的图像分类数据集上的分类性能。

这两个数据集是 CIFAR10 和 CIFAR100,分别由分别来自 10 和 100 个类别的 32×32 RGB 图像组成。两者都具有 50000 个训练图像和 10000 个测试图像,并且每个类具有相同数量的训练和测试图像。为了公平比较,KD 方法的实验分类准确度结果(%)直接来自相应的原始论文,如 CIFAR10 的表5和 CIFAR100 的表6所示。当使用不同类型的知识,蒸馏方案和教师/学生模型的结构时,报告了不同方法的性能。具体而言,括号中的准确度是教师和学生模型的分类结果,它们是经过单独训练的。应该注意的是,DML 和 DCM 的成对精度是在线蒸馏后师生的表现。

总结和讨论

近年来,知识蒸馏及其应用引起了相当大的关注。本文从知识,蒸馏方案,师生架构,蒸馏算法,性能比较和应用的角度对知识蒸馏进行了全面综述。下面,讨论知识蒸馏的挑战,并对知识蒸馏的未来研究提供一些见识。

挑战

对于知识蒸馏,关键是:1)从教师那里提取丰富的知识;2)从教师那里转移知识以指导学生的训练。因此,本文从以下几个方面讨论知识蒸馏的挑战:知识的均等性,蒸馏的类型,师生体系结构的设计以及知识蒸馏的理论基础

大多数KD方法利用各种知识的组合,包括基于响应的知识,基于特征的知识和基于关系的知识。因此,重要的是要了解每种知识类型的影响,并知道不同种类的知识如何以互补的方式互相帮助。例如,基于响应的知识具有相似的动机来进行标签平滑和模型正则化; 基于特征的知识通常用于模仿教师的中间过程,而基于关系的知识则用于捕获不同样本之间的关系。为此,在统一和互补的框架中对不同类型的知识进行建模仍然是挑战。例如,来自不同提示层的知识可能对学生模型的训练有不同的影响:1)基于响应的知识来自最后一层;2)来自较深的提示/指导层的基于特征的知识可能会遭受过度规范化的困扰。

如何将丰富的知识从老师传授给学生是知识蒸馏的关键一步。通常,现有的蒸馏方法可分为离线蒸馏,在线蒸馏和自蒸馏。离线蒸馏通常用于从复杂的教师模型中转移知识,而教师模型和学生模型在在线蒸馏和自我蒸馏的设置中具有可比性。为了提高知识转移的效率,应进一步研究模型复杂性与现有蒸馏方案或其他新颖蒸馏方案之间的关系

目前,大多数KD方法都将重点放在新型知识或蒸馏损失函数上,而对师生体系结构的设计研究不足。实际上,除了知识和蒸馏算法之外,教师和学生的结构之间的关系也显着影响知识蒸馏的性能。例如,一方面,最近的一些研究发现,由于教师模型和学生模型之间的模型能力差距,学生模型无法从某些教师模型中学习到很多东西;另一方面,从对神经网络容量的一些早期理论分析来看,浅层网络能够学习与深层神经网络相同的表示。因此,设计有效的学生模型或构建合适的教师模型仍然是知识蒸馏中的难题。

尽管有大量的知识蒸馏方法和应用,但对知识蒸馏的理解(包括理论解释和实证评估)仍然不够。例如,蒸馏可以被视为一种获得特权信息的学习形式。线性教师模型和学生模型的假设使得能够通过蒸馏来研究学生学习特征的理论解释。此外,Cho和Hariharan(2019)对知识蒸馏的功效进行了一些实证评估和分析。但是,仍然很难获得对知识提升的可概括性的深刻理解,尤其是如何衡量知识的质量或师生架构的质量。

未来发展方向

为了提高知识蒸馏的性能,最重要的因素包括:怎样设计师生网络体系结构,从老师网络中学习什么样的知识,以及在何处提炼到学生网络中

深层神经网络的模型压缩和加速方法通常分为四个不同类别,即模型剪枝和量化,低秩分解,紧凑型卷积滤波器和知识蒸馏。在现有的知识蒸馏方法中,只有很少的相关工作讨论了知识蒸馏与其他压缩方法的结合。例如,量化知识蒸馏可以看作是一种参数修剪方法,它将网络量化整合到师生架构中。因此,为了学习用于在便携式平台上部署的高效轻巧的深度模型,由于大多数压缩技术都需要重新训练/微调过程,因此需要通过知识蒸馏和其他压缩技术进行混合压缩的方法。此外,如何决定使用不同压缩方法的正确顺序将是未来研究的有趣话题

除了用于深度神经网络加速的模型压缩之外,由于教师架构上知识转移的自然特性,知识蒸馏还可以用于其他问题。最近,知识蒸馏已应用于数据隐私和安全性,深度模型的对抗攻击,跨模态,多个域,灾难性遗忘,加速深度模型的学习,神经结构搜索的效率,自我监督和数据增强。另一个有趣的例子是,知识从小型教师网络向大型学生网络的转移可以加速学生的学习。这与传统的知识蒸馏有很大不同。大型模型从未标记的数据中学习的特征表示也可以通过蒸馏来监督目标模型。为此,将知识蒸馏扩展到其他目的和应用可能是有意义的未来方向。

知识蒸馏的学习类似于人类的学习。将知识转移推广到经典和传统的机器学习方法是可行的。例如,基于知识蒸馏的思想,传统的两阶段分类适用于单老师单学生问题。此外,知识蒸馏可以灵活地部署到各种学习方案中,例如对抗学习,自动机器学习,终身学习,和强化学习。因此,将来将知识蒸馏与其他学习方案整合起来以应对实际挑战将是有用的。

Cyclegan实现赛博朋克风格转换

偶然看到有个b站视频,是关于如何实现一个图片的赛博朋克风,通过调整色调就可以实现。然后就看到有博主使用python的opencv库实现这个效果。

原始
风格化

因此,我想使用Cyclegan生成对抗网络实现风格迁移。

cyclegan网络

先是准备数据集,我在https://wallhaven.cc/网站爬取了了大约2000张赛博朋克风的图片,因为设备条件有限,图片都是320大小的:

另外我在该网站爬取了了现实城市和风景的图片大概两千多张:

利用论文作者提供的github代码,并修改训练参数、准备数据集,进行训练:(训练的中间结果)

原始
赛博化
原始
转换

因为这个网络分辨率比较低,所以效果一般,此外,数据集也有一些问题,中间有些脏数据。有些图片并不是赛博朋克风格。

医学分割评价指标

可以看到,常见的Dice、mIou等指标作为图像分割的主要指标:

1.5.1、混淆矩阵

TP:真阳性(True Positive),被预测为正样本,事实上也是正样本

TN:真阴性(True Negative),被预测为负样本,事实上也是负样本

FP:假阳性(False Positive),被判定为正样本,但事实上是负样本(误报)

FN:假阴性(False Negative),被判定为负样本,但事实上是正样本(漏报)

1.5.2、Dice系数(dice similarity coefficient)(常用)

计算两个样本间相似度,现多用于三维医学图像分割领域

15.3、交并比(intersection over union,IoU)

预测(predict label)与真值(ground truth) 集合的交集与两个集合的并集之比

1.5.4、均交并比(mean intersection over union,mIoU)(常用)

是对所有类的IoU取均值得到的,在语义分割广为使用。

1.5.5、像素精度(Pixel Accuracy)

标记正确的像素占总像素的百分比

1.5.6、召回率(Recall)

预测值为正且真实值也为正在真实值为正的所有样本中所占的比例

1.5.7、频权交并比(FWIoU)

频权交并比(Frequency Weighted Intersection-over-Union, FWIoU)是根据每一类出现的频率设置权重,权重乘以每一类的IoU并进行求和。

AI 文本生成图片工具汇总

1、https://openai.com/dall-e-2/

2、 https://imagen.research.google/

3、https://github.com/alembics/disco-diffusion

试玩: https://colab.research.google.com/github/alembics/disco-diffusion/blob/main/Disco_Diffusion.ipynb

4、https://github.com/jina-ai/discoart

5、https://replicate.com/nightmareai/disco-diffusion(API)

6、https://midjourney.gitbook.io/docs/(app)

7、https://www.midjourney.com/showcase/

7、https://blog.tiamat.ai/about/

RefineNet

论文地址(2016):RefineNet: Multi-Path Refinement Networks with Identity Mappings for High-Resolution Semantic Segmentation

对于高分辨率的图像分割问题,基于编解码结构的分割网络虽然有效,但因为卷积和池化下采样的存在,特征图在变小的过程会逐渐损失一些细粒度的信息,非常不利于高分辨率图像的像素稠密预测。针对这个问题,此前的各项研究归纳而言提出了如下三点处理方法:

(1)类似于FCN和UNet,直接使用转置卷积上采样来恢复图像像素,但转置卷积对于下采样过程中丢失的低层信息的恢复能力有限。

(2)使用空洞卷积,通过给常规卷积中插入空洞的方式来增大卷积感受野,并且没有缩小图像尺寸,但这种方式计算开销增大,模型运行效率降低,并且空洞卷积作为一种较为粗糙的子采样(sub-sampling),也会存在图像重要信息损失的问题。

(3)使用跳跃连接。类似于UNet中编解码器间的跳跃连接,直接将编码器每一层的特征图连接到解码器上采样结果上,能够对解码图像进行信息补充。

相关研究认为,对于编解码结构而言,所有层次的特征对语义分割都是有帮助的。高层次的特征用于识别图像中的语义信息,低层次的特征则有助于恢复高分辨率图像的边界细节。但如何有效利用中间层次的信息值得进一步探索,前述充分使用跳跃连接的方法或许会更加有效。基于此,研究人员提出了一种针对高分辨率图像语义分割的多层次特征精细化网络:RefineNet。提出RefineNet的论文为RefineNet: Multi-Path Refinement Networks for High-Resolution Semantic Segmentation,该网络基于ResNet结构和跳跃连接,使用多路径的精细化网络结构来获取最佳的分割结果,是高分辨率图像分割的经典网络。

RefineNet简要结构如图5-9所示。RefineNet总体上仍然是编解码结构,编码器部分根据预训练的ResNet网络划分了4个卷积块,与之对应的是级联了4个RefineNet单元到解码器部分。每个编码器ResNet卷积块的输出特征图都会被连接到对应的RefineNet单元,如图中的ResNet-4连接到RefineNet-4单元,到了RefineNet-3单元,除了接收来自ResNet-3的输出外,还需要接收RefineNet-4单元的输出,对于RefineNet-3单元而言就构成了两路径的输入。这样层层向上级联,就构成了多路径的RefineNet。其中编码器中每个特征图到解码器RefineNet单元的连接也叫长程残差连接(long-range residual connections)。

上图仅给出了包含了编码器在内的RefineNet简要结构,而RefineNet单元的具体结构如下图所示。一个RefineNet单元由残差卷积单元(Residual convolution unit,RCU)、多分辨率融合(Multi-resolution Fusion)和链式残差池化(Chained Residual Pooling,CRP)组成。RCU较为简单,就是常规的ResNet结构,每一个输入路径都会经过两次RCU操作后再输出到下一个单元。RCU的跳跃连接在RefineNet中也被称为短程残差连接(short-range residual connections)。紧接着是一个多分辨率特征图融合层,将上一层RCU输出的多路径特征图经过一个33的卷积和上采样操作后进行加总,得到合并后的特征图。最后是一个CRP单元,这也是RefineNet的特色结构,通过3个链式的池化和卷积残差组合来捕捉大图像区域的背景上下文信息。将CRP之后得到特征图再经过一次RCU即可到最终的分割输出。

作为一种针对高分辨率图像的精细化分割网络,RefineNet的结构设计无疑是成功的,当时在多个公开数据集上均取得了SOTA性能表现。这种多路径的精细化网络能够通过迭代精炼的方式将粗糙的语义特征精炼为细粒度的语义特征。其次,基于长短程的残差连接能够使得模型进行端到端的训练,推理时也非常高效。最后,链式残差池化也使得网络能够更好的捕捉大图像的上下文信息。

RefineNet代码完整实现可参考:

https://github.com/DrSleep/refinenet-pytorch

其中关于RCU和CRP模块的实现如下代码所示。

class RCUBlock(nn.Module):
    
    def __init__(self, in_planes, out_planes, n_blocks, n_stages):
        super(RCUBlock, self).__init__()
        for i in range(n_blocks):
            for j in range(n_stages):
                setattr(self, '{}{}'.format(i + 1, stages_suffixes[j]),
                        conv3x3(in_planes if (i == 0) and (j == 0) else out_planes,
                                out_planes, stride=1,
                                bias=(j == 0)))
        self.stride = 1
        self.n_blocks = n_blocks
        self.n_stages = n_stages
    
    def forward(self, x):
        for i in range(self.n_blocks):
            residual = x
            for j in range(self.n_stages):
                x = F.relu(x)
                x = getattr(self, '{}{}'.format(i + 1, stages_suffixes[j]))(x)
            x += residual
        return 
        
class CRPBlock(nn.Module):

    def __init__(self, in_planes, out_planes, n_stages):
        super(CRPBlock, self).__init__()
        for i in range(n_stages):
            setattr(self, '{}_{}'.format(i + 1, 'outvar_dimred'),
                    conv3x3(in_planes if (i == 0) else out_planes,
                            out_planes, stride=1,
                            bias=False))
        self.stride = 1
        self.n_stages = n_stages
        self.maxpool = nn.MaxPool2d(kernel_size=5, stride=1, padding=2)

    def forward(self, x):
        top = x
        for i in range(self.n_stages):
            top = self.maxpool(top)
            top = getattr(self, '{}_{}'.format(i + 1, 'outvar_dimred'))(top)
            x = top + x
        return x

预测效果:

关于NLP多标签文本分类的一些思路–(待更新)

作为刚入门的小白,有必要去记录一些NLP分类任务的小trick,感觉对于涨点提分十分有用。这个文章后面有新的想法会持续更新

华为有一个NLP关于医学电子病历的疾病多标签分类比赛,因为之前比较少去做NLP方向的东西,仅仅是学习过相关rnn、transformer、bert论文呢,所以,参赛纯粹是为了了解了解NLP方向,好在nlp做文本分类算是比较简单的下游任务,但在参赛过程中,会发现,其实对于文本分类来说,基本的bert-base的效果不是很好,但其实感觉不是出在模型架构方面,对于简单的分类任务,一个12层的bert应该适足以胜任了,因此将注意力不要过多的放在模型结构上。

任务说明

本赛题是利用病人电子病历文本信息推断出其可能患有疾病的疾病诊断任务。电子病历文本信息主要包括病人的性别、年龄、主诉、现病史、既往史、体格检查和辅助检查。标签信息为病人的出院诊断疾病。本赛题任务需要根据病人的电子病历文本信息推断出病人所患有的全部疾病。(注:病人的出院诊断疾病并不是单一的) 

模型输出格式:

{ “ZY000001”: [“高血压”, “肺气肿”, “先天性心脏病”]}

评分标准

本赛题采用macro F1作为评价指标。评价指标计算公式如下:

对于每一个预测的疾病有真阳性(True Positive,TP),假阳性(False Positive,FP),假阴性(False Negative),真阴性(True Negative),n表示n种疾病。

这个得分最高在 0.83左右。我也试了几次,但到0.57就没再动过了…..,后面准备去尝试下下面的方法,看看有啥效果吗。

思路:

在github中找到一个分类会议任务的比赛ppt模型讲解:

https://github.com/TJBioMedNLP/chip2019task3

废话说完,来点或许能提分的干货:

数据方面:

1、数据清洗(很多脏数据)、数据增强

说实话,感觉这个比赛的要点就是数据处理,想提分就看你的数据的好坏,现在是真的意识到数据处理对于一个模型的影响之大了,后面要着重关注下这方面了。

本次提供的训练集中出现了一些不需要诊断的疾病:睾丸鞘膜积液、宫颈炎性疾病、口腔粘膜溃疡、头部外伤、急性阴道炎、女性盆腔炎、急性气管炎,需要自己去将该类数据清洗,另外,通过数据统计分析,可以获得训练集中各个label的数量严重不平衡,如何处理也是一个问题,是否可以通过数据集增强,提高某些类别的测试数据。

另外 性别、年龄、主诉、现病史、既往史、体格检查和辅助检查 等长度会超出模型的最大长度,如何解决、最大化利用上述信息也是一个问题。我做过对这些数据做过分析,对于 性别、年龄、主诉、现病史、既往史、体格检查和辅助检查 等 统计过平均长度、最大最小长度,从几十到几百不等。另外,去看下数据集就可以看到,有大量的标点符号和短语。另外,据说emr_id这个信息也是一个重要的信息???我一脸震惊。此外年龄和性别也会影响。

其他:

1、如果训练时使用文本长度为n,测试使用比n长一些的长度,可以涨点分

2、模型预训练会提分(或者找相关领域预训练模型)

这里我找了两个预训练模型:

https://huggingface.co/trueto/medbert-base-chinese

https://huggingface.co/nghuyong/ernie-health-zh

3、增大训练时输入编码长度(文本序列长度),当然,需要显卡的性能支持

all_tokens = self.tokenizer.encode_plus(content, max_length=pad_size, padding=”max_length”, truncation=True)

可以提高max_length的大小,但是比较吃显卡。

4、交叉验证

交叉验证经常用于给定的数据集训练、评估和最终选择机器学习模型,因为它有助于评估模型的结果在实践中如何推广到独立的数据集,最重要的是,交叉验证已经被证明产生比其他方法更低的偏差的模型。重复的k折交叉验证,主要是会重复进行n次的k折交叉验证,这样会产生n次结果,一般通过平均方法或者(投票规则)得到最后的结果

 第一种是简单交叉验证,所谓的简单,是和其他交叉验证方法相对而言的。首先,我们随机的将样本数据分为两部分(比如: 70%的训练集,30%的测试集),然后用训练集来训练模型,在测试集上验证模型及参数。接着,我们再1把样本打乱,重新选择训练集和测试集,继续训练数据和检验模型。最后我们选择损失函数评估最优的模型和参数。

选择分层k-折交叉验证:

分层采样就是在每一份子集中都保持原始数据集的类别比例,保证采样数据跟原始数据的类别分布保持一致,该方法在有效的平衡方差和偏差。当针对不平衡数据时,使用随机的K-fold交叉验证,可能出现在子集中叫少的类别的分布与原始类别分布不一致。因此,针对不平衡数据往往使用stratified k-fold交叉验证。

当训练数据集不能代表整个数据集分布是,这时候使用stratified k折交叉验证可能不是好的方法,而可能比较适合使用简单的重复随机k折交叉验证。

  • 1.把整个数据集随机划分成k份
  • 2.用其中k-1份训练模型,然后用第k份验证模型
  • 3.记录每个预测结果获得的误差
  • 4.重复这个过程,知道每份数据都做过验证集
  • 5.记录下的k个误差的平均值,被称为交叉验证误差。可以被用做衡量模型性能的标准
>>> from sklearn.model_selection import StratifiedKFold
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([0, 0, 1, 1])
>>> skf = StratifiedKFold(n_splits=2)
>>> skf.get_n_splits(X, y)
2
>>> print(skf)  
StratifiedKFold(n_splits=2, random_state=None, shuffle=False)
>>> for train_index, test_index in skf.split(X, y):
...    print("TRAIN:", train_index, "TEST:", test_index)
...    X_train, X_test = X[train_index], X[test_index]
...    y_train, y_test = y[train_index], y[test_index]
TRAIN: [1 3] TEST: [0 2]
TRAIN: [0 2] TEST: [1 3]

具体来说:

以k-fold CV为例:仍然是把原始数据集分成训练集和测试集,但是训练模型的时候不使用测试集。最常见的一个叫做k_fold CV

  • 具体来说就是把训练集平分为k个fold,其中每个fold依次作为测试集、余下的作为训练集,进行k次训练,得到共计k组参数。取k组参数的均值作为模型的最终参数
  1. 优点:充分压榨了数据集的价值。在样本集不够大的情况下尤其珍贵。
  2. 缺点:运算起来花时间。

K折交叉验证训练单个模型:

通过对 k 个不同分组训练的结果进行平均来减少方差,因此模型的性能对数据的划分就不那么敏感,经过多次划分数据集,大大降低了结果的偶然性,从而提高了模型的准确性。具体做法如下:

  • step1:不重复抽样将原始数据随机分为 k 份。
  • step2:每一次挑选其中 1 份作为验证集,剩余 k-1 份作为训练集用于模型训练。一共训练k个模型。
  • step3:在每个训练集上训练后得到一个模型,用这个模型在测试集上测试,计算并保存模型的评估指标,
  • step4:计算 k 组测试结果的平均值作为模型最终在测试集上的预测值,求k 个模型评估指标的平均值,并作为当前 k 折交叉验证下模型的性能指标。

6、模型融合

模型融合:通过融合多个不同的模型,可能提升机器学习的性能。这一方法在各种机器学习比赛中广泛应用, 也是在比赛的攻坚时刻冲刺Top的关键。而融合模型往往又可以从模型结果,模型自身,样本集等不同的角度进行融合。即多个模型的组合可以改善整体的表现。集成模型是一种能在各种的机器学习任务上提高准确率的强有力技术。

模型融合是比赛后期一个重要的环节,大体来说有如下的类型方式:

1. 简单加权融合:

  • 回归(分类概率):算术平均融合(Arithmetic mean),几何平均融合(Geometric mean);
  • 分类:投票(Voting);
  • 综合:排序融合(Rank averaging),log融合。

2. stacking/blending:

  • 构建多层模型,并利用预测结果再拟合预测。

3. boosting/bagging:

  • 多树的提升方法,在xgboost,Adaboost,GBDT中已经用到。

平均法(Averaging)

基本思想:对于回归问题,一个简单直接的思路是取平均。稍稍改进的方法是进行加权平均。权值可以用排序的方法确定,举个例子,比如A、B、C三种基本模型,模型效果进行排名,假设排名分别是1,2,3,那么给这三个模型赋予的权值分别是3/6、2/6、1/6。

平均法或加权平均法看似简单,其实后面的高级算法也可以说是基于此而产生的,Bagging或者Boosting都是一种把许多弱分类器这样融合成强分类器的思想。

简单算术平均法:Averaging方法就多个模型预测的结果进行平均。这种方法既可以用于回归问题,也可以用于对分类问题的概率进行平均。

加权算术平均法:这种方法是平均法的扩展。考虑不同模型的能力不同,对最终结果的贡献也有差异,需要用权重来表征不同模型的重要性importance。

投票法(voting)

基本思想:假设对于一个二分类问题,有3个基础模型,现在我们可以在这些基学习器的基础上得到一个投票的分类器,把票数最多的类作为我们要预测的类别。

绝对多数投票法:最终结果必须在投票中占一半以上。

相对多数投票法:最终结果在投票中票数最多。

加权投票法:每个弱学习器的分类票数乘以权重,并将各个类别的加权票数求和,最大值对应的类别即最终类别。

硬投票:对多个模型直接进行投票,不区分模型结果的相对重要度,最终投票数最多的类为最终被预测的类。

软投票:增加了设置权重的功能,可以为不同模型设置不同权重,进而区别模型不同的重要度。

堆叠法(Stacking)

基本思想

stacking 就是当用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集,来学习一个新的学习器。对不同模型预测的结果再进行建模。

将个体学习器结合在一起的时候使用的方法叫做结合策略。对于分类问题,我们可以使用投票法来选择输出最多的类。对于回归问题,我们可以将分类器输出的结果求平均值。

上面说的投票法和平均法都是很有效的结合策略,还有一种结合策略是使用另外一个机器学习算法来将个体机器学习器的结果结合在一起,这个方法就是Stacking。在stacking方法中,我们把个体学习器叫做初级学习器,用于结合的学习器叫做次级学习器或元学习器(metalearner),次级学习器用于训练的数据叫做次级训练集。次级训练集是在训练集上用初级学习器得到的。

  • step1:训练T个初级学习器,要使用交叉验证的方法在Train Set上面训练(因为第二阶段建立元学习器的数据是初级学习器输出的,如果初级学习器的泛化能力低下,元学习器也会过拟合)
  • step2:T个初级学习器在Train Set上输出的预测值,作为元学习器的训练数据D,有T个初级学习器,D中就有T个特征。D的label和训练初级学习器时的label一致。
  • step3:T个初级学习器在Test Set上输出的预测值,作为训练元学习器时的测试集,同样也是有T个模型就有T个特征。
  • step4:训练元学习器,元学习器训练集D的label和训练初级学习器时的label一致。

混合法(Blending)

基本思想:Blending采用了和stacking同样的方法,不过只从训练集中选择一个fold的结果,再和原始特征进行concat作为元学习器meta learner的特征,测试集上进行同样的操作。

把原始的训练集先分成两部分,比如70%的数据作为新的训练集,剩下30%的数据作为测试集。

  • 第一层,我们在这70%的数据上训练多个模型,然后去预测那30%数据的label,同时也预测test集的label。
  • 在第二层,我们就直接用这30%数据在第一层预测的结果做为新特征继续训练,然后用test集第一层预测的label做特征,用第二层训练的模型做进一步预测。

Blending训练过程:

  1. 整个训练集划分成训练集training sets和验证集validation sets两个部分;
  2. 在training sets上训练模型;
  3. 在validation sets和test sets上得到预测结果;
  4. 将validation sets的原始特征和不同基模型base model预测得到的结果作为新的元学习器meta learner的输入,进行训练 ;
  5. 使用训练好的模型meta learner在test sets以及在base model上的预测结果上进行预测,得到最终结果。

Stacking与Blending的对比:

优点在于:

  • blending比stacking简单,因为不用进行k次的交叉验证来获得stacker feature
  • blending避开了一个信息泄露问题:generlizers和stacker使用了不一样的数据集

缺点在于:

  • blending使用了很少的数据(第二阶段的blender只使用training set10%的量)
  • blender可能会过拟合
  • stacking使用多次的交叉验证会比较稳健

Bagging

基本思想:Bagging基于bootstrap(自采样),也就是有放回的采样。训练子集的大小和原始数据集的大小相同。Bagging的技术使用子集来了解整个样本集的分布,通过bagging采样的子集的大小要小于原始集合。

  • 采用bootstrap的方法基于原始数据集产生大量的子集
  • 基于这些子集训练弱模型base model
  • 模型是并行训练并且相互独立的
  • 最终的预测结果取决于多个模型的预测结果

Bagging是一种并行式的集成学习方法,即基学习器的训练之间没有前后顺序可以同时进行,Bagging使用“有放回”采样的方式选取训练集,对于包含m个样本的训练集,进行m次有放回的随机采样操作,从而得到m个样本的采样集,这样训练集中有接近36.8%的样本没有被采到。按照相同的方式重复进行,我们就可以采集到T个包含m个样本的数据集,从而训练出T个基学习器,最终对这T个基学习器的输出进行结合。

Boosting

基础思想:Boosting是一种串行的工作机制,即个体学习器的训练存在依赖关系,必须一步一步序列化进行。Boosting是一个序列化的过程,后续模型会矫正之前模型的预测结果。也就是说,之后的模型依赖于之前的模型。

其基本思想是:增加前一个基学习器在训练训练过程中预测错误样本的权重,使得后续基学习器更加关注这些打标错误的训练样本,尽可能纠正这些错误,一直向下串行直至产生需要的T个基学习器,Boosting最终对这T个学习器进行加权结合,产生学习器委员会。

Boosting训练过程:

  • 基于原始数据集构造子集
  • 初始的时候,所有的数据点都给相同的权重
  • 基于这个子集创建一个基模型
  • 使用这个模型在整个数据集上进行预测
  • 基于真实值和预测值计算误差
  • 被预测错的观测值会赋予更大的权重
  • 再构造一个模型基于之前预测的误差进行预测,这个模型会尝试矫正之前的模型
  • 类似地,构造多个模型,每一个都会矫正之前的误差
  • 最终的模型(strong learner)是所有弱学习器的加权融合

7、损失函数,注意不同类别的权重(使用F1_loss、Hamming Loss、数据类别分布不均,如何解决长尾分布(加权损失、先验权重))

相关论文: sigmoidF1: A Smooth F1 Score Surrogate Loss for Multilabel Classification

平时我们在做多标签分类,或者是多分类的时候,经常使用的loss函数一般是binary_crossentropy(也就是log_loss)或者是categorical_crossentropy,不过交叉熵其实还是有点问题的,在多标签分类的问题里,交叉熵并非是最合理的损失函数。在多标签分类的问题中,我们最终评价往往会选择F1分数作为评价指标,那么是否能直接将F1-score制作成为一个loss函数呢?当然是可以的。

在多分类/多标签分类中,F1-score有两种衍生格式,分别是micro-F1和macro-F1。是两种不同的计算方式。

micro-F1是先计算先拿总体样本来计算出TP、TN、FP、FN的值,再使用这些值计算出percision和recall,再来计算出F1值。

macro-F1则是先对每一种分类,视作二分类,计算其F1值,最后再对每一个分类进行简单平均。

简单的记的话其实是这样的,micro(微观)与macro(宏观)的含义其实是,micro-F1是在样本的等级上做平均,是最小颗粒度上的平均了,所以是微观。macro-F1是在每一个分类的层面上做平均,每一个分类都包含很多样本,所以相对是宏观。

作为loss函数的F1

F1-score改造成loss函数相对较为简单,F1是范围在0~1之间的指标,越大代表性能越好,在作为loss时只需要取(1-F1)即可。

一下是keras中的实现:

(这里的K就是keras的后端,一般来说就是tensorflow)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
K = keras.backend
def f1_loss(y_true, y_pred):
#计算tp、tn、fp、fn
tp = K.sum(K.cast(y_true*y_pred, ‘float’), axis=0)
tn = K.sum(K.cast((1-y_true)*(1-y_pred), ‘float’), axis=0)
fp = K.sum(K.cast((1-y_true)*y_pred, ‘float’), axis=0)
fn = K.sum(K.cast(y_true*(1-y_pred), ‘float’), axis=0)

#percision与recall,这里的K.epsilon代表一个小正数,用来避免分母为零
p = tp / (tp + fp + K.epsilon())
r = tp / (tp + fn + K.epsilon())

#计算f1
f1 = 2*p*r / (p+r+K.epsilon())
f1 = tf.where(tf.is_nan(f1), tf.zeros_like(f1), f1)#其实就是把nan换成0
return 1 – K.mean(f1)

这个函数可以直接在keras模型编译时使用,如下:

1
2
3
4
# 类似这样
model.compile(optimizer=tf.train.AdamOptimizer(0.003),
loss=f1_loss,
metrics=[‘acc’,’mae’])
 
def f1_loss(predict, target):
    predict = torch.sigmoid(predict)
    predict = torch.clamp(predict * (1-target), min=0.01) + predict * target
    tp = predict * target
    tp = tp.sum(dim=0)
    precision = tp / (predict.sum(dim=0) + 1e-8)
    recall = tp / (target.sum(dim=0) + 1e-8)
    f1 = 2 * (precision * recall / (precision + recall + 1e-8))
    return 1 - f1.mean()

8、考虑将多分类 变成多个二分类任务

9、除了bert模型,还可以尝试Performer、ernie-health

Performer 是ICLR 2021的新paper,在处理长序列预测方面有非常不错的结果,速度快,内存小,在LRA(long range arena 一个统一的benchmark)上综合得分不错。

论文:https://arxiv.org/pdf/2009.14794.pdf

ernie-health :Building Chinese Biomedical Language Models via Multi-Level Text Discrimination
中文题目:基于多层次文本辨析构建中文生物医学语言模型
论文地址:https://arxiv.org/pdf/2110.07244.pdf
领域:自然语言处理,生物医学
发表时间:2021
作者:Quan Wang等,百度
模型下载:https://huggingface.co/nghuyong/ernie-health-zh
模型介绍:https://github.com/PaddlePaddle/Research/tree/master/KG/eHealth
模型代码:https://github.com/PaddlePaddle/PaddleNLP/tree/develop/model_zoo/ernie-health

10、NLP中的对抗训练 (添加的扰动是微小)

  1. 提高模型应对恶意对抗样本时的鲁棒性;
  2. 作为一种regularization,减少overfitting,提高泛化能力。

对抗训练其实是“对抗”家族中防御的一种方式,其基本的原理呢,就是通过添加扰动构造一些对抗样本,放给模型去训练,以攻为守,提高模型在遇到对抗样本时的鲁棒性,同时一定程度也能提高模型的表现和泛化能力。

那么,什么样的样本才是好的对抗样本呢?对抗样本一般需要具有两个特点:

  1. 相对于原始输入,所添加的扰动是微小的;
  2. 能使模型犯错。

NLP中的两种对抗训练 + PyTorch实现

a. Fast Gradient Method(FGM)

上面我们提到,Goodfellow在15年的ICLR [7] 中提出了Fast Gradient Sign Method(FGSM),随后,在17年的ICLR [9]中,Goodfellow对FGSM中计算扰动的部分做了一点简单的修改。假设输入的文本序列的embedding vectors [v1,v2,…,vT] 为 x ,embedding的扰动为:

实际上就是取消了符号函数,用二范式做了一个scale,需要注意的是:这里的norm计算的是,每个样本的输入序列中出现过的词组成的矩阵的梯度norm。原作者提供了一个TensorFlow的实现 [10],在他的实现中,公式里的 x 是embedding后的中间结果(batch_size, timesteps, hidden_dim),对其梯度 g 的后面两维计算norm,得到的是一个(batch_size, 1, 1)的向量 ||g||2 。为了实现插件式的调用,笔者将一个batch抽象成一个样本,一个batch统一用一个norm,由于本来norm也只是一个scale的作用,影响不大。实现如下:

import torch
class FGM():
    def __init__(self, model):
        self.model = model
        self.backup = {}

    def attack(self, epsilon=1., emb_name='emb.'):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name:
                self.backup[name] = param.data.clone()
                norm = torch.norm(param.grad)
                if norm != 0 and not torch.isnan(norm):
                    r_at = epsilon * param.grad / norm
                    param.data.add_(r_at)

    def restore(self, emb_name='emb.'):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name: 
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}

需要使用对抗训练的时候,只需要添加五行代码:

# 初始化
fgm = FGM(model)
for batch_input, batch_label in data:
    # 正常训练
    loss = model(batch_input, batch_label)
    loss.backward() # 反向传播,得到正常的grad
    # 对抗训练
    fgm.attack() # 在embedding上添加对抗扰动
    loss_adv = model(batch_input, batch_label)
    loss_adv.backward() # 反向传播,并在正常的grad基础上,累加对抗训练的梯度
    fgm.restore() # 恢复embedding参数
    # 梯度下降,更新参数
    optimizer.step()
    model.zero_grad()

PyTorch为了节约内存,在backward的时候并不保存中间变量的梯度。因此,如果需要完全照搬原作的实现,需要用register_hook接口[11]将embedding后的中间变量的梯度保存成全局变量,norm后面两维,计算出扰动后,在对抗训练forward时传入扰动,累加到embedding后的中间变量上,得到新的loss,再进行梯度下降。

b. Projected Gradient Descent(PGD)

内部max的过程,本质上是一个非凹的约束优化问题,FGM解决的思路其实就是梯度上升,那么FGM简单粗暴的“一步到位”,是不是有可能并不能走到约束内的最优点呢?当然是有可能的。于是,一个很intuitive的改进诞生了:Madry在18年的ICLR中[8],提出了用Projected Gradient Descent(PGD)的方法,简单的说,就是“小步走,多走几步”,如果走出了扰动半径为 ϵ 的空间,就映射回“球面”上,以保证扰动不要过大:

import torch
class PGD():
    def __init__(self, model):
        self.model = model
        self.emb_backup = {}
        self.grad_backup = {}

    def attack(self, epsilon=1., alpha=0.3, emb_name='emb.', is_first_attack=False):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name:
                if is_first_attack:
                    self.emb_backup[name] = param.data.clone()
                norm = torch.norm(param.grad)
                if norm != 0 and not torch.isnan(norm):
                    r_at = alpha * param.grad / norm
                    param.data.add_(r_at)
                    param.data = self.project(name, param.data, epsilon)

    def restore(self, emb_name='emb.'):
        # emb_name这个参数要换成你模型中embedding的参数名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name: 
                assert name in self.emb_backup
                param.data = self.emb_backup[name]
        self.emb_backup = {}

    def project(self, param_name, param_data, epsilon):
        r = param_data - self.emb_backup[param_name]
        if torch.norm(r) > epsilon:
            r = epsilon * r / torch.norm(r)
        return self.emb_backup[param_name] + r

    def backup_grad(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                self.grad_backup[name] = param.grad.clone()

    def restore_grad(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                param.grad = self.grad_backup[name]

使用的时候,要麻烦一点:

pgd = PGD(model)
K = 3
for batch_input, batch_label in data:
    # 正常训练
    loss = model(batch_input, batch_label)
    loss.backward() # 反向传播,得到正常的grad
    pgd.backup_grad()
    # 对抗训练
    for t in range(K):
        pgd.attack(is_first_attack=(t==0)) # 在embedding上添加对抗扰动, first attack时备份param.data
        if t != K-1:
            model.zero_grad()
        else:
            pgd.restore_grad()
        loss_adv = model(batch_input, batch_label)
        loss_adv.backward() # 反向传播,并在正常的grad基础上,累加对抗训练的梯度
    pgd.restore() # 恢复embedding参数
    # 梯度下降,更新参数
    optimizer.step()
    model.zero_grad()

实验对照

为了说明对抗训练的作用,笔者选了四个GLUE中的任务进行了对照试验。实验代码是用的Huggingface的transfomers/examples/run_glue.py [12],超参都是默认的,对抗训练用的也是相同的超参。

除了监督训练,对抗训练还可以用在半监督任务中,尤其对于NLP任务来说,很多时候输入的无监督文本多的很,但是很难大规模地进行标注,Distributional Smoothing with Virtual Adversarial Training. https://arxiv.org/abs/1507.00677 提到的 Virtual Adversarial Training进行半监督训练。

11、Pseudo Labeling(伪标签)提高模型的分类效果

简而言之,Pseudo Labeling将测试集中判断结果正确的置信度高的样本加入到训练集中,从而模拟一部分人类对新对象进行判断推演的过程。效果比不上人脑那么好,但是在监督学习问题中,Pseudo Labeling几乎是万金油,几乎能够让你模型各个方面的表现都得到提升。

  1. 使用原始训练集训练并建立模型
  2. 使用训练好的模型对测试集进行分类
  3. 将预测正确置信度高的样本加入到训练集中
  4. 使用结合了部分测试集样本的新训练集再次训练模型
  5. 使用新模型再次进行预测

总之:提分点很多,但能否有效以及能否实现又是另一个事情了,毕竟有时候是否有效也取决于数据集,毕竟缘分,妙不可言~,后续我会抽时间将上面的tricks都尝试尝试。

区域卷积神经⽹络(R-CNN)系列

来自:动手学深度学习

视频讲解:Faster R-CNN原理 、源码解析

1、R-CNN

R-CNN ⾸先从输⼊图像中选取若⼲(例如2000个)提议区域(如锚框也是⼀种选取方法),并标注它们的类别和边界框(如偏移量)。[Girshick et al., 2014] 然后,⽤卷积神经⽹络对每个提议区域进⾏前向计算以抽取其特征。接下来,我们⽤每个提议区域的特征来预测类别和边界框。

具体来说,R-CNN包括以下四个步骤:

  1. 对输⼊图像使⽤ 选择性搜索来选取多个⾼质量的提议区域 [Uijlings et al., 2013] 。这些提议区域通常是在多个尺度下选取的,并具有不同的形状和⼤小。每个提议区域都将被标注类别和真实边界框。
  2. 选择⼀个预训练的卷积神经⽹络,并将其在输出层之前截断。将每个提议区域变形为⽹络需要的输⼊尺⼨,并通过前向计算输出抽取的提议区域特征。
  3. 将每个提议区域的特征连同其标注的类别作为⼀个样本。训练多个⽀持向量机对⽬标分类,其中每个⽀持向量机⽤来判断样本是否属于某⼀个类别。
  4. 将每个提议区域的特征连同其标注的边界框作为⼀个样本,训练线性回归模型来预测真实边界框。
  • 尽管 R-CNN 模型通过预训练的卷积神经⽹络有效地抽取了图像特征,但它的速度很慢。想象⼀下,我们可能从⼀张图像中选出上千个提议区域,这需要上千次的卷积神经⽹络的前向计算来执⾏⽬标检测。这种庞⼤的计算量使得 R-CNN 在现实世界中难以被⼴泛应⽤。

2、Fast R-CNN

R-CNN 的主要性能瓶颈在于,对每个提议区域,卷积神经⽹络的前向计算是独⽴的,而没有共享计算。由于这些区域通常有重叠,独⽴的特征抽取会导致重复的计算。Fast R-CNN [Girshick, 2015] 对 R-CNN 的主要改进之⼀,是仅在整张图象上执⾏卷积神经⽹络的前向计算。

它的主要计算如下:

  1. 与 R-CNN 相⽐,Fast R-CNN ⽤来提取特征的卷积神经⽹络的输⼊是整个图像,而不是各个提议区域。此外,这个⽹络通常会参与训练。设输⼊为⼀张图像,将卷积神经⽹络的输出的形状记为 1×c×h1×w1。
  2. 假设选择性搜索⽣成了n个提议区域。这些形状各异的提议区域在卷积神经⽹络的输出上分别标出了形状各异的兴趣区域。然后,这些感兴趣的区域需要进⼀步抽取出形状相同的特征(⽐如指定⾼度h2和宽度w2),以便于连结后输出。为了实现这⼀⽬标,Fast R-CNN 引⼊了 兴趣区域 (RoI) 池化层:将卷积神经⽹络的输出和提议区域作为输⼊,输出连结后的各个提议区域抽取的特征,形状为n × c × h2 × w2。
  3. 通过全连接层将输出形状变换为n × d,其中超参数d取决于模型设计。
  4. 预测n个提议区域中每个区域的类别和边界框。更具体地说,在预测类别和边界框时,将全连接层的输出分别转换为形状为 n × q(q 是类别的数量)的输出和形状为 n × 4 的输出。其中预测类别时使⽤softmax 回归。

在Fast R-CNN 中提出的兴趣区域汇聚层与 6.5节 中介绍的汇聚层有所不同。在汇聚层中,我们通过设置池化窗口、填充和步幅的⼤小来间接控制输出形状。而兴趣区域汇聚层对每个区域的输出形状是可以直接指定的。例如,指定每个区域输出的⾼和宽分别为 h2 和 w2。对于任何形状为 h × w 的兴趣区域窗口,该窗口将被划分为 h2 × w2 ⼦窗口⽹格,其中每个⼦窗口的⼤小约为(h/h2) × (w/w2)。在实践中,任何⼦窗口的⾼度和宽度都应向上取整,其中的最⼤元素作为该⼦窗口的输出。因此,兴趣区域汇聚层可从形状各异的兴趣区域中均抽取出形状相同的特征。

3、Faster R-CNN

为了较精确地检测⽬标结果,Fast R-CNN 模型通常需要在选择性搜索中⽣成⼤量的提议区域。Faster R-CNN [Ren et al., 2015] 提出将选择性搜索替换为 区域提议⽹络(region proposal network),从而减少提议区域的⽣成数量,并保证⽬标检测的精度。

与Fast R-CNN 相⽐,Faster R-CNN 只将⽣成提议区域的⽅法从选择性
搜索改为了区域提议⽹络,模型的其余部分保持不变。具体来说,区域提议⽹络的计算步骤如下:

  1. 使⽤填充为1的 3 × 3 的卷积层变换卷积神经⽹络的输出,并将输出通道数记为 c。这样,卷积神经⽹络为图像抽取的特征图中的每个单元均得到⼀个⻓度为 c 的新特征。
  2. 以特征图的每个像素为中⼼,⽣成多个不同⼤小和宽⾼⽐的锚框并标注它们。
  3. 使⽤锚框中⼼单元⻓度为 c 的特征,分别预测该锚框的⼆元类别(含⽬标还是背景)和边界框。
  4. 使⽤⾮极⼤值抑制,从预测类别为⽬标的预测边界框中移除相似的结果。最终输出的预测边界框即是兴趣区域汇聚层所需的提议区域。

值得⼀提的是,区域提议⽹络作为 Faster R-CNN 模型的⼀部分,是和整个模型⼀起训练得到的。换句话说,Faster R-CNN 的⽬标函数不仅包括⽬标检测中的类别和边界框预测,还包括区域提议⽹络中锚框的⼆元类别和边界框预测。作为端到端训练的结果,区域提议⽹络能够学习到如何⽣成⾼质量的提议区域,从而在减少了从数据中学习的提议区域的数量的情况下,仍保持⽬标检测的精度

4、Mask R-CNN

如果在训练集中还标注了每个⽬标在图像上的像素级位置,那么 Mask R-CNN [He et al., 2017] 能够有效地利⽤这些详尽的标注信息进⼀步提升⽬标检测的精度。

如 图13.8.5 所⽰,Mask R-CNN 是基于 Faster R-CNN 修改而来的。具体来说,Mask R-CNN 将兴趣区域汇聚层替换为了 兴趣区域 (RoI) 对⻬层使⽤ 双线性插值(bilinear interpolation)来保留特征图上的空间信息,从而更适于像素级预测。兴趣区域对⻬层的输出包含了所有与兴趣区域的形状相同的特征图。它们不仅被⽤于预测每个兴趣区域的类别和边界框,还通过额外的全卷积⽹络预测⽬标的像素级位置。

补充:ROI Align 和 ROI Pooling

这两个都是用在rpn之后的。具体来说,从feature map上经过RPN得到一系列的proposals,大概2k个,这些bbox大小不等,如何将这些bbox的特征进行统一表示就变成了一个问题。即需要找一个办法从大小不等的框中提取特征使输出结果是等长的。最开始目标检测模型Faster RCNN中用了一个简单粗暴的办法,叫ROI Pooling。该方式在语义分割这种精细程度高的任务中,不够精准,由此发展来了ROI Align。

ROI Pooling:

假如现在有一个8×8的feature map,现在希望得到2×2的输出,有一个bbox坐标为[0,3,7,8]。

这个bbox的w=7,h=5,如果要等分成四块是做不到的,因此在ROI Pooling中会进行取整。就有了上图看到的h被分割为2,3,w被分割成3,4。这样之后在每一块(称为bin)中做max pooling,可以得到下图的结果。

这样就可以将任意大小bbox转成2×2表示的feature。

ROI Pooling需要取整,这样的取整操作进行了两次,一次是得到bbox在feature map上的坐标时。

例如:原图上的bbox大小为665×665,经backbone后,spatial scale=1/32。因此bbox也相应应该缩小为665/32=20.78,但是这并不是一个真实的pixel所在的位置,因此这一步会取为20。0.78的差距反馈到原图就是0.78×32=25个像素的差距。如果是大目标这25的差距可能看不出来,但对于小目标而言差距就比较巨大了。

ROI Align

因此有人提出不需要进行取整操作,如果计算得到小数,也就是没有落到真实的pixel上,那么就用最近的pixel对这一点虚拟pixel进行双线性插值,得到这个“pixel”的值。

  1. 将bbox区域按输出要求的size进行等分,很可能等分后各顶点落不到真实的像素点上
  2. 没关系,在每个bin中再取固定的4个点(作者实验后发现取4效果较好),也就是图二右侧的蓝色点
  3. 针对每一个蓝点,距离它最近的4个真实像素点的值加权(双线性插值),求得这个蓝点的值
  4. 一个bin内会算出4个新值,在这些新值中取max,作为这个bin的输出值
  5. 最后就能得到2×2的输出

ROI Pooling和ROI Align

这两个都是用在rpn之后的。具体来说,从feature map上经过RPN得到一系列的proposals,大概2k个,这些bbox大小不等,如何将这些bbox的特征进行统一表示就变成了一个问题。即需要找一个办法从大小不等的框中提取特征使输出结果是等长的。

最开始目标检测模型Faster RCNN中用了一个简单粗暴的办法,叫ROI Pooling。

该方式在语义分割这种精细程度高的任务中,不够精准,由此发展来了ROI Align。

今天就总结下两者的思想。

ROI Pooling

假如现在有一个8×8的feature map,现在希望得到2×2的输出,有一个bbox坐标为[0,3,7,8]。

这个bbox的w=7,h=5,如果要等分成四块是做不到的,因此在ROI Pooling中会进行取整。就有了上图看到的h被分割为2,3,w被分割成3,4。这样之后在每一块(称为bin)中做max pooling,可以得到下图的结果。

这样就可以将任意大小bbox转成2×2表示的feature。

ROI Pooling需要取整,这样的取整操作进行了两次,一次是得到bbox在feature map上的坐标时。

例如:原图上的bbox大小为665×665,经backbone后,spatial scale=1/32。因此bbox也相应应该缩小为665/32=20.78,但是这并不是一个真实的pixel所在的位置,因此这一步会取为20。0.78的差距反馈到原图就是0.78×32=25个像素的差距。如果是大目标这25的差距可能看不出来,但对于小目标而言差距就比较巨大了。

图1

ROI Align

因此有人提出不需要进行取整操作,如果计算得到小数,也就是没有落到真实的pixel上,那么就用最近的pixel对这一点虚拟pixel进行双线性插值,得到这个“pixel”的值。

具体做法如下图所示:

  1. 将bbox区域按输出要求的size进行等分,很可能等分后各顶点落不到真实的像素点上
  2. 没关系,在每个bin中再取固定的4个点(作者实验后发现取4效果较好),也就是图二右侧的蓝色点
  3. 针对每一个蓝点,距离它最近的4个真实像素点的值加权(双线性插值),求得这个蓝点的值
  4. 一个bin内会算出4个新值,在这些新值中取max,作为这个bin的输出值
  5. 最后就能得到2×2的输出

UNET 3+

论文: https://arxiv.org/abs/2004.08790

github: https://github.com/ZJUGiveLab/UNet-Version

UNet是医学影像分割领域应用最广泛的的网络,其性能和网络中多尺度特征的融合密切相关。此后的UNet++通过嵌套结构和密集的跳过连接原始网络进行了改进。本文提出的UNet3+通过全尺度的连接和深度监督来融合深层和浅层特征的同时对各个尺度的特征进行监督。提出的UNet3+网络可以在减少网络参数的同时提高计算效率,在两个数据集上验证了方法有效性。相关代码已经开源。

现有的分割网络如UNET、PSPNET和DeepLab等网络都通常会通过多尺度的方式提取图像的信息。低层次的细节特征图中具有更丰富的例如边界这样空间信息,高层特征图中包含更多的例如物体位置这样的高级语义特征。然而,随着网络的下采样和上采样,这些高低层的信息并没有被充分地利用。因此,文章提出的UNet3+对网络的编码器与解码器连接以及解码器内部之间的连接进行了改进。此外,文章通过提出的混合损失函数对各层进行深度监督和分类分支指导分割的方式,进一步提高了分割的精度。总结来说,文章主要有以下四点贡献:

  1. 设计了一种新的网络结构UNet3+,通过引入全尺度的跳过连接,在全尺度特征映射中融合了低层细节和高层语义,充分利用了多尺度特征的同时具有更少的参数;
  2. 通过深度监督让网络从全尺度特征中学习分割表示,提出了更优的混合损失函数以增强器官的边界;
  3. 提出分类指导模块,通过与图像分类分支联合训练的方式,减少了网络在非器官图像的过度分割(over-segmentation);
  4. 在肝脏和脾脏数据集上进行了广泛的实验,证明了UNet 3+的有效性。
  5. 从图中可以看到,UNet3+与UNet主体上非常相似,不同之处在于从编码器到解码器的跳过连接以及不同层级的编码器之间的连接。以图中的节点 XDe3 为例,它的信息来自于两方面,一是比其更浅(包括同一层级)的编码器,二十比其更深的解码器。不同层级的特征通过maxpooling和双线性上采样的方式进行尺寸统一。解码层的卷积分两步,第一步是对来及各个节点的信息进行各自的卷积,第二步是对堆叠的特征通过卷积来进行信息的融合和提取。值得注意的一个细节是,进行第一个卷积层时,来自各层的数据被卷积到相同的特征图数(在这里是n/5,n为所在层的特征图数)。

从图中可以看到,UNet3+与UNet主体上非常相似,不同之处在于从编码器到解码器的跳过连接以及不同层级的编码器之间的连接。以图中的节点 XDe3 为例,它的信息来自于两方面,一是比其更浅(包括同一层级)的编码器,二是比其更深的解码器。不同层级的特征通过maxpooling和双线性上采样的方式进行尺寸统一。解码层的卷积分两步,第一步是对来及各个节点的信息进行各自的卷积,第二步是对堆叠的特征通过卷积来进行信息的融合和提取。值得注意的一个细节是,进行第一个卷积层时,来自各层的数据被卷积到相同的特征图数(在这里是n/5,n为所在层的特征图数)。

2.全尺度的深度监督

为了进一步优化网络对图像边界的分割,文章借鉴了图像质量评估中常用的多尺度SSIM(MS-SSIM)提出了MS-SSIM loss。

本文最终采用了混合损失函数(focal loss,ms-ssim loss和iou loss)来对各层进行监督。

ℓseg=ℓfl+ℓms−ssim+ℓiou

3. 分类指导模块(CGM)

在大多数医学图像分割中,非器官图像中出现假阳性不可避免。这通常是保留在较浅层中背景噪声信息导致的过分割现象。为了实现更精确的分割,文章尝试通过添加一个额外的分类任务来解决这个问题,该分类任务被设计用于预测输入图像是否有器官。简单来说,当预测到图像包含待分割器官的概率较小时,对输出图像乘以0使得输出全黑。

文章采用了LITS的肝脏数据集和自己采集的脾脏数据集通过两组实验来进行验证。

第一组对UNet、UNet++、UNet3+(带深度监督和不带)以Vgg和ResNet101作为backbone进行了对比。可以在以Vgg为backbone时,UNet3+比其UNet在两个数据集上分别有2.8%和4.1%的提升。网络相比于UNet++也有较大的提升。另外,可以看到UNet3+使用了更少的参数得到了更好的结果。可视化的结果表明即使在器官较小的情况下网络也能得到更加精细连贯的分割。

文章进一步以ResNet作为backbone,将网络与当前比较先进的分割网络进行对比。在这里,在验证网络有效性的同时,文章对提出的损失函数和分类分支进行了消融实验。

文章对之前在UNet解码器只接收的来自同一层编码器和深一层解码器的连接方式进行了改进,使得解码器都能获得来自每一个更浅的编码器和更深的解码器的信息,使得网络能够更好地提取和融合多尺度的信息。网络的结构设计简洁优雅,是一篇非常不错的UNet改进文章。另外文章提出的MS-SSIM损失和分类指导模块也挺有意思。当然我对文章也有一些思考。第一,网络结构设计中,对于来自不同层级的特征,进行融合时可以考虑通过PSP或者Deeplab的方式(JPU是一种很好选择),也可以考虑通过SE的方式来进行通道的选择。第二,文章提出的MS-SSIM能够更好地分割图像的边界,那么选取豪斯多夫距离这样的指标可以更好地证明方法的有效性。第三,文章通过CGM来对输出进行限制,但是对于器官的顶端和底端这样本身有比较多歧义性图像,容易造成无法分割的情况,可以考虑进行soft的指导。