集智专栏
资源加载中,请稍后...
集智专栏

如何用Dropout降低过拟合风险

Kaiser

如果要给本文配一个BGM,我希望是杜德伟的《脱掉》。

过拟合/欠拟合

之前介绍了卷积提取边缘特征的基本原理,但这还远远不足以从图片中识别出奶罩,因为并不是所有奶罩都像柏木由纪的那么简单朴实,而是存在很多种不同的款式,这不是单从边缘就能识别出来的。

要提取如此复杂的特征,需要很多不同的卷积核。巧妇难为无米之炊,没有足量的训练样本,再好的卷积也出不来。训练样本当然是越多越好,有道是“熟识奶罩三百款,不会解扣也会穿”。人工神经网络虽然不具备真正的人格,但有一点却是相通的,那就是“贪”。

但不管是训练样本的累积,还是计算能力的上限,资源总是有限的。所以我们始终只能从有限多个样本中,提取一小撮特征,这是一个以有涯求无涯的过程。训练样本不足就会产生一个问题,叫作“少见多怪”。比如我们看上面几张图里的奶罩,大多是有肩带的,那么自然地,神经网络会把“肩带”当作一个特征。如果一个物体没有类似肩带的结构,被认作奶罩的概率便会下降。

引入过多不必要的特征,导致模型泛化能力(Generalization)下降,这一问题叫作“过拟合”(Overfitting)。比如下图,坐标上有10个点,蓝色拟合曲线逐一穿过,看起来像一个完美的拟合,似乎我们已经看透了这一分布规律的本质。

但是如果有第11个点,它真的会继续落在蓝线上吗?而且一旦没有,那么拟合曲线就面临严重的威胁,需要很大的调整才能满足“穿过所有点”。如果有第12个点呢?这种过拟合的预测对于新数据的作用有限,实际上一个比较稳妥的拟合是线性关系,我们有很大把握预测,第11个乃至以后的数据,都会落在蓝色直线附近。

过拟合就好比背过了历年的考试题(包括所有数字),但是一见到新题目(数字变了)就不会做了。以前有个傻子卖咸鸭蛋的小品,里面傻子说“五毛钱俩,一块钱不卖”,这就是钻进了“五毛钱俩”的牛角尖而产生的笑话。过拟合的神经网络,就像一个认死理的人,实则是愚痴的。所幸,人工智能还没有发展出情态,否则恐怕就“贪、嗔、痴”三毒俱全了。我想,如果真的有那一天,机器人也会发展出宗教吧(遁入智瞳)。

反过来的情况叫“欠拟合”(Underfitting),就是没有学习到真正的规律,听风就是雨。比如下图这个把眼罩当成奶罩的惨案,买家确实没有把“肩带”当作特征,“形状”也确实有点类似,但是她把最基本的“大小”因素忽略了。

在实际应用当中,“过拟合”是更加常见的问题。


脱掉

如何遏制过拟合?方法有很多,比如L1、L2正则化(Regularization)是在代价函数(Cost Function)上动手脚。本文要介绍的方法,叫作Dropout。其原意是“辍学”,中文资料里基本也都保留了原词,“脱掉”是我自己想的翻译。

Dropout不修改代价函数,而是直接调整神经网络的结构,是一种更加彻底的革命。人工神经元都是与特征相关的,而有的特征(比如肩带)会造成过拟合。那么只要脱掉肩带所对应的的神经元,不就可以避免了吗?

理是这么个理,但是我们并不知道,具体哪些神经元对应哪些特征。如果我们都知道的话,也就不用训练了,直接求解就行了。

考虑下面这个简单的神经网络,其隐藏层(中间那层)有6个神经元。神经元与特征并非一一对应的,可能3个神经元共同表征1个特征,比如从上数第2、3、5个。假设我们抛掉了这三个神经元,用其余的网络进行计算,就可以在一定程度上降低过拟合。实际上这里的[2, 3, 5]只是猜测,我们要做的是,把所有能脱掉的3神经元组合,全都脱一遍,对剩余部分分别进行计算,最后再求平均。诚然,每一个经过Dropout的不完整神经网络,还是有可能过拟合,但是各自的过拟合情况是不同的,通过求平均就可以抵消。

本质上,Dropout消解了神经网络对某一部分神经元的依赖性。


实践

实践是检验罩杯的唯一标准,但是对于广大新手而言,直接处理彩色图片的难度还是偏高,而且本站的计算能力暂时还很有限。所以这里改用手写数字识别项目作为案例,搭建简单的卷积神经网络,并且对比Dropout的效果。

该项目是Kaggle的入门级比赛,且用到了著名的MNIST手写数字数据集。MNIST可谓是深度学习界的Hello World

首先导入依赖库和数据集,并进行预处理,以便神经网络模型使用。

程序说明
数据预处理
预处理代码
import os os.system("ln -s /mnt/vol0/Kaggle/mnist/train.csv") os.system("ln -s /mnt/vol0/Kaggle/mnist/test.csv") %matplotlib inline
示例代码
import pandas as pd import numpy as np from keras.utils.np_utils import to_categorical import matplotlib.pyplot as plt df_train = pd.read_csv("train.csv", nrows=5000) df_test = pd.read_csv("test.csv", nrows=5000) X_train = df_train.drop(['label'], axis=1).values.astype('float32') y_train = df_train['label'].values X_test = df_test.values.astype('float32') # 由向量重构图片 img_w, img_h = 28, 28 # 获取图片张数 n_train = X_train.shape[0] n_test = X_test.shape[0] # 数据集归一化 X_train = X_train.reshape(n_train,img_w,img_h,1) X_test = X_test.reshape(n_test,img_w,img_h,1) X_train = X_train / 255.0 X_test = X_test / 255.0 # 标签编码 y_train = to_categorical(y_train) # 显示第233张手写数字的灰度图像 imgplot = plt.imshow(X_train[233,:,:,0],cmap='gray')
程序验证过程
True

请点击运行按钮执行数据预处理。您可以修改最后一行代码里的233查看其它数字的灰度图像。


然后构建、训练模型。这里采用一个最简单的卷积神经网络结构:

  • 卷积层(8个5x5卷积核,ReLU激活函数)
  • 池化层(2x2最大池化)
  • 全连接层(16个神经元,ReLU激活函数)
  • 输出层(10个神经元,SoftMax激活函数)

为节省计算开销和减少等待时间,训练的迭代次数已设为1,所以得到的各项结果参数当然偏低,这并非所用的方法有问题。

程序说明
Keras搭建卷积神经网络
示例代码
# 导入依赖库 import numpy from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten, Activation from keras.layers.convolutional import Convolution2D, MaxPooling2D # 构建基准网络 def baseline_model(): # 创建模型 model = Sequential() model.add(Convolution2D(8, 5, 5, input_shape=(28, 28, 1), activation='relu')) # 卷积层 + ReLU model.add(MaxPooling2D(pool_size=(2, 2))) # 最大池化 model.add(Flatten()) model.add(Dense(16, activation='relu')) # 全连接层 + ReLU model.add(Dense(10, activation='softmax')) # 输出层 + SoftMax # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) return model # 设置随机数发生器种子 seed = 557373801 # 集智·数据科学QQ群 numpy.random.seed(seed) model = baseline_model() # 拟合模型 model.fit(X_train, y_train, validation_split=0.2, nb_epoch=1, batch_size=128, verbose=2)
程序验证过程
True

请点击运行按钮搭建卷积神经网络并进行训练和验证。可以试着调整代码中的参数,观察对训练-验证结果的影响。在默认参数情况下,该模型的验证精度为:0.6090


Dropout

作为对比,我们搭建另外一个架构类似、但包含Dropout层的神经网络。Dropout的位置是最大池化层之后、全连接层之前

程序说明
添加Dropout层
示例代码
def dropout_model(): model = Sequential() # 补完神经网络构建代码 # 在池化层之后添加Dropout层,Dropout率为0.25 # 补完代码 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) return model # 保持相同的随机数发生器种子 seed = 557373801 numpy.random.seed(seed) model = dropout_model() model.fit(X_train, y_train, validation_split=0.2, nb_epoch=1, batch_size=128, verbose=2)
正确答案
model.add(Convolution2D(8, 5, 5, input_shape=(28, 28, 1), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(16, activation='relu')) model.add(Dense(10, activation='softmax'))
程序验证过程
def dropout_check(): dropout_add = False dropout_set = False if model.get_config()[2]['class_name'] == "Dropout": dropout_add = True if model.get_config()[2]['config']['p'] == 0.25: dropout_set = True return dropout_add & dropout_set dropout_check()
提示信息
网络架构与上一代码框的baseline_model()基本相同,只需在对应位置添加Dropout()。

请点击运行按钮带有Dropout的卷积神经网络,并进行训练和验证。

如果正确地添加了Dropout,则得到的验证精度(val_acc)应为0.7390。而其他的指数,如训练精度(acc)却大致相等。可见,Dropout的作用是消解过拟合,提高模型泛化能力。

您也许喜欢这些文章

集智专栏

两分钟论文解析184:DeepMind自主学得音视频概念

发表至业界新闻
来自YouTube的《Two Minute PAPERS》系列,该系列视频在极短的时间浓缩介绍新近科研论文的成果,本篇(第184期)讲的是DeepMind的最新成果,用无监督的方式让算法自主学会了匹配声音和画面中的乐器。
集智专栏

一小时建立终身受害的AI创作系统

发表至趣味项目
使用TensorFlow框架,搭建一个简单的LSTM(长短记忆神经元)示例。LSTM是目前自然语言处理领域的核心算法,因为它可以很好地处理序列数据中的前后顺序依赖问题。文末提供了在线开发环境,读者可以自由地尝试修改参数。
集智专栏

[2018.03.13直播] Python之字符串+元组

发表至系列教程
2018年3月13日直播配套专栏,继续讲解Python的数据类型——字符串,并通过两个趣味项目来巩固对字符串的掌握。另外介绍了Python的另一种数据结构——元组,其特殊的性质使之放在列表和字符串之后再介绍,更加容易理解。

文章评论(7)

新用户770 发表于 1年前回复
您好,这个Dropout的位置可能有争议,我看过很多案例,dropout的位置都是放在了第一个全连接层和第二个全连接层之间的,不知道你是否有更好的解释??
Kaiser集智 站长 发表于 1年前回复
回复老哥:有其他在线可以运行的环境吗
目前支持Python2, Python3, R, JavaScript, Julia
老哥 发表于 1年前回复
回复Kaiser:咱俩说的应该是一个小品,大概就那意思
有其他在线可以运行的环境吗
Kaiser集智 站长 发表于 1年前回复
回复幽灵猫:我怎么觉得是“一块钱俩,五毛钱不卖”
咱俩说的应该是一个小品,大概就那意思
幽灵猫 发表于 1年前回复
我怎么觉得是“一块钱俩,五毛钱不卖”
Kaiser集智 站长 发表于 1年前回复
回复bamboo:( ̄ε(# ̄) Σ哎呀一直喜欢对着网页代码敲到本地一句句看,但额滴Py34本地导入keras提示缺少tensorflow,tensorflow又只支持py35...额滴一页多的py34库啊
我们正在尝试通过后台提供GPU计算资源,以后就不用在本地调试运行了,都在网页里完成。
bamboo 发表于 1年前回复
( ̄ε(# ̄) Σ哎呀一直喜欢对着网页代码敲到本地一句句看,但额滴Py34本地导入keras提示缺少tensorflow,tensorflow又只支持py35...额滴一页多的py34库啊