人人都能懂的机器学习——用Keras搭建人工神经网络07

使用Sequential API搭建回归MLP

前面的文章讲述了如何用Sequential API搭建分类MLP,接下来我们要用它来搭建回归MLP来解决加州房价问题。为了方便起见,我们使用Scikit-Learn的fetch_california_housing()函数来加载数据。这个数据集相对简单,只包含数值特征,并且没有缺失值。加载数据后,我们将其分为训练集、验证集和测试集,并缩放所有的特性:

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

housing = fetch_california_housing()
X_train_full, X_test, y_train_full, y_test = train_test_split(housing.data, housing.target)
X_train, X_valid, y_train, y_valid = train_test_split(X_train_full, y_train_full)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_valid = scaler.transform(X_valid)
X_test = scaler.transform(X_test)

使用Sequential API构建、训练、评估和使用回归MLP进行预测与我们分类MLP所作的工作非常相似。主要的区别是输出层只有一个神经元(因为我们只想预测一个值),没有使用激活函数,而损失函数是均方误差。由于数据集有很多的噪声,为了避免过度拟合,我们只使用了一个神经元比以前少的隐藏层:

model = keras.models.Sequential([
    keras.layers.Dense(30, activation="relu", input_shape=X_train.shape[1:]),
    keras.layers.Dense(1)
])
model.compile(loss="mean_squared_error", optimizer="sgd")
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid))
mse_test = model.evaluate(X_test, y_test)
X_new = X_test[:3]
y_pred = model.predict(X_new)

我们可以看到,Sequential API使用方法非常简单。然而,尽管Sequential模型很常用,但是搭建一些更加复杂拓扑结构的模型也是十分有用的,有时还会增加一些输入或输出神经元。出于这个目的,Keras提供了Functional API。

使用Functional API搭建复杂模型

非Sequential神经网络的一个例子是Wide & Deep神经网络。这种神经网络结构是由Heng-Tze Cheng等在2016年的一篇论文中提出的。如图1.14所示,它将所有或部分输入神经元直接连接到输出层,这种体系结构使神经网络能够同时学习深层规则(使用深路径)和简单规则(通过短路径)。

图1.14 Wide&Deep神经网络.jpg

让我们建立这样一个神经网络来解决加州的房价问题:

input_ = keras.layers.Input(shape=X_train.shape[1:])
hidden1 = keras.layers.Dense(30, activation="relu")(input_)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.Concatenate()([input_, hidden2])
output = keras.layers.Dense(1)(concat)
model = keras.Model(inputs=[input_], outputs=[output])

让我们逐行来看一下上面的代码:

  • 首先,我们需要创建一个输入对象(Input)。这是模型将得到的输入数据的说明,包括数据的形状和类型。一个模型实际上可能有多个输入对象,稍后我们将看到这一点。
  • 接下来,我们创建了一个30个神经元的Dense层,使用ReLU激活函数。在创建完成之后的第二个括号里,我们加了一个类似函数的结构,设置这个Dense层的输入。这个类似函数的结构就是Functional API这个名字的由来。但是这里要注意,我们只是告诉Keras如何将这些层连接起来,目前并没有将实际的数据进行传输。
  • 然后我们创建第二个隐藏层,并再次使用函数的结构。然后再将第一个隐藏层的输出传递给它。
  • 接下来,我们创建一个Concatenate()层,我们再次像使用函数一样用它来连接第二个隐藏层的输入和输出。Concatenate层的功能非常简单,就是将一个列表的输入张量连接起来。
  • 然后我们创建只有一个神经元输出层,没有激活函数,并且把Concatenate层连接的结果传递给它。
  • 最后创建Keras模型,指定要使用哪些输入和输出。

在构建了Keras模型之后,所有的工作就都跟前文一样了,编译,训练,评估,预测,这里就不再次赘述了。

但是,如果想要用宽路径发送特征的子集,而通过深路径发送不同的子集(也有可能重叠),那该怎么办呢(见图10-15)?在这种情况下,一种解决办法是使用多个输入。例如,假设我们想通过深路径发送5个特征(特征0到4),通过宽路径发送6个特征(特征2到7):

input_A = keras.layers.Input(shape=[5], name="wide_input")
input_B = keras.layers.Input(shape=[6], name="deep_input")
hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
output = keras.layers.Dense(1, name="output")(concat)
model = keras.Model(inputs=[input_A, input_B], outputs=[output])
图1.15 使用多个输入.jpg

代码本身很好理解,不过在搭建模型时注意至少要将重要的层命名,特别是当模型像这样变得有些复杂的时候。注意我们在创建模型时指定了inputs=[input_A, input_B]。现在,我们可以像往常一样编译模型,但是当我们调用fit()方法时,就不可以直接传递单个输入矩阵了,而必须传递一对矩阵(X_train_A, X_train_B),这样每个矩阵与每个输入对应。在评估和预测的时候,X_valid,X_test和X_new也需要用相同的方法处理。

model.compile(loss="mse", optimizer=keras.optimizers.SGD(lr=1e-3))
X_train_A, X_train_B = X_train[:, :5], X_train[:, 2:]
X_valid_A, X_valid_B = X_valid[:, :5], X_valid[:, 2:]
X_test_A, X_test_B = X_test[:, :5], X_test[:, 2:]
X_new_A, X_new_B = X_test_A[:3], X_test_B[:3]
history = model.fit((X_train_A, X_train_B), y_train, epochs=20,
                    validation_data=((X_valid_A, X_valid_B), y_valid))
mse_test = model.evaluate((X_test_A, X_test_B), y_test)
y_pred = model.predict((X_new_A, X_new_B))

在很多情况下,还可能会用到多个输出:

  • 任务要求使用多个输出。例如,图片中的主要对象分类和定位的任务,既是一个回归任务(查找对象中心的坐标,以及它的宽度和高度),也是一个分类任务。
  • 类似地,还可能会有多个基于相同数据集的独立任务。当然,我们可以为每个任务训练一个神经网络,但是通常通过训练一个神经网络并为每个任务设置一个输出会获得更好的结果。这是因为神经网络可以学习数据中对不同任务有用的特征。例如,对人脸图片执行多任务分类,使用一个输出来识别人的面部表情(微笑、惊讶等),另一个输出来识别他们是否戴眼镜等。
  • 另一个用例是作为正则化技术(即训练约束,其目标是减少过拟合,从而提高模型的泛化能力)。例如,您可能希望在神经网络体系结构中添加一些辅助输出(参见图1.16),以确保网络的底层部分能独立地学习一些有用的东西,而不依赖于网络的其他部分。
图1.16 多个输出-用于正则化的辅助输出.jpg

添加额外的输出非常简单:只需将输出层连接到适当的层,并将它们添加到模型的输出列表中。例如,下面的代码构建图1.16中所示的网络:

output = keras.layers.Dense(1, name="main_output")(concat)
aux_output = keras.layers.Dense(1, name="aux_output")(hidden2)
model = keras.Model(inputs=[input_A, input_B], outputs=[output, aux_output])

每一个输出层都需要一个损失函数,因此我们在编译模型时,我们应向模型传递一个损失函数列表(看注释)(如果我们只传递了单个损失,那么Keras会认为所有输出层都使用同一个损失函数)。 Keras会默认将计算所有损失,并将它们简单相加,得到用于训练的最终损失。 我们更关心主要输出的表现,而不是辅助输出(因为它只是用于正则化),所以我们希望赋予主输出更大的权重。所以Keras也提供了在编译模型时设置所有损失权重的功能:

model.compile(loss=["mse", "mse"], loss_weights=[0.9, 0.1], optimizer="sgd")

现在,当我们训练模型时,我们需要为每个输出提供标签。在这个例子当中,主输出和辅助输出应该尝试预测相同的内容,因此它们应该使用相同的标签。所以,我们需要输入(y_train, y_train)(对于y_valid和y_test也一样)。

history = model.fit([X_train_A, X_train_B], [y_train, y_train], epochs=20,
                    validation_data=([X_valid_A, X_valid_B], [y_valid, y_valid]))

当对模型进行评估时,Keras会返回总损失,以及每个单独损失:

total_loss, main_loss, aux_loss = model.evaluate([X_test_A, X_test_B], [y_test, y_test])

类似地,predict()方法将为每个输出进行预测:

y_pred_main, y_pred_aux = model.predict([X_new_A, X_new_B])

正如上文所述,我们可以很简单地用Functional API构建任何类型的模型架构。下一篇文章我们将介绍如何搭建动态模型,以及模型的一些常用操作。

敬请期待吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容