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。