TensorFlow 笔记(十三):tf.contrib.learn入门

TensorFlow有很多高级的机器学习API,tf.contrib.learn就是其中之一。tf.contrib.learn的出现让机器学习模型的建立、训练和评估变得十分的简单。本文介绍了用tf.contrib.learn搭建神经网络分类器并对Iris数据集进行分类,我们的代码分为以下五步:

  1. 载入包含Iris训练数据和测试数据的CSV文件并保存为Dataset格式
  2. 构建神经网络分类器
  3. 用训练数据训练模型
  4. 评估模型的准确率
  5. 对新的样本进行分类

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


数据集介绍

Iris数据集是一个关于鸢尾花的数据集,样式如下:

Sepal Length Sepal Width Petal Length Petal Width Species
5.1 3.5 1.4 0.2 0
4.9 3.0 1.4 0.2 0
4.7 3.2 1.3 0.2 0
7.0 3.2 4.7 1.4 1
6.4 3.2 4.5 1.5 1
6.9 3.1 4.9 1.5 1
6.5 3.0 5.2 2.0 2
6.2 3.4 5.4 2.3 2
5.9 3.0 5.1 1.8 2

数据集包含了150行数据,代表着150个样本,共有3个类别,每个类别有50个样本,具体类别如下(分别用0、1、2表示):

  • setosa
  • versicolor
  • virginica

数据集共有四个特征(浮点数):

  • Sepal length
  • Sepal width
  • Petal length
  • Petal width

我们要利用花萼的长度和宽度,花瓣的长度和宽度这四个特征来预测花的类别。


导入数据集

对于本教程,Iris数据集已经被随机排序并且分割成了两个CSV文件:

  • 训练集包含120个样本,文件名iris_training.csv
  • 测试集包含30个样本,文件名iris_test.csv

我们只需要从TensorFlow官网下载这两个文件即可,其中iris_training.csv文件如下:

120 4 setosa versicolor virginica
6.4 2.8 5.6 2.2 2
5.0 2.3 3.3 1.0 1

第一行为头信息,分别描述了训练数据的样本数、特征数以及种类名,接下来的120行为训练数据。前四列为四种特征,最后一列为所属类别。iris_test.csv与此类似。

代码开始阶段,我们首先要导入必要的模块,并且定义我们下载并存储数据集的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import urllib
import tensorflow as tf
import numpy as np
IRIS_TRAINING = "iris_training.csv"
IRIS_TRAINING_URL = "http://download.tensorflow.org/data/iris_training.csv"
IRIS_TEST = "iris_test.csv"
IRIS_TEST_URL = "http://download.tensorflow.org/data/iris_test.csv"

接下来判断目标地址有没有该数据集,如果没有则需要下载数据集:

1
2
3
4
5
6
7
8
9
if not os.path.exists(IRIS_TRAINING):
raw = urllib.urlopen(IRIS_TRAINING_URL).read()
with open(IRIS_TRAINING,'w') as f:
f.write(raw)
if not os.path.exists(IRIS_TEST):
raw = urllib.urlopen(IRIS_TEST_URL).read()
with open(IRIS_TEST,'w') as f:
f.write(raw)

最后使用load_csv_with_header()函数载入训练和测试数据到Dataset中,该函数在tf.contrib.learn.datasets.base类中,有三个必须的参数:

  • filename:表示CSV文件的路径。
  • target_dtype:表示数据集label的numpy类型。
  • features_dtype:表示数据集特征的numpy类型。

在这里,我们的label共有三类,用0-2表示,因此它的numpy类型为np.int

1
2
3
4
5
6
7
8
9
# Load datasets.
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)

tf.contrib.learnDataset是命名元组(named tuples),我们可以通过training_set.datatraining_set.target命令非常方便的获得训练集的特征数据和目标标签数据。同样的,test_set.datatest_set.target分别包含了测试集的特征数据和目标标签数据。


补充

1、关于load_csv_with_header()函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Dataset = collections.namedtuple('Dataset', ['data', 'target'])
#...
def load_csv_with_header(filename,
target_dtype,
features_dtype,
target_column=-1):
"""Load dataset from CSV file with a header row."""
with gfile.Open(filename) as csv_file:
data_file = csv.reader(csv_file)
header = next(data_file) # 返回第一行数据
# 此处data_file可以理解为一个迭代器
# 该行代码可以替换为: header = data_file.__next__()
n_samples = int(header[0]) # 返回样本数目
n_features = int(header[1]) # 返回特征数目
data = np.zeros((n_samples, n_features), dtype=features_dtype)
target = np.zeros((n_samples,), dtype=target_dtype)
for i, row in enumerate(data_file):
# enumerate()函数可以同时遍历索引和元素
# 因为data_file为迭代器,上面用过一次next,此处则从第二行开始
target[i] = np.asarray(row.pop(target_column), dtype=target_dtype)
data[i] = np.asarray(row, dtype=features_dtype)
return Dataset(data=data, target=target)

关于生成器的相关概念,可以看下面这个教程:


2、关于命名元组:

1
2
3
4
collections.namedtuple(typename,
field_names,
verbose=False,
rename=False)

返回一个名为typenname的元组。参数field_names是一个字符串表示的元素名称,每个字段之间可以通过空格、逗号方式来分隔,比如’x y’,’x, y’。另外也可以采用列表的方式,比如[‘x’, ‘y’]。在字段名称命名上需要注意的是每个字段必须是有效的Python标识符,同时不能是python关键字,另外不要以下划线或数字开头。

如果参数rename为True就会自动地把不合法名称转换为相应合法的名称,比如:['abc', 'def', 'ghi', 'abc']转换为['abc', '_1', 'ghi', '_3'],在这里把def转换_1,同时把重复的abc转换_3

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#python 3.4
import collections
Point = collections.namedtuple('Point', 'x, y, z')
p1 = Point(30, 40, 50)
print(p1)
# Point(x=30, y=40, z=50)
print(p1[0] + p1[1] + p1[2])
# 120
x, y, z = p1
print(x, y, z)
# 30 40 50
print(p1.x, p1.y, p1.z)
# 30 40 50


构建深度神经网络分类器

tf.contrib.learn提供了多种预定义好的模型,我们叫它Estimators,我们可以把它当作黑箱子来训练和评估我们的数据。在这里,我们将搭建一个深度神经网络分类器来训练我们的Iris数据。在使用tf.contrib.learn之前,我们首先要用简短的代码实例化tf.contrib.learn.DNNClassifier

1
2
3
4
5
6
7
8
# Specify that all features have real-value data
feature_columns = [tf.contrib.layers.real_valued_column("", dimension=4)]
# Build 3 layer DNN with 10, 20, 10 units respectively.
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,
hidden_units=[10, 20, 10],
n_classes=3,
model_dir="/tmp/iris_model")

上面代码的第一部分定义了特征列(feature columns),它定义了数据集的数据类型。因为所有的特征数据是连续的,因此我们用tf.contrib.layers.real_valued_column()函数来构建特征列。在数据集中有四种特征(sepal width, sepal height, petal width, and petal height),因此我们设置dimension=4

然后,代码用以下参数创建了一个DNNClassifier模型:

  • feature_columns=feature_columns,上面提到的特征列的设置。
  • hidden_units=[10, 20, 10],三个隐藏层,分别包含10、20和10个神经元
  • n_classes=3,目标种类数,共三个类别
  • model_dir=/tmp/iris_model,在训练过程中TensorFlow保存checkpoint数据的路径。


输入训练数据

tf.contrib.learn的API使用的是输入函数,它们会创建TensorFlow的ops然后生成数据。在本例中,因为数据量足够小因此我们直接把它们存在TensorFlow constants中。代码如下:

1
2
3
4
5
6
# Define the training inputs
def get_train_inputs():
x = tf.constant(training_set.data)
y = tf.constant(training_set.target)
return x, y


训练模型

我们已经构建好模型名为classifier,现在我们可以用fit方法来训练Iris数据,通过调用get_train_inputs函数给参数input_fn来传入训练数据,同时设置迭代次数。代码如下:

1
2
# Fit model.
classifier.fit(input_fn=get_train_inputs, steps=2000)

模型的状态将会保存在classifier中,这意味着我们可以反复的训练。上面的代码等同于如下代码:

1
2
classifier.fit(x=training_set.data, y=training_set.target, steps=1000)
classifier.fit(x=training_set.data, y=training_set.target, steps=1000)


评估模型

我们已经在Iris数据集上训练好了一个DNNClassifier模型classifier,现在我们要在Iris测试集上评估模型的准确率。用到的函数为evaluate。诸如fitevaluate这种函数需要传入一个输入函数来建立输入管道。evaluate函数会返回一个包含着评估结果的dict字典。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
# Define the test inputs
def get_test_inputs():
x = tf.constant(test_set.data)
y = tf.constant(test_set.target)
return x, y
# Evaluate accuracy.
accuracy_score = classifier.evaluate(input_fn=get_test_inputs,
steps=1)["accuracy"]
print("\nTest Accuracy: {0:f}\n".format(accuracy_score))

我们会得到以下输出:

1
Test Accuracy: 0.966667


补充

1、evaluate函数输出的为一个dict字典,字典如下:

1
{'accuracy': 0.93333334, 'global_step': 2000, 'loss': 0.068770193}

其中global_step为训练迭代的次数。代码中我们直接令accuracy_score等于其中的accuracy的值。


2、python格式化输出print("\nTest Accuracy: {0:f}\n".format(accuracy_score))

其中的0表示后面第0个数,f表示数据类型为float型。


分类新样本

使用classifierpredict()函数可以对新的样本进行分类。例如我们有如下两个新的鸾尾花的样本:

Sepal Length Sepal Width Petal Length Petal Width
6.4 3.2 4.5 1.5
5.8 3.1 5.0 1.7

我们可以用predict()函数来预测它们的种类。predict会返回一个生成器,我们可以把它很方便的转化为列表(list)。代码如下:

1
2
3
4
5
6
7
8
9
10
11
# Classify two new flower samples.
def new_samples():
return np.array(
[[6.4, 3.2, 4.5, 1.5],
[5.8, 3.1, 5.0, 1.7]], dtype=np.float32)
predictions = list(classifier.predict(input_fn=new_samples))
print(
"New Samples, Class Predictions: {}\n"
.format(predictions))

输出结果如下:

1
New Samples, Class Predictions: [1 2]

结果表明第一个样本种类为Iris versicolor,第二个样本种类为Iris virginica


参考