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()
- 用于填充tensor
- 函数定义如下:
1
2
3
4
5
6
7tf.pad(
tensor,
paddings,
mode='CONSTANT',
constant_values=0,
name=None
)
tf.slice()
tf.slice(x, [0, 2], [-1, 1])等价于x[:, 2:3]- 两者都会在graph中增加一个节点
- 需要注意,在graph里面两者的节点不一样,但是获得的结果是相同的
- 图中
x[:, 2:3]会多几个输出属性,所以猜测tf.slice更好些【不精确】
- 图中
tf.map_fn()
- 主行执行某个函数
tf.add_n()
函数说明(将多个
tf.Tensor对象相加并返回一个tf.Tensor对象):1
tf.add_n(inputs, name=None)
inputs:一个包含多个tf.Tensor的列表(或可迭代对象)name(可选):操作的名称- 返回:返回值是一个
tf.Tensor对象
示例代码执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import tensorflow as tf
# 创建多个张量
tensor1 = tf.constant([1, 2, 3])
tensor2 = tf.constant([4, 5, 6])
tensor3 = tf.constant([7, 8, 9])
# 使用 tf.add_n 对多个张量进行相加
result = tf.add_n([tensor1, tensor2, tensor3])
# 创建会话并运行计算
with tf.Session() as sess:
result_value = sess.run(result)
print("相加结果:", result_value)
# 相加结果: [12 15 18]- 注:在tf=1.x的版本中,必须使用
with tf.Session() as sess:加sess.run来执行图并抽取tensor的值,在tf=2.x的版本中则可以直接使用print("相加结果:", result.numpy())
- 注:在tf=1.x的版本中,必须使用
tf.add_n和tf.reduce_sum的区别
tf.reduce_sum的输入是一个张量,输出会降维(调用时若输入一个tensor的列表,也会默认地被TensorFlow隐式转换为一个tensor然后再输入,相当于隐式调用了tf.stack)tf.add_n输入是一个列表,输出和列表中的元素shape相同- 等价实现示例:
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
29import tensorflow as tf
# 创建多个张量
tensor1 = tf.constant([1, 2, 3])
tensor2 = tf.constant([4, 5, 6])
tensor3 = tf.constant([7, 8, 9])
# 使用 tf.add_n 对多个张量进行相加
result_add_n = tf.add_n([tensor1, tensor2, tensor3])
# 将多个张量堆叠成一个新的张量
stacked_tensors = tf.stack([tensor1, tensor2, tensor3])
# 使用 tf.reduce_sum 对堆叠后的张量在第 0 维上求和,达到与 tf.add_n 相同的效果
result_reduce_sum = tf.reduce_sum(stacked_tensors, axis=0)
## 不考虑可读性时,也可以使用下面的方法调用,tensorflow将隐式地将`[tensor1, tensor2, tensor3]`合并成一个tensor对象
# result_reduce_sum = tf.reduce_sum([tensor1, tensor2, tensor3], axis=0)
# 创建会话并运行计算
with tf.Session() as sess:
result_add_n_value = sess.run(result_add_n)
result_reduce_sum_value = sess.run(result_reduce_sum)
print("tf.add_n 的结果:", result_add_n_value)
print("tf.reduce_sum 的结果:", result_reduce_sum_value)
# tf.add_n 的结果: [12 15 18]
# tf.reduce_sum 的结果: [12 15 18]
字符串转固定二维数组
场景:每行包含一个字符串,每个字符串中包含多个值,按照’;’分隔开
转换方式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文件
变量的reuse和trainable
- reuse和trainable互不影响
- 两次resue变量时使用不同的trainable是不会影响变量复用的,只要name相同,符合reuse条件即可
- 变量的trainable属性(是否被加入
tf.GraphKeys.TRAINABLE_VARIABLES)由其第一次被定义时决定,后续对该变量的复用(reuse=true)将不会影响变量的trainable属性(不管后续变量申明时使用trainable=Trueortrainable=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.identitytf.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