*参考链接:Spark调优 | 一文搞定 Join 优化
TensorFlow——variable_scope和name_scope
- 在 TensorFlow 1.x 中,
variable_scope和name_scope都是用于管理命名空间的工具,但它们的用途和行为有所不同,本文将详细介绍二者的区别
主要用途不同
variable_scope:主要用于管理变量的命名和共享,特别是在构建复杂的神经网络模型时,确保不同层或不同部分的变量可以正确命名和复用name_scope:主要用于组织图中的操作,使图的结构更加清晰,便于在 TensorBoard 中查看和分析
对 tf.Variable 的影响相同
variable_scope:variable_scope会为tf.Variable创建的变量添加前缀:1
2
3
4
5import tensorflow as tf
with tf.variable_scope('var_scope'):
var3 = tf.Variable(3.0, name='var3')
print(var3.name) # 输出: var_scope/var3:0name_scope:name_scope同样会为tf.Variable创建的变量添加前缀:1
2
3
4
5import tensorflow as tf
with tf.name_scope('name_scope'):
var4 = tf.Variable(4.0, name='var4')
print(var4.name) # 输出: name_scope/var4:0
对 tf.get_variable 的影响不同
variable_scope:variable_scope会影响tf.get_variable创建的变量的命名,并且支持变量共享。tf.get_variable创建的变量名称会带上variable_scope的前缀:1
2
3
4
5import tensorflow as tf
with tf.variable_scope('var_scope'):
var1 = tf.get_variable('var1', shape=[1], initializer=tf.constant_initializer(1.0))
print(var1.name) # 输出: var_scope/var1:0name_scope:name_scope不会影响tf.get_variable创建的变量的命名。tf.get_variable创建的变量会忽略name_scope,直接使用variable_scope或默认的命名空间:1
2
3
4
5import tensorflow as tf
with tf.name_scope('name_scope'):
var2 = tf.get_variable('var2', shape=[1], initializer=tf.constant_initializer(2.0))
print(var2.name) # 输出: var2:0
variable_scope变量共享功能
variable_scope:支持变量共享,通过设置reuse参数(如reuse=True或reuse=tf.AUTO_REUSE),可以在不同的作用域中复用相同名称的变量共享变量功能在构建具有共享参数的神经网络时非常有用,下面是构建神经网络的最佳实践:
1
2
3
4
5
6
7
8
9
10
11
12
13
14import tensorflow as tf
def my_network(inputs):
with tf.variable_scope('my_network', reuse=tf.AUTO_REUSE):
w = tf.get_variable('weights', shape=[1], initializer=tf.constant_initializer(3.0))
output = inputs * w
return output
input1 = tf.constant(1.0)
input2 = tf.constant(2.0)
output1 = my_network(input1)
output2 = my_network(input2)
# 这里 w 在两个调用中是共享的注:
name_scope:不支持变量共享 ,主要用于组织操作(如tf.add、tf.matmul等)的命名,方便在 TensorBoard 中可视化
附录:variable_scope vs name_scope更多代码示例
测试代码1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51def 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继承和覆盖
- 在多层级
tf.variable_scope中使用reuse参数时,reuse参数的状态在嵌套的variable_scope中会进行继承和覆盖- 继承 :子作用域会继承父作用域的
reuse状态, - 覆盖 :子作用域可以通过显式设置
reuse参数来覆盖继承的状态
- 继承 :子作用域会继承父作用域的
子作用域未显式设置 reuse 参数(继承)
当子作用域没有显式设置
reuse参数时,它会继承父作用域的reuse状态1
2
3
4
5import tensorflow as tf
with tf.variable_scope('outer_scope', reuse=True) as outer:
with tf.variable_scope('inner_scope') as inner:
print(inner.reuse) # 输出: True在上述代码中,
outer_scope的reuse设置为True,inner_scope未显式设置reuse参数,所以inner_scope继承了outer_scope的reuse状态,即True
子作用域显式设置 reuse 参数(覆盖)
若子作用域显式设置了
reuse参数,那么它会覆盖从父作用域继承的状态1
2
3
4
5import tensorflow as tf
with tf.variable_scope('outer_scope', reuse=True) as outer:
with tf.variable_scope('inner_scope', reuse=False) as inner:
print(inner.reuse) # 输出: False- 这里,
outer_scope的reuse为True,但inner_scope显式将reuse设置为False,所以inner_scope的reuse状态为False
- 这里,
reuse=tf.AUTO_REUSE(与True和False一致)
reuse=tf.AUTO_REUSE允许在变量存在时复用,不存在时创建。在多层级作用域中,它同样遵循继承和覆盖规则1
2
3
4
5
6
7
8
9
10
11
12
13
14import tensorflow as tf
def create_or_reuse_variable():
with tf.variable_scope('outer', reuse=tf.False):
with tf.variable_scope('inner', reuse=tf.AUTO_REUSE):
var = tf.get_variable('my_var', shape=[1], initializer=tf.constant_initializer(1.0))
return var
# 两次调用
var1 = create_or_reuse_variable()
var2 = create_or_reuse_variable()
print(var1.name) # 输出: outer/inner/my_var:0
print(var2.name) # 输出: outer/inner/my_var:0- 在这个例子中,两次调用
create_or_reuse_variable函数时,由于使用了reuse=tf.AUTO_REUSE,第二次调用会复用第一次创建的变量
- 在这个例子中,两次调用
TensorFlow——使用笔记
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
TensorFlow——变量初始化
- 注:本文的设置仅针对 TensorFlow 1.x
变量的内存分配与初始值设定
变量创建 :在 TensorFlow 1.x 中,当你定义一个变量(
Variable)时,实际上是在图中创建了一个占位符(创建了一个表示该变量的节点),它仅仅定义了变量的形状和数据类型 ,并未为其分配实际的内存空间和设定初始值1
2
3import tensorflow as tf
my_variable = tf.Variable(initial_value=3.0, dtype=tf.float32)- 这里的
my_variable只是一个图中的节点,它描述了变量的基本信息,但在内存中还没有真正存储值
- 这里的
变量初始化 :在会话中执行初始化操作时(注意,初始化操作需要和变量在同一个图中),TensorFlow 会为这个变量分配内存空间,用于存储其值(注意:如果有多个会话绑定同一个图,变量需要重新初始化)
- 在 TensorFlow 1.x 里,变量的状态是与会话(Session)相关联的。每个会话都有自己独立的变量副本和状态
- 多个 Session 绑定同一个 Graph 的特殊场景 :当在一个会话中初始化变量时,只会影响该会话中的变量状态,其他会话中的同一变量仍然处于未初始化状态,如果有多个会话绑定同一个图,为了能在每个会话中正常使用变量,需要分别对每个会话中的变量进行初始化操作
防止未定义行为 :如果不进行初始化就尝试使用变量,会导致未定义行为,因为变量在内存中的值是不确定的。这可能会引发错误或得到意外的计算结果。通过强制要求初始化变量,可以确保在使用变量之前,它们已经被正确地赋予了初始值,从而保证计算的正确性和稳定性
全局变量初始化
在大多数情形下,你需要对所有的变量进行初始化。可以借助
tf.global_variables_initializer()来实现这一目的。示例如下:1
2
3
4
5
6
7
8
9
10
11
12import tensorflow as tf
a = tf.Variable(3, name='a')
b = tf.Variable(2, name='b')
c = tf.add(a, b)
with tf.Session() as sess:
# 初始化所有全局变量
init = tf.global_variables_initializer()
sess.run(init)
result = sess.run(c)
print("结果: ", result)- 在这个示例中,
tf.global_variables_initializer()会初始化所有定义的全局变量。在运行计算图之前,必须先运行这个初始化操作
- 在这个示例中,
初始化部分变量
如果你只想初始化部分变量 ,可以使用
tf.variables_initializer()。示例如下:1
2
3
4
5
6
7
8
9
10
11
12import tensorflow as tf
a = tf.Variable(3, name='a')
b = tf.Variable(2, name='b')
c = tf.add(a, b)
with tf.Session() as sess:
# 初始化部分变量
init_ab = tf.variables_initializer([a, b])
sess.run(init_ab)
result = sess.run(c)
print("结果: ", result)- 在这个示例中,
tf.variables_initializer([a, b])仅初始化了变量a和b
- 在这个示例中,
初始化单个变量
你还可以使用
variable.initializer来初始化单个变量。示例如下:1
2
3
4
5
6
7
8
9
10
11
12import tensorflow as tf
a = tf.Variable(3, name='a')
b = tf.Variable(2, name='b')
c = tf.add(a, b)
with tf.Session() as sess:
# 初始化单个变量
sess.run(a.initializer)
sess.run(b.initializer)
result = sess.run(c)
print("结果: ", result)- 在这个示例中,分别对变量
a和b进行了初始化
- 在这个示例中,分别对变量
新加入的变量都要初始化
- 在已经创建session后,依然是可以加入新的变量的,但是需要进行初始化才可以使用
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
28import tensorflow as tf
var1 = tf.Variable(3, dtype=tf.float32)
var2 = tf.Variable(5, dtype=tf.float32)
add_op1 = tf.add(var1, var2)
sess = tf.Session()
init_op = tf.global_variables_initializer() # 初始化所有变量
sess.run(init_op)
result1 = sess.run(add_op1)
print("两个变量的和:", result1)
# 在Session后继续创建变量
var3 = tf.Variable(7, dtype=tf.float32)
add_op2 = tf.add(add_op1, var3)
init_new_var = tf.variables_initializer([var3]) # 初始化新创建的变量
sess.run(init_new_var)
result2 = sess.run(add_op2)
print("三个变量的和:", result2)
sess.close() # 显式关闭会话
# 两个变量的和: 8.0
# 三个变量的和: 15.0
附录:多次初始化变量会发生什么?
在 TensorFlow 1.x 中,对同一个变量进行两次初始化后,从内存占用的角度来说,变量所占用的内存位置通常是一致的,但变量存储的值会被重置为初始值
当你在 TensorFlow 1.x 里:
- 定义一个变量时,实际上是在图中创建了一个表示该变量的节点
- 在会话中执行初始化操作时,TensorFlow 会为这个变量分配内存空间,用于存储其值
- 同一个Session中,再次对该变量执行初始化操作,并不会重新分配内存空间,而是直接在已分配的内存位置上修改存储的值
- 同一个图中,内存分配操作只会在第一次初始化时发生
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import tensorflow as tf
var = tf.Variable(10, dtype=tf.float32)
init = tf.global_variables_initializer()
with tf.Session() as sess:
# 第一次初始化变量
sess.run(init)
print("第一次初始化后变量的值:", sess.run(var))
# 修改变量的值
assign_op = var.assign(20)
sess.run(assign_op)
print("修改变量后的值:", sess.run(var))
# 再次初始化变量
sess.run(init)
print("第二次初始化后变量的值:", sess.run(var))
# 第一次初始化后变量的值: 10.0
# 修改变量后的值: 20.0
# 第二次初始化后变量的值: 10.0- 定义变量和初始化操作 :定义了变量
var,并创建了初始化所有全局变量的操作init - 第一次初始化 :运行
init操作,此时 TensorFlow 为变量var分配内存空间,并将初始值 10 存储在该内存位置 - 修改变量的值 :使用
assign操作将变量var的值修改为 20,这会直接在已分配的内存位置上更新存储的值 - 第二次初始化 :再次运行
init操作,TensorFlow 不会重新分配内存空间,而是直接将内存中存储的值重置为初始值 10
- 定义变量和初始化操作 :定义了变量
附录:多个Session执行同一个图
核心注意点 :在 TensorFlow 1.x 中,当一个图被多个 Session 执行时,需要分别对每个 Session 中的变量进行初始化
在 TensorFlow 1.x 里,变量的状态是与会话(
Session)相关联的。每个会话都有自己独立的变量副本和状态。当你在一个会话中初始化变量时,只会影响该会话中的变量状态,其他会话中的同一变量仍然处于未初始化状态。因此,为了能在每个会话中正常使用变量,需要分别对每个会话中的变量进行初始化操作多个Session执行同一个图的示例如下:
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
34import tensorflow as tf
# 创建一个图
graph = tf.Graph()
with graph.as_default():
var = tf.Variable(10, dtype=tf.float32)
add_op = tf.add(var, 5)
init_op = tf.global_variables_initializer() # 注意:这个操作必须添加到图中,后续所有Session的初始化都可以调用这个操作
# 创建第一个会话
sess1 = tf.Session(graph=graph)
# 初始化第一个会话中的变量
sess1.run(init_op)
result1 = sess1.run(add_op)
print("第一个会话执行结果:", result1)
# 创建第二个会话
sess2 = tf.Session(graph=graph)
try:
result2 = sess2.run(add_op) # 尝试在未初始化变量的情况下执行操作
except tf.errors.FailedPreconditionError:
print("第二个会话未初始化变量,操作失败")
# 初始化第二个会话中的变量
sess2.run(init_op)
result2 = sess2.run(add_op) # 在第二个会话中执行操作
print("第二个会话初始化变量后执行结果:", result2)
# 关闭会话以释放资源
sess1.close()
sess2.close()
# 第一个会话执行结果: 15.0
# 第二个会话未初始化变量,操作失败
# 第二个会话初始化变量后执行结果: 15.0- 图的定义 :创建了一个图,并在图中定义了一个变量
var和一个操作add_op,同时定义初始化操作init_op - 第一个会话 :创建第一个会话
sess1,对其中的变量进行初始化(执行图中的操作init_op),然后执行操作并打印结果 - 第二个会话 :创建第二个会话
sess2,尝试在未初始化变量的情况下执行操作,会抛出FailedPreconditionError异常;捕获异常后,对第二个会话中的变量进行初始化(执行图中的操作init_op),再次执行操作并打印结果
- 图的定义 :创建了一个图,并在图中定义了一个变量
Scala——Scala对象转Java对象
Background
- 使用 Scala 语言调用 Java 的接口时,经常出现参数传递需要将 Scala 对象转换成 Java 对象的情况
转换方式
增加一行接口引用
1
import scala.collection.JavaConverters._
将对象转成Java对象
原生的一些对象转换
1
2val a: Double = 1.0
calDouble(java.lang.Double.valueOf(a)) // calDouble的参数要求是Java的对象List 对象转换
1
2val a: List[Double] = List(1.0D,2.0D,3.0D,4.0D)
calList(list.map(java.lang.Double.valueOf).asJava) // calDouble的参数要求是Java的对象
TensorFlow——LossNan问题
问题描述
- 训练时梯度出现 nan 或 -nan
- 问题详细描述:在训练时,出现异常:NanLossDuringTrainingError: NaN loss during training.
问题分析
- 进一步分析,batch_size=1 且 worker=1 时,发现首先是线上梯度出现 nan 或 -nan,然后下一轮跌待中所有数据都是 nan,进一步导致 loss 为 nan
- 在问题确认过程中,需要一步步排除:
- 排除分母除 0 的情况,可以简单的用
tf.div_no_nan代替\ - 排除 log 参数为负数或 0 的情况
- 排除抽取 batch 中选择符合条件的行后出现行数为 [] 的情况,使用
tf.shape(x)[0]是否为 0 来判断是否为空 - 重点:排除整数除法等导致的值为 0 的情况,这种情况比较隐晦,难以感知
- 有时候会是 TensorFlow 版本导致,需要注意
- 排除分母除 0 的情况,可以简单的用
TensorFlow——计算图管理
整体说明
- 在 TensorFlow 1.x 里,
placeholder函数用于创建占位符张量 - 定义计算图时 :定义计算图时指定输入数据的形状和类型
- 在会话中执行图时 :在运行计算图时需要为这些占位符张量提供具体数值
- 注意:这个数值不能是TensorFlow的Tensor类型,必须是numpy或者Python原生类型
- 注意:当实际运行会话(Session)并传入数据时,如果 placeholder 的类型与实际输入数据的类型不一致,TensorFlow 会抛出错误
placeholder函数的定义
函数定义:
1
tf.placeholder(dtype, shape=None, name=None)
dtype:必须指定,代表占位符张量的数据类型,像tf.float32、tf.int32等shape:可选参数,代表占位符张量的形状- 常见的设置为
shape=(None, 2),表示第一维为batch_size,维度可变 - 注:若设为
shape=None,就表示可以接受任意形状的输入
- 常见的设置为
name:可选参数,为占位符张量赋予名称,方便调试和可视化
placeholder函数的示例
- 示例代码如下:
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
41import tensorflow as tf
# 创建占位符
x = tf.placeholder(tf.float32, shape=[None, 2], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
# 定义计算图
w = tf.Variable(tf.random_normal([2, 1]), name='w')
b = tf.Variable(tf.zeros([1]), name='b')
pred = tf.matmul(x, w) + b
loss = tf.reduce_mean(tf.square(pred - y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train_op = optimizer.minimize(loss)
# 初始化变量
init = tf.global_variables_initializer()
# 模拟数据
import numpy as np
x_data = np.array([[1, 2], [3, 4], [5, 6]])
y_data = np.array([[3], [7], [11]])
# 运行计算图
with tf.Session() as sess:
sess.run(init)
for i in range(100):
_, loss_val = sess.run([train_op, loss], feed_dict={x: x_data, y: y_data})
if (i + 1) % 10 == 0:
print(f'Step {i + 1}, Loss: {loss_val}')
# Step 10, Loss: 0.23915673792362213
# Step 20, Loss: 0.2156977653503418
# Step 30, Loss: 0.19454030692577362
# Step 40, Loss: 0.17545770108699799
# Step 50, Loss: 0.15824727714061737
# Step 60, Loss: 0.1427248865365982
# Step 70, Loss: 0.1287250965833664
# Step 80, Loss: 0.1160985603928566
# Step 90, Loss: 0.10471046715974808
# Step 100, Loss: 0.09443938732147217
placeholder使用过程中需要注意的问题
- 数据类型匹配 :在使用
feed_dict为占位符提供数据时,要保证提供的数据类型和占位符定义的数据类型一致,不然会引发类型错误 - 形状匹配 :提供的数据形状必须和占位符定义的形状相匹配。要是占位符形状中有
None,那就表示该维度可以接受任意长度的输入 - 必须提供值 :在运行计算图时,所有依赖占位符的操作都得在
feed_dict里为占位符提供具体的值,否则会抛出异常- 计算目标不依赖的的不输入
- 性能问题 :频繁使用
feed_dict会带来一定的性能开销,尤其是在处理大规模数据时。可以考虑使用tf.data.Dataset来提升性能
附录:使用tf.data.Dataset提升性能
tf.data.Dataset提升性能的示例代码: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
33import tensorflow as tf
import numpy as np
x_data = np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float32)
y_data = np.array([[3], [7], [11]], dtype=np.float32)
dataset = tf.data.Dataset.from_tensor_slices((x_data, y_data)) # 创建 tf.data.Dataset
dataset = dataset.repeat().batch(3) # 对数据集进行重复和批量处理
iterator = dataset.make_one_shot_iterator()# 创建迭代器
next_x, next_y = iterator.get_next() # 获取下一个批次的数据
# 定义计算图
w = tf.Variable(tf.random_normal([2, 1]), name='w')
b = tf.Variable(tf.zeros([1]), name='b')
pred = tf.matmul(next_x, w) + b
# 定义损失函数
loss = tf.reduce_mean(tf.square(pred - next_y))
# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train_op = optimizer.minimize(loss)
# 初始化变量
init = tf.global_variables_initializer()
# 运行计算图
with tf.Session() as sess:
sess.run(init)
for i in range(100):
_, loss_val = sess.run([train_op, loss]) # 由于不使用placeholder,故而无需使用feed_back
if (i + 1) % 10 == 0:
print(f'Step {i + 1}, Loss: {loss_val}')同时使用
Dataset和placeholder的示例: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
41import tensorflow as tf
import numpy as np
train_x = np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float32)
train_y = np.array([[3], [7], [11]], dtype=np.float32)
# 创建训练数据集
train_dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
train_dataset = train_dataset.shuffle(buffer_size=len(train_x)).batch(3).repeat()
# 创建迭代器
train_iterator = train_dataset.make_one_shot_iterator()
next_train_x, next_train_y = train_iterator.get_next()
# 通过placeholder定义模型
x = tf.placeholder(tf.float32, shape=[None, 2])
w = tf.Variable(tf.random_normal([2, 1]), name='w')
b = tf.Variable(tf.zeros([1]), name='b')
pred = tf.matmul(x, w) + b
# 通过Dataset输出,重新定义训练损失函数(包含模型结构,即如何训练模型变量 w)
train_pred = tf.matmul(next_train_x, w) + b
train_loss = tf.reduce_mean(tf.square(train_pred - next_train_y))
# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train_op = optimizer.minimize(train_loss)
# 初始化变量
init = tf.global_variables_initializer()
# 训练模型
with tf.Session() as sess:
sess.run(init)
for i in range(100):
sess.run(train_op) # 由于train_op不依赖placeholder,无需使用feed_back
# 使用 feed_dict 进行推理
test_x = np.array([[7, 8]], dtype=np.float32)
prediction = sess.run(pred, feed_dict={x: test_x}) # pred依赖placeholder,故而需要使用feed_back
print("推理结果:", prediction)
TensorFlow——变量定义及复用
定量定义及复用
- 运行结果
- 结果分析
- 结论
trainable = None与trainable = True等价,也是trainable参数的默认值,会被添加到训练变量中- 只有当
trainable = False时不会被添加到训练变量中
- 只有当
reuse参数取值为True,None,tf.AUTO_REUSE,False(说明文档没明确列出False,但尝试可以用)True: 表示读取一个旧的同名变量,如没有则抛出异常None: 表示继承父scope的情况,若没有父scope,则默认为创建新变量,此时有同名变量则会抛出异常tf.AUTO_REUSE: 表示使用自动模式,没有同名变量,则创建一个,否则返回同名变量False特殊:- 经测试,当reuse参数在
variable_scope或者layer中使用时,False等价于None,会继承父scope的情况
- 经测试,当reuse参数在
- 注意,reuse参数在
variable_scope或者layer中使用时- 父:
tf.AUTO_REUSE,子None,False=> 最终tf.AUTO_REUSE - 父:
tf.AUTO_REUSE,子True=> 最终True - 父:
True,子None,False=> 最终True - 父:
True,子tf.AUTO_REUSE=> 最终tf.AUTO_REUSE - 父:
None,False, 子 xxx => 最终 xxx - 对于多层的级联架构中,依然满足上面的规律,举例来说,中途任意一层有一个
- 父:
reuse参数只在variable_scope或者layer中可以使用,tf.get_variable,name_scope,tf.Variable中都不可用tf.Variable一定会新建变量,而且会在重名时自动修改变量名称- 注意,
tf.Variable不等价于tf.get_variable中reuse=False的情况,后者在有同名变量时会报错
- 注意,
TensorFlow——计算图管理
整体总结
- 在 TensorFlow 1.x 里,类型转换是把张量从一种数据类型转换为另一种数据类型的操作,可分为隐式类型转换和显式类型转换
- 简单总结:
- TensorFlow 会自动进行类型提升以确保操作数兼容
- 如果类型不兼容,会抛出错误
- 使用
tf.cast()可以手动控制类型转换 - 布尔类型可以参与数值运算(变成
0或1),但建议做显示类型转换
显式类型转换(推荐使用)
若要明确地把一个张量从一种数据类型转换为另一种数据类型,你可以使用
tf.cast函数,下面是一个显式类型转换的示例:1
2
3
4
5
6
7
8
9
10
11
12
13import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 创建一个整数类型的张量
a = tf.constant([1, 2, 3], dtype=tf.int32)
# 显式地将其转换为浮点数类型
b = tf.cast(a, dtype=tf.float32)
with tf.Session() as sess:
result = sess.run(b)
print("Result:", result)
print("Data type:", b.dtype)- 在这个例子中,借助
tf.cast函数把a从int32类型显式转换为float32类型
- 在这个例子中,借助
注:在实际使用中,建议尽量采用显式类型转换,这样能让代码的意图更加清晰,也便于调试和维护
隐式类型转换(不建议)
在TensorFlow 1.x中,部分操作会自动进行隐式类型转换。不过,这种转换并非在所有情形下都会发生,并且不同类型之间的运算可能会引发错误。通常,当操作涉及不同数据类型的张量时,TensorFlow会尝试把它们转换为兼容的类型
下面是一个简单的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 创建两个不同类型的张量
a = tf.constant(2, dtype=tf.int32)
b = tf.constant(3.0, dtype=tf.float32)
# 进行加法操作,会发生隐式类型转换
c = a + b
with tf.Session() as sess:
result = sess.run(c)
print("Result:", result)
print("Data type:", c.dtype)- 在这个例子中,
a是int32类型,b是float32类型。在执行加法操作时,TensorFlow 会把a隐式转换为float32类型,然后再进行计算
- 在这个例子中,
隐式类型转换规则
- 在 TensorFlow 中,不同类型(
dtype)的张量或常量相加时,TensorFlow 会尝试自动进行类型转换(type casting),以便使两个操作数的类型兼容。如果类型无法安全地转换,就会抛出错误 - 自动类型提升 :当两个不同类型的张量或常量相加时,TensorFlow 会根据操作数的类型选择一个更“宽”的类型来存储结果。这种行为类似于 NumPy 的类型提升规则:
int32和float32相加,结果会被提升为float32float32和float64相加,结果会被提升为float64
- 不兼容的类型引发错误 :如果两个类型的张量无法安全地转换,TensorFlow 会抛出
TypeError或类似的错误:string和int32是完全不同的类型,无法直接相加complex64和float32可能需要显式转换
- 布尔类型的操作(特殊) :布尔类型(
bool)在 TensorFlow 中被视为数值类型(True等价于1,False等价于0),但需要使用显示类型转换1
2
3
4
5a = tf.constant(True, dtype=tf.bool) # bool 类型
b = tf.constant(2, dtype=tf.int32) # int32 类型
a = tf.cast(a, dtype=tf.int32) # 如果没有这一行,下面做加法时可能会报类型错误
result = a + b # 自动将 bool 提升为 int32
print(result) # 输出: tf.Tensor(3, shape=(), dtype=int32)
TensorFlow——计算图管理
一句话说明
- 在 TensorFlow 1.x 版本中,
tf.Graph()用于创建一个新的计算图,而会话(tf.Session())则用于执行计算图中的操作 - 本文将分别给出明确创建图(
tf.Graph())和不明确创建图创建会话的示例,并说明它们之间的区别
不明确创建图
当不指定图时,TensorFlow 会使用一个全局默认的计算图。以下是一个简单的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13# 在TensorFlow 2.x中可以通过下面的代码替换 `import tensorflow as tf`,仍可在TensorFlow 2.x环境中使用TensorFlow 1.x
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 定义操作,使用默认计算图
a = tf.constant(3.0)
b = tf.constant(4.0)
c = tf.add(a, b)
# 创建会话并执行操作
with tf.Session() as sess:
result = sess.run(c)
print("不使用 tf.Graph() 的结果:", result)在这个示例中,我们没有显式地创建计算图,TensorFlow 会使用默认的计算图来定义操作
a、b和c。然后创建一个会话并在该会话中运行操作c,最终得到计算结果
明确创建图(使用tf.Graph())
也可以使用
tf.Graph()函数,创建一个新的独立计算图,并在这个图中定义操作。以下是示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 在TensorFlow 2.x中可以通过下面的代码替换 `import tensorflow as tf`,仍可在TensorFlow 2.x环境中使用TensorFlow 1.x
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 创建一个新的计算图
graph = tf.Graph()
# as_default() 只在 with 上下文中生效,退出后默认图会恢复为原来的全局默认图
with graph.as_default():
# 在新的计算图中定义操作
a = tf.constant(5.0)
b = tf.constant(6.0)
c = tf.add(a, b)
# 创建会话并指定使用新的计算图
with tf.Session(graph=graph) as sess:
result = sess.run(c)
print("使用 tf.Graph() 的结果:", result)在这个示例中,我们做了以下操作:
- 首先使用
tf.Graph()创建了一个新的计算图graph - 然后使用
graph.as_default()上下文管理器,将这个新的计算图设置为默认图,并在其中定义操作a、b和c - 最后创建一个会话,并通过
graph=graph参数指定该会话使用我们创建的新计算图,在会话中运行操作c并得到结果
- 首先使用
多图管理(TensorFlow 1.x)
- TensorFlow 1.x中创建和管理多个计算图的示例:
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
35import tensorflow as tf
# 创建第一个计算图
graph1 = tf.Graph()
with graph1.as_default():
# 在图1中定义变量和操作
a = tf.constant(5, name='a')
b = tf.constant(3, name='b')
c = tf.add(a, b, name='sum')
# 在图1中初始化所有变量
init1 = tf.global_variables_initializer()
# 创建第二个计算图
graph2 = tf.Graph()
with graph2.as_default():
# 在图2中定义不同的变量和操作
x = tf.constant(10, name='x')
y = tf.constant(4, name='y')
z = tf.multiply(x, y, name='product')
# 在图2中初始化所有变量
init2 = tf.global_variables_initializer()
# 创建会话执行图1
with tf.Session(graph=graph1) as sess1:
sess1.run(init1)
result1 = sess1.run(c)
print("图1的结果:", result1) # 输出: 图1的结果: 8
# 创建会话执行图2
with tf.Session(graph=graph2) as sess2:
sess2.run(init2)
result2 = sess2.run(z)
print("图2的结果:", result2) # 输出: 图2的结果: 40
多图管理的必要性
- 隔离性 :不同的图完全隔离,变量和操作不会相互干扰。这在以下场景特别有用:
- 同时运行多个模型
- 比较不同模型结构
- 隔离训练和推理图
- 资源管理 :每个图有自己的资源集合,可以独立释放,避免内存泄漏
- 命名空间 :不同图中的操作可以有相同的名称而不会冲突
- 并行执行 :可以在不同线程中同时运行不同的图(每个线程有自己的会话)
- 调试 :当出现问题时,可以更容易地定位是哪个图出现了错误
多图管理的最佳实践
- 使用
with graph.as_default():上下文管理器来明确操作属于哪个图 - 会话(Session)和图(Graph)是一一对应的,需要为每个图创建独立的会话,明确关闭不再需要的会话以释放资源
- 一般情况可以不用明确创建多个图 ,仅维护一个默认的图即可,更好的做法是在单个图中使用不同的名称作用域(namescope)来组织操作
- 多线程运行时需要维护不同的图,确保图不会被同时访问(注:不同图的变量命名也可以相同,也就是可以使用相同的代码来定义)
- 在 TensorFlow 2.x 中,默认启用了即时执行(Eager Execution)模式,不再需要显式地创建计算图和会话
关于图的作用域示例
as_default()只在 with 上下文中生效,退出后默认图会恢复为原来的全局默认图:1
2
3
4with new_graph.as_default():
op1 = tf.constant(1) # 属于 new_graph
op2 = tf.constant(2) # 属于全局默认图(不是 new_graph)嵌套多个
as_default()的示例(不建议使用)1
2
3
4
5
6
7
8g1 = tf.Graph()
g2 = tf.Graph()
with g1.as_default():
a = tf.constant(1) # 属于 g1
with g2.as_default():
b = tf.constant(2) # 属于 g2通过 tf.get_default_graph() 可以检查当前默认图:
1
2with new_graph.as_default():
print(tf.get_default_graph() is new_graph) # 输出 True
新增变量及其初始化
在已经创建session后,依然是可以加入新的变量的,但是需要进行初始化才可以使用
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
28import tensorflow as tf
var1 = tf.Variable(3, dtype=tf.float32)
var2 = tf.Variable(5, dtype=tf.float32)
add_op1 = tf.add(var1, var2)
sess = tf.Session()
init_op = tf.global_variables_initializer() # 初始化所有变量
sess.run(init_op)
result1 = sess.run(add_op1)
print("两个变量的和:", result1)
# 在Session后继续创建变量
var3 = tf.Variable(7, dtype=tf.float32)
add_op2 = tf.add(add_op1, var3)
init_new_var = tf.variables_initializer([var3]) # 初始化新创建的变量
sess.run(init_new_var)
result2 = sess.run(add_op2)
print("三个变量的和:", result2)
sess.close() # 显式关闭会话
# 两个变量的和: 8.0
# 三个变量的和: 15.0注意:TensorFlow 1.x中,所有变量都需要初始化(因为变量定义操作不会分配内存,只有在初始化后才会分配内存),重复初始化变量会将变量重置为初始化值