1、单个神经元
欢迎来到深度学习!
欢迎来到卡格尔的深度学习课程介绍!你即将学会所有你需要的东西,开始构建你自己的深层神经网络。使用Keras和Tensorflow,您将学习如何:
创建一个完全连接的神经网络架构
将神经网络应用于两个经典的最大似然问题:回归和分类
用随机梯度下降法训练神经网络,以及
使用退出、批处理规范化和其他技术提高性能
本教程将通过完整的示例向您介绍这些主题,然后在练习中,您将更深入地探讨这些主题,并将其应用于实际数据集。
让我们开始吧!
什么是深度学习?
近年来,人工智能领域最令人印象深刻的一些进展是在深度学习领域。自然语言翻译、图像识别和游戏都是深度学习模型接近甚至超过人类水平的任务。
那么什么是深度学习呢?深度学习是一种机器学习方法,其特点是计算量大。这种计算深度使得深度学习模型能够理清在最具挑战性的现实世界数据集中发现的各种复杂和分层模式。
神经网络凭借其强大的功能和可扩展性,已经成为深度学习的定义模型。神经网络由神经元组成,每个神经元单独执行一个简单的计算。神经网络的力量来自于这些神经元所能形成的复杂连接。
线性单位
让我们从神经网络的基本组成部分开始:单个神经元。如图所示,具有一个输入的神经元(或单元)如下所示:
输入是x。它与神经元的连接有一个重量w。每当一个值流经一个连接时,就将该值乘以该连接的权重。对于输入x,到达神经元的是w*x。神经网络通过修改其权重来“学习”。
b是一种特殊的权重,我们称之为偏差。偏差没有任何与之相关的输入数据;相反,我们把1放在图中,这样到达神经元的值就是b(因为1*b=b)。偏压使神经元能够独立于输入修改输出。
y是神经元最终输出的值。为了得到输出,神经元将通过其连接接收到的所有值相加。这个神经元的激活是y=w*x+b,或者用公式y=wx+b。
公式y=wx+b看起来熟悉吗?
这是一个直线方程!这是斜率截距方程,其中w是斜率,b是y截距。
示例-线性单元作为模型
虽然单个神经元通常只作为更大网络的一部分发挥作用,但从单个神经元模型开始作为基线通常是有用的。单神经元模型是线性模型。
让我们考虑一下,这在像80种谷物这样的数据集上是如何工作的。以“糖”(每餐糖克数)作为输入,以“卡路里”(每餐卡路里数)作为输出训练模型,我们可能会发现偏差为b=90,重量为w=2.5。我们可以这样估计每份含有5克糖的谷物的卡路里含量:
而且,对照我们的配方,我们的卡路里=2.5×5+90=102.5,就像我们预期的那样。
多输入
80谷物数据集的功能远不止“糖”。如果我们想扩展我们的模型以包括纤维或蛋白质含量之类的东西呢?这很容易。我们可以给神经元增加更多的输入连接,每增加一个功能就增加一个。为了找到输出,我们将每个输入乘以其连接权重,然后将它们相加。
这个神经元的公式是y=w0x0+w1x1+w2x2+b。具有两个输入的线性单元将适合一个平面,具有更多输入的单元将适合一个超平面。
Keras中的线性单位
在Keras中创建模型的最简单方法是通过Keras.Sequential,它创建一个神经网络作为一组层。我们可以使用密集层创建类似上面的模型(我们将在下一课中了解更多)。
我们可以定义一个线性模型,接受三个输入特征(“糖”、“纤维”和“蛋白质”),并产生单一输出(“卡路里”),如下所示:
from tensorflow import keras
from tensorflow.keras import layers
# 创建具有1个线性单位的网络
model = keras.Sequential([
layers.Dense(units=1, input_shape=[3])
])
使用第一个参数units,我们定义了需要多少输出。在这种情况下,我们只是预测“卡路里”,所以我们将使用单位=1。
对于第二个参数,input_shape,我们告诉Keras输入的维度。设置input_shape=[3]可确保模型将接受三个特征作为输入(“糖”、“纤维”和“蛋白质”)。
这个模型现在可以适应训练数据了!
为什么input_shape是Python列表?
我们将在本课程中使用的数据是表格数据,如熊猫数据框中的数据。对于数据集中的每个特性,我们将有一个输入。这些特性是按列排列的,所以我们总是有input\u shape=[num\u columns]。Keras在这里使用列表的原因是允许使用更复杂的数据集。例如,图像数据可能需要三个维度:[高度、宽度、通道]。
2、深度神经网络
介绍
在这节课中,我们将看到如何构建能够学习复杂关系的神经网络,而深层神经网络以其独特的功能而闻名。
这里的关键思想是模块化,从简单的功能单元构建复杂的网络。我们已经了解了线性单元是如何计算线性函数的——现在我们将了解如何组合和修改这些单元,以建模更复杂的关系。
层
神经网络通常将神经元组织成层。当我们收集具有一组公共输入的线性单元时,我们得到一个密集层。
一个输入层中的三个圆与密集层中的两个圆相连的堆栈。
两个线性单元的密集层,接收两个输入和一个偏置。
你可以把神经网络中的每一层看作是执行某种相对简单的转换。神经网络通过层层叠加,可以以越来越复杂的方式转换其输入。在一个训练有素的神经网络中,每一层都是一个变换,使我们更接近一个解决方案。
多种层次
Keras中的“层”是一种非常普遍的东西。从本质上讲,层可以是任何类型的数据转换。许多层,如卷积层和递归层,通过使用神经元来转换数据,主要不同于它们形成的连接模式。其他的则用于特征工程或只是简单的算法。有一整个世界的层要发现-看看他们!
激活函数
然而,事实证明,两个致密层之间没有任何东西,并不比单个致密层本身好多少。致密层本身永远无法将我们带出线和平面的世界。我们需要的是非线性的东西。我们需要的是激活函数。
没有激活函数,神经网络只能学习线性关系。为了拟合曲线,我们需要使用激活函数。
激活函数只是我们应用于层的每个输出(其激活)的函数。最常见的是整流器功能max(0,x)。
整流器功能图。当x>0时,线y=x,当x<0时,线y=0,使“铰链”形状像“/”。
整流器功能有一个图形,它是一条负部分“整流”为零的线。将该函数应用于神经元的输出将使数据发生弯曲,使我们远离简单的线条。
当我们将整流器连接到一个线性单元时,我们得到一个整流的线性单元或ReLU(因此,通常将整流函数称为“ReLU函数”。)对线性单元应用ReLU激活意味着输出变为最大值(0,w*x+b),我们可以在如下图中绘制:
堆积致密层
现在我们有了一些非线性,让我们看看如何堆叠层以获得复杂的数据转换。
输出层之前的层有时被称为隐藏层,因为我们从未直接看到它们的输出。
现在,请注意,最后一个(输出)层是一个线性单元(表示没有激活函数)。这使得这个网络适合于回归任务,我们试图预测一些任意的数值。其他任务(如分类)可能需要在输出上使用激活函数。
建立序列模型
我们使用的顺序模型将按照从第一层到最后一层的顺序将一系列层连接在一起:第一层获取输入,最后一层生成输出。这将创建上图中的模型:
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
# 隐藏的ReLU层
layers.Dense(units=4, activation='relu', input_shape=[2]),
layers.Dense(units=3, activation='relu'),
# 线性输出层
layers.Dense(units=1),
])
确保将所有层一起传递到一个列表中,如[layer,layer,layer,…],而不是作为单独的参数。要向层添加激活函数,只需在激活参数中给出其名称。
3、随机梯度下降
介绍
在前两节课中,我们学习了如何从密集层的堆栈中构建完全连接的网络。第一次创建时,所有网络的权重都是随机设置的——网络还“不知道”任何东西。在这节课中,我们将看到如何训练神经网络;我们将看到神经网络是如何学习的。
与所有机器学习任务一样,我们从一组训练数据开始。训练数据中的每个示例都包含一些特征(输入)和一个预期目标(输出)。训练网络意味着调整其权重,使其能够将特征转化为目标。例如,在80种谷物的数据集中,我们需要一个网络,可以获取每种谷物的“糖”、“纤维”和“蛋白质”含量,并对该谷物的“热量”进行预测。如果我们能够成功地训练一个网络来做到这一点,那么它的权重必须以某种方式表示这些特征与训练数据中表示的目标之间的关系。
除了培训数据外,我们还需要两样东西:
一种“损失函数”,用于衡量网络预测的好坏。
可以告诉网络如何改变其权重的“优化器”。
损失函数
我们已经看到了如何为网络设计架构,但还没有看到如何告诉网络要解决什么问题。这是损失函数的工作。
损失函数测量目标真实值与模型预测值之间的差异。
不同的问题需要不同的损失函数。我们一直在研究回归问题,任务是预测一些数值——80种谷物中的卡路里,红酒质量的等级。其他回归任务可能是预测房价或汽车的燃油效率。
回归问题的一个常见损失函数是平均绝对误差或MAE。对于每个预测y_pred,MAE通过绝对差值abs(y_真-y_pred)测量与真实目标y_真的视差。
数据集上的总MAE损失是所有这些绝对差异的平均值。
描绘从数据点到拟合线的误差条的图形。。
平均绝对误差是拟合曲线和数据点之间的平均长度。
除MAE外,您可能会看到回归问题的其他损失函数是均方误差(MSE)或Huber损失(两者都可在Keras中使用)。
在训练期间,模型将使用损失函数作为找到正确权重值的指南(损失越小越好)。换句话说,损失函数告诉网络它的目标。
优化器-随机梯度下降
我们已经描述了我们希望网络解决的问题,但现在我们需要说明如何解决它。这是优化器的工作。优化器是一种调整权重以最小化损失的算法。
实际上,深度学习中使用的所有优化算法都属于一个称为随机梯度下降的家族。它们是逐步训练网络的迭代算法。培训的一个步骤如下:
采集一些训练数据并通过网络运行以进行预测。
测量预测值和真实值之间的损失。
最后,在使损失更小的方向上调整权重。
然后反复这样做,直到损失尽可能小(或者直到损失不再减少)
一批一批地装配生产线。损失减少,权重接近其真实值。
用随机梯度下降法训练神经网络。
每次迭代的训练数据样本称为一个小批量(或通常仅称为“批量”),而一轮完整的训练数据称为一个历元。你训练的次数是网络看到每个训练示例的次数。
动画显示了第1课中使用SGD训练的线性模型。浅红色点表示整个训练集,而实心红色点表示小批量。每次SGD看到一个新的小批量,它都会将权重(w斜率和b y截距)移向该批次上的正确值。一批又一批,生产线最终会收敛到最佳拟合状态。可以看到,随着权重越来越接近其真实值,损失也越来越小。
学习率和批量大小
请注意,该行仅在每个批次的方向上进行一个小的移动(而不是一直移动)。这些变化的大小取决于学习率。较小的学习速率意味着网络需要在其权重收敛到最佳值之前看到更多的小批量。
学习率和小批量的大小是对SGD培训进度影响最大的两个参数。它们之间的相互作用往往是微妙的,对这些参数的正确选择并不总是显而易见的(我们将在练习中探讨这些影响。)
幸运的是,对于大多数工作来说,没有必要进行广泛的超参数搜索以获得满意的结果。Adam是一种SGD算法,具有自适应学习速率,使其适用于大多数无需任何参数调整的问题(从某种意义上说,它是“自调整”)。Adam是一个伟大的通用优化器。
添加损失和优化程序
定义模型后,可以使用模型的编译方法添加损失函数和优化器:
model.compile(
optimizer="adam",
loss="mae",
)
请注意,我们可以用一个字符串指定loss和optimizer。您还可以通过KerasAPI直接访问这些参数——例如,如果您想调优参数——但对我们来说,默认值可以正常工作。
名字里有什么?
梯度是一个向量,它告诉我们权重需要朝哪个方向移动。更准确地说,它告诉我们如何改变重量,使损失变化最快。我们称我们的过程为梯度下降,因为它使用梯度将损失曲线下降到最小值。随机意味着“偶然决定”。我们的训练是随机的,因为小批量是数据集中的随机样本。这就是为什么它被称为SGD!
示例-红酒质量
现在我们知道了开始培训深度学习模型所需的一切。让我们看看它的实际行动吧!我们将使用红酒质量数据集。
该数据集包括约1600种葡萄牙红葡萄酒的理化测量数据。此外,还包括盲品测试中每种葡萄酒的质量评级。通过这些测量,我们能预测葡萄酒的感知质量吗?
我们已经把所有的数据准备工作都放到了下一个隐藏的单元中。接下来的内容并不重要,所以请跳过它。不过,现在您可能会注意到,我们已经重新调整了每个功能的比例,使其位于间隔[0,1]内。正如我们将在第5课中进一步讨论的那样,当神经网络的输入在一个共同的尺度上时,它们往往表现得最好。
import pandas as pd
from IPython.display import display
red_wine = pd.read_csv('../input/dl-course-data/red-wine.csv')
# 创建培训和验证拆分
df_train = red_wine.sample(frac=0.7, random_state=0)
df_valid = red_wine.drop(df_train.index)
display(df_train.head(4))
# 按比例[0, 1]
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_)
# 分割特征和目标
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']
这个网络应该有多少个输入?我们可以通过查看数据矩阵中的列数来发现这一点。确保此处不包括目标(“质量”),仅包括输入特性。
print(X_train.shape)
十一列表示十一个输入。
我们选择了一个有1500多个神经元的三层网络。这个网络应该能够学习数据中相当复杂的关系。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=[11]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
确定模型的体系结构应该是过程的一部分。从简单开始,使用验证损失作为指南。您将在练习中了解有关模型开发的更多信息。
定义模型后,我们在优化器和loss函数中编译。
model.compile(
optimizer='adam',
loss='mae',
)
现在我们准备好开始训练了!我们已经告诉Keras一次向优化器提供256行训练数据(批大小),并在整个数据集(历代)中执行10次。
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=10,
)
您可以看到,随着模型列车的出现,Keras将不断向您通报损失情况。
通常,观察损失的一个更好的方法是绘制它。事实上,拟合方法在历史对象中记录训练过程中产生的损失。我们将把数据转换成熊猫数据框,这样绘图就容易了。
import pandas as pd
# 将培训历史记录转换为数据帧
history_df = pd.DataFrame(history.history)
# 使用本地绘图方法
history_df['loss'].plot();
请注意,随着时代的推移,损失是如何趋于平稳的。当损失曲线变得像这样水平时,这意味着模型已经学会了它所能学到的一切,没有理由继续进行更多的历次。
4、过拟合和欠拟合
介绍
回想上一课中的示例,Keras将在其培训模型的各个时期保留培训和验证损失的历史记录。在本课中,我们将学习如何解释这些学习曲线,以及如何使用它们来指导模型开发。特别是,我们将检查学习曲线,寻找拟合不足和拟合过度的证据,并查看两种纠正策略。
解读学习曲线
您可能认为训练数据中的信息有两种:信号和噪声。信号是概括的部分,这部分可以帮助我们的模型根据新数据进行预测。噪声是仅对训练数据有效的部分;噪声是所有来自真实世界数据的随机波动,或者所有偶然的、非信息性的模式,实际上无法帮助模型做出预测。噪音是这样的:零件看起来可能有用,但实际上不是。
我们通过在训练集中选择使损失最小化的权重或参数来训练模型。但是,您可能知道,为了准确评估模型的性能,我们需要在一组新的数据(验证数据)上对其进行评估(您可以在《机器学习导论》中查看我们关于模型验证的课程以进行回顾。)
当我们训练一个模型时,我们已经在训练集上一个历元一个历元地绘制损失。我们还将为验证数据添加一个绘图。这些曲线我们称之为学习曲线。为了有效地训练深度学习模型,我们需要能够解释它们。
现在,当模型学习信号或噪声时,训练损失将下降。但只有当模型学习信号时,验证损失才会下降(因此,当模型学习信号时,两条曲线都会下降,但当它学习噪声时,曲线中会产生一个间隙。间隙的大小告诉您模型已学习了多少噪波。
理想情况下,我们将创建能够学习所有信号而无噪声的模型。这几乎永远不会发生。相反,我们做了一笔交易。我们可以让模型学习更多的信号,但代价是学习更多的噪声。只要交易对我们有利,验证损失将继续减少。然而,在某一点之后,交易可能会对我们不利,成本超过收益,验证损失开始上升。
这种权衡表明,在训练模型时可能会出现两个问题:信号不足或噪声过大。欠拟合训练集是指由于模型没有学习到足够的信号,导致损失没有达到可能的程度。过度拟合训练集是指由于模型学习了太多的噪声,因此损失没有尽可能低。培养深度学习模型的诀窍在于找到两者之间的最佳平衡。
我们将研究几种从训练数据中获取更多信号的方法,同时减少噪声量。
容量
模型的容量是指它能够学习的模式的大小和复杂性。对于神经网络来说,这在很大程度上取决于它有多少个神经元以及它们是如何连接在一起的。如果您的网络似乎不适合数据,则应尝试增加其容量。
可以通过使网络更宽(将更多单元添加到现有层)或使其更深(添加更多层)来增加网络的容量。更广泛的网络更容易学习更多的线性关系,而更深的网络更喜欢非线性关系。哪一个更好取决于数据集。
model = keras.Sequential([
layers.Dense(16, activation='relu'),
layers.Dense(1),
])
wider = keras.Sequential([
layers.Dense(32, activation='relu'),
layers.Dense(1),
])
deeper = keras.Sequential([
layers.Dense(16, activation='relu'),
layers.Dense(16, activation='relu'),
layers.Dense(1),
])
在本练习中,您将探索网络容量如何影响其性能。
早停
我们提到,当模型过于急切地学习噪声时,验证损失可能会在训练期间开始增加。为了防止这种情况,只要验证损失不再减少,我们就可以停止培训。以这种方式中断训练称为提前停止。
一旦我们检测到验证损失再次开始上升,我们就可以将权重重置回发生最小值的位置。这确保了模型不会继续学习噪声和过度拟合数据。
提前停止训练也意味着我们在网络完成学习信号之前过早停止训练的危险更小。因此,除了防止训练时间过长导致过度训练外,提前停止训练也可以防止训练时间不足。只需将你的训练时间设置为一个大的数字(超过你需要的时间),然后提前停止就可以了。
增加提前停车
在Keras中,我们通过回调在训练中包括提前停止。回调只是一个函数,您希望在网络运行时经常运行它。提前停止回调将在每个历元之后运行(Keras预先定义了各种有用的回调,但您也可以定义自己的回调。)
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(
min_delta=0.001, # 作为改进的最小变化量
patience=20, # 停车前要等多少个纪元
restore_best_weights=True,
)
这些参数表示:“如果验证损失在过去20个时期内没有至少改善0.001,则停止培训并保留您找到的最佳模型。”有时很难判断验证损失是由于过度拟合还是仅仅由于随机批次变化而增加。这些参数允许我们设置一些关于何时停止的余量。
正如我们将在示例中看到的,我们将把这个回调与loss和optimizer一起传递给fit方法。
示例-训练提前停车的模型
让我们继续根据上一个教程中的示例开发模型。我们将增加该网络的容量,但也会添加一个提前停止回调,以防止过度拟合。
这又是数据准备。
import pandas as pd
from IPython.display import display
red_wine = pd.read_csv('../input/dl-course-data/red-wine.csv')
# 创建培训和验证拆分
df_train = red_wine.sample(frac=0.7, random_state=0)
df_valid = red_wine.drop(df_train.index)
display(df_train.head(4))
# 缩放到[0,1]
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_)
# 分割特征和目标
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']
现在让我们增加网络的容量。我们将选择一个相当大的网络,但是一旦验证丢失显示出增加的迹象,就依靠回调来停止训练。
from tensorflow import keras
from tensorflow.keras import layers, callbacks
early_stopping = callbacks.EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=[11]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
model.compile(
optimizer='adam',
loss='mae',
)
定义回调后,将其添加为fit中的一个参数(您可以有多个参数,因此将其放入列表中)。使用“提前停止”时,请选择大量时间段,超过您的需要。
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=500,
callbacks=[early_stopping], # put your callbacks in a list
verbose=0, # turn off training log
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
print("Minimum validation loss: {}".format(history_df['val_loss'].min()))
果不其然,凯拉斯在整整500个纪元之前就停止了训练!
5、Dropout和批量归一化
介绍
深度学习的世界不仅仅是密集的层次。有几十种类型的图层可以添加到模型中(尝试浏览Keras文档以获取示例!)有些类似于致密层,定义神经元之间的连接,而另一些可以进行预处理或其他类型的转换。
在本课中,我们将学习两种特殊的层,它们本身不包含任何神经元,但它们添加了一些功能,这些功能有时可以以各种方式使模型受益。两者都是现代建筑中常用的。
辍学者
第一个是“脱落层”,它可以帮助纠正过度装配。
在上一课中,我们讨论了网络学习训练数据中的虚假模式是如何导致过度拟合的。为了识别这些虚假模式,网络通常会依赖于非常特定的权重组合,这是一种权重的“共谋”。由于如此具体,他们往往是脆弱的:删除一个和阴谋解体。
这就是辍学背后的想法。为了打破这些阴谋,我们在训练的每一步都随机删除一层输入单元的一小部分,这使得网络更难从训练数据中学习这些虚假模式。相反,它必须寻找广泛的、通用的模式,这些模式的权重模式往往更稳健。
网络在各种随机退出配置中循环的动画。
在这里,两个隐藏层之间添加了50%的退出。
你也可以把辍学看作是创造一种网络的集合。这些预测将不再由一个大型网络做出,而是由一个由小型网络组成的委员会做出。委员会中的个人往往会犯各种各样的错误,但同时也要正确,使委员会作为一个整体比任何个人都好(如果您熟悉将随机森林作为决策树的集合,那么您的想法也是一样的。)
增加辍学率
在Keras中,退出率参数rate定义了要关闭的输入单元的百分比。将下拉图层放在要应用该下拉图层的图层之前:
keras.Sequential([
# ...
layers.Dropout(rate=0.3), # apply 30% dropout to the next layer
layers.Dense(16),
# ...
])
批量标准化
我们将看到的下一个特殊层执行“批量规范化”(或“batchnorm”),这有助于纠正缓慢或不稳定的训练。
对于神经网络,将所有数据放在一个通用的尺度上通常是一个好主意,也许可以使用scikit learn的StandardScaler或MinMaxScaler之类的工具。原因是SGD将根据数据产生的激活量的大小按比例改变网络权重。倾向于产生不同大小激活的功能可能导致不稳定的训练行为。
现在,如果在数据进入网络之前对其进行规范化是好的,那么在网络内部进行规范化也会更好!事实上,我们有一种特殊的层可以做到这一点,批量规范化层。批次标准化层查看每个批次,首先使用其自身的平均值和标准偏差对批次进行标准化,然后使用两个可训练的重缩放参数将数据放在新的尺度上。实际上,Batchnorm对其输入执行一种协调的重新缩放。
大多数情况下,batchnorm是作为优化过程的辅助工具添加的(尽管它有时也有助于预测性能)。使用batchnorm的模型往往需要较少的时间来完成训练。此外,batchnorm还可以解决可能导致培训“停滞”的各种问题。考虑向模型添加批量规范化,尤其是在培训过程中遇到问题时。
添加批处理规范化
看起来,批处理规范化几乎可以在网络中的任何一点上使用。你可以把它放在一层之后。。。
layers.Dense(16, activation='relu'),
layers.BatchNormalization(),
或在层与其激活功能之间:
layers.Dense(16),
layers.BatchNormalization(),
layers.Activation('relu'),
如果你把它作为网络的第一层,它可以作为一种自适应预处理器,代替Sci Kit Learn的StandardScaler。
示例-使用退出和批处理规范化
让我们继续开发红酒模型。现在,我们将进一步增加容量,但添加dropout以控制过度拟合和批量标准化以加快优化。这一次,我们还将停止标准化数据,以演示批量标准化如何稳定培训。
# Setup plotting
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
# Set Matplotlib defaults
plt.rc('figure', autolayout=True)
plt.rc('axes', labelweight='bold', labelsize='large',
titleweight='bold', titlesize=18, titlepad=10)
import pandas as pd
red_wine = pd.read_csv('../input/dl-course-data/red-wine.csv')
# Create training and validation splits
df_train = red_wine.sample(frac=0.7, random_state=0)
df_valid = red_wine.drop(df_train.index)
# Split features and target
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']
添加衰减时,可能需要增加密集层中的单位数。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(1024, activation='relu', input_shape=[11]),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1024, activation='relu'),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1024, activation='relu'),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1),
])
这次我们的训练安排没有什么可改变的。
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=100,
verbose=0,
)
# 显示学习曲线
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
如果在将数据用于培训之前对其进行标准化,通常会获得更好的性能。然而,我们完全能够使用原始数据,这表明在更困难的数据集上批处理规范化是多么有效。
6、二元分类
介绍
到目前为止,在本课程中,我们已经了解了神经网络如何解决回归问题。现在我们将把神经网络应用于另一个常见的机器学习问题:分类。到目前为止,我们所学到的大部分知识仍然适用。主要区别在于我们使用的损耗函数,以及我们希望最后一层产生什么样的输出。
二元分类
分类为两类之一是一个常见的机器学习问题。你可能想预测客户是否有可能购买,信用卡交易是否有欺诈行为,深空信号是否显示有新行星的证据,或是疾病的医学测试证据。这些都是二进制分类问题。
在原始数据中,类可能由“Yes”和“No”或“Dog”和“Cat”等字符串表示。在使用这些数据之前,我们将指定一个类标签:一个类将为0,另一个类将为1。指定数字标签将以神经网络可以使用的形式放置数据。
精度与交叉熵
准确度是衡量分类问题成功与否的众多指标之一。准确度是正确预测与总预测的比率:准确度=数字\正确/总。始终正确预测的模型的准确度得分为1.0。在所有其他条件相同的情况下,无论何时数据集中的类以大约相同的频率出现,准确度都是一个合理的指标。
准确度(以及大多数其他分类指标)的问题在于它不能用作损失函数。SGD需要一个平稳变化的损失函数,但精度,即计数比率,在“跳跃”中变化。因此,我们必须选择一个替代品作为损失函数。这个替代品是交叉熵函数。
现在,回想一下损失函数定义了训练期间网络的目标。通过回归,我们的目标是最小化预期结果和预测结果之间的距离。我们选择MAE来测量这个距离。
对于分类,我们想要的是概率之间的距离,这就是交叉熵提供的。交叉熵是一种度量从一个概率分布到另一个概率分布的距离的方法。
我们希望我们的网络以1.0的概率预测正确的班级。预测概率离1.0越远,交叉熵损失越大。
我们使用交叉熵的技术原因有点微妙,但从本节中要去掉的主要内容是:使用交叉熵进行分类损失;您可能关心的其他指标(如准确性)也会随之提高。
用Sigmoid函数求概率
交叉熵函数和精度函数都需要概率作为输入,即0到1的数字。为了将密集层产生的实值输出转化为概率,我们附加了一种新的激活函数,即sigmoid激活函数。
为了得到最终的类预测,我们定义了一个阈值概率。通常这将是0.5,因此四舍五入将为我们提供正确的类别:低于0.5表示标签为0的类别,0.5或以上表示标签为1的类别。0.5阈值是Keras默认使用的精度度量。
示例-二进制分类
现在让我们试试看!
电离层数据集包含从聚焦于地球大气层电离层的雷达信号中获得的特征。任务是确定信号是显示存在某种物体,还是仅显示空空气。
import pandas as pd
from IPython.display import display
ion = pd.read_csv('../input/dl-course-data/ion.csv', index_col=0)
display(ion.head())
df = ion.copy()
df['Class'] = df['Class'].map({'good': 0, 'bad': 1})
df_train = df.sample(frac=0.7, random_state=0)
df_valid = df.drop(df_train.index)
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_)
df_train.dropna(axis=1, inplace=True) # drop the empty feature in column 2
df_valid.dropna(axis=1, inplace=True)
X_train = df_train.drop('Class', axis=1)
X_valid = df_valid.drop('Class', axis=1)
y_train = df_train['Class']
y_valid = df_valid['Class']
我们将定义我们的模型,就像我们为回归任务所做的一样,只有一个例外。在最后一层中包括一个“sigmoid”激活,这样模型将产生类概率。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(4, activation='relu', input_shape=[33]),
layers.Dense(4, activation='relu'),
layers.Dense(1, activation='sigmoid'),
])
将交叉熵损失和精度度量添加到模型及其编译方法中。对于两类问题,请确保使用“二进制”版本(更多类的问题会略有不同。)Adam优化器对于分类也很有效,所以我们将继续使用它。
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['binary_accuracy'],
)
这个特定问题中的模型可能需要很多时间才能完成训练,因此为了方便起见,我们将包括一个提前停止回调。
early_stopping = keras.callbacks.EarlyStopping(
patience=10,
min_delta=0.001,
restore_best_weights=True,
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=1000,
callbacks=[early_stopping],
verbose=0, # 隐藏输出,因为我们有太多的时代
)
我们将一如既往地查看学习曲线,并检查我们在验证集上获得的损失和准确性的最佳值(请记住,提前停止会将权重恢复为获得这些值的权重。)
history_df = pd.DataFrame(history.history)
# 在第5纪元开始绘图
history_df.loc[5:, ['loss', 'val_loss']].plot()
history_df.loc[5:, ['binary_accuracy', 'val_binary_accuracy']].plot()
print(("Best Validation Loss: {:0.4f}" +\
"\nBest Validation Accuracy: {:0.4f}")\
.format(history_df['val_loss'].min(),
history_df['val_binary_accuracy'].max()))