《从锅炉工到AI专家(6)》一文中,我们把神经网络模型降维,简单的在二维空间中介绍了过拟合和欠拟合的现象和解决方法。但是因为条件所限,在该文中我们只介绍了理论,并没有实际观察现象和应对。

现在有了TensorFLow 2.0 / Keras的支持,可以非常容易的构建模型。我们可以方便的人工模拟过拟合的情形,实际来操作监控、调整模型,从而显著改善模型指标。

从图中识别过拟合和欠拟合

先借用上一篇的两组图:





先看上边的一组图,随着训练迭代次数的增加,预测的错误率迅速下降。

我们上一篇中讲,达到一定迭代次数之后,验证的错误率就稳定不变了。实际上你仔细观察,训练集的错误率在稳定下降,但验证集的错误率还会略有上升。两者之间的差异越来越大,图中的两条曲线,显著分离了,并且分离的趋势还在增加。这就是过拟合的典型特征。

这表示,模型过分适应了当前的训练集数据,对于训练集数据有了较好表现。对于之外的数据,反而不适应,从而效果很差。

这通常都是由于较小的数据样本造成的。如果数据集足够大,较多的训练通常都能让模型表现的更好。过拟合对于生产环境伤害是比较大的,因为生产中大多接收到的都是新数据,而过拟合无法对这些新数据达成较好表现。

所以如果数据集不够的情况下,采用适当的迭代次数可能是更好的选择。这也是上一节我们采用EarlyStopping机制的原因之一。最终的表现是上边下面一组图的样子。

欠拟合与此相反,表示模型还有较大改善空间。上面两组图中,左侧下降沿的曲线都可以认为是欠拟合。表现特征是无论测试集还是验证集,都没有足够的正确率。当然也因此,测试集和验证集表现类似,拟合非常紧密。

欠拟合的情况,除了训练不足之外,模型不够强大或者或者模型不适合业务情况都是可能的原因。

实验模拟过拟合

我们使用IMDB影评样本库来做这个实验。实验程序主要部分来自于本系列第五篇中第二个例子,当然有较大的修改。

程序主要分为几个部分:

  • 下载IMDB影评库(仅第一次),载入内存,并做单词向量化。
  • 单词向量化编码使用了multi-hot-sequences,这种编码跟one-hot类似,但一句话中有多个单词,因此会有多个'1'。一个影评就是一个0、1序列。这种编码模型非常有用,但在本例中,数据歧义会更多,更容易出现过拟合。
  • 定义baseline/small/big三个不同规模的神经网络模型,并分别编译训练,训练时保存过程数据。
  • 使用三组过程数据绘制曲线图,指标是binary_crossentropy,这是我们经常当做损失函数使用的指征,这个值在正常训练的时候收敛到越小越好。

程序中,文本的编码方式、模型都并不是很合理,因为我们不是想得到一个最优的模型,而是想演示过拟合的场景。

#!/usr/bin/env python3

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
from tensorflow import keras import numpy as np
import matplotlib.pyplot as plt NUM_WORDS = 10000
# 载入IMDB样本数据
(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS) # 将单词数字化,转化为multi-hot序列编码方式
def multi_hot_sequences(sequences, dimension):
# 建立一个空矩阵保存结果
results = np.zeros((len(sequences), dimension))
for i, word_indices in enumerate(sequences):
results[i, word_indices] = 1.0 # 出现过的词设置为1.0
return results train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS) # 建立baseline模型,并编译训练
baseline_model = keras.Sequential([
# 指定`input_shape`以保证下面的.summary()可以执行,
# 否则在模型结构无法确定
keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)),
keras.layers.Dense(16, activation='relu'),
keras.layers.Dense(1, activation='sigmoid')
])
baseline_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy'])
baseline_model.summary()
baseline_history = baseline_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
# 小模型定义、编译、训练
smaller_model = keras.Sequential([
keras.layers.Dense(4, activation='relu', input_shape=(NUM_WORDS,)),
keras.layers.Dense(4, activation='relu'),
keras.layers.Dense(1, activation='sigmoid')
])
smaller_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy'])
smaller_model.summary()
smaller_history = smaller_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
# 大模型定义、编译、训练
bigger_model = keras.models.Sequential([
keras.layers.Dense(512, activation='relu', input_shape=(NUM_WORDS,)),
keras.layers.Dense(512, activation='relu'),
keras.layers.Dense(1, activation='sigmoid')
]) bigger_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy']) bigger_model.summary()
bigger_history = bigger_model.fit(train_data, train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2) # 绘图函数
def plot_history(histories, key='binary_crossentropy'):
plt.figure(figsize=(16,10)) for name, history in histories:
val = plt.plot(
history.epoch, history.history['val_'+key],
'--', label=name.title()+' Val')
plt.plot(
history.epoch, history.history[key], color=val[0].get_color(),
label=name.title()+' Train') plt.xlabel('Epochs')
plt.ylabel(key.replace('_',' ').title())
plt.legend() plt.xlim([0,max(history.epoch)])
plt.show() # 绘制三个模型的三组曲线
plot_history([('baseline', baseline_history),
('smaller', smaller_history),
('bigger', bigger_history)])

程序在命令行的输出就不贴出来了,除了输出的训练迭代过程,在之前还输出了每个模型的summary()。这里主要看最后的binary_crossentropy曲线图。



图中的虚线都是验证集数据的表现,实线是训练集数据的表现。三个模型的训练数据和测试数据交叉熵曲线都出现了较大的分离,代表出现了过拟合。尤其是bigger模型的两条绿线,几乎是一开始就出现了较大的背离。

优化过拟合

优化过拟合首先要知道过拟合产生的原因,我们借用一张前一系列讲解过拟合时候用过的图,是吴恩达老师课程的笔记:



如果一个模型产生过拟合,那这个模型的总体效果就可能是一个非常复杂的非线性方程。方程非常努力的学习所有“可见”数据,导致了复杂的权重值,使得曲线弯来弯去,变得极为复杂。多层网络更加剧了这种复杂度,最终的复杂曲线绕开了可行的区域,只对局部的可见数据有效,对于实际数据命中率低。所以从我们程序跑的结果图来看,也是越复杂的网络模型,过拟合现象反而越严重。

这么说简单的模型就好喽?并非如此,太简单的模型往往无法表达复杂的逻辑,从而产生欠拟合。其实看看成熟的那些模型比如ResNet50,都是非常复杂的结构。

过拟合既然产生的主要原因是在权重值上,我们在这方面做工作即可。

增加权重的规范化

通常有两种方法,称为L1规范化和L2规范化。前者为代价值增加一定比例的权重值的绝对值。后者增加一定比例权重值的平方值。具体的实现来源于公式,有兴趣的可以参考一下这篇文章《L1 and L2 Regularization》

我们删除掉上面源码中的bigger模型和small模型的部分,包括模型的构建、编译和训练,添加下面的代码:

# 构建一个L2规范化的模型
l2_model = keras.models.Sequential([
keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
activation='relu', input_shape=(NUM_WORDS,)),
keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
activation='relu'),
keras.layers.Dense(1, activation='sigmoid')
]) l2_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy']) l2_model_history = l2_model.fit(train_data, train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)

这个模型的逻辑结构同baseline的模型完全一致,只是在前两层中增加了L2规范化的设置参数。

先不着急运行,我们继续另外一种方法。

添加DropOut

DropOut是我们在上个系列中已经讲过的方法,应用的很广泛也非常有效。

其机理非常简单,就是在一层网络中,“丢弃”一定比例的输出(设置为数值0)给下一层。丢弃的比例通常设置为0.2至0.5。这个过程只在训练过程中有效,一般会在预测过程中关闭这个机制。

我们继续在上面代码中,添加一组采用DropOut机制的模型,模型的基本结构依然同baseline相同:


dpt_model = keras.models.Sequential([
keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)),
keras.layers.Dropout(0.5),
keras.layers.Dense(16, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(1, activation='sigmoid')
]) dpt_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy']) dpt_model_history = dpt_model.fit(train_data, train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
....
# 最后的绘图函数不变,绘图语句修改如下:
plot_history([
('baseline', baseline_history),
('l2', l2_model_history),
('dropout', dpt_model_history)])

现在可以执行程序了。

程序获得的曲线图如下,图中可见,我们在不降低模型的复杂度的情况下,L2规范化(黄色曲线)和DropOut(绿色曲线)都有效的改善了模型的过拟合问题。

(待续...)

TensorFlow从1到2(八)过拟合和欠拟合的优化的更多相关文章

  1. AI - TensorFlow - 示例04:过拟合与欠拟合

    过拟合与欠拟合(Overfitting and underfitting) 官网示例:https://www.tensorflow.org/tutorials/keras/overfit_and_un ...

  2. 斯坦福大学公开课机器学习: advice for applying machine learning - evaluatin a phpothesis(怎么评估学习算法得到的假设以及如何防止过拟合或欠拟合)

    怎样评价我们的学习算法得到的假设以及如何防止过拟合和欠拟合的问题. 当我们确定学习算法的参数时,我们考虑的是选择参数来使训练误差最小化.有人认为,得到一个很小的训练误差一定是一件好事.但其实,仅仅是因 ...

  3. 过拟合VS欠拟合、偏差VS方差

    1. 过拟合 欠拟合 过拟合:在训练集(training set)上表现好,但是在测试集上效果差,也就是说在已知的数据集合中非常好,但是在添加一些新的数据进来训练效果就会差很多,造成这样的原因是考虑影 ...

  4. ML(附录3)——过拟合与欠拟合

    过拟合与欠拟合 我们希望机器学习得到好的模型,该模型能够从训练样本中找到一个能够适应潜在样本的普遍规律.然而,如果机器学习学的“太好”了,以至把样本的自身特点当作潜在样本的一般特性,这就使得模型的泛化 ...

  5. 评价指标的局限性、ROC曲线、余弦距离、A/B测试、模型评估的方法、超参数调优、过拟合与欠拟合

    1.评价指标的局限性 问题1 准确性的局限性 准确率是分类问题中最简单也是最直观的评价指标,但存在明显的缺陷.比如,当负样本占99%时,分类器把所有样本都预测为负样本也可以获得99%的准确率.所以,当 ...

  6. [一起面试AI]NO.5过拟合、欠拟合与正则化是什么?

    Q1 过拟合与欠拟合的区别是什么,什么是正则化 欠拟合指的是模型不能够再训练集上获得足够低的「训练误差」,往往由于特征维度过少,导致拟合的函数无法满足训练集,导致误差较大. 过拟合指的是模型训练误差与 ...

  7. 过拟合和欠拟合(Over fitting & Under fitting)

    欠拟合(Under Fitting) 欠拟合指的是模型没有很好地学习到训练集上的规律. 欠拟合的表现形式: 当模型处于欠拟合状态时,其在训练集和验证集上的误差都很大: 当模型处于欠拟合状态时,根本的办 ...

  8. 过拟合/欠拟合&logistic回归等总结(Ng第二课)

    昨天学习完了Ng的第二课,总结如下: 过拟合:欠拟合: 参数学习算法:非参数学习算法 局部加权回归 KD tree 最小二乘 中心极限定律 感知器算法 sigmod函数 梯度下降/梯度上升 二元分类 ...

  9. [DeeplearningAI笔记]改善深层神经网络1.1_1.3深度学习使用层面_偏差/方差/欠拟合/过拟合/训练集/验证集/测试集

    觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.1 训练/开发/测试集 对于一个数据集而言,可以将一个数据集分为三个部分,一部分作为训练集,一部分作为简单交叉验证集(dev)有时候也成为验 ...

随机推荐

  1. Django时区设置的郁闷

    第一次在windows下看到这个设置的时候,就设置成Ubuntu上时区设置的字符串“Asia/Shanghai”,结果报错通不过,最后记不清楚从哪儿查的,改成GMT+8. 最近把应用放到Linux上做 ...

  2. 浅析fork()和底层实现

    记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎.不过,“黄天不负留心人".哈~ 终于在学习进程和进程创建fork相关知识后,总算是大致摸清了其中的 ...

  3. Scala编程入门---Map与Tuple

    创建Map //创建一个不可变的Map val ages = Map("Leo" -> 30,"Jen" ->25,"Jack" ...

  4. 如何利用Python网络爬虫抓取微信朋友圈的动态(上)

    今天小编给大家分享一下如何利用Python网络爬虫抓取微信朋友圈的动态信息,实际上如果单独的去爬取朋友圈的话,难度会非常大,因为微信没有提供向网易云音乐这样的API接口,所以很容易找不到门.不过不要慌 ...

  5. lambda函数常见用法

    # lambda 参数:返回值/表达式 # print((lambda :100)()) # f = lambda a,b : a + b # print(f(10, 20)) # f = lambd ...

  6. Python进阶开发之元类编程

    系列文章 √第一章 元类编程,已完成 ; 本文目录 类是如何产生的如何使用type创建类理解什么是元类使用元类的意义元类实战:ORM . 类是如何产生的 类是如何产生?这个问题肯定很傻.实则不然,很多 ...

  7. js流程语句

    一.跳转语句1.break; 终止整个循环,不再进行判断2.continue; 终止本次循环,接着去判断是否执行下次循环 二.选择(判断)结构1.if 如果               if(条件1) ...

  8. css绝对底部的实现方法

    最近发现公司做的好多管理系统也存在这样的问题,当页面不够长的时候,页尾也跟着跑到了页面中部,这样确实感觉视觉体验不太好,没有研究之前还真不知道还能用css实现,主要利用min-height;paddi ...

  9. mondrian 4.7 源码部署

    mondrian是一个开源的数据分析工程, 网上有关mondrian3.X的源码部署比较多, 有关4.X的部署较少. 目前官方推荐使用的时mondrian3.7的修订版, 可以再github上下载到最 ...

  10. RedHat Linux下iptables防火墙设置

    一般情况下iptables已经包含在Linux发行版中.运行 # iptables --version 来查看系统是否安装iptables 启动iptables:# service iptables ...