大家好,我技术人Howzit,这是深度学习入门系列第六篇,欢迎大家一起交流!
深度学习入门系列1:多层感知器概述
深度学习入门系列2:用TensorFlow构建你的第一个神经网络
深度学习入门系列3:深度学习模型的性能评价方法
深度学习入门系列4:用scikit-learn找到最好的模型
深度学习入门系列5项目实战:用深度学习识别鸢尾花种类
深度学习入门系列6项目实战:声纳回声识别
深度学习入门系列7项目实战:波士顿房屋价格回归
深度学习入门系列8:用序列化保存模型便于继续训练
深度学习入门系列9:用检查点保存训练期间最好的模型
深度学习入门系列10:从绘制记录中理解训练期间的模型行为
深度学习入门系列11:用Dropout正则减少过拟合
深度学习入门系列12:使用学习规划来提升性能
深度学习入门系列13:卷积神经网络概述
深度学习入门系列14:项目实战:基于CNN的手写数字识别
深度学习入门系列15:用图像增强改善模型性能
深度学习入门系列16:项目实战:图像中目标识别
深度学习入门系列17:项目实战:从电影评论预测情感
深度学习入门系列18:递归神经网络概述
深度学习入门系列19:基于窗口(window)的多层感知器解决时序问题
深度学习入门系列20:LSTM循环神经网络解决国际航空乘客预测问题
深度学习入门系列21:项目:用LSTM+CNN对电影评论分类
深度学习入门系列22:从猜字母游戏中理解有状态的LSTM递归神经网络
深度学习入门系列23:项目:用爱丽丝梦游仙境生成文本
让我们开始
我们将要在这个教程中使用的数据集是声呐数据集。这个数据集描述了不同曲面的声呐回声。60个输入变量是不同角度的回声强度。这个二分类问题需要模型从金属圆柱中区分岩石。
这个数据集很好理解。所有的变量都是连续的而且一般在0-1之间。输出变量M代表矿物质,R代表岩石,这需要将它们转化为1和0。数据集包含208个观察对象。这本书的源码中也提供了这个数据集,或者你可以下载数据集,并以sonar.csv名字放在你的工作目录中。
使用这个数据集的好处是它是个标准化的基准问题。这意味着我们知道一个好的模型所期望的技巧是什么。使用交叉验证情况下,一个神经网络模型能够获得大约84%的准确率,对于自定义模型精度上限大概在88%。你可以在UCI机器学习仓库上了解更多关于这个数据集的信息。
对于这个问题,让我们创建基准模型和结果。我们从导入我们需要的类和函数开始。
import numpy
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
接下来,我初始化随机数生成器,以确保当我们执行这个代码时总能得到相同的结果。
# fix random seed for reproducibility
seed = 7 numpy.random.seed(seed)
现在,我们使用pandas加载数据并把列划分为60输入变量(X)和1个输出变量(Y)。我们使用Pandas加载数据,因为它很容易处理字符串(输出变量),然而尝试使用NumPy直接加载数据比较困难。
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input and output variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
输出变量是字符串,我们必须把他们转成整型。我们使用scikit-learn 中 LabelEnCoder类进行处理。这个类通过 fit() 函数建立需要的编码模型,然后使用 transform() 函数把编码模型应用到创建的新输出变量上。
# encode class values as integers
encoder = LabelEncoder() encoder.fit(Y)
encoded_Y = encoder.transform(Y)
现在我们准备使用Keras创建神经网络模型。我们使用scikit-learn库评价使用了多层K折交叉验证的模型。这是个重采样技术,它将提供了模型性能的估计。为了使用scikit-learn中的Keras模型,我们必须使用KerasClassifier封装器。这个类入参是我们创建和返回神经网络模型的函数。它将以fit()函数中参数顺序传入参数,如迭代次数和批量大小。让我们从定义基准模型函数开始。我们有一个单层模型的隐藏层,它的神经元数量和输出层一样多的神经元。当在新问题上创建神经网络时,这将是一个好的默认起始点。
使用小的高斯随机数来初始化权重。使用整流器激活函数。为了做预测,输出层只含有一个神经元,并使用sigmoid激活函数,为了得到0-1之间的输出概率,又能轻松自动转成清晰的类值。最后,训练期间,我们使用对数损失函数(binary_crossentropy),对于二分类问题它是首选损失函数。模型的梯度下降算法采用Adam优化算法并在训练时收集了精度。
# baseline model
def create_baseline():
# create model
model = Sequential()
model.add(Dense(60, input_dim=60, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
现在是时候使用scikit-learn框架中的多层交叉验证来评估模型了。我们把训练迭代次数传到KerasClassifier中,还可以使用默认值。鉴于10-折交叉验证,模型被创建10次,详细输出被关闭了。
# evaluate model with standardized dataset
estimator = KerasClassifier(build_fn=create_baseline, epochs=100, batch_size=5, verbose=0) kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed)
results = cross_val_score(estimator, X, encoded_Y, cv=kfold)
print("Baseline: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
为了完整性,下面提供所有列表的代码。
# Binary Classification with Sonar Dataset: Baseline
import numpy
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)
# baseline model
def create_baseline():
# create model
model = Sequential()
model.add(Dense(60, input_dim=60, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# evaluate model with standardized dataset
estimator = KerasClassifier(build_fn=create_baseline, epochs=100, batch_size=5, verbose=0)
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed)
results = cross_val_score(estimator, X, encoded_Y, cv=kfold)
print("Baseline: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
运行这段代码,得到下面的结果,显示了在未知数据评估模型精度的平均值和标准差。
Baseline: 81.68% (7.26%)
在没有做任何努力的情况下,这个结果已经很棒了。
在建模之前预处理数据这是个好的习惯。神经网络特别适合输入值在大小和分布都一致的。当构建神经网络模型时表格数据的有效预处理是标准化的。这是重新调整数据的位置,使每个属性的平均值为0,标准差为1。这能保持高斯分布和类高斯分布同时规范化每个属性的中心趋势。
我们可以使用scikit-learn中的StandScaler类来规范化我们声呐数据。而不是对整个数据集进行规范化。在训练集上的一个交叉验证流程之内来训练标准程序并使用标准实例来预处理未知测试数据,这是一个很好的练习。这使得在模型预处理和交叉验证过程标准化。它可以防止算法在评估过程中掌握未知的数据,从而可以从数据预处理方案中获得知识,如更清晰的分布。
我们可以在scikit-learn中只用pipeline类获取这些。pipeline是一个封装类,他能在一个交叉验证程序内运行不止一个的模型。在这,我们能够用StandardScaler定义一个Pipeline,跟在我们的神经网络模型的后面。
# Binary Classification with Sonar Dataset: Standardized
import numpy
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)
# baseline model
def create_baseline():
# create model
model = Sequential()
model.add(Dense(60, input_dim=60, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# evaluate baseline model with standardized dataset
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_baseline, epochs=100,
batch_size=5, verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed)
results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Standardized: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
运行这个例子,得到下面的结果,我们确实看到了一个很小但很好的平均精度提升。
调节神经网络有很多事情可以做,如权重初始化,激活函数,优化程序等等。有很大影响的一方面是网络本身的结构,我们叫做拓扑结构。在这一部分,我们看看在网络结构上的两个实验:使得它小点,使得它大点。当我们在你的问题上调节网络时,可以做一个很好的实验。
我怀疑这个问题的输入变量有很大的冗余。数据从不同的角度描述了同样的信号。可能这些角度和其他信号有关联。我们强制某一种网络的特征提取,限制第一个隐藏层的表示空间。我们采用隐藏层中60个神经元的基线模型,并减少到一半30。这将在训练期间对网络施加压力,以挑选输入数据中最重要的结构来建模。
正如前面实验,我们用预处理来标准化数据,并尝试小幅度提升性能。
Standardized: 84.56% (5.74%)
运行上面这个例子,提供了西面的结果。我们能看到模型在均值估计上略微提升,标准差(平均速度)的精度分数显著减少。这是个很棒的结果,因为我们对网络大小为一半的网络做的更好,反过来要花费一半的时间来训练。
# Binary Classification with Sonar Dataset: Standardized Smaller
import numpy
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)
# smaller model
def create_smaller():
# create model
model = Sequential()
model.add(Dense(30, input_dim=60, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_smaller, epochs=100,
batch_size=5, verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed) results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Smaller: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
一个不止一层的神经网络拓扑结构有更多的机会提取关键特征并且用有效的非线性方法结合它们。我们可以评估是否向网络中添加更多的层并稍微调整创建我们模型的函数来提高性能。在这,我们添加新的一层到网络,在第一隐藏层之后引入有30个神经元隐藏层。我们网络现在有如下的结构。
60 inputs -> [60 -> 30] -> 1 output
这里的想法是出现瓶颈之前,网络有机会建模所有的输入变量,而且被迫减半表示容量,更像上面小规模网络的实验。我们能用额外的层来处理这个过程,而不是压缩输入表示本身。
# Binary Classification with Sonar Dataset: Standardized Larger
import numpy
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import cross_val_score from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline # fix random seed for reproducibility seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("sonar.csv", header=None) dataset = dataframe.values
# split into input (X) and output (Y) variables X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder() encoder.fit(Y)
encoded_Y = encoder.transform(Y) # larger model
def create_larger():
# create model
model = Sequential()
model.add(Dense(60, input_dim=60, kernel_initializer='normal', activation='relu'))
model.add(Dense(30, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_larger, epochs=100, batch_size=5,
verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed) results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Larger: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
运行这个例子得到下面结果,我们可以看到,我们的确获得不错的性能提升,花很少力气获得了很好结果。
Larger: 83.14% (4.52%)
进一步调节优化算法和训练周期次数等方面,期望尽可能进一步提高。你能在这个数据集上获得最好的分数是多少?
在这节课,你已经学习如何一步步用Keras解决二分类问题,特别是:
对于多分类和二分类问题,你现在知道如何开发神经网络模型。在接下来教程中,你将完成一个项目,开发一个神经网络模型解决回归问题。