Google机器学习-模型训练与评估

作者: 引线小白-本文永久链接:http://www.limoncc.com/工程实践/2018-03-07-Google机器学习-模型训练与评估/
知识共享许可协议: 本博客采用署名-非商业-禁止演绎4.0国际许可证

摘要:本文意在理清机器学习的基础问题。若有错误,请大家指正。
关键词: tensorflow,模型训练,模型评估

我们首先加载并准备数据。这一次,我们将使用多个特征,因此我们会将逻辑模块化,以对特征进行预处理。我们将使用加利福尼亚州住房数据集,尝试根据1990年的人口普查数据在城市街区级别预测房价中位数 median_house_value

一、数据处理

1.1、载入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 忽略警告,但是好像没有什么用。
import warnings
warnings.filterwarnings("ignore")

import math
from IPython import display
from matplotlib import cm
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from sklearn import metrics
import tensorflow as tf


# 设置工具基本参数,含义依次是:
# 输出训练过程中的loss信息,代码直译的意思是:日志信息显示设置(误差日志)
# pd数据显示设置最多显示10行。
# 显示小数点后1位。

tf.logging.set_verbosity(tf.logging.ERROR)
pd.options.display.max_rows = 10
pd.options.display.float_format = '{:.1f}'.format

#==============================
# 读取数据
#==============================

california_housing_dataframe = pd.read_csv(
"california_housing_train.csv", sep=",")

# 参看前五条数据

california_housing_dataframe.head(5)

# 对数据集进行随机化处理
# np的随机排列函数可以对array(向量)的元素随机重新安排位置。
# pd的dataframe数据类型的reindex函数可以通过输入重新安排顺序的索引,来对数据重新排序,但是索引是不变的。
# 记住这不会改变原来的数据顺序。所以写法上,我们再次用原来的变量名赋值了一次

california_housing_dataframe = california_housing_dataframe.reindex(
np.random.permutation(california_housing_dataframe.index))

我们来解释一下数据:

序号 经度 纬度 房龄中位数 房间总数 卧室总数 人口 户数 收入中位数 房价中位数
0 -114.3 34.2 15.0 5612.0 1283.0 1015.0 472.0 1.5 66900.0
1 -114.5 34.4 19.0 7650.0 1901.0 1129.0 463.0 1.8 80100.0
1.2、预处理数据

另外我们需要处理数据,以便让数据适用于我们的模型。下面我们通过自定义一些函数,来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#==============================
# 定义两个数据预处理函数
#==============================


def preprocess_features(california_housing_dataframe):

# 我们通过输入dataframe格式的数据,来生成一个同样是dataframe格式的数据,
# 其中我们会合成一个特征:人均房间数,以适用于我们的模型。
# 函数是这样来写的:定义个选择特征变量数据集,然后生成一个预处理特征变量数据集
# 输入:
# california_housing_dataframe:dataframe格式数据集
# 输出:
# processed_features:预处理特征变量数据集
# 疑问解决: Email:limoncc@icloud.com

selected_features = california_housing_dataframe[
["latitude",
"longitude",
"housing_median_age",
"total_rooms",
"total_bedrooms",
"population",
"households",
"median_income"]]
processed_features = selected_features.copy()

# 创建一个混合特征:人均房间数

processed_features["rooms_per_person"] = (
california_housing_dataframe["total_rooms"] / california_housing_dataframe["population"])
return processed_features
# end def of preprocess_features


def preprocess_targets(california_housing_dataframe):

# 改变一下预测变量单位:房价中位数单位改为千美元 thousand dollars。即放缩变量。
# 同时准备一个单独的输出变量数据集,格式同样是dataframe。
# 函数是这样来写的:生成一个空的 dataframe,然后赋值。
# 疑问解决: Email:limoncc@icloud.com

output_targets = pd.DataFrame()

# 放缩变量,房价中位数单位改为千美元.

output_targets["median_house_value"] = (
california_housing_dataframe["median_house_value"] / 1000.0)
return output_targets
# end def of preprocess_targets
1.3、切分数据集为训练集、验证集

对于训练集,我们从共 17000 个样本中选择前 12000 个样本。对于验证集,我们从共 17000 个样本中选择后 5000 个样本。

1
2
3
4
5
6
7
8
9
10
11
#==============================
# 切分数据集为训练集,验证集
#==============================

# 从共 17000 个样本中选择前 12000 个样本作为训练集


training_examples = preprocess_features(
california_housing_dataframe.head(12000))
print("训练特征集概览")
print(training_examples.describe())

训练特征集概览

经度 纬度 房龄中位数 房间总数 卧室总数 人口 户数 收入中位数 人均房间数
count 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0
mean 35.6 -119.6 28.5 2655.5 541.8 1435.4 503.5 3.9 2.0
std 2.1 2.0 12.6 2207.5 425.8 1163.5 388.4 1.9 1.2
min 32.5 -124.3 1.0 8.0 1.0 3.0 1.0 0.5 0.0
25% 33.9 -121.8 18.0 1465.0 299.0 792.0 282.8 2.6 1.5
50% 34.2 -118.5 28.0 2132.5 434.0 1165.0 409.0 3.5 1.9
75% 37.7 -118.0 37.0 3165.0 652.0 1726.0 608.0 4.8 2.3
max 42.0 -114.3 52.0 37937.0 5471.0 35682.0 5189.0 15.0 52.0
1
2
3
training_targets = preprocess_targets(california_housing_dataframe.head(12000))
print("训练输出集概览")
training_targets.describe()

训练输出集概览

median_house_value
count 12000.0
mean 208.0
std 116.6
min 15.0
25% 119.8
50% 180.9
75% 266.0
max 500.0
1
2
3
4
# 从共 17000 个样本中选择后 5000 个样本作为验证集
validation_examples = preprocess_features(california_housing_dataframe.tail(5000))
print("验证特征集概览")
validation_examples.describe()

验证特征集概览

经度 纬度 房龄中位数 房间总数 卧室总数 人口 户数 收入中位数 人均房间数
count 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0
mean 35.6 -119.6 28.8 2615.3 533.6 1415.6 495.6 3.9 2.0
std 2.1 2.0 12.6 2112.2 410.9 1109.5 375.0 1.9 1.2
min 32.5 -124.3 1.0 2.0 2.0 6.0 2.0 0.5 0.1
25% 33.9 -121.8 18.0 1452.0 293.0 785.0 279.0 2.5 1.5
50% 34.2 -118.5 29.0 2112.0 434.0 1169.0 409.0 3.5 1.9
75% 37.7 -118.0 37.0 3120.0 640.0 1712.2 596.0 4.7 2.3
max 41.9 -114.5 52.0 32627.0 6445.0 28566.0 6082.0 15.0 55.2
1
2
3
validation_targets = preprocess_targets(california_housing_dataframe.tail(5000))
print("验证输出集概览")
validation_targets.describe()

验证输出集概览

median_house_value
count 12000.0
mean 208.0
std 116.6
min 15.0
25% 119.8
50% 180.9
75% 266.0
max 500.0
1.4、观察数据有没有异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#==============================
# 观察数据有没有异常
#==============================

# 让图表能在ipython 中可以显示。
# % matplotlib inline

# figure函数
# figure(num=None, figsize=None, dpi=None,
# facecolor=None, edgecolor=None, frameon=True, FigureClass=<class 'matplotlib.figure.Figure'>, clear=False, **kwargs)
# 创建一个figure对象,并设置大小为13x8英寸

plt.figure(figsize=(13, 8))

# 在figure对象创建1行2列的第一个图。

ax = plt.subplot(1, 2, 1)

# 设置1行2列的第一个图

ax.set_title("Validation Data")
ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])

# 绘制一经纬度为坐标,以中位数房价与验证集中位数房价比例绘制颜色。来作散点图

plt.scatter(validation_examples["longitude"],
validation_examples["latitude"],
cmap="coolwarm",
c=validation_targets["median_house_value"] / validation_targets["median_house_value"].max())

# 以下命令相同。

ax = plt.subplot(1, 2, 2)
ax.set_title("Training Data")
ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])

plt.scatter(training_examples["longitude"],
training_examples["latitude"],
cmap="coolwarm",
c=training_targets["median_house_value"] / training_targets["median_house_value"].max())

# 下面这句可以去掉ipython的out信息。让ipython看上去更美观点。
_ = plt.plot()

png

现在应该已经呈现出一幅不错的加利福尼亚州地图了,其中旧金山和洛杉矶等住房成本高昂的地区用红色表示。根据训练集呈现的地图有几分像真正的地图,但根据验证集呈现的明显不像。训练数据和验证数据之间的特征或目标分布明显不同

这一事实表明我们创建训练集和验证集的拆分方式很可能存在问题。同时我们也会发现:机器学习中的调试通常是数据调试 而不是代码调试。如果我们在创建训练集和验证集之前,没有对数据进行正确的 随机化处理
,那么以某种特定顺序接收数据可能会导致出现问题(似乎就是此时的问题)。

我们在一开始应该加上如下代码:

1
2
3
# 对数据集进行随机化处理
california_housing_dataframe = california_housing_dataframe.reindex(
np.random.permutation(california_housing_dataframe.index))

二、训练模型

花费约 5 分钟的时间尝试不同的超参数设置。尽可能获取最佳验证效果。然后,我们会使用数据集中的所有特征训练一个线性回归器,看看其表现如何。我们来定义一下以前将数据加载到 TensorFlow 模型中时所使用的同一输入函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#==============================
# 训练模型
#==============================


def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):

# 定义一个满足线性回归模型输入特征
# 输入:
# features: DataFrame格式的特征
# targets: DataFrame格式的目标输出
# batch_size: 通过模型的批量大小
# shuffle: True or False. 是否打乱数据
# num_epochs: 需要重复数据的次数。None = 无限重复
# 输出:
# features:下个批量数据的元组(特征)
# labels:下个批量数据的元组(标签)
# 疑问解决: Email:limoncc@icloud.com

# 转换数据为np中arrays的字典格式

features = {key: np.array(value) for key, value in dict(features).items()}

# 构建数据集,并设置批量和重复次数

ds = tf.data.Dataset.from_tensor_slices(
(features, targets)) # warning: 2GB limit
ds = ds.batch(batch_size).repeat(num_epochs)

# Shuffle the data, if specified 如果需要,打乱数据。它维护一个固定大小的缓冲区,并从缓冲区中随机选择下一个元素。
# 从Dataset中消耗(consume)值的最常见的方法是创建一个iterator对象,该对象对数据集一次只提供一个元素访问。

if shuffle:
ds = ds.shuffle(10000)

# 返回数据的下一个批量
# 从Dataset中消耗值的最常见的方法是创建一个iterator对象,该对象对数据集一次只提供一个元素访问。

features, labels = ds.make_one_shot_iterator().get_next()
return features, labels
# end 结束my_input_fn

由于我们现在使用的是多个输入特征,因此需要把用于将特征列配置为独立函数的代码模块化。(目前此代码相当简单,因为我们的所有特征都是数值,但当我们在今后的练习中使用其他类型的特征时,会基于此代码进行构建。

1
2
3
4
5
6
7
8
9
10
11
def construct_feature_columns(input_features):

# 构建一个Tensorflow的特征列对象(Feature Columns)。
# 输入:
# input_features: 数字输入特征的名字
# 输出:
# 特征列集合
# 疑问解决: Email:limoncc@icloud.com

return set([tf.feature_column.numeric_column(my_feature) for my_feature in input_features])
# end 结束标记

接下来,继续完成下面的 train_model() 代码,以设置输入函数和计算预测。注意:可以参考以前的练习中的代码,但要确保针对相应数据集调用 predict()。
比较训练数据和验证数据的损失。使用一个原始特征时,我们得到的最佳均方根误差 (RMSE) 约为 180。现在我们可以使用多个特征,不妨看一下可以获得多好的结果。
使用我们之前了解的一些方法检查数据。这些方法可能包括:
1、比较预测值和实际目标值的分布情况
2、绘制预测值和目标值的散点图
3、使用 latitude 和 longitude 绘制两个验证数据散点图:
4、一个散点图将颜色映射到实际目标 median_house_value
5、另一个散点图将颜色映射到预测的 median_house_value,并排进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#==============================
# 训练模型
#==============================


def train_model(
learning_rate,
stepsbatch_size,
training_examples,
training_targets,
validation_examples,
validation_targets):

# 训练一个特征的线性模型
# 除了训练,这个函数还将打印训练进度信息,
# 以及一段时间内的训练损失和验证损失。
# 输入:
# learning_rate:训练速度,是一个浮点数
# steps:训练步骤的总数,是一个非零的整数。训练步骤由一个使用单一批量的前后和向后传递组成。
# batch_size:批量大小一个非零的整数,是一个非零的整数
# training_examples:一个dataframe数据格式对象,包含了来自‘california_housing_dataframe’数据的一个列或多列,用在训练用的输入特征
# training_targets:一个dataframe数据格式对象,包含了来自‘california_housing_dataframe’数据的恰好一列,用作训练目标
# validation_examples:一个dataframe数据格式对象,包含了来自‘california_housing_dataframe’数据的一个列或多列,用作验证的输入特征
# validation_targets: 一个dataframe数据格式对象,包含了来自‘california_housing_dataframe’数据的恰好一列,用作验证目标

# 输出:
# 返回一个在训练数据上训练的'LinearRegressor'对象
# 疑问解决: Email:limoncc@icloud.com

periods = 10
steps_per_period = steps / periods

# 创建线性回归对象
my_optimizer = tf.train.GradientDescentOptimizer(
learning_rate=learning_rate)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(
my_optimizer, 5.0)
linear_regressor = tf.estimator.LinearRegressor(
feature_columns=construct_feature_columns(training_examples),
optimizer=my_optimizer)

# 创建输入函数
def training_input_fn(): return my_input_fn(
training_examples,
training_targets["median_house_value"],
batch_size=batch_size)

def predict_training_input_fn(): return my_input_fn(
training_examples,
training_targets["median_house_value"],
num_epochs=1,
shuffle=False)

def predict_validation_input_fn(): return my_input_fn(
validation_examples, validation_targets["median_house_value"],
num_epochs=1,
shuffle=False)

# 训练模型,但要在循环中运行,这样我们就可以周期性地进行评估。
# 损失指标(loss metrics)

print("Training model...")
print("RMSE (on training data):")
training_rmse = []
validation_rmse = []
for period in range(0, periods):

# 从初始状态开始训练模型
linear_regressor.train(input_fn=training_input_fn,steps=steps_per_period,)

# 暂停,然后计算训练集和验证集的预测,并转化为np的array格式
training_predictions = linear_regressor.predict(input_fn=predict_training_input_fn)
training_predictions = np.array([item['predictions'][0] for item in training_predictions])

validation_predictions = linear_regressor.predict(input_fn=predict_validation_input_fn)
validation_predictions = np.array([item['predictions'][0] for item in validation_predictions])

# 计算训练集合验证的残差
training_root_mean_squared_error = math.sqrt(metrics.mean_squared_error(training_predictions, training_targets))

validation_root_mean_squared_error = math.sqrt(metrics.mean_squared_error(validation_predictions, validation_targets))

# 偶尔打印当前损失。
print(" period %02d : %0.2f" %(period, training_root_mean_squared_error))

# 将此期间的损失指标添加到我们的列表中。
training_rmse.append(training_root_mean_squared_error)

validation_rmse.append(validation_root_mean_squared_error)
print("Model training finished.")

# 输出一段时间内损失指标的图表。
plt.ylabel("RMSE")
plt.xlabel("Periods")
plt.title("Root Mean Squared Error vs. Periods")
plt.tight_layout()
plt.plot(training_rmse, label="training")
plt.plot(validation_rmse, label="validation")
plt.legend()

return linear_regressor
# end 结束标记
1
2
3
4
5
6
7
8
9
10
11
12
#==============================
# 运行模型
#==============================

linear_regressor = train_model(
learning_rate=0.00003,
steps=500,
batch_size=5,
training_examples=training_examples,
training_targets=training_targets,
validation_examples=validation_examples,
validation_targets=validation_targets)
Training model...
RMSE (on training data):
  period 00 : 218.32
  period 01 : 200.74
  period 02 : 187.11
  period 03 : 177.11
  period 04 : 171.24
  period 05 : 168.59
  period 06 : 168.07
  period 07 : 168.07
  period 08 : 168.74
  period 09 : 170.20
Model training finished.

png

三、测试模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#==============================
# 测试模型
#==============================

california_housing_test_data = pd.read_csv(
"https://storage.googleapis.com/ml_universities/california_housing_test.csv", sep=",")

test_examples = preprocess_features(california_housing_test_data)
test_targets = preprocess_targets(california_housing_test_data)


def predict_test_input_fn(): return my_input_fn(
test_examples,
test_targets["median_house_value"],
num_epochs=1,
shuffle=False)

test_predictions = linear_regressor.predict(input_fn=predict_test_input_fn)
test_predictions = np.array([item['predictions'][0]for item in test_predictions])

root_mean_squared_error = math.sqrt(metrics.mean_squared_error(test_predictions, test_targets))

print("Final RMSE (on test data): %0.2f" % root_mean_squared_error)
Final RMSE (on test data): 221.57


版权声明
引线小白创作并维护的柠檬CC博客采用署名-非商业-禁止演绎4.0国际许可证。
本文首发于柠檬CC [ http://www.limoncc.com ] , 版权所有、侵权必究。
本文永久链接http://www.limoncc.com/工程实践/2018-03-07-Google机器学习-模型训练与评估/

予汝玫瑰,渡人沃土。