- 深度学习训练营 21天实战TensorFlow+Keras+scikit-learn
- 张强
- 2455字
- 2020-08-27 11:30:47
1.3 基于Keras实现房价预测
前面我们使用scikit-learn机器学习库实现了房价预测。本节将使用神经网络模型来实现房价预测,使用的是TensorFlow下的Keras的API代码。我们将使用Keras创建模型、训练模型、预测和对比图预览。
Keras封装了一套高级的API用于构建深度学习模型,常用于快速实现原型设计和高级搜索。Keras对开发者的友好性、模块化、可组合性和易于扩展的关键特征,使其成为目前使用较为广泛的深度学习开源框架,它的后端之一就是TensorFlow。Keras在2015年12月份就将其以TensorFlow为后端的部分API融合到了TensorFlow框架中,所以目前我们可以通过tf.keras或者独立的Keras库来使用它。本书后面的章节会大量使用TensorFlow和Keras来构建深度学习模型。
1.3.1 数据准备
我们通过TensorFlow提供的Keras接口下的datasets模块来加载数据集。数据集由卡内基梅隆大学维护,是波士顿近郊房价数据。读者也可以通过前言中的链接去查看后续更新的数据集。在tf.keras.datasets下加载的数据集和我们在链接中看到的数据集的格式是一样的,只是年份可能不同。加载数据集的代码如下。
import tensorflow as tf # 从TensorFlow导入Keras模块 from tensorflow import keras import numpy as np # 加载波士顿房价数据集 (train_data, train_labels), (test_data, test_labels) = \ keras.datasets.boston_housing.load_data() # 清洗训练集数据 # np.random.random()表示返回在0.0到1.0之间指定个数的随机浮点数 # np.argsort()表示返回对数组进行排序的索引 order = np.argsort(np.random.random(train_labels.shape)) train_data = train_data[order] train_labels = train_labels[order] # 归一化处理数据 # 对不同的范围和比例进行归一化处理,并且每个元素都要减去均值后再除以标准差 # 虽然模型在没有特征归一化时也可以得到收敛,但是这会让训练更加困难, # 而且会导致结果模型依赖于训练数据 mean = train_data.mean(axis=0) std = train_data.std(axis=0) train_data = (train_data - mean) / std test_data = (test_data - mean) / std print("train_data.shape: {}, train_labels.shape: {}." .format(train_data.shape, train_labels.shape)) print("test_data.shape: {}, test_labels.shape: {}." .format(test_data.shape, test_labels.shape))
输出如下。
train_data.shape:(404,13),train_labels.shape:(404,). test_data.shape:(102,13),test_labels.shape:(102,).
1.3.2 创建神经网络模型
在Keras中,我们创建神经网络模型,就是使用Sequential类来创建Keras模型。本次创建的模型比较简单,通过Dense类来创建神经网络层。对于输入层,层的深度是64个units,输入层必须传入input_shape参数,表示输入数据的特征维度的大小。
激活函数(activation),我们指定的是修正线性单元(ReLU),它是神经网络中最常用的激活函数。当输入的值为正数时,导数不为0,返回它本身,这就允许在训练模型时进行基于梯度的学习,也会使计算变得更快;当输入的值为负数时,学习速度可能会变得很慢,甚至会使神经元直接失效,这是因为输入的值是小于0的值,计算它的梯度也为0,从而使其权重无法得到更新,因此在传播到下一个神经网络层的时候,返回的值为0就没有什么意义了。
然后我们再添加一个隐藏层,这一层不需要input_shape参数,因为在输入层时已经指定了。我们仍然设置层的深度是64个units,激活函数是ReLu。
输出层只有一个unit,代码如下。
# 定义创建模型函数 def build_model(): model = keras.Sequential([ keras.layers.Dense(64, activation=tf.nn.relu, input_shape=(train_data.shape[1],)), keras.layers.Dense(64, activation=tf.nn.relu), keras.layers.Dense(1) ]) # 使用RMSProp(均方根传播)优化器,它可以加速梯度下降,其中学习速度适用于每个参数 optimizer = tf.train.RMSPropOptimizer(0.001) # mse(均方差)一般用于回归问题的损失函数 # mae(平均绝对误差)一般用于回归问题的测量/评估 model.compile(loss='mse', optimizer=optimizer, metrics=['mae']) return model model = build_model() # 查看模型的架构 model.summary()
输出的模型架构如图1.12所示。全部参数有5121个,模型中的每一层参数都可以在表格的Param列看到。每一层的输出大小在Output Shape列中显示,其中None表示是可变的batch size,它会在训练模型或者模型预测时被自动填充上具体的值。
图1.12 一个简单的Keras模型架构图
1.3.3 训练网络模型
训练这个模型500次,并且将训练精确度和验证精确度记录在history对象中,以便绘图预览。我们自定义一个回调对象类,重写on_epoch_end()函数,在每次epoch结束时会调用该函数。
# 自定义一个回调对象类,在每次epoch(代)结束时都会调用该函数 class PrintDot(keras.callbacks.Callback): def on_epoch_end(self, epoch, logs): if epoch % 100 == 0: print('') print('.', end='') EPOCHS = 500 # 训练模型 # 参数1:房屋特征数据 # 参数2:房屋价格数据 # 参数3:迭代次数 # 参数4:验证集分割比例,0.2表示20%的数据用于验证,80%的数据用于训练 # 参数5:输出打印日志信息,0表示不输出打印日志信息 # 参数6:回调对象,这里我们使用自定义的回调类PrintDot history = model.fit(train_data, train_labels, epochs=EPOCHS, validation_split=0.2, verbose=0, callbacks=[PrintDot()])
1.3.4 可视化模型的结果
通过history对象,我们可以读取该模型训练时的误差数值,以便于观察何时是最佳模型,何时应该停止训练。
import matplotlib.pyplot as plt # 绘制图来显示训练时的accuracy和loss def plot_history(history): plt.figure() plt.xlabel('Epoch') plt.ylabel('Mean Abs Error [1000$]') plt.plot(history.epoch, np.array(history.history['mean_absolute_error']), label='Train Loss') plt.plot(history.epoch, np.array(history.history['val_mean_absolute_error']), label='Val loss') plt.legend() plt.ylim([0, 5]) plt.show() plot_history(history)
输出如图1.13所示。
图1.13 模型训练时的平均绝对误差表现图
可以发现,大约在150到200次迭代时,训练损失值就没怎么降低了。所以,这里我们要用到一个降低过拟合技术:早期停止(Early Stopping)。它是指在指定的迭代次数内,如果依旧没有损失降低、模型性能提升的话,就自动终止训练。
我们重新构建和训练该模型,在重新构建模型前,请先清除Keras的内存状态。最简单的办法就是重新运行Jupyter Notebook;如果读者使用的是终端,就重新启动该脚本程序。
# 重新构建模型 model = build_model() # 设置早期停止,如果20次的迭代依旧没有降低验证损失,则自动停止训练 early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=20) # 重新训练模型,此时的callbacks有两个回调函数,所以我们使用数组的形式传入它们 history = model.fit(train_data, train_labels, epochs=EPOCHS, validation_split=0.2, verbose=0, callbacks=[early_stop, PrintDot()]) # 打印输出历史记录的曲线图 plot_history(history)
输出如图1.14所示。
图1.14 训练和验证模型时的平均绝对误差图(使用早期停止技术)
1.3.5 评估和预测模型
接下来,我们来测试模型在测试集下的表现,这就需要对模型进行评估了。通过evaluate()函数,传入测试房屋特征数据集,测试房屋价格数据,计算测试集的平均绝对误差。
[loss, mae] = model.evaluate(test_data, test_labels, verbose=0) print("Testing set Mean Abs Error: ${:7.2f}".format(mae * 1000))
输出日志如下。
Testing set Mean Abs Error:$2930.86
然后预测模型,通过predict()函数,传入测试房屋特征数据集,返回预测房价;最后将返回的数据通过flatten()函数进行扁平化处理,以便于绘制散点图。
# 使用测试数据集预测模型 test_predictions = model.predict(test_data).flatten() plt.scatter(test_labels, test_predictions) plt.xlabel('True Values [1000$]') plt.ylabel('Predictions [1000$]') plt.axis('equal') plt.xlim(plt.xlim()) plt.ylim(plt.ylim()) plt.plot([-100, 100], [-100, 100]) plt.show()
输出结果如图1.15所示。
图1.15 预测房价的回归模型图
1.3.6 预测可视化显示
接下来,我们来看下将真实房价和预测房价进行对比,从而形成的价格差直方图。我们先计算预测房价和真实房价的差价,代码如下。
error = test_predictions - test_labels plt.hist(error, bins=50) plt.xlabel("Prediction Error [1000$]") plt.ylabel("Count") plt.show()
输出如图1.16所示。
图1.16 预测房价和真实房价的价格差直方图
最后,我们通过真实房价和预测房价生成一张更直观的图。函数plotVersusFigure()在上面的代码中已经定义过,这里就不再介绍。
plotVersusFigure(test_labels,test_predictions)
输出如图1.17所示。
图1.17 真实房价与预测房价的对比图
通过以上分析可知,不管是用scikit-learn的机器学习库来预测房价,还是使用Keras的神经网络模型来预测房价,真实房价和预测房价总是有些误差。所以我们能控制的就是在训练神经网络模型时,调整训练的超参数、迭代次数、网络层数和优化器等参数,以得到更好的、适用于该房屋数据的预测模型。这里的数据量比较小,如果数据量更大一些,模型效果会更好。