0%

TensorFlow 笔记一


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
2
3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

这里从__future__这个包下引入了absolute_importdivisionprint_function

首先看future官方API, 简单来说就是将python的后面版本新特性导入到当前版本,使得当然版本也可以使用后面版本的语法。

看文档最后的表就知道,这里我使用的python3.6.1,所以这里的代码可以不写,如果是python较低版本,比如2.xxx,那就能需要导入一下了。


1
2
3
import itertools

predictions = list(p["predictions"] for p in itertools.islice(y, 6))

同样的,来查看itertools官方API, 它是一个迭代的工具类,提供了各种迭代的方法。

这里的,

1
itertools.islice(y, 6)

创建了一个迭代器,从y集合的第1个元素开始到第6个元素结束。


1
import pandas as pd

一样的官方10 Minutes to pandaspandas是基于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
2
3
4
5
6
7
8
9
10
11
12
13
INFO:tensorflow:Restoring parameters from /tmp5/boston_model\model.ckpt-15000
INFO:tensorflow:Saving checkpoints for 15001 into /tmp5/boston_model\model.ckpt.
INFO:tensorflow:loss = 1842.78, step = 15001
INFO:tensorflow:global_step/sec: 702.316
INFO:tensorflow:loss = 3675.04, step = 15101 (0.143 sec)
INFO:tensorflow:global_step/sec: 779.157
INFO:tensorflow:loss = 2918.1, step = 15201 (0.128 sec)
INFO:tensorflow:global_step/sec: 667.102
INFO:tensorflow:loss = 4292.76, step = 15301 (0.150 sec)
INFO:tensorflow:global_step/sec: 704.81
INFO:tensorflow:loss = 2817.55, step = 15401 (0.142 sec)
INFO:tensorflow:global_step/sec: 727.968
...

这里每100次迭代打印一次当前损失。


1
2
3
4
5
COLUMNS = ["crim", "zn", "indus", "nox", "rm", "age",
"dis", "tax", "ptratio", "medv"]
FEATURES = ["crim", "zn", "indus", "nox", "rm",
"age", "dis", "tax", "ptratio"]
LABEL = "medv"

定义三个list,第一个是读文件时需要读取的列,第二个是样本的属性,第三个是标签列。定义这个方便对属性和标签进行拆分。


1
2
regressor = tf.estimator.DNNRegressor(feature_columns=feature_cols, 
hidden_units=[10, 10], model_dir="/tmp5/boston_model")

定义了一个深度学习回归模型,[10, 10]指定了模型共两层,每层10个神经元,model_dir="/tmp5/boston_model"表示了模型存储的位置。

注意,这里的/tmp5/boston_modelwindows下面表示是在根目录下的tmp5文件夹,比如程序是在F盘下执行的, 那么它建立一个F:/tmp5/目录,如果需要当前目录下的tmp5目录,那就要使用tmp5/boston_model路径。

1
feature_cols = [tf.feature_column.numeric_column(k) for k in FEATURES]

这里先将FEATURES转化成回归模型所需的格式,然后传入模型。


1
2
3
4
5
6
7
8
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)

注意这里get_input_fn中调用的tf.estimator.inputs.pandas_input_fn(...)返回的是一个函数,因为train()里面接受的是一个函数对象。

这里调用时按照格式来的,num_epochs代表数据集可以过几遍,对训练集当然没有限制,所以输入数None,对于验证集或者测试集, 这里的num_epochs就要设置为1,因为一个样本只需要过一次,同样的,shuffle表示是否随机读取样本,也只有训练集需要随机读取操作。


1
2
3
4
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))

训练完成后,使用验证集进行验证,这里打印出损失分数。


1
2
3
4
5
6
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
Predictions: [33.480186, 18.6161, 23.09123, 34.338253, 16.050083, 19.354153]

实际的结果是,

1
2
3
Predictions: [array([ 33.83599091], dtype=float32), array([ 17.83500481], dtype=float32),
array([ 24.12747383], dtype=float32), array([ 35.41732025], dtype=float32),
array([ 15.54900551], dtype=float32), array([ 17.97283173], dtype=float32)]

说明这里predict()返回的是一个列表,但是列表中的元素是array对象,所以这里要简单改动一下,

1
predictions = list(p["predictions"][0] for p in itertools.islice(y, 6))


TensorBoard: Visualizing Learning

同样的,直接阅读代码,来看看整个流程到底发生了什么,mnist_with_summaries.py


首先,看入口函数:

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
if __name__ == '__main__':

parser = argparse.ArgumentParser()
parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
default=False,
help='If true, uses fake data for unit testing.')
parser.add_argument('--max_steps', type=int, default=1000,
help='Number of steps to run trainer.')
parser.add_argument('--learning_rate', type=float, default=0.001,
help='Initial learning rate')
parser.add_argument('--dropout', type=float, default=0.9,
help='Keep probability for training dropout.')
parser.add_argument(
'--data_dir',
type=str,
default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
'tensorflow/mnist/input_data'),
help='Directory for storing input data')
parser.add_argument(
'--log_dir',
type=str,
default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
'tensorflow/mnist/logs/mnist_with_summaries'),
help='Summaries log directory')
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

第一句,

1
if __name__ == '__main__':

表示下面的代码只有在这个文件被当做脚本执行时才会执行。

下一句,

1
2
3
4
parser = argparse.ArgumentParser()
parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
default=False,
help='If true, uses fake data for unit testing.')

参考argparse笔记来理解, 那么这里也就是定义了输入的可选参数,同时也设定了默认值。

然后关注这一句,

1
2
default=os.path.join( os.getenv('TEST_TMPDIR', '/tmp'),
'tensorflow/mnist/input_data')

其中os.getenv('TEST_TMPDIR', '/tmp')是查询系统信息的函数,它去查询系统中'TEST_TMPDIR'的值, 显然这里没有对'TEST_TMPDIR'定义,于是它就会返回这里的设置默认值'/tmp',否则返回null

然后,对于os.path.join()它就是一个将路径合并的函数,例如,

1
2
3
$ os.path.join('/hello/','good/boy/','doiido')

'/hello/good/boy/doiido'

但是如果是在windows下面执行,它就会变成下面这样,

1
2
3
$ os.path.join( '/tmp', 'tensorflow/mnist/input_data')

'/tmp\\tensorflow/mnist/input_data'

我去,所以在windows下面执行的时候老是路径报错。

接着就是main函数的最后两句,

1
2
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

随便百度就可以看到它的源代码,它所进行的操作就是传入main函数,然后再传入参数,然后运行。 这里FLAGS里面的参数我们已经使用了,所以将剩下的参数传入,其中sys.argv[0]是当前文件的路径位置, unparsed就是剩下未使用的参数。


其次看main函数,

1
2
3
4
5
def main(_):
if tf.gfile.Exists(FLAGS.log_dir):
tf.gfile.DeleteRecursively(FLAGS.log_dir)
tf.gfile.MakeDirs(FLAGS.log_dir)
train()

这里的log_dir就是日志文件,也就是TensorBoard画图时所需要的文件。 操作就是删除旧的日志文件,然后重新建一个文件夹,再调用train()函数。


这里来到train()函数,剩下的所有代码都在这里面。

1
2
3
4
5
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets(FLAGS.data_dir,
one_hot=True,
fake_data=FLAGS.fake_data)

这个部分会读取本地的mnist数据集,如果没有它就会先将数据集下载下来,放到指定的数据目录里面。

1
sess = tf.InteractiveSession()

这一句使用了InteractiveSession()来建立了会话,它与Session()的不同之处在于,使用了它之后, 调用时run()变成了tf.global_variables_initializer().run()


1
2
3
4
# Input placeholders
with tf.name_scope('input'):
x = tf.placeholder(tf.float32, [None, 784], name='x-input')
y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')

这里定义了两个输入的占位符。使用name_scope()使得在TensorBoard上它们会同处于'input'这个命名之下。

1
2
3
with tf.name_scope('input_reshape'):
image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
tf.summary.image('input', image_shaped_input, 10)

这里将输入重新reshape变成方阵(图片本来的形式),然后使用tf.summary.image()将它记录到日志里。

1
2
3
4
5
6
7
8
9
10
# We can't initialize these variables to 0 - the network will get stuck.
def weight_variable(shape):
"""Create a weight variable with appropriate initialization."""
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)

def bias_variable(shape):
"""Create a bias variable with appropriate initialization."""
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)

定义了一个权重初始化的函数,输入需要初始化权重的shape,然后第一句进行了一个truncated_normal(), 它进行正态初始化,但是对于超出正态一定范围的值进行丢弃,返回的是一个tensor。 然后使用tf.Variable()将它变成一个变量。

同样的,将bias初始为0.1


下面的函数是专门用来对变量进行记录的,提供给TensorBoard去使用,

1
2
3
4
5
6
7
8
9
10
11
def variable_summaries(var):
"""Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
with tf.name_scope('summaries'):
mean = tf.reduce_mean(var)
tf.summary.scalar('mean', mean)
with tf.name_scope('stddev'):
stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
tf.summary.scalar('stddev', stddev)
tf.summary.scalar('max', tf.reduce_max(var))
tf.summary.scalar('min', tf.reduce_min(var))
tf.summary.histogram('histogram', var)

记录下变量的均值,标准差,最大值,最小值,柱状图。

接着的一个函数用来构建神经网络层,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
"""Reusable code for making a simple neural net layer.

It does a matrix multiply, bias add, and then uses ReLU to nonlinearize.
It also sets up name scoping so that the resultant graph is easy to read,
and adds a number of summary ops.
"""
# Adding a name scope ensures logical grouping of the layers in the graph.
with tf.name_scope(layer_name):
# This Variable will hold the state of the weights for the layer
with tf.name_scope('weights'):
weights = weight_variable([input_dim, output_dim])
variable_summaries(weights)
with tf.name_scope('biases'):
biases = bias_variable([output_dim])
variable_summaries(biases)
with tf.name_scope('Wx_plus_b'):
preactivate = tf.matmul(input_tensor, weights) + biases
tf.summary.histogram('pre_activations', preactivate)
activations = act(preactivate, name='activation')
tf.summary.histogram('activations', activations)
return activations

初始化权重并记录,初始化偏置并记录。计算通过激活函数前的输出并记录,计算输出并记录,最后返回输出。


下面开始构建神经网络,

1
hidden1 = nn_layer(x, 784, 500, 'layer1')

首先构建了第一层隐藏层,神经元数量500。

1
2
3
4
with tf.name_scope('dropout'):
keep_prob = tf.placeholder(tf.float32)
tf.summary.scalar('dropout_keep_probability', keep_prob)
dropped = tf.nn.dropout(hidden1, keep_prob)

下一步则是在第一层之后,加入了一个dropout层,这里的keep_prob使用了占位符,以便调整。同样也将概率进行记录。

1
2
# Do not apply softmax activation yet, see below.
y = nn_layer(dropped, 500, 10, 'layer2', act=tf.identity)

这里建立了神经网络的第二层,但是激活函数这里传入的是一个tf.identity,这个函数的意思是, 传入什么数,它就传出什么数…那么这里就相当于是没有激活函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
with tf.name_scope('cross_entropy'):
# The raw formulation of cross-entropy,
#
# tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.softmax(y)),
# reduction_indices=[1]))
#
# can be numerically unstable.
#
# So here we use tf.nn.softmax_cross_entropy_with_logits on the
# raw outputs of the nn_layer above, and then average across
# the batch.
diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
with tf.name_scope('total'):
cross_entropy = tf.reduce_mean(diff)
tf.summary.scalar('cross_entropy', cross_entropy)

这里就像它注释里面写的,它先计算了整体的交叉熵,然后取了一下均值,最后进行记录。

1
2
3
with tf.name_scope('train'):
train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(
cross_entropy)

调用AdamOptimizer()来进行优化。

1
2
3
4
5
6
with tf.name_scope('accuracy'):
with tf.name_scope('correct_prediction'):
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
with tf.name_scope('accuracy'):
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.summary.scalar('accuracy', accuracy)

首先,这里的y是一个行向量,所以使用tf.argmax(y, 1)将它每一行的最大值得下标找出来。同时, tf.equal()将返回一个bool值组成的tensor

使用tf.reduce_mean()计算一下均值,就得到了当前的准确率,然后将它进行记录。

1
2
3
4
5
6
# Merge all the summaries and write them out to
# /tmp/tensorflow/mnist/logs/mnist_with_summaries (by default)
merged = tf.summary.merge_all()
train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
tf.global_variables_initializer().run()

将所有的日志合并,进行共同的操作。然后定义日志写入的位置。最后将变量初始化。


最后就到了训练阶段,首先定义了feed_dict函数,用来给占位符赋值,

1
2
3
4
5
6
7
8
9
def feed_dict(train):
"""Make a TensorFlow feed_dict: maps data onto Tensor placeholders."""
if train or FLAGS.fake_data:
xs, ys = mnist.train.next_batch(100, fake_data=FLAGS.fake_data)
k = FLAGS.dropout
else:
xs, ys = mnist.test.images, mnist.test.labels
k = 1.0
return {x: xs, y_: ys, keep_prob: k}

如果是训练阶段,那么就mnist.train中取出一个batchxy,如果不是训练阶段, 在这里肯定就是测试阶段了,那么就把整个测试集传给xy

下面就是训练代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Train the model, and also write summaries.
# Every 10th step, measure test-set accuracy, and write test summaries
# All other steps, run train_step on training data, & add training summaries
for i in range(FLAGS.max_steps):
if i % 10 == 0: # Record summaries and test-set accuracy
summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
test_writer.add_summary(summary, i)
print('Accuracy at step %s: %s' % (i, acc))
else: # Record train set summaries, and train
if i % 100 == 99: # Record execution stats
run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
run_metadata = tf.RunMetadata()
summary, _ = sess.run([merged, train_step],
feed_dict=feed_dict(True),
options=run_options,
run_metadata=run_metadata)
train_writer.add_run_metadata(run_metadata, 'step%03d' % i)
train_writer.add_summary(summary, i)
print('Adding run metadata for', i)
else: # Record a summary
summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True))
train_writer.add_summary(summary, i)
train_writer.close()
test_writer.close()

首先,每运行10步,就计算一下当前的准确度,并将日志数据写入test日志文件。

然后,每隔100步,加入tf.RunOptions()tf.RunMetadata(),这好像是一些原信息,类似运行时间什么的。

剩下的步骤就是正常的训练,每次训练的日志都写入train日志文件。

最后关闭train_writertest_writer