TensorFlow 笔记(十四):tf.contrib.learn输入函数

本教程介绍了tf.contrib.learn中的输入函数,我们将首先学习如何建立一个input_fn来预处理数据并且把它们送入模型中训练、评估或测试。接着我们将运用input_fn构造一个神经网络回归器用来预测房价。

教程地址:https://www.tensorflow.org/get_started/input_fn


使用input_fn的输入方法

当我们用tf.contrib.learn来训练一个神经网络时,我们可以直接把特征数据和标签数据送入fitevaluatepredict操作中。如下例子是我们上篇教程中用到的方法:

1
2
3
4
5
6
7
8
9
training_set = tf.contrib.learn.datasets.base.load_csv_with_header(
filename=IRIS_TRAINING, target_dtype=np.int, features_dtype=np.float32)
test_set = tf.contrib.learn.datasets.base.load_csv_with_header(
filename=IRIS_TEST, target_dtype=np.int, features_dtype=np.float32)
...
classifier.fit(x=training_set.data,
y=training_set.target,
steps=2000)

当我们对源数据操作较小时,这种方法很好。但如果我们需要大量的特征工程,我们可以使用输入函数input_fn来进行数据预处理的操作。


input_fn结构

如下代码显示了一个输入函数的基本框架:

1
2
3
4
5
def my_input_fn():
# 在这里预处理你的数据...
# return:1) 特征列与相应特征数据的Tensors的映射
# 2) 包含标签的Tensor
return feature_cols, labels

函数主体包含了对输入数据的预处理操作,例如清除异常点等。输入函数必须返回如下两个值:

  • feature_cols:一个包含了键值对的字典,把特征名映射到对应的Tensor上,该Tensor包含了特征数据。
  • labels:一个包含了标签数据的Tensor


把特征数据转化为Tensors

如果在input_fn函数中,我们的特征数据或标签数据保存在pandas dataframes或者numpy arrays里面,我们需要把它们转化为Tensor返回。

对于连续数据,我们可以用tf.constant创建一个Tensor

1
2
feature_column_data = [1, 2.4, 0, 9.9, 3, 120]
feature_tensor = tf.constant(feature_column_data)

对于稀疏矩阵数据(大部分值为0),我们可以使用SparseTensor,一般来说我们用三个参数来实例化一个SparseTensor

  • dense_shapetensor的形状,用一个列表来表示每个维度的元素的个数,例如dense_shape=[3,5]表示一个二维的tensor,有3行5列;而dense_shape=[9]表示一个一维的tensor,有9个元素。
  • indices:表示tensor中非0值的位置。用一个嵌套列表来表示,每个嵌套列表代表一个非0值所在的位置。例如indices=[[0,1], [2,4]]表示在tensor中第0行第1个元素和第2行第4个元素为非零值。
  • values:一维tensorvalues中的第i个值位于在indices中第i个值表示的位置上。例如下面的例子,表示在第0行第1个元素为6,第2行第4个元素为0.5。
1
2
3
sparse_tensor = tf.SparseTensor(indices=[[0,1], [2,4]],
values=[6, 0.5],
dense_shape=[3, 5])

对应的tensor为:

1
2
3
[[0, 6, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0.5]]


把input_fn送入模型中

为了把数据送入模型进行训练,我们只需要把创建的输入函数赋值给fitinput_fn参数即可:

1
classifier.fit(input_fn=my_input_fn, steps=2000)

注意input_fn会把特征数据和标签数据一起送入模型中,它代替了fit中的xy参数,如果我们同时保留了input_fnx或者y,程序会报错。

同时需要注意的是input_fn参数必须接受一个函数名(input_fn=my_input_fn)而不是接受一个函数调用(input_fn=my_input_fn())。这意味着如果我们尝试在fit函数的参数中使用一个函数调用,如下代码,将会报错。

1
classifier.fit(input_fn=my_input_fn(training_set), steps=2000)

不过如果我们想让我们的输入函数具有输入参数,第一个方法是用另一个无参数函数包含它,然后用该函数名作为input_fn的值,这样我们就可以在内层函数送入我们需要的参数。例如:

1
2
3
4
def my_input_function_training_set():
return my_input_function(training_set)
classifier.fit(input_fn=my_input_fn_training_set, steps=2000)

第二个方法是使用python内置的偏函数functools.partial来实现上述功能,它会建立一个新的函数同时把所有的参数固定住:

1
2
classifier.fit(input_fn=functools.partial(my_input_function,
data_set=training_set), steps=2000)

第三个方法我们可以使用lambda操作:

1
classifier.fit(input_fn=lambda: my_input_fn(training_set), steps=2000)

上述三个方法最大的优点是我们可以把同一个input_fn送到evaluatepredict操作上,而只需要修改内层函数的输入参数即可:

1
classifier.evaluate(input_fn=lambda: my_input_fn(test_set), steps=2000)

这些方法增强了代码的可操作性,我们不需要对每个操作都分别获取xy的值并用两个变量表示出来,如 x_train, x_test, y_train, y_test


补充

关于functools.partial()

如果某一个函数的其中一个输入参数始终固定,而我们每次调用该函数都要输入一次就显得很没有必要,于是我们就可以对该函数进行封装,使得输入参数减少。用到的函数就是functools.partial()。举个例子:

1
2
def input1(key1,key2):
return key1+'_'+key2

如果我们的key1在使用过程中始终是固定的,而只有key2是变化的,如:

1
2
3
4
a = input1('China','liming')
b = input1('China','zhangsan')
c = input1('China','wangwei')
...

这时候我们就可以固定key1的值,对input1()进行封装:

1
2
import functools
input2 = functools.partial(input1, 'China')

这样我们就可以很方便的使用该函数:

1
2
3
a = input2('liming')
b = input2('zhangsan')
c = input2('wangwei')

输出a,b,c的值得到:

1
2
3
China_liming
China_zhangsan
China_wangwei


波士顿房价神经网络模型

接下来我们将携一个输入函数来预处理波士顿房价数据,然后我们把处理后的数据送入神经网络回归器中进行训练,进而预测房价中位数。

我们要用到的数据集的CSV文件包含以下特征:

Feature Description
CRIM Crime rate per capita
ZN Fraction of residential land zoned to permit 25,000+ sq ft lots
INDUS Fraction of land that is non-retail business
NOX Concentration of nitric oxides in parts per 10 million
RM Average Rooms per dwelling
AGE Fraction of owner-occupied residences built before 1940
DIS Distance to Boston-area employment centers
TAX Property tax rate per $10,000
PTRATIO Student-teacher ratio

模型预测的标签是MEDV,表示房价的中位数,以千美元为计数单位。

通过以下链接下载数据集:

接下来我们将依次介绍如何创建一个输入函数,把它们送入神经网络回归器中,训练并评估模型,进行房价预测。


引入房价数据

首先我们需要引入必要的库,包括pandastensorflow,同时我们利用set_verbosity来获得更多的输出信息。

1
2
3
4
5
6
7
8
9
10
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import itertools
import pandas as pd
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.INFO)

关于logging我们将在下个笔记中介绍,简单来说我们在代码开始(import之后)加上这行代码,训练过程将打印出详细的loss信息和step信息,可以更好的监控整个训练过程。

因为我们是使用pandas来倒入CSV数据,所以在此之前先定义好column的名字,以及特征名和标签名。

1
2
3
4
5
6
7
8
9
10
11
12
COLUMNS = ["crim", "zn", "indus", "nox", "rm", "age",
"dis", "tax", "ptratio", "medv"]
FEATURES = ["crim", "zn", "indus", "nox", "rm",
"age", "dis", "tax", "ptratio"]
LABEL = "medv"
training_set = pd.read_csv("boston_train.csv", skipinitialspace=True,
skiprows=1, names=COLUMNS)
test_set = pd.read_csv("boston_test.csv", skipinitialspace=True,
skiprows=1, names=COLUMNS)
prediction_set = pd.read_csv("boston_predict.csv", skipinitialspace=True,
skiprows=1, names=COLUMNS)

因为第一行是关于数据的介绍,所以我们在这里跳过第一行,用skiprows=1


定义FeatureColumns并创建回归器

接下来我们创建一系列FeatureColumn来组成特征数据。每个FeatureColumn表示一个特征,根据数据类型的不同用相应的函数创建,更详细内容可以看以下两个参考链接:

因为本数据集所有的特征都是连续值,所以我们可以用tf.contrib.layers.real_valued_column()函数来创建,函数输入是特征的名字,这和下面的input_fn()函数相对应。

1
2
feature_cols = [tf.contrib.layers.real_valued_column(k)
for k in FEATURES]

接下来,我们将实例化一个DNNRegressor,我们需要提供两个参数,一个hidden_units,它是用来定义隐藏层神经元个数的超参数,这里我们用两层各10个神经元;另一个是feature_columns,即我们刚刚定义的FeatureColumns的列表。

1
2
3
regressor = tf.contrib.learn.DNNRegressor(feature_columns=feature_cols,
hidden_units=[10, 10],
model_dir="/tmp/boston_model")


建立input_fn

为了把输入数据送入regressor中,我们要创建一个输入函数,它接受一个pandasDatafram同时返回包含特征信息和标签信息的两个Tensor

1
2
3
4
5
def input_fn(data_set):
feature_cols = {k: tf.constant(data_set[k].values)
for k in FEATURES}
labels = tf.constant(data_set[LABEL].values)
return feature_cols, labels

注意data_set[k].values将返回一个numpy arrayfeature_cols是一个字典。input_fn()函数将对data_set进行处理,这意味着我们可以把任何我们需要的DataFrame送入该函数中,包括training_settest_setprediction_set


训练回归器

训练神经网络回归器,我们只需要执行fit方法,然后把training_set送入input_fn中:

1
regressor.fit(input_fn=lambda: input_fn(training_set), steps=5000)

我们将会看到打印出的训练信息,每100步迭代打印一次训练损失:

1
2
3
4
5
6
7
8
9
10
11
INFO:tensorflow:Step 1: loss = 483.179
INFO:tensorflow:Step 101: loss = 81.2072
INFO:tensorflow:Step 201: loss = 72.4354
...
INFO:tensorflow:Step 1801: loss = 33.4454
INFO:tensorflow:Step 1901: loss = 32.3397
INFO:tensorflow:Step 2001: loss = 32.0053
INFO:tensorflow:Step 4801: loss = 27.2791
INFO:tensorflow:Step 4901: loss = 27.2251
INFO:tensorflow:Saving checkpoints for 5000 into /tmp/boston_model/model.ckpt.
INFO:tensorflow:Loss for final step: 27.1674.


评估模型

借下来我们在测试集上评估训练好的模型,执行evaluate指令,这次把test_set送入input_fn函数:

1
ev = regressor.evaluate(input_fn=lambda: input_fn(test_set), steps=1)

打印出损失的确切值:

1
2
loss_score = ev["loss"]
print("Loss: {0:f}".format(loss_score))

可以看到以下输出:

1
2
3
INFO:tensorflow:Eval steps [0,1) for training step 5000.
INFO:tensorflow:Saving evaluation summary for 5000 step: loss = 11.9221
Loss: 11.922098


进行预测

最后,我们可以用训练好的模型来进行房价的预测,这里我们使用的数据集是prediction_set,共有6个样本,包含了特征数据但是没有包含标签数据。

1
2
3
4
y = regressor.predict(input_fn=lambda: input_fn(prediction_set))
# .predict() returns an iterator; convert to a list and print predictions
predictions = list(itertools.islice(y, 6))
print ("Predictions: {}".format(str(predictions)))

结果输出如下:

1
Predictions: [ 33.30348587 17.04452896 22.56370163 34.74345398 14.55953979 19.58005714]

结果给出了六个样本的预测房价。


参考