回归:预测汽车燃油效率
在 回归 (regression) 问题中,我们的目的是预测出如价格或概率这样连续值的输出。相对于分类(classification) 问题,分类(classification) 的目的是从一系列的分类出选择出一个分类 (如,给出一张包含苹果或橘子的图片,识别出图片中是哪种水果)。
1.导入基本的库
#使用面向对象的编程方式来表示文件系统路径。
import pathlib
import matplotlib.pyplot as plt
import pandas as pd
# 使用 seaborn 绘制矩阵图 (pairplot)
import seaborn as sns
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
2.Auto MPG 数据集
数据涉及城市循环燃油消耗量,单位为英里/加仑,将根据3个多值离散和5个连续属性进行预测。
- mpg: continuous 连续
- cylinders: multi-valued discrete 多值离散
- displacement: continuous 连续
- horsepower: continuous 连续
- weight: continuous 连续
- acceleration: continuous 连续
- model year: multi-valued discrete 多值离散
- origin: multi-valued discrete 多值离散
- car name: string (unique for each instance)
2.1下载数据
dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
dataset_path
'C:\\Users\\13487\\.keras\\datasets\\auto-mpg.data'
2.2 使用 pandas 导入数据集。
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(dataset_path,
names=column_names, #指定列名,如果文件中不包含header的行,应该显性表示header=None
na_values = "?", #将?识别为空值
comment='\t', #指示不应分析行的其余部分。如果在一行的开头找到\t,该行将被完全忽略。
sep=" ", #指定分割符,默认是’,’
skipinitialspace=True)#忽略分隔符后的空格,默认false
#是想改变其中的数据,而不影响其他的
dataset = raw_dataset.copy()
#输出调用者对象的最后 n 行。
dataset.tail()
MPG | Cylinders | Displacement | Horsepower | Weight | Acceleration | Model Year | Origin | |
---|---|---|---|---|---|---|---|---|
393 | 27.0 | 4 | 140.0 | 86.0 | 2790.0 | 15.6 | 82 | 1 |
394 | 44.0 | 4 | 97.0 | 52.0 | 2130.0 | 24.6 | 82 | 2 |
395 | 32.0 | 4 | 135.0 | 84.0 | 2295.0 | 11.6 | 82 | 1 |
396 | 28.0 | 4 | 120.0 | 79.0 | 2625.0 | 18.6 | 82 | 1 |
397 | 31.0 | 4 | 119.0 | 82.0 | 2720.0 | 19.4 | 82 | 1 |
2.3 数据清洗
数据集中包括一些未知值。
#统计数据中空值数据的个数
dataset.isna().sum()
MPG 0
Cylinders 0
Displacement 0
Horsepower 6
Weight 0
Acceleration 0
Model Year 0
Origin 0
dtype: int64
为了保证这个初始示例的简单性,删除这些行。
dataset = dataset.dropna()
2.4 original转换为独热码 (one-hot)
#返回项目并从数据集中删除origin。
origin = dataset.pop('Origin')
#增加三列,用独热编码展示
dataset['USA'] = (origin == 1)*1.0
dataset['Europe'] = (origin == 2)*1.0
dataset['Japan'] = (origin == 3)*1.0
dataset.tail()
MPG | Cylinders | Displacement | Horsepower | Weight | Acceleration | Model Year | USA | Europe | Japan | |
---|---|---|---|---|---|---|---|---|---|---|
393 | 27.0 | 4 | 140.0 | 86.0 | 2790.0 | 15.6 | 82 | 1.0 | 0.0 | 0.0 |
394 | 44.0 | 4 | 97.0 | 52.0 | 2130.0 | 24.6 | 82 | 0.0 | 1.0 | 0.0 |
395 | 32.0 | 4 | 135.0 | 84.0 | 2295.0 | 11.6 | 82 | 1.0 | 0.0 | 0.0 |
396 | 28.0 | 4 | 120.0 | 79.0 | 2625.0 | 18.6 | 82 | 1.0 | 0.0 | 0.0 |
397 | 31.0 | 4 | 119.0 | 82.0 | 2720.0 | 19.4 | 82 | 1.0 | 0.0 | 0.0 |
3 拆分训练数据集和测试数据集
train_dataset = dataset.sample(frac=0.8,random_state=0)
test_dataset = dataset.drop(train_dataset.index)
3.1 数据检查
快速查看训练集中几对列的联合分布。
#该函数可以显示变量子集或绘制不同的图形行和列上的变量。
sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde")
#密度图也被称作KDE(Kernel Density Estimate,核密度估计)图
<seaborn.axisgrid.PairGrid at 0x2c4d888e288>
也可以查看总体的数据统计:
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
3.2 从标签中分离特征
将特征值从目标值或者"标签"中分离。 这个标签是你使用训练模型进行预测的值。
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')
3.3 数据规范化
再次审视下上面的 train_stats
部分,并注意每个特征的范围有什么不同。
使用不同的尺度和范围对特征归一化是好的实践。尽管模型可能 在没有特征归一化的情况下收敛,它会使得模型训练更加复杂,并会造成生成的模型依赖输入所使用的单位选择。
注意:尽管我们仅仅从训练集中有意生成这些统计数据,但是这些统计信息也会用于归一化的测试数据集。我们需要这样做,将测试数据集放入到与已经训练过的模型相同的分布中。
def norm(x):
return (x - train_stats['mean']) / train_stats['std']
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)
我们将会使用这个已经归一化的数据来训练模型。
警告: 用于归一化输入的数据统计(均值和标准差)需要反馈给模型从而应用于任何其他数据,以及我们之前所获得独热码。这些数据包含测试数据集以及生产环境中所使用的实时数据。
4 模型
4.1 构建模型
让我们来构建我们自己的模型。这里,我们将会使用一个“顺序”模型,其中包含两个紧密相连的隐藏层,以及返回单个、连续值得输出层。模型的构建步骤包含于一个名叫 ‘build_model’ 的函数中,稍后我们将会创建第二个模型。 两个密集连接的隐藏层。
def build_model():
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
optimizer=optimizer,
metrics=['mae', 'mse'])
return model
model = build_model()
4.2 训练模型
对模型进行1000个周期的训练,并在 history
对象中记录训练和验证的准确性。
# 通过为每个完成的时期打印一个点来显示训练进度
class PrintDot(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs):
if epoch % 100 == 0: print('')
print('.', end='')
EPOCHS = 1000
#预测MPG
history = model.fit(
normed_train_data, train_labels,
epochs=EPOCHS, validation_split = 0.2, verbose=0,
callbacks=[PrintDot()])
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
....................................................................................................
使用 history
对象中存储的统计信息可视化模型的训练进度。
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
loss | mae | mse | val_loss | val_mae | val_mse | epoch | |
---|---|---|---|---|---|---|---|
995 | 2.805213 | 1.083687 | 2.805213 | 9.537119 | 2.361976 | 9.537119 | 995 |
996 | 2.844828 | 1.121372 | 2.844828 | 9.127841 | 2.371664 | 9.127841 | 996 |
997 | 2.991760 | 1.116026 | 2.991760 | 8.750285 | 2.277748 | 8.750285 | 997 |
998 | 2.695455 | 1.042576 | 2.695455 | 8.830916 | 2.302002 | 8.830916 | 998 |
999 | 2.747425 | 1.072627 | 2.747425 | 8.984677 | 2.320511 | 8.984677 | 999 |
def plot_history(history):
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Abs Error [MPG]')
plt.plot(hist['epoch'], hist['mae'],
label='Train Error')
plt.plot(hist['epoch'], hist['val_mae'],
label = 'Val Error')
plt.ylim([0,5])
plt.legend()
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Square Error [$MPG^2$]')
plt.plot(hist['epoch'], hist['mse'],
label='Train Error')
plt.plot(hist['epoch'], hist['val_mse'],
label = 'Val Error')
plt.ylim([0,20])
plt.legend()
plt.show()
plot_history(history)
该图表显示在约100个 epochs 之后误差非但没有改进,反而出现恶化。 更新 model.fit
调用,当验证值没有提高时自动停止训练。
我们将使用一个 EarlyStopping callback 来测试每个 epoch 的训练条件。如果经过一定数量的 epochs 后没有改进,则自动停止训练。
4.3 EarlyStopping callback测试
model = build_model()
# patience 值用来检查改进 epochs 的数量
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)
history = model.fit(normed_train_data, train_labels, epochs=EPOCHS,
validation_split = 0.2, verbose=0, callbacks=[early_stop, PrintDot()])
plot_history(history)
...............................................
让我们看看通过使用 测试集 来泛化模型的效果如何,我们在训练模型时没有使用测试集。这告诉我们,当我们在现实世界中使用这个模型时,我们可以期望它预测得有多好。
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2)
print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))
3/3 - 0s - loss: 7.0770 - mae: 2.0769 - mse: 7.0770 - 14ms/epoch - 5ms/step
Testing set Mean Abs Error: 2.08 MPG
4.做预测
最后,使用测试集中的数据预测 MPG 值:
#将数据展平,这样test_labels和test_predictions的维度就都为(78,),可以绘制散点图
test_predictions = model.predict(normed_test_data).flatten()
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
#进行缩放
plt.axis('equal')
plt.axis('square')
#plt.xlim()[1]大约等于MPG取值的最大值
plt.xlim([0,plt.xlim()[1]])
plt.ylim([0,plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100])
这看起来我们的模型预测得相当好。我们来看下误差分布。
error = test_predictions - test_labels
plt.hist(error, bins = 25)
#bins:柱的个数
plt.xlabel("Prediction Error [MPG]")
_ =plt.ylabel("Count")
它不是完全的高斯分布,但我们可以推断出,这是因为样本的数量很小所导致的。
结论
本笔记本 (notebook) 介绍了一些处理回归问题的技术。
- 均方误差(MSE)是用于回归问题的常见损失函数(分类问题中使用不同的损失函数)。
- 类似的,用于回归的评估指标与分类不同。 常见的回归指标是平均绝对误差(MAE)。
- 当数字输入数据特征的值存在不同范围时,每个特征应独立缩放到相同范围。
- 如果训练数据不多,一种方法是选择隐藏层较少的小网络,以避免过度拟合。
- 早期停止是一种防止过度拟合的有效技术。
参考文献
https://tensorflow.google.cn/tutorials/keras/regression