Building Input Functions with tf.estimator
直接从Building Input Functions with tf.estimator
这一节开始,前面的就先不做笔记了。
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
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)
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)
feature_cols = [tf.feature_column.numeric_column(k) for k in FEATURES]
regressor = tf.estimator.DNNRegressor(feature_columns=feature_cols,
hidden_units=[10, 10], model_dir="/tmp5/boston_model")
def get_input_fn(data_set, num_epochs=None, shuffle=True):
return tf.estimator.inputs.pandas_input_fn(
x=pd.DataFrame({k: data_set[k].values for k in FEATURES}),
y = pd.Series(data_set[LABEL].values),
num_epochs=num_epochs,
shuffle=shuffle)
regressor.train(input_fn=get_input_fn(training_set), steps=5000)
ev = regressor.evaluate(input_fn=get_input_fn(test_set, num_epochs=1, shuffle=False))
loss_score = ev["loss"]
print("Loss: {0:f}".format(loss_score))
y = regressor.predict(
input_fn=get_input_fn(prediction_set, num_epochs=1, shuffle=False))
# .predict() returns an iterator of dicts; convert to a list and print predictions
predictions = list(p["predictions"] for p in itertools.islice(y, 6))
print("Predictions: {}".format(str(predictions)))
1 | from __future__ import absolute_import |
一段一段的理解:
1 | from __future__ import absolute_import |
这里从__future__
这个包下引入了absolute_import
,division
,print_function
。
首先看future官方API, 简单来说就是将python的后面版本新特性导入到当前版本,使得当然版本也可以使用后面版本的语法。
看文档最后的表就知道,这里我使用的python3.6.1,所以这里的代码可以不写,如果是python较低版本,比如2.xxx,那就能需要导入一下了。
1 | import itertools |
同样的,来查看itertools官方API, 它是一个迭代的工具类,提供了各种迭代的方法。
这里的,
1 | itertools.islice(y, 6) |
创建了一个迭代器,从y
集合的第1个元素开始到第6个元素结束。
1 | import pandas as pd |
一样的官方10 Minutes to pandas,
pandas是基于numpy
的,在做数据分析时,用它来表示矩阵向量可能更加方便快捷。
在安装pandas时,直接使用pip3 install pandas
会很慢,基本等于失败,换源后效果显著,
1 | pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package |
这里换成清华源来安装,速度很快。
1 | tf.logging.set_verbosity(tf.logging.INFO) |
打开这个过后,在进行训练时,会打印很多中间过程的信息,
1 | INFO:tensorflow:Restoring parameters from /tmp5/boston_model\model.ckpt-15000 |
这里每100次迭代打印一次当前损失。
1 | COLUMNS = ["crim", "zn", "indus", "nox", "rm", "age", |
定义三个list,第一个是读文件时需要读取的列,第二个是样本的属性,第三个是标签列。定义这个方便对属性和标签进行拆分。
1 | regressor = tf.estimator.DNNRegressor(feature_columns=feature_cols, |
定义了一个深度学习回归模型,[10, 10]
指定了模型共两层,每层10个神经元,model_dir="/tmp5/boston_model"
表示了模型存储的位置。
注意,这里的/tmp5/boston_model
在windows下面表示是在根目录下的tmp5
文件夹,比如程序是在F盘
下执行的,
那么它建立一个F:/tmp5/
目录,如果需要当前目录下的tmp5
目录,那就要使用tmp5/boston_model
路径。
1 | feature_cols = [tf.feature_column.numeric_column(k) for k in FEATURES] |
这里先将FEATURES转化成回归模型所需的格式,然后传入模型。
1 | def get_input_fn(data_set, num_epochs=None, shuffle=True): |
注意这里get_input_fn
中调用的tf.estimator.inputs.pandas_input_fn(...)
返回的是一个函数,因为train()
里面接受的是一个函数对象。
这里调用时按照格式来的,num_epochs
代表数据集可以过几遍,对训练集当然没有限制,所以输入数None
,对于验证集或者测试集,
这里的num_epochs
就要设置为1,因为一个样本只需要过一次,同样的,shuffle
表示是否随机读取样本,也只有训练集需要随机读取操作。
1 | ev = regressor.evaluate(input_fn=get_input_fn(test_set, num_epochs=1, shuffle=False)) |
训练完成后,使用验证集进行验证,这里打印出损失分数。
1 | y = regressor.predict( |
这里就是测试预测的结果,想要的效果是,
1 | Predictions: [33.480186, 18.6161, 23.09123, 34.338253, 16.050083, 19.354153] |
实际的结果是,
1 | Predictions: [array([ 33.83599091], dtype=float32), array([ 17.83500481], dtype=float32), |
说明这里predict()
返回的是一个列表,但是列表中的元素是array
对象,所以这里要简单改动一下,
1
predictions = list(p["predictions"][0] for p in itertools.islice(y, 6))
1 | predictions = list(p["predictions"][0] for p in itertools.islice(y, 6)) |
TensorBoard: Visualizing Learning
同样的,直接阅读代码,来看看整个流程到底发生了什么,mnist_with_summaries.py。
首先,看入口函数:
1 | if __name__ == '__main__': |
第一句,
1 | if __name__ == '__main__': |
表示下面的代码只有在这个文件被当做脚本执行时才会执行。
下一句,
1 | parser = argparse.ArgumentParser() |
参考argparse笔记来理解, 那么这里也就是定义了输入的可选参数,同时也设定了默认值。
然后关注这一句,
1 | default=os.path.join( os.getenv('TEST_TMPDIR', '/tmp'), |
其中os.getenv('TEST_TMPDIR', '/tmp')
是查询系统信息的函数,它去查询系统中'TEST_TMPDIR'
的值,
显然这里没有对'TEST_TMPDIR'
定义,于是它就会返回这里的设置默认值'/tmp'
,否则返回null
。
然后,对于os.path.join()
它就是一个将路径合并的函数,例如,
1 | $ os.path.join('/hello/','good/boy/','doiido') |
但是如果是在windows下面执行,它就会变成下面这样,
1 | $ os.path.join( '/tmp', 'tensorflow/mnist/input_data') |
我去,所以在windows下面执行的时候老是路径报错。
接着就是main
函数的最后两句,
1 | FLAGS, unparsed = parser.parse_known_args() |
随便百度就可以看到它的源代码,它所进行的操作就是传入main
函数,然后再传入参数,然后运行。
这里FLAGS
里面的参数我们已经使用了,所以将剩下的参数传入,其中sys.argv[0]
是当前文件的路径位置,
unparsed
就是剩下未使用的参数。
其次看main函数,
1 | def main(_): |
这里的log_dir
就是日志文件,也就是TensorBoard画图时所需要的文件。
操作就是删除旧的日志文件,然后重新建一个文件夹,再调用train()函数。
这里来到train()函数,剩下的所有代码都在这里面。
1 | from tensorflow.examples.tutorials.mnist import input_data |
这个部分会读取本地的mnist
数据集,如果没有它就会先将数据集下载下来,放到指定的数据目录里面。
1 | sess = tf.InteractiveSession() |
这一句使用了InteractiveSession()来建立了会话,它与Session()的不同之处在于,使用了它之后, 调用时run()变成了tf.global_variables_initializer().run()。
1 | # Input placeholders |
这里定义了两个输入的占位符。使用name_scope()使得在TensorBoard上它们会同处于'input'
这个命名之下。
1 | with tf.name_scope('input_reshape'): |
这里将输入重新reshape变成方阵(图片本来的形式),然后使用tf.summary.image()
将它记录到日志里。
1 | # We can't initialize these variables to 0 - the network will get stuck. |
定义了一个权重初始化的函数,输入需要初始化权重的shape
,然后第一句进行了一个truncated_normal()
,
它进行正态初始化,但是对于超出正态一定范围的值进行丢弃,返回的是一个tensor
。
然后使用tf.Variable()
将它变成一个变量。
同样的,将bias
初始为0.1。
下面的函数是专门用来对变量进行记录的,提供给TensorBoard
去使用,
1 | def variable_summaries(var): |
记录下变量的均值,标准差,最大值,最小值,柱状图。
接着的一个函数用来构建神经网络层,
1 | def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu): |
初始化权重并记录,初始化偏置并记录。计算通过激活函数前的输出并记录,计算输出并记录,最后返回输出。
下面开始构建神经网络,
1 | hidden1 = nn_layer(x, 784, 500, 'layer1') |
首先构建了第一层隐藏层,神经元数量500。
1 | with tf.name_scope('dropout'): |
下一步则是在第一层之后,加入了一个dropout
层,这里的keep_prob
使用了占位符,以便调整。同样也将概率进行记录。
1 | # Do not apply softmax activation yet, see below. |
这里建立了神经网络的第二层,但是激活函数这里传入的是一个tf.identity
,这个函数的意思是,
传入什么数,它就传出什么数…那么这里就相当于是没有激活函数。
1 | with tf.name_scope('cross_entropy'): |
这里就像它注释里面写的,它先计算了整体的交叉熵,然后取了一下均值,最后进行记录。
1 | with tf.name_scope('train'): |
调用AdamOptimizer()
来进行优化。
1 | with tf.name_scope('accuracy'): |
首先,这里的y是一个行向量,所以使用tf.argmax(y, 1)
将它每一行的最大值得下标找出来。同时,
tf.equal()
将返回一个bool
值组成的tensor
。
使用tf.reduce_mean()
计算一下均值,就得到了当前的准确率,然后将它进行记录。
1 | # Merge all the summaries and write them out to |
将所有的日志合并,进行共同的操作。然后定义日志写入的位置。最后将变量初始化。
最后就到了训练阶段,首先定义了feed_dict
函数,用来给占位符赋值,
1 | def feed_dict(train): |
如果是训练阶段,那么就mnist.train
中取出一个batch
给x
和y
,如果不是训练阶段,
在这里肯定就是测试阶段了,那么就把整个测试集传给x
和y
。
下面就是训练代码:
1 | # Train the model, and also write summaries. |
首先,每运行10步,就计算一下当前的准确度,并将日志数据写入test日志文件。
然后,每隔100步,加入tf.RunOptions()
和tf.RunMetadata()
,这好像是一些原信息,类似运行时间什么的。
剩下的步骤就是正常的训练,每次训练的日志都写入train
日志文件。
最后关闭train_writer
和test_writer
。