深度学习debug沉思录第二集

前言

在之前文章[1]中,我们曾经讨论过在深度学习实践过程中可能遇到的一些bug或者问题,总共讨论了20个问题。在本文中,我们将继续讨论与深度学习有关的bug和问题,其中很多是关于一些超参数的实践中的调优问题。本文将持续更新。需要强调的是,本文的很多单纯只是经验,在尽可能列出参考文献的同时却并无严格理论验证,希望大家见谅。欢迎大家集思广益,共同维护这个经验集,为整个社区贡献微弱力量。

∇ \nabla 联系方式:

e-mail: FesianXu@gmail.com

QQ: 973926198

github: https://github.com/FesianXu

知乎专栏: 计算机视觉/计算机图形理论与应用

微信公众号
qrcode


1. 协变性和不变性

协变性(Covariance)和不变性(Invariance)是在深度学习任务中比较重要的两个概念,用于指导根据不同任务来进行卷积网络的设计。

其中,协变性[6]简单来说,是网络的输入和输出能够在线形変换(比如translation操作,简单的deformation也可以看成线性的)下,呈现相同比例的变化,用Wikipedia[6]上的解释来说,就是:

For a covector1 to be basis-independent, its components must co-vary with a change of basis to remain representing the same covector. That is, the components must be transformed by the same matrix as the change of basis matrix.(In English)
对于某个covector是基底无关的,而且它的线性分解的系数 w i w_i wi( c = ∑ i N w i b i c = \sum_{i}^Nw_ib_i c=iNwibi)必须跟随着基底的变化而变化,以保持同个covector表示的不变性,也就是,当基底用矩阵 M M M进行变换时,系数 w i w_i wi必须也用同样的矩阵 M M M进行变换。

而不变性则好理解得多,就是指不管网络的输入怎么变化,其输出始终会保持在一个误差范围 ϵ \epsilon ϵ内(很难做到绝对的不变),数学表达为:
∣ f ( τ ( x ) ) − f ( x ) ∣ ≤ ϵ |f(\tau(\mathbf{x}))-f(\mathbf{x})| \leq \epsilon f(τ(x))f(x)ϵ
其中 τ ( ⋅ ) \tau(\cdot) τ()为一个变换。

那么,在一些应用场景,比如语义分割(semantic image segmentation)动作估计(motion estimation),物体识别(object detection)中,则要求网络模型具有协变性,因为其实体可能分布在图像的任意角落中,并且图像中通常有多个不同的实体,如果网络模型要求协变性,那么通常不使用池化pooling等手段进行尺度下采样

反之,在一些应用中,比如物体分类(image classification)中,因为通常图像上只包含一种目标实体,因此要求网络模型具有不变性,此时就可以用池化pooling手段对图像feature map进行下采样了。[7]

我们知道,池化pooling[8]会选取某个固定大小区域中的具有代表性的值(最大值或者平均值等)作为这个区域的代表,那么,如果图像的某个小块在其中旋转(rotation)或者平移(translation),因为这个下采样的特性,将会使得特征具有平移和旋转不变性,然而,正如上面所述的,在某些场景中,这个不变性是不期望得到的,可能会有负效果。


2. 在视觉任务中从头开始训练模型可能存在loss的“平坦期”

笔者以前多在skeleton数据[9]上跑动作识别模型,而skeleton数据有个特点就是足够的紧致(compact),而传统的RGB视频数据则有着很多和动作信息(pose-related)无关的信息,比如背景信息,光照等,因此使用skeleton信息作为动作识别的输入媒体,能在刚开始训练模型时就得到快速的loss下降。

而一些视觉模型使用的是RGB信息,比如ImageNet, UCF101等,如果从头训练这些模型(from scratch),因为没有任何预训练模型提供足够好的浅层特征,在训练过程中就需要模型去“关注表演动作的实体”并且去学习图像的浅层特征,如纹理,边缘等,因此训练过程中可能出现:loss在刚开始的几个epoch不会下降,即便是训练率已经很低了,在某个epoch后才开始正常下降。

因此,即便是你的模型在前几个epoch不收敛,也可以继续跑下去观察是否是这个问题造成的。可以尝试在前面使用预训练的模型进行浅层特征提取,将有利于整个模型的收敛。


3. 若出现training loss一直不下降的情况,可以尝试缩小网络参数量

在第二节中笔者曾经谈到过关于training from scratch时的开始的“平坦期”的情况,现在还有一种情况是:整个网络在训练过程中,训练损失总是不下降,或者下降得很慢,也就是说网络模型甚至没有办法过拟合。而这个相同的网络在其他更大型的数据集上却能正常工作 这个问题很可能是因为你的网络模型参数量太大了,特别是在全连接层中参数量太大了,而训练集样本不足以支撑整个网络。

这个解释有点奇怪而且违反了常理,因为即便是网络参数量太大也应该只会导致过拟合而不至于没法拟合才对。但是笔者在实验过程中却发现了这个情况。后面笔者将最后一层的卷积层(shape (N, c=512, h=128,w=171))用1x1卷积将通道数弄成1之后,才大大减少了后续的全连接层的参数量,解决了这个问题。这个情况欢迎各位讨论,给予意见。

PS: 这个情况在笔者跑C3D模型[10]验证RGB video数据时出现的,数据集是笔者实验室自己采集的,规模较小。


4. 进行模型迁移时,要根据任务进行特定层的保留和舍去

在进行视觉相关的任务,如image/video caption, video/image action recognition, object detection时,常常会用预先在大型数据集ImageNet上进行过预训练的卷积模型,如resnet,vgg等作为迁移特征提取器,以在新任务中提取出有效地低级特征,如条纹,边缘等,加快训练的收敛。有些模型因为训练难度,甚至一定需要用预训练好的模型进行特征提取后,进行分步骤地训练,才能得到正常的收敛,如LRCN[11]。

然而,在这些预训练模型中,经常也是和特定任务有关的,比如说分类任务,就肯定会在最后存在若干全连接层。一般来说,越靠近最后的层,其和特定任务的关联就最大,因此在迁移模型的时候,只会使用卷积层过后的最前面的全连接层作为特征,然后进行后续其他任务的迁移等。

conv_1
conv_2
conv_3
fc_1
fc_2

如上图所示,一般来说只会用fc_1的特征进行模型的迁移使用。


5. 在某些场景可能使用局部连接的卷积层更好

卷积网络是在图像处理领域率先使用的,在卷积网络中,一个很重要的假设就是图片的局部统计特征具有一致性,换句话说就是,一张图片上,某个小区域的统计特性应该和同一张图片的其他区域的是相似的,在这种情况下,就可以采用参数共享(weights sharing)的手段以大幅度减少需学习的参数的数量,并且有利于泛化。[12]

local_weight_sharing)
但是,在某些场景中,比如人脸识别中,因为人脸的局部特征不能很好地符合这个假设,因此在一些工作中,会尝试使用不带有参数共享的卷积网络进行处理[15],比如DeepFace[13,14],其结构就如:

  • Conv:32个11×11×3的卷积核
  • max-pooling: 3×3, stride=2
  • Conv: 16个9×9的卷积核
  • Local-Conv: 16个9×9的卷积核,Local的意思是卷积核的参数不共享
  • Local-Conv: 16个7×7的卷积核,参数不共享
  • Local-Conv: 16个5×5的卷积核,参数不共享
  • Fully-connected: 4096维
  • Softmax: 4030维

因此,在某些特定的图像领域,如果觉得你的待处理对象不符合这个假设都可以尝试这个手段。其也有成熟的API接口,比如:

  • TensorFlow: tf.keras.layers.LocallyConnected2D。 (Keras中同样也有)
  • PyTorch: 可以通过torch.nn.foldtorch.nn.unfold间接实现,目前还没有直接的API,不过下一版本应该会出来。[16,17,18]

6. 一种特殊的过拟合现象

笔者在近日的实验中,发现了一种比较特殊的过拟合现象,实验场景大致是:

对特征 x \mathbf{x} x进行注意力机制的操作,公式如: x : = x ⋅ ( 1 + M ) \mathbf{x} := \mathbf{x} \cdot (1 + \mathbf{M}) x:=x(1+M), 其中加上1的目的是做所谓的加强特征的处理。这里的 M \mathbf{M} M中的元素都在 [ 0 , 1 ] [0,1] [0,1]之间。

在实验过程中发现,输出的分类的logit中,经常有一个值会比其他值有数量级上的差距,比如20.03和0.03, 0.20等,这样导致很容易在交叉熵的计算过程中导致出现接近0的现象(当logit预测位和label相同时),同时,此时的loss很容易上下震荡,并且很容易出现0.0000的现象(交叉熵不可能等于0,此处只是因为太接近0了)。此时应该也是出现了过拟合的现象,形成原因暂且不明。


7. 通过矩阵分解对全连接层进行压缩

全连接层在深度学习中也有着广泛的应用,数学表示为:
y n = W n − 1 x n − 1 y_{n} = W_{n-1} x_{n-1} yn=Wn1xn1
其中 W n − 1 ∈ R n × m W_{n-1} \in \mathbb{R}^{n \times m} Wn1Rn×m,当其中的 n , m n,m n,m都比较大时,无论是对于计算还是内存都是一个大考验,因此可以用基于矩阵奇异值分解的思想,进行分解。我们将奇异值缩减到只保留最大的 t t t个,称之为truncated SVD,我们有:
W ≈ U Σ t V T W \approx U \Sigma_t V^T WUΣtVT
其中 U ∈ R n × t U \in \mathbb{R}^{n \times t} URn×t, Σ t ∈ R t × t \Sigma_t \in \mathbb{R}^{t \times t} ΣtRt×t V ∈ R m × t V \in \mathbb{R}^{m \times t} VRm×t,通过这种手段可以将参数量从之前的 n × m n \times m n×m缩减到 t × ( n + m ) t \times (n+m) t×(n+m),当然,要求这里的 t < < min ⁡ ( n , m ) t << \min(n,m) t<<min(n,m)
那么在实践中,我们把一层的全连接层更换成两层全连接层叠加,两层之间不添加非线性激活单元,第一层的参数矩阵为 Σ t V T \Sigma_t V^T ΣtVT(同时,这个层没有添加偏置),第二层的参数矩阵为 U U U,这层的偏置就是原先的 W W W中的偏置。

因此,在学习到了 W W W之后,可以通过这种方法,将一层换成两层全连接进行近似,取得更高的计算效率和储存效率。


8. 在使用CTC Loss时其loss会Nan

CTC Loss(Connectionist Temporal Classification Loss)[19]是在序列到序列(Seq2Seq)模型中常见的loss,使用此类loss的好处就是可以避免对序列进行显式地对齐(alignment),比如在语音翻译成文本的过程中,显式地对语音进行每个音节的标注是需要大量人力物力的,而使用CTC Loss可以避免这种标注。
在使用这种loss的时候,很基本的,需要注意你预测出来的类别是 C a l l + 1 C_{all}+1 Call+1,其中的加1表示的是加上blank字符也就是下图中的 ϵ \epsilon ϵ。如果预测出来的类别没有加1,则可能会导致loss变成Nan,梯度爆炸等。
最一般的的就是在特征提取网络之后的LSTM之后需要输出若干个类的概率分布时,将这个类设置成 C a l l + 1 C_{all}+1 Call+1。[20]

在这里插入图片描述


9. 在进行预训练模型的测试时,容易出现标签没对齐的情况

笔者最近犯了一个很傻丫丫的错误。笔者最近需要评估一个动作识别模型的性能,其在github上有预训练好的模型,因此笔者直接下载完数据集Kinetics 400之后就直接考虑去评估模型性能了。因为数据集中的每个文件夹中都包含着同一类的样本,一共有着400个类别,因此有400个文件夹。笔者一开始并不知道每个文件名,也就是类别名字与其label数字标签的对应关系,竟然傻傻地对其进行os.listdir()之后按顺序进行排序,顺序给了一个数字标签,这个显然是不对的,因为模型是加载预训练模型的,其输出的标签的规则和我自己定义的不一定相同,如果是自己从头训练还好不会受到影响,如果是打算在预训练模型上进行,笔者的这个操作就放了大错误了。后果当然就是模型的评估结果很差,让笔者花了不少时间调bug啊。读者一定要注意这种低级错误,如果评估一个现有的预训练模型,一定要注意这个预训练模型对标签是怎么定义的。不要一上手就急着进行评估和编码,欲速则不达。


10. 在进行不同的深度学习框架之间进行模型迁移时的调试

不同深度学习框架(比如pytorchtensorflowcaffe2等等)之间的底层运算细则,底层运算精度可能相差很多,并且其某些层的默认参数(比如BN层中有eps参数)可能每个框架都不同,因此为了更好地在不同的深度学习框架中进行模型迁移,及时地盘查出出错的迁移层,我们可以用相同的输入(比如全1矩阵),在两个不同的框架中的相同模型中进行前向传播,当然,两个不同的框架的模型需要载入相同的模型参数,然后观测到底在哪层开始中间的输出张量的具体值(比如对张量进行取均值layer_out.mean())开始变得不同,据此去进行高效地调试。


11. 在大批次训练中,可以增加学习率

文献[21]指出,训练过程中,在batch size越大的情况下,可以加大学习率。这个想法其实相当直观,从理论上说,深度学习模型在学习过程中是从随机样本中采样得到的,采样的一个批次我们称之为batch,增大批次的数量大小,并不会影响到随机梯度下降的均值,但会减少其方差。换句话说,批次越大,在梯度下降过程中其噪声就越小,梯度的方向可信度就越高,因此可以用更大的学习率进行学习。文献[22]指出在resnet-50的训练过程中,学习率可以随着batch size线性增大或减小。如果将batch size = 256时的学习率初始化为0.1,让实际的batch size表示为 b b b,那么有初始化学习率为 0.1 × b 256 0.1 \times \dfrac{b}{256} 0.1×256b


12. 保证可复现性

深度学习是偏向于“玄学”的,意味着存在很多随机因素可能会导致实验的不可复现,因此为了保证实验的可复现,尽量需要控制无关的随机变量。随机种子的设置决定了随机数生成,也就对参数随机初始化产生影响,因此实验中一开始尽量设置固定的随机种子以保证每次实验中的参数初始化都是固定的,可控的。代码如:

def init_seed(set_seed=1):
    torch.cuda.manual_seed_all(set_seed)
    torch.manual_seed(set_seed)
    np.random.seed(set_seed)
    random.seed(set_seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    # 使用benchmark以启动CUDNN_FIND自动寻找最快的操作,当计算图不会改变的时候(每次输入形状相同,模型不改变)的情况下可以提高性能,反之则降低性能

Reference

[1]. 深度学习debug沉思录

[2]. Ioffe S, Szegedy C. Batch normalization: accelerating deep network training by reducing internal covariate shift[C]// International Conference on International Conference on Machine Learning. JMLR.org, 2015:448-456.

[3]. Pytorch的BatchNorm层使用中容易出现的问题

[4]. https://github.com/tensorflow/tensorflow/blob/r1.12/tensorflow/python/ops/nn_impl.py

[5]. https://www.tensorflow.org/api_docs/python/tf/nn/batch_normalization

[6]. https://en.wikipedia.org/wiki/Covariance_and_contravariance_of_vectors

[7]. Bronstein M M, Bruna J, LeCun Y, et al. Geometric deep learning: going beyond euclidean data[J]. IEEE Signal Processing Magazine, 2017, 34(4): 18-42.

[8]. https://www.cnblogs.com/makefile/p/pooling.html

[9]. 【动作识别相关,第一篇】skeleton骨骼点数据类型介绍

[10]. Tran, Du, et al. “Learning spatiotemporal features with 3d convolutional networks.”
Proceedings of the IEEE international conference on computer vision. 2015.

[11]. Donahue J, Anne Hendricks L, Guadarrama S, et al. Long-term recurrent convolutional networks for visual recognition and description[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2015: 2625-2634.

[12]. https://www.quora.com/What-exactly-is-meant-by-shared-weights-in-convolutional-neural-network

[13]. Taigman Y, Yang M, Ranzato M A, et al. Deepface: Closing the gap to human-level performance in face verification[C]//Computer Vision and Pattern Recognition (CVPR), 2014 IEEE Conference on. IEEE, 2014: 1701-1708.

[14]. https://blog.csdn.net/app_12062011/article/details/78510531

[15]. https://prateekvjoshi.com/2016/04/12/understanding-locally-connected-layers-in-convolutional-neural-networks/

[16]. https://github.com/pytorch/pytorch/pull/1583

[17]. https://github.com/pytorch/pytorch/issues/499

[18]. https://discuss.pytorch.org/t/locally-connected-layers/26979

[19]. https://towardsdatascience.com/intuitively-understanding-connectionist-temporal-classification-3797e43a86c

[20]. https://stackoverflow.com/questions/39565451/how-to-avoid-nan-value-in-ctc-training/43380602

[21]. He, T., Zhang, Z., Zhang, H., Zhang, Z., Xie, J., & Li, M. (2019). Bag of tricks for image classification with convolutional neural networks. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 558-567).

[22]. P. Goyal, P. Dollar, R. B. Girshick, P. Noordhuis, ´L. Wesolowski, A. Kyrola, A. Tulloch, Y. Jia, and K. He.Accurate, large minibatch SGD: training imagenet in 1 hour.CoRR, abs/1706.02677, 2017

[23]. https://pytorch.org/docs/stable/notes/randomness.html#reproducibility


  1. Covector, 指的是值范围在 [ 0 , 1 ] [0,1] [0,1]之间的张量,也被称之为dual vector↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FesianXu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值