命令式编程与声明式编程
命令式编程(imperative programming):每个语句都按原来的意思执行,可以精确控制行为。通常可以无缝的和主语言交互,方便的利用主语言的各类算法,工具包,bug和性能调试器。缺点是实现统一的辅助函数困和提供整体优化都很困难。比如numpy和Torch。
声明式语言(declarative programing):用户只需要声明要做什么,而具体执行则由系统完成。在真正开始计算的时候已经拿到了整个计算图,所以我们可以做一系列优化来提升性能。实现辅助函数也容易,例如对任何计算图都提供forward和backward函数,对计算图进行可视化,将图保存到硬盘和从硬盘读取。缺点是某些功能在主语言中实现简单,但在这里却经常麻烦,例如if-else语句 ;debug也不容易,例如监视一个复杂的计算图中的某个节点的中间结果并不简单。比如Caffe,mxnet和TensorFlow。
参考:a short introduction to mxnet design and implementation (chinese)
基本概念
TensorFlow程序一般可以分为两个阶段,
- 构造部分,使用tensor表示数据,使用graph来表示计算任务
- 执行部分,在被称为Session的context里执行图中的计算(使用feed和fetch可以为任意的op赋值或从中获取数据)
计算图
TensorFlow中每一个计算都是计算图上的一个节点。TensorFlow会自动生成一个默认的计算图,如果没有特殊指定,运算会自动加入这个计算图中。
张量
“node:src_output”
的形式来给出,node
为节点的名称,src_ouput
表示当前张量来自节点的第几个输出(编号从0开始)。当计算图构造完成后,可以通过会话(session)来得到张量的计算结果(或者借助eval()),如tf.Session().run(result)
。
会话
TensorFlow中的会话(session)用来执行定义好的运算。TensorFlow不会自动生成默认的会话,需要手动指定。
placeholder机制
TensorFlow提供了placeholder机制用于提供输入数据:placeholder相当于定义了一个位置,这个位置的数据在程序运行时再使用feed_dict来指定。
为什么会出现这个?若每轮迭代选取的数据都要通过常量来表示,计算图的节点就会非常多。(那为什么不适用variable呢?我现在还没想清楚)
collection集合
tf.add_to_collection('losses', mse_loss)
loss = tf.get_collection('losses')
- 1
- 2
- 1
- 2
tf.add_to_collection
函数可以将资源加入一个或多个集合中,然后通过tf.get_collection
获取一个集合里面的所有资源。
TensorFlow中也自动管理了一些最常用的集合,如:tf.GraphKeys.TRAINABLE_VARIABLES是可学习变量的集合,可通过tf.trainable_variables()
获得。
见《TensorFlow实战Google深度学习框架》P42。
tf.constant
a = tf.constant([1.0, 2.0], name='a')
tf.constant
是一个计算,这个计算的结果为一个张量,保存在变量a中。
张量主要的三个属性:名字(name)、维度(shape)和类型(type)。
见《TensorFlow实战Google深度学习框架》P43。
tf.matmul
a=tf.matmul(x, w1)
tf.matmul
实现了矩阵乘法的功能。
tf.Variable
weights = tf.Variable(tf.random_normal([2, 3], stddev=2))
w2=tf.Variable(weights.initiallized_value())
- 1
- 2
- 1
- 2
tf.Variable
的作用就是保存和更新神经网络中的参数,也支持通过其他变量的初始值来初始化新的变量。tf.Variable
是一个类。
tensorflow随机数生成函数:tf.random_normal
、tf.truncated_normal
、tf.random_uniform
、tf.random_gamma
tensorflow常数生成函数:tf.zeros
、tf.ones
、tf.fill
、tf.constant
见《TensorFlow实战Google深度学习框架》P43。
tf.get_variable() 和 tf.variable_scope()
tf.get_variable
必须包含一个指定变量名称的参数,它会根据这个名字去创建或者获取变量。如果需要通过tf.get_variable
获取一个已经创建的变量,需要通过tf.variable_scope
函数生成一个上下文管理器。具体说来,当tf.variable_scope
函数使用参数reuse=True生成上下文管理器时,这个上下文管理器内所有的tf.get_variable
函数会直接获取已经创建的变量,如果变量不存在则tf.get_variable
函数将报错;相反,如果tf.variable_scope函数使用参数reuse=False生成上下文管理器时,tf.get_variable
操作将创建新的变量,如果同名的变量已经存在,则tf.get_variable
函数将报错。说起来有点绕,可以看下书籍《TensorFlow实战Google深度学习框架》5.3节 变量管理。
想一下,为什么这么做呢?答:这种变量管理的方式就不需要将所有变量都作为参数传递到不同的函数中了,并且大大提高程序的可读性。
除此之外,tf.variable_scope
函数也会创建一个命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀。tf.name_scope
也会创建一个命名空间,但和前者的区别在于此函数将会自动忽略tf.get_variable
创建的变量。
参见:《TensorFlow实战Google深度学习框架》5.3节 变量管理 ;What’s the difference of name scope and a variable scope in tensorflow?
tf.assign
assign(ref, value, validate_shape=None, use_locking=None, name=None)
tf.assign用来更新模型中变量的值,效果等同于 ref = value。
tf.train.ExponentialMovingAverage滑动平均
ema = tf.train.ExponentialMovingAverage(decay, num_updates) # 定义了一个滑动平均类的对象,初始化时给定了衰减率deay和控制衰减率的变量num_updates
maintain_averages_op = ema.apply([v1]) #定义一个更新变量滑动平均的操作,每次执行这个操作时参数中的变量都会被更新
sess.run(maintain_averages_op) # 更新v1的滑动平均值
v1_shadow = ema.average(v1) # 获取滑动平均之后变量的取值
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
滑动平均不会改变变量本身的取值,而是会维护一个影子变量来记录其滑动平均值。所以当需要使用这个滑动平均值时,需要明确调用average函数。
参见《TensorFlow实战Google深度学习框架》P90
tf.contrib.layer.l2_regularizer 正则化
tf.contrib.layer.l2_regularizer
loss_regular = tf.contrib.layer.l2_regularizer(lambda)(weights) # lambda为正则化项的权重,weights为需要正则化的值
- 1
- 1
参见《TensorFlow实战Google深度学习框架》P88
tf.train.exponential_decay 指数衰减学习率
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, mnist.train.num_examples/BATCH_SIZE, LEARNING_RATE_DECAY) # (基础学习率,当前迭代轮数,衰减速度(通常为过完一次所有训练数据需要的迭代次数),学习率衰减系数)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(my_loss, global_step=global_step) # 使用指数衰减的学习率。在minimize函数中传入global_step将自动更新global_step参数,从而使得学习率也得到相应更新。
- 1
- 2
- 1
- 2
learning_rate=learning_rate_base∗decay_rate(global_step/decay_steps)
参见《TensorFlow实战Google深度学习框架》P85
placeholder机制
为什么会出现这个?若每轮迭代选取的数据都要通过常量来表示,计算图的节点就会非常多。为了避免这个问题,TensorFlow提供了placeholder机制用于提供输入数据:placeholder相当于定义了一个位置,这个位置的数据在程序运行时再使用feed_dict来指定。
x = tf.placeholder(tf.float32, shape=(3, 2), name="input")
sess.run(y, feed_dict={x:[[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]})
- 1
- 2
- 1
- 2
tf.control_dependencies控制依赖
with g.control_dependencies([a, b, c]):
# `d` 和 `e` 将在 `a`, `b`, 和`c`执行完之后运行
d = …
e = …
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
train_op = tf.group(train_step, variables_averages_op)
是等价的。
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op(name='train')
- 1
- 2
- 1
- 2
tf.nn.sparse_softmax_cross_entropy_with_logits交叉熵函数
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y, tf.argmax(y_, 1))
- 1
- 1
当分类问题只有一个正确答案时,可以使用这个函数来加速交叉熵的计算。这个函数的第一个参数时神经网络不包括softmax层的前向传播结果,第二个是训练数据的正确答案。因为标准答案是一个长度为10的一维数组,而该函数需要提供的是一个正确答案的数字,所以需要使用tf.argmax函数来得到正确答案对应的类别编号。
tf.train.GradientDescentOptimizer优化算法
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
- 1
- 1
global_step
的值加1。
tf.train.Saver()保存加载模型
保存模型:
saver = tf.train.Saver()
saver.save(sess, path_to_save, global_step=global_step)
- 1
- 2
- 1
- 2
通过变量重命名的方式加载部分模型:
variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
variables_to_restore = variable_averages.variables_to_restore() # Returns a map of names to Variables to restore
saver = tf.train.Saver(variables_to_restore) #参数可以是一个dict(即这里的用法)也可以是一个list
saver.restore(sess, path_saved)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
参考:变量:创建、初始化、保存和加载;variables_to_restore(moving_avg_variables=None)
tf.gfile.FastGFile
一个文件读写的封装类。
tf.gfile.FastGFile(image_path, ‘rb’).read() # Returns the contents of a file as a string.
参考:tf.gfile.FastGFile
tf.argmax
返回沿着某个维度最大值的位置。
a = np.arange(6).reshape(2,3)
a.argmax()
#输出:5
a.argmax(0)
#输出:array([1, 1, 1])
a.argmax(1)
#输出:array([2, 2])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
GPU配置
gpu_options = tf.GPUOptions(visible_device_list="0,1", allow_growth=True) # 指定使用的GPU设备号;允许运行过程中tf根据需求增长扩大显存的使用
gpu_options = tf.GPUOptions(visible_device_list="0", per_process_gpu_memory_fraction=0.4) # 指定使用的GPU设备号;指定每个可用GPU上的显存分配比率
session_config = tf.ConfigProto(gpu_options=gpu_options, allow_soft_placement=True, log_device_placement=True) # gpu配置;如果指定的设备不存在允许tf自动分配设备;打印设备分配日志
sess = tf.Session(config=session_config)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
命令式编程与声明式编程
命令式编程(imperative programming):每个语句都按原来的意思执行,可以精确控制行为。通常可以无缝的和主语言交互,方便的利用主语言的各类算法,工具包,bug和性能调试器。缺点是实现统一的辅助函数困和提供整体优化都很困难。比如numpy和Torch。
声明式语言(declarative programing):用户只需要声明要做什么,而具体执行则由系统完成。在真正开始计算的时候已经拿到了整个计算图,所以我们可以做一系列优化来提升性能。实现辅助函数也容易,例如对任何计算图都提供forward和backward函数,对计算图进行可视化,将图保存到硬盘和从硬盘读取。缺点是某些功能在主语言中实现简单,但在这里却经常麻烦,例如if-else语句 ;debug也不容易,例如监视一个复杂的计算图中的某个节点的中间结果并不简单。比如Caffe,mxnet和TensorFlow。
参考:a short introduction to mxnet design and implementation (chinese)
基本概念
TensorFlow程序一般可以分为两个阶段,
- 构造部分,使用tensor表示数据,使用graph来表示计算任务
- 执行部分,在被称为Session的context里执行图中的计算(使用feed和fetch可以为任意的op赋值或从中获取数据)
计算图
TensorFlow中每一个计算都是计算图上的一个节点。TensorFlow会自动生成一个默认的计算图,如果没有特殊指定,运算会自动加入这个计算图中。
张量
“node:src_output”
的形式来给出,node
为节点的名称,src_ouput
表示当前张量来自节点的第几个输出(编号从0开始)。当计算图构造完成后,可以通过会话(session)来得到张量的计算结果(或者借助eval()),如tf.Session().run(result)
。
会话
TensorFlow中的会话(session)用来执行定义好的运算。TensorFlow不会自动生成默认的会话,需要手动指定。
placeholder机制
TensorFlow提供了placeholder机制用于提供输入数据:placeholder相当于定义了一个位置,这个位置的数据在程序运行时再使用feed_dict来指定。
为什么会出现这个?若每轮迭代选取的数据都要通过常量来表示,计算图的节点就会非常多。(那为什么不适用variable呢?我现在还没想清楚)
collection集合
tf.add_to_collection('losses', mse_loss)
loss = tf.get_collection('losses')
- 1
- 2
- 1
- 2
tf.add_to_collection
函数可以将资源加入一个或多个集合中,然后通过tf.get_collection
获取一个集合里面的所有资源。
TensorFlow中也自动管理了一些最常用的集合,如:tf.GraphKeys.TRAINABLE_VARIABLES是可学习变量的集合,可通过tf.trainable_variables()
获得。
见《TensorFlow实战Google深度学习框架》P42。
tf.constant
a = tf.constant([1.0, 2.0], name='a')
tf.constant
是一个计算,这个计算的结果为一个张量,保存在变量a中。
张量主要的三个属性:名字(name)、维度(shape)和类型(type)。
见《TensorFlow实战Google深度学习框架》P43。
tf.matmul
a=tf.matmul(x, w1)
tf.matmul
实现了矩阵乘法的功能。
tf.Variable
weights = tf.Variable(tf.random_normal([2, 3], stddev=2))
w2=tf.Variable(weights.initiallized_value())
- 1
- 2
- 1
- 2
tf.Variable
的作用就是保存和更新神经网络中的参数,也支持通过其他变量的初始值来初始化新的变量。tf.Variable
是一个类。
tensorflow随机数生成函数:tf.random_normal
、tf.truncated_normal
、tf.random_uniform
、tf.random_gamma
tensorflow常数生成函数:tf.zeros
、tf.ones
、tf.fill
、tf.constant
见《TensorFlow实战Google深度学习框架》P43。
tf.get_variable() 和 tf.variable_scope()
tf.get_variable
必须包含一个指定变量名称的参数,它会根据这个名字去创建或者获取变量。如果需要通过tf.get_variable
获取一个已经创建的变量,需要通过tf.variable_scope
函数生成一个上下文管理器。具体说来,当tf.variable_scope
函数使用参数reuse=True生成上下文管理器时,这个上下文管理器内所有的tf.get_variable
函数会直接获取已经创建的变量,如果变量不存在则tf.get_variable
函数将报错;相反,如果tf.variable_scope函数使用参数reuse=False生成上下文管理器时,tf.get_variable
操作将创建新的变量,如果同名的变量已经存在,则tf.get_variable
函数将报错。说起来有点绕,可以看下书籍《TensorFlow实战Google深度学习框架》5.3节 变量管理。
想一下,为什么这么做呢?答:这种变量管理的方式就不需要将所有变量都作为参数传递到不同的函数中了,并且大大提高程序的可读性。
除此之外,tf.variable_scope
函数也会创建一个命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀。tf.name_scope
也会创建一个命名空间,但和前者的区别在于此函数将会自动忽略tf.get_variable
创建的变量。
参见:《TensorFlow实战Google深度学习框架》5.3节 变量管理 ;What’s the difference of name scope and a variable scope in tensorflow?
tf.assign
assign(ref, value, validate_shape=None, use_locking=None, name=None)
tf.assign用来更新模型中变量的值,效果等同于 ref = value。
tf.train.ExponentialMovingAverage滑动平均
ema = tf.train.ExponentialMovingAverage(decay, num_updates) # 定义了一个滑动平均类的对象,初始化时给定了衰减率deay和控制衰减率的变量num_updates
maintain_averages_op = ema.apply([v1]) #定义一个更新变量滑动平均的操作,每次执行这个操作时参数中的变量都会被更新
sess.run(maintain_averages_op) # 更新v1的滑动平均值
v1_shadow = ema.average(v1) # 获取滑动平均之后变量的取值
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
滑动平均不会改变变量本身的取值,而是会维护一个影子变量来记录其滑动平均值。所以当需要使用这个滑动平均值时,需要明确调用average函数。
参见《TensorFlow实战Google深度学习框架》P90
tf.contrib.layer.l2_regularizer 正则化
tf.contrib.layer.l2_regularizer
loss_regular = tf.contrib.layer.l2_regularizer(lambda)(weights) # lambda为正则化项的权重,weights为需要正则化的值
- 1
- 1
参见《TensorFlow实战Google深度学习框架》P88
tf.train.exponential_decay 指数衰减学习率
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, mnist.train.num_examples/BATCH_SIZE, LEARNING_RATE_DECAY) # (基础学习率,当前迭代轮数,衰减速度(通常为过完一次所有训练数据需要的迭代次数),学习率衰减系数)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(my_loss, global_step=global_step) # 使用指数衰减的学习率。在minimize函数中传入global_step将自动更新global_step参数,从而使得学习率也得到相应更新。
- 1
- 2
- 1
- 2
learning_rate=learning_rate_base∗decay_rate(global_step/decay_steps)
参见《TensorFlow实战Google深度学习框架》P85
placeholder机制
为什么会出现这个?若每轮迭代选取的数据都要通过常量来表示,计算图的节点就会非常多。为了避免这个问题,TensorFlow提供了placeholder机制用于提供输入数据:placeholder相当于定义了一个位置,这个位置的数据在程序运行时再使用feed_dict来指定。
x = tf.placeholder(tf.float32, shape=(3, 2), name="input")
sess.run(y, feed_dict={x:[[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]})
- 1
- 2
- 1
- 2
tf.control_dependencies控制依赖
with g.control_dependencies([a, b, c]):
# `d` 和 `e` 将在 `a`, `b`, 和`c`执行完之后运行
d = …
e = …
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
train_op = tf.group(train_step, variables_averages_op)
是等价的。
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op(name='train')
- 1
- 2
- 1
- 2
tf.nn.sparse_softmax_cross_entropy_with_logits交叉熵函数
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y, tf.argmax(y_, 1))
- 1
- 1
当分类问题只有一个正确答案时,可以使用这个函数来加速交叉熵的计算。这个函数的第一个参数时神经网络不包括softmax层的前向传播结果,第二个是训练数据的正确答案。因为标准答案是一个长度为10的一维数组,而该函数需要提供的是一个正确答案的数字,所以需要使用tf.argmax函数来得到正确答案对应的类别编号。
tf.train.GradientDescentOptimizer优化算法
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
- 1
- 1
global_step
的值加1。
tf.train.Saver()保存加载模型
保存模型:
saver = tf.train.Saver()
saver.save(sess, path_to_save, global_step=global_step)
- 1
- 2
- 1
- 2
通过变量重命名的方式加载部分模型:
variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
variables_to_restore = variable_averages.variables_to_restore() # Returns a map of names to Variables to restore
saver = tf.train.Saver(variables_to_restore) #参数可以是一个dict(即这里的用法)也可以是一个list
saver.restore(sess, path_saved)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
参考:变量:创建、初始化、保存和加载;variables_to_restore(moving_avg_variables=None)
tf.gfile.FastGFile
一个文件读写的封装类。 tf.gfile.FastGFile(image_path, ‘rb’).read() # Returns the contents of a file as a string. 参考:tf.gfile.FastGFile
tf.argmax
返回沿着某个维度最大值的位置。
a = np.arange(6).reshape(2,3)
a.argmax()
#输出:5
a.argmax(0)
#输出:array([1, 1, 1])
a.argmax(1)
#输出:array([2, 2])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
GPU配置
gpu_options = tf.GPUOptions(visible_device_list="0,1", allow_growth=True) # 指定使用的GPU设备号;允许运行过程中tf根据需求增长扩大显存的使用
gpu_options = tf.GPUOptions(visible_device_list="0", per_process_gpu_memory_fraction=0.4) # 指定使用的GPU设备号;指定每个可用GPU上的显存分配比率
session_config = tf.ConfigProto(gpu_options=gpu_options, allow_soft_placement=True, log_device_placement=True) # gpu配置;如果指定的设备不存在允许tf自动分配设备;打印设备分配日志
sess = tf.Session(config=session_config)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4