TensorFlow封装了很多有用的函数,本文主要介绍介绍其中常用的函数
- 优秀参考链接:
tf.identify()
- 参考链接: https://blog.csdn.net/qq_23981335/article/details/81361748
y=x
图上是看不到y的具体节点的,因为这仅仅是个普通的Python变量引用复制,不是一个TF操作- 由于不是一个TF操作,所以执行
y
与对x
取值操作一样,没有多余的操作
- 由于不是一个TF操作,所以执行
tf.identify()
会复制一个变量,此时图中会增加一个操作
string与数字类型的转换
- tf.as_string()
- tf.string_to_number()
tf.pad()
tf.slice()
tf.slice(x, [0, 2], [-1, 1])
等价于x[:, 2:3]
- 两者都会在graph中增加一个节点
- 需要注意,在graph里面两者的节点不一样,但是获得的结果是相同的
- 图中
x[:, 2:3]
会多几个输出属性,所以猜测tf.slice
更好些【不精确】
- 图中
tf.map_fn()
字符串转固定二维数组
场景:每行包含一个字符串,每个字符串中包含多个值,按照’;’分隔开
转换方式1
方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14nums = tf.string_split(tf.reshape(nums, [-1]), delimiter=";")
# SparseTensor不能使用string_to_number,下面的句子不能用
# nums = tf.string_to_number(nums, out_type=tf.float32)
# 如果知道batch_size的话可以用下面的句子来减少内存使用,但不是必要的
# nums = tf.sparse_slice(nums, [0, 0], [batch_size, target_len])
nums = tf.sparse_tensor_to_dense(nums, default_value='0.0')
nums = tf.string_to_number(nums, out_type=tf.float32)
# 目前无法保障行的长度,通过pad 0实现最大长度,然后再截断到目标长度(有点浪费内存了)
nums = tf.pad(nums, paddings=[[0, 0], [0, target_len]], mode="CONSTANT")
# nums = nums[:, :target_len] ==
nums = tf.slice(nums, [0, 0], [-1, target_len])缺点
- 浪费内存和时间
转换方式2
方案:
1
2
3
4
5
6
7
8nums = tf.string_split(tf.reshape(nums, [-1]), delimiter=";")
# 确保每行大小不超过target_len,否则sparse_to_dense在稀疏长度大于目标长度会报错
# target_len可以大于稀疏长度的,第一个维度不能为-1,可用nums.dense_shape[0]
nums = tf.sparse_slice(nums, [0, 0], [nums.dense_shape[0], target_len])
# sparse_to_dense的batch_size是必要的,不能为-1,处理到最后一个batch时,`nums = tf.sparse_to_dense(nums.indices, [batch_size, target_len], nums.values, default_value='0.0')`会出现问题,因为最后一层真实的数据量不是batch_size
nums = tf.sparse_to_dense(nums.indices, [nums.dense_shape[0], target_len], nums.values, default_value='0.0')
nums = tf.string_to_number(nums, out_type=tf.float32)优点
- 简单快捷,节省内存
数据mask处理
- 应用场景:屏蔽部分数据,比如包含所有粗排队列及输入ctr长度信息,需要屏蔽掉没有进入CTR的数据
- 屏蔽方案:
1
2
3
4
5
6# 屏蔽被移除的广告对应的数据,max_len为最大长度,fix_len为截断长度,默认值为0
def mask_fix_len_nums(nums, max_len, real_len, name):
with tf.name_scope("mask_%s" % name):
mask = tf.sequence_mask(real_len, max_len)
mask_nums = tf.where(mask, nums, tf.zeros_like(nums))
return mask_nums
tf.layers.max_pooling1d
需要先将数据处理为rank=3才可以,如果原始数据为rank=1或rank=2,可通过reshape函数修改shape,然后pooling处理完后再恢复到原来的shape
举例
1
2
3
4
5pool_raw_bids = tf.reshape(raw_bids, [-1, 1, 100])
print pool_raw_bids.shape
bid_pool_result = tf.layers.max_pooling1d(pool_raw_bids, 10, strides=10, padding="valid", data_format="channels_first")
# 10个一组,pooling后长度为10
final_result = tf.reshape(bid_pool_result, [-1, 10])关于参数
valid
:https://vimsky.com/article/3881.html参数
data_format
:- “channels_first”: 指明channel维度在前数据维度前
- “channels_last”: 指明channel维度在前数据维度后
- 详情看函数源码文档即可
人工转换数值型特征为类别型
- 代码
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
36queue_len = tf.convert_to_tensor([81, 72, 63, 144, 33, 10, 209])
ori_len = tf.convert_to_tensor([84, 109, 80, 203, 68, 300, 245])
queue_len = tf.reshape(queue_len, [-1])
ori_len = tf.reshape(ori_len, [-1])
b = [4,10,19,33,52,78,114,164,240]
def bucket(x):
# 下面个的代码无法保证顺序,会出现编码混乱的局面,返回值全是[8,8,...]
# bucket_dict = {
# tf.less(x, b[0]): lambda: 0,
# tf.less(x, b[1]): lambda: 1,
# tf.less(x, b[2]): lambda: 2,
# tf.less(x, b[3]): lambda: 3,
# tf.less(x, b[4]): lambda: 4,
# tf.less(x, b[5]): lambda: 5,
# tf.less(x, b[6]): lambda: 6,
# tf.less(x, b[7]): lambda: 7,
# tf.less(x, b[8]): lambda: 8
# }
# 下面的代码也无法保证顺序
# bucket_dict = list()
# for i in range(len(b)):
# bucket_dict.append((tf.less(x, b[i]), lambda: b[i]))
bucket_dict = [
(tf.less(x, b[0]), lambda: 0),
(tf.less(x, b[1]), lambda: 1),
(tf.less(x, b[2]), lambda: 2),
(tf.less(x, b[3]), lambda: 3),
(tf.less(x, b[4]), lambda: 4),
(tf.less(x, b[5]), lambda: 5),
(tf.less(x, b[6]), lambda: 6),
(tf.less(x, b[7]), lambda: 7),
(tf.less(x, b[8]), lambda: 8)
]
return tf.case(bucket_dict, default=lambda:len(b), exclusive=False)
ori_len_bucket = tf.map_fn(bucket, elems=ori_len)
queue_len_bucket = tf.map_fn(lambda x: x/10, elems=queue_len)
慎用随机算子
使用随机算子后,每次调用都会重新初始化随机算子,获取到的值可能不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14data = tf.random_uniform([1], 0.0, 1.0)
# data = tf.constant([2], tf.float32)
data = tf.Print(data, [data])
pow_data_2 = tf.pow(data, [2])
data_3 = tf.multiply(pow_data_2, 3)
# print data
with tf.Session() as s:
s.run(tf.global_variables_initializer())
# print s.run([data])
print s.run([data])
print s.run([pow_data_2])
print s.run([data_3])
# 以上代码会答应3次tf.Print,3次print的结果不同,因为调用了两次初始化操作
# TensorFlow是惰性的,不执行s.run操作就不会走一遍算子进一步的
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
28data = tf.random_uniform([1], 0.0, 1.0)
data_list = [data, data, data]
# data = tf.constant([2], tf.float32)
data = tf.Print(data, [data])
pow_data_2 = tf.pow(data, 2)
data_3 = tf.multiply(pow_data_2, 3)
data_list = tf.Print(data_list, [data_list])
# print data
with tf.Session() as s:
s.run(tf.global_variables_initializer())
# print s.run([data])
print s.run([data])
print s.run([pow_data_2])
print s.run([data_3])
print s.run([data_list])
""" 输出如下, 上面四行为Print结果,后面的是run的结果
[0.974354625]
[0.14039886]
[0.219203591]
[[0.507081509][0.507081509][0.507081509]]
[array([0.9743546], dtype=float32)]
[array([0.01971184], dtype=float32)]
[array([0.14415064], dtype=float32)]
[array([[0.5070815],
[0.5070815],
[0.5070815]], dtype=float32)]
"""
# 由于data_list是一个对象,只会进行一次打印,同理,使用`s.run([data, pow_data_2])`时将打印出相同data对应的data和data^2
tf.stop_gradient
tf.AUTO_REUSE
- tf.layers.dense, 通过name复用
- 被复用时,以dense对应的整个层为单位,这个层的参数数量应该相同
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# encoding=utf-8
import tensorflow as tf
input_layer = tf.Variable([[1,2,3,4]], trainable=False, dtype=tf.float32)
input_layer10 = tf.Variable([[10,20,30,40]], trainable=False, dtype=tf.float32)
def get_net1():
kernel_initializer = tf.random_normal_initializer(mean=0.0, stddev=0.01)
net = tf.layers.dense(
inputs=input_layer[:, :1],
units=1,
kernel_initializer=kernel_initializer,
trainable=True,
# reuse=False, # 这个值可以随便设置,似乎不影响程序正确性?
name='m')
return net
def get_net2():
kernel_initializer = tf.random_normal_initializer(mean=0.0, stddev=0.01)
net = tf.layers.dense(
inputs=input_layer10[:, :1],
units=1,
kernel_initializer=kernel_initializer,
trainable=True,
# # 当外层有reuse=tf.AUTO_REUSE的variable_scope时,这个reuse为True或False,只要name相同,都会复用;
# # 否则,当没有外层没有reuse=tf.AUTO_REUSE的variable_scope时,则True代表复用,False代表不复用(不复用时name不能相同)
# reuse=True,
name='m')
return net
with tf.variable_scope('main_qnet', reuse=tf.AUTO_REUSE):
net1 = get_net1()
net2 = get_net2()
net_list = [net1, net2]
with tf.Session() as s:
s.run(tf.global_variables_initializer())
print s.run([net_list])
- 被复用时,以dense对应的整个层为单位,这个层的参数数量应该相同
minimize分解为compute_gradients和apply_gradients两步骤
tf.GraphKeys.UPDATE_OPS
tf.train.get_or_create_global_step()
- 创建或获取当前
Estimator.export_savedmodel()
- 重点参数:serving_input_receiver_fn
- 返回
tf.estimator.export.ServingInputReceiver
或tf.estimator.export.TensorServingInputReceiver
对象的函数 - 对象的构造包含了输入参数的信息,相当于在定义
model_fn
的features字段 - 该对象中的输入字段均是占位符的形式
- 返回
- 函数动作:
- 通过调用
serving_input_receiver_fn
生成相关输入tensors,并按照字典结构存储到一个对象,暂命名为features
中 - 将该
features
作为参数传入model_fn
并构造网络 - 从cpkt中恢复相关参数并将模型必要信息存储到指定目录下
- 说明:线上使用文件时,可直接加载网络进行推断,其中占位符可以被线上实时指定的向量替代
- 通过调用
- 需要关注的点:
- 如果使用estimator框架训练模型,则该函数存储时默认仅存储
model_fn
部分,在input_fn
部分进行的一些无参数的特征工程等操作是不会被继承下来的,也就是说,线上线下一致性保障仅仅从model_fn
处开始- 一种良好的编程习惯是:将线上特征处理操作在
model_fn
前完成,及tfrecord生成或input_fn
中完成,后续的操作都放到model_fn
中,从而保证线上线下的一致性
- 一种良好的编程习惯是:将线上特征处理操作在
- 如果使用estimator框架训练模型,则该函数存储时默认仅存储
一般TensorFlow模型存储为PB文件
variable_scope vs name_scope
- 测试代码1:
1
2
3
4
5
6
7
8
9
10
11
12
13def test():
data = tf.ones(shape=[3,5], dtype=tf.float32)
with tf.variable_scope("vs_test"):
x = tf.get_variable("x", initializer=[10])
y = tf.constant(20)
z = tf.layers.dense(inputs=data, units=1, name="output")
a = tf.Variable("a")
with tf.name_scope("ns_test"):
x1 = tf.get_variable("x", initializer=[10])
y1 = tf.constant(20)
z1 = tf.layers.dense(inputs=data, units=1, name="output")
a1 = tf.Variable("a")
test()
<tf.Variable ‘vs_test/x:0’ shape=(1,) dtype=int32_ref>
Tensor(“vs_test/Const:0”, shape=(), dtype=int32)
Tensor(“vs_test/output/BiasAdd:0”, shape=(3, 1), dtype=float32)
<tf.Variable ‘vs_test/Variable:0’ shape=() dtype=string_ref>
==========
<tf.Variable ‘x:0’ shape=(1,) dtype=int32_ref>
Tensor(“ns_test/Const:0”, shape=(), dtype=int32)
Tensor(“ns_test/output/BiasAdd:0”, shape=(3, 1), dtype=float32)
<tf.Variable ‘ns_test/Variable:0’ shape=() dtype=string_ref>
结论1:
- 对于
variable_scope()
来说,所有方式获取的变量或layer等调用都会被加上前缀variable_scope()
包含reuse
参数,对这个scope下的所有变量生效(包括通过layer
调用或get_variable
获取的变量)reuse = True
: 复用之前的同名变量,没有同名变量则抛出异常reuse = False
: 创建新变量,有同名变量则抛出异常reuse = tf.AUTO_REUSE
: 如果有同名变量,则复用之前的同名变量,否则创建新变量
- 对于
name_scope()
来说,通过tf.get_variable
和layer
获取到的变量不会被加上前缀,上面示例中打印出来的不是变量,而是网络输出值,可以被name_scope
来管理name_scope()
没有reuse
参数
- 对于
参考链接:https://blog.csdn.net/shenxiaoming77/article/details/79141078
name_scope: 为了更好地管理变量的命名空间而提出的。比如在 tensorboard 中,因为引入了 name_scope, 我们的 Graph 看起来才井然有序。
variable_scope: 大部分情况下,跟 tf.get_variable() 配合使用,实现变量共享的功能。测试代码2:
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
30def test():
data = tf.ones(shape=[3,5], dtype=tf.float32)
with tf.variable_scope("vs_test"):
x = tf.get_variable("x", initializer=[10])
y = tf.constant(20)
z = tf.layers.dense(inputs=data, units=1, name="output")
a = tf.Variable(1, name="a")
with tf.variable_scope("vs_test"):
x = tf.get_variable("y", initializer=[10])
# 下面这行会报错ValueError: Variable vs_test/output/kernel already exists
# z = tf.layers.dense(inputs=data, units=1, name="output")
a = tf.Variable(1, name="b")
with tf.name_scope("ns_test"):
x1 = tf.get_variable("x", initializer=[10])
y1 = tf.constant(20)
z1 = tf.layers.dense(inputs=data, units=1, name="output")
a1 = tf.Variable(1, name="a")
test()
print "=====trainable===="
trainable_var = tf.trainable_variables()
for v in trainable_var: print v
print "=====vs_test===="
main_qnet_var = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='vs_test')
for v in main_qnet_var: print v
print "=====ns_test===="
main_qnet_var = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='ns_test')
for v in main_qnet_var: print v
=====trainable====
<tf.Variable ‘vs_test/x:0’ shape=(1,) dtype=int32_ref>
<tf.Variable ‘vs_test/output/kernel:0’ shape=(5, 1) dtype=float32_ref>
<tf.Variable ‘vs_test/output/bias:0’ shape=(1,) dtype=float32_ref>
<tf.Variable ‘vs_test/a:0’ shape=() dtype=int32_ref>
<tf.Variable ‘vs_test/y:0’ shape=(1,) dtype=int32_ref>
<tf.Variable ‘vs_test_1/b:0’ shape=() dtype=int32_ref>
<tf.Variable ‘x:0’ shape=(1,) dtype=int32_ref>
<tf.Variable ‘output/kernel:0’ shape=(5, 1) dtype=float32_ref>
<tf.Variable ‘output/bias:0’ shape=(1,) dtype=float32_ref>
<tf.Variable ‘ns_test/a:0’ shape=() dtype=int32_ref>
=====vs_test====
<tf.Variable ‘vs_test/x:0’ shape=(1,) dtype=int32_ref>
<tf.Variable ‘vs_test/output/kernel:0’ shape=(5, 1) dtype=float32_ref>
<tf.Variable ‘vs_test/output/bias:0’ shape=(1,) dtype=float32_ref>
<tf.Variable ‘vs_test/a:0’ shape=() dtype=int32_ref>
<tf.Variable ‘vs_test/y:0’ shape=(1,) dtype=int32_ref>
<tf.Variable ‘vs_test_1/b:0’ shape=() dtype=int32_ref>
=====ns_test====
<tf.Variable ‘ns_test/a:0’ shape=() dtype=int32_ref>
- 结论2:
- 在多次定义
vs_test
后,tf.get_variable
获得的变量命名是vs_test
开头的tf.Variable
获得的变量命名是vs_test_1
开头的
- 在多次定义
变量的reuse和trainable
- reuse和trainable互不影响
- 两次resue变量时使用不同的trainable是不会影响变量复用的,只要name相同,符合reuse条件即可
- 变量的trainable属性(是否被加入
tf.GraphKeys.TRAINABLE_VARIABLES
)由其第一次被定义时决定,后续对该变量的复用(reuse=true
)将不会影响变量的trainable属性(不管后续变量申明时使用trainable=True
ortrainable=False
)
tf.layers.batch_normalization()方法
- 需要注意两个参数
training
: 是否处于train模式?默认为False- True表示会根据当前的batch滑动平均更新均值和方差参数
- False则表示不会更新该值,如果训练阶段设置为False,则滑动平均的均值和方差不会被更新
trainable
: 是否将参数加入GraphKeys.TRAINABLE_VARIABLES
中- 这里的参数不包括均值和方差(均值和方差由滑动平均根据Batch数据更新,不是训练参数)
- 参数指beta和gamma
- 只有参数被加入
GraphKeys.TRAINABLE_VARIABLES
中时才会被更新
- 总结,参数
training
和trainable
参数在训练阶段一般设置为True,其他阶段设置为False- 其中
training
负责滑动平均的均值和方差更新,设置为False,相当于均值和方差都不会自动更新
- 其中
tf.identity、tf.Print与tf.gradients
tf.Print
与tf.identity
类似,可以认为是增加打印其他变量的tf.identity
tf.Print
与tf.identity
操作可以认为是复制操作,梯度为1- 注意:
tf.Print
与tf.identity
都是图里面的一个操作,会分支出来一个节点,在求梯度时,必须保证目标自变量和因变量在同一个路径上(注意判断identity
节点是否在梯度路径上)
梯度裁剪相关函数
不可导梯度处理
- 在神经网络中,有许多包含不可导点的函数,常用的比如
- MAE损失函数在x=0(或pred和label相等)时不可导
- ReLU在x=0处不可导
- 在TensorFlow中,定义这些函数时都可以直接正常定义,在计算梯度tf.gradients时,会自动处理不可导点,处理逻辑如下:
- MAE中,x<0梯度为-1,x>0梯度为1,则x=0取两者之间的值均可,比如TensorFlow中默认取0
- ReLU类似的,在x<0时梯度为0,x>0时梯度为1,则x=0时取两者之间的值均可,比如TensorFlow中默认取0
- 注意,整数可以做MAE运算,但是不能求梯度,会报错
TypeError: Fetch argument None has invalid type <type 'NoneType'>
- 这一点上,所有运算都一样,整数不能求梯度
tf.squeeze
慎用
- 在预估服务中,往往是单个请求输入的,输入会导致
tf.squeeze
将该维度丢弃,从而产生一些意想不到的问题
tf.shape
和a.get_shape
tf.shape
: 返回一个Tensora.get_shape
: 返回一个元组- 对于batch等未知值返回
?
, 例如(?, 1)
- 对于batch等未知值返回
- 在某些特殊情况下,使用
tf.shape
作为reshape的shape参数,会导致输出shape为None,但是使用a.get_shape
则不会,建议任何地方均优先使用a.get_shape