Skip to content

Keras - Regression Prediction using MPL

虽然之前的示例着重于分类(预测离散类别),但 Keras 同样擅长回归任务,其目标是预测连续的数值。一个常见的示例是根据大小、位置和房间数量等特征预测房价。

在本章中,我们将使用 tf.keras 构建一个 Multi-Layer Perceptron (MLP) 来预测房价,使用 Boston Housing 数据集,这是一个经典的回归基准数据集,可在 tf.keras.datasets 中获取。

回归模型的关键区别:

  • 输出层: 通常只有一个神经元(如果预测一个值),并且经常使用线性激活函数(或无激活函数),因为输出不像概率那样被限制在特定范围内。
  • 损失函数: 常见选择包括均方误差('mse')或平均绝对误差('mae')。这些衡量预测连续值与实际连续值之间的平均差异。
  • 指标: 评估通常使用 MAE(平均绝对差)或 RMSE(均方根误差)等指标,以了解目标变量原始单位(例如,房价的美元)中的典型误差大小。

回归 MLP 的概念结构:

输入特征 -> 全连接隐藏层(例如,ReLU 激活) -> 全连接输出层(1 个单元,线性激活) -> 预测值。

模型规格:

  • 数据集: 波士顿房价数据集(13 个输入特征,1 个目标值:房屋中位价格)。
  • 预处理: 特征缩放(归一化/标准化)对于具有不同尺度的特征的回归任务至关重要。
  • 架构: 具有两个隐藏全连接层(例如,64 个单元,ReLU 激活)的 MLP。
  • 输出层: 具有 1 个单元的全连接层(未指定激活函数,默认为线性)。
  • 编译: 'mse' 损失、'RMSprop' 或 'Adam' 优化器、'mae' 指标。
  • 训练: 使用 EarlyStopping 等回调函数防止过拟合。
import tensorflow as tf
from tensorflow.keras.datasets import boston_housing
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Normalization # 添加了 Normalization 层
from tensorflow.keras.optimizers import Adam # 使用 Adam 优化器
from tensorflow.keras.callbacks import EarlyStopping
import numpy as np
import matplotlib.pyplot as plt # 用于绘制结果
# 用于 sklearn 预处理(Normalization 层的替代方案)
# from sklearn.preprocessing import StandardScaler
# 加载数据
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()
print(f"训练数据形状: {x_train.shape}")
print(f"测试数据形状: {x_test.shape}")
print(f"训练标签形状: {y_train.shape}")
# 示例:查看第一个训练样本的特征和标签
# print("第一个训练样本特征:", x_train[0])
# print("第一个训练样本标签(价格,单位 $1000):", y_train[0])

数据集每个样本有 13 个输入特征。

第 3 步:预处理数据(特征缩放)

Section titled “第 3 步:预处理数据(特征缩放)”

Boston Housing 数据集中的特征范围各不相同。对它们进行缩放(例如,使其具有零均值和单位方差)有助于模型更有效地训练。我们可以使用 scikit-learn 的 StandardScaler 或 Keras 的 Normalization 层。

方法 1:使用 Keras Normalization 层(推荐)

Section titled “方法 1:使用 Keras Normalization 层(推荐)”

此层可以直接包含在模型中。它会适应训练数据并在推理期间应用相同的转换。

# 初始化 Normalization 层
normalizer = Normalization(axis=-1) # 独立归一化特征
# 使层适应训练数据(计算均值和方差)
normalizer.adapt(x_train)
print('\n特征均值:', normalizer.mean.numpy())
print('特征方差:', normalizer.variance.numpy())
# 注意:我们将把这个 'normalizer' 层作为模型的第一层添加。

方法 2:使用 Scikit-learn StandardScaler(替代方案)

Section titled “方法 2:使用 Scikit-learn StandardScaler(替代方案)”

这在模型外部执行缩放。

# scaler = StandardScaler()
# x_train_scaled = scaler.fit_transform(x_train)
# x_test_scaled = scaler.transform(x_test) # 对测试数据使用 transform,而不是 fit_transform!
# print("\n缩放后的形状 (sklearn):")
# print("训练集:", x_train_scaled.shape)
# print("测试集:", x_test_scaled.shape)
# 如果使用此方法,模型的 input_shape 仍将是 (13,)
# 并且你会将 x_train_scaled/x_test_scaled 传递给 fit/evaluate 方法。

我们将构建一个简单的 Sequential 模型,包括 Normalization 层(如果使用方法 1)。

input_shape = (x_train.shape[1],) # 应为 (13,)
model = Sequential([
tf.keras.Input(shape=input_shape), # 显式定义输入层
normalizer, # 添加已适应的 normalization 层(方法 1)
Dense(64, activation='relu'),
Dense(64, activation='relu'),
Dense(1) # 输出层:1 个单元,线性激活(默认)
], name="boston_housing_mlp")
model.summary()

如果使用 scikit-learn 缩放(方法 2),则从模型定义中省略 normalizer 层。

使用合适的回归损失和指标进行编译。

model.compile(loss='mean_absolute_error', # 使用 MAE 作为损失
optimizer=Adam(learning_rate=0.001),
metrics=['mae', 'mse']) # 同时跟踪 MAE 和 MSE

训练模型,使用 EarlyStopping 防止过拟合并找到一个好的停止点。

epochs = 500
# 定义 EarlyStopping 回调函数
early_stopping = EarlyStopping(
monitor='val_loss', # 监控验证损失
patience=20, # 在此数量的 epoch 中性能没有提升时停止训练
restore_best_weights=True # 恢复监控指标值最佳的那个 epoch 的模型权重
)
print("\n训练模型...")
history = model.fit(
x_train, y_train, # 如果使用 Normalization 层,则传递原始 x_train
# x_train_scaled, y_train, # 如果使用 sklearn scaler,则传递缩放后的数据
epochs=epochs,
validation_split=0.2, # 使用 20% 的训练数据进行验证
verbose=0, # 设置 verbose=0 以减少训练期间的输出
callbacks=[early_stopping]
)
print("\n训练完成。")
print(f"停止于 epoch: {early_stopping.stopped_epoch}")

在未见过的测试集上评估性能。

print("\n在测试数据上评估...")
results = model.evaluate(x_test, y_test, verbose=0)
# results = model.evaluate(x_test_scaled, y_test, verbose=0) # 如果使用 sklearn scaler
print(f"测试损失 (MAE): {results[0]:.2f}")
print(f"测试 MAE: {results[1]:.2f}")
print(f"测试 MSE: {results[2]:.2f}")

‘测试 MAE’ 告诉你模型在测试集上预测的房价与实际房价之间的平均绝对差异(单位 $1000)。

对测试集进行预测,并将预测值与实际值进行比较。

test_predictions = model.predict(x_test).flatten()
# test_predictions = model.predict(x_test_scaled).flatten() # 如果使用 sklearn scaler
# 预测值与实际值的简单散点图
plt.figure(figsize=(8, 8))
plt.scatter(y_test, test_predictions)
plt.xlabel('True Values [Price in $1000s]')
plt.ylabel('Predictions [Price in $1000s]')
plt.axis('equal')
plt.axis('square')
plt.xlim([0, plt.xlim()[1]])
plt.ylim([0, plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100]) # 对角线
plt.title('回归预测 vs 实际值')
plt.grid(True)
plt.show()
# 绘制训练和验证损失曲线
plt.figure(figsize=(10, 6))
plt.plot(history.history['loss'], label='训练集 MAE 损失')
plt.plot(history.history['val_loss'], label='验证集 MAE 损失')
plt.title('训练和验证损失 (MAE)')
plt.xlabel('Epoch')
plt.ylabel('损失 (MAE)')
plt.legend()
plt.grid(True)
plt.show()

散点图有助于可视化模型的性能。靠近对角线的点表示预测准确。损失曲线显示了模型在 epoch 训练过程中的学习情况,有助于诊断过拟合或欠拟合问题。