深度学习中的正则化技术(附Python代码)

作者:SHUBHAM JAIN

翻译:和中华

校对:丁楠雅

本文约3500字,建议阅读20分钟。

本文简单介绍了什么是正则化以及在深度学习任务中可以采用哪些正则化技术,并以keras代码具体讲解了一个案例。

简介

数据科学家面临的常见问题之一是如何避免过拟合。你是否碰到过这样一种情况:你的模型在训练集上表现异常好,却无法预测测试数据。或者在一个竞赛中你排在public leaderboard的顶端,但是在最终排名中却落后了几百名?那么这篇文章就是为你而准备的!

(译者注: 在kaggle这样的数据竞赛中, public leaderboard排名是根据一部分测试集来计算的,用于给选手提供及时的反馈和动态展示比赛的进行情况;而private leaderboard是根据测试集的剩余部分计算而来,用于计算选手的最终得分和排名。 通常我们可以把public LB理解为在验证集上的得分,private LB为真正未知数据集上的得分,这样做的目的是提醒参赛者,我们建模的目标是获取一个泛化能力好的模型)

避免过拟合可以提高我们模型的性能。

目录

1. 什么是正则化?

2. 正则化如何减少过拟合?

3. 深度学习中的各种正则化技术:

  • L2和L1正则化
  • Dropout
  • 数据增强(Data augmentation)
  • 提前停止(Early stopping)

4. 案例:在MNIST数据集上使用Keras的案例研究

1. 什么是正则化?

在深入该主题之前,先来看看这几幅图:

之前见过这幅图吗?从左到右看,我们的模型从训练集的噪音数据中学习了过多的细节,最终导致模型在未知数据上的性能不好。

换句话说,从左向右,模型的复杂度在增加以至于训练误差减少,然而测试误差未必减少。如下图所示:

Source: Slideplayer

如果你曾经构建过神经网络,你就知道它们有多复杂。这也使得它们更容易过拟合。

正则化技术是对学习算法做轻微的修改使得它泛化能力更强。这反过来就改善了模型在未知数据上的性能。

2. 正则化如何减少过拟合?

我们来看一个在训练数据上过拟合的神经网络,如下图所示:

如果你曾经学习过机器学习中的正则化,你会有一个概念,即正则化惩罚了系数。在深度学习中,它实际上惩罚了节点的权重矩阵。

假设我们的正则化系数很高,以至于某些权重矩阵近乎于0:

这会得到一个简单的线性网络,而且在训练数据集上轻微的欠拟合。

如此大的正则化系数并不是那么有用。我们需要对其进行优化从而得到一个拟合良好的模型,正如下图所示:

3. 深度学习中的各种正则化技术

我们已经理解了正则化如何帮助减少过拟合,现在我们将学习一些把正则化用于深度学习的技术。

  • L1和L2正则化

L1和L2是最常见的正则化类型。它们通过增加一个被称为正则项的额外项来更新成本函数:

Cost function = Loss (say, binary cross entropy) + Regularization term

由于增加了这个正则项,权重矩阵的值减小了,因为这里假定了具有较小权重矩阵的神经网络会导致更简单的模型。因此,它也会在相当程度上减少过拟合。

然而,该正则项在L1和L2中是不同的。

L2中,我们有:

这里,lambda是正则参数。它是一个超参数用来优化得到更好的结果。L2正则化也叫权重衰减(weight decay),因为它强制权重朝着0衰减(但不会为0)

在L1中,我们有:

这里,我们惩罚了权重的绝对值。不像L2, 这里的权重是有可能衰减到0的。因此,当我们想压缩模型的时候, L1非常有用,否则会偏向使用L2.

在Keras中,我们可以使用regularizers直接在任意层上应用正则化。

下面是一段样例代码,把L2正则化用于一层之上:

from keras import regularizers
model.add(Dense(64, input_dim=64,
                kernel_regularizer=regularizers.l2(0.01)

注意:这里的值0.01是正则化参数的值,即lambda, 它需要被进一步优化。可以使用grid-search的方法来优化它。

同样的,我们也可以采用L1正则化。后文中的案例研究会看到更多细节。

  • Dropout

这是一种非常有趣的正则化技术。它的效果非常好因此在深度学习领域中最常被使用。

为了理解dropout,假设我们的神经网络结构如下所示:

dropout做什么呢?每次迭代,随机选择一些节点,将它们连同相应的输入和输出一起删掉,如下图:

所以,每一轮迭代都有不同的节点集合,这也导致了不同的输出。它也可以被认为是一种机器学习中的集成技术(ensemble technique)。

集成模型(ensemble models)通常比单一模型表现更好,因为捕获了更多的随机性。同样的,比起正常的神经网络模型,dropout也表现的更好。

选择丢弃多少节点的概率是dropout函数的超参数。如上图所示,dropout可以被用在隐藏层以及输入层。

由于这些原因,当我们有较大的神经网络时,为了引入更多的随机性,通常会优先使用dropout。

在Keras中,可以使用Keras core layer来实现dropout。下面是对应的Python代码:

from keras.layers.core import Dropout
 
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
 Dropout(0.25),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])

正如你看到的,这里定义丢弃概率为0.25。我们可以使用grid search方法来微调它以获得更好的结果。

  • 数据增强(Data augmentation)

减少过拟合最简单的方式其实是增加训练集的大小。在机器学习中,由于人工标注数据成本过高所以很难增加训练集的大小。

但是,考虑一下如果我们处理的是图像。在这种情况下,有一些方法可以增加训练集的大小——旋转、翻转、缩放、移动等等。下图中,对手写数字数据集进行了一些转换:

这种技术叫做数据增强。通常会明显改善模型的准确率。为了提高模型预测能力,这种技术可以被视为一种强制性技巧。

在Keras中,我们使用ImageDataGenerator来执行所有这些转换。它有一大堆参数,你可以用它们来预处理你的训练数据。

下面是示例代码:

from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(horizontal flip=True)
datagen.fit(train)
  • 提前停止(Early stopping)

提前停止是一种交叉验证的策略,即把一部分训练集保留作为验证集。当看到验证集上的性能变差时,就立即停止模型的训练。

在上图中,我们在虚线处停止模型的训练,因为在此处之后模型会开始在训练数据上过拟合。

在Keras中,使用callbacks函数来应用提前停止。下面是代码:

from keras.callbacks import EarlyStopping
 
EarlyStopping(monitor='val_err', patience=5)

这里的monitor是表示需要监视的量,‘val_err’代表验证集错误.

Patience表示在该数量的epochs内没有进一步的性能改进后,就停止训练。为了更好地理解,我们再看看上面的图。在虚线之后,每个epoch都会导致一个更高的验证集错误。因此,在虚线之后的5个epoch(因为我们设置patience等于5),由于没有进一步的改善,模型将停止训练。

注意:可能在5个epoch之后(这是一般情况下为patience设定的值)模型再次开始改进,并且验证集错误也开始减少。因此,在调整这个超参数的时候要格外小心。

  • 在MNIST数据集上使用Keras的案例研究

至此,你应该对我们提到的各种技术有了一个理论上的理解。现在我们把这些知识用在深度学习实际问题上——识别数字。下载完数据集之后,你就可以开始下面的代码。首先,我们导入一些基本的库。

%pylab inline
 
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score
 
from matplotlib import pyplot
 
import tensorflow as tf
import keras
 
# To stop potential randomness
seed = 128
rng = np.random.RandomState(seed)

现在,加载数据。

现在拿一些图片来看看。

 img_name = rng.choice(train.filename)
filepath = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
 
img = imread(filepath, flatten=True)
 
pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()
#storing images in numpy arrays
temp = []
for img_name in train.filename:
 image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
 img = imread(image_path, flatten=True)
 img = img.astype('float32')
 temp.append(img)
 
x_train = np.stack(temp)
 
x_train /= 255.0
x_train = x_train.reshape(-1, 784).astype('float32')
 
y_train = keras.utils.np_utils.to_categorical(train.label.values)

创建一个验证集,以便优化我们的模型以获得更好的分数。我们将采用70:30的训练集、验证集比例。

split_size = int(x_train.shape[0]*0.7)
 
x_train, x_test = x_train[:split_size], x_train[split_size:]
y_train, y_test = y_train[:split_size], y_train[split_size:]

首先,我们先建立一个具有5个隐藏层,每层500个节点的简单神经网络。

# import keras modules
from keras.models import Sequential
from keras.layers import Dense
# define vars
input_num_units = 784
hidden1_num_units = 500
hidden2_num_units = 500
hidden3_num_units = 500
hidden4_num_units = 500
hidden5_num_units = 500
output_num_units = 10
 
epochs = 10
batch_size = 128
 
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
 Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
 Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
 Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
 Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
 
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])

注意,我们只运行10个epoch,快速检查一下模型的性能。

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
 
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))

现在,我们尝试用L2正则化,并检查它是否给出了比简单神经网络更好的性能。

from keras import regularizers
 
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu',
 kernel_regularizer=regularizers.l2(0.0001)),
 Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu',
 kernel_regularizer=regularizers.l2(0.0001)),
 Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu',
 kernel_regularizer=regularizers.l2(0.0001)),
 Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu',
 kernel_regularizer=regularizers.l2(0.0001)),
 Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu',
 kernel_regularizer=regularizers.l2(0.0001)),
 
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
 
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))

注意这里lambda的值等于0.0001. 太棒了!我们获得了一个比之前NN模型更好的准确率。

现在尝试一下L1正则化。

## l1
 
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu',
 kernel_regularizer=regularizers.l1(0.0001)),
 Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu',
 kernel_regularizer=regularizers.l1(0.0001)),
 Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu',
 kernel_regularizer=regularizers.l1(0.0001)),
 Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu',
 kernel_regularizer=regularizers.l1(0.0001)),
 Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu',
 kernel_regularizer=regularizers.l1(0.0001)),
 
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))

这次并没有显示出任何的改善。我们再来试试dropout技术。

## dropout
 
from keras.layers.core import Dropout
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
 Dropout(0.25),
 
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))

效果不错!dropout也在简单NN模型上给出了一些改善。

现在,我们试试数据增强。

from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(zca_whitening=True)
# loading data
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
temp = []
for img_name in train.filename:
 image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
 img = imread(image_path, flatten=True)
 img = img.astype('float32')
 temp.append(img)
 
x_train = np.stack(temp)
 
X_train = x_train.reshape(x_train.shape[0], 1, 28, 28)
 
X_train = X_train.astype('float32')

现在,拟合训练数据以便增强。

# fit parameters from data
datagen.fit(X_train)

这里,我使用了zca_whitening作为实参,它突出了每个数字的轮廓,如下图所示:

## splitting
y_train = keras.utils.np_utils.to_categorical(train.label.values)
split_size = int(x_train.shape[0]*0.7)
 
x_train, x_test = X_train[:split_size], X_train[split_size:]
y_train, y_test = y_train[:split_size], y_train[split_size:]
## reshaping
x_train=np.reshape(x_train,(x_train.shape[0],-1))/255
x_test=np.reshape(x_test,(x_test.shape[0],-1))/255
## structure using dropout
from keras.layers.core import Dropout
model = Sequential([
 Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
 Dropout(0.25),
 Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
 Dropout(0.25),
 
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
 ])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))

哇!我们在准确率得分上有了一个飞跃。并且好消息是它每次都奏效。我们只需要根据数据集中的图像来选择一个合适的实参。

现在,试一下最后一种技术——提前停止。

from keras.callbacks import EarlyStopping
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test)
 , callbacks = [EarlyStopping(monitor='val_acc', patience=2)])

可以看到我们的模型在仅仅5轮迭代后就停止了,因为验证集准确率不再提高了。当我们使用更大值的epochs来运行它时,它会给出好的结果。你可以说它是一种优化epoch值的技术。

结语

我希望现在你已经理解了正则化以及在深度学习模型中实现正则化的不同技术。 无论你处理任何深度学习任务,我都强烈建议你使用正则化。它将帮助你开阔视野并更好的理解这个主题。

原文链接: https://www.analyticsvidhya.com/blog/2018/04/fundamentals-deep-learning-regularization-techniques/

译者简介

和中华,留德软件工程硕士。由于对机器学习感兴趣,硕士论文选择了利用遗传算法思想改进传统kmeans。目前在杭州进行大数据相关实践。加入数据派THU希望为IT同行们尽自己一份绵薄之力,也希望结交许多志趣相投的小伙伴。

转载须知

如需转载,请在开篇显著位置注明作者和出处(转自:数据派THU ID:DatapiTHU),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。

发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。

原文发布于微信公众号 - 数据派THU(DatapiTHU)

原文发表时间:2018-05-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT派

入门 | 深度学习模型的简单优化技巧

以下是我与同事和学生就如何优化深度模型进行的对话、消息和辩论的摘要。如果你发现了有影响力的技巧,请分享。

10820
来自专栏小詹同学

深度学习神经网络第①篇——感知器及其Python实现

感知器是由美国计算机科学家罗森布拉特(F.Roseblatt)于1957年提出的。感知器可谓是最早的人工神经网络。单层感知器是一个具有一层神经元、采用阈值激活函...

18640
来自专栏fangyangcoder

四旋翼姿态解算之理论推导

对于每个像我一样入坑四轴飞行器不久的新手来说,最初接触也颇为头疼的东西之一就是四轴的姿态解算。由于涉及较多的数学知识,很多人也是觉得十分头疼。所以,我在这里分享...

13220
来自专栏ATYUN订阅号

深度学习要点:可视化卷积神经网络

AiTechYun 编辑:yuxiangyu 深度学习中最深入讨论的话题之一是如何解释和理解一个训练完成的模型,尤其是在医疗保健等高风险行业的背景下。“黑盒”这...

1.5K100
来自专栏null的专栏

简单易学的机器学习算法——Latent Dirichlet Allocation(理论篇)

引言 LDA(Latent Dirichlet Allocation)称为潜在狄利克雷分布,是文本语义分析中比较重要的一个模型,同时,LDA模型中使用到了贝叶斯...

452100
来自专栏机器学习算法工程师

【TPAMI重磅综述】 SIFT与CNN的碰撞:万字长文回顾图像检索任务十年探索历程(下篇)

本文是《SIFT Meets CNN: A Decade Survey of Instance Retrieval》的下篇。在上 篇中概述了图像检索任务极其发...

20730
来自专栏智能算法

机器学习三人行(系列十)----机器学习降压神器(附代码)

系列九我们从算法组合的角度一起实战学习了一下组合算法方面的知识,详情戳下链接: 机器学习三人行(系列九)----千变万化的组合算法(附代码) 但是,我们也知道算...

43190
来自专栏量化投资与机器学习

【ML系列】一招鲜,判断哪些输入特征对神经网络是重要的!

没错,但这不是今天的重点。我们想知道的是输入特征对神经网络的预测计算有多重要。例如,通过学习时间、年龄、身高和缺席人数等几个预测因素来预测谁会通过考试。直觉上,...

12720
来自专栏null的专栏

简单易学的机器学习算法——主成分分析(PCA)

一、数据降维        对于现在维数比较多的数据,我们首先需要做的就是对其进行降维操作。降维,简单来说就是说在尽量保证数据本质的前提下将数据中的维数降低。降...

36150
来自专栏鸿的学习笔记

写给开发者的机器学习指南(二)

在机器学习领域有两种主要的学习方式,即监督学习和无监督学习。当您想在您的应用程序中使用机器学习时,需要简要说明下,因为选择正确的机器学习方法和算法是一个重要但有...

11320

扫码关注云+社区

领取腾讯云代金券