美文网首页
Tensorflow的进阶操作

Tensorflow的进阶操作

作者: climb66的夏天 | 来源:发表于2020-09-30 00:00 被阅读0次

    下面学习一下Tensorflow张量的进阶操作,如张量的合并与分割、范数统计、张量填充、张量限幅等操作。

    1. 张量的合并与分割操作

    1.1 张量的合并

    合并是指将多个张量在某个维度上合并为一个张量。

    张量的合并可以使用拼接和堆叠操作实现,拼接操作并不会产生新的维度,仅在现有的维度上合并,而堆叠会创建新维度。选择使用拼接还是堆叠操作来合并张量,取决于具体的场景是否需要创建新维度。

    拼接 在Tensorflow中,可以通过 tf.concat(tensors, axis) 函数拼接张量,其中参数tensors保存了所有需要合并的张量 list,axis 参数指定需要合并的维度索引。

    下面看示例:

    a = tf.random.normal([4,35,8])
    b = tf.random.normal([6,35,8])
    c = tf.concat([a,b],axis=0)#拼接操作
    print(c.shape)
    (10, 35, 8)
    

    再看一例:

    a = tf.random.normal([10,35,4])
    b = tf.random.normal([10,35,4])
    c = tf.concat([a,b],axis=2)#拼接操作
    print(c.shape)
    (10, 35, 8)
    

    从语法上来说,拼接合并操作可以在任意的维度上进行,唯一的约束是非合并维度的长度必须一致。

    堆叠如果在合并数据时,希望创建一个新的维度,则需要使用 tf.stack(tensors, axis) 操作。
    tf.stack(tensors, axis) 中的tensors表示需要合并的张量,参数axis指定新维度插入的位置,axis的用法与tf.expand_dims一致,当axis>=0时,在axis之前插入;当axis<0时,在axis之后插入新维度。
    下面是使用示例:

    a = tf.random.normal([35,8])
    b = tf.random.normal([35,8])
    c = tf.stack([a,b],axis=0)# 堆叠操作
    print(c.shape)
    (2, 35, 8)
    

    tf.stack也需要满足张量堆叠合并条件,它需要所有待合并的张量shape完全一致才可合并。

    1.2 张量的分割

    合并操作的逆过程就是分割,将一个张量拆分为多个张量。
    tf.split(x, num_or_size_splits, axis)可以完成张量的分割操作,参数意义如下:

    • x 参数::待分割张量。
    • num_or_size_splits 参数:切割方案。当 num_or_size_splits 为单个数值时,如10,表示等长切割为10份;当 num_or_size_splits 为 list 时,list的每个元素表示每份的长度,如[2,4,2,2]表示切割为4份,每份的长度依次是2、4、2、2 。
    • axis 参数:指定分割的维度索引号。

    下面看示例:

    a = tf.random.normal([10,35,8])
    results = tf.split(a, num_or_size_splits=10, axis=0)#把张量拆分成10份
    print(len(results))
    10
    
    #查看切割后的某个张量的形状
    print(results[0].shape)#可以看到,切割后的张量仍然保留了切割的维度,这一点要注意
    (1, 35, 8)
    

    下面,示例一下不等长切割:

    a = tf.random.normal([10,35,8])
    results = tf.split(a, num_or_size_splits=[4,2,2,2], axis=0)#切割为不等长 4 份
    print(len(results))
    4
    
    #查看切割后的某个张量的形状
    print(results[0].shape)
    (4, 35, 8)
    

    特别地,如果希望在某个维度上全部按长度为 1 的方式切割,还可以使用 tf.unstack(x, axis)函数。这是tf.split的一种特殊情况,切割长度固定为1,只需要指定切割维度的索引号即可。
    下面看示例:

    a = tf.random.normal([10,35,8])
    results = tf.unstack(a,axis=0)#张量切割
    print(len(results))
    10
    
    #查看切割后张量的形状
    print(results[0].shape)#可以看到,这种切割方式去掉了切割维度
    (35, 8)
    

    2. 张量数据统计

    在神经网络的计算过程中,经常需要统计数据的各种属性,如最值、最值位置、均值、和。

    通过tf.reduce_max、tf.reduce_min、tf.reduce_mean、tf.reduce_sum 可以求解张量在某个维度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。

    下面看一些使用示例:

    x = tf.random.normal([4,10])
    
    out = tf.reduce_max(x, axis=1)#求样本的最大值
    print(out.shape)
    4
    
    out = tf.reduce_min(x, axis=1)#求样本的最小值
    print(out.shape)
    4
    
    out = tf.reduce_mean(x, axis=1)#求样本的均值
    print(out.shape)
    4
    

    当不指定 axis 参数时,tf.reduce_*函数会求解出全局元素的最大、最小、均值、和。

    x = tf.random.normal([4,10])
    
    tf.reduce_max(x)#求解样本全局的最大值
    tf.reduce_min(x)#求解样本全局的最小值
    tf.reduce_mean(x)#求解样本全局的均值
    

    在求解误差函数时,通过Tensorflow 的 MSE 误差函数可以求得每个样本的误差。若是需要计算样本的平均误差,此时可以通过 tf.reduce_mean 在样本数维度上计算均值。
    看下面示例:

    import tensorflow as tf
    from tensorflow import keras
    
    out = tf.random.normal([4,10])#网络预测输出
    y = tf.constant([1,2,2,0])#真实标签
    y = tf.one_hot(y,depth=10)#one-hot编码
    
    loss = keras.losses.mse(y,out)#计算每个样本的误差
    loss = tf.reduce_mean(loss)#计算平均误差
    print(float(loss))
    1.067490816116333
    

    除了希望获得张量的最值信息,还希望获得最值所在的索引号。
    通过 tf.argmax(x, axis),tf.argmin(x, axis) 可以求解在 axis 轴上,x的最大值、最小值所在的索引号。

    out = tf.random.normal([2,10])#网络输出值
    out = tf.nn.softmax(out,axis=1)#通过softmax转换为概率值
    
    pred = tf.argmax(out,axis=1)#求出最值所在的索引号
    print(pred)
    tf.Tensor([7 3], shape=(2,), dtype=int64)
    

    3.张量比较操作

    为了计算分类任务的准确率等指标,一般需要将预测结果和真实标签比较,统计比较结果中正确的数量来计算准确率。
    下面看一个示例:

    out = tf.random.normal([100,10])
    out = tf.nn.softmax(out,axis=1)#输出转换为概率
    pred = tf.argmax(out,axis=1)#选取预测值
    pred = tf.cast(pred,dtype=tf.int32)
    #print(pred)
    
    y = tf.random.uniform([100],maxval=10,dtype=tf.int32)#真实标签
    out = tf.equal(pred,y)#预测值与真实值比较
    out = tf.cast(out,dtype=tf.int32)
    #print(out)
    correct = tf.reduce_sum(out)#计算正确个数
    print(float(correct)/100)
    0.12
    

    可以看到,我们随机产生的预测数据的准确度是12%,这也是随机预测模型的正常水平。

    4.填充与复制操作

    填充操作可以通过 tf.pad(x, paddings) 函数实现,paddings 是包含了多个[left padding, right padding]的嵌套方案 list,如[[0,0],[2,1],[1,2]]表示第一个维度不填充,第二个维度左起(起始处)填充两个单元,右边(结束处)填充一个单元,第三个维度左边填充一个单元,右边填充两个单元。
    下面看使用示例:

    a = tf.constant([1,2,3,4,5,6])
    b = tf.constant([7,8,1,6])
    b = tf.pad(b, [[0,2]])#填充操作
    print(b)
    tf.Tensor([7 8 1 6 0 0], shape=(6,), dtype=int32)
    
    out = tf.stack([a,b],axis=0)#填充后,合并两个张量
    print(out)
    tf.Tensor(
    [[1 2 3 4 5 6]
     [7 8 1 6 0 0]], shape=(2, 6), dtype=int32)
    

    下面再看一例:

    total_words = 10000 #设定词汇量大小
    max_review_len = 80 #最大句子长度
    embedding_len = 100 #词向量长度
    
    #加载IMDB数据集
    (x_train,y_train),(x_test,y_test) = keras.datasets.imdb.load_data(num_words = total_words)
    #将句子截断或填充到相同长度,设置为末尾填充和末尾截断方式R
    x_train = keras.preprocessing.sequence.pad_sequences(x_train,maxlen=max_review_len,truncating='post',padding='post')
    x_test = keras.preprocessing.sequence.pad_sequences(x_test,maxlen=max_review_len,truncating='post',padding='post')
    print(x_train.shape,x_test.shape)
    (25000, 80) (25000, 80)
    

    上述代码中,我们将句子的最大长度 max_review_len 设置为80个单词,通过keras.preprocessing.sequence.pad_sequences 可以快速完成句子的填充和截断工作。

    下面我们介绍对多个维度进行填充的例子。

    x = tf.random.normal([4,28,28,3])
    print('before: ',x.shape)
    x = tf.pad(x,[[0,0],[2,2],[2,2],[0,2]])#对第二维,第三维进行填充
    print('after:',x.shape)
    before:  (4, 28, 28, 3)
    after: (4, 32, 32, 5)
    

    复制操作通过 tf.tile 函数可以对长度为1的维度进行复制若干份,也可以对任意长度的维度进行复制若干份。

    下面看使用示例:

    x = tf.random.normal([4,32,32,3])
    x = tf.tile(x,[2,3,3,1])#进行复制操作
    print(x.shape)
    (8, 96, 96, 3)
    

    5.数据限幅操作

    通过 tf.maximum(x,a) 实现数据的下限幅,通过 tf.minimum(x,a)实现数据的上限幅。
    使用示例如下:

    x = tf.range(9)
    print(x)
    x = tf.maximum(x,2)#下限幅2
    print(x)
    tf.Tensor([0 1 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
    tf.Tensor([2 2 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
    
    x = tf.range(9)
    print(x)
    x = tf.minimum(x,7)# 上限幅7
    print(x)
    tf.Tensor([0 1 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
    tf.Tensor([0 1 2 3 4 5 6 7 7], shape=(9,), dtype=int32)
    

    更方便地,我们可以使用 tf.clip_by_value 实现上下限幅。

    x = tf.range(9)
    x = tf.clip_by_value(x,2,7)#实现上下限幅 
    print(x)
    tf.Tensor([2 2 2 3 4 5 6 7 7], shape=(9,), dtype=int32)
    

    6. Tensorflow的高级操作

    6.1 tf.gather操作

    tf.gather 可以实现根据索引号收集数据的目的。考虑班级成绩册例子,共有4个班级,每个班级35个学生,8门科目,保存成绩册张量 shape 为 [4,35,8]。
    下面看使用示例:

    x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
    print(x.shape)
    results = tf.gather(x,[0,1],axis=0)#在班级维度收集第1-2号班级成绩册
    print(results.shape)
    (4, 35, 8)
    (2, 35, 8)
    
    #收集第1,4,9,12,13,27号同学的成绩
    results = tf.gather(x,[0,3,8,11,12,26],axis=1)
    print(results.shape)
    (4, 6, 8)
    
    #收集所有同学的第3、5等科目成绩
    results = tf.gather(x,[2,4],axis=2)
    print(results.shape)
    (4, 35, 2)
    

    通过上面的例子可以看到,tf.gather非常适合索引没有规则的场合,其中索引号可以乱序排列,此时收集的数据也是对应顺序。
    看如下例子:

    x = tf.range(8)
    x = tf.reshape(x,[4,2])
    print(x)
    results = tf.gather(x,[3,1,0,2],axis=0)#收集第4,2,1,3号元素
    print(results)
    tf.Tensor(
    [[0 1]
     [2 3]
     [4 5]
     [6 7]], shape=(4, 2), dtype=int32)
    tf.Tensor(
    [[6 7]
     [2 3]
     [0 1]
     [4 5]], shape=(4, 2), dtype=int32)
    

    下面看一个问题,如果希望抽查第[2,3]班级的第[3,4,6,27]号同学的科目成绩,则可以通过组合多个tf.gather实现。

    x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
    students = tf.gather(x,[1,2],axis=0)#收集第2,3号班级
    print(students.shape)
    results = tf.gather(students,[2,3,5,26],axis=1)#收集第3,4,6,27号同学
    print(results.shape)
    (2, 35, 8)
    (2, 4, 8)
    

    下面将问题再复杂一点,如果希望抽查第2个班级的第2个同学的所有科目,第3个班级的第3个同学的所有科目,第4个班级的第4个同学的所有科目。可以怎么实现呢?
    可以通过笨方式一个一个手动提取,然后合并数据。

    x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
    results = tf.stack([x[1,1],x[2,2],x[3,3]],axis=0)
    print(results.shape)
    (3, 8)
    

    虽然这种方法可以得到正确的结果,但是效率很低,下面来看一种更加高效的方法。

    6.2 tf.gather_nd 操作

    通过 tf.gather_nd,可以通过指定每次采样的坐标来实现多个采样的目的。
    在上面的那个问题中,我们可以将这个采样方案合并为一个list参数:[[1,1],[2,2],[3,3]],通过 tf.gather_nd 实现如下。

    x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
    print(x.shape)
    results = tf.gather_nd(x,[[1,1],[2,2],[3,3]])
    print(results.shape)
    (4, 35, 8)
    (3, 8)
    

    可以看到,这种实现方式更加简洁,计算效率大大提升。

    results = tf.gather_nd(x,[[1,1,2],[2,2,3],[3,3,4]])
    print(results.shape)
    (3,)
    

    上述代码中,我们抽出了班级1,学生1的科目2;班级2,学生2的科目3;班级3,学生3的科目4的成绩,共有3个成绩数据,结果汇总为一个shape为[3]的张量。

    6.3 tf.boolean_mask 操作

    除了可以通过给定索引号的方式采样,还可以通过给定掩码(mask)的方式采样。通过 tf.boolean_mask(x, mask, axis)可以在 axis 轴上根据 mask 方案进行采样。
    下面看使用示例:

    x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
    print(x.shape)
    
    results = tf.boolean_mask(x,[True,False,False,True],axis=0)#采样第一个和第四个数据
    print(results.shape)
    (4, 35, 8)
    (2, 35, 8)
    

    注意掩码的长度必须与对应维度的长度一致。

    现要我们来对比一下tf.gather_nd 和 tf.boolean_mask的用法。

    x = tf.random.uniform([2,3,8],maxval=100,dtype=tf.int32)
    print(x.shape)
    #采集第一个班级的第一和第二个学生的成绩。第二个班级的第二和第三个学生的成绩。
    results = tf.gather_nd(x,[[0,0],[0,1],[1,1],[1,2]])#多维座标采集
    print(results.shape)
    (2, 3, 8)
    (4, 8)
    
    #使用掩码来实现上述方案
    results = tf.boolean_mask(x,[[True,True,False],[False,True,True]])
    print(results.shape)
    (4, 8)
    

    可以看到,掩码采样的结果与tf.gather_nd的结果完全一样。可见,tf.boolean_mask既可以实现 tf.gather方式的一维掩码采样,又可以实现 tf.gather_nd 方式的多维掩码采样。

    6.4 tf.where 操作

    通过tf.where(cond,a,b)操作可以根据cond条件的真假从a 或 b中读取数据。
    下面看使用示例:

    a = tf.ones([3,3])# 构造 a 为全1
    b = tf.zeros([3,3])# 构造 b 为全0
    
    cond = tf.constant([[True,False,False],[False,True,False],[False,True,True]])
    results = tf.where(cond,a,b)#根据条件从 a,b 中采样
    print(results)
    tf.Tensor(
    [[1. 0. 0.]
     [0. 1. 0.]
     [0. 1. 1.]], shape=(3, 3), dtype=float32)
    

    这个例子可以看到,返回的张量中为 1 的位置来自张量a,返回的张量中为0的位置来自张量b。

    当a=b=None 即a,b参数不指定时,tf.where会返回 cond 张量中所有True的元素的索引座标。
    看如下示例:

    results = tf.where(cond)#返回cond 中True无素的索引座标
    print(results)
    tf.Tensor(
    [[0 0]
     [1 1]
     [2 1]
     [2 2]], shape=(4, 2), dtype=int64)
    

    那么这个操作有什么作用呢,考虑一个例子,我们需要提取张量中所有正数的数据和索引。

     = tf.random.normal([3,3])#构造 a
    mask = a > 0#通过比较运算,得到正数的掩码
    indices = tf.where(mask)#提取此掩码处True元素的索引座标
    results = tf.gather_nd(a,indices)#拿到索引后,即可提取出所有正数
    print(results)
    tf.Tensor([0.11021124 0.553751   1.3484484  1.2101223 ], shape=(4,), dtype=float32)
    
    results = tf.boolean_mask(a,mask)#通过掩码提取正数,和tf.gather_nd的结果是一致的
    print(results)
    tf.Tensor([0.11021124 0.553751   1.3484484  1.2101223 ], shape=(4,), dtype=float32)
    

    6.5 scatter_nd 操作

    通过 tf.scatter_nd(indices,updates,shapes)可以高效地刷新张量的部分数据,但是只能在全0张量的白板上面刷新。
    下面看使用示例:

    indices = tf.constant([[4],[3],[1],[7]])#构造需要刷新数据的位置
    updates = tf.constant([4.4,3.3,1.1,7.7])#构造需要写入的数据
    #在长度为8的全0向量上根据indices写入updates数据
    tf.scatter_nd(indices,updates,[8])
    <tf.Tensor: shape=(8,), dtype=float32, numpy=array([0. , 1.1, 0. , 3.3, 4.4, 0. , 0. , 7.7], dtype=float32)>
    

    下面考虑3维张量的刷新例子:

    indices = tf.constant([[1],[3]])#构造写入位置
    updates = tf.constant([[[5,5,5,5],[6,6,6,6],[7,7,7,7],[8,8,8,8]],
                          [[1,1,1,1],[2,2,2,2],[3,3,3,3],[4,4,4,4]]])#构造刷新数据
    
    #在shape为[4,4,4]的白板上根据indices写入updates数据
    results = tf.scatter_nd(indices,updates,[4,4,4])
    print(results)
    tf.Tensor(
    [[[0 0 0 0]
      [0 0 0 0]
      [0 0 0 0]
      [0 0 0 0]]
    
     [[5 5 5 5]
      [6 6 6 6]
      [7 7 7 7]
      [8 8 8 8]]
    
     [[0 0 0 0]
      [0 0 0 0]
      [0 0 0 0]
      [0 0 0 0]]
    
     [[1 1 1 1]
      [2 2 2 2]
      [3 3 3 3]
      [4 4 4 4]]], shape=(4, 4, 4), dtype=int32)
    

    根据打印数据可以看到,数据被刷新到第2,4个通道上。

    相关文章

      网友评论

          本文标题:Tensorflow的进阶操作

          本文链接:https://www.haomeiwen.com/subject/gcaduktx.html