TensorFlow 2之回归预测燃油效率
背景
前面的例子均为分类问题,类别是固定的,目的是判断属于哪一类。本文中的回归问题,是用来预测连续值,比如价格和概率。
本文使用经典的 Auto MPG 数据集,这个数据集包括气缸(cylinders),排量(displayment),马力(horsepower) 和重量(weight)等属性。我们需要利用这些属性搭建模型,预测汽车的燃油效率(fuel efficiency)。
数据集
初体验
import pathlib
import matplotlib.pyplot as plt
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import seaborn as sns
# # 第一步 准备数据集
# 下载数据集到本地
url = "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"
dataset_path = keras.utils.get_file("auto-mpg.data", url)
# 使用Pandas读取数据
column_names = ['MPG', '气缸', '排量', '马力', '重量', '加速度', '年份', '产地']
raw_dataset = pd.read_csv(dataset_path, names=column_names,
na_values="?", comment='\t',
sep=" ", skipinitialspace=True)
dataset = raw_dataset.copy()
# 查看前3条数据
# print(dataset.head(3))
# # MPG 气缸 排量 马力 重量 加速度 年份 产地
# # 0 18.0 8 307.0 130.0 3504.0 12.0 70 1
# # 1 15.0 8 350.0 165.0 3693.0 11.5 70 1
# # 2 18.0 8 318.0 150.0 3436.0 11.0 70 1
# 第二步 预处理
# 1. 清洗数据
# 检查是否有 NA 值。
# print(dataset.isna().sum())
# # MPG 0
# # 气缸 0
# # 排量 0
# # 马力 6
# # 重量 0
# # 加速度 0
# # 年份 0
# # 产地 0
# # dtype: int64
# 直接去除含有NA值的行(马力)
dataset = dataset.dropna()
# 在获取的数据集中,Origin(产地)不是数值类型,需转为独热编码。
origin = dataset.pop('产地')
dataset['美国'] = (origin == 1)*1.0
dataset['欧洲'] = (origin == 2)*1.0
dataset['日本'] = (origin == 3)*1.0
# 看一看转换后的结果
# print(dataset.head(3))
# # MPG 气缸 排量 马力 重量 加速度 年份 美国 欧洲 日本
# # 0 18.0 8 307.0 130.0 3504.0 12.0 70 1.0 0.0 0.0
# # 1 15.0 8 350.0 165.0 3693.0 11.5 70 1.0 0.0 0.0
# # 2 18.0 8 318.0 150.0 3436.0 11.0 70 1.0 0.0 0.0
# 2. 划分训练集与测试集:训练集 80%, 测试集 20%
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)
# 检查数据
# 快速看一看训练集中属性两两之间的关系吧。
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
sns.pairplot(train_dataset[["MPG", "气缸", "排量", "重量"]], diag_kind="kde")
# 也可以使用train_dataset.describle()快速浏览每一属性的平均值、标准差、最小值、最大值等信息,能够帮助你快速地识别出不合理的数据。
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
# print(train_stats)
# # count mean std min 25% 50% 75% max
# # 气缸 314.0 5.477707 1.699788 3.0 4.00 4.0 8.00 8.0
# # 排量 314.0 195.318471 104.331589 68.0 105.50 151.0 265.75 455.0
# # 马力 314.0 104.869427 38.096214 46.0 76.25 94.5 128.00 225.0
# # 重量 314.0 2990.251592 843.898596 1649.0 2256.50 2822.5 3608.00 5140.0
# # 加速度 314.0 15.559236 2.789230 8.0 13.80 15.5 17.20 24.8
# # 年份 314.0 75.898089 3.675642 70.0 73.00 76.0 79.00 82.0
# # 美国 314.0 0.624204 0.485101 0.0 0.00 1.0 1.00 1.0
# # 欧洲 314.0 0.178344 0.383413 0.0 0.00 0.0 0.00 1.0
# # 日本 314.0 0.197452 0.398712 0.0 0.00 0.0 0.00 1.0
# 3. 分离 label
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')
# 4. 归一化数据
# 通常训练前需要归一化数据,不同属性使用的计量单位不一样,值的范围不一样,训练就会很困难。
# 比如其中一个属性的范围是[0.1, 0.5],而另一个属性的范围是[1000, 5000],那数值大的属性就容易对训练产生干扰,
# 很可能导致训练不能收敛,或者是数值小的属性在模型中几乎没有发挥作用。
# 归一化将不同范围的数据映射到[0,1]的空间内,可以有效地避免这个问题。
def norm(x):
return (x - train_stats['mean']) / train_stats['std']
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)
# 第三步 搭建模型
# 包含2个全连接的隐藏层构成,输出层返回一个连续值。
def build_model():
input_dim = len(train_dataset.keys())
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=[input_dim, ]),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
return model
model = build_model()
# 打印模型的描述信息,每一层的大小、参数个数等
# model.summary()
# # Model: "sequential"
# # _________________________________________________________________
# # Layer (type) Output Shape Param #
# # =================================================================
# # dense (Dense) (None, 64) 640
# # _________________________________________________________________
# # dense_1 (Dense) (None, 64) 4160
# # _________________________________________________________________
# # dense_2 (Dense) (None, 1) 65
# # =================================================================
# # Total params: 4,865
# # Trainable params: 4,865
# # Non-trainable params: 0
# # _________________________________________________________________
# 第四步 编译模型
model.compile(loss='mse', metrics=['mae', 'mse'],
optimizer=tf.keras.optimizers.RMSprop(0.001))
# 第五步 训练模型
# 我们禁用默认的行为,并自定义训练进度条。
history = model.fit(
normed_train_data, train_labels,
epochs=1000, validation_split=0.2, verbose=0)
# 第六步 评估准确率
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=0)
print("测试集平均绝对误差(MAE): {:5.2f} MPG".format(mae))
# 测试集平均绝对误差(MAE): 1.67 MPG
# 第七步 训练过程可视化
# 训练过程都存储在了history对象中,我们可以借助 matplotlib 将训练过程可视化。
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail(3)
def plot_history(history):
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
plt.figure()
plt.xlabel('epoch')
plt.ylabel('metric - MSE')
plt.plot(hist['epoch'], hist['mse'], label='训练集')
plt.plot(hist['epoch'], hist['val_mse'], label='验证集')
plt.ylim([0, 20])
plt.legend()
plt.figure()
plt.xlabel('epoch')
plt.ylabel('metric - MAE')
plt.plot(hist['epoch'], hist['mae'], label='训练集')
plt.plot(hist['epoch'], hist['val_mae'], label='验证集')
plt.ylim([0, 5])
plt.legend()
plot_history(history)
# 从图中可以看到,在从100 epoch开始,训练集的loss仍旧继续降低,但验证集的loss却在升高,说明过拟合了,训练应该早一点结束。
# 接下来,我们使用 keras.callbacks.EarlyStopping,每一波(epoch)训练结束时,测试训练情况,
# 如果训练不再有效果(验证集的loss,即val_loss 不再下降),则自动地停止训练。
model = build_model()
model.compile(loss='mse', metrics=['mae', 'mse'],
optimizer=tf.keras.optimizers.RMSprop(0.001))
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)
history = model.fit(normed_train_data, train_labels, epochs=1000,
validation_split=0.2, verbose=0,
callbacks=[early_stop])
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=0)
print("测试集平均绝对误差(MAE): {:5.2f} MPG".format(mae))
# 测试集平均绝对误差(MAE): 1.87 MPG
plot_history(history)
# 在第 70 epoch 时,停止了训练。
# 第八步 预测
test_pred = model.predict(normed_test_data).flatten()
plt.scatter(test_labels, test_pred)
plt.xlabel('真实值')
plt.ylabel('预测值')
plt.axis('equal')
plt.axis('square')
plt.xlim([0, plt.xlim()[1]])
plt.ylim([0, plt.ylim()[1]])
plt.plot([-100, 100], [-100, 100])