本文将详细介绍基于 Pandas 和 TensorFlow 探索、清理以及转换用于训练模型的数据集的方法,辅以代码和图片。
学习目标:
- 了解使用 Pandas 进行数据清理和处理丢失数据的基础知识。
- 使用校准图评估模型性能。
- 使用各种特征转换训练模型。
- 使用可视化来了解特征转换的价值。
关于环境搭建,请前往《机器学习6:使用 TensorFlow 的训练线性回归模型》,本文不再赘述。
目录
1.准备工作
1.1 导入依赖模块
1.2 Pandas 基本设置
1.3 用 Pandas 加载数据集
2.任务一:使用 Pandas 探索和准备数据
2.1 探索数据
2.2 准备数据
3. 任务二:基于数字特征(未规范化-Normalization)训练模型
3.1 基于数字特征训练模型
3.2 将模型的预测可视化
3.3 完整代码运行
4.任务三:基于数值特征训练模型(需规范化-Normalization)模型
4.1 特征规范化
4.2 预测结果可视化
5.任务四:基于类别特征来训练模型
6.参考文献
1.准备工作
1.1 导入依赖模块
import numpy as np
import pandas as pd
import math
from matplotlib import pyplot as plt
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
1.2 Pandas 基本设置
Pandas 是一个有用的内存数据集数据分析库。基于 Pandas 的工具包,我们可以读取数据、探索数据并进行一些基本处理。它对适合内存存储的数据集非常有帮助!首先,我们设置一些选项来控制项目的显示方式以及显示表时要显示的最大行数——注意:可以根据需要更改此设置。
# 基础设置:展示样式和最大行数
pd.options.display.float_format = '{:.2f}'.format
pd.options.display.max_rows = 15
1.3 用 Pandas 加载数据集
数据原始来源:https://storage.googleapis.com/mledu-datasets/cars_data.csv,这个数据集是关于汽车的数据。在本文中,我们将基于该数据集训练模型来预测汽车价格。
为了便于在本地执行,需要读者先将数据下载到本地——笔者存储该数据集的本地文件路径为:/Users/jinKwok/Downloads/cars_data.csv。
需要说明的是,汽车数据集中的字段是以逗号分隔的,没有标题行。为了让每一列都有一个有意义的标题,我们必须进行一些处理——即从汽车数据集中获取有关列的信息,然后据此设置标题。
# 由于数据集的列没有标题,因此,我们需要根据数据的实际语义为每一列设置一个标题
feature_names = ['symboling', 'normalized-losses', 'make', 'fuel-type',
'aspiration', 'num-doors', 'body-style', 'drive-wheels',
'engine-location', 'wheel-base', 'length', 'width', 'height', 'weight',
'engine-type', 'num-cylinders', 'engine-size', 'fuel-system', 'bore',
'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg',
'highway-mpg', 'price']
# 使用 pandas 从本地路径加载数据到内存中
car_data = pd.read_csv('/Users/jinKwok/Downloads/cars_data.csv',
sep=',', names=feature_names, header=None, encoding='latin-1')
# 将数据随机化,确保梯度下降的性能
car_data = car_data.reindex(np.random.permutation(car_data.index))
print("Data set loaded. Num examples: ", len(car_data))
2.任务一:使用 Pandas 探索和准备数据
Pandas 的 DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。DataFrame 既有行索引也有列索引。Pandas Series 类似表格中的一个列(column),类似一维数组,可以保存任何数据类型。Series 由索引(index)和列组成。关于 Pandas,本文不展开介绍。
2.1 探索数据
本节中将使用 Pandas 检查数据,并定义 numeric_feature_name 和 categorical_feature_name 的特征列表。此外,还可以执行以下操作:
- dataframe[4:7]:拉出 Pandas 数据帧中的第 4、5、6 行;
- dataframe[['mycol1','mycol2']] 将取出名为 mycol1 和 mycol2 的两列,并生成一个新的 Pandas DataFrame 对象;
- dataframe['mycol1'] 将取出名为 mycol1 的列,并生成一个 Pandas Series 对象,而不是DataFrame;
- dataframe.describe():打印每个 DataFrame 的统计信息;
# 使用Pandas 拉出 car_data的第5~7行
car_data[4:7]
# 定义标签和两组特征:1.数字特征-numeric_feature_names;2.类别特征-categorical_feature_names
LABEL = 'price'
numeric_feature_names = ['symboling', 'normalized-losses', 'wheel-base',
'length', 'width', 'height', 'weight', 'engine-size', 'horsepower',
'peak-rpm', 'city-mpg', 'highway-mpg', 'bore', 'stroke',
'compression-ratio']
categorical_feature_names = list(set(feature_names) - set(numeric_feature_names) - set([LABEL]))
# 通过断言,确认两组特征的数量是否符合预期
assert len(numeric_feature_names) == 15
assert len(categorical_feature_names) == 10
# 运行以检查数字特征。
car_data[numeric_feature_names]
# 运行以检查类别特征。
car_data[categorical_feature_names]
需要特别说明的是,如果在 PyCharm 中直接执行包含上述代码的 .py 文件,是无法直接观察到数据的,因为数据并不会直接打印出来。你可以采用类似 print(car_data[4:7]) 打印 DataFrame,也可以通过 Python Console 来运行代码,从而查看数据,如下所示:
>>> car_data[4:7]
symboling normalized-losses make ... city-mpg highway-mpg price
145 0 102 subaru ... 24 29 11259
60 0 115 mazda ... 26 32 8495
32 1 101 honda ... 38 42 5399
[3 rows x 26 columns]
类似的,在 Python Console 执行:car_data[numeric_feature_names] 和 car_data[categorical_feature_names],可以分别查看我们定义的数字特征和类别特征,如下所示:
>>> car_data[numeric_feature_names]
symboling normalized-losses wheel-base ... bore stroke compression-ratio
57 3 150 95.30 ... ? ? 9.40
148 0 85 96.90 ... 3.62 2.64 9.00
56 3 150 95.30 ... ? ? 9.40
189 3 ? 94.50 ... 3.19 3.40 8.50
145 0 102 97.00 ... 3.62 2.64 7.70
.. ... ... ... ... ... ... ...
26 1 148 93.70 ... 2.97 3.23 9.40
169 2 134 98.40 ... 3.62 3.50 9.30
50 1 104 93.10 ... 3.03 3.15 9.00
80 3 153 96.30 ... 3.17 3.46 7.50
8 1 158 105.80 ... 3.13 3.40 8.30
此外,通过 Python Console 也可以直接将 DataFrame 可视化,如下图所示:点击“View as DataFrame”,即可将 DataFrame 可视化为表格形式。
2.2 数据转换
模型无法给予非数值类型的数据训练模型,因此,对于原始数据中的非数值类型,我们需要进行强制转换。此外,原始数据中,列纬度可能是缺失的,对于这些缺失的数据,需要进行人工填充,如下代码所示:
# 遍历,将上面定义的数字特征(numeric_feature_names)强制转换为数字。这是必要的,因为模型无法处理非数字型特征。
# 如果非数字型特征直接输入,模型会崩溃
for feature_name in numeric_feature_names + [LABEL]:
car_data[feature_name] = pd.to_numeric(car_data[feature_name], errors='coerce')
# 对于缺省的数据,用0填充
# 注意:这种处理方式并非是最好的,后面我们将尝试其他方法
car_data.fillna(0, inplace=True)
3. 任务二:基于数字特征(未规范化-Normalization)训练模型
在本节,我们利用任务一中定义的数字特征组(numeric_feature_names)作为输入特征,来训练汽车价格预测模型。我们可以修改以下超参数尝试降低损失:
- 学习率:
- 优化器;
- 隐藏层维度——需考虑训练示例的数量,确保选择是有意义的
- 批大小
- 训练步骤数量
3.1 基于数字特征训练模型
# 设置训练模型需要的配置:批大小-batch_size,数字特征-numeric_feature_names,预测标签-price
# 改变其他参数可以提高模型的质量,但要谨慎对待,因为数据集非常小
batch_size = 16
x_df = car_data[numeric_feature_names]
y_series = car_data['price']
train_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
y=y_series,
batch_size=batch_size,
num_epochs=None,
shuffle=True)
eval_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
y=y_series,
batch_size=batch_size,
shuffle=False)
predict_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
batch_size=batch_size,
shuffle=False)
# 对于特征列(上面定义的数值特征组 numeric_feature_names 中的列),允许模型解析数据,执行公共预处理,并自动生成 tf.Estimator 的输入层
model_feature_columns = [
tf.feature_column.numeric_column(feature_name) for feature_name in numeric_feature_names
]
print('model_feature_columns', model_feature_columns)
est = tf.estimator.DNNRegressor(
feature_columns=model_feature_columns,
hidden_units=[64],
optimizer=tf.train.AdagradOptimizer(learning_rate=0.01),
)
# 训练模型
num_print_statements = 10
num_training_steps = 10000
for _ in range(num_print_statements):
est.train(train_input_fn, steps=num_training_steps // num_print_statements)
scores = est.evaluate(eval_input_fn)
# “分数”字典有几个由估计器自动生成的度量指标
# 1-`average_loss’是单个示例的平均损失
# 2-`loss`是批次的总损失
print('scores', scores)
输出信息如下:显然,这很不直观。
scores {'average_loss': 37720610.0, 'label/mean': 12949.43, 'loss': 594824960.0, 'prediction/mean': 13431.765, 'global_step': 1000}
scores {'average_loss': 29733780.0, 'label/mean': 12949.43, 'loss': 468878850.0, 'prediction/mean': 13133.609, 'global_step': 2000}
scores {'average_loss': 26735054.0, 'label/mean': 12949.43, 'loss': 421591230.0, 'prediction/mean': 13078.116, 'global_step': 3000}
scores {'average_loss': 25478890.0, 'label/mean': 12949.43, 'loss': 401782500.0, 'prediction/mean': 13164.152, 'global_step': 4000}
scores {'average_loss': 24855248.0, 'label/mean': 12949.43, 'loss': 391948130.0, 'prediction/mean': 13136.9795, 'global_step': 5000}
scores {'average_loss': 24471574.0, 'label/mean': 12949.43, 'loss': 385897920.0, 'prediction/mean': 13141.731, 'global_step': 6000}
scores {'average_loss': 24200630.0, 'label/mean': 12949.43, 'loss': 381625300.0, 'prediction/mean': 13081.306, 'global_step': 7000}
scores {'average_loss': 23986446.0, 'label/mean': 12949.43, 'loss': 378247800.0, 'prediction/mean': 13127.951, 'global_step': 8000}
scores {'average_loss': 23794016.0, 'label/mean': 12949.43, 'loss': 375213340.0, 'prediction/mean': 13052.78, 'global_step': 9000}
scores {'average_loss': 23594436.0, 'label/mean': 12949.43, 'loss': 372066100.0, 'prediction/mean': 13173.646, 'global_step': 10000}
3.2 预测结果可视化
模型训练完成之后,我们需要了解模型的推断与实际数据的差异——即观察模型的预测效果。当然,最好的方式是将预测效果可视化。
关于可视化,我们可以基于辅助函数 scatter_plot_inference 来实现。注意:实际数据为灰色。你的模型的预测是橙色的。
# 可视化模型预测结果
def scatter_plot_inference_grid(est, x_df, feature_names):
"""打印预测结果.
Args:
est:经过训练的tf估计器
x_df: 输入特征,即 pandas dataframe 对象
feature_names: 要绘制的特征名称组
"""
def scatter_plot_inference(axis,
x_axis_feature_name,
y_axis_feature_name,
predictions):
"""生成一个子图区域."""
# 用灰色打印真实数据.
y_axis_feature_name = 'price'
axis.set_ylabel(y_axis_feature_name)
axis.set_xlabel(x_axis_feature_name)
axis.scatter(car_data[x_axis_feature_name],
car_data[y_axis_feature_name],
c='grey')
# 用橙色打印预测数据.
axis.scatter(car_data[x_axis_feature_name], predictions, c='orange')
predict_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
batch_size=batch_size,
shuffle=False)
predictions = [
x['predictions'][0]
for x in est.predict(predict_input_fn)
]
num_cols = 3
num_rows = int(math.ceil(len(feature_names) / float(num_cols)))
f, axarr = plt.subplots(num_rows, num_cols)
size = 4.5
f.set_size_inches(num_cols * size, num_rows * size)
for i, feature_name in enumerate(numeric_feature_names):
axis = axarr[int(i / num_cols), i % num_cols]
scatter_plot_inference(axis, feature_name, 'price', predictions)
plt.show()
# 调用打印函数,将预测结果可视化
scatter_plot_inference_grid(est, x_df, numeric_feature_names)
3.3 完整代码运行
import numpy as np
import pandas as pd
import math
from matplotlib import pyplot as plt
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 设置展示样式和最大行数
pd.options.display.float_format = '{:.2f}'.format
pd.options.display.max_rows = 15
# 由于数据集的列没有标题,因此,我们需要根据数据的实际语义为每一列设置一个标题
feature_names = ['symboling', 'normalized-losses', 'make', 'fuel-type',
'aspiration', 'num-doors', 'body-style', 'drive-wheels',
'engine-location', 'wheel-base', 'length', 'width', 'height', 'weight',
'engine-type', 'num-cylinders', 'engine-size', 'fuel-system', 'bore',
'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg',
'highway-mpg', 'price']
# 使用 pandas 从本地路径加载数据
car_data = pd.read_csv('/Users/shulan/Downloads/cars_data.csv',
sep=',', names=feature_names, header=None, encoding='latin-1')
# 将数据随机化,确保梯度下降的性能
car_data = car_data.reindex(np.random.permutation(car_data.index))
print("Data set loaded. Num examples: ", len(car_data))
# 定义标签和两组特征:1.数字特征-numeric_feature_names;2.类别特征-categorical_feature_names
LABEL = 'price'
numeric_feature_names = ['symboling', 'normalized-losses', 'wheel-base',
'length', 'width', 'height', 'weight', 'engine-size', 'horsepower',
'peak-rpm', 'city-mpg', 'highway-mpg', 'bore', 'stroke',
'compression-ratio']
categorical_feature_names = list(set(feature_names) - set(numeric_feature_names) - set([LABEL]))
# 通过断言,确认两组特征的数量是否符合预期
assert len(numeric_feature_names) == 15
assert len(categorical_feature_names) == 10
# 遍历,将上面定义的数字特征(numeric_feature_names)强制转换为数字。这是必要的,因为模型无法处理非数字型特征。
# 如果非数字型特征直接输入,模型会崩溃
for feature_name in numeric_feature_names + [LABEL]:
car_data[feature_name] = pd.to_numeric(car_data[feature_name], errors='coerce')
# 对于缺省的数据,用0填充
# 注意:这种处理方式并非是最好的,后面我们将尝试其他方法
car_data.fillna(0, inplace=True)
# 设置训练模型需要的配置:批大小-batch_size,数字特征-numeric_feature_names,预测标签-price
# 改变其他参数可以提高模型的质量,但要谨慎对待,因为数据集非常小
batch_size = 16
x_df = car_data[numeric_feature_names]
y_series = car_data['price']
train_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
y=y_series,
batch_size=batch_size,
num_epochs=None,
shuffle=True)
eval_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
y=y_series,
batch_size=batch_size,
shuffle=False)
predict_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
batch_size=batch_size,
shuffle=False)
# 对于特征列(上面定义的数值特征组 numeric_feature_names 中的列),允许模型解析数据,执行公共预处理,并自动生成 tf.Estimator 的输入层
model_feature_columns = [
tf.feature_column.numeric_column(feature_name) for feature_name in numeric_feature_names
]
print('model_feature_columns', model_feature_columns)
est = tf.estimator.DNNRegressor(
feature_columns=model_feature_columns,
hidden_units=[64],
optimizer=tf.train.AdagradOptimizer(learning_rate=0.01),
)
# 训练模型
num_print_statements = 10
num_training_steps = 10000
for _ in range(num_print_statements):
est.train(train_input_fn, steps=num_training_steps // num_print_statements)
scores = est.evaluate(eval_input_fn)
# “分数”字典有几个由估计器自动生成的度量指标
# 1-`average_loss’是单个示例的平均损失
# 2-`loss`是批次的总损失
print('scores', scores)
# 可视化模型预测结果
def scatter_plot_inference_grid(est, x_df, feature_names):
"""打印预测结果.
Args:
est:经过训练的tf估计器
x_df: 输入特征,即 pandas dataframe 对象
feature_names: 要绘制的特征名称组
"""
def scatter_plot_inference(axis,
x_axis_feature_name,
y_axis_feature_name,
predictions):
"""生成一个子图区域."""
# 用灰色打印真实数据.
y_axis_feature_name = 'price'
axis.set_ylabel(y_axis_feature_name)
axis.set_xlabel(x_axis_feature_name)
axis.scatter(car_data[x_axis_feature_name],
car_data[y_axis_feature_name],
c='grey')
# 用橙色打印预测数据.
axis.scatter(car_data[x_axis_feature_name], predictions, c='orange')
predict_input_fn = tf.estimator.inputs.pandas_input_fn(
x=x_df,
batch_size=batch_size,
shuffle=False)
predictions = [
x['predictions'][0]
for x in est.predict(predict_input_fn)
]
num_cols = 3
num_rows = int(math.ceil(len(feature_names) / float(num_cols)))
f, axarr = plt.subplots(num_rows, num_cols)
size = 4.5
f.set_size_inches(num_cols * size, num_rows * size)
for i, feature_name in enumerate(numeric_feature_names):
axis = axarr[int(i / num_cols), i % num_cols]
scatter_plot_inference(axis, feature_name, 'price', predictions)
plt.show()
# 调用打印函数,将预测结果可视化
scatter_plot_inference_grid(est, x_df, numeric_feature_names)
运行结果——可视化预测结果(灰色为真实值,橙色为预测值)如下图所示:
4.任务三:基于数值特征训练模型(需规范化-Normalization)模型
4.1 特征规范化
更进一步,对任务二中的数值特征进行规范化处理,而后再用来训练模型。关于规范化处理方法,在《机器学习26:《数据准备和特征工程-IV》数据转换》中有详细的介绍。由于本案例中的数值特征并不包含极端异常值,因此可采用 “Z 分数” 法来进行规范化。代码如下:
# 使用 Z 分数对输入数值特征进行规范化
# Epsilon:防止除数为零
epsilon = 0.000001
model_feature_columns = [
tf.feature_column.numeric_column(feature_name,
normalizer_fn=lambda val: (val - x_df.mean()[feature_name]) / (
epsilon + x_df.std()[feature_name]))
for feature_name in numeric_feature_names
]
print('model_feature_columns', model_feature_columns)
其中,用到了 pandas 的功能很有:
- dataframe.mmean()['your_feature_name']:获取特征“your_feature_name”的平均值;
- dataframe.std()['your_feature_name']:获取特征“your_feature_name”的标准差。
4.2 预测结果可视化
用 4.1 中的代码,替换掉 3.3 中的的相关代码,然后运行,可以得到如下结果。
5.任务四:基于类别特征来训练模型
与基于特征组 numeric_feature_names 训练模型类似,采用类别特征组 categorical_feature_names 训练模型,本质差异只是采用的输入特征不同。基于上面的代码,修改输入特征即可。这里不再演示了。
6.参考文献
链接-https://developers.google.cn/machine-learning/data-prep/programming-exercise