所用环境为Colab.
定义任务目标
拿到某地区的一些信息, 如犯罪率, 地方房产税率等, 预测该地区的房价.
数据收集
使用 波士顿房价 数据集.
1
2
3
| # 加载数据集(需联网)
from tensorflow.keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
|
数据可视化
建立对数据形状的感受.
1
2
3
4
5
6
| # 检查数据
train_data.shape, train_targets.shape, test_data.shape, test_targets.shape, train_data.dtype
# ((404, 13), (404,), (102, 13), (102,), dtype('float64'))
type(train_data), type(train_data[0]), train_data.ndim
# (numpy.ndarray, numpy.ndarray, 2)
|
看一下数据集里的数据到底长什么样子, 数据对应的标签是什么.
1
2
3
4
5
| train_data[0]
# array([ 1.23247, 0. , 8.14 , 0. , 0.538 , 6.142 , 91.7 , 3.9769 , 4. , 307. , 21. , 396.9 , 18.72 ])
train_targets[0]
# 15.2
|
数据标注
已标注好
数据清理
无需清理
数据预处理
数据向量化和标准化
1
2
3
4
5
6
7
8
9
| # 数据标准化
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean # 注意, 测试数据标准化所用的均值和标准差值也是从训练数据里得到的, 而不是用测试数据的.
test_data /= std
|
处理缺失值
不需要处理缺失值.
数据划分: 训练集, 验证集, 测试集
数据量太少了, 使用K折交叉验证.
选择模型评估方法
评估方法选择
这里选择平均绝对误差(mean absolute error, MAE)作为模型评估的指标. 它是预测值与目标值之差的绝对值.
如果这个问题的MAE等于0.5, 就表示预测房价与实际价格平均相差500美元.
确定简单基准(模型应能超越这个基准)
无
构建第一个模型
特征选择(过滤没有信息量的特征; 开发新特征)
在本例中特征就是该地区各房屋周边的相关信息了.
选择架构
两个中间层, 每层64个单元. 第三层输出预测值.
层1: Dense层, “表示空间"的维数units
为64
, 激活函数activation
为relu
.
层2: Dense层, “表示空间"的维数units
为64
, 激活函数activation
为relu
.
层3: Dense层, “表示空间"的维数units
为1
, 它是一个线性层, 没有激活函数. 这是标量回归(预测单一连续值的回归)的典型设置.
训练配置(损失函数, 批量大小, 学习率)
优化器 optimizer
这里选rmsprop
损失函数 loss function
这里选均方误差(mean squared error, MSE)损失函数mse
, 预测值与目标值之差的平方.
训练轮数
这里训练100轮.
数据批量大小
批量大小设为16.
模型构建代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| from tensorflow import keras
from tensorflow.keras import layers
# 由于需要将同一个模型多次实例化, 因此用一个函数来构建模型.
def build_model():
# 构建模型
model = keras.Sequential([
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(1)
])
# 编译模型
model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
return model
|
拟合模型 k折交叉验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import numpy as np
k = 4 # 分为4折, 3折作为训练数据, 1折作为验证数据
num_val_samples = len(train_data) // k # 多少条数据作为验证数据
num_epochs = 100 # 训练100轮
all_scores = []
for i in range(k):
print(f"Processing fold #{i}")
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] # 准备验证数据: 第i个分区的数据
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples] # 验证数据目标值: 第i个分区的数据
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)
model = build_model() # 构建模型(已编译)
model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=16, verbose=0) # 训练模型(静默模式, verbose=0)
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0) # 在验证数据上评估模型
all_scores.append(val_mae)
|
输出:
Processing fold #0
Processing fold #1
Processing fold #2
Processing fold #3
训练时各轮的所有分数:
1
2
3
4
5
| all_scores
# [1.963736653327942, 2.7470197677612305, 2.514702558517456, 2.423671007156372]
np.mean(all_scores)
# 2.41228249669075
|
改进模型
上面的模型, 最后得到的是在每一折上训练100轮后的得分(mae). 现在修改模型, 在每一折上训练500轮, 把每一轮的得分都记录下来. 最后计算每一轮在每一折上的平均得分. 最后得到的应该是500个值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import numpy as np
k = 4 # 分为4折, 3折作为训练数据, 1折作为验证数据
num_val_samples = len(train_data) // k # 多少条数据作为验证数据
num_epochs = 500 # 训练100轮
all_mae_histories = []
for i in range(k):
print(f"Processing fold #{i}")
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] # 准备验证数据: 第i个分区的数据
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples] # 验证数据目标值: 第i个分区的数据
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)
model = build_model() # 构建模型(已编译)
history = model.fit(partial_train_data, partial_train_targets, validation_data=(val_data, val_targets), epochs=num_epochs, batch_size=16, verbose=0) # 训练模型(静默模式, verbose=0)
mae_history = history.history["val_mae"]
all_mae_histories.append(mae_history)
# Processing fold #0
# Processing fold #1
# Processing fold #2
# Processing fold #3
|
结果:
1
2
3
4
5
6
| len(all_mae_histories)
# 4
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
len(average_mae_history)
# 500
|
可视化拟合结果
绘制验证MAE曲线
1
2
3
4
5
6
| 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()
|

前面几轮的验证MAE远大于后面的轮次, 很难从图中发现规律. 忽略前10个数据点.
绘制验证MAE曲线(剔除前10个数据点)
1
2
3
4
5
6
7
8
| import matplotlib.pyplot as plt
truncated_mae_history = average_mae_history[10:]
plt.plot(range(1, len(truncated_mae_history) + 1), truncated_mae_history)
plt.xlabel("Epochs")
plt.ylabel("Validation MAE")
plt.show()
|

从图中看出, 当训练到120轮之后验证数据的MAE就不再显著降低, 之后就开始过拟合.
训练最终模型
1
2
3
| model = build_model()
model.fit(train_data, train_targets, epochs=130, batch_size=16, verbose=0) # 在所有训练数据上训练模型
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
|
结果:
1
2
| test_mae_score
# 2.342545747756958
|
评价模型
上面得到的MAE为2.34, 也就是说, 房价预测值和实际值平均相差了2340美元. 差距依旧很大, 不过比最开始的模型有了一点点进步.
利用模型进行预测
拿到一栋房屋的各项特征数据, 把它像上面那样进行数据预处理, 之后用model.predict()
进行预测.
1
2
| predictions = model.predict(test_data)
predictions[0]
|
结果:
4/4 [==============================] - 0s 4ms/step
array([7.722374], dtype=float32)
预测得到的值为7.722374
, 表明模型认为, 测试数据里第一所房子的价格约为7722美元.
1
2
| test_targets[0]
# 7.2
|
房屋的实际价格是7200美元, 预测结果和真实价格相差了500美元.
部署模型
不部署.