踩坑记----keras,训练准确率远高于验证准确率,keras底层代码解剖

前几天,帮朋友处理一个深度学习网络问题,具体场景是这样的,总共有12张,分为3个类别,同时作为训练集跟验证集,训练集跟验证集的预处理一样,使用的模型为ResNet50,最后的激活函数为softmax。使用keras框架,总共10个epoch,每个epoch都是只有1个batch(因为数据集就12张图片,所以一个batch也就12张图片)。

在训练前几个epoch时,训练准确率便达到100%,因为模型较为复杂,而且数据集也有12张,所以很好拟合,但验证准确率只有33%,也即出现训练准确率远高于验证准确率的问题,

在验证了 https://blog.csdn.net/qq_16564093/article/details/103563517 中提到可能出现的情况后,仍然未能解决问题。于是我花了三天时间去寻找答案。

接下来是我的问题解决思路:

(1).模型是否出现过拟合现象

猜测,模型可能过于复杂,出现过拟合现象,导致模型过度拟合训练数据,而不适用于验证数据集。

于是,为了验证这个猜想,我将训练数据集与验证数据集统一使用同一份,即数据迭代器都是同一份数据,但结果,训练准确率依然远高于验证准确率,可推测,并非模型过度拟合训练数据集。于是,我用同一份数据,对模型进行测试,得出结果是,准确率只有33%,即可以说明一个问题,模型是完全未拟合,那么,问题,为什么模型的训练准确率能达到100%呢?即使出现过拟合现象,训练集跟验证集一样的情况下,模型如若对训练集过度拟合,那么模型应该也对验证集过度拟合,验证准确率应该也达到100%才对。

 

(2).训练集与验证集是否存在随机处理。

猜测,训练集与验证集是否存在一些随机的处理方式,比如随机旋转一定角度,亮度,色度的一定范围内的变化,那么在每次数据集迭代器进行迭代完一轮后,数据都做了一些处理,而当模型出现过拟合时,对训练集进行过度拟合后,数据集迭代器对数据进行数据处理,那么模型就没办法适用于做了数据处理后产生新的数据集。但我将所有数据处理的手段都设置Flase,也即保证数据一直不做改变.的情况下,验证准确率依然只有33%,而训练准确率达到100%。

 

(3).设置计算训练准确率跟验证准确率的计算方式不同。

因为使用的keras,那么我便猜想,是否在设置计算训练准确率跟验证准确率的方式存在差异,是否可以额外设置计算训练准确率的方式,但我查了keras官方文档,并未找到额外的设置方式,也即表示,计算训练准确率跟验证准确率的方式标准是一样的。

 

(4).keras底层是否对训练集跟验证集的准确率,损失的计算方式不同。

于是,排除了种种外部因素,我开始对keras底层进行探索,猜想,除了keras底层对训练集跟验证集的准确率计算方式不同,虽然计算标准都是准确率。还有一种可能,那便是,keras底层在训练的模式下,对一个batch的数据进行了分割处理。所以接下来便是我对底层代码的探究过程,也是谜底的慢慢揭开。

 

keras底层代码解剖

###模型的生成代码是:
model = keras.models.Model(inputs = base_model.input, outputs = x)
###模型的训练代码是:
model.fit_generator(train_data_generator,
                    validation_data = train_data_generator,
                    epochs = 10)

 

(1).首先model.fit_generator函数进行解剖。

通过一层一层的解剖,发现,模型对训练集跟验证集的准确率计算方式是一样,但模型对训练集跟验证集的前向传递,推断出来的输出结果却不一致,也即,在训练的模式下跟验证的模型下,模型输入相同的图片,输出的结果是不一致的。

 

(2).对训练模型下跟验证模式下,模型的输出结果出现差异进行解剖。

于是,我继续追踪模型的输出,一一比对训练时,模型调用的推断函数,跟验证时,模型调用的推断函数的差异,最终,在这个脚本里面发现了差异:

###site-packages\tensorflow_core\python\keras\engine\training_eager.py
def _model_loss(model,inputs,targets,output_loss_metrics=None,sample_weights=None,training=False)
###在这个函数里面,有这么一段代码,就是在这里输出结果产生了差异。
  outs = model(inputs, **kwargs)

(3).对model(inputs, **kwargs)函数进行深入解剖。

我们在keras构建完model后,一般都是使用fit函数进行拟合,使用mode,predict进行预估,很少使用model()这种方式进行调用。比如:

###通常情况下
model = keras.models.Model(inputs = base_model.input, outputs = x)
model.fit_generator(train_data_generator,
                    validation_data = train_data_generator,
                    epochs = 10)
res = model.predict(input)
###但其实可以直接这样调用
res = model(pic,training=True)
res = model(pic,training=False)
###这两种区别在于模型是否处于训练模式下。其实它调用的是class的__call__函数。

(4).对model的__call__函数进行解剖。

对函数进行一层一层的解剖,并一一比对训练模式跟非训练模式的区别,最终,终于在批归归一化层的处理函数发现差异,也是在数据经过批归一化后,数据产生了差异。

###site-packages\tensorflow_core\python\keras\layers\normalization.py
def _fused_batch_norm(self, inputs, training):
###在上面内部函数中,有这个段代码
    def _fused_batch_norm_training():
      return nn.fused_batch_norm(
          inputs,
          gamma,
          beta,
          epsilon=self.epsilon,
          data_format=self._data_format)

    def _fused_batch_norm_inference():
      return nn.fused_batch_norm(
          inputs,
          gamma,
          beta,
          mean=self.moving_mean,
          variance=self.moving_variance,
          epsilon=self.epsilon,
          is_training=False,
          data_format=self._data_format)
    output, mean, variance = tf_utils.smart_cond(
        training, _fused_batch_norm_training, _fused_batch_norm_inference)
###从上面代码可知
###在训练模式下,模型调用的批归一化函数为_fused_batch_norm_training
###在非训练模式下,模型调用的批归一化函数为_fused_batch_norm_inference
###两者虽然调用的都是nn.fused_batch_norm函数
###但训练模式下,mean跟variance都是在传递过程中临时计算出来的。
###而非训练模式下,mean跟variance都是自己传递进去的。

那么,我发现,在训练模式下,返回的variance数值都在(-1,1)之间,而在非训练模式下,传进批归一化函数的variance数据却达到上千,那么这个就是问题所在,在训练模式下,因为mean跟variance都在合理的数值下,那么批归一化后数据属于正常分布,但当非训练模式下,variance过大,造成数据输出已经出现偏差。

但这就又有一个疑问,因为在训练模式下,批归一化后返回的mean跟variance都会重新保存下来,赋值给self.moving_mean跟self.moving_variance,那么在非训练模式下,应该会使用到正确的mean跟variance才对,于是我继续追踪self.moving_mean跟self.moving_variance的update过程。

然后,我就追踪到这么一段代码:

decay = ops.convert_to_tensor(1.0 - momentum, name='decay')
if decay.dtype != variable.dtype.base_dtype:
    decay = math_ops.cast(decay, variable.dtype.base_dtype)
update_delta = (variable - math_ops.cast(value, variable.dtype)) * decay

这也就是表示,mean跟variance的update存在一个动量,而非直接赋值。所以要等mean跟variance达到正常范围,需要较多的epoch去迭代更新。

 

所以,结果是模型并非过拟合,而是还未拟合,因为批归一化中的mean跟variance仍需要迭代很多次,才能达到合理值,而在前几个epoch中,训练模型下,mean跟variance都是通过数据集进行计算的,所以模型的输出是合理的,训练准确率也较高,但在验证集上,模型处于非训练模式,mean跟variance都是使用保存下来的,都仍然偏大,还未迭代到合理数值,所以模型的输出都异常,验证准确率也偏低。

 

    原文作者:程序猿也可以很哲学
    原文地址: https://blog.csdn.net/qq_16564093/article/details/104070119
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞