模型压缩

最近在做的yolo网络硬件加速项目,需要去对原始网络进行压缩,因此记录下相关知识:

相关综述:

A Survey of Model Compression and Acceleration for Deep Neural Networks

《A Comprehensive Survey on Model Compression and Acceleration》

目前,在模型压缩和加速方面常用的方法大致可以分为四类:剪枝与量化(parameter pruning and quantization)、低秩因子分解(low-rank factorization)、迁移/压缩卷积滤波器(transferred/compact convolutional filters)、蒸馏学习(knowledge distillation)。

模型压缩方法

背景

近年来,深度神经网络(deep neural networks,DNN)逐渐受到各行各业的关注。它是指具有更深层(不止一个隐藏层)的神经网络,是深度学习的基础。很多实际的工作通常依赖于数百万甚至数十亿个参数的深度网络,这样复杂的大规模模型通常对计算机的CPU和GPU有着极高的要求,并且会消耗大量内存,产生巨大的计算成本。随着一些便携式设备(如移动电话)的快速发展,如何将这些复杂的计算系统部署到资源有限的设备上就成为了需要应对的全新挑战。这些设备通常内存有限,而且计算能力较低,不支持大模型的在线计算。因此需要对模型进行压缩和加速,以求在基本不损失模型精度的条件下,节约参数并降低其计算时间。

剪枝与量化主要针对模型中的冗余参数进行删减;低秩因子分解使用张量分解的方法来估计神经网络的参数;迁移/压缩卷积滤波器则是设计了一个特殊结构的卷积滤波器,能够减少参数空间并且节约内存;蒸馏学习是先训练一个较大的模型,再训练一个较小的神经网络以达到跟大模型同样的效果。其中,低秩因子分解和迁移/压缩卷积滤波器两种方法提供了端到端的管道,可以在CPU/GPU环境中轻松实现;而剪枝与量化使用二进制及稀疏约束等方法来实现目标。此外,剪枝与量化和低秩因子分解方法可以从预训练的模型中提取或者是从头开始训练,而另外两种方法仅支持从头开始的训练。这四种方法大多是独立设计的,但又相互补充,在实际应用中常常可以一起使用,实现对模型进一步的压缩或加速。接下来将分别对这四种方法进行介绍。

剪枝与量化(parameter pruning and quantization)

早期的研究表明,对构建的网络进行剪枝和量化在降低网络复杂性以及解决过拟合问题方面是有效的(Gong et al. 2014)。同剪枝与量化有关的方法可以进一步分为三个子类:量化与二值化(quantization and binarization)、网络剪枝(network pruning)、结构矩阵(structural matrix)。

1.量化与二值化(quantization and binarization)

在DNN中,权重通常是以32位浮点数的形式(即32-bit)进行存储,量化法则是通过减少表示每个权重需要的比特数(the number of bits)来压缩原始网络。此时权重可以量化为16-bit、8-bit、4-bit甚至是1-bit(这是量化的一种特殊情况,权重仅用二进制表示,称为权重二值化)。8-bit的参数量化已经可以在损失小部分准确率的同时实现大幅度加速(Vanhoucke et al. 2011)。图2展示了基于修剪、量化和编码三个过程的压缩法:首先修剪小权重的连接,然后使用权重共享来量化权重,最后将哈夫曼编码应用于量化后的权重和码本上。

此方法的缺点是,在处理大型CNN(如GoogleNet)时,二值网络的精度明显降低。此外,现有的二值化方法大多基于简单的矩阵近似,忽略了二值化对精度损失产生的影响。

2.网络剪枝(network pruning)

剪枝是指通过修剪影响较小的连接来显著减少DNN模型的存储和计算成本,目前比较主流的剪枝方法主要有以下几种:

  • 权重剪枝(weight pruning):此方法主要应用于对不重要的连接权重进行修剪。如果连接权重低于预先设定的某个阈值,则该连接权重将会被修剪(Han et al. 2015)。
  • 神经元剪枝(neuron pruning):此方法与逐个修剪权重的方法不同,它直接移除某个冗余的神经元。这样一来,该神经元的所有传入和传出连接也将被移除(Srinivas and Babu 2015)。
  • 卷积核剪枝(filter pruning):此方法依据卷积核的重要程度将其进行排序,并从网络中修剪最不重要/排名最低的卷积核。卷积核的重要程度可以通过或范数或一些其他方法计算(Li et al. 2016)。
  • 层剪枝(layer pruning):此方法主要应用于一些非常深度的网络,可以直接修剪其中的某些层(Chen and Zhao 2018)。

按照剪枝的对象分类,可以分为在全连接层上剪枝和在卷积层上剪枝两种。DNN中的全连接层是存储密集的,对全连接层中的参数进行剪枝能够显著降低存储成本。对于卷积层而言,每个卷积层中都有许多卷积核,从卷积层修剪不重要的卷积核也能够减少计算成本并加速模型。

在全连接层上剪枝:考虑一个输入层、隐藏层和输出层分别具有3、2和1个神经元的前馈神经网络,如图3所示。

其中, x1​、x2​、x3​ 是网络的输入, wijl​ 是从当前层中节点 i 的层 l 到下一层中的节点 j 的权重。从图3(a)可以清楚地看出,目前总共有8个连接权重,如果删除两个橙色(虚线)的连接,那么总连接权重将减少到6个。类似地,从图3(b)中,如果移除红色神经元,那么其所有相关的连接权重(虚线)也将被移除,导致总连接权重减少到4个(参数数量减少50%)。

  • 在卷积层上剪枝: 在卷积神经网络中, 卷积核 WRh×w×ic×f 应用于每个输入的图像 I,IRm×n×ic, 并且经过卷积操作后输出特征映射 T,TRp×q×f 。其中, h 和 w 是卷积核的尺寸, ic 是输入图像中输入通道的数量, f 是应用的卷积核 的数量, m 和 n 是输入图像的尺寸, p 和 q 是结果特征映射的输出尺寸。输出特征映射的形状计算如下:

其中, s 为步长 (stride), p 为填充(padding)。图4显示了最简单的CNN形式,其 中输入图像的大小为 4×4×3, 应用的卷积核大小为 3×3×3×2 (2是卷积核的数 量)。

受到早期剪枝方法和神经网络过度参数化问题的启发,Han et al.(2015)提出了三步法来进行剪枝。其思想是,首先修剪激活小于某个预定义阈值的所有连接权重(不重要的连接),随后再识别那些重要的连接权重。最后,为了补偿由于修剪连接权重而导致的精度损失,再次微调/重新训练剪枝模型。这样的剪枝和再训练过程将重复数次,以减小模型的大小,将密集网络转换为稀疏网络。这种方法能够对全连接层和卷积层进行修剪,而且卷积层比全连接层对修剪更加敏感。

从卷积层修剪一些不重要的卷积核能够直接减少计算成本并且加速模型。但是,使用网络剪枝方法同样存在着一些问题。首先,使用或正则化进行剪枝比常规方法需要更多的迭代次数才能收敛。其次,所有的剪枝都需要手动设置神经网络层的灵敏度,这需要对参数进行微调,在某些应用中可能会十分复杂。最后,网络剪枝虽然通常能够使大模型变小,但是却不能够提高训练的效率。

3.结构矩阵(structural matrix)

神经网络各层之间使用的是非线性变换 f(x,N)=σ(Mx), 这里的 σ(⋅) 是对每个 元素特异的非线性算子, x 是输入向量, M 代表 m×n 维的参数矩阵, 此时的运算复 杂度为 O(mn) (V. Sindhwani et al. 2015) 。一个直观的剪枝方法就是使用参数化的 结构矩阵。一个大小为 m×n, 但是参数量却小于 mn 的矩阵就叫做结构矩阵。Cheng et al. ( 2015 ) 提 出了一种 基于循环预测的简单方法, 对于一个向量 r=(r0​,r1​,⋯,r(d−1)​), 其对应的 d×d 维循环矩阵定义如下:

这样一来存储的成本就从O (d2) 变成了O (d) 。给定 d 维 r 向量的条件下, 上式中的 一层循环神经网络的时间复杂度为 O(dlogd) 。

结构矩阵不仅能够降低内存成本,而且能够通过矩阵向量和梯度计算大幅度加快训练的速度。但是这种方法的缺点在于,结构约束通常会给模型带来偏差,从而损害模型的性能。再者,如何找到合适的结构矩阵也是一个难题,目前还没有理论上的方法能够推导出结构矩阵。

低秩因子分解(low-rank factorization)

低秩分解的思想是, 如果原始权重矩阵具有维数 m×n 和秩 r, 则满秩矩阵可以分 解为一个 m×r 的权重矩阵和一个 r×n 的权重矩阵。该方法通过将大矩阵分解为小矩 阵, 以减小模型的尺寸。CNN通常由许多层组成, 每层都有一组权重矩阵, 这些权重可以用张量 (Tensor) 来表示。图5展示了一个维数为 X×Y×Z 的三维张量。

给定一个维数为 N×N×D, 且有 K 个卷积核的卷积层, 其权重矩阵 W 可以表示为一个 N×N×D×K 维的张量 (Granés and Santamaria 2017) 。对于全连接层而言, W 可以用矩阵 (2阶张量) 来表示。因此对权重矩阵进行分解就是对张量进行分解。张量分解指的是, 用标量 (O阶张量) 、向量 (1阶张量) 、矩阵 (2阶张量) 和一些其他高阶的张量来表示原始张量的方法。对矩阵可以应用满秩分解 (full-rank decomposition) 和奇异值分解 (singular value decomposition, SVD), 对三维及三维以上张量可以应用 Tucker 分解和 CP分解 (Canonical Polyadic) (Deng et al.2020) 。

1.对矩阵的分解

  • 满秩分解。对任何给定的矩阵 AR(m×n), 其秩 rmin(m,n), 则 A 的满秩分解可以表示为 A=WH, 其中 WR(m×r),HR(r×n) 。如果 r 远小于 m 或 n,我们称 A 为低秩矩阵 (low-rank matrix) 。通过满秩分解可以将空间复杂度从O(mn) 显著减小到 O(r(m+n)) 。特别地, 当 m 和 n 非常接近, 并且原始矩阵是行(或列) 满秩时, 这种减小空间复杂度的作用会失效。满秩分解方法对于全连接层十分有效, 特别是当两层之间的神经元数量相差很大或权重矩阵低秩稀疏时。给定一个较小的正整数 k<r, 可以通过如下的式子求解最优的 WR(m×k),HR(k×n), 其中, F 表示Frobenius范数。
  • SVD。SVD是一种将原始权重矩阵分解为三个较小的矩阵以替换原始权重矩阵的 方法。对于任意的矩阵 AR(m×n), 存在分解 A=USVT, 其中, UR(m×r), SR(r×r),VTR(r×n) 。 U 和 V 是正交矩阵, S 是对角线上只有奇异值的对角矩 阵, 其中的每一个元素都比其下一个对角线上的元素大。这种方法可以使空间复 杂度从 O(mn) 减小到 O(r(m+n+1)) 。实际应用中, 可以用更小的 k 替换 r, 这 种方法称为截断奇异值分解 (truncated SVD, TSVD) 。在前馈神经网络和卷积神 经网络中, SVD是一种常用的分解方法, 主要用于减少参数的个数。

2.对三维及三维以上张量的分解

  • Tucker分解。该方法是将TSVD方法中的对角矩阵扩展为张量的一种方法。TSVD和Tucker分解之间的关系可以用图来表示:
  • CP分解。该分解是Tucker分解的一种特殊形式。如果Tucker分解中的每个 ri​ 等于正 整数 rC​, 并且核张量 K 满足, 除了 K(x1​,x2​,…,xd​),x1​=x2​=⋯=xd​ 之外 的所有元素都是 0 , 此时Tucker分解就成为了CP分解。与Tucker分解相比, CP分解 常用于解释数据的组成成分, 而前者主要用于数据压缩。图7展示了三阶张量 xR(I×J×K) 被 R 个组成部分分解的过程, 这个过程也可以用如下的公式来表示, 其中, ar​∈RI,br​∈RJ,cr​∈RK (Marcella Astrid and Seung- and Ik Lee 2018)。

基于低秩近似的方法虽然是模型压缩和加速的前沿,然而具体实现却并非易事。因为这涉及到分解操作,需要付出高昂的计算成本。此外,当前的方法仍集中于逐层执行低秩近似,因此无法执行全局的参数压缩。但全局的参数压缩十分重要,因为不同的层包含不同的信息。最后,与原始的模型相比,因子分解需要对大量的模型进行再训练以实现收敛。

迁移/压缩卷积滤波器(transferred/compact convolutional filters)

Cohen and Welling (2016) 提出了使用卷积滤波器压缩CNN模型的想法, 并在 研究中引入了等变群理论 (the equivariant group theory)。让 x 作为输入, Φ(⋅) 作为 一个神经网络或者网络层, Γ(⋅) 作为迁移矩阵, 则等价的概念定义如下:Γ′(Φ(x))=Φ(Γ(x))

这样的定义指的是, 迁移矩阵 Γ(⋅) 先对输入x进行变换, 再将其传输到 Φ(⋅) 所得到 的结果应该跟先将输入 x 映射到神经网络 Φ(⋅) 上再做变换 Γ(⋅) 得到的结果相同。值得注 意的是, Γ(⋅) 和 Γ′(⋅) 不一定相同, 因为它们作用在不同的对象上。根据这样的理论, 通过将变换应用于层或者滤波器 Φ(⋅) 来压缩整个网络模型就十分合理。从经验来看, 使用一组大的卷积滤波器也对深层CNN有益, 具体方法是将一些变换 Γ(⋅) 应用于一组 充当模型正则化器的小型基滤波器上。

沿着这一研究方向, 近期的许多研究提出了从一组基滤波器出发构建卷积层的思 想。它们的共同点是, 迁移矩阵 Γ(⋅) 是只在卷积滤波器的空间域中操作的一类函数。 例如, Shang et al. (2016) 发现, CNN的较低卷积层通过学习㐌余的滤波器来提取 输入信号的正负相位信息, 并将 Γ(⋅) 定义为简单的否定函数:

其中, Wx​ 是基础的卷积滤波器, Wx−​是由激活与 Wx​ 相反的移位 (shift) 构成的滤波 器, 并且这些移位是在最大池 (max-pooling) 操作后选择的。通过这样操作, 就可以 很容易的实现在所有卷积层上的二倍压缩率。它还表明, 否定变换作为一个强大的正 则化方法, 能够用以提高分类精度。一种直观的理解是, 具有成对正负约束的学习算 法可以产生实用而不是冗余的的卷积滤波器。此外, Zhai et al. (2016) 将 Γ(⋅) 定义为 应用于 2 维滤波器的平移函数集:Γ′Φ(x)=T(⋅,x,y)x,y∈{−k,…,k},(x,y)=(0,0)

其中, T(⋅,x,y) 表示第一个操作数沿其空间维度平移 (x,y), 并在边界处进行适当的零 填充以保持形状。提出的框架可用于公式 (1) 改善分类精度的问题, 进而作为 maxout网络的正则化版本。

对于将变换约束应用于卷积滤波器的方法,还有几个问题需要解决。首先,这些方法可以在宽/平的架构(如VGGNet,AlexNet)上实现有竞争力的性能,但是在窄/深的架构(如ResNet)上则不行。其次,迁移假设有时太强,无法指导学习过程,导致得到的结果在某些情况下不稳定。此外,使用紧凑的卷积滤波器虽然可以直接降低计算成本,但关键思想是要用紧凑的块替换松散的和过度参数化的滤波器以提高计算速度。

蒸馏学习(knowledge distillation)

蒸馏学习(knowledge distillation,KD)是指通过构建一个轻量化的小模型,利用性能更好的大模型的监督信息,来训练这个小模型,以期达到更好的性能和精度。KD与迁移学习(transfer learning)不同,在迁移学习中,我们使用相同的模型体系结构和学习的权重,仅根据应用的要求使用新层来替换部分全连接层。而在KD中,通过在大数据集上训练的更大的复杂网络(也称之为教师模型(teacher model))学习到的知识可以迁移到一个更小、更轻的网络上(也称之为学生模型(student model))。前一个大模型可以是单个的大模型,也可以是独立训练模型的集合。KD方法的主要思想是通过softmax函数学习课堂分布输出,将知识从大型教师模型转换为一个更小的学生模型。从教师模型训练学生模型的主要目的是学习教师模型的泛化能力。

在现有的KD方法中,学生模型的学习依赖于教师模型,是一个两阶段的过程。Lan et al.(2018)提出了实时本地集成(On-the-fly Native Ensemble,ONE),这是一种高效的单阶段在线蒸馏学习方法。在训练期间,ONE添加辅助分支以创建目标网络的多分支变体,然后从所有分支中创建本地集成教师模型。对于相同的目标标签约束,可以同时学习学生和每个分支。每个分支使用两个损失项进行训练,其中最常用的就是最大交叉熵损失(softmax cross-entropy loss)和蒸馏损失(distillation loss)。

在网络压缩这一步,可以使用深度神经网络方法来解决这个问题。Romero et al.(2015)提出了一种训练薄而深的网络的方法,称为FitNets,用以压缩宽且相对较浅(但实际上仍然很深)的网络。该方法扩展了原来的思想,允许得到更薄、更深的学生模型。为了学习教师网络的中间表示,FitNet让学生模仿老师的完全特征图。然而,这样的假设太过于严格,因为教师和学生的能力可能会有很大的差别。

基于蒸馏学习的方法可以使模型的深度变浅,并且能够显著降低计算成本。然而,这个方法也存在一些弊端。其中之一是KD方法只能应用于具有softmax损失函数的任务中。再者就是,与其他类型的方法相比,基于蒸馏学习的方法往往具有较差的竞争性能。

面临的问题

在文章的最后一部分,作者总结了现有的这些模型压缩和加速的方法仍然面临的一些问题与挑战,主要有以下几个方面:

  1. 当前的大多数先进方法建立在精心设计的CNN模型之上,这些模型限制了更改配置的自由度(例如,网络架构、超参数等)。为了处理更复杂的任务,未来应该提供更加合理的方法来配置压缩模型。
  2. 各种小型平台(例如移动设备、机器人、自动驾驶汽车等)的硬件限制仍然是阻碍深层CNN扩展的主要问题。如何充分利用有限的计算资源以及如何为这些平台设计特殊的压缩方法仍然是需要解决的问题。
  3. 剪枝是压缩和加速CNN的有效方法。目前的剪枝技术大多是为了修剪神经元之间的连接而设计的。此外,对通道进行剪枝能够直接减少特征映射的宽度并压缩模型。这种方法虽然很有效,但是修剪通道可能会显著地改变下一层的输入,因此也存在挑战性。
  4. 如前所述,结构矩阵和迁移卷积滤波器的方法必须使模型具有人类的先验知识,这将会显著影响模型的性能和稳定性。研究如何控制强加这些先验知识带来的影响至关重要。
  5. 蒸馏学习的方法具有很多的优点,比如无需特定的硬件就能够直接加速模型。开发基于KD的更多方法并且探索如何提高其性能是未来主要的发展方向。
  6. 尽管这些压缩方法取得了巨大的成就,但是黑箱机制(black box mechanism)仍然是其应用的关键障碍。比如,某些神经元/连接被修剪的原因尚不清楚。探索这些方法的解释能力仍然是一个重大挑战。

半监督学习综述

半监督学习(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代码,并修改训练参数、准备数据集,进行训练:(训练的中间结果)

原始
赛博化
原始
转换

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

Python爬虫:常用的爬虫工具汇总

最近需要跑一个风格迁移cyclegan项目,这个并不难,github上随便search一个就可以,但是数据集很是头疼,没有比较合适的数据集,因此需要自己在网上寻找一些图片,但如果不使用爬虫爬数据,不知道要到猴年马月,因此需要使用爬虫爬取谷歌、百度、以及一些图片网站的图片,之前倒是学过request库,但没怎么用过,因此先开个帖子,记录下相关知识。

爬虫整体思路:页面下载 –> 页面解析 –> 数据存储

一、页面下载器

 requests(必学)
      
     Requests: HTTP for Humans™
  1. python爬虫入门requests模块
  2. Python爬虫:requests库基本使用
  3. Python爬虫:使用requests库下载大文件
  4. Python爬虫:requests多进程爬取猫眼电影榜单
  5. requests InsecureRequestWarning: Unverified HTTPS request is being made.
  1. scrapy
    1. Python网络爬虫之scrapy框架
    2. scrapy学习
    3. Python爬虫:关于scrapy模块的请求头
    4. Python爬虫:scrapy框架请求参数meta、headers、cookies一探究竟
    5. Python爬虫:scrapy辅助功能实用函数
  2. selenium+chrome + PhantomJS(抓取动态网页,不推荐)
    1. mac下安装selenium+phantomjs+chromedriver
    2. Python爬虫:selenium模块基本使用
    3. Python爬虫selenium模块
    4. Python爬虫:selenium和Chrome无头浏览器抓取烯牛数据动态网页
    5. Python爬虫:利用selenium爬取淘宝商品信息
    6. Python爬虫:selenium使用chrome和PhantomJS实用参数
  1. Splash(抓取动态网页,推荐)
    1. Python爬虫:splash的安装与简单示例
    2. Python爬虫:splash+requests简单示例
    3. Python爬虫:scrapy利用splash爬取动态网页

总结: 对于下载器而言,python自带的urllib就不要花时间去学了,学了就忘,直接requests能满足大部分测试+抓取需求,进阶工程化scrapy,动态网页优先找API接口,如果有简单加密就破解,实在困难就使用splash渲染

二、页面解析器

  1. BeautifulSoup(入门级)
    1. Python爬虫入门BeautifulSoup模块
    2. Beautiful Soup 4.4.0 文档¶
  1. pyquery (类似jQuery)
    1. Python爬虫:pyquery模块解析网页
  2. lxml
    1. Python爬虫:使用lxml解析网页内容
  1. parsel
    1. Extract text using CSS or XPath selectors
  2. scrapy的Selector (强烈推荐, 比较高级的封装,基于parsel)
    1. 选择器(Selectors)
    2. python爬虫:scrapy框架xpath和css选择器语法

总结: 其实解析器学习一个就够了,其他都不用学,很多培训会教你从上到下的学习,我不是很推荐,直接学习scrapy的Selector 就行,简单、直接、高效

三、数据存储

  1. txt文本
    1. Python全栈之路:文件file常用操作
  1. csv文件
    1. python读取写入csv文件
  2. sqlite3 (python自带)
    1. Python编程:使用数据库sqlite3
  1. MySQL
    1. SQL:pymysql模块读写mysql数据
  2. MongoDB
    1. Python编程:mongodb的基本增删改查操作

总结: 数据存储没有什么可深究的,按照业务需求来就行,一般快速测试使用MongoDB,业务使用MySQL

四、其他工具

  1. execjs :执行js Python爬虫:execjs在python中运行javascript代码
  2. pyv8: 执行js mac安装pyv8模块-JavaScript翻译成python
  3. html5lib 1. Python爬虫:scrapy利用html5lib解析不规范的html文本

医学分割评价指标

可以看到,常见的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/

python 爬取网站图片

对于做人工智能来说,最主要的爬取目标是图片,需要在网上获取大量的图片数据用于模型训练。这里参考网上资料,自己写一个简单的爬虫程序。

1、爬取百度图片:

百度图片比较简单,通过一个ajax请求,来获取图片的url:

参数:

2、爬取 谷歌图片:

谷歌跟百度不同,需要使用 selenium

由于google图片界面是属于那种往下划会在本页面中加载出更多信息,但未刷新的机制,但是它又并未使用ajax。
所以这里我们使用selenium。selenium是一个能够模拟浏览器的工具,如果你没有安装,请pip install 一下。
然后是下载符合你的浏览器的驱动,我这里用的是Chrome,所以下载了ChromeDriver,将其放在D:\python\Scripts(你的python安装目录)。
用这两个来模拟用户的浏览器操作。


from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
import time
import os
import urllib.request
import uuid

def download_pic(url, name, path):

    if not os.path.exists(path):
        os.makedirs(path)
    res = urllib.request.urlopen(url, timeout=3).read()
    with open(path + name +'.jpg', 'wb') as file:
        file.write(res)
        file.close()

def get_image_url(num, key_word):

    box = driver.find_element_by_xpath('/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input')
    box.send_keys(key_word)
    box.send_keys(Keys.ENTER)
    box = driver.find_element_by_xpath('//*[@id="hdtb-msb"]/div[1]/div/div[2]/a').click()

    # 滚动页面
    last_height = driver.execute_script('return document.body.scrollHeight')
    while True:
        driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
        time.sleep(2)
        new_height = driver.execute_script('return document.body.scrollHeight')
        try:
            driver.find_elements_by_xpath('//*[@id="islmp"]/div/div/div/div/div[5]/input').click()
        except:
            pass
        if new_height == last_height:
            # 点击显示更多结果
            try:
                box = driver.find_element_by_xpath('//*[@id="islmp"]/div/div/div/div[1]/div[2]/div[2]/input').click()
            except:
                break
        last_height = new_height

    image_urls = []

    for i in range(1, num):
        try:
            image = driver.find_element_by_xpath('//*[@id="islrg"]/div[1]/div[' + str(i) + ']/a[1]/div[1]/img')
            # 此选项为下载缩略图
            # image_src = image.get_attribute("src")
            image.click() # 点开大图
            time.sleep(4)  # 因为谷歌页面是动态加载的,需要给予页面加载时间,否则无法获取原图url,如果你的网络状况一般请适当延长
            # 获取原图的url
            image_real = driver.find_element_by_xpath('//*[@id="Sva75c"]/div/div/div[3]/div[2]/c-wiz/div/div[1]/div[1]/div[2]/div[1]/a/img')
            image_url = image_real.get_attribute("src")
            image_urls.append(image_url)
            print(str(i) + ': ' + image_url)
        except:
            print(str(i) + ': error')
            pass
    return image_urls
if __name__ == '__main__':
    # 创建一个参数对象,用来控制chrome是否以无界面模式打开
    ch_op = Options()
    # 设置谷歌浏览器的页面无可视化,如果需要可视化请注释这两行代码
    ch_op.add_argument('--headless')
    ch_op.add_argument('--disable-gpu')

    url = "https://www.google.com/"
    driver = webdriver.Chrome(r'D:\anconda3\chromedriver.exe', options=ch_op)
    driver.get(url)

    key_word = input('请输入关键词:')
    num = int(input('请输入需要下载的图片数:'))
    _path = input('请输入图片保存路径,例如G:\\\\google\\\\images\\\\ :')

    # path = "G:\\google\\images_download\\" + key_word + "\\"  # 图片保存路径改为自己的路径
    path = _path + key_word + "\\"
    print('正在获取图片url...')
    image_urls = get_image_url(num, key_word)
    for index, url in enumerate(image_urls):
        try:
            print('第' + str(index) + '张图片开始下载...')
            download_pic(url, str(uuid.uuid1()), path)
        except Exception as e:
            print(e)
            print('第' + str(index) + '张图片下载失败')
            continue
    driver.quit()

Systemd 入门教程:实战篇

一、开机启动

对于那些支持 Systemd 的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件。

如果你想让该软件开机启动,就执行下面的命令(以httpd.service为例)。


$ sudo systemctl enable httpd

上面的命令相当于在/etc/systemd/system目录添加一个符号链接,指向/usr/lib/systemd/system里面的httpd.service文件。

这是因为开机时,Systemd只执行/etc/systemd/system目录里面的配置文件。这也意味着,如果把修改后的配置文件放在该目录,就可以达到覆盖原始配置的效果。

二、启动服务

设置开机启动以后,软件并不会立即启动,必须等到下一次开机。如果想现在就运行该软件,那么要执行systemctl start命令。


$ sudo systemctl start httpd

执行上面的命令以后,有可能启动失败,因此要用systemctl status命令查看一下该服务的状态。


$ sudo systemctl status httpd

httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled)
   Active: active (running) since 金 2014-12-05 12:18:22 JST; 7min ago
 Main PID: 4349 (httpd)
   Status: "Total requests: 1; Current requests/sec: 0; Current traffic:   0 B/sec"
   CGroup: /system.slice/httpd.service
           ├─4349 /usr/sbin/httpd -DFOREGROUND
           ├─4350 /usr/sbin/httpd -DFOREGROUND
           ├─4351 /usr/sbin/httpd -DFOREGROUND
           ├─4352 /usr/sbin/httpd -DFOREGROUND
           ├─4353 /usr/sbin/httpd -DFOREGROUND
           └─4354 /usr/sbin/httpd -DFOREGROUND

12月 05 12:18:22 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
12月 05 12:18:22 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
12月 05 12:22:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server.

上面的输出结果含义如下。

  • Loaded行:配置文件的位置,是否设为开机启动
  • Active行:表示正在运行
  • Main PID行:主进程ID
  • Status行:由应用本身(这里是 httpd )提供的软件当前状态
  • CGroup块:应用的所有子进程
  • 日志块:应用的日志

三、停止服务

终止正在运行的服务,需要执行systemctl stop命令。

$ sudo systemctl stop httpd.service

有时候,该命令可能没有响应,服务停不下来。这时候就不得不”杀进程”了,向正在运行的进程发出kill信号。

$ sudo systemctl kill httpd.service

此外,重启服务要执行systemctl restart命令。


$ sudo systemctl restart httpd.service

四、读懂配置文件

一个服务怎么启动,完全由它的配置文件决定。下面就来看,配置文件有些什么内容。

前面说过,配置文件主要放在/usr/lib/systemd/system目录,也可能在/etc/systemd/system目录。找到配置文件以后,使用文本编辑器打开即可。

systemctl cat命令可以用来查看配置文件,下面以sshd.service文件为例,它的作用是启动一个 SSH 服务器,供其他用户以 SSH 方式登录。

$ systemctl cat sshd.service

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
Type=simple
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

可以看到,配置文件分成几个区块,每个区块包含若干条键值对。

五、 [Unit] 区块:启动顺序与依赖关系。

Unit区块的Description字段给出当前服务的简单描述,Documentation字段给出文档位置。

接下来的设置是启动顺序和依赖关系,这个比较重要。

After字段:表示如果network.targetsshd-keygen.service需要启动,那么sshd.service应该在它们之后启动。

相应地,还有一个Before字段,定义sshd.service应该在哪些服务之前启动。

注意,AfterBefore字段只涉及启动顺序,不涉及依赖关系。

举例来说,某 Web 应用需要 postgresql 数据库储存数据。在配置文件中,它只定义要在 postgresql 之后启动,而没有定义依赖 postgresql 。上线后,由于某种原因,postgresql 需要重新启动,在停止服务期间,该 Web 应用就会无法建立数据库连接。

设置依赖关系,需要使用Wants字段和Requires字段。

Wants字段:表示sshd.servicesshd-keygen.service之间存在”弱依赖”关系,即如果”sshd-keygen.service”启动失败或停止运行,不影响sshd.service继续执行。

Requires字段则表示”强依赖”关系,即如果该服务启动失败或异常退出,那么sshd.service也必须退出。

注意,Wants字段与Requires字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动的。

六、[Service] 区块:启动行为

Service区块定义如何启动当前服务。

6.1 启动命令

许多软件都有自己的环境参数文件,该文件可以用EnvironmentFile字段读取。

EnvironmentFile字段:指定当前服务的环境参数文件。该文件内部的key=value键值对,可以用$key的形式,在当前配置文件中获取。

上面的例子中,sshd 的环境参数文件是/etc/sysconfig/sshd

配置文件里面最重要的字段是ExecStart

ExecStart字段:定义启动进程时执行的命令。

上面的例子中,启动sshd,执行的命令是/usr/sbin/sshd -D $OPTIONS,其中的变量$OPTIONS就来自EnvironmentFile字段指定的环境参数文件。

与之作用相似的,还有如下这些字段。

  • ExecReload字段:重启服务时执行的命令
  • ExecStop字段:停止服务时执行的命令
  • ExecStartPre字段:启动服务之前执行的命令
  • ExecStartPost字段:启动服务之后执行的命令
  • ExecStopPost字段:停止服务之后执行的命令

请看下面的例子。


[Service]
ExecStart=/bin/echo execstart1
ExecStart=
ExecStart=/bin/echo execstart2
ExecStartPost=/bin/echo post1
ExecStartPost=/bin/echo post2

上面这个配置文件,第二行ExecStart设为空值,等于取消了第一行的设置,运行结果如下。


execstart2
post1
post2

所有的启动设置之前,都可以加上一个连词号(-),表示”抑制错误”,即发生错误的时候,不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd(注意等号后面的那个连词号),就表示即使/etc/sysconfig/sshd文件不存在,也不会抛出错误。

6.2 启动类型

Type字段定义启动类型。它可以设置的值如下。

  • simple(默认值):ExecStart字段启动的进程为主进程
  • forking:ExecStart字段将以fork()方式启动,此时父进程将会退出,子进程将成为主进程
  • oneshot:类似于simple,但只执行一次,Systemd 会等它执行完,才启动其他服务
  • dbus:类似于simple,但会等待 D-Bus 信号后启动
  • notify:类似于simple,启动结束后会发出通知信号,然后 Systemd 再启动其他服务
  • idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合

下面是一个oneshot的例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写。


[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off

[Install]
WantedBy=multi-user.target

上面的配置文件,启动类型设为oneshot,就表明这个服务只要运行一次就够了,不需要长期运行。

如果关闭以后,将来某个时候还想打开,配置文件修改如下。


[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off start
ExecStop=/usr/bin/touchpad-off stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

上面配置文件中,RemainAfterExit字段设为yes,表示进程退出以后,服务仍然保持执行。这样的话,一旦使用systemctl stop命令停止服务,ExecStop指定的命令就会执行,从而重新开启触摸板。

6.3 重启行为

Service区块有一些字段,定义了重启行为。

KillMode字段:定义 Systemd 如何停止 sshd 服务。

上面这个例子中,将KillMode设为process,表示只停止主进程,不停止任何sshd 子进程,即子进程打开的 SSH session 仍然保持连接。这个设置不太常见,但对 sshd 很重要,否则你停止服务的时候,会连自己打开的 SSH session 一起杀掉。

KillMode字段可以设置的值如下。

  • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
  • process:只杀主进程
  • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
  • none:没有进程会被杀掉,只是执行服务的 stop 命令。

接下来是Restart字段。

Restart字段:定义了 sshd 退出后,Systemd 的重启方式。

上面的例子中,Restart设为on-failure,表示任何意外的失败,就将重启sshd。如果 sshd 正常停止(比如执行systemctl stop命令),它就不会重启。

Restart字段可以设置的值如下。

  • no(默认值):退出后不会重启
  • on-success:只有正常退出时(退出状态码为0),才会重启
  • on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
  • on-abnormal:只有被信号终止和超时,才会重启
  • on-abort:只有在收到没有捕捉到的信号终止时,才会重启
  • on-watchdog:超时退出,才会重启
  • always:不管是什么退出原因,总是重启

对于守护进程,推荐设为on-failure。对于那些允许发生错误退出的服务,可以设为on-abnormal

最后是RestartSec字段。

RestartSec字段:表示 Systemd 重启服务之前,需要等待的秒数。上面的例子设为等待42秒。

七、[Install] 区块

Install区块,定义如何安装这个配置文件,即怎样做到开机启动。

WantedBy字段:表示该服务所在的 Target。

Target的含义是服务组,表示一组服务。WantedBy=multi-user.target指的是,sshd 所在的 Target 是multi-user.target

这个设置非常重要,因为执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。

Systemd 有默认的启动 Target。


$ systemctl get-default
multi-user.target

上面的结果表示,默认的启动 Target 是multi-user.target。在这个组里的所有服务,都将开机启动。这就是为什么systemctl enable命令能设置开机启动的原因。

使用 Target 的时候,systemctl list-dependencies命令和systemctl isolate命令也很有用。


# 查看 multi-user.target 包含的所有服务
$ systemctl list-dependencies multi-user.target

# 切换到另一个 target
# shutdown.target 就是关机状态
$ sudo systemctl isolate shutdown.target

一般来说,常用的 Target 有两个:一个是multi-user.target,表示多用户命令行状态;另一个是graphical.target,表示图形用户状态,它依赖于multi-user.target。官方文档有一张非常清晰的 Target 依赖关系图

八、Target 的配置文件

Target 也有自己的配置文件。


$ systemctl cat multi-user.target

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes

注意,Target 配置文件里面没有启动命令。

上面输出结果中,主要字段含义如下。

Requires字段:要求basic.target一起运行。

Conflicts字段:冲突字段。如果rescue.servicerescue.target正在运行,multi-user.target就不能运行,反之亦然。

After:表示multi-user.targetbasic.target 、 rescue.service、 rescue.target之后启动,如果它们有启动的话。

AllowIsolate:允许使用systemctl isolate命令切换到multi-user.target

九、修改配置文件后重启

修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。


# 重新加载配置文件
$ sudo systemctl daemon-reload

# 重启相关服务
$ sudo systemctl restart foobar