利用人工神经网络框架Keras解决二分类问题 —— Jinkey 翻译

译者:Jinkey(微信公众号 jinkey-love)
英文原版地址:点击跳转

在看完本教程之后你将学会:

  1. 如何加载和准备数据
  2. 如何创建一个基线神经网络模型
  3. 如何使用scikit-learn 和 k-fold 交叉验证评估Keras模型
  4. 数据准备如何提升你的模型的性能
  5. 如何调整网络拓扑结构可以提高模型的性能实验

1 数据集描述

本教程中我们将使用的数据集是声纳数据集

这是一个从多个服务收集回来描述声纳返回反射的数据集。60个输入变量描述了在不同角度的反射强度。这是一个二元分类问题,需要一个模型来区分岩石和金属圆筒。
你可以了解更多关于这个在UCI机器学习数据集库。你可以免费下载数据集,并将其重命名为sonar.csv放在您的工作目录。
这是一个很好理解的数据集。所有的变量是连续的,一般在0到1的范围。输出变量是一个字符串“M”和“R”我岩石,它将需要转化为整数 1 和 0 。
使用这个数据集的一个好处是,它是一个标准的基准问题。这意味着我们有一个好的模型的预期能力的想法。使用交叉验证,神经网络能够实现性能大约84%的上限为自定义模型精度在88%左右。

2 基准神经网络模型的表现

让我们来建立这个问题的基准模型和预测结果。
我们将从导入所有我们需要的类和函数开始:

import time
import numpy
import pandas
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

接下来,我们可以初始化随机数生成器,以确保执行这段代码时,我们总是得到相同的随机数序列。这有助于我们调试。

seed = 7
numpy.random.seed(seed)

现在我们可以使用 pandas 库来加载数据集。将列向量分割成60输入变量(X)和1个输出变量(Y)。我们使用 pandas 加载数据是因为它很容易处理字符串(输出变量),而试图使用 NumPy 加载数据会更困难。

dataframe = pandas.read_csv("data/sonar.csv", header=None) # 加载数据集
dataset = dataframe.values
X = dataset[:, 0:60].astype(float) # 分割为60个输入变量
Y = dataset[:, 60] # 1个输出变量

输出变量是字符串类型,我们必须将其转换为整数值 0 和 1。
我们可以使用从scikit-learn LabelEncoder类。这个类通过 fit() 函数获取整个数据集模型所需的编码,然后使用transform()函数应用编码来创建一个新的输出变量。

encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)

现在我们已经准备好使用Keras创建我们的神经网络模型。
我们将使用scikit-learn的k-fold交叉评估模型验证。这是一个重采样进行模型性能评估的技术。它通过把数据分成k个部分,训练模型在所有k部分中分出一个作为测试集对模型的性能进行评估。这个过程重复 k 次的平均分数作为所有构造模型稳健的性能评估。它是分层的,这意味着它将关注输出值并试图平衡K份数据中每个类别的势利数量。
在 scikit-learn 中使用 Keras 的模型,我们必须使用 KerasClassifier 进行包装。这个类起到创建并返回我们的神经网络模型的作用。它需要传入调用 fit()所需要的参数,比如迭代次数和批处理大小。
让我们开始定义一个函数用于创建我们的基准模型。我们的模型会有一个与输入层神经元相同数量(60个)的全连接隐藏层。这是一个建立神经网络时很好的默认起点。
权值初始化使用一个小的高斯随机数,激活函数使用 ReLU 函数。输出层包含单个神经元以作出预测。使用 sigmoid 激活函数是为了产生一个0到1的范围的概率,这可以很容易地和自动地转换为离散类型的值。
最后,我们使用对数损失函数(binary_crossentropy)训练,这是二元分类问题会优先使用的损失函数。模型还使用高效的 Adam 优化器来做梯度下降,精度指标将模在型训练时收集。

# 基准模型
def create_baseline():
    # create model
    model = Sequential()
    model.add(Dense(60, input_dim=60, init='normal', activation='relu'))
    model.add(Dense(1, init='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
start_time = time.time()
estimator = KerasClassifier(build_fn=create_baseline, nb_epoch=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) # 如果get_parameters方法报错,查看 https://github.com/fchollet/keras/pull/5121/commits/01c6b7180116d80845a1a6dc1f3e0fe7ef0684d8
print("Baseline: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))
end_time = time.time()
print"用时: ", end_time - start_time

运行这段代码输出了以下内容,显示了不可见数据集上的预测准确率和平均标准偏差。

Baseline: 81.68% (5.67%)

3 带有数据准备地重新运行基准模型

建模之前准备数据是一个很好的练习。
神经网络模型特别适合有一致的输入值的情况,无论是在规模和分布。
有一种在训练神经网络模型中有效地准备一维数据的方法是“标准化”, 使每个属性的平均值为0,方差为1。这在保留高斯分布或类高斯分布的同时让数据向中心集中。
我们可以使用scikit-learn 的 StandardScaler 类来标准化声纳数据集.
我们应该在训练集而不是整个数据集上对数据进行标准化, 通过运行交叉验证和训练过的标准化数据去准备“未知”的训练数据。这让标准化在交叉验证中成为模型准备的一步, 也防止了算法在评估期间获得了数据准备过程对测试集的记忆。
我们可以利用 scikit-learn 的 Pipeline 类来实现这个过程。Pipeline 是一个包装器, 在交叉验证过程的执行一个或多个模型。在这里,我们可以定义一个Pipeline 带有一个tandardScaler, 后面紧跟着就是我们的神经网络模型。

# evaluate baseline model with standardized dataset
start_time = time.time()
numpy.random.seed(seed)
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_baseline, nb_epoch=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))
end_time = time.time()
print"用时: ", end_time - start_time

运行这段代码输出了以下内容,显示了测试集上的预测准确率和平均标准偏差。

Standardized: 84.07% (6.23%)

4 优化模型的层和神经元数量

有许多点可以优化神经网络,比如权重初始化,激活函数,优化器等等。
可能带来巨大影响的一个方面是网络本身的结构, 称为网络拓扑(the network topology)。在本节中,我们来看一看网络结构的两个实验:让它更小,让它大。
当在你的具体问题中优化神经网络,这些都是很好的试验方向。

4.2 评估一个更小型的网络

我怀疑在这个问题上有很多冗余的输入变量。
数据从不同的角度描述了相同的信号, 有些角度有可能比其他角度和信号更加相关。我们可以通过限制第一层的特征数量(输入层的神经元数量)来强制神经网络进行特征提取。
在这个实验中, 我们将基准模型的60个输入变量减少一半到30个。这会迫使神经网络从输入数据中提取出最重要的结构。
我们还会进行前面所说的数据标准化过程并利用减少规模带来的性能提升。

# smaller model
def create_smaller():
    # create model
    model = Sequential()
    model.add(Dense(30, input_dim=60, init='normal', activation='relu'))
    model.add(Dense(1, init='normal', activation='sigmoid'))
    # Compile model
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model

start_time = time.time()
numpy.random.seed(seed)
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_smaller, nb_epoch=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))
end_time = time.time()
print"用时: ", end_time - start_time

运行这段代码输出了以下内容。我们可以看到在平均准确率上有了小幅度的提升,和标准误差的重要下降。
这是一个很好的结果,因为我们用了一半的规模却实现了准确率的提高,进而减少了一半的训练时间。

Smaller: 84.61% (4.65%)

4.2 评估一个更大型的网络

有更多层的神经网络拓扑提供更多的机会来提取关键特征并把他们以非线性的方式联合起来。
我们可以评估是否可以简单地对模型做一些小的调整,添加更多层来提升神经网络的性能。这里我们在第一层隐藏层神经元后面再加一层30个神经元的隐藏层,我们的网络拓扑如下:

60 inputs -> [60 -> 30] -> 1 output

这里的想法是: 在变成瓶颈和被迫减半表征能力之前, 给更多机会去拟合所有的输入变量,非常像我们之前试验小型网络那样子。
不同于直接挤压输出的规模, 我们额外的添加一个隐藏层来实现这个过程。

# larger model
def create_larger():
    # create model
    model = Sequential()
    model.add(Dense(60, input_dim=60, init='normal', activation='relu'))
    from keras.layers import Dropout
    model.add(Dropout(0.5))
    model.add(Dense(30, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(60, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    # Compile model
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model


start_time = time.time()
numpy.random.seed(seed)
estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_larger, nb_epoch=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))
end_time = time.time()
print"用时: ", end_time - start_time

运行这段代码输出了以下内容。我们看到了模型性能有个很给力的提升,用很小的努力就接近了完美的结果。

Larger: 86.47% (3.82%)

进一步优化方面优化算法和训练时期的数量,预计进一步改进是可能的。这个数据集您可以实现最好的成绩是多少呢?

总结

在这篇文章中你学习了如何通过 Keras 循序渐进地解决二分类问题, 具体地来说:

  1. 如何加载和准备数据
  2. 如何创建一个基线神经网络模型
  3. 如何使用scikit-learn 和 k-fold 交叉验证评估Keras模型
  4. 数据准备如何提升你的模型的性能
  5. 如何调整网络拓扑结构可以提高模型的性能实验
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容