Keras 深度神经网络学习手册(二)
四、用于监督学习的深度神经网络:分类
在第三章中,我们探索了一个用于回归的 DL 用例。我们用业务推进战略探索了整个问题解决方法。我们利用我们从基础 DL 和 Keras 框架的第 1 和 2 章节中学到的知识来开发回归用例的 DNNs。在这一章中,我们将进一步学习并设计一个分类用例的网络。总体方法保持不变,但是在解决分类用例时,我们需要记住一些细微差别。此外,我们将把本章的学习向前推进一步,扩展 DNN 体系结构。让我们开始吧。
入门指南
类似于第三章中的,我们将考虑 Kaggle 作为我们用例的数据源。从可用选项中,我们将使用为“Red Hat Business Value”竞赛提供的数据集。这个比赛是几年前在 Kaggle 上举办的,数据集对于我们的研究来说是一个非常好的商业用例。存档比赛可在 www.kaggle.com/c/predicting-red-hat-business-value 获取。就像在前面的用例中一样,我们需要在为我们的实验下载数据集之前阅读并接受竞争规则。一旦你接受了比赛规则,你就可以从“数据”标签或 www.kaggle.com/c/predicting-red-hat-business-value/data 下载数据集。数据将以 zip 文件的形式下载。解压缩后,您将有四个不同的数据集。我们只需要其中的两个:act_train.csv 和 people.csv。
您可以复制这两个数据集,并将它们保存在一个新的文件夹中,用于当前章节的实验。我们将为用例使用相同的环境,但是在我们开始之前,让我们看一下问题陈述并定义 SCQ 和解决方案方法,就像我们在第三章中所做的一样。
问题陈述
高层次的问题陈述在比赛的描述页中被提及。它强调了基于运营交互数据来预测高价值客户的问题,从而帮助公司有效地优化资源以产生更多业务并更好地服务于客户。
让我们从一个更加以业务为中心的角度来看问题陈述。我们将从更好地了解客户开始。该组织是一家美国跨国软件公司,向企业社区提供开源软件产品。他们的主要产品是 Red Hat Enterprise Linux,这是 Linux OS 最流行的发行版,被各种大型企业所使用。在其服务中,它通过开放的业务模式和经济实惠、可预测的订阅模式提供企业级解决方案,从而帮助组织调整其 it 战略。这些来自大型企业客户的订阅为他们创造了很大一部分收入,因此对他们来说,了解他们有价值的客户并通过优化资源和策略来更好地服务他们以提高业务价值是至关重要的。
设计 SCQ
这个问题的最终涉众可能是销售团队或业务开发团队;这两个团队都处于公司运营的最前沿,为他们最有价值的客户提供最好的服务。为了更有效地实现这一目标,业务开发团队现在已经探索了一种数据驱动的解决方案。鉴于大量的运营交互数据和一些客户属性,他们希望开发数据驱动的技术来预测业务的潜在高价值客户。在这种背景下,现在让我们为业务问题起草 SCQ,就像我们在第三章中为回归用例所做的那样。
设计解决方案
上图展示的 SCQ 清楚地界定了当前局势和期望的未来局势,同时阐明了障碍和需要回答的问题,以便克服实现更大目标的障碍。为了设计解决方案,我们需要从关键问题开始,然后逆向工作。
我们如何识别潜在客户?
红帽已经存在超过 25 年了。在长期的业务中,他们从客户互动及其描述性属性中积累并获取了大量数据。这种丰富的数据源可能是模式的金矿,可以通过研究交互数据中大量复杂的历史模式来帮助识别潜在客户。
随着 DL 的日益普及和强大,我们可以开发一种 DNN,它可以从历史客户属性和运营交互数据中学习,以了解深层模式并预测新客户是否有可能成为各种业务服务的高价值客户。
因此,我们将开发和训练一个 DNN,使用各种客户属性和运营互动属性来学习一个客户成为潜在高价值客户的机会。
探索数据
现在,我们已经清楚地草拟了业务问题,并准备好了高级解决方案,让我们开始研究数据。从 Kaggle 下载红帽商业价值竞赛数据的流程与之前在第三章中展示的流程相同。所需数据集可在此下载: www.kaggle.com/c/predicting-red-hat-business-value/data 。请按照上一章演示的五个步骤下载数据。
现在,让我们打开 Jupyter 笔记本,为当前的实验创建一个新的笔记本。
#Import the necessary packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
#Import the 2 datasets provided in the Zip Folder
df = pd.read_csv("/Users/jojomoolayil/Book/Chapter4/Data/act_train.csv")
people = pd.read_csv("/Users/jojomoolayil/Book/Chapter4/Data/people.csv")
#Explore the shape of the datasets
print("Shape of DF:",df.shape)
print("Shape of People DF:",people.shape)
输出
Shape of DF: (2197291, 15)
Shape of People DF: (189118, 41)
最后一行代码:
#Explore the contents of the first dataset
df.head()
输出
我们可以看到,为比赛提供的训练数据超过 200 万行 15 列,而 people 数据集大约有 19 万行 41 列。探索训练数据集的内容,我们可以看到它大部分有客户交互数据,但完全匿名。考虑到客户及其属性的保密性,整个数据都是匿名的,这使得我们对其真实性质知之甚少。这是数据科学中常见的问题。开发 DL 模型的团队经常面临终端客户数据保密性的挑战,因此只能得到匿名的、有时是加密的数据。这仍然不应该是一个路障。拥有一个数据字典和对数据集的完全理解肯定是最好的,但是尽管如此,我们仍然可以用提供的信息开发模型。
act_train.csv(以下称为活动数据)有许多空数据点。在高层次上,数据集捕获客户活动并提供一些活动属性、一些客户属性(在前面的输出中显示为空)、另一个我们不太了解的名为“char_10”的分类特征,以及最后的结果变量。
让我们看看活动数据有多少个空值。
#Calculating the % of Null values in each column for activity data
df.isnull().sum()/df.shape[0]
输出
people_id 0.000000
activity_id 0.000000
date 0.000000
activity_category 0.000000
char_1 0.928268
char_2 0.928268
char_3 0.928268
char_4 0.928268
char_5 0.928268
char_6 0.928268
char_7 0.928268
char_8 0.928268
char_9 0.928268
char_10 0.071732
outcome 0.000000
dtype: float64
大约九个特征的空值超过 90%。我们无法修复这些功能。让我们继续,看看人员数据集。
#Explore the contents of People dataset
people.head()
输出
我们已经知道,人员数据集(此后称为客户数据)大约有 41 列;当我们进入内容时(由于大量的列,在前面的图中只显示了一部分),我们看到提供给我们许多客户属性,尽管我们无法理解它们。此外,列名与活动数据中的列名相同。我们需要在加入之前更改它们,以避免名称冲突。
让我们检查一下客户数据集有多少缺失的数据点。由于客户数据集有大约 40 多个特性,我们可以将所有列的缺失值百分比与前面的代码结合起来,而不是单独查看每一列。
#Calculate the % of null values in for the entire dataset
people.isnull().sum().sum()
输出
0
我们看到客户数据集中没有任何列缺少值。
为了创建一个合并的数据集,我们需要在 people_id 键上连接活动和客户数据。但在此之前,我们需要注意一些事情。我们需要删除活动数据中有 90%缺失值的列,因为它们无法修复。其次,“date”和“char_10”列出现在两个数据集中。为了避免名称冲突,让我们将活动数据集中的“date”列重命名为“activity_date”,将活动数据中的“char_10”重命名为“activity_type”接下来,我们还需要修复“activity_type”列中缺失的值。一旦这两项任务完成,我们将连接这两个数据集并研究整合后的数据。
#Create the list of columns to drop from activity data
columns_to_remove = ["char_"+str(x) for x in np.arange(1,10)]
print("Columns to remove:",columns_to_remove)
#Remove the columns from the activity data
df = df[list(set(df.columns) - set(columns_to_remove))]
#Rename the 2 columns to avoid name clashes in merged data
df = df.rename(columns={"date":"activity_date","char_10":"activity_type"})
#Replace nulls in the activity_type column with the mode
df["activity_type"] = df["activity_type"].fillna(df["activity_type"].mode()[0])
#Print the shape of the final activity dataset
print("Shape of DF:",df.shape)
输出
Columns to remove: ['char_1', 'char_2', 'char_3', 'char_4', 'char_5', 'char_6', 'char_7', 'char_8', 'char_9']
Shape of DF: (2197291, 6)
我们现在可以连接这两个数据集,以创建一个整合的活动和客户属性数据集。
#Merge the 2 datasets on 'people_id' key
df_new = df.merge(people,on=["people_id"],how="inner")
print("Shape before merging:",df.shape)
print("Shape after merging :",df_new.shape)
输出
Shape before merging: (2197291, 6)
Shape after merging : (2197291, 46)
一致的行数和增加的列数有助于我们验证连接操作是否按预期工作。现在让我们研究数据集中名为“结果”的目标(即我们想要预测的变量)。我们可以检查潜在客户和非潜在客户之间的分布。
print("Unique values for outcome:",df_new["outcome"].unique())
print("\nPercentage of distribution for outcome-")
print(df_new["outcome"].value_counts()/df_new.shape[0])3
Outcome
Unique values for outcome: [0 1]
Percentage of distribution for outcome-
0 0.556046
1 0.443954
Name: outcome, dtype: float64
我们可以看到,潜在客户的分布情况很好,大约 45%是潜在客户。
数据工程
接下来,假设我们总共有 45 个专栏要探索和转换,让我们通过自动化一些事情来加速这个过程。让我们看看整合数据框架中的不同数据类型。
#Checking the distinct datatypes in the dataset
print("Distinct DataTypes:",list(df_new.dtypes.unique()))
输出
Distinct DataTypes: [dtype('int64'), dtype('O'), dtype('bool')]
数据集中有数字、分类(对象)和布尔特征。Python 中的 Boolean 表示真或假值;我们需要将其转换成数字(1 和 0 ),以便模型处理数据。以下代码片段将 dataframe 中的布尔列转换为基于数字(1 和 0)的值。
#Create a temp dataset with the datatype of columns
temp = pd.DataFrame(df_new.dtypes)
temp.columns = ["DataType"]
#Create a list with names of all Boolean columns
boolean_columns = temp.index[temp["DataType"] == 'bool'].values
print("Boolean columns - \n",boolean_columns)
#Convert all boolean columns to Binary numeric values
for column in boolean_columns:
df_new[column] = np.where(df_new[column] == True,1,0)
print("\nDistinct DataTypes after processing:",df.dtypes.unique())
输出
Boolean columns -
['char_10"char_11"char_12"char_13"char_14"char_15"char_16'
'char_17"char_18"char_19"char_20"char_21"char_22"char_23'
'char_24"char_25"char_26"char_27"char_28"char_29"char_30'
'char_31"char_32"char_33"char_34"char_35"char_36"char_37']
Distinct DataTypes after processing: [dtype('int64') dtype('O')]
现在让我们来看看分类特征。我们将首先进行健全性检查,以了解每个分类特性中不同值的数量。如果分类特征中有非常多的不同值,我们必须决定是否真的可以将它们转换成一个独一无二的编码结构,以便进一步处理。
#Extracting the object columns from the above dataframe
categorical_columns = temp.index[temp["DataType"] == 'O'].values
#Check the number of distinct values in each categorical column
for column in categorical_columns:
print(column+" column has :",str(len(df_new[column].unique()))+" distinct values")
输出
activity_category column has : 7 distinct values
activity_id column has : 2197291 distinct values
people_id column has : 151295 distinct values
activity_type column has : 6516 distinct values
activity_date column has : 411 distinct values
char_1 column has : 2 distinct values
group_1 column has : 29899 distinct values
char_2 column has : 3 distinct values
date column has : 1196 distinct values
char_3 column has : 43 distinct values
char_4 column has : 25 distinct values
char_5 column has : 9 distinct values
char_6 column has : 7 distinct values
char_7 column has : 25 distinct values
char_8 column has : 8 distinct values
char_9 column has : 9 distinct values
输出中显示的五个突出显示的列具有大量不同的值。很难将它们转换成一键编码的形式,因为它们在处理过程中会消耗太多的内存。如果您有多余的 RAM,请随意将它们转换为一键编码的数据形式。
现在,我们可以看看这些分类列中的内容,以了解将它们转换成数字的方法。另外,date和activity_date列是日期值;因此,我们可以像上一章那样将它们转换成与数据相关的特征。让我们首先修复与日期相关的列,然后再处理剩余的分类列。以下代码片段将日期值转换为新功能,然后删除实际的列。
#Create date related features for 'date' in customer data
df_new["date"] = pd.to_datetime(df_new["date"])
df_new["Year"] = df_new["date"].dt.year
df_new["Month"] = df_new["date"].dt.month
df_new["Quarter"] = df_new["date"].dt.quarter
df_new["Week"] = df_new["date"].dt.week
df_new["WeekDay"] = df_new["date"].dt.weekday
df_new["Day"] = df_new["date"].dt.day
#Create date related features for 'date' in activity data
df_new["activity_date"] = pd.to_datetime(df_new["activity_date"])
df_new["Activity_Year"] = df_new["activity_date"].dt.year
df_new["Activity_Month"] = df_new["activity_date"].dt.month
df_new["Activity_Quarter"] = df_new["activity_date"].dt.quarter
df_new["Activity_Week"] = df_new["activity_date"].dt.week
df_new["Activity_WeekDay"] = df_new["activity_date"].dt.weekday
df_new["Activity_Day"] = df_new["activity_date"].dt.day
#Delete the original date columns
del(df_new["date"])
del(df_new["activity_date"])
print("Shape of data after create Date Features:",df_new.shape)
输出
Shape of data after create Date Features: (2197291, 56)
现在让我们看看剩下的分类列,它们有非常多的不同值。
print(df_new[["people_id","activity_type","activity_id","group_1"]].head())
输出
people_id activity_type activity_id group_1
0 ppl_100 type 76 act2_1734928 group 17304
1 ppl_100 type 1 act2_2434093 group 17304
2 ppl_100 type 1 act2_3404049 group 17304
3 ppl_100 type 1 act2_3651215 group 17304
4 ppl_100 type 1 act2_4109017 group 17304
似乎我们可以通过从每一列中提取相关的数字 ID,将前面所有的分类列转换成数字,因为每一列都有形式为someText_someNumber的值。我们可以暂时将这些分类列用作数字特征,而不是将它们转换成臃肿的一次性编码数据集。然而,如果在几次实验后,模型的性能没有达到我们的期望,我们可能不得不重新审视这些特性,并尽最大努力以不同的方式融入它们。但是现在,我们可以把它们看作数字特征。
下面的代码片段提取列的数字部分,并将列从字符串转换为数字特征。
#For people ID, we would need to extract values after '_'
df_new.people_id = df_new.people_id.apply(lambda x:x.split("_")[1])
df_new.people_id = pd.to_numeric(df_new.people_id)
#For activity ID also, we would need to extract values after '_'
df_new.activity_id = df_new.activity_id.apply(lambda x:x.split("_")[1])
df_new.activity_id = pd.to_numeric(df_new.activity_id)
#For group_1 , we would need to extract values after "
df_new.group_1 = df_new.group_1.apply(lambda x:x.split("")[1])
df_new.group_1 = pd.to_numeric(df_new.group_1)
#For activity_type , we would need to extract values after "
df_new.activity_type = df_new.activity_type.apply(lambda x:x.split("")[1])
df_new.activity_type = pd.to_numeric(df_new.activity_type)
#Double check the new values in the dataframe
print(df_new[["people_id","activity_type","activity_id","group_1"]].head())
输出
people_id activity_type activity_id group_1
0 100.0 76 1734928.0 17304
1 100.0 1 2434093.0 17304
2 100.0 1 3404049.0 17304
3 100.0 1 3651215.0 17304
4 100.0 1 4109017.0 17304
现在,我们将布尔列转换为数字列,并且将包含大量不同值的分类列也转换为数字列。(注意:这种分类到数字的转换并不总是可行的。)接下来,让我们将剩余的非重复值数量相对较少的分类列转换为一键编码形式,并呈现最终的合并数据集。
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
#Define a function that will intake the raw dataframe and the column name and return a one hot encoded DF
def create_ohe(df, col):
le = LabelEncoder()
a=le.fit_transform(df_new[col]).reshape(-1,1)
ohe = OneHotEncoder(sparse=False)
column_names = [col+ "_"+ str(i) for i in le.classes_]
return(pd.DataFrame(ohe.fit_transform(a),columns =column_names))
#Since the above function converts the column, one at a time
#We create a loop to create the final dataset with all features
target = ["outcome"]
numeric_columns = list(set(temp.index[(temp.DataType =="float64") |
(temp.DataType =="int64")].values) - set(target))
temp = df_new[numeric_columns]
for column in categorical_columns:
temp_df = create_ohe(df_new,column)
temp = pd.concat([temp,temp_df],axis=1)
print("\nShape of final df after onehot encoding:",temp.shape)
输出
Shape of final df after onehot encoding: (2197291, 183)
我们现在已经为模型开发准备好了数据集的最终形式。在本练习中,我们转换并保留了与日期相关的功能,因为它们是数字形式,而不是转换为一键编码形式。这个选项是可选的。我考虑了数据集的大小,大约有 180 列,开始时足够大了。我们将进行一些基本实验,如果我们没有看到良好的性能,我们将需要重新访问数据。在这种情况下,我们需要寻找改进的策略,以便以最节省内存和计算的方式从大量选择的特征中提取最佳信息。
最后,在我们开始模型开发之前,我们需要将我们的数据集分成训练、验证和测试,就像我们在第三章中对回归用例所做的那样。以下代码片段利用 Python 中 sklearn 包的“train_test_split”将前面创建的最终数据集拆分为 train 和 test,然后进一步将 train 划分为 train 和 validation。
from sklearn.model_selection import train_test_split
#split the final dataset into train and test with 80:20
x_train, x_test, y_train, y_test = train_test_split(temp,df_new[target], test_size=0.2,random_state=2018)
#split the train dataset further into train and validation with 90:10
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.1, random_state=2018)
#Check the shape of each new dataset created
print("Shape of x_train:",x_train.shape)
print("Shape of x_test:",x_test.shape)
print("Shape of x_val:",x_val.shape)
print("Shape of y_train:",y_train.shape)
print("Shape of y_test:",y_test.shape)
print("Shape of y_val:",y_val.shape)
输出
Shape of x_train: (1582048, 183)
Shape of x_test: (439459, 183)
Shape of x_val: (175784, 183)
Shape of y_train: (1582048, 1)
Shape of y_test: (439459, 1)
Shape of y_val: (175784, 1)
现在,我们有了构建分类的 DL 模型所需形式的训练数据。我们需要定义一个基线基准,它将帮助我们设置我们应该从模型中期望的阈值性能,以使它们被认为是有用的和可接受的。
定义模型基线准确性
在第三章中,当我们使用回归用例时,我们通过使用训练数据集的平均值作为测试数据集中所有值的最终预测来定义基线精度。然而,在分类用例中,我们需要一个稍微不同的方法。
对于所有监督分类用例,我们的目标变量将是二元或多类(两个以上的类)结果。在我们的用例中,我们的结果是 0 或 1。为了验证模型的有效性,我们应该将结果与如果我们没有模型会发生的情况进行比较。在这种情况下,我们将最大的类作为所有客户的预测,并检查其准确性。
如果您还记得,我们用例中的目标(即结果变量)具有 1 和 0 的良好分布。这里是结果变量在 1 和 0 之间的分布。
#Checking the distribution of values in the target
df_new["outcome"].value_counts()/df_new.shape[0]
Output
0 0.556046
1 0.443954
Name: outcome, dtype: float64
因此,对于前面的分布,我们可以说,如果我们没有任何模型,所有预测都为 0(最大类),也就是说,预测没有一个客户是潜在的高价值客户,那么无论如何,我们都至少有 55.6%的准确性。这是我们的基线精度。如果我们建立一个模型,提供给我们的整体精度低于我们的基准,那么它实际上是没有用的。
设计用于分类的 DNN
对于这个用例,我们有更大的数据集。训练过程可能比回归用例更耗时。为了节省我们的时间,并且能够快速得到一个功能良好的架构,我们将使用一个简单的策略。对于我们将要试验的每种网络,我们将从三个时期开始,一旦我们发现有希望的结果,我们将用期望数量的时期来重新训练最佳架构以获得改进的结果。
首先,让我们遵循我们在第三章中学到的相同的架构开发指南。也就是说,让我们遵循规则 1:从小处着手。
下面的代码片段构建了一个只有一层和 256 个神经元的 DNN。我们使用binary_crossentropy(因为这是一个二进制分类问题)作为损失函数,并使用精确度作为监控的度量。对于分类问题,我们可以使用 Keras 中可用的其他几个指标,但是准确性很简单,也很容易理解。我们将只训练三个时期的网络,并持续监控训练和验证数据集的损失和准确性。如果我们看不到有希望的结果,我们可能不得不尝试新的架构。
from keras.models import Sequential
from keras.layers import Dense
#Design the deep neural network [Small + 1 layer]
model = Sequential()
model.add(Dense(256,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(256,activation="relu"))
model.add(Dense(1,activation = "sigmoid")) #activation = sigmoid for binary classification
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Using TensorFlow backend.
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8505 - acc: 0.4449 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 111s 70us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 110s 69us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
如果您仔细观察来自训练输出的结果,您将会看到训练和验证数据集的总体准确度约为 0.44 (44%),这远远低于我们的基线准确度。因此,我们可以得出结论,进一步训练这个模型可能不是一个富有成效的想法。
让我们为相同数量的神经元尝试一个更深的网络。所以,我们保持一切不变,但增加了一层相同数量的神经元。
#Design the deep neural network [Small + 2 layers]
model = Sequential()
model.add(Dense(256,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(256,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 124s 79us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 125s 79us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 124s 78us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
同样,正如我们所看到的,最初的结果一点也不乐观。来自更深层次网络的训练和验证准确性与我们预期的相差甚远。让我们试着用一个更大(中等规模)的网络来训练,而不是尝试另一个更深层次的网络,比如三到五层。这次我们将使用一个只有一层但有 512 个神经元的新架构。让我们再次训练三个纪元,并查看度量标准,以检查它是否符合我们的预期。
#Design the deep neural network [Medium + 1 layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 113s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 2/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
Epoch 3/3
1582048/1582048 [==============================] - 112s 71us/step - loss: 8.8669 - acc: 0.4438 - val_loss: 8.8394 - val_acc: 0.4455
中型网络也返回了令人失望的结果。中型网络的训练和验证准确性与我们的预期相差甚远。现在,让我们尝试增加中型网络的深度,看看结果是否有所改善。
#Design the deep neural network [Medium + 2 layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train.shape[1],activation="relu"))
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train,y_train, validation_data = (x_val,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 135s 86us/step - loss: 7.1542 - acc: 0.5561 - val_loss: 7.1813 - val_acc: 0.5545
Epoch 2/3
1582048/1582048 [==============================] - 134s 85us/step - loss: 7.1534 - acc: 0.5562 - val_loss: 7.1813 - val_acc: 0.5545
Epoch 3/3
1582048/1582048 [==============================] - 135s 85us/step - loss: 7.1534 - acc: 0.5562 - val_loss: 7.1813 - val_acc: 0.5545
我们可以看到结果有所改善,但只是一点点。我们看到训练和验证数据集的准确率约为 55%,但这些结果也不是很好,尽管比我们之前的结果要好。
重新审视数据
最初试图建立一个具有良好结果的模型的努力已经失败。我们可以进一步增加网络的规模和深度,但这只会略微提高网络性能。如前所述,我们可能不得不考虑改进用于训练的数据。我们有两个主要选择。我们已经在第二章的“输入数据”部分和第三章的“探索数据”部分讨论了这两点。我们可以使用 Python 的 sklearn 包中的工具,使用“Standardscaler”或“Minmaxscaler”对输入数据进行标准化,或者我们可以探索各种选项,重新对我们编码为数值的分类特征进行一次性编码。从这两个选项中,最简单和最省时的是标准化或规范化数据。
标准化、规范化或缩放数据
如果您还记得,在第二章“Keras 中的 DL 入门”下的“输入数据”一节中,我们讨论过在提供数据作为 DL 模型的训练数据之前,将数据标准化或规范化是一个好的做法。我们没有在第三章的回归用例中使用这一选项,因为该模型在常规数据上表现良好。然而,在我们的分类用例中,我们可以看到在原始数据上的性能非常差。为了提高我们的模型性能,让我们尝试标准化我们的数据。(或者,您也可以标准化数据。)
在标准化中,我们将数据转换为均值为 0、标准差为 1 的形式。这种形式的数据分布是我们神经元激活功能的一个很好的输入候选,因此提高了更恰当地学习的能力。
最简单的形式是,标准化可以通过以下使用虚拟输入数据集的示例来解释。我们执行标准缩放;查看转换后的值、平均值及其标准差;最后将输出逆变换为其原始形式。
#Create a dummy input
dummy_input = np.arange(1,10)
print("Dummy Input = ",dummy_input)
from sklearn.preprocessing import StandardScaler
#Create a standardscaler instance and fit the data
scaler = StandardScaler()
output = scaler.fit_transform(dummy_input.reshape(-1,1))
print("Output =\n ",list(output))
print("Output's Mean = ",output.mean())
print("Output's Std Dev = ",output.std())
print("\nAfter Inverse Transforming = \n",list(scaler.inverse_transform(output)))
输出
Dummy Input = [1 2 3 4 5 6 7 8 9]
Output =
[array([-1.54919334]), array([-1.161895]), array([-0.77459667]),
array([-0.38729833]), array([0.]), array([0.38729833]),
array([0.77459667]), array([1.161895]), array([1.54919334])]
Output's Mean = 0.0
Output's Std Dev = 1.0
After Inverse Transforming =
[array([1.]), array([2.]), array([3.]), array([4.]), array([5.]),
array([6.]), array([7.]), array([8.]), array([9.])]
转换输入数据
要转换用于模型开发的输入数据,请注意,我们应该仅使用训练数据来拟合缩放器转换,并使用相同的拟合对象来转换验证和测试输入数据。以下代码片段使用x_train数据集来拟合和转换所有三个数据集的缩放值(即x_train和x_val以及x_test)。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train)
x_train_scaled = scaler.transform(x_train)
x_val_scaled = scaler.transform(x_val)
x_test_scaled = scaler.transform(x_test)
既然我们已经有了标准的缩放数据集,我们就可以为训练提供这些新增加的数据。请注意,我们没有对标签或目标进行任何转换。
用于改进数据分类的 DNNs
现在让我们从一个中等规模的网络开始,看看我们是否能得到改进的结果。我们将从三个时代开始。
from keras import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val), epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 109s 69us/step - loss: 0.2312 - acc: 0.8994 - val_loss: 0.1894 - val_acc: 0.9225
Epoch 2/3
1582048/1582048 [==============================] - 108s 68us/step - loss: 0.1710 - acc: 0.9320 - val_loss: 0.1558 - val_acc: 0.9387
Epoch 3/3
1582048/1582048 [==============================] - 108s 68us/step - loss: 0.1480 - acc: 0.9444 - val_loss: 0.1401 - val_acc: 0.9482
现在,我们走吧!
我们可以看到网络在提供标准化数据集方面的性能有了显著提高。我们在训练和验证数据集上有几乎 95%的准确率。让我们使用这个模型来评估我们之前创建的测试数据集上的模型性能。
result = model.evaluate(x_test_scaled,y_test)
for i in range(len(model.metrics_names)):
print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
输出
439459/439459 [==============================] - 34s 76us/step
Metric loss : 0.1
Metric acc : 0.96
我们在测试数据集上看到了很好的结果。让我们试着改进一下架构,然后看看。我们可以建立一个中等规模的深度网络,看看结果是否比中等规模的网络更好。
#Designing the Deep Neural Network [Medium – 2 Layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(512,activation="relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 131s 83us/step - loss: 0.1953 - acc: 0.9141 - val_loss: 0.1381 - val_acc: 0.9421
Epoch 2/3
1582048/1582048 [==============================] - 130s 82us/step - loss: 0.1168 - acc: 0.9529 - val_loss: 0.1051 - val_acc: 0.9578
Epoch 3/3
1582048/1582048 [==============================] - 131s 83us/step - loss: 0.0911 - acc: 0.9646 - val_loss: 0.0869 - val_acc: 0.9667
训练和验证准确率进一步提高到 96%。这个只有 3 个纪元的小增长是令人敬畏的。我们现在可以对该架构模型的性能充满信心。我们肯定可以尝试更多的架构并检查结果,但让我们用一个更大更深的网络做最后一次尝试,看看 3 个时期的结果。如果我们只看到很小的改进,我们将在 15 个时期使用相同的架构,并使用该模型进行最终预测。
#Designing the network Deep Neural Network – [Large + 2 Layers]
model = Sequential()
model.add(Dense(1024,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(1024,activation = "relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=3, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/3
1582048/1582048 [==============================] - 465s 294us/step - loss: 0.2014 - acc: 0.9099 - val_loss: 0.1438 - val_acc: 0.9390
Epoch 2/3
1582048/1582048 [==============================] - 483s 305us/step - loss: 0.1272 - acc: 0.9469 - val_loss: 0.1184 - val_acc: 0.9524
Epoch 3/3
1582048/1582048 [==============================] - 487s 308us/step - loss: 0.1015 - acc: 0.9593 - val_loss: 0.1011 - val_acc: 0.9605
我们看到验证数据集的总体准确率为 96%,训练数据集也有类似的分数。因此,由于将规模从中等(512 个神经元)增加到更大(1024 个神经元)的架构,模型的性能实际上没有太大的改善。利用这些结果来验证我们的实验,让我们训练一个中等规模(512 个神经元)的两层深度网络 15 个时期,查看最终的训练和验证准确性,然后使用训练好的模型来评估测试数据集。
#Designing the network Deep Neural Network – [Medium + 2 Layers]
model = Sequential()
model.add(Dense(512,input_dim = x_train_scaled.shape[1],activation="relu"))
model.add(Dense(512,activation = "relu"))
model.add(Dense(1,activation = "sigmoid"))
model.compile(optimizer = "Adam",loss="binary_crossentropy",metrics=["accuracy"])
model.fit(x_train_scaled,y_train, validation_data = (x_val_scaled,y_val),epochs=15, batch_size=64)
输出
Train on 1582048 samples, validate on 175784 samples
Epoch 1/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.1949 - acc: 0.9142 - val_loss: 0.1375 - val_acc: 0.9426
Epoch 2/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.1173 - acc: 0.9527 - val_loss: 0.1010 - val_acc: 0.9599
Epoch 3/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0911 - acc: 0.9643 - val_loss: 0.0887 - val_acc: 0.9660
----Skipping output from intermediate epochs -----
Epoch 14/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0402 - acc: 0.9863 - val_loss: 0.0614 - val_acc: 0.9821
Epoch 15/15
1582048/1582048 [==============================] - 133s 84us/step - loss: 0.0394 - acc: 0.9869 - val_loss: 0.0629 - val_acc: 0.9818
具有 512 个神经元和两层的中等规模架构的最终模型在训练和验证数据集上给出了很好的性能结果。我们对两个数据集的准确率都在 98%左右。现在让我们在测试数据集上验证模型性能。
result = model.evaluate(x_test_scaled,y_test)
for i in range(len(model.metrics_names)):
print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
输出
439459/439459 [==============================] - 20s 45us/step
Metric loss : 0.06
Metric acc : 0.98
在看不见的测试数据集上的性能也很好,并且一致。我们的模型在测试数据集上表现得非常好。让我们看看模型的损失曲线,就像我们对回归用例所做的那样。我们将为训练和验证数据集绘制每个历元中的损失(对于该模式总共 15 个)。下面的代码片段利用了模型历史并绘制了这些指标。
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(model.history.history['loss'])
plt.plot(model.history.history['val_loss'])
plt.title("Model's Training & Validation loss across epochs")
plt.ylabel('Loss')
plt.xlabel('Epochs')
plt.legend(['Train', 'Validation'], loc='upper right')
plt.show()
我们可以看到两个数据集的损失都在减少。类似地,让我们看看模型训练期间的准确性度量。训练和验证数据集的准确性度量也存储在模型历史中。
plt.plot(model.history.history['acc'])
plt.plot(model.history.history['val_acc'])
plt.title("Model's Training & Validation Accuracy across epochs")
plt.ylabel('Accuracy')
plt.xlabel('Epochs')
plt.legend(['Train', 'Validation'], loc='upper right')
plt.show()
正如我们所看到的,精确度随着每个时期不断提高。在我们观察到任何指标的训练和验证数据之间存在巨大差距的情况下,这将是模型过度拟合的迹象。在这种情况下,模型在训练数据集上表现很好,但在看不见的数据(即验证和测试数据集)上表现很差。
摘要
在本章中,我们探索了一个业务用例,并通过利用 DNN 进行分类来解决它。我们从理解问题陈述的业务本质开始,探索所提供的数据,并扩充成适合 DNNs 的形式。我们试验了一些架构,牢记我们在第三章中学到的经验法则,我们看到了模型性能的一个主要缺点。然后,我们重新审视了这些数据,并使用标准化技术以一种更加 DL 友好的形式和架构为一些 dnn 表示这些数据,我们看到了惊人的结果。总的来说,我们加强了在数据工程、数据探索、DL 以及 Keras 和 Python 方面的学习。在下一章中,我们将探索通过超参数调整提高模型性能的其他策略,了解迁移学习,并探索大型软件的模型部署的高级流程。
五、调整和部署深度神经网络
到目前为止,在本书的旅程中,我们主要讨论了如何为一个给定的用例开发一个 DNN,并且查看了一些策略和经验法则来绕过我们在这个过程中可能面临的障碍。在这一章中,我们将讨论开发初始模型之后的旅程,探索当开发的模型没有达到您的期望时,您需要实现的方法和途径。我们将讨论正则化和超参数调整,在本章的最后,我们还将对调整后部署模型的过程有一个高层次的了解。然而,我们实际上不会讨论部署的实现细节;这只是一个概述,提供了在这一过程中取得成功的指南。让我们开始吧。
过度拟合的问题
在开发和训练 ML 和 DL 模型的过程中,您经常会遇到这样的情况:经过训练的模型在训练数据集上表现良好,但在测试数据集上表现不佳。在数据科学中,这种现象被称为“过度拟合”。从字面上看,你的模型过度拟合了数据。虽然你在本书的前面已经遇到过这个术语,但是到目前为止我们还没有详细讨论过这个话题。让我们试着用一种更简化的方式来理解这种现象。
训练一个模型的过程叫做“拟合数据”;神经网络学习数据中的潜在模式,并在数学上改进模型权重/结构,以适应它在学习过程中发现的模式。简而言之,训练模型会调整其结构(权重)以适应数据(模式),从而提高其性能。当它发现的模式在现实中仅仅是噪音时,这个美丽的过程就变得复杂了。不幸的是,数学方程并不具有总是区分信号和噪声的能力(对于噪声,我们指的是不代表训练样本但由于随机机会而出现的数据点)。当它失败时,它也学习噪声并调整它的权重以适应新的信号,这实际上是噪声。
为了理解这个过程,我们举一个简单的例子。比如说一个五岁的孩子爱吃妈妈烤的蛋糕。他要求每天在家烤蛋糕。他的母亲礼貌地拒绝了这些要求,但向他保证,她会在某些场合烤蛋糕。小男孩现在期待着每一个新的一天,希望这将是他妈妈烤蛋糕的一个机会。另一方面,他的母亲并不想找机会烤蛋糕。她每周日下班后都会烤一个蛋糕。这个五岁的孩子继续每天观看,慢慢地知道他的妈妈会在每个星期天烤一个蛋糕。所以,他学会了下面的模式:“如果 day == Sunday,那么妈妈会烤蛋糕。”一个晴朗的星期天,他的母亲不得不出差,没有时间烤蛋糕。这个五岁的孩子无法理解他的模式被打破了。因此,为了适应新的事件,他修改了他的规则,制定了新的模式如下:“如果 day == Sunday,那么妈妈将烤一个蛋糕,但如果这一天是在一个月的最后一周,那么没有蛋糕。”事实上,他妈妈错过烤蛋糕的那个星期天是一个噪音。理想情况下,他应该忽略这一点,并保持他之前学到的模式不变。但不幸的是,他未能区分信号和噪音,从而使他的规则过于复杂,数据过于拟合。
类似地,当 DL 模型从噪声中学习并通过调整权重来适应噪声时,它会过度拟合数据。这个问题变得很严重,因为学习噪声会导致模型性能的显著下降。这就是为什么您会观察到模型在训练数据上的性能和在看不见的数据上的性能之间有很大的差距。通过正则化,可以在很大程度上(尽管不是完全)避免这个问题,并定制模型的学习过程,以仅适应信号(或真实模式)而不是噪声。
那么,什么是正规化呢?
简而言之,正则化是一个减少过度拟合的过程。这是一种数学方法,当模型适应噪声时,将警告引入模型的学习过程。为了给出更现实的定义,这是一种在过度拟合的情况下惩罚模型权重的方法。
让我们用一种非常简单的方式来理解这个过程。在 DL 中,神经元连接的权重在每次迭代后更新。当模型遇到有噪声的样本并假设该样本是有效样本时,它会尝试通过更新与噪声一致的权重来适应该模式。在真实的数据样本中,噪声数据点不像任何接近常规数据点的东西;他们离他们很远。因此,权重更新也将与噪声同步(即,权重的变化将是巨大的)。正则化过程将边缘的权重添加到定义的损失函数中,并且整体上表示更高的损失。然后,网络调整自身以减少损失,从而使权重在正确的方向上更新;这是通过忽略噪音而不是在学习过程中适应噪音来实现的。
正则化的过程可以表示为
成本函数= 损失 (如模型定义)+ 超参数×权重】**
*超参数表示为,λ的值由用户定义。
基于如何将权重添加到损失函数,我们有两种不同类型的正则化技术:L1 和 L2。
L1 正则化
在 L1 正则化中,绝对权重被添加到损失函数中。为了使模型更一般化,权重的值被减少到 0,因此当我们试图压缩模型以进行更快的计算时,这种方法是非常优选的。
该等式可以表示为
成本函数=损失(定义)+
在 Keras 中,通过向“内核正则化”参数提供“正则化”对象,可以将 L1 损失添加到图层中。以下代码片段演示了如何将 L1 正则化器添加到 Keras 中的密集图层。
from keras import regularizers
from keras import Sequential
model = Sequential()
model.add(Dense(256, input_dim=128, kernel_regularizer=regularizers.l1(0.01))
值 0.01 是我们为λ设置的超参数值。
L2 正则化
在 L2 正则化中,平方权重被添加到损失函数中。为了使模型更一般化,权重值被减少到接近 0(但实际上不是 0),因此这也被称为“权重衰减”方法。在大多数情况下,L2 比 L1 更能减少过度拟合。
该等式可以表示为
成本函数=损失(定义)+
我们可以像 L1 一样给 DL 模型添加一个 L2 正则化子。以下代码片段演示了如何向密集图层添加 L2 正则化因子。
model = Sequential()
model.add(Dense(256, input_dim=128, kernel_regularizer=regularizers.l2(0.01))
值 0.01 是我们为λ设置的超参数值。
辍学正规化
除了 L1 和 L2 正则化,在 d L 中还有另一种流行的技术来减少过拟合。这种技术使用了一种退出机制。在这种方法中,模型在每次迭代过程中任意删除或停用一层的几个神经元。因此,在每一次迭代中,该模型会查看自身稍有不同的结构进行优化(因为一些神经元和连接会被停用)。假设我们有两个连续的层,H1 和 H2,分别有 15 和 20 个神经元。在这两层之间应用丢弃技术将导致 H1 随机丢弃一些神经元(基于定义的百分比),从而减少 H1 和 H2 之间的连接。这个过程在每次迭代中随机重复,因此如果模型已经学习了一批并更新了权重,下一批可能会有一组完全不同的权重和连接来训练。该过程不仅由于减少的计算而有效,而且在减少过拟合方面直观地起作用,因此提高了整体性能。
使用下图可以直观地理解辍学的概念。我们可以看到,规则网络的所有神经元和两个连续层之间的连接都完好无损。使用 dropout,每次迭代都会通过任意停用或丢弃一些神经元及其关联的权重连接来引入一定程度的随机性。
在 Keras 中,我们可以按照以下约定对层使用 dropout:
keras.layers.Dropout(rate, noise_shape=None, seed=None)
以下代码片段展示了添加到密集隐藏层的 dropout。参数值 0.25 表示退出率(即要退出的神经元的百分比)。
from keras import Sequential
from keras.layers.core import Dropout, Dense
model = Sequential()
model.add(Dense(100, input_dim= 50, activation="relu"))
model.add(Dropout(0.25))
model.add(Dense(1,activation="linear"))
超参数调谐
超参数是定义模型整体结构和学习过程的参数。我们也可以将超参数作为模型的元参数。它不同于模型的实际参数,模型的实际参数是在训练过程中学习的(例如,模型权重)。与模型参数不同,超参数无法学习;我们需要用不同的方法来调优它们,以获得更好的性能。
为了更好地理解这个话题,让我们以一种更简化的方式来看这个定义。当我们设计一个 DNN 时,模型的架构是由一些高级工件定义的。这些工件可以是层中神经元的数量、隐藏层的数量、激活函数、优化器、架构的学习速率、时期的数量、批量大小等等。所有这些参数共同用于设计网络,它们对模型的学习过程及其最终性能有着巨大的影响。这些参数不能被训练;事实上,它们需要用经验和判断来选择,就像我们在第三章学到的规则一样,来决定架构的大小。定义模型整体架构的参数统称为超参数。选择正确的超参数是一个密集和迭代的过程,但随着经验的积累,它会变得更容易。用超参数的不同值进行试验以改进整个模型过程的过程称为模型调整或超参数调整。
DL 中的超参数
让我们看看 DL 模型可用的不同超参数,并研究可供选择的选项。然后,我们将研究为模型选择正确的超参数集的各种方法。
一层中的神经元数量
对于大多数使用表格横截面数据的分类和回归用例,可以通过调整网络的宽度(即一层中神经元的数量)来增强 DNNs 的鲁棒性。通常,选择第一层神经元数量的简单经验法则是参考输入维度的数量。如果给定训练数据集中输入维度的最终数量(这也包括一次性编码的特征)是 x,我们应该至少使用最接近 2x 的 2 次方的数字。假设您的训练数据集中有 100 个输入维度:最好从 2 × 100 = 200 开始,取最接近的 2 的幂,即 256。神经元的数量最好是 2 的幂,因为这有助于网络的计算更快。此外,神经元数量的最佳选择是 8、16、32、64、128、256、512、1024 等等。根据输入尺寸的数量,取最接近尺寸两倍的数字。所以,当你有 300 个输入维度时,试着用 512 个神经元。
层数
的确,仅仅增加几层通常会提高性能,至少是略微提高。但问题是,随着层数的增加,训练时间和计算量显著增加。此外,要看到有希望的结果,您需要更多的历元数。不使用更深的网络并不总是一个选项;在必要的情况下,尝试使用一些最佳实践。
如果您正在使用一个非常大的网络,比方说超过 20 层,请尝试使用一个逐渐变小的架构(即,随着深度的增加,逐渐减少每层中的神经元数量)。因此,如果您使用 30 层架构,每层有 512 个神经元,请尝试慢慢减少层中神经元的数量。一个改进的架构是前 8 层有 512 个神经元,接下来的 8 层有 256 个,接下来的 8 层有 128 个,依此类推。对于最后一个隐藏层(不是输出层),尝试将神经元的数量至少保持在输入大小的 30–40%左右。
或者,如果您使用更宽的网络(即,不减少较低层的神经元数量),请始终使用 L2 正则化或丢弃率约为 30%的丢弃层。过度拟合的机会大大减少。
时代数
有时,仅仅增加模型训练的历元数就能获得更好的结果,尽管这是以增加计算和训练时间为代价的。
重量初始化
初始化网络的权重也会对整体性能产生巨大影响。好的权重初始化技术不仅加速了训练过程,而且避免了模型训练过程中的死锁。默认情况下,Keras 框架使用 glorot 统一初始化,也称为 Xavier 统一初始化,但这可以根据您的需要进行更改。我们可以使用内核初始化参数初始化层的权重,也可以使用偏差初始化参数初始化偏差。
可供选择的其他常用选项有“He Normal”和“He Uniform”初始化以及“lecun normal”和“lecun uniform”初始化。在 Keras 中也有相当多的其他选择,但上述选择是最受欢迎的。
以下代码片段展示了一个使用random_uniform在 DNN 的图层中初始化权重的示例。
from keras import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(64,activation="relu", input_dim = 32, kernel_initializer = "random_uniform",bias_initializer = "zeros"))
model.add(Dense(1,activation="sigmoid"))
批量
使用适度的批量总是有助于实现模型的更平滑的学习过程。在大多数情况下,不管数据集大小和样本数量如何,32 或 64 的批量大小将提供平滑的学习曲线。即使在您的硬件环境具有大 RAM 内存来容纳更大的批处理大小的情况下,我仍然建议保持 32 或 64 的批处理大小。
学习率
学习率是在优化算法的上下文中定义的。它定义了每一步的长度,或者简单地说,在每次迭代中可以对权重进行多大的更新。在本书中,我们忽略了设置或改变学习率,因为我们使用了各自优化算法的默认值,在我们的例子中是 Adam。默认值为 0.001,这是大多数情况下的最佳选择。然而,在一些特殊的情况下,您可能会遇到一个用例,在这个用例中,学习率较低或者稍高可能会更好。
激活功能
我们对神经元的激活功能有很多选择。在大多数情况下,ReLU 可以完美地工作。您几乎总是可以将 ReLU 作为任何用例的激活来进行,并获得良好的结果。在 ReLU 可能不会提供很好的结果的情况下,尝试 PReLU 是一个很好的选择。
最佳化
类似于激活函数,对于网络的优化算法,我们也有相当多的选择。虽然最推荐的是 Adam,但在 Adam 可能无法为您的架构提供最佳结果的情况下,您可以探索 Adamax 以及 Nadam 优化器。对于像单词嵌入这样的参数更新很少的架构来说,Adamax 通常是更好的选择,单词嵌入通常用于自然语言处理技术。我们在书中没有涉及这些高级主题,但是在探索各种体系结构时记住这些要点是有好处的。
超参数调谐方法
到目前为止,我们已经讨论了可用于 DL 模型的各种超参数,并且还研究了针对一般情况的最推荐选项。然而,根据数据和问题类型为超参数选择最合适的值更像是一门艺术。这门艺术也是艰巨而缓慢的。DL 中超参数调整的过程几乎总是很慢并且资源密集。然而,基于为超参数选择值和进一步调整模型性能的方式,我们可以将不同类型的方法大致分为四大类:
-
手动搜索
-
网格搜索
-
随机搜索
-
贝叶斯优化
在上述四种方法中,我们将简要介绍前三种。贝叶斯优化是一个漫长而困难的话题,超出了本书的范围。让我们简单看一下前三种方法。
手动搜索
顾名思义,手动搜索是为 DL 模型中所需的超参数选择最佳候选值的一种完全手动的方式。这种方法需要在训练网络方面有丰富的经验,以便使用最少的实验次数为所有期望的超参数获得正确的候选值集。通常这种方法效率很高,前提是你有使用它们的丰富经验。开始手动搜索的最佳方法是简单地利用给定超参数的所有推荐值,然后开始训练网络。结果可能不是最好的,但绝对不会是最差的。对于该领域的任何新手来说,尝试几个风险最低的超参数候选者都是一个很好的起点。
网格搜索
在网格搜索方法中,您实际上是在为一组定义的超参数值试验所有可能的组合。“网格”这个名称实际上来源于每个超参数所提供值的网格状组合。下面是一个示例视图,展示了逻辑网格如何寻找三个超参数,每个超参数中有三个不同的值。
方法是尝试为每个组合开发一个模型,如前面所示。“x”表示将使用该特定超参数值开发的模型。例如,对于学习率(0.1),垂直列显示将使用不同的优化器和批处理大小值开发的不同模型。类似地,如果您查看超参数“batch-size”= 32 的水平行,该行所有单元格中的“x”表示将使用不同的学习率和优化器值开发的不同模型。因此,在一个只有三个超参数和三个值的网格中,我们正在开发太多的模型。如果我们正在开发相当大的网络并使用更大的训练数据样本,这个过程将会非常漫长。
这种方法的优点是,它为超参数的定义网格提供了最佳模型。然而,缺点是如果你的网格没有很好的选择,你的模型也不会是最好的。简单地假设,研究模型的科学家对哪些模型可能是给定超参数的最佳候选模型有一个合理的想法。
Keras 没有直接提供在模型上执行网格搜索调优的方法。但是,我们可以使用自定义 for 循环和已定义的训练值,或者使用 Keras 提供的 sklearn 包装器将模型打包到 sklearn 类型对象中,然后利用 sklearn 中的网格搜索方法来完成结果。下面的代码片段展示了通过使用虚拟模型的 Keras 包装器从 sklearn 包中使用网格搜索的方法。
from keras import Sequential
from sklearn.model_selection import GridSearchCV
from keras.wrappers.scikit_learn import KerasClassifier
from keras.layers import Dense
import numpy as np
#Generate dummy data for 3 features and 1000 samples
x_train = np.random.random((1000, 3))
#Generate dummy results for 1000 samples: 1 or 0
y_train = np.random.randint(2, size=(1000, 1))
#Create a python function that returns a compiled DNN model
def create_dnn_model():
model = Sequential()
model.add(Dense(12, input_dim=3, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
model.compile(loss='binary_crossentropy', optimizer="adam", metrics=['accuracy'])
return model
#Use Keras wrapper to package the model as an sklearn object
model = KerasClassifier(build_fn=create_dnn_model)
# define the grid search parameters
batch_size = [32,64,128]
epochs = [15, 30, 60]
#Create a list with the parameters
param_grid = {"batch_size":batch_size, "epochs":epochs}
#Invoke the grid search method with the list of hyperparameters
grid_model = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1)
#Train the model
grid_model.fit(x_train, y_train)
#Extract the best model grid search
best_model = grid_model.best_estimator_
随机搜索
网格搜索的一个改进的替代方法是随机搜索。在随机搜索中,我们可以从一个分布中随机选择一个值,而不是从一个定义的数字列表中为超参数选择一个值,如学习率。然而,这仅适用于数值超参数。因此,代替尝试 0.1、0.01 或 0.001 的学习率,它可以从我们用一些属性定义的分布中选择一个随机值作为学习率。该参数现在有更大范围的值可以试验,并且获得更好性能的机会也更大。它通过引入随机性为更好的超参数选择带来机会,克服了人为猜测限定在限定范围内的超参数的最佳值的缺点。在现实中,对于大多数实际情况,随机搜索大多优于网格搜索。
进一步阅读
要探索一些更具体的例子和贝叶斯优化的简要指南,请参考以下内容:
模型部署
现在,我们终于可以讨论一些关于模型部署的重要问题了。我们从学习 Keras 和 DL 开始,用实际的 dnn 进行回归和分类实验,然后讨论调整超参数以提高模型性能。我们现在可以讨论一些在生产环境中部署 DL 模型的指导方针。我想澄清的是,我们实际上不会作为软件工程师学习在生产中部署模型的过程,或者讨论大型企业项目的 DL 软件管道和架构。相反,我们将重点关注在部署实际模型时需要记住的几个重要方面。
裁剪测试数据
在本书的整个过程中,我们已经看到测试数据与训练数据完全同步。在本书和任何 ML/DL 学习指南中,实验总是在模型训练开始前准备好测试数据。我们通常将现有数据分为训练样本和测试样本,然后使用我们这边的测试数据来验证模型的真实性能。这是一个公平的过程,只要你的目标是培训和开发一个模型。一旦你训练好的模型在软件中投入使用,你实际上并不能访问测试数据。为了实际使用该模型,需要以预期的格式定制数据,以便该模型可以预测并返回预测结果。这个过程实际上是艰巨的,需要精心设计生产软件的数据角力和转换管道。
我们用一个例子来理解这个过程。假设您已经设计并开发了一个 DNN,使用监督分类模型来预测信用卡交易是“真实的”还是“欺诈的”。开发模型时,您可以访问客户数据、交易、销售点属性、时间相关属性、地理属性等。所有这些数据点都存在于不同的来源中。对于模型的开发,您将努力从这些不同的来源获取数据,并以统一的形式呈现出来。对于您的实验,这实际上是一次性的工作。实际上,一旦模型投入使用,整个流程的设计方式就需要能够复制给定客户的数据摄取以及来自不同来源的所有其他必要属性,将其统一并转换为模型预测所需的形式,然后进行大规模的推理。想象一下一家大型银行,其中的实时应用程序同时处理全球数千笔交易。从模型中获取用于推理的定制数据需要真正合理的工程原则,以使模型能够无故障地工作。
设置将实时计算查询请求的数据库或群集/节点的设计原则需要考虑您在训练数据集上完成的数据工程和转换,因为每次使用模型进行预测时都需要执行完全相同的过程。这种动态定制数据以做出推论的过程本身是一种完全不同的艺术,需要精心的工程来构建。通常,数据科学家最不担心这部分问题。我们认为这是软件和数据工程师的工作,我们可以不去管它。这个神话最终会被打破,因为需要建立一个严肃的和谐来解决这个难题。这两个团队,即数据科学家和软件工程师,需要携手合作来完成这项任务。一个数据科学家在理解软件工程师的需求时所面临的困难,反之亦然,这导致了一个新的行业角色 ML engineer 的出现。一个 ML 工程师是一个对这两个领域的交叉有很好理解的候选人。
将模型保存到内存中
在本章的过程中,我们没有讨论的另一个有用的点是将模型作为文件保存到内存中,并在其他时间点重用它。这在 DL 中变得极其重要的原因是训练大型模型所消耗的时间。当你遇到连续几周在超级计算机上训练模型的 DL 工程师时,你不应该感到惊讶。包含图像、音频和非结构化文本数据的现代 DL 模型耗费了大量的训练时间。在这种情况下,一个方便的做法是能够暂停和恢复 DL 模型的训练,并保存中间结果,以便在某个时间点之前执行的训练不会浪费。这可以通过一个简单的回调(Keras 中的一个过程,可以在训练的不同阶段应用于模型)来实现,回调会在定义的里程碑之后将模型的权重与模型结构一起保存到一个文件中。这个保存的模型可以在以后您想要恢复训练时再次导入。这个过程就像你希望的那样继续。我们需要做的就是在一个时期之后或者当我们有了最佳模型时,保存模型结构以及权重。Keras 提供了在每个时期后保存模型或在多个时期的训练期间保存最佳模型的能力。
下面的代码片段显示了在大量时期的训练期间保存模型的最佳权重的示例。
from keras.callbacks import ModelCheckpoint
filepath = "ModelWeights-{epoch:.2f}-{val_acc:.2f}.hdf5"
checkpoint = ModelCheckpoint(filepath, save_best_only=True, monitor="val_acc")
model.fit(x_train, y_train, callbacks=[checkpoint],epochs=100, batch_size=64)
正如您在这个代码片段中看到的,我们用期望的参数定义了一个callbacks对象。我们定义何时保存模型,度量什么指标,以及在哪里保存模型。文件路径使用命名约定,将模型权重存储到文件中,文件名描述纪元编号和相应的精度度量。最后,callbacks对象作为列表传递给模型拟合方法。
或者,您也可以在完成训练后使用save_model方法保存整个模型,然后使用load_model方法将其加载到内存中(可能是第二天)。下面的代码片段显示了一个示例。
from keras.models import load_model
#Train a model for defined number of epochs
model.fit(x_train, y_train, epochs=100, batch_size=64)
# Saves the entire model into a file named as 'dnn_model.h5'
model.save('dnn_model.h5')
# Later, (maybe another day), you can load the trained model for prediction.
model = load_model('dnn_model.h5')
用新数据重新训练模型
当您将模型部署到生产中时,生态系统将继续生成更多数据,这些数据可用于再次训练您的模型。比方说,对于信用卡欺诈用例,您使用 10 万个样本训练您的模型,并获得了 93%的准确率。您觉得性能足够好,可以开始使用,所以您将模型部署到生产中。在一个月的时间内,可以从客户的新交易中获得额外的 10K 样本。现在,您会希望您的模型利用这些新的可用数据,并进一步提高其性能。要实现这一点,你不需要重新训练整个模型;你可以使用暂停再继续的方法。您所需要做的就是使用已经训练好的模型的权重,并提供几个时期的附加数据来传递和迭代新样本。它已经学会的权重不需要被处理掉;您可以简单地使用暂停-恢复公式,继续处理增量数据。
在线模型
在理解了重新训练模型的过程之后,您可能会思考的一个直接问题是,您应该多长时间进行一次这样的训练:每天、每周还是每月进行一次重新训练是一个好的方法吗?正确的答案是你想多频繁就多频繁。只要所需的计算不是瓶颈,每次有新的数据点可用时,递增地训练您的模型是无害的。一个好的做法是,一旦有新的一批样本可用,就迭代一个训练实例。因此,如果您将batch_size设置为 64,您可以自动进行模型训练,以获取最新可用的一批数据,并通过自动执行软件基础架构来为每一批新的数据样本训练模型,从而进一步提高未来预测的性能。将模型性能保持在最佳状态的一个非常积极的方法是,用每个新的数据点进行增量训练,并添加以前的样本作为该批次的剩余样本。这种方法计算量极大,而且回报也较少。这种对每个新样本而不是一批样本进行超实时和增量训练的方法通常不被推荐。
这种模型被称为在线模型,当新的一批数据可用时,它总是在学习。最受欢迎的在线模型的例子可以在你的手机上看到。像预测文本和自动更正这样的功能会随着时间的推移而显著改进。如果你通常以一种特定的风格打字,比如结合两种语言或者缩短几个单词或者使用俚语等等,你会注意到手机非常积极地倾向于适应你的风格。这纯粹是因为手机的操作系统在后台启动了在线模型不断学习和改进的机制。
将您的模型作为 API 交付
如今,将模型作为服务交付给更大的软件堆栈的最佳实践是将其作为 API 交付。这是非常有用和有效的,因为它完全摆脱了技术栈的需求。您的模型可以轻松地在软件生态系统中各种复杂的组件之间进行协作,您可以更少地担心用于开发模型的语言或框架。通常,当您开发 ML 或 DL 模型时,交付模型的选择仅仅由两个简单的点驱动:
- 用软件工程师理解的语言构建模型
或者
- 使用 API
虽然 Python 和 Keras 在今天的现代技术堆栈中几乎是通用的,但我们仍然可以期待一些例外情况,在这些情况下,这种选择可能不是集成的简单选择。因此,我们总是可以选择 API 作为 DL 模型的首选部署模式,并适当地定义数据需求和 API 的调用方式。
有两个非常有用且易于操作的选项可以将您的服务部署为 API。您可以使用 Flask(一种轻量级 Python web 框架)或 Amazon Sagemaker(在 AWS 上可用)。还有其他的选择,我鼓励你去探索它们。Keras 博客上有一篇关于使用 Flask 部署 DL 模型的文章写得非常好。
你可以在这里了解更多: https://blog.keras.io/building-a-simple-keras-deep-learning-rest-api.html 。
此外,您可以在这里通过几个步骤探索如何使用 AWS Sagemaker 将您的模型部署为 API:https://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-hosting.html。
把所有的拼图拼在一起
最后,我们可以把上一节学到的所有这些小组件收集起来,放入一个简单的(小)架构中,如下图所示。
对于生产你的模型来说,这绝对是一个过于简单的解释;我建议您以一种更合适的方式探索您的用例的改进架构。当您的规模、数据量、安全性和可用性达到更高水平时,很多事情都会发生变化。前面的视觉展示了一个适用于小型软件的架构。一旦您完成了模型构建过程,您就可以设置您的大部分逻辑来预测、定制数据、定期测量性能、自动化在线学习、日志记录等,并将其放入一个小 Flask 应用程序中,在服务器上运行,并将其作为 API 进行部署。软件客户端可以是 web 客户端或运行在同一服务器上的另一个服务,它可以通过调用定义格式的 API 来利用该模型。这种架构适用于小型概念验证(POC ),不建议用于生产企业应用程序。讨论 DL 模型的大规模部署、动态定制数据的艺术、支持在线学习以及扩展整个服务基本上还需要几本书。
摘要
在这一章中,我们讨论了当模型性能与您的期望不一致时可以期待的方法和策略。简而言之,我们研究了当您的 DL 模型运行不佳时的整合方法。我们讨论了正则化和超参数调整,还探索了可以用来调整超参数并获得改进模型的不同策略。最后,我们讨论了在部署模型时需要解决的几个原则。我们对模型预测的数据裁剪过程进行了概述,了解了如何使用暂停-恢复方法训练模型,并研究了在线模型和重新训练它们的方法。最后,我们还查看了可用于部署模型的选项,并研究了使用 Flask 部署模型的小型架构。*
六、前方的路
本快速入门指南旨在让您以最快且最有效的方式熟悉使用 Keras 的 DL 领域。我希望你在这次旅行中过得愉快。在这最后一章中,我们将简要地看一下前方的道路。我们将尝试回答以下问题:对于一名数据科学家来说,在 DL 旅程中取得成功还有哪些重要的额外主题?
让我们开始吧。
DL 专家的下一步是什么?
我们已经用分类和回归的 DNNs 介绍了 DL 中的基础知识。最有趣的部分,事实上也是 DL 在 2012 年获得人气和发展势头的主要原因,是计算机视觉领域的 DL。几年前,设计一种算法来帮助计算机理解图像几乎是不可能的。使用算法从图像中提取含义或将图像归类到特定类别的想法是不可想象的。随着时间的推移,ML 变得流行起来,在图像中使用手工制作的特征,然后使用分类器来训练算法的方法展示了改进的结果,但这不是我们想要的结果。2012 年,Alexnet(由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 开发的架构)被用于参加“ImageNet 大规模视觉识别挑战赛”。这是一场开发算法的比赛,该算法可以学习和预测如何将图像分类到一组定义的类别中。Alexnet 取得了 15.3%的前五名误差;这比之前的最好成绩低了近 11%,创下了挑战赛的历史纪录。该建筑是一种 DNN 建筑,专门用于图像分类。就在那时,DL 受到了关注,并立即成为研究的热门话题。DL 的旅程从此一飞冲天。随着对 DL 的更多研究和实验,这个领域扩展到了视频、音频、文本和几乎任何形式的数据。如今,DL 无处不在。几乎每一个主要的技术公司都在它的整个产品堆栈中采用了 DL。
作为一个 DL 爱好者,探索高级 DL 主题的一小步是首先从计算机视觉的 DL 开始。这是您将探索卷积神经网络(CNN)的地方。
美国有线新闻网;卷积神经网络
CNN 是一类用于计算机视觉用例的 DL 算法,例如对图像或视频进行分类,并检测图像中的对象,甚至图像中的区域。CNN 算法是计算机视觉领域的一个巨大突破,因为与当时其他流行的技术相比,它只需要最少的图像处理,并且表现得非常好。CNN 对图像分类的性能改进是惊人的。构建 CNN 的过程在 Keras 中也得到了简化,所有的逻辑组件都被巧妙地抽象出来。Keras 提供了 CNN 层,开发 CNN 模型的整体过程与我们在开发回归和分类模型时所学的非常相似。
为了简单地理解这个过程,我们将使用一个小例子来说明它的实现。以下代码片段展示了 CNN 的“hello world”等效实现。我们将使用 MNIST 数据(即带有手写数字的图像集合)。目标是将图像分类为[0,1,2,3,4,5,6,7,8,9]中的一个数字。Keras 数据集模块中已经提供了这些数据。尽管这个主题是全新的,但是代码片段中的注释将为您提供模型设计的基本概念。
#Importing the necessary packages
import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
#Importing the CNN related layers as described in Chapter 2
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.utils import np_utils
#Loading data from Keras datasets
(x_train, y_train), (x_test, y_test) = mnist.load_data()
#Defining the height and weight and number of samples
#Each Image is a 28 x 28 with 1 channel matrix
training_samples, height, width = x_train.shape
testing_samples,_,_ = x_test.shape
print("Training Samples:",training_samples)
print("Testing Samples:",testing_samples)
print("Height: "+str(height)+" x Width:"+ str(width))
输出
Training Samples: 60000
Testing Samples: 10000
Height: 28 x Width:28
该法典继续规定:
#Lets have a look at a sample image in the training data
plt.imshow(x_train[0],cmap='gray', interpolation="none")
#We now have to engineer the image data into the right form
#For CNN, we would need the data in Height x Width X Channels form Since the image is in grayscale, we will use channel = 1
channel =1
x_train = x_train.reshape(training_samples, height, width,channel).astype('float32')
x_test = x_test.reshape(testing_samples, height, width, channel).astype('float32')
#To improve the training process, we would need to standardize or normalize the values We can achieve this using a simple
divide by 256 for all values
x_train = x_train/255
x_test =x_test/255
#Total number of digits =10
target_classes = 10
# numbers 0-9, so ten classes
n_classes = 10
# convert integer labels into one-hot vectors
y_train = np_utils.to_categorical(y_train, n_classes)
y_test = np_utils.to_categorical(y_test, n_classes)
#Designing the CNN Model
model = Sequential()
model.add(Conv2D(64, (5, 5), input_shape=(height,width ,1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dense(n_classes, activation="softmax"))
# Compile model
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
# Fit the model
model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=10, batch_size=200)
输出
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
60000/60000 [==============================] - 61s 1ms/step - loss: 0.2452 - acc: 0.9266 - val_loss: 0.0627 - val_acc: 0.9806
Epoch 2/10
60000/60000 [==============================] - 64s 1ms/step - loss: 0.0651 - acc: 0.9804 - val_loss: 0.0414 - val_acc: 0.9860
Epoch 3/10
60000/60000 [==============================] - 62s 1ms/step - loss: 0.0457 - acc: 0.9858 - val_loss: 0.0274 - val_acc: 0.9912
--- Skipping intermediate output----
Epoch 9/10
60000/60000 [==============================] - 58s 963us/step - loss: 0.0172 - acc: 0.9943 - val_loss: 0.0284 - val_acc: 0.9904
Epoch 10/10
60000/60000 [==============================] - 56s 930us/step - loss: 0.0149 - acc: 0.9949 - val_loss: 0.0204 - val_acc: 0.9936
最后,让我们评估模型性能:
metrics = model.evaluate(x_test, y_test, verbose=0)
for i in range(0,len(model.metrics_names)):
print(str(model.metrics_names[i])+" = "+str(metrics[i]))
输出
loss = 0.02039033946258933
acc = 0.9936
我们可以看到,我们在测试数据集上的总体准确率约为 99%。这是一个相当简单的例子。当图像的大小和要预测的类的数量增加时,复杂性就出现了。
为了对 CNN 的工作有一个高层次的理解,你可以参考一些有趣的博客:
为了进行更多的实验并研究一些非常酷且简单易懂的例子,您可以查看一些流行的 git 库,以了解与 CNN 相关的用例。
以下是一些例子:
RNN
在探索了 CNN 之后,DL 的下一步是开始探索 RNN,俗称“序列模型”这个名字变得流行是因为 RNN 利用了顺序信息。到目前为止,我们研究的所有 dnn 都在假设任何两个训练样本之间没有关系的情况下处理训练数据。然而,这是我们可以使用数据解决的许多问题中的一个问题。考虑你的 iOS 或 Android 手机中的预测文本功能;下一个单词的预测高度依赖于您已经键入的最后几个单词。这就是顺序模型发挥作用的地方。RNNs 也可以理解为有记忆的神经网络。它将一个层连接到自身,从而可以同时访问两个或多个连续的输入样本,以处理最终输出。这一特性是 RNN 独有的,随着其研究的兴起,它在自然语言理解领域取得了惊人的成功。所有遗留的自然语言处理技术现在都可以用 RNNs 得到显著的改进。聊天机器人的兴起,短信中改进的自动更正,电子邮件客户端和其他应用程序中的建议回复,以及机器翻译(即,将文本从源语言翻译成目标语言,谷歌翻译是典型的例子)都是随着 RNN 的采用而推动的。还有不同类型的 LSTM(长短期记忆)网络,它们克服了现有 RNN 架构中的限制,并将自然语言处理相关任务的性能提升了一个档次。RNN 最流行的版本是 LSTM 和 GRU(门控循环单元)网络。
与我们为 CNN 所做的类似,我们将看一下 RNN/LSTM 网络的一个简单(hello world 等效)示例实现。以下代码片段对 Keras 中的 IMDB reviews 数据集执行二进制分类。这是一个用例,其中我们提供了用户评论(文本日期)和相关的积极或消极的结果。
#Import the necessary packages
from keras.datasets import imdb
from keras.models import Sequential
from keras.layers import Dense, LSTM
from keras.layers.embeddings import Embedding
from keras.preprocessing import sequence
#Setting a max cap for the number of distinct words
top_words = 5000
#Loading the training and test data from keras datasets
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=top_words)
#Since the length of each text will be varying
#We will pad the sequences (i.e. text) to get a uniform length throughout
max_text_length = 500
x_train = sequence.pad_sequences(x_train, maxlen=max_text_length)
x_test = sequence.pad_sequences(x_test, maxlen=max_text_length)
#Design the network
embedding_length = 32
model = Sequential()
model.add(Embedding(top_words, embedding_length, input_length=max_text_length))
model.add(LSTM(100))
model.add(Dense(1, activation="sigmoid"))
#Compile the model
model.compile(loss='binary_crossentropy', optimizer="adam", metrics=['accuracy'])
#Fit the model
model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=3, batch_size=64)
输出
Train on 25000 samples, validate on 25000 samples
Epoch 1/3
25000/25000 [==============================] - 222s 9ms/step - loss: 0.5108 - acc: 0.7601 - val_loss: 0.3946 - val_acc: 0.8272
Epoch 2/3
25000/25000 [==============================] - 217s 9ms/step - loss: 0.3241 - acc: 0.8707 - val_loss: 0.3489 - val_acc: 0.8517
Epoch 3/3
25000/25000 [==============================] - 214s 9ms/step - loss: 0.3044 - acc: 0.8730 - val_loss: 0.5213 - val_acc: 0.7358
评估测试数据集的准确性:
scores = model.evaluate(x_test, y_test, verbose=0)
print("Accuracy:",scores[1])
输出
Accuracy: 0.73584
准确度随着用于训练和改进的架构的时期数量的增加而提高。要全面了解 RNN 是如何运作的,你可以浏览几个博客:
为了进行更多的实验和研究一些非常酷的例子,您可以查看一些流行的 git 库,以了解与 LSTM 相关的用例。以下是一些例子:
-
https://github.com/danielefranceschi/lstm-climatological-time-series -
https://github.com/shashankbhatt/Keras-LSTM-Sentiment-Classification
CNN + RNN
另一个有趣的探索领域是有线电视新闻网和 RNN 的交集。听起来很困惑?想象一下,你可以结合 CNN(即理解图像)和 RNN(即理解自然文本)的力量;交集或组合会是什么样子?你可以用文字描述一幅画。没错,通过将 RNN 和 CNN 结合在一起,我们可以帮助计算机用自然风格的文本描述图像。这个过程被称为图像字幕。今天,如果你在 google.com 搜索类似“黄色汽车”的查询,你的结果实际上会返回一吨黄色汽车。如果你认为这些图片的说明是由人类完成的,然后可以被搜索引擎索引,那你就大错特错了。对于人类来说,我们无法将为图像添加字幕的过程扩展到每天数十亿张图像。这个过程是不可行的。你需要一个更聪明的方法来做到这一点。CNN+RNN 的图像字幕不仅在搜索引擎的图像搜索方面带来了突破,而且在我们日常生活中使用的其他一些产品上也带来了突破。RNN 和 CNN 的交集带给人类的最重要和最具革命性的成果是智能眼镜(百度称之为 duLight):一种配备在老花镜上的摄像头,可以描述周围的环境。对于视力受损的人来说,这是一个很棒的产品。今天,我们有一个较小的版本,在几个应用程序中实现,可以安装在手机上,并与手机摄像头配合工作。如果你有兴趣阅读更多,你可以浏览以下博客:
-
https://towardsdatascience.com/image-captioning-in-deep-learning-9cd23fb4d8d2 -
https://machinelearningmastery.com/introduction-neural-machine-translation/ -
https://towardsdatascience.com/neural-machine-translation-with-python-c2f0a34f7dd
展示图像标题的例子超出了本书的范围。但是,这里有几个 github 存储库,您可以开始探索:
DL 为什么需要 GPU?
在探索第二章设置的环境时,我们偶然发现为 GPU 安装了 TensorFlow。我相信你已经听说了很多关于 GPU 用于 DL 和 NVIDIA 等公司推出专门为 DL 设计的 GPU。一般来说,任何人首先会问的问题是 GPU 与 DL 有什么关系。我们将尝试立即获得这个问题和其他问题的答案。
鉴于您在本指南中对 DL 的了解,我假设您已经意识到 DL 是计算密集型的。为特定任务训练模型确实需要大量的 CPU 能力和时间。如果你更深入一步,并试图理解在一个 DL 模型的训练过程中实际发生了什么,它将归结为一个简单的任务(即矩阵乘法)。你有张量形式的输入数据(比如三维矩阵),测试数据也有类似的形式,神经元连接的权重也以矩阵形式存储,事实上,任何形式的 DNN 的一切,比如 CNN、RNN、DNN 或所有这些的组合,在内部很大程度上都表示为不同维度的矩阵。具有反向传播的学习过程也通过矩阵乘法来执行。
有趣的是,矩阵乘法很大一部分可以并行处理。因此,为了加快训练过程,您的 CPU 中的核心数量可以进一步提高模型所需的训练时间。不幸的是,虽然 CPU 实现的并行处理水平很高,但还不是最好的。特别是对于大型矩阵乘法,这个过程并不像我们希望的那样有效。
但是,我们有市场上已经有的 GPU。使用 GPU 的主要目的是为了增强视频性能(即更高的屏幕刷新率)。一般来说,你的笔记本电脑或计算机的屏幕是一个确定大小的图像,比如 1920 × 1080 像素。这个图像也是一个大小为 1920 × 1080 × 3 的三维矩阵。第三维表示颜色通道“RGB”。因此,简而言之,你在屏幕上看到的任何时间点都是使用 1920 × 1080 × 3 矩阵显示的图像。当这个矩阵每秒刷新(计算)30 次时,它就变成了一个平滑的视频,你可以看到物体没有延迟地移动。因此,为了在屏幕上显示一秒钟,计算机内部计算 1920 × 1080 × 3 矩阵的值至少 30 次。那是相当多的计算。此外,当您玩游戏或执行任何需要高端图形的任务(如视频编辑或在 Photoshop 中设计图像等任务)时,刷新率需要大幅提高。一个好的估计是每秒 60 的屏幕刷新率,而不是每秒 30。现在,为了显示如此高的图形内容,CPU 上有不寻常的额外负载,它可能无法提供所需的性能。为了解决这个问题,我们有专门设计用于渲染高端图形的 GPU,帮助计算机处理刷新屏幕 60 次所需的计算。GPU 承担处理屏幕刷新计算的全部责任。这种处理是通过大规模并行处理完成的。我们在普通笔记本电脑中使用的现代 GPU 至少有 400 个内核,而台式机上的 GPU 要强大得多。这些内核有助于大规模并行处理,以高刷新率显示高清图形内容。
碰巧的是,同样的技术可以用来解决我们在数字图书馆面临的问题。对矩阵进行大规模并行处理以在屏幕上呈现平滑的图形内容可以替代地用于处理 DL 模型训练过程中的计算。在那一刻之后,NVIDIA 开发了 CUDA,这是一个为 GPU 创建的并行处理接口模型。它允许开发人员使用支持 CUDA 的图形处理单元进行通用处理。这项技术为训练 DL 模型带来了巨大的突破。用数字来描述,一个模型在我的笔记本电脑上用 CPU 训练了 40 分钟,用 GPU 训练了 2 分钟。几乎快了 20 倍。您可以想象使用更强大的 GPU 我们可以实现什么。今天,大多数 DL 库都支持 GPU。一旦您为您的 GPU 安装并设置了 CUDA 驱动程序,并安装了一个 GPU 兼容的 DL 库,您就一切就绪了。剩下的就完全抽象给你了。你所需要做的就是以通常的方式训练模型,框架会无缝地使用来自 GPU 和 CPU 的资源。
同样的过程也可以用其他厂商的 GPU 实现,比如 AMD 用 OpenGL。但 NVIDAs GPUs 要优越得多,至少领先其他任何竞争对手五年。如果你打算投资硬件来研究 DL,我强烈建议你购买一台配备兼容 NVIDIA CUDA 的 GPU 的笔记本电脑或台式机(首选)。你将在实验中节省大量的时间。
DL (GAN)中的其他热点区域
我们为您探索了掌握高级 DL 主题的前进道路。但是如果不讨论 DL 中最热门的研究领域,这个讨论将是不完整的。我们将简要讨论生成性对抗网络(GANs ),尽管还有更多。
gan 处于 DL 中断的最前沿,最近一直是一个活跃的研究课题。简而言之,GAN 允许网络从代表现实世界实体(比如,猫或狗;当我们简单地开发 DL 模型以在猫和狗之间进行分类,然后使用它在该过程中学习到的相同特征来生成新图像时;也就是说,它可以生成一个看起来(几乎)真实的猫的新图像,并且与您为训练提供的图像集完全不同。我们可以将 GAN 的整个解释简化为一个简单的任务(即图像生成)。如果训练时间和训练时提供的样本图像足够大,它可以学习一个网络,该网络可以生成与训练时提供的图像不相同的新图像;它会生成新的图像。
如果你想知道图像生成的应用,有一个直到最近才被想到的全新的可能性。之前,大多数 DL 模型只有推理(相对容易)和勉强生成(非常难)。如果你看一看蒙娜丽莎,很容易把它归类为一幅女人的画,但是要做出一幅真的很难。然而,如果有可能这样做,那么就可以开发出全新一代的应用程序。给你一个很好的例子,印度在线时尚零售商 Myntra 使用 GAN 来创建新的 t 恤设计。它用一系列 t 恤设计训练 GAN 网络,然后该模型生成新的设计。在系统生成的 100 个新设计中,即使有 50 个被认为是他们可以制造的好设计,这个领域的奇迹也将是无穷无尽的。同样的想法可以扩展到任何其他领域。在上一节中,我们讨论了图像字幕(即从图像中生成类似描述的自然文本)。这已经是一个很酷的应用程序了,现在想想相反的情况;想象一下,向一个系统提供一个自然的文本描述,然后它生成一张图片作为回报。这个想法听起来太超前了,但是我们已经非常接近这种可能性了。想象一下,你在路上看到一个罪犯,警察需要你帮忙画出他的脸,以便进一步调查;对于未来的 GAN 系统,我们可以想象一个系统,你描述罪犯面部的细节,系统为你绘制面部草图。GAN 的应用过于超前,但研究仍在进行中。到目前为止,研究人员设计的 GAN 网络能够以高清晰度呈现/生成图像,并且在该领域中有持续的实验和研究来开发能够生成高清晰度视频的 GAN 网络。
您可以在此阅读更多关于 GAN 及其应用的信息:
总结想法
这一章的目的是强调数字图书馆领域是多么有前途,现在是开始学习它的基础的好时机。我希望您现在对该领域的高级主题以及您可以立即采取的进一步探索 DL 前沿的下一步有一个公平的想法。这本书旨在以最快但最有效的方式帮助您入门,作为使用 DNNs 的现代 DL 的入门指南。
我们从 DL 主题的简单介绍开始了本指南,并理解了它的基本原理和与市场上流行词汇的区别。我们研究了使用框架开发 DL 模型的必要性,探索了当今市场上几种流行的选择,并理解了为什么 Keras 最有可能成为初学者的首选框架。在后面的章节中,我们通过研究 Keras 框架提供的逻辑抽象和在 DL 生态系统中以小而渐进的步骤映射它的对等物,探索了 Keras 框架,然后将所有的知识与分类和回归中的两个以业务为中心的基本用例结合在一起。然后,我们研究了设计网络的技巧和诀窍,在难以入门的情况下的一些变通方法,以及通过正则化和超参数优化进行模型调整的过程。我们还研究了在生产中部署 DL 模型时应该遵守的一些准则,并最终通过 CNN、、CNN+RNN 以及 DL 中最热门的研究领域(即 GAN)对 DL 中的高级产品进行了初步了解。
我非常享受以加速模式交付本指南内容的过程,我希望你也喜欢这个旅程。现在是结束的时候了,祝你们在 DL 的旅程中好运。我希望你们在发展 DL 技能的过程中有一个非常快乐和愉快的学习之路。