【tf.keras】basic 03: Predict fuel efficiency (Regression)

本文是tf.keras系列教程的第三篇。通过燃油效率预测问题,介绍使用Keras实现回归问题的建模流程。

回归(regression) 问题中,目的是预测出如价格或概率这样连续值的输出(输出空间 y R y \in \R )。而 分类(classification) 的目的是从一系列的分类出选择出一个分类 (如,给出一张包含苹果或橘子的图片,识别出图片中是哪种水果),期输出是离散的值。



代码环境:

python version: 3.7.6 
tensorflow version: 2.1.0

安装必要的包:

pip install -q seaborn #为了绘制 矩阵图 (pairplot)

导入必要的包:

import pathlib

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

1. 数据预处理

UCI Auto MPG 数据集,构建了一个用来预测70年代末到80年代初汽车燃油效率的模型。数据集中的属性信息包含:气缸数,排量,马力以及重量。

1.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\\34123\\.keras\\datasets\\auto-mpg.data'

1.2 加载数据集

column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Model Year', 'Origin'] # 设置DataFrame表的列名

raw_dataset = pd.read_csv(dataset_path, names=column_names, # na_values = "?" 将空值标记为?
                      na_values = "?", comment='\t', # comment 指示不应分析行的其余部分。如果在一行的开头找到该行,则将完全忽略该行。此参数必须是单个字符。
                      sep=" ", skipinitialspace=True) # sep=" ",使用空格为定界符。skipinitialspace=True 在定界符后跳过空格。

dataset = raw_dataset.copy()
dataset.tail() # tail()不传参,默认查看后五行

输出:
在这里插入图片描述


1.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()

“Origin” 列实际上代表分类,而不仅仅是一个数字。将其转换为one-hot(哑变量):

origin = dataset.pop('Origin')

dataset['USA'] = (origin == 1)*1.0
dataset['Europe'] = (origin == 2)*1.0
dataset['Japan'] = (origin == 3)*1.0
dataset.tail()

输出:
在这里插入图片描述


1.4 拆分训练集和测试集

将数据集拆分为训练集和测试集,最后使用测试集对模型进行评估。

train_dataset = dataset.sample(frac=0.8,random_state=0)
test_dataset = dataset.drop(train_dataset.index)

pandas.DataFrame.samples函数说明:

pandas.DataFrame.samples(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)
  • n:要抽取的行数;
  • frac:抽取比列;
  • replace:四否为有放回抽样;
  • weights:字符索引或概率数组;
  • random_state:随机种子发生器,int类型,random_state=None,所取数据不重复;
  • axis:选择抽取数据的行还是列,axis=0 表示抽取行。

1.5 数据检查(可视化)

1.绘制矩阵图

sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde") # kde 表示对单变量图使用核密度估计。

seaborn.pairplot() 函数说明:

seaborn.pairplot(data, hue=None, hue_order=None, palette=None, vars=None, x_vars=None, y_vars=None, kind='scatter', diag_kind='auto', markers=None, height=2.5, aspect=1, dropna=True, plot_kws=None, diag_kws=None, grid_kws=None, size=None)

接收DataFrame格式数据,绘制数据集中的成对关系。

默认情况下,此函数将创建一个 Axes 网络,以便data中的每个变量将在 y 轴上共享一行,并在 x 轴上共享一列。对角轴的处理方式并不同,以此绘制一个图表来显示该列中变量的数据的单变量分布。还可以显示变量的子集或在行和列上绘制不同的变量。详细介绍可参考:

输出:
在这里插入图片描述
2.查看总体训练集数据信息

train_stats = train_dataset.describe() # 生成描述性统计信息
train_stats.pop("MPG")
train_stats = train_stats.transpose() # 转置
train_stats

在这里插入图片描述


1.6 分离标签(预测值)

train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')

1.7 数据规范化

再次查看 train_stats 部分,注意每个特征的范围有什么不同。

使用不同的尺度和范围对特征归一化是好的实践。尽管模型可能在没有特征归一化的情况下收敛,但它会使得模型训练更加复杂,并会造成生成的模型依赖输入变量所使用的单位。

注意:测试集也做同样的归一化(Normalization)操作。

def norm(x):
    return (x - train_stats['mean']) / train_stats['std']

normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)

查看缩放后的训练数据:
在这里插入图片描述
可以看到数据都缩放到[-1,1]区间了。


2. 建模

2.1 定义模型

定义一个“顺序”模型,其中包含两个紧密相连的隐藏层,以及一个返回连续值的输出层。

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

2.2 训练模型

训练 1000 个epoch,并在 history 对象中记录训练和验证准确率。

# 因为训练周期过长,以下代码实现每100个epoch 打印一次训练进度
class PrintDot(keras.callbacks.Callback):
    def __init__(self, epochs):
        self.epochs = epochs
    def on_epoch_end(self, epoch, logs):
        if epoch % 100 == 0: 
            print(f'epochs:{epoch}/{self.epochs}')

EPOCHS = 1000

history = model.fit(
  normed_train_data, train_labels,
  epochs=EPOCHS, validation_split = 0.2, verbose=0,
  callbacks=[PrintDot(EPOCHS)])

2.3 查看训练结果

使用 history 对象中存储的统计信息可视化模型的训练进度。

hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()

在这里插入图片描述


2.4 绘制训练集和验证集的损失和准确率曲线

def plot_history(history):
    '''
    该函数实现绘制训练集和验证集的损失和准确率曲线
    '''
    hist = pd.DataFrame(history.history)
    hist['epoch'] = history.epoch

    plt.figure(dpi=150)
    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(dpi=150)
    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)

输出:
MAE损失:
在这里插入图片描述
MSE损失:
在这里插入图片描述
从以上两张图片可以看出在约100个 epochs 之后误差非但没有降低,反而出现上升的趋势,很可能出现了过拟合。 可以通过设置回调来解决这个问题。 使用 EarlyStopping callback 测试验证集上每个 epoch 的指标,如果经过一定数量的 epochs 后没有改进,则自动停止训练。

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(EPOCHS)])

plot_history(history)

输出:
在这里插入图片描述
在这里插入图片描述


2.5 验证模型

通过 测试集 查看模型的泛化能力,在训练模型时没有使用测试集。这告诉我们在现实世界中使用这个模型时,可以期望它预测得有多好。

loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2)

print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))

输出:

78/78 - 0s - loss: 5.4078 - mae: 1.8193 - mse: 5.4078
Testing set Mean Abs Error:  1.82 MPG

2.6 使用模型进行预测

test_predictions = model.predict(normed_test_data).flatten()

plt.figure(dpi=150)
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
plt.axis('equal')
plt.axis('square')
plt.xlim([0,plt.xlim()[1]])
plt.ylim([0,plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100])

在这里插入图片描述
看起来模型预测得相当好,查看一下误差分布。

error = test_predictions - test_labels

plt.figure(dpi=150)
plt.hist(error, bins = 25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel("Count")

在这里插入图片描述
它不是完全的高斯分布,由此可以推断出:这是因为样本的数量很小所导致的。


Summary

本文介绍了一些处理回归问题建模流程和方法:

  • 均方误差(MSE)是用于回归问题的常见损失函数(分类问题中使用不同的损失函数)。
  • 类似的,用于回归的评估指标与分类不同。 常见的回归指标是平均绝对误差(MAE)。
  • 当数字输入数据特征的值范围不同时,每个特征应独立缩放到相同范围。
  • 如果训练数据不多,一种方法是选择隐藏层较少的小网络,以避免过度拟合。
  • 通过回调设置及时停止训练是防止过拟合的有效方法。

参考:
https://www.tensorflow.org/tutorials/keras/regression

发布了176 篇原创文章 · 获赞 694 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_39653948/article/details/105720276
今日推荐