波士顿房价预测
在这节课中,我们使用波士顿房价数据集来实现一个更加完整的例子,关于波士顿房价数据集,我们在第6讲中进行了详细的介绍,这是对它可视化的结果
其中的每一个子图是数据集中的一个属性,和房价之间的关系,可以看到平均房间数和低收入人口的比例,这两个属性和房价之间的关系是比较接近于线性分布的,我们可以选取其中的一个属性做一元线性回归建立它和房价之间的关系模型,例如我们举出房间数这个属性和房价做一元线性回归
第1步导入需要的库,加载波士顿房价数据集
boston_housing = tf.keras.datasets.boston_housing
(train_x, train_y), (test_x, test_y) = boston_housing.load_data()
print(train_x.shape, train_y)
# (404, 13) (404,)
print(test_x.shape, test_y.shape)
# (102, 13) (102,)
第2步数据处理
x_train = train_x[:, 5]
这是取出所有训练样本中的房间数,y_train = train_y
这是房价。为了和训练集中的原始数据集区分开,我们采用x_train和y_train来表示他们。虽然这里不需要对房价做处理,但是我们也对他重新赋值,这样可以和x_train的名称保持一致,而且也可以和原始数据区分开。以后如果需要改变它的值,那么就改变这个变量的值。
它们都是一维数组,x_test = test_x[:, 5] y_test = test_y
这是测试集中的房间数属性和房价。使用测试集,可以帮助我们观察模型在新样本上的表现。
x_train = train_x[:, 5]
y_train = train_y
print(x_train.shape, y_train.shape)
x_test = test_x[:, 5]
y_test = test_y
print(x_test.shape, y_test.shape)
第3步设置超参数
这里将学习率设为0.04,迭代次数为2000,显示间隔为200
learn_rate = 0.04
iter = 2000
display_step = 200
第4步设置模型参数初始值
np.random.seed(612)
w = tf.Variable(np.random.randn())
b = tf.Variable(np.random.randn())
print(w.numpy().dtype, b.numpy().dtype)
# float32 float32
第5步训练模型
mse_train:这个列表用来记录训练集上的损失,也就是训练误差。
mse_test:这个列表用来记录测试集上的损失,也就是测试误差。
mse_train = []
mse_test = []
for i in range(0, iter + 1):
with tf.GradientTape() as tape:
pred_train = w * x_train + b
loss_train = 0.5 * tf.reduce_mean(tf.square(y_train - pred_train))
pred_test = w * x_test + b
loss_test = 0.5 * tf.reduce_mean(tf.square(y_test - pred_test))
mse_train.append(loss_train)
mse_test.append(loss_test)
dL_dw, dL_db = tape.gradient(loss_train, [w, b])
w.assign_sub(learn_rate * dL_dw)
b.assign_sub(learn_rate * dL_db)
if i % display_step == 0:
print("i: %i, Train Loss: %f, Test Loss: %f" % (i, loss_train, loss_test))
这是计算训练集上的预测值和均方误差
这是计算测试集上的预测值和均方误差
把它们都放在梯度带对象的with语句块中,实现对变量w和b的自动监视
把本次迭代的训练误差和测试误差,记录在列表中
使用训练集中的数据计算损失函数,对w和b的梯度
并使用这个梯度更新模型变量w和b
最后输出模型训练过程中的训练误差和测试误差
在这个过程中,我们只使用训练集中的数据来更新模型参数测试集,并没有参与训练模型
但是在每一步迭代中,都使用当前的模型参数,w和b计算测试集上的误差,并把它记录下来,这样就可以实时的观察模型在新样本上的表现
这是训练的结果
可以看到训练误差和测试误差都是一直单调递减的,在测试集上损失的下降更快。
第6步可视化输出
为了更直观的评价模型,我们绘制出这4张图
在这张图中,蓝色的点是房间数的散点图,红色的直线是训练得到的线性模型。可以看到它能够比较好地反映出这些数据点的总体的变化规律
这是损失值随迭代次数变化的曲线。一元线性回归的损失函数是一个凸函数,采用梯度下降法,只要步长足够小,迭代次数足够大,就一定可以通过不断的迭代到达极值点,但是这样可能会造成过度训练产生过拟合。因此需要同时观察训练误差和测试误差,如果两者同时下降,说明还可以继续训练。
如果到了某个点之后,训练误差继续下降,而测试误差不再下降,甚至开始上升了,那么就说明出现了过拟合,应该在这个点停止下来。
在这个图中蓝色的线是训练误差,红色的是测试误差,可以看到这两条线基本上是一致的,红色的线更低一点,这和我们刚才看到的运行结果也是一致的。
这张图是训练集中的实际房价和使用模型预测出的房价的对比。测试集中一共有404条数据,每个横坐标对应一个样本点,纵坐标是房价,蓝色的点是训练集中实际的房价,红色的点是使用这个模型预测出的房价。可以看到,实际房价的波动范围更大,预测的房价和实际的房价总体变化规律是一致的
这张图是测试集中实际房价和预测房价的对比,测试集中只有102个数据,所以它们的分布更加松散一些。
下面我们来绘制这些图
plt.figure(figsize=(15, 10))
plt.subplot(221)
plt.scatter(x_train, y_train, color="blue", label="data")
plt.plot(x_train, pred_train, color="red", label="model")
plt.legend(loc="upper left")
plt.subplot(222)
plt.plot(mse_train, color="blue", linewidth=3, label="train loss")
plt.plot(mse_train, color="red", linewidth=1.5, label="test loss")
plt.legend(loc="upper right")
plt.subplot(223)
plt.plot(y_train, color="blue", marker="o", label="true_price")
plt.plot(pred_train, color="red", marker=".", label="predict")
plt.legend()
plt.subplot(224)
plt.plot(y_test, color="blue", marker="o", label="true_price")
plt.plot(pred_test, color="red", marker=".", label="predict")
plt.legend()
plt.show()
首先设置画布尺寸。然后,划分子图。
- 在子图1中绘制散点图和模型直线
- 在子图2中分别绘制训练误差和测试误差,因为这两条线靠的比较近,为了避免互相遮挡,我们使用不同的线条宽度和颜色来区分
- 在子图3中绘制所有样本点的实际房价和预测房价
- 在子图4中绘制所有测试数据集的实际房价和预测房价
现在我们已经建立了房间数和房价之间的一元线性回归模型,下面就可以使用它来预测房价了。
多元线性回归
波士顿房价数据集
波士顿房价数据集中包含13个属性,它们的取值范围相差很大。
比如这个城镇人均犯罪率的取值非常小,而这个财产税率和黑人的比例的取值是它的几万倍,为了平衡所有的属性对于模型参数的影响,首先需要对他们进行归一化处理。
一维数组归一化
在前面的例子中,商品房面积和房价都是一维数组,我们分别对他们进行了归一化处理。
二维数组归一化–循环实现
波士顿房价数据集的属性是一个二维数组,其中每一行是一条记录,每一列是一个属性,对它进行归一化,就是要对其中的每一列进行归一化。
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
boston_housing = tf.keras.datasets.boston_housing
(train_x, train_y), (test_x, test_y) = boston_housing.load_data()
print(train_x.shape, train_y.shape)
# (404, 13) (404,)
print(test_x.shape, test_y.shape)
# (102, 13) (102,)
要对所有的列进行归一化,首先可以想到采用循环语句实现。
例如创建一个二维数组x。
x = np.array([[3., 10, 500],
[2., 20, 200],
[1., 30, 300],
[5., 50, 100]])
print(x.dtype, x.shape)
# float64 (4, 3)
print(len(x))
# 4
print()
这是一个浮点数组,形状是(4, 3),如果要获取这个数组的行数,可以使用len()函数,那么怎样得到它的列数呢?
这个形状其实就是用一个元组来表示的,其中的两个元素分别就是数组x的行数和列数,我们可以使用x.shape
表示这个元组,然后用索引值一取出其中的第2个元素,就是x的列数。
现在我们就使用这个列数来控制for语句循环的次数。
print(x.shape[0], x.shape[1])
# 4 3
for i in range(x.shape[1]):
x[:, i] = (x[:, i]-x[:, i].min())/(x[:, i].max()-x[:, i].min())
print(x)
"""
[[0.5 0. 1. ]
[0.25 0.25 0.25]
[0. 0.5 0.5 ]
[1. 1. 0. ]]
"""
依次读取x中的每一列,并对它进行归一化处理,这是第i列属性,这分别是第i列中的最大值和最小值。这是对第i列归一化。
循环完成之后,所有的列都被归一化了。
二维数组归一化–广播运算
除此之外我们还可以直接利用多维数组的广播运算,实现对所有列的归一化。
x是一个形状为(4, 3)的二维数组,这是取到这个二维数组中每一列的最小值。
x = np.array([[3., 10, 500],
[2., 20, 200],
[1., 30, 300],
[5., 50, 100]])
# 这是取到这个二维数组中每一列的最小值。
print(x. min(axis=0))
# [ 1. 10. 100.]
这是取到这个二维数组中每一列的最大值。
print(x.max(axis=0))
# [ 5. 50. 500.]
print(x.max(axis=0) - x. min(axis=0))
# [ 4. 40. 400.]
# 广播运算,分别用x的每一行减去最小值
print(x - x.min(axis=0))
"""
[[ 2. 0. 400.]
[ 1. 10. 100.]
[ 0. 20. 200.]
[ 4. 40. 0.]]
"""
# 广播运算,每一列归一化
print((x-x.min(axis=0))/(x.max(axis=0) - x.min(axis=0)))
"""
[[0.5 0. 1. ]
[0.25 0.25 0.25]
"""
波士顿房价数据多元线性回归
现在我们使用波士顿房价数据集中的所有13个属性来实现多元线性回归。
第1步导入需要的库加载数据集
num_train
,num_test
这分别是训练集合测试集中样本的数量,在后面创建全1数组需要使用它
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
boston_housing = tf.keras.datasets.boston_housing
(train_x, train_y), (test_x, test_y) = boston_housing.load_data()
print(train_x.shape, train_y.shape)
# (404, 13) (404,)
print(test_x.shape, test_y.shape)
# (102, 13) (102,)
num_train = len(train_x)
num_test = len(test_y)
第2步数据处理
# 对训练样本的所有属性归一化
x_train = (train_x-train_x.min(axis=0))/(train_x.max(axis=0) - train_x.min(axis=0))
# 标签y虽然不用归一化,但是我们也给它重新赋值
y_train = train_y
# 对测试样本的所有属性归一化
x_test = (test_x-test_x.min(axis=0))/(test_x.max(axis=0) - test_x.min(axis=0))
y_test = test_y
x0_train = np.ones(num_train).reshape(-1, 1)
x0_test = np.ones(num_test).reshape(-1, 1)
X_train = tf.cast(tf.constant([x0_train, x_train], axis=1), tf.float32)
X_test = tf.cast(tf.constant([x0_test, x_test], axis=1), tf.float32)
print(X_train.shape, X_test.shape)
# (404, 14) (102, 14)
# 这是把房价堆叠为列向量
Y_train = tf.cast(tf.constant(y_train.reshape(-1, 1)), tf.float32)
Y_test = tf.cast(tf.constant(y_test.reshape(-1, 1)), tf.float32)
print(Y_train.shape, Y_test.shape)
# (404, 1) (102, 1)
第3步设置超参数
learn_rate = 0.01
iter = 2000
display_step = 200
第4步模型变量初始值
np.random.seed(612)
W = tf.Variable(np.random.randn(14, 1), dtype=tf.float32)
第5步训练模型
mse_train = []
mse_test = []
for i in range(0, iter + 1):
with tf.GradientTape() as tape:
# 这是计算训练集的预测房价和损失
PRED_train = tf.matmul(X_train, W)
Loss_train = 0.5 * tf.reduce_mean(tf.square(Y_train - PRED_train))
# 这是计算测试集的预测房价和损失
PRED_test = tf.matmul(X_test, W)
Loss_test = 0.5 * tf.reduce_mean(tf.square(Y_test - PRED_test))
# 记录每一次迭代之后,使用更新后的参数,模型在训练集上的损失和在测试集上的损失,
mse_train.append(Loss_train)
mse_test.append(Loss_test)
# 训练集中的样本来更新模型参数
dL_dW = tape.gradient(Loss_train, W)
W.assign_sub(learn_rate * dL_dW)
# 输出训练集上的损失和测试集上的损失
if i % display_step == 0:
print("i: %i, Loss_train: %f, Loss_train: %f" % (i, Loss_train, Loss_test))
这是运行结果
i: 0, Loss_train: 263.193481, Loss_train: 276.994110
i: 200, Loss_train: 36.176544, Loss_train: 37.562954
i: 400, Loss_train: 28.789461, Loss_train: 28.952515
i: 600, Loss_train: 25.520697, Loss_train: 25.333912
i: 800, Loss_train: 23.460527, Loss_train: 23.340536
i: 1000, Loss_train: 21.887274, Loss_train: 22.039745
i: 1200, Loss_train: 20.596283, Loss_train: 21.124844
i: 1400, Loss_train: 19.510202, Loss_train: 20.467239
i: 1600, Loss_train: 18.587011, Loss_train: 19.997717
i: 1800, Loss_train: 17.797461, Loss_train: 19.671589
i: 2000, Loss_train: 17.118927, Loss_train: 19.456860
这是运行结果,可以看到随着迭代次数的递增,训练集上的损失和测试集上的损失都是单调递减的。迭代结束之后,PRED_train和PRED_test中分别是最后一次迭代之后,模型对训练集和测试集中的每一个点的预测值,mse_train和mse_test分别是训练集和测试集中每一次迭代的损失值。
下面就利用这些数据来实现训练过程和结果的可视化输出,这是损失值随着迭代次数的变化曲线,其中蓝色的是训练集的损失,红色的是测试集的损失,可以看到他们的趋势是大致相同的,也存在着一点细微的差别,红色的线开始下降的更快一点,后期更加平缓。
plt.plot(mse_train, color="blue", linewidth=3)
plt.plot(mse_test, color="red", linewidth=1.5)
这是训练集中实际房价和预测房价的对比。
plt.plot(y_train, color="blue", marker="o", label="true_price")
plt.plot(PRED_train, color="red", marker=".", label="predict")
这是测试集中实际房价和预测房价的对比。
plt.plot(y_test, color="blue", marker="o", label="true_price")
plt.plot(PRED_test, color="red", marker=".", label="predict" )
可以把这些图放在同一个画布中显示出来。
plt.figure(figsize=(20, 4))
plt.subplot(131)
plt.ylabel("MSE")
plt.plot(mse_train, color="blue", linewidth=3)
plt.plot(mse_test, color="red", linewidth=1.5)
plt.subplot(132)
plt.plot(y_train, color="blue", marker="o", label="true_price")
plt.plot(PRED_train, color="red", marker=".", label="predict")
plt.legend()
plt.ylabel("Price")
plt.subplot(133)
plt.plot(y_test, color="blue", marker="o", label="true_price")
plt.plot(PRED_test, color="red", marker=".", label="predict" )
plt.legend()
plt.ylabel("Price")
plt.show()
下面把迭代次数增加到8000次,同时把显示间隔增加到500
这是运行的结果
i: 0, Loss_train: 263.193481, Loss_train: 276.994110
i: 500, Loss_train: 26.911524, Loss_train: 26.827423
i: 1000, Loss_train: 21.887274, Loss_train: 22.039745
i: 1500, Loss_train: 19.030268, Loss_train: 20.212137
i: 2000, Loss_train: 17.118927, Loss_train: 19.456860
i: 2500, Loss_train: 15.796996, Loss_train: 19.260981
i: 3000, Loss_train: 14.858855, Loss_train: 19.365536
i: 3500, Loss_train: 14.177204, Loss_train: 19.623528
i: 4000, Loss_train: 13.671041, Loss_train: 19.949774
i: 4500, Loss_train: 13.287542, Loss_train: 20.295109
i: 5000, Loss_train: 12.991436, Loss_train: 20.631866
i: 5500, Loss_train: 12.758677, Loss_train: 20.945164
i: 6000, Loss_train: 12.572536, Loss_train: 21.227783
i: 6500, Loss_train: 12.421192, Loss_train: 21.477072
i: 7000, Loss_train: 12.296152, Loss_train: 21.693033
i: 7500, Loss_train: 12.191257, Loss_train: 21.877157
i: 8000, Loss_train: 12.101961, Loss_train: 22.031698
可以看到训练误差仍然在不断,多元线性回归的损失函数是凸函数,只要步长足够小,那么一直训练下去,一定可以到达损失的极小值点附近。
但是在训练到第2500轮到3000轮之间时,测试集的损失值开始逐渐上升了,这就说明产生了过拟合。在这之后虽然模型在训练集上的表现更好了,但是在新样本上的表现却更差了。
现在可以把迭代次数修改为3000,显示间隔设置为100。找到那个测试及损失开始上升的时间点作为最佳的迭代次数。
i: 0, Loss_train: 263.193481, Loss_train: 276.994110
i: 100, Loss_train: 44.476341, Loss_train: 47.471565
i: 200, Loss_train: 36.176544, Loss_train: 37.562954
i: 300, Loss_train: 31.584028, Loss_train: 32.202709
i: 400, Loss_train: 28.789461, Loss_train: 28.952515
i: 500, Loss_train: 26.911524, Loss_train: 26.827423
i: 600, Loss_train: 25.520697, Loss_train: 25.333912
i: 700, Loss_train: 24.405626, Loss_train: 24.216911
i: 800, Loss_train: 23.460527, Loss_train: 23.340536
i: 900, Loss_train: 22.630888, Loss_train: 22.629448
i: 1000, Loss_train: 21.887274, Loss_train: 22.039745
i: 1100, Loss_train: 21.212658, Loss_train: 21.544201
i: 1200, Loss_train: 20.596283, Loss_train: 21.124844
i: 1300, Loss_train: 20.030684, Loss_train: 20.769012
i: 1400, Loss_train: 19.510202, Loss_train: 20.467239
i: 1500, Loss_train: 19.030268, Loss_train: 20.212137
i: 1600, Loss_train: 18.587011, Loss_train: 19.997717
i: 1700, Loss_train: 18.177065, Loss_train: 19.818951
i: 1800, Loss_train: 17.797461, Loss_train: 19.671589
i: 1900, Loss_train: 17.445545, Loss_train: 19.551966
i: 2000, Loss_train: 17.118927, Loss_train: 19.456860
i: 2100, Loss_train: 16.815464, Loss_train: 19.383457
i: 2200, Loss_train: 16.533215, Loss_train: 19.329269
i: 2300, Loss_train: 16.270422, Loss_train: 19.292067
i: 2400, Loss_train: 16.025496, Loss_train: 19.269896
i: 2500, Loss_train: 15.796996, Loss_train: 19.260981
i: 2600, Loss_train: 15.583610, Loss_train: 19.263735
i: 2700, Loss_train: 15.384136, Loss_train: 19.276777
i: 2800, Loss_train: 15.197502, Loss_train: 19.298813
i: 2900, Loss_train: 15.022708, Loss_train: 19.328751
i: 3000, Loss_train: 14.858855, Loss_train: 19.365536
这是训练8000轮的损失变化
可以看到在2500~3000轮时测试及损失和,开始出现分化,通过这个图也可以看出来,在训练集上预测房价和实际房价更加接近了,但是在测试集上预测房价和实际房价的偏差更大了。
现在我们已经建立了波士顿房价的多元线性回归模型,可以使用它来预测房价了,要说明的是这个数据集中的数据比较老,是1978年统计的美国麻省波士顿的房屋价格数据,不具备通用性。而且房价与很多因素都有关系,我们只是利用它来学习算法而已,如果用它来预测当前中国的房价,绝对是谬以千里。