1. 回归问题
1.1 波士顿房价数据集加载:
-
预测20世界70年代中期波士顿郊区房屋价格的中位数。
-
已知当时郊区有一些数据点,如犯罪率、房产税率等。
-
与IMDB和路透社数据集相比,波士顿房价数据集样本量比较少,只有506个样本;同时,每个特征都都有不同的取值范围。
-
本次实验将其中102个作为测试样本,剩下的404个作为训练样本。
## 加载波士顿房价数据集
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
## 因为是回归问题,所以不再用labels,而是用targets
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/boston_housing.npz
57026/57026 [==============================] - 0s 4us/step
print(train_data.shape) ##训练样本数和特征数(单位是:千美元)
print(train_data) ## 训练样本
(404, 13)
[[1.23247e+00 0.00000e+00 8.14000e+00 ... 2.10000e+01 3.96900e+02
1.87200e+01]
[2.17700e-02 8.25000e+01 2.03000e+00 ... 1.47000e+01 3.95380e+02
3.11000e+00]
[4.89822e+00 0.00000e+00 1.81000e+01 ... 2.02000e+01 3.75520e+02
3.26000e+00]
...
[3.46600e-02 3.50000e+01 6.06000e+00 ... 1.69000e+01 3.62250e+02
7.83000e+00]
[2.14918e+00 0.00000e+00 1.95800e+01 ... 1.47000e+01 2.61950e+02
1.57900e+01]
[1.43900e-02 6.00000e+01 2.93000e+00 ... 1.56000e+01 3.76700e+02
4.38000e+00]]
print(test_data.shape) ## 测试样本数和特征数
print(test_data) ## 测试样本
(102, 13)
[[1.80846e+01 0.00000e+00 1.81000e+01 ... 2.02000e+01 2.72500e+01
2.90500e+01]
[1.23290e-01 0.00000e+00 1.00100e+01 ... 1.78000e+01 3.94950e+02
1.62100e+01]
[5.49700e-02 0.00000e+00 5.19000e+00 ... 2.02000e+01 3.96900e+02
9.74000e+00]
...
[1.83377e+00 0.00000e+00 1.95800e+01 ... 1.47000e+01 3.89610e+02
1.92000e+00]
[3.58090e-01 0.00000e+00 6.20000e+00 ... 1.74000e+01 3.91700e+02
9.71000e+00]
[2.92400e+00 0.00000e+00 1.95800e+01 ... 1.47000e+01 2.40160e+02
9.81000e+00]]
print(train_targets[0:10]) ## 预测目标 (房价的中位数)
print(test_targets[0:10]) ## 测试目标 (房价的中位数)
[15.2 42.3 50. 21.1 17.7 18.5 11.3 15.6 15.6 14.4]
[ 7.2 18.8 19. 27. 22.2 24.5 31.2 22.9 20.5 23.2]
1.2 数据预处理:
因为不同特征的取值范围差别比较大,所以不能直接输入神经网络中。因此需要先对这些特征做标准化 (主要是对列做标准差处理,使每一列得到均值为0,标准差为1的特征值)。
mean = train_data.mean(axis=0) ## 取每一列的均值
train_data -= mean ## 每一列特征值减去该列的均值
std = train_data.std(axis=0) ## 取每一列的标准差
train_data /= std ## 得到标准化结果
## 需要注意的是:这里测试集的均值和标准差用的也是训练集上的,因为在工作流程中,不能使用在测试集上计算得到的任何结果。
test_data -= mean
test_data /= std
1.3 构建网络:
因为样本较少,所以用一个比较小的网络:两个隐藏层,每层含有32个隐藏单元。
from keras import models
from keras import layers
def build_model():
## 因为后面要用交叉验证,所以需要将一个模型进行多次的实例化,所以这里定义了一个函数
## 构建模型
model = models.Sequential()
model.add(layers.Dense(32, activation="relu", input_shape=(train_data.shape[1],))) ## train_data.shape[1]=13,因为有13个特征
model.add(layers.Dense(32, activation="relu"))
model.add(layers.Dense(1)) ## 因为目的是预测房价中位数(是一个数值),所以保留原始输出即可。没有添加激活函数,因为激活函数会限制输出范围。
## 编译模型
model.compile(optimizer="rmsprop",
loss="mse", ## 损失函数用的是 均方误差 (mse, mean squared error),预测值与真实值之差的平方,是回归问题中常用的损失函数
metrics=["mae"]) ## 监控指标用的是 平均绝对误差 (mae, mean absolute error) ,预测值与真实值之差的绝对值
return model
1.4 K
折交叉验证:
在调节网络参数 (比如epochs等) 的同时对网络进行评估,可以用K折交叉验证的方法来处理。
K折交叉验证:将数据集分为 K 份,实例化 K 个相同的模型。将每个模型在 K-1 个数据集上训练,在剩下的那一个数据集上做验证评估。模型的验证分数等于 K 个验证分数的均值。
import numpy as np
k = 4 ## K折
num_val_samples = len(train_data) // k ## 验证集中的样本数,将训练集分为k份,每一份都会用作验证集
num_epochs = 500 ## 训练500次
all_mae_histories = [] ##保留每一轮训练中所有的 mae
for i in range(k):
print("processing fold #", i) ## 每一折都要单独做
val_data = train_data[i*num_val_samples: (i+1)*num_val_samples] ## 第k份的数据作为验证集
val_targets = train_targets[i*num_val_samples: (i+1)*num_val_samples] ## 验证标签
partial_train_data = np.concatenate([train_data[:i*num_val_samples], train_data[(i+1)*num_val_samples:]], axis=0) ## 其他的 k-1 份数据集合并,作为训练集
partial_train_targets = np.concatenate([train_targets[:i*num_val_samples], train_targets[(i+1)*num_val_samples:]], axis=0) ## 训练标签
model = build_model() ## 每一折都要实例化一个相同的模型
## 在训练集上训练模型,在验证集上评估模型
history = model.fit(partial_train_data,
partial_train_targets,
epochs=num_epochs,
batch_size=1,
verbose=0,
validation_data=(val_data, val_targets))
mae_history = history.history["val_mae"]
all_mae_histories.append(mae_history)
## 模型参数的 verbose 用法详解: https://blog.csdn.net/qq_23933415/article/details/111212898
processing fold # 0
processing fold # 1
processing fold # 2
processing fold # 3
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)] ## 求每一次训练得到的4个模型的mae的平均值
1.5 可视化 监控指标 (mae):
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个点之后(因为前10个点取值范围与其他点不同),剩下的每个数据点做一下处理:
将每个数据点替换为前面数据点的指数移动平均值,用于得到光滑的曲线。
就是前一个点占0.9,当前点占0.1,二者加和作为当前点。
## 舍弃前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)) ## 当前点 = 前一个点*0.9 + 当前点*0.1
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()
1.6 从头开始训练最终模型:
从上述平滑后的曲线图可以看出,但epochs大约为90时,Validation MAE最小。所以选择epochs=80来重新训练模型。
model = build_model()
model.fit(train_data, train_targets, epochs=90, batch_size=8, verbose=0) ## 在所有的训练数据集上重新训练模型。
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
4/4 [==============================] - 0s 25ms/step - loss: 19.7469 - mae: 2.7381
## 最终预测结果:
print(test_mae_score)
## 结果表示 预测价格的中位数和实际价格的中位数之间相差大约2738美元。
2.738111972808838
1.7 小结:
-
回归问题常用的损失函数是 均方误差 (MSE);
-
回归问题的评估指标最常用的是 平均绝对误差 (MAE);
-
如果输入特征有不同的取值范围,那么需要各自进行标准化处理 (需要注意的是:测试集的均值和标准差用的也是训练集上的,因为在工作流程中,不能使用在测试集上计算得到的任何结果);
-
如果可用的样本数据较少,可以用 K折交叉验证对模型进行可靠地评估;
-
如果训练数据较少,可以使用更小的网络来 (更少的层数、更少的隐藏单元个数) 避免出现严重的过拟合;
-
在从头开始重新训练模型时,要用全部的训练数据 (包括训练集和验证集) 进行模型的训练。