知识蒸馏(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的指导。

关于NLP数据清洗和数据增强

最近参加一个NLP关于医学电子病历的疾病多标签分类比赛,因为之前比较少去做NLP方向,所以,参赛纯粹是为了了解了解NLP方向,好在nlp做文本分类算是比较简单的下游任务,但在参赛过程中,会发现,其实对于文本分类来说,基本的bert-base的效果不是很好,但其实感觉不是出在模型架构方面,对于简单的分类任务,一个12层的bert应该适足以胜任了,因此需要将注意力看向数据预处理、数据清洗、数据增强。以及数据类别分布不均匀,也可以尝试使用不同的损失函数。另外,不可否认,输入的文本长度越长,效果应该会更好一些,但奈何没有“钞”能力。此外,还可以尝试模型集成、交叉验证(五折交叉验证,即:将训练集分为五部分,一部分做验证集,剩下四部分做训练集,相当于得到五个模型。验证集组合起来就是训练集。五个模型对测试集的预测取均值得到最终的预测结果。)这块的思路还挺多的,算是做个记录,方便后面在处理类似文本分类任务时候的一个参考。

言归正传:今天来总结下NLP中的数据处理。

1、数据清洗

什么是数据清洗:

数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。与问卷审核不同,录入后的数据清理一般是由计算机而不是人工完成。数据清洗从名字上也看的出就是把“脏”的“洗掉”,指发现并纠正数据文件中可识别的错误的最后一道程序,包括检查数据一致性,处理无效值和缺失值等。

为什么要进行数据清洗

因为数据仓库中的数据是面向某一主题的数据的集合,这些数据从多个业务系统中抽取而来而且包含历史数据,这样就避免不了有的数据是错误数据、有的数据相互之间有冲突,这些错误的或有冲突的数据显然是我们不想要的,称为“脏数据”。我们要按照一定的规则把“脏数据”“洗掉”,这就是数据清洗。

清洗后,一个数据集应该与系统中其他类似的数据集保持一致。 检测到或删除的不一致可能最初是由用户输入错误、传输或存储中的损坏或不同存储中类似实体的不同数据字典定义引起的。 数据清理与数据确认(data validation)的不同之处在于,数据确认几乎总是意味着数据在输入时被系统拒绝,并在输入时执行,而不是执行于批量数据。

数据清洗不仅仅更正错误,同样加强来自各个单独信息系统不同数据间的一致性。专门的数据清洗软件能够自动检测数据文件,更正错误数据,并用全企业一致的格式整合数据。

数据清洗流程:

(1)中文首先需要分词,可以采用结巴分词、HanNLP、刨丁解牛等分词工具;

(2)数据规范化处理(Normalization):比如通常会把文本中的大写转成小写,清除文本中的句号、问号、感叹号等特殊字符,并且仅保留字母表中的字母和数字。小写转换和标点移除是两个最常见的文本 Normalization 步骤。是否需要以及在哪个阶段使用这两个步骤取决于你的最终目标。

去除一些停用词。而停用词是文本中一些高频的代词、连词、介词等对文本分类无意义的词,通常维护一个停用词表,特征提取过程中删除停用表中出现的词,本质上属于特征选择的一部分。具体可参考Hanlp的停用词表https://github.com/hankcs/HanLP

(3)Tokenization,Token 是“符号”的高级表达。一般指具有某种意义,无法再分拆的符号。在英文自然语言处理中,Tokens 通常是单独的词。因此,Tokenization 就是将每个句子分拆成一系列词。可以使用NLTK工具箱来完成相关操作。

(4)Stop Word 是无含义的词,例如 ‘is’/‘our’/‘the’/‘in’/‘at’ 等。它们不会给句子增加太多含义,单停止词是频率非常多的词。 为了减少我们要处理的词汇量,从而降低后续程序的复杂度,需要清除停止词。

(5)Part-of-Speech Tagging:还记得在学校学过的词性吗?名词、代词、动词、副词等等。识别词在句子中的用途有助于我们更好理解句子内容。并且,标注词性还可以明确词之间的关系,并识别出交叉引用。同样地,NLTK 给我们带来了很多便利。你可以将词传入 PoS tag 函数。然后对每个词返回一个标签,并注明不同的词性。

(6)Named Entity 一般是名词短语,又来指代某些特定对象、人、或地点 可以使用 ne_chunk()方法标注文本中的命名实体。在进行这一步前,必须先进行 Tokenization 并进行 PoS Tagging。

(7)Stemming and Lemmatization:为了进一步简化文本数据,我们可以将词的不同变化和变形标准化。Stemming 提取是将词还原成词干或词根的过程。

(8)一些词在句首句尾句中出现的概率不一样,统计N-GRAM特征的时候要在句首加上BOS,句尾加上EOS作标记。

(9)把长文本分成句子和单词这些fine granularity会比较有用。

(10) 一般会有一个dictionary,不在dictionary以内的单词就用UNK取代。

(11)单词会被转成数字(它对应的index,从0开始,一般0就是UNK)。

(12)做机器翻译的时候会把单词转成subword units。

这块的代码还是比较多

1、A Python toolkit for file processing, text cleaning and data splitting. 文件处理,文本清洗和数据划分的python工具包。

2、基本的文本清洗,主要解决文本数据处理的问题

数据增强

与计算机视觉中使用图像进行数据增强不同,NLP中文本数据增强是非常罕见的。这是因为图像的一些简单操作,如将图像旋转或将其转换为灰度,并不会改变其语义。语义不变变换的存在使增强成为计算机视觉研究中的一个重要工具。

方法

1. 词汇替换

这一类的工作,简单来说,就是去替换原始文本中的某一部分,而不改变句子本身的意思。

1.1 基于同义词典的替换

在这种方法中,我们从句子中随机取出一个单词,将其替换为对应的同义词。例如,我们可以使用英语的 WordNet 数据库来查找同义词,然后进行替换。WordNet 是一个人工维护的数据库,其中包含单词之间的关系。

Zhang 等人在2015年的论文 “Character-level Convolutional Networks for Text Classification” 中使用了这种方法。Mueller 等人也使用类似的方法为他们的句子相似度模型生成额外的 10K 条训练数据。这一方法也被 Wei 等人在他们的 “Easy Data Augmentation” 论文中使用。对于如何使用,NLTK 提供了对 WordNet 的接口;我们还可以使用 TextBlob API。此外,还有一个名为 PPDB 的数据库,其中包含数百万条同义词典,可以通过编程方式下载和使用。

1.2 基于 Word-Embeddings 的替换

在这种方法中,我们采用预先训练好的词向量,如 Word2Vec、GloVe、FastText,用向量空间中距离最近的单词替换原始句子中的单词。Jiao 等人在他们的论文 “TinyBert” 中使用了这种方法,以改进语言模型在下游任务上的泛化性;Wang 等人使用它来对 tweet 语料进行数据增强来学习主题模型。

例如,可以用三个向量空间中距离最近的单词替换原始句子中的单词,可以得到原始句子的三个变体。我们可以使用像 Gensim 包来完成这样的操作。在下面这个例子中,我们通过在 Tweet 语料上训练的词向量找到了单词 “awesome” 的同义词。

1.3 基于 Masked Language Model 的替换

像 BERT、ROBERTA 和 ALBERT 这样基于 Transformer 的模型已经使用 “Masked Language Modeling” 的方式,即模型要根据上下文来预测被 Mask 的词语,通过这种方式在大规模的文本上进行预训练。

Masked Language Modeling 同样可以用来做文本的数据增强。例如,我们可以使用一个预先训练好的 BERT 模型,然后对文本的某些部分进行 Mask,让 BERT 模型预测被 Mask 的词语。我们称这种方法叫 Mask Predictions。和之前的方法相比,这种方法生成的文本在语法上更加通顺,因为模型在进行预测的时候考虑了上下文信息。我们可以很方便的使用 HuggingFace 的 transfomers 库,通过设置要替换的词语并生成预测来做文本的数据增强。

1.4 基于 TF-IDF 的替换

这种数据增强方法是 Xie 等人在 “Unsupervised Data Augmentation” 论文中提出来的。其基本思想是,TF-IDF 分数较低的单词不能提供信息,因此可以在不影响句子的基本真值标签的情况下替换它们。

具体如何计算整个文档中单词的 TF-IDF 分数并选择最低的单词来进行替换,可以参考作者公开的代码

2. Back Translation(回译)

在这种方法中,我们使用机器翻译的方法来复述生成一段新的文本。Xie 等人使用这种方法来扩充未标注的样本,在 IMDB 数据集上他们只使用了 20 条标注数据,就可以训练得到一个半监督模型,并且他们的模型优于之前在 25000 条标注数据上训练得到的 SOTA 模型。

使用机器翻译来回译的具体流程如下:

  • 找一些句子(如英语),翻译成另一种语言,如法语
  • 把法语句子翻译成英语句子
  • 检查新句子是否与原来的句子不同。如果是,那么我们使用这个新句子作为原始文本的补充版本。

我们还可以同时使用多种不同的语言来进行回译以生成更多的文本变体。如下图所示,我们将一个英语句子翻译成目标语言,然后再将其翻译成三种目标语言:法语、汉语和意大利语。

这种方法也在 Kaggle 上的 “Toxic Comment Classification Challenge” 的第一名解决方案中使用。获胜者将其用于训练数据扩充和测试,在应用于测试的时候,对英语句子的预测概率以及使用三种语言(法语、德语、西班牙语)的反向翻译进行平均,以得到最终的预测。

对于如何实现回译,可以使用 TextBlob 或者谷歌翻译。

3. Text Surface Transformation

这些是使用正则表达式应用的简单模式匹配变换,Claude Coulombe 在他的论文中介绍了这些变换的方法。

在论文中,他给出了一个将动词由缩写形式转换为非缩写形式的例子,我们可以通过这个简单的方法来做文本的数据增强。

需要注意的是,虽然这样的转换在大部分情况下不会改变句子原本的含义,但有时在扩展模棱两可的动词形式时可能会失败,比如下面这个例子:

为了解决这一问题,论文中也提出允许模糊收缩 (非缩写形式转缩写形式),但跳过模糊展开的方法 (缩写形式转非缩写形式)。

我们可以在这里找到英语缩写的列表。对于展开,可以使用 Python 中的 contractions 库

4. Random Noise Injection

这些方法的思想是在文本中注入噪声,来生成新的文本,最后使得训练的模型对扰动具有鲁棒性。

4.1 Spelling error injection

在这种方法中,我们在句子中添加一些随机单词的拼写错误。可以通过编程方式或使用常见拼写错误的映射来添加这些拼写错误,具体可以参考这个链接

4.2 QWERTY Keyboard Error Injection

这种方法试图模拟在 QWERTY 键盘布局上打字时由于键之间非常接近而发生的常见错误。这种错误通常是在通过键盘输入文本时发生的。

4.3 Unigram Noising

这种方法已经被 Xie 等人和 UDA 的论文所使用,其思想是使用从 unigram 频率分布中采样的单词进行替换。这个频率基本上就是每个单词在训练语料库中出现的次数。

4.4 Blank Noising

该方法由 Xie 等人在他们的论文中提出,其思想是用占位符标记替换一些随机单词。本文使用 “_” 作为占位符标记。在论文中,他们使用它作为一种避免在特定上下文上过度拟合的方法以及语言模型平滑的机制,这项方法可以有效提高生成文本的 Perplexity 和 BLEU 值。

4.5 Sentence Shuffling

这是一种很初级的方法,我们将训练样本中的句子打乱,来创建一个对应的数据增强样本。

Sentence Shuffling 的过程

4.6 Random Insertion

这个方法是由 Wei 等人在其论文 “Easy Data Augmentation” 中提出的。在该方法中,我们首先从句子中随机选择一个不是停止词的词。然后,我们找到它对应的同义词,并将其插入到句子中的一个随机位置。(也比较 Naive)

4.7 Random Swap

这个方法也由 Wei 等人在其论文 “Easy Data Augmentation” 中提出的。该方法是在句子中随机交换任意两个单词。

4.8 Random Deletion

该方法也由 Wei 等人在其论文 “Easy Data Augmentation” 中提出。在这个方法中,我们以概率 p 随机删除句子中的每个单词。

5. Instance Crossover Augmentation

这种方法由 Luque 在他 TASS 2019 的论文中介绍,灵感来自于遗传学中的染色体交叉操作。

在该方法中,一条 tweet 被分成两半,然后两个相同情绪类别(正/负)的 tweets 各自交换一半的内容。这么做的假设是,即使结果在语法和语义上不健全,新的文本仍将保留原来的情绪类别。

Instance Crossover 的过程

这中方法对准确性没有影响,并且在 F1-score 上还有所提升,这表明它帮助了模型提升了在罕见类别上的判断能力,比如 tweet 中较少的中立类别。

6. Syntax-tree Manipulation

这种方法最先是由 Coulombe 提出的,其思想是解析并生成原始句子的依赖树,使用规则对其进行转换来对原句子做复述生成。

例如,一个不会改变句子意思的转换是句子的主动语态和被动语态的转换。

7. MixUp for Text

Mixup 是 Zhang 等人在 2017 年提出的一种简单有效的图像增强方法。其思想是将两个随机图像按一定比例组合成,以生成用于训练的合成数据。对于图像,这意味着合并两个不同类的图像像素。它在模型训练的时候可以作为的一种正则化的方式。

为了把这个想法带到 NLP 中,Guo 等人修改了 Mixup 来处理文本。他们提出了两种将 Mixup 应用于文本的方法:

7.1 wordMixup

在这种方法中,在一个小批中取两个随机的句子,它们被填充成相同的长度;然后,他们的 word embeddings 按一定比例组合,产生新的 word embeddings 然后传递下游的文本分类流程,交叉熵损失是根据原始文本的两个标签按一定比例计算得到的。

7.2 sentMixup

在这种方法中,两个句子首先也是被填充到相同的长度;然后,通过 LSTM/CNN 编码器传递他们的 word embeddings,我们把最后的隐藏状态作为 sentence embedding。这些 embeddings 按一定的比例组合,然后传递到最终的分类层。交叉熵损失是根据原始文本的两个标签按一定比例计算得到的。

8. 生成式的方法

这一类的工作尝试在生成额外的训练数据的同时保留原始类别的标签。

Conditional Pre-trained Language Models

这种方法最早是由 Anaby-Tavor 等人在他们的论文 “Not Enough Data? Deep Learning to the Rescue!” Kumar 等人最近的一篇论文在多个基于 Transformer 的预训练模型中验证了这一想法。

问题的表述如下:

  1. 在训练数据中预先加入类别标签,如下图所示。
训练数据中加入类别标签

2. 在这个修改过的训练数据上 finetune 一个大型的预训练语言模型 (BERT/GPT2/BART) 。对于 GPT2,目标是去做生成任务;而对于 BERT,目标是要去预测被 Mask 的词语。

3. 使用经过 finetune 的语言模型,可以使用类标签和几个初始单词作为模型的提示词来生成新的数据。本文使用每条训练数据的前 3 个初始词来为训练数据做数据增强。

9. 实现过程

nlpaug 和 textattack 等第三方 Python 库提供了简单易用的 API,可以轻松使用上面介绍的 NLP 数据增强方法。

1、 NLP Chinese Data Augmentation 一键中文数据增强工具: https://github.com/425776024/nlpcda

使用:pip install nlpcda

介绍

一键中文数据增强工具,支持:

2、 TextAttack 是一个可以实行自然语言处理的Python 框架,用于方便快捷地进行对抗攻击,增强数据,以及训练模型。https://github.com/QData/TextAttack/blob/master/README_ZH.md

文档:https://textattack.readthedocs.io/en/latest/0_get_started/basic-Intro.html

3、中文语料的EDA数据增强工具

4、中文谐音词/字库

10. 结论

通过阅读许多 NLP 数据增强方面的论文,我发现大多数方法都是具有很强的任务属性的,并且针对这些方法的实验也只在某些特定的场景进行了验证。可以见得,系统地比较这些方法并且分析它们在其他任务上的表现在未来将是一项有趣的研究。