TensorFlow采用声明式编程范式,其优势包括:
- 代码可读性强
- 支持引用透明,声明式编程没有内部状态,不依赖于外部环境
- 提供预编译优化能力
基础概念
数据流图基本概念
- 节点
a. 数学函数和表达式:Add,MatMul
b. 存储模型参数的变量:W,b
c. 占位符(placeholder):Input
d. 梯度值:Gradients
e. 更新参数的操作:Update W - 有向边
包括数据边(实线)和依赖边(虚线) - 执行原理
不以代码定义顺序执行,以节点逻辑关系决定各节点的执行顺序,数据流图是一个有向无环图
张量(Tensor)
TensorFlow中的张量包括标量,向量,矩阵,高维张量等等。
TF中使用句柄实现张量,存储张量的元信息和指向张量数据内存的指针,使用引用计数释放不再需要的张量。
import tensorflow as tf
a = tf.constant(1.0) # 一般不使用Tensor()构造函数创建张量
b = tf.constant(2.0) # 使用操作间接创建
c = tf.add(a, b)
print([a, b, c])
with tf.Session() as sess:
print(c.eval()) # 调用张量的eval()函数求解
print(sess.run([a, b, c])) # 使用会话求解
稀疏张量(SparseTensor)
用于处理高维稀疏数据,包含indices,values,dense_shape三个属性。
indices:形状为(N, ndims)的Tensor,N为非0元素个数,ndims表示张量阶数
values:形状为(N)的Tensor,保存indices中指定的非0元素的值
dense_shape:形状为(ndims)的Tensor,表示该稀疏张量对应稠密张量的形状
import tensorflow as tf
sp = tf.SparseTensor(indices = [[0, 2], [1, 3]], values = [1, 2], dense_shape = [3, 4])
with tf.Session() as sess:
print(sp.eval())
操作
计算节点
Operation类,不需要显式构造
tf.global_variables_initializer的本质是将initial_value传入变量中的Assign节点,实现赋值
存储节点
Variable类
包含四个子节点:变量初始值(initial_value),更新操作(Assign),读取操作(read)和变量操作((a),括号代表有状态)
变量有状态,生命周期和数据流图相同,普通节点是临时创建的,当依赖它们的所有操作执行完毕后会被释放
数据节点
Placeholder类,调用tf.placeholder()创建,用于向数据流图中传入数据
import tensorflow as tf
import numpy as np
with tf.name_scope("PlaceholderExample"):
x = tf.placeholder(tf.float32, shape=(2, 2), name="x") # 创建placeholder
y = tf.matmul(x, x, name="natmul")
with tf.Session() as sess:
rand_array = np.random.randn(2, 2)
print(sess.run(y, feed_dict={x: rand_array})) # 使用feed_dict喂入数据
会话
Session类
sess = tf.Session() # sess不会被设置为默认会话
sess.run(#something)
sess.close()
# or
with tf.Session() as sess:
Tensor.eval() # 参数内需传入session,如果在with作用域内,则会使用默认sess
Operation.run() # 在内部调用了Session.run()
sess.run()
# do something
# with结束后调用Session.__exit__关闭会话
InteractiveSession交互式会话
a = tf.constant(5.0)
sess = tf.InteractiveSession() # sess会被设置为默认会话
print(a.eval()) # 可直接调用eval()
sess.close()
优化器
损失函数
常用损失函数:
平方损失函数[图片上传失败...(image-e7292-1539227980315)])^{2})
交叉熵损失函数[图片上传失败...(image-b988f9-1539227980315)]))
指数损失函数[图片上传失败...(image-8b85bc-1539227980315)]))
经验风险(平均损失)[图片上传失败...(image-e0b247-1539227980315)]&space;=&space;\frac{1}{N}\sum&space;L(f(x_{i};\theta),y_{i}))
常用正则化项有L0、L1、L2范式
结构风险能减少过拟合,由经验风险加上正则项构成[图片上传失败...(image-191c50-1539227980315)]&space;=&space;\frac{1}{N}\sum&space;L(f(x_{i};\theta),y_{i})&space;+&space;\lambda&space;J(\theta))
优化算法包括梯度下降法,牛顿法,Adam等
优化器
Optimizer类,通过实例子类使用。
实现了minimize()函数,该函数内部调用compute_gradients()和apply_gradiets()计算梯度值并更新参数
示例代码
X = tf.placeholder(...)
Y_ = tf.placeholder(...)
w = tf.Variable(...)
b = tf.Variable(...)
Y = tf.matmul(X, w) + b
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y_, logits=Y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op=optimizer.minimize(loss, global_step=global_step)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for step in xrange(max_train_steps):
sess.run(train_op, feed_dict={...})
一元线性回归实例
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# 设定超参数
learning_rate = 0.01
max_train_steps = 1000
log_step = 50
# 加载训练数据
train_X = np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168], [9.779], [6.182], [7.59], [2.167], [7.042], [10.791], [5.313], [7.997], [5.654], [9.27], [3.1]], dtype = np.float32)
train_Y = np.array([[1.7], [2.76], [2.09], [3.19], [1.694], [1.573], [3.366], [2.596], [2.53], [1.221], [2.827], [3.465], [1.65], [2.904], [2.42], [2.94], [1.3]], dtype = np.float32)
total_samples = train_X.shape[0]
# 设定参数
X = tf.placeholder(tf.float32, [None, 1])
W = tf.Variable(tf.random_normal([1, 1]), name = "weight")
b = tf.Variable(tf.zeros([1]), name = "bias")
Y = tf.matmul(X, W) + b
# 设定loss函数和初始化optimizer
Y_ = tf.placeholder(tf.float32, [None, 1])
loss = tf.reduce_sum(tf.pow(Y-Y_, 2)) / (total_samples)
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
# 训练数据
train_op = optimizer.minimize(loss)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print("Start training")
for step in range(max_train_steps):
sess.run(train_op, feed_dict={X: train_X, Y_: train_Y})
if step % log_step == 0:
c = sess.run(loss, feed_dict={X: train_X, Y_: train_Y})
print("Step:%d, loss==%.4f, W==%.4f, b==%.4f" % (step, c, sess.run(W), sess.run(b)))
final_loss = sess.run(loss, feed_dict={X: train_X, Y_: train_Y})
weight, bias = sess.run([W, b])
print("Step:%d, loss==%.4f, W==%.4f, b==%.4f" % (max_train_steps, final_loss, sess.run(W), sess.run(b)))
print("Linear Regression Model: Y==%.4f*X+%.4f" % (weight, bias))
#使用matplotlib绘制图片
%matplotlib inline
plt.plot(train_X, train_Y, 'ro', label = 'Training data')
plt.plot(train_X, weight * train_X + bias, label = 'Fitting line')
plt.legend()
plt.show()
数据处理方式
输入数据集
用于模型训练、验证和测试的数据集
大数据集一般使用输入流水线并行读取
- 创建文件名列表:使用python列表或tf.train.match_filenames_once()
- 创建文件名队列:使用tf.train.string_input_producer(),打乱数据,随机读取
- 创建Reader和Decoder:
# 创建文件名队列
filename_queue = tf.train.string_input_producer(['stat0.csv', 'stat1.csv'])
reader = tf.TextLineReader()
# 从中取出一条数据
_, value = reader.read(filename_queue)
record_defaults = [[0], [0], [0.0], [0.0]]
# 将数据转换为特征张量
id, age, income, outgo = tf.decode_csv(value, record_defaults = record_defaults)
# 将特征组合成一条记录
features = tf.stack([id, age, income, outgo])
- 创建样例队列:使用tf.train.start_queue_runners()
- 批样例队列:使用tf.train.shuffle_batch(),打乱样例并聚合成批数据
模型参数
W和b,训练对象
- 创建模型参数:W = tf.Variable(...)
几种生成随机张量的方法:
a. tf.random_normal:正态分布
b. tf.truncated:截尾正态分布
c. tf.random_uniform:均匀分布
d. tf.multinomial:多项式分布
e. tf.random_gamma:伽马分布
f. tf.random_shuffle: 按维度重洗
g. tf.random_crop:按形状裁剪
W = tf.Variable(tf.random_normal(shape=(1, 4), stddev=0.35), name="W") # 使用随机方法初始化参数
W_replica = tf.Variable(W.initialized_value(), name="W_replica") # 使用W的初始值初始化参数
W_twice = tf.Variable(W.initialized_value() * 2.0, name="W_twice")
- 初始化模型参数
w = tf.Variable(...)
b = tf.Variable(...)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 初始化所有全局变量
sess.run(tf.local_variables_initializer()) # 初始化所有局部变量
sess.run(tf.variables_initializer([w])) # 初始化部分变量
- 更新模型参数
使用tf.assign()或tf.assign_add()等函数进行赋值 - 保存参数模型
使用tf.train.Saver 读写模型参数到checkpoint文件 - 恢复参数模型
W = tf.Variable(0.0, name='W')
double = tf.multiply(2.0, W)
saver = tf.train.Saver({'weights': W})
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 使用初始化器初始化
saver.restore(sess, 'tmp/summary/test.ckpt') # 读取ckpt文件初始化
for i in range(4):
sess.run(tf.assign_add(W, 1.0))
saver.save(sess, '/tmp/summary/test.ckpt') # 保存到ckpt文件,注意保存的是sess
- 变量作用域
深度神经网络有大量参数,使用tf.Variable非常笨重,这时候使用tf.get_variable和tf.variable_scope更方便
def conv_relu(input, kernel_shape, bias_shape):
weights = tf.get_variable("weights", kernel_shape, initializer=tf.random_normal_initializer()) # 通过 initializer来初始化变量
biases = tf.get_variable("biases", bias_shape, initializer-tf.constant_initializer(0.0))
conv = tf.nn.conv2d(input, weights, strides=[1,1,1,1], padding='SAME')
return tf.nn.relu(conv + biases)
def my_image_filter(input_images):
with tf.variable_scope("conv1", reuse=True): # 使用tf.variable_scope()设置作用域
relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
with tf.variable_scope("conv2", reuse=True):
relu2 = conv_relu(relu1, [5, 5, 32, 32], [32])
命令行参数
一般指超参数和集群参数
- 使用argparse解析命令行参数
a. 创建解析器
b. 添加待解析参数
c. 解析参数
parser = argparse.ArgumentParser(prog='demo', description='A demo program', epilog='The end of usage') # 创建解析器
parser.add_argument('name') # 添加参数
parser.add_argument('-a', '--age', type=int, required=True)
parser.add_argument('-s', '--status', choices=['alpha', 'beta', 'released'], type=str, dest='myStatus')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0')
args = parser.parse_args() # 解析
args, unparsed = parser.parse.known_args() # 将命令中未定义的参数返回给unparsed
print(args)
- 使用tf.app.flags解析
flags = tf.app.flags # 创建flags
flags.DEFINE_string("data_dir", "tmp/mnist-data", "Directory for string mnist data") # 定义string参数,内部会调用add_argument()
FLAGS = flags.FLAGS # 解析出来的参数字典
def main(_):
print(FLAGS.data_dir) # 调用FLAGS的__getattr__获取参数
if __name__ == "__main__"
tf.app.run()
编程框架
单机
- 创建数据流图
- 创建会话
分布式
PS-worker框架
PS用于存储模型参数,worker负责进行训练,计算梯度
- 创建集群
tf.train.ClusterSpec({"worker": ["worker().example.com:2222", "worker1.example.com:2222", "worker2.example.com:2222"], "ps": ["ps0.example.com:2222", "ps1.example.com:2222"]}) # 定义cluster
if FLAGS.num_gpus > 0:
if FLAGS.num_gpus < num_workers:
raise ValueError("number of gpus is less than number of workers")
gpu = (FLAGS.task_index % FLAGS.num_gpus)
worker_device = "/job:worker/task:%d/gpu:%d" % (FLAGS.task_index, gpu)
elif FLAGS.num_gpus == 0:
cpu = 0
worker_device = "/job:worker/task:%d/cpu:%d" % (FLAGS.task_index, cpu)
with tf.device(tf.train.replica_device_setter(worker_device=worker_device, ps_device="/job:ps/cpu:0", cluster=cluster)): # 将操作放置到目标设备
W1 = tf.Variable(...)
...
- 创建分布式数据流图
- 创建并行分布式会话
- 使用Supervisor管理模型训练
sv = tf.train.Supervisor(logdir="/my/training/dir") # Supervisor管理单机训练
with sv.managed_session() as sess:
while not sv.should_stop():
sess.run(train_op)
server = tf.train.Server(cluster, job_name=FLAGS.job_name, task_index=FLAGS.task_index) # Supervisor管理分布式训练
is_chief = (FLAGS.task_index == 0)
sv = tf.train.Supervisor(logdir="/sharde_dir/...", is_chief=is_chief)
with sv.managed_session(server.target) as sess:
while not sv.should_stop:
sess.run(train_op)
TensorBoard
引入抽象节点,代表一组特定操作的集合,用于简化数据流图的网络结构。
示例代码:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('/Users/lyfne/Documents/PythonProject/CIFAR10/mnist_data', one_hot=True)
with tf.name_scope('input'): # 使用name_scope()创建抽象节点
x = tf.placeholder(tf.float32, [None, 784], name='x-input')
y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')
with tf.name_scope('softmax_layer'):
with tf.name_scope('weights'):
weights = tf.Variable(tf.zeros([784, 10]))
with tf.name_scope('biases'):
biases = tf.Variable(tf.zeros([10]))
with tf.name_scope('Wx_plus_b'):
y = tf.matmul(x, weights) + biases
with tf.name_scope('cross_entropy'):
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)
with tf.name_scope('train'):
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
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))
sess = tf.InteractiveSession()
writer = tf.summary.FileWriter('/Users/lyfne/Documents/PythonProject/CIFAR10/summary', sess.graph) # 创建FileWriter,向事件文件写入序列化数据。传入参数为存放目录和当前数据流图
tf.global_variables_initializer().run()
writer.close()
启动方法:
在命令行输入tensorboard --logdir=/dir, 其中dir为你存放log文件的目录
打开浏览器。输入localhost:6006进入TensorBoard页面
汇总数据、事件数据和FileWriter工作原理
- 汇总数据是tf.summary.Summary类或其内嵌类的实例,包括Image、Audio和Value。其内容在summary.proto里。
- 事件数据是tf.summary.Event类的实例,表示在会话中执行操作时产生的事件信息,包括时间戳、全局步数等。其内容在event.proto中。
- FileWriter向事件文件中写入的是事件数据,其他数据会转换为事件数据进行存储。当用户创建FileWriter的时候,会在logdir目录下创建一个事件文件,FileWriter内部的SummaryToEventTransformer会调用add_graph将序列化后的数据流图写入事件文件。
可视化学习过程
汇总操作是一种独立的操作,输入张量,输出汇总数据。
tf.summary.scalar # 获取带标量值的汇总数据
tf.summary.histogram # 获取带统计值的汇总数据
tf.summary.image # 获取带图像的汇总数据
tf.summary.audio # 获取带音频的汇总数据
可视化高维数据
EMBEDDINGS,嵌入投影仪,内置t-SNE和主成分分析两种降维方法。
三种输入数据:嵌入变量,嵌入变量元数据,投影配置参数