踩坑记----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都是使用保存下来的,都仍然偏大,还未迭代到合理数值,所以模型的输出都异常,验证准确率也偏低。

 

  • 13
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
在使用nsl-kdd数据集进行训练和测试时,我们可以使用交叉熵损失函数和准确率作为评估指标。以下是一个简单的代码示例: ```python import tensorflow as tf import numpy as np import pandas as pd from sklearn.preprocessing import LabelEncoder, OneHotEncoder, StandardScaler from sklearn.compose import ColumnTransformer import nsl_kdd # 加载数据集 (train_x, train_y), (test_x, test_y), (_, _) = nsl_kdd.load_data() # 对类别型特征进行编码 categorical_features = ['protocol_type', 'service', 'flag'] ct = ColumnTransformer([('encoder', OneHotEncoder(), categorical_features)], remainder='passthrough') train_x = ct.fit_transform(train_x).toarray() test_x = ct.transform(test_x).toarray() # 对标签进行编码 le = LabelEncoder() train_y = le.fit_transform(train_y) test_y = le.transform(test_y) # 特征缩放 sc = StandardScaler() train_x = sc.fit_transform(train_x) test_x = sc.transform(test_x) # 定义模型 model = tf.keras.Sequential([ tf.keras.layers.Dense(64, input_dim=train_x.shape[1], activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(32, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax') ]) # 编译模型 model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 训练模型 model.fit(train_x, train_y, epochs=10, batch_size=128, validation_data=(test_x, test_y)) # 评估模型 loss, acc = model.evaluate(test_x, test_y) print(f'Test loss: {loss:.3f}') print(f'Test accuracy: {acc:.3f}') ``` 在上述代码中,我们首先加载nsl-kdd数据集,并对类别型特征进行编码和标签进行编码。接下来,我们对特征进行缩放,并使用Sequential模型定义一个简单的神经网络。我们使用`compile`方法指定损失函数为交叉熵损失函数,并使用准确率作为评估指标。最后,我们使用`fit`方法训练模型,并使用`evaluate`方法评估模型的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿也可以很哲学

让我尝下打赏的味道吧

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

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

打赏作者

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

抵扣说明:

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

余额充值