深度学习图像分类问题涨分总结

    近来刚参加完公司内部比赛,现在整理下各种训练技巧,提升图像分类问题的得分。所有资源整理于网络,不再一一列举引用出处。

目录

经典网络模型

Label smooth

背景介绍

Label smooth 计算公式

Mixup

Test Time Augmentation

注意力机制

空间注意力模型(spatial attention)

通道注意力机制

空间和通道注意力机制的融合

分类问题的模型融合

投票法

加权投票法

Bagging

Boosting

Stacking

其他高阶玩法

GPU显存容量不够用怎么办

One cycle policy

Fix Resolution


经典网络模型

网络名称

相关链接

RegNet

https://arxiv.org/abs/2003.13678

EfficientNet

https://arxiv.org/pdf/1905.11946.pdf

VovNet

https://arxiv.org/abs/1904.09730

Resnet

https://arxiv.org/pdf/1512.03385.pdf

HRNet

https://arxiv.org/abs/1902.09212

Label smooth

背景介绍

    label smoothing是一种在分类问题中,防止过拟合的方法。多分类任务中,神经网络会输出一个当前数据对应于各个类别的置信度分数,将这些分数通过softmax进行归一化处理,最终会得到当前数据属于每个类别的概率。

$$q_i=\frac{exp(z_i)}{\sum_{j=1}^Kexp(z_j))}$$

    然后计算交叉熵损失函数如下,其中i表示多类别中的某一类。

Loss=\sum_{i=1}^Kp_ilogq_i

p_i=\begin{cases}1,&if(i=y))\\0,&if(i \neq y))\end{cases}

    训练神经网络时,最小化预测概率和标签真实概率之间的交叉熵,从而得到最优的预测概率分布。最优的预测概率分布:

Z_i=\begin{cases}+\infty,&if(i=y))\\0,&if(i\neq y))\end{cases}

   神经网络会促使自身往正确标签和错误标签差值最大的方向学习,在训练数据较少,不足以表征所有的样本特征的情况下,会导致网络过拟合。

Label smooth 计算公式

      将标签强制one-hot的方式使网络过于自信会导致过拟合,因此软化这种编码方式。通过soft one-hot来加入噪声,减少了真实样本标签的类别在计算损失函数时的权重,最终起到抑制过拟合的效果。注意:K表示多分类的类别总数,\epsilon是一个较小的超参数。

P_i=\begin{cases}1,&if(i=y)) \\ 0,& if(i\neq y)) \end{cases}\Rightarrow P_i=\begin{cases}(1-\epsilon),&if(i=y)) \\ \frac{\epsilon}{K-1},& if(i\neq y)) \end{cases}

      下式中等号左侧是一种新的预测分布,等号右侧前半部分是对原分布乘上一个权重,\epsilon是一个超参数,需要自己设定,取值范围在0到1之间,后半部分u是一个均匀分布,k表示模型的类别数。

q'(k|x)=(1-\epsilon)\delta_{k,y}+\epsilon u(k)

      由以上公式可以看出,这种方式使label有\epsilon概率来自于均匀分布,(1-\epsilon) 概率来自于原分布。这就相当于在原label上增加噪声,让模型的预测值不要过度集中于概率较高的类别,把一些概率放在概率较低的类别。交叉熵损失函数的改变如下:

Loss=-\sum_{i=1}^Kp_ilogq_i \Rightarrow Loss_i=\begin{cases}(1-\epsilon)*Loss,&if(i=y)) \\ \epsilon*Loss,& if(i\neq y)) \end{cases}

因此交叉熵可以替换为:

H(q',p)=-\sum_{k=1}&Klogp(k)q'(k)=(1-\epsilon)H(q,p)+\epsilon H(u,p)

最优预测概率分布如下:

Z_i=\begin{cases} +\infty,&if(i=y)\\ 0,&if(i\neq y)\end{cases} \Rightarrow Z_i=\begin{cases}log\frac{(k-1)(1-\epsilon)}{\epsilon+\alpha},&if(i=y)\\ \alpha,&if(i\neq y)\end{cases}

      这里的α是任意实数,最终模型通过抑制正负样本输出差值,使得网络有更强的泛化能力。可以理解为:对“预测的分布与真实分布”及“预测分布与先验分布(均匀分布)”的惩罚。

代码实现

# 修改y的取值,做平滑
def label_smoothing(inputs, epsilon=0.1):
    K = inputs.get_shape().as_list()[-1]    # number of channels
    return ((1-epsilon) * inputs) + (epsilon / K)

Mixup

       mixup是一种运用在计算机视觉中的对图像进行混类增强的算法,它可以将不同类之间的图像进行混合,从而扩充训练数据集。mixup可以改进当前最先进的神经网络架构的泛化能力。我们还发现,mixup能够减少对错误标签的记忆,增加对抗样本的鲁棒性,并能够稳定对生成对抗网络的训练过程。其以线性插值的方式来构建新的训练样本和标签,具体公式:

\tilde{x}=\lambda x_i+(1-\lambda)x_j

\tilde y =\lambda y_i+(1-\lambda) y_j

\lambda = Beta(\alpha, \beta)

(x_i,y_i),(x_j,y_j) 来自原始数据集中的训练样本(图片+标签)。其中\lambda是一个服从B分布的参数,\lambda \sim Beta(\alpha, \alpha).Beta分布的概率密度函数如下图所示,其中\alpha \in [0,+\infty]。无论如何设置α和β的值,期望\frac{\alpha}{\alpha+\beta}始终近似为0.5。在整个训练过程中有N个batch,权重在N个batch中期望近似为0.5,mixup作者认为α=β=0.5时,效果相对较好。

        随着超参数a的增大,网络的训练误差就会增加,而其泛化能力会随之增强。而当a趋近无穷大时,模型就会退化成最原始的训练策略。Mixup就是一种抑制过拟合的策略,增加了一些扰动,从而提升了模型的泛化能力。

代码实现:

def get_batch(x, y, step, batch_size, alpha=0.2):
    """
    get batch data
    :param x: training data
    :param y: one-hot label
    :param step: step
    :param batch_size: batch size
    :param alpha: hyper-parameter α, default as 0.2
    :return:
    """
    candidates_data, candidates_label = x, y
    offset = (step * batch_size) % (candidates_data.shape[0] - batch_size)

    # get batch data
    train_features_batch = candidates_data[offset:(offset + batch_size)]
    train_labels_batch = candidates_label[offset:(offset + batch_size)]

    # 最原始的训练方式
    if alpha == 0:
        return train_features_batch, train_labels_batch
    # mixup增强后的训练方式
    if alpha > 0:
        weight = np.random.beta(alpha, alpha, batch_size)
        x_weight = weight.reshape(batch_size, 1, 1, 1)
        y_weight = weight.reshape(batch_size, 1)
        index = np.random.permutation(batch_size)
        x1, x2 = train_features_batch, train_features_batch[index]
        x = x1 * x_weight + x2 * (1 - x_weight)
        y1, y2 = train_labels_batch, train_labels_batch[index]
        y = y1 * y_weight + y2 * (1 - y_weight)
        return x, y
import matplotlib.pyplot as plt
import matplotlib.image as Image
import numpy as np

im1 = Image.imread(r"C:\Users\Daisy\Desktop\1\xyjy.png")
im2 = Image.imread(r"C:\Users\Daisy\Desktop\1\xyjy2.png")
for i in range(1,10):
    lam= i*0.1
    im_mixup = (im1*lam+im2*(1-lam))
    plt.subplot(3,3,i)
    plt.imshow(im_mixup)
plt.show()

Test Time Augmentation

    TTA(Test-Time Augmentation) ,即测试时的数据增强。它会为原始图像造出多个不同版本,包括不同区域裁剪和更改缩放程度等,并将它们输入到模型中;然后对多个版本进行计算得到平均输出,作为图像的最终输出分数。

    实现步骤如下: [此处只考虑分类,忽略语义分割]

  1. 将1个batch的数据通过flips, rotation, scale, etc.等操作生成batches
  2. 将各个batch分别输入网络
  3. 每个batch的masks/labels反向转换 [仅对mask有效,labels无反变换]
  4. 通过mean, max, gmean, etc.合并各个batch预测的结果
  5. 最后输出最终的masks/labels

代码实现:这里推荐github上一个库https://github.com/qubvel/ttach,可以直接调用tta,非常方便

tta_model = tta.ClassificationTTAWrapper(model, tta.aliases.five_crop_transform(),  merge_mode='mean')
  • 第一个参数为model,即为输入的模型
  • 第二个参数为transform类型,调用作者已经设定好的tta类型tta.aliases

注意力机制

所谓Attention机制,便是聚焦于局部信息的机制,比如图像中的某一个图像区域。随着任务的变化,注意力区域往往会发生变化。注意力机制的本质就是定位到感兴趣的信息,抑制无用信息,结果通常都是以概率图或者概率特征向量的形式展示,从原理上来说,主要分为空间注意力模型,通道注意力模型,空间和通道混合注意力模型三种。

空间注意力模型(spatial attention)

    不是图像中所有的区域对任务的贡献都是同样重要的,只有任务相关的区域才是需要关心的,比如分类任务的主体,空间注意力模型就是寻找网络中最重要的部位进行处理。我们在这里给大家介绍两个具有代表性的模型,第一个就是Google DeepMind提出的STN网络(Spatial Transformer Network[1])。它通过学习输入的形变,从而完成适合任务的预处理操作,是一种基于空间的Attention模型,网络结构如下:

    这里的Localization Net用于生成仿射变换系数,输入是C×H×W维的图像,输出是一个空间变换系数,它的大小根据要学习的变换类型而定,如果是仿射变换,则是一个6维向量。这样的一个网络要完成的效果如下图:

\

    由于在大部分情况下我们感兴趣的区域只是图像中的一小部分,因此空间注意力的本质就是定位目标并进行一些变换或者获取权重。

通道注意力机制

    对于输入2维图像的CNN来说,一个维度是图像的尺度空间,即长宽,另一个维度就是通道,因此基于通道的Attention也是很常用的机制。SENet(Sequeeze and Excitation Net)[3]是2017届ImageNet分类比赛的冠军网络,本质上是一个基于通道的Attention模型,它通过建模各个特征通道的重要程度,然后针对不同的任务增强或者抑制不同的通道,原理图如下

    在正常的卷积操作后分出了一个旁路分支,首先进行Squeeze操作(即图中Fsq(·)),它将空间维度进行特征压缩,即每个二维的特征图变成一个实数,相当于具有全局感受野的池化操作,特征通道数不变。然后是Excitation操作(即图中的Fex(·)),它通过参数w为每个特征通道生成权重,w被学习用来显式地建模特征通道间的相关性。在文章中,使用了一个2层bottleneck结构(先降维再升维)的全连接层+Sigmoid函数来实现。得到了每一个特征通道的权重之后,就将该权重应用于原来的每个特征通道,基于特定的任务,就可以学习到不同通道的重要性。将其机制应用于若干基准模型,在增加少量计算量的情况下,获得了更明显的性能提升。作为一种通用的设计思想,它可以被用于任何现有网络,具有较强的实践意义。

空间和通道注意力机制的融合

    前述的Dynamic Capacity Network是从空间维度进行Attention,SENet是从通道维度进行Attention,自然也可以同时使用空间Attention和通道Attention机制。卷积块注意模块(CBAM),这是一个简单的用于前馈卷积神经网络的有效注意模块。给出一个中间特征映射,我们的模块依次沿着两个独立的维度,通道和空间推断注意图,然后将注意图倍增到用于自适应特征细化的输入特征映射。因为CBAM是一个轻量级的通用模块,它可以无缝地集成到任何CNN架构中,开销可以忽略不计,并且可以与基本CNN一起进行端到端的跟踪。CBAM(Convolutional Block Attention Module)[5]是其中的代表性网络,结构如下:

      通道方向的Attention建模的是特征的重要性,结构如下:

        同时使用平均池化和最大池化操作来聚合特征映射的空间信息,送到一个共享网络, 压缩输入特征图的空间维数,逐元素求和合并,以产生我们的通道注意力图 Mc

      空间方向的Attention建模的是空间位置的重要性,结构如下:

    

  还是使用average pooling和max pooling对输入feature map进行压缩操作,只不过这里的压缩变成了通道层面上的压缩,连接起来,用7*7卷积生成空间注意力图谱.

       CBAM集成网络很好的学习目标对象区域中的信息并从中聚合特征。通过实验发现串联两个attention模块的效果要优于并联。通道attention放在前面要优于空间attention模块放在前面。除此之外,还有很多的注意力机制相关的研究,比如残差注意力机制,多尺度注意力机制,递归注意力机制等。

    pytorch版本实现:

class CBAMBlock(tf.keras.layers.Layer):
    def __init__(self, channel, ratio=8):
        super(CBAMBlock, self).__init__()
        self.avgpool = tf.keras.layers.GlobalAveragePooling2D()
        self.maxpool = tf.keras.layers.GlobalMaxPool2D()
        self.shared_layer_one = tf.keras.layers.Dense(channel//ratio, activation='relu',
                                                        kernel_initializer='he_normal',
                                                        use_bias=True,
                                                        bias_initializer='zeros')
        self.shared_layer_two = tf.keras.layers.Dense(channel, kernel_initializer='he_normal',
                                        use_bias=True, bias_initializer='zeros')
        self.reshape = tf.keras.layers.Reshape((1,1,channel))
        self.multiply = tf.keras.layers.Multiply()
        self.add = tf.keras.layers.Add()
        self.activation = tf.keras.layers.Activation('sigmoid')
        self.concat = tf.keras.layers.Concatenate(axis=3)
        self.conv = tf.keras.layers.Conv2D(filters = 1, kernel_size=(7,7), strides=1,
                                            padding='same', activation='sigmoid', kernel_initializer='he_normal', 
                                            use_bias=False)
        self.channel = channel

    def call(self, inputs, **kwargs):
        #channel_attention
        avg_pool = self.avgpool(inputs)
        avg_pool = self.reshape(avg_pool)
        #assert avg_pool.shape[1:] == (1,1,self.channel)
        avg_pool = self.shared_layer_one(avg_pool)
        #assert avg_pool.shape[1:] == (1,1,self.channel//8)
        avg_pool = self.shared_layer_two(avg_pool)
        #assert avg_pool.shape[1:] == (1,1,self.channel)
        
        max_pool = self.maxpool(inputs)
        max_pool = self.reshape(max_pool)
        #assert max_pool.shape[1:] == (1,1,self.channel)
        max_pool = self.shared_layer_one(max_pool)
        #assert max_pool.shape[1:] == (1,1,self.channel//8)
        max_pool = self.shared_layer_two(max_pool)
        #assert max_pool.shape[1:] == (1,1,self.channel)
        
        channel_feature = self.add([avg_pool,max_pool])
        channel_feature = self.activation(channel_feature)
        channel_feature = self.multiply([inputs, channel_feature])
        
        #spatial_attention
        avg_pool = tf.reduce_mean(channel_feature, axis=[3], keepdims=True)
        #assert avg_pool.shape[-1] == 1
        max_pool = tf.reduce_max(channel_feature, axis=[3], keepdims=True)
        #assert max_pool.shape[-1] == 1
        concat = self.concat([avg_pool, max_pool])
        #assert concat.shape[-1] == 2
        spatial_feature = self.conv(concat)
        cbam_feature = self.multiply([channel_feature, spatial_feature])
        
        return cbam_feature

分类问题的模型融合

投票法

    假设对于一个二分类问题,有3个基础模型,那么就采取投票制的方法,投票多者确定为最终的分类。

H(x)=c\underset{x}{argmin} \sum_{i=1}^T h_i^j (x)

   即各个分类器输出其预测的类别,取最高票对应的类别作为结果。若有多个类别都是最高票,那么随机选取一个。

加权投票法

   和上面的简单投票法类似,不过多了权重αi,这样可以区分分类器的重要程度,通常

H(x)=c\underset{x}{argmin} \sum_{i=1}^T \alpha_i \cdot h_i^j (x)

\alpha \geq 0; \sum_{i=1}^T \alpha_i =1

  此外,个体学习器可能产生不同的h_i^j(x)的值,比如类标记和类概率。

  • 类标记取值0或1,即hi将样本x预测为类别cj,则取值为1,否则为0。使用类别标记的投票称为“硬投票”
  • 类概率的取值为0~1,即输出类别为cj的概率,使用类别概率的投票称为软投票,对应sklearn中的VotingClassifier,voting参数设置为soft

Bagging

    使用训练数据的不同随机子集来训练每个 Base Model,最后进行每个 Base Model 权重相同的 Vote。也即 Random Forest 的原理。大概分为这样两步:

1.重复K次

有放回地重复抽样建模,训练子模型

2.模型融合

分类问题:voting

Boosting

    每一次训练的时候都更加关心分类错误的样例,给这些分类错误的样例增加更大的权重,下一次迭代的目标就是能够更容易辨别出上一轮分类错误的样例。最终将这些弱分类器进行加权相加。

Stacking

这边是横向stack实现,即预训练了好几种模型,然后通过全连接层连接他们。固定之前的参数,只训练新增的全连接层参数。个人认为就是soft voting的参数学习过程。

pytorch实现代码:

class JSTNET(nn.Module):
    def __init__(self, model_b3, model_12GF, model_32GF, nb_class=4):
    #def __init__(self, model_b3, model_12GF, model_32GF, model_vovnet, nb_class=4):
        super(JSTNET, self).__init__()
        self.model_b3 = model_b3
        self.model_12GF = model_12GF
        self.model_32GF = model_32GF
        #self.model_vovnet = model_vovnet
        
        self.model_b3.fc = nn.Identity()
        self.model_12GF.fc = nn.Identity()
        self.model_32GF.fc = nn.Identity()
        #self.model_vovnet.fc = nn.Identity()
        
        #self.dropout = nn.Dropout(0.5)
        #self.relu = nn.ReLU(inplace=True)
        #self.classifier = nn.Linear(8512, nb_class)
        self.classifier = nn.Linear(7488, nb_class)
        #self.classifier_2 = nn.Linear(200, nb_class)
        
    def forward(self, inputs):
        output_b3 = self.model_b3(inputs.clone())
        output_b3 = output_b3.view(output_b3.size(0), -1)
        output_12GF = self.model_12GF(inputs.clone())
        output_12GF = output_12GF.view(output_12GF.size(0), -1)
        #output_vovnet = self.model_vovnet(inputs.clone())
        #output_vovnet = output_vovnet.view(output_vovnet.size(0), -1)
        output_32GF = self.model_32GF(inputs.clone())
        output_32GF = output_32GF.view(output_32GF.size(0), -1)
        
        #new_input = torch.cat([output_b3,output_12GF,output_vovnet,output_32GF],dim=1)
        new_input = torch.cat([output_b3,output_12GF,output_32GF],dim=1)
        x = self.classifier(new_input)
        #x = self.relu(x)
        #x = self.dropout(x)
        #x = self.classifier_2(x)
        '''
        print('new_input:',new_input.shape)
        test_input = new_input.view(new_input.shape[0],-1)
        print('test_inpu:',test_input.shape)
        (b,in_f) = test_input.shape
        print(b, in_f)
        
        self.fc1 = nn.Linear(in_f, 100) 
        x = self.fc1(new_input)
        x = self.relu(x)
        x = self.dropout(x)
        test_input = x.view(x.size[0],-1)
        (b,in_f) = test_input.shape
        self.fc2 = nn.Linear(in_f,4)
        x = self.fc2(x)
        x = F.softmax(x, dim=1)
        '''
        return x

其他高阶玩法

GPU显存容量不够用怎么办

    我们知道网络训练时,使用较大的batch size会比小batch size得到更好的结果。如果GPU资源充裕的,可以考虑GPU分布式训练(模型分布式和数据分布式),这里不扩展。 如果GPU资源不充裕,可以在训练初始阶段,采用“小尺寸图片+大批量”的训练方式。然后再基于上一步的模型,逐渐扩大图片大小,减少batch size,进行迁移学习。 这样确保精调阶段的最终解,不会过分远离大批量时找到初期的较优解。

One cycle policy

    对于网络训练,有没有办法可以既快又好呢?有兴趣的同学,可以参考如下论文。

  1. "Super-convergence: Very fast training of neural networks using large learning rates.“
  2. “Cyclical Learning Rates for Training Neural Networks”

    就是通过寻找一个较好的学习率策略,让网络快速收敛。下面是论文里截图,在cifar10数据集上采用resnet-56训练,one-cycle policy达到了既好又快的目的。

    当前fastai和pytorch lightning都支持该技术。

        第一步寻找学习率上下界,在训练集上学习率从小到大依次递增,随着学习率增大,当梯度爆炸loss反弹后停止,对应的损失函数如下图所示。

      上图中红点是fastai给出的建议学习率,我们只需挑选loss能显著下降的学习率,1e-3和1e-2都是可以接受的。通过这张图就可以挑选学习率的上界,另外论文推荐,学习率的下界就是上界的1/10或1/20。

      第二步在整个训练集上采用下图所示的学习率策略训练。注意下图迭代次数是N个epoch内所有迭代次数,迭代次数=图片数量/batch size。下图中每个iteration会采用不同的学习率进行训练。另一张图就是动量了,它和学习率的大小关系正好相反,防止网络陷入到局部极值无法跳出来。

      学习率从小到大,可以保证Loss快速收敛,因为在learning rate finding阶段已经是事实了。学习率从大到小,属于网络精调,收敛到最佳位置。

Fix Resolution

    FixRes是Fix Resolution的简写形式,最初用在对EfficientNet的改进。固定分辨率是指,在训练时间和测试裁剪的分辨率保持固定大小,属于数据增强技术。具体参考论文:Fixing the train-test resolution discrepancy。

     红框表示crop的区域,它会被送入神经网络。对于传统标准训练,假设训练和测试时都用224*224大小裁剪图片,我们可以发现,尽管两张照片的马大小是一样的,但是训练时“马儿”图片大小比测试时大很多,这会影响性能。为了尽可能保证训练和测试时马儿大小一样,减少网络对于尺度不变性的要求。可以在训练时采用更小的图片裁剪尺寸比如128*128,然后测试时采用224*224的裁剪尺寸;也可以训练224*224,测试时384*384。这里需要注意,这个缩放比例采用如下公式,K是crop后的size,HW是原始图片大小。

    

pytorch版本代码实现:

    network = "RegNetY_6_4GF"
    pretrained = False
    num_classes = 4
    seed = 0
    input_image_size = 500
    scale = 600 / 500

    train_dataset = datasets.ImageFolder(
        train_dataset_path,
        transforms.Compose([
            transforms.RandomHorizontalFlip(),
            transforms.RandomResizedCrop(input_image_size),
            transforms.ColorJitter(brightness=0.5,
                                   contrast=0.5,
                                   saturation=0.5,
                                   hue=0.5),
            transforms.RandomRotation(10),
            transforms.ToTensor()
        ]))
    val_dataset = datasets.ImageFolder(
        val_dataset_path,
        transforms.Compose([
            transforms.Resize(int(input_image_size*scale)),
            transforms.CenterCrop(input_image_size),
            transforms.ToTensor()
        ]))

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值