TensorFlow 2之回归预测燃油效率

  |   0 评论   |   0 浏览

背景

前面的例子均为分类问题,类别是固定的,目的是判断属于哪一类。本文中的回归问题,是用来预测连续值,比如价格和概率。

本文使用经典的 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])

参考