TensorFlow——使用笔记

TensorFlow封装了很多有用的函数,本文主要介绍介绍其中常用的函数


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()


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
    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.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两步骤


tf.GraphKeys.UPDATE_OPS


tf.train.get_or_create_global_step()

  • 创建或获取当前

Estimator.export_savedmodel()

  • 重点参数:serving_input_receiver_fn
    • 返回tf.estimator.export.ServingInputReceivertf.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文件


variable_scope vs name_scope

  • 测试代码1:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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_variablelayer获取到的变量不会被加上前缀,上面示例中打印出来的不是变量,而是网络输出值,可以被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
    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和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中时才会被更新
  • 总结,参数trainingtrainable参数在训练阶段一般设置为True,其他阶段设置为False
    • 其中training负责滑动平均的均值和方差更新,设置为False,相当于均值和方差都不会自动更新

tf.identity、tf.Print与tf.gradients

  • tf.Printtf.identity类似,可以认为是增加打印其他变量的tf.identity
  • tf.Printtf.identity操作可以认为是复制操作,梯度为1
  • 注意:tf.Printtf.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.shapea.get_shape

  • tf.shape: 返回一个Tensor
  • a.get_shape: 返回一个元组
    • 对于batch等未知值返回?, 例如(?, 1)
  • 在某些特殊情况下,使用tf.shape作为reshape的shape参数,会导致输出shape为None,但是使用a.get_shape则不会,建议任何地方均优先使用a.get_shape