在 TensorFlow.org 上查看 | 在 Google Colab 中运行 | 在 GitHub 上查看源代码 | 下载笔记本 |
在回归问题中,目标是预测连续值的输出,例如价格或概率。与之形成对比的是分类问题,其目标是从类别列表中选择一个类别(例如,一张图片中包含苹果或橙子,识别图片中哪种水果)。
本教程使用经典的Auto MPG数据集,并演示如何构建模型来预测 1970 年代后期和 1980 年代初期的汽车的燃油效率。为此,您将向模型提供那个时期许多汽车的描述。此描述包括气缸数、排量、马力、重量等属性。
本示例使用 Keras API。(访问 Keras 的教程和指南了解更多信息。)
# Use seaborn for pairplot.
pip install -q seaborn
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
# Make NumPy printouts easier to read.
np.set_printoptions(precision=3, suppress=True)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
print(tf.__version__)
Auto MPG 数据集
该数据集可从UCI 机器学习资源库获取。
获取数据
首先使用 pandas 下载并导入数据集
url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data'
column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight',
'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(url, names=column_names,
na_values='?', comment='\t',
sep=' ', skipinitialspace=True)
dataset = raw_dataset.copy()
dataset.tail()
清理数据
该数据集包含一些未知值
dataset.isna().sum()
为了使本入门教程保持简单,请删除这些行
dataset = dataset.dropna()
"Origin"
列是分类的,而不是数值的。因此,下一步是使用pd.get_dummies对该列中的值进行独热编码。
dataset['Origin'] = dataset['Origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
dataset = pd.get_dummies(dataset, columns=['Origin'], prefix='', prefix_sep='')
dataset.tail()
将数据拆分为训练集和测试集
现在,将数据集拆分为训练集和测试集。您将在模型的最终评估中使用测试集。
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)
检查数据
查看训练集中几对列的联合分布。
第一行表明燃油效率(MPG)是所有其他参数的函数。其他行表明它们是彼此的函数。
sns.pairplot(train_dataset[['MPG', 'Cylinders', 'Displacement', 'Weight']], diag_kind='kde')
我们也检查一下总体统计数据。请注意每个特征覆盖的范围差异很大
train_dataset.describe().transpose()
将特征与标签分开
将目标值(“标签”)与特征分开。此标签是您将训练模型预测的值。
train_features = train_dataset.copy()
test_features = test_dataset.copy()
train_labels = train_features.pop('MPG')
test_labels = test_features.pop('MPG')
归一化
在统计数据表中,很容易看到每个特征的范围差异很大
train_dataset.describe().transpose()[['mean', 'std']]
将使用不同比例和范围的特征进行归一化是一种良好的做法。
这很重要的一点原因是,特征会乘以模型权重。因此,输出的比例和梯度的比例会受到输入比例的影响。
虽然模型可能在没有特征归一化的情况下收敛,但归一化会使训练更加稳定。
归一化层
tf.keras.layers.Normalization
是在模型中添加特征归一化的简洁方法。
第一步是创建层
normalizer = tf.keras.layers.Normalization(axis=-1)
然后,通过调用Normalization.adapt
将预处理层的状态拟合到数据
normalizer.adapt(np.array(train_features))
计算均值和方差,并将它们存储在层中
print(normalizer.mean.numpy())
当调用该层时,它将返回输入数据,其中每个特征都独立地进行了归一化
first = np.array(train_features[:1])
with np.printoptions(precision=2, suppress=True):
print('First example:', first)
print()
print('Normalized:', normalizer(first).numpy())
线性回归
在构建深度神经网络模型之前,先使用一个或多个变量进行线性回归。
使用一个变量的线性回归
从使用单个变量的线性回归开始,根据"Horsepower"
预测"MPG"
。
使用tf.keras
训练模型通常从定义模型架构开始。使用tf.keras.Sequential
模型,该模型表示一系列步骤。
使用单个变量的线性回归模型有两个步骤
- 使用
tf.keras.layers.Normalization
预处理层对"Horsepower"
输入特征进行归一化。 - 应用线性变换(\(y = mx+b\)),使用线性层(
tf.keras.layers.Dense
)生成 1 个输出。
输入的数量可以通过input_shape
参数设置,也可以在模型首次运行时自动设置。
首先,创建一个由"Horsepower"
特征组成的 NumPy 数组。然后,实例化tf.keras.layers.Normalization
,并将它的状态拟合到horsepower
数据
horsepower = np.array(train_features['Horsepower'])
horsepower_normalizer = layers.Normalization(input_shape=[1,], axis=None)
horsepower_normalizer.adapt(horsepower)
构建 Keras Sequential 模型
horsepower_model = tf.keras.Sequential([
horsepower_normalizer,
layers.Dense(units=1)
])
horsepower_model.summary()
此模型将根据"Horsepower"
预测"MPG"
。
在未经训练的模型上运行前 10 个"Horsepower"
值。输出不会很好,但请注意它具有预期的形状(10, 1)
horsepower_model.predict(horsepower[:10])
构建模型后,使用 Keras 的Model.compile
方法配置训练过程。compile
最重要的参数是loss
和optimizer
,因为它们定义了要优化的内容(mean_absolute_error
)以及优化方式(使用tf.keras.optimizers.Adam
)。
horsepower_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss='mean_absolute_error')
使用 Keras 的Model.fit
执行 100 个 epochs 的训练
%%time
history = horsepower_model.fit(
train_features['Horsepower'],
train_labels,
epochs=100,
# Suppress logging.
verbose=0,
# Calculate validation results on 20% of the training data.
validation_split = 0.2)
使用存储在history
对象中的统计信息可视化模型的训练进度
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
def plot_loss(history):
plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.ylim([0, 10])
plt.xlabel('Epoch')
plt.ylabel('Error [MPG]')
plt.legend()
plt.grid(True)
plot_loss(history)
收集测试集上的结果以备后用
test_results = {}
test_results['horsepower_model'] = horsepower_model.evaluate(
test_features['Horsepower'],
test_labels, verbose=0)
由于这是一个单变量回归,因此可以轻松地将模型的预测视为输入的函数
x = tf.linspace(0.0, 250, 251)
y = horsepower_model.predict(x)
def plot_horsepower(x, y):
plt.scatter(train_features['Horsepower'], train_labels, label='Data')
plt.plot(x, y, color='k', label='Predictions')
plt.xlabel('Horsepower')
plt.ylabel('MPG')
plt.legend()
plot_horsepower(x, y)
使用多个输入的线性回归
您可以使用几乎相同的设置根据多个输入进行预测。此模型仍然执行相同的 \(y = mx+b\),只是 \(m\) 是一个矩阵,而 \(x\) 是一个向量。
再次创建一个两步 Keras Sequential 模型,第一层是您之前定义并适应整个数据集的normalizer
(tf.keras.layers.Normalization(axis=-1)
)
linear_model = tf.keras.Sequential([
normalizer,
layers.Dense(units=1)
])
当您在输入批次上调用Model.predict
时,它会为每个示例生成units=1
个输出
linear_model.predict(train_features[:10])
当您调用模型时,它的权重矩阵将被构建,请检查kernel
权重(\(y=mx+b\) 中的 \(m\)) 的形状是否为(9, 1)
linear_model.layers[1].kernel
使用 Keras 的Model.compile
配置模型,并使用Model.fit
进行 100 个 epochs 的训练
linear_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss='mean_absolute_error')
%%time
history = linear_model.fit(
train_features,
train_labels,
epochs=100,
# Suppress logging.
verbose=0,
# Calculate validation results on 20% of the training data.
validation_split = 0.2)
在这个回归模型中使用所有输入比只有一个输入的horsepower_model
实现了更低的训练和验证误差
plot_loss(history)
收集测试集上的结果以备后用
test_results['linear_model'] = linear_model.evaluate(
test_features, test_labels, verbose=0)
使用深度神经网络 (DNN) 进行回归
在上一节中,您实现了用于单个和多个输入的两个线性模型。
在这里,您将实现单个输入和多个输入的 DNN 模型。
代码基本相同,只是模型扩展为包含一些“隐藏”的非线性层。“隐藏”一词在这里只是表示不直接连接到输入或输出。
这些模型将包含比线性模型更多的层
- 归一化层,如前所述(对于单个输入模型使用
horsepower_normalizer
,对于多个输入模型使用normalizer
)。 - 两个隐藏的、非线性的、具有 ReLU(
relu
)激活函数非线性的Dense
层。 - 一个线性的
Dense
单输出层。
这两个模型都将使用相同的训练过程,因此compile
方法包含在下面的build_and_compile_model
函数中。
def build_and_compile_model(norm):
model = keras.Sequential([
norm,
layers.Dense(64, activation='relu'),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
model.compile(loss='mean_absolute_error',
optimizer=tf.keras.optimizers.Adam(0.001))
return model
使用 DNN 和单个输入进行回归
创建一个 DNN 模型,该模型仅使用"Horsepower"
作为输入,并使用horsepower_normalizer
(之前定义)作为归一化层
dnn_horsepower_model = build_and_compile_model(horsepower_normalizer)
此模型的可训练参数比线性模型多很多
dnn_horsepower_model.summary()
使用 Keras 的Model.fit
训练模型
%%time
history = dnn_horsepower_model.fit(
train_features['Horsepower'],
train_labels,
validation_split=0.2,
verbose=0, epochs=100)
此模型比线性单输入horsepower_model
略好
plot_loss(history)
如果您将预测绘制为"Horsepower"
的函数,您应该注意到此模型如何利用隐藏层提供的非线性
x = tf.linspace(0.0, 250, 251)
y = dnn_horsepower_model.predict(x)
plot_horsepower(x, y)
收集测试集上的结果以备后用
test_results['dnn_horsepower_model'] = dnn_horsepower_model.evaluate(
test_features['Horsepower'], test_labels,
verbose=0)
使用 DNN 和多个输入进行回归
重复上一个过程,使用所有输入。该模型在验证数据集上的性能略有提高。
dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
%%time
history = dnn_model.fit(
train_features,
train_labels,
validation_split=0.2,
verbose=0, epochs=100)
plot_loss(history)
收集测试集上的结果
test_results['dnn_model'] = dnn_model.evaluate(test_features, test_labels, verbose=0)
性能
由于所有模型都已训练完毕,因此您可以查看它们的测试集性能
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T
这些结果与训练期间观察到的验证误差相符。
进行预测
您现在可以使用 Keras 的Model.predict
在测试集上使用dnn_model
进行预测,并查看损失
test_predictions = dnn_model.predict(test_features).flatten()
a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
lims = [0, 50]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)
看来该模型预测得相当好。
现在,检查误差分布
error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error [MPG]')
_ = plt.ylabel('Count')
如果您对模型感到满意,请使用Model.save
保存它以备后用
dnn_model.save('dnn_model.keras')
如果您重新加载模型,它将给出相同的输出
reloaded = tf.keras.models.load_model('dnn_model.keras')
test_results['reloaded'] = reloaded.evaluate(
test_features, test_labels, verbose=0)
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T
结论
本笔记本介绍了一些处理回归问题的技术。以下是一些可能会有所帮助的额外提示
- 均方误差 (MSE)(
tf.keras.losses.MeanSquaredError
)和平均绝对误差 (MAE)(tf.keras.losses.MeanAbsoluteError
)是回归问题中常用的损失函数。MAE 对异常值不太敏感。分类问题使用不同的损失函数。 - 类似地,用于回归的评估指标与分类不同。
- 当数值输入数据特征的值具有不同的范围时,应将每个特征独立地缩放到相同的范围。
- 过拟合是 DNN 模型的常见问题,虽然在本教程中没有出现。访问 过拟合和欠拟合 教程以获取更多帮助。
# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.