原文地址:https://github.com/fchollet/deep-learning-with-python-notebooks/blob/master/3.7-predicting-house-prices.ipynb
预测房价:一个回归的例子
在我们之前的两个例子中,我们正在考虑分类问题,其目标是预测输入数据点的单个离散标签。 另一种常见的机器学习问题是“回归”,它包括预测连续值而不是离散标签。 例如,根据气象数据预测明天气温,或根据其规格预测软件项目完成时间。
不要混淆“逻辑回归”算法的“回归”:令人困惑的是,“逻辑回归”不是回归算法,它是一种分类算法。
波士顿房价数据集
考虑到当时郊区的一些数据点,例如犯罪率,地方财产税率等,我们将尝试预测1970年代中期某个特定波士顿郊区的房屋中位价格。我们将使用的数据集与我们之前的两个示例有另一个有趣的区别:它只有极少的数据点,总共只有506个,分为404个训练样本和102个测试样本,并且输入数据中的每个“特征”(例如 犯罪率是一个特征)具有不同的规模。 例如,某些值是比例,取值介于0和1之间,其他取值介于1和12之间,其他值介于0和100之间...
我们来看看数据:
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
正如你所看到的,我们有404个训练样本和102个测试样本。 数据包含13个功能。 输入数据中的13个特征如下:
1. 划分为25,000平方英尺以上的住宅用地的比例。
2.每个城镇的非零售业务比例。
3. 查尔斯河虚拟变量(= 1,如果道界河;否则为0)。
4.一氧化氮浓度(每1000万份)。
5.每个住所的平均房间数量。
6. 1940年以前建造的自住单位比例。
7. 到五个波士顿就业中心的加权距离。
8.径向高速公路的可达性索引。
9.每万美元的全价值物业税税率。
10. 城镇的小学教师比例。
11.1000 *(Bk - 0.63)** 2其中Bk是城镇黑人的比例。
12. 人口状况较低。
13.人均犯罪率。
目标是自住房屋的中值,单位为千美元:
train_targets
价格通常在$ 10,000到$ 50,000之间。 如果这听起来很便宜,请记住这是在20世纪70年代中期,这些价格不是通货膨胀调整的。
准备数据
将神经网络的数值输入到大致不同的范围内是有问题的。 网络可能能够自动适应这种异构数据,但肯定会使学习变得更加困难。 处理这种数据的广泛的最佳做法是进行特征方式归一化:对于输入数据中的每个特征(输入数据矩阵中的一列),我们将减去特征的平均值并除以标准偏差,所以 该特征将以0为中心并具有单位标准偏差。 这在Numpy中很容易完成:
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std
请注意,我们用于标准化测试数据的数量已使用训练数据进行计算。 我们绝不应该在测试数据中使用任何计算量的工作流程,即使是像数据规范化一样简单的事情。
建立我们的网络
由于只有很少的样本可用,我们将使用具有两个隐藏层的非常小的网络,每个网络有64个单元。 一般来说,您拥有的训练数据越少,过拟合就会越差,并且使用小型网络是缓解过度拟合的一种方法。
from keras import models
from keras import layers
def build_model():
# Because we will need to instantiate
# the same model multiple times,
# we use a function to construct it.
model = models.Sequential()
model.add(layers.Dense(64, activation='relu',
input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model
我们的网络以一个单元结束,并且没有激活(即它将是线性层)。这是标量回归的典型设置(即我们试图预测单个连续值的回归)。应用激活函数会限制输出可能的范围;例如,如果我们将S形激活函数应用到最后一层,则网络只能学习预测0到1之间的值。这里,因为最后一层是纯线性的,所以网络可以自由学习预测任何范围内的值。
请注意,我们使用mse损失函数 - 均方误差,预测和目标之间的差异的平方来编译网络,这是广泛使用的回归问题的损失函数。
我们还在训练期间监测新的指标:mae。这代表平均绝对误差。它只是预测与目标之间差异的绝对值。例如,对这个问题的MAE为0.5就意味着我们的预测平均下降了500美元。
使用K-fold验证验证我们的方法
为了评估我们的网络,同时不断调整其参数(例如用于训练的历元数),我们可以简单地将数据分成训练集和验证集,就像我们在前面的例子中所做的那样。但是,由于我们的数据点非常少,验证集最终会很小(例如,大约有100个示例)。结果是,我们的验证分数可能会根据我们选择用于验证的哪些数据点以及我们选择用于训练的数据点而改变很多,即,验证分数可能在验证分割方面具有高度差异。这会阻止我们可靠地评估我们的模型。在这种情况下的最佳实践是使用K-fold交叉验证。它包括将可用数据分成K个分区(通常K = 4或5),然后实例化K个相同的模型,并在K-1分区上训练每个分区,同时对剩余的分区进行评估。所使用的模型的验证分数将是所获得的K个验证分数的平均值。
就代码而言,这很简单:
import numpy as np
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
print('processing fold #', i)
# Prepare the validation data: data from partition # k
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
# Prepare the training data: data from all other partitions
partial_train_data = np.concatenate(
[train_data[:i * num_val_samples],
train_data[(i + 1) * num_val_samples:]],
axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i + 1) * num_val_samples:]],
axis=0)
# Build the Keras model (already compiled)
model = build_model()
# Train the model (in silent mode, verbose=0)
model.fit(partial_train_data, partial_train_targets,
epochs=num_epochs, batch_size=1, verbose=0)
# Evaluate the model on the validation data
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
all_scores.append(val_mae)
正如你可以注意到的,不同的运行确实表现出不同的验证分数,从2.1到2.9。 他们的平均值(2.4)比这些分数中的任何一个都更可靠 - 这就是K-fold交叉验证的全部要点。 在这种情况下,我们的平均价格为2400美元,考虑到价格从1万美元到5万美元不等,这仍然很重要。
让我们尝试对网络进行更长的训练:500个时代。 为了记录模型在每个时期的表现如何,我们将修改我们的训练循环以保存每个时期的验证评分日志:
from keras import backend as K
# Some memory clean-up
K.clear_session()
num_epochs = 500
all_mae_histories = []
for i in range(k):
print('processing fold #', i)
# Prepare the validation data: data from partition # k
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
# Prepare the training data: data from all other partitions
partial_train_data = np.concatenate(
[train_data[:i * num_val_samples],
train_data[(i + 1) * num_val_samples:]],
axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i + 1) * num_val_samples:]],
axis=0)
# Build the Keras model (already compiled)
model = build_model()
# Train the model (in silent mode, verbose=0)
history = model.fit(partial_train_data, partial_train_targets,
validation_data=(val_data, val_targets),
epochs=num_epochs, batch_size=1, verbose=0)
mae_history = history.history['val_mean_absolute_error']
all_mae_histories.append(mae_history)
然后,我们可以计算所有褶皱的每个时代MAE分数的平均值:
average_mae_history = [
np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
绘制曲线:
import matplotlib.pyplot as plt
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
由于缩放问题和相对较高的方差,可能有点难以看清情节。 让我们:
省略前10个数据点,它们与曲线的其余部分不同。
用前一个点的指数移动平均值代替每个点,以获得平滑的曲线。
def smooth_curve(points, factor=0.9):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
smooth_mae_history = smooth_curve(average_mae_history[10:])
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
根据这个图,似乎验证MAE在80个时代后停止显着改善。 过去这一点,我们开始过度配合。
一旦我们完成了我们模型的其他参数的调整(除了时代的数量,我们也可以调整隐藏层的大小),我们可以在所有训练数据上训练最终的“生产”模型,使用最佳参数, 然后看看它在测试数据上的表现:
# Get a fresh, compiled model.
model = build_model()
# Train it on the entirety of the data.
model.fit(train_data, train_targets,
epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
In [29]:test_mae_score
Out[29]:2.5532484335057877