Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Spark——SQL-Join详解


*参考链接: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
    5
    import tensorflow as tf

    with tf.variable_scope('var_scope'):
    var3 = tf.Variable(3.0, name='var3')
    print(var3.name) # 输出: var_scope/var3:0
  • name_scope :name_scope 同样会为 tf.Variable 创建的变量添加前缀:

    1
    2
    3
    4
    5
    import 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
    5
    import 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:0
  • name_scope :name_scope 不会影响 tf.get_variable 创建的变量的命名。tf.get_variable 创建的变量会忽略 name_scope,直接使用 variable_scope 或默认的命名空间:

    1
    2
    3
    4
    5
    import 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
    14
    import 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
    22
    def 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
    51
    def 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
    5
    import 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
    5
    import 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
    14
    import 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封装了很多有用的函数,本文主要介绍介绍其中常用的函数

  • 优秀参考链接:
    • https://www.cnblogs.com/ying-chease/p/9723309.html
    • https://www.cnblogs.com/wuzhitj/p/6298004.html

tf.identify()

  • 参考链接: https://blog.csdn.net/qq_23981335/article/details/81361748
  • y=x 图上是看不到y的具体节点的,因为这仅仅是个普通的Python变量引用复制,不是一个TF操作
    • 由于不是一个TF操作,所以执行y与对x取值操作一样,没有多余的操作
  • tf.identify()会复制一个变量,此时图中会增加一个操作

string与数字类型的转换

  • tf.as_string()
  • tf.string_to_number()

tf.pad()

  • 用于填充tensor
  • 函数定义如下:
    1
    2
    3
    4
    5
    6
    7
    tf.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
    16
    import 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.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
    29
    import 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
    14
    nums = 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
    8
    nums = 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
    5
    pool_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
    36
    queue_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
    14
    data = 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
    28
    data = 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.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])

minimize分解为compute_gradients和apply_gradients两步骤

  • 参考链接:以终为始:compute_gradients 和 apply_gradients

tf.GraphKeys.UPDATE_OPS

  • 参考链接:tensorflow中的batch_norm以及tf.control_dependencies和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中,从而保证线上线下的一致性

一般TensorFlow模型存储为PB文件

  • 参考链接:TensorFlow 保存模型为 PB 文件

变量的reuse和trainable

  • reuse和trainable互不影响
    • 两次resue变量时使用不同的trainable是不会影响变量复用的,只要name相同,符合reuse条件即可
    • 变量的trainable属性(是否被加入tf.GraphKeys.TRAINABLE_VARIABLES)由其第一次被定义时决定,后续对该变量的复用(reuse=true)将不会影响变量的trainable属性(不管后续变量申明时使用trainable=True or trainable=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节点是否在梯度路径上)

梯度裁剪相关函数

  • 参考链接:https://www.cnblogs.com/marsggbo/p/10055760.html

不可导梯度处理

  • 在神经网络中,有许多包含不可导点的函数,常用的比如
    • 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: 返回一个Tensor
  • a.get_shape: 返回一个元组
    • 对于batch等未知值返回?, 例如(?, 1)
  • 在某些特殊情况下,使用tf.shape作为reshape的shape参数,会导致输出shape为None,但是使用a.get_shape则不会,建议任何地方均优先使用a.get_shape

TensorFlow——变量初始化

  • 注:本文的设置仅针对 TensorFlow 1.x

变量的内存分配与初始值设定

  • 变量创建 :在 TensorFlow 1.x 中,当你定义一个变量(Variable)时,实际上是在图中创建了一个占位符(创建了一个表示该变量的节点),它仅仅定义了变量的形状和数据类型 ,并未为其分配实际的内存空间和设定初始值

    1
    2
    3
    import 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
    12
    import 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
    12
    import 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
    12
    import 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
    28
    import 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
    22
    import 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
    34
    import 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
      2
      val a: Double = 1.0
      calDouble(java.lang.Double.valueOf(a)) // calDouble的参数要求是Java的对象
    • List 对象转换

      1
      2
      val 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 版本导致,需要注意

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
    41
    import 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
    33
    import 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
    41
    import 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参数在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
    13
    import 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
    14
    import 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 相加,结果会被提升为 float32
    • float32 和 float64 相加,结果会被提升为 float64
  • 不兼容的类型引发错误 :如果两个类型的张量无法安全地转换,TensorFlow 会抛出 TypeError 或类似的错误:
    • string 和 int32 是完全不同的类型,无法直接相加
    • complex64 和 float32 可能需要显式转换
  • 布尔类型的操作(特殊) :布尔类型(bool)在 TensorFlow 中被视为数值类型(True 等价于 1,False 等价于 0),但需要使用显示类型转换
    1
    2
    3
    4
    5
    a = 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
    35
    import 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
    4
    with 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
    8
    g1 = 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
    2
    with 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
    28
    import 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中,所有变量都需要初始化(因为变量定义操作不会分配内存,只有在初始化后才会分配内存),重复初始化变量会将变量重置为初始化值

1…565758…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4