深度学习框架TensorFlow之系列(三)张量相关操作

一 背景

水无常形,人无常势,事事如棋,一世如梦!

猛然发现,最近好久没对深度学习框架方面的专栏进行更新了,既然立了这个Flag,就得坚持下去。其实最近一直在看一些书,计算机技术类的书籍亦有、哲学类的书籍亦、小说类的书籍亦还有。最近迷上了看《遥远的救世主》,里面的每一篇章节、每一个文字都吸引着我,有些话可谓是字字珠玑,感叹于作者对于“释 道 儒 庙”的精深理解与表述。或者对于我这种天生不善言辞的人,文字的力量是巨大的吧。对书中的一些描述、一些人物的刻画,感触颇深,以至于今天座地铁没注意,一下子坐到反方向去了,没事,哥伦布告诉我们,地球是圆的,总归是可以去到要去的地方的,不同的只是到的时间和状态吧!

这里我先借书中的一首诗来开白(开始白话儿的意思,哈哈)吧,话说一篇好的文章,需要一个好的开始!


悟道休言天命,
修行勿取真经。
一悲一喜一枯荣,
哪个前生注定?
袈裟本无清净,
红尘不染性空。
幽幽古刹千年钟,
都是痴人说梦。

二 TensorFlow的数据载体

其实呢,前面的文章中已经介绍了TensorFlow的数据载体:张量,为何这章又要专门开一章节来写呢?答案当然不是为了凑章节,其实数据载体对于TensorFlow非常重要,可以说它是TensorFlow整体运转中最重要的一种数据结构。

张量广泛应用于物理学、数学和工程学中 。 在不同的应用领域,张量具有不同的学术定义 。这里援引维基百科的解释:张量是用来表示一些矢量、 标量和其他张量之间线性关系的多线性函数,这些线性关系的典型例子有内积、外积、线性映射以及笛卡儿积等 。 张量的抽象理论是线性代数的分支:多重线性代数。

在 TensorFlow 中 ,张量是数据流图上的数据载体 。为了更方便地定义数学表达式 、更准确地描述数学模型、更优雅的设计工程上通用的接口,进行了数据使用层面的统一封装。模型所对应的表达式中的数据由张量来承载。 TensorFlow 提供 Tensor 和 SparseTensor 两种张量抽象,分别表示稠密数据和稀疏数据 。 后者旨在减少高维稀疏数据的内存占用 。

1 Tensor

  • 函数定义
tf.Tensor(
    op, value_index, dtype
)
  • 张量的阶
阶数数据实体Python示例
0标量scalar = 1
1向量Vector = [1, 2, 3]
2矩阵Matrix = [[1,2,3],[4,5,6],[7,8,9]]
  • 张量的属性
rgs
opAn Operation. Operation that computes this tensor.
value_indexAn int. Index of the operation’s endpoint that produces this tensor.
dtypeA DType. Type of elements stored in this tensor.
Raises
TypeErrorIf the op is not an Operation.
Attributes
deviceThe name of the device on which this tensor will be produced, or None.
dtypeThe DType of elements in this tensor.
graphThe Graph that contains this tensor.
nameThe string name of this tensor.
opThe Operation that produces this tensor as an output.
shapeReturns the TensorShape that represents the shape of this tensor.The shape is computed using shape inference functions that are registered in the Op for each Operation. See tf.TensorShape for more details of what a shape represents.The inferred shape of a tensor is used to provide shape information without having to launch the graph in a session. This can be used for debugging, and providing early error messages. For example:c = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])print(c.shape)==> TensorShape([Dimension(2), Dimension(3)])d = tf.constant([[1.0, 0.0], [0.0, 1.0], [1.0, 0.0], [0.0, 1.0]])print(d.shape)==> TensorShape([Dimension(4), Dimension(2)])# Raises a ValueError, becausecandddo not have compatible# inner dimensions.e = tf.matmul(c, d)f = tf.matmul(c, d, transpose_a=True, transpose_b=True)print(f.shape)==> TensorShape([Dimension(3), Dimension(4)])In some cases, the inferred shape may have unknown dimensions. If the caller has additional information about the values of these dimensions, Tensor.set_shape() can be used to augment the inferred shape.
value_indexThe index of this tensor in the outputs of its Operation.
  • 代码真香
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

a=tf.constant([1.0,3.0], name="xxxxxxxxx")
#a=tf.constant([1.0,3.0])
b=tf.constant([2.0,4.0])
c=tf.add(a,b)
d=tf.add(c,a)
e=tf.add(d,b)

with tf.Session() as sess:
    print("************begin***************")
    print("a[0]=%s, a[1]=%s" % (a[0].eval(), a[1].eval()))
    print("a.name=%s" % (a.name))
    print("b.name=%s" % (b.name))
    print("a.op=\n[\n%s]" % (a.op))
    print("a.consumers=%s" % (a.consumers()))
    print("b.consumers=%s" % (b.consumers()))
    print("c.name=%s" % (c.name))
    print("c.shape=%s" % (c.shape))
    print("c.value=%s" % (c.eval()))
    print("c.op=\n[\n%s]" % (c.op))
    print("***************************")
    print("d.name=%s" % (d.name))
    print("d.op=\n[\n%s]" % (d.op))
    print("***************************")
    print("e.name=%s" % (e.name))
    print("e.op=\n[\n%s]" % (e.op))

输出结果如下:

************begin***************
a[0]=1.0, a[1]=3.0
a.name=xxxxxxxxx:0       # 显式设置张量a的名字
b.name=Const:0					 # Tensorflow设置的默认的名字
a.op=										 
[
name: "xxxxxxxxx"
op: "Const"
attr {
  key: "dtype"
  value {
    type: DT_FLOAT
  }
}
attr {
  key: "value"
  value {
    tensor {
      dtype: DT_FLOAT
      tensor_shape {
        dim {
          size: 2
        }
      }
      tensor_content: "\000\000\200?\000\000@@"
    }
  }
}
]
# 张量a的后续的操作
a.consumers=[<tf.Operation 'Add' type=Add>, <tf.Operation 'Add_1' type=Add>, <tf.Operation 'strided_slice' type=StridedSlice>, <tf.Operation 'strided_slice_1' type=StridedSlice>]  
# 张量b的后续的操作
b.consumers=[<tf.Operation 'Add' type=Add>, <tf.Operation 'Add_2' type=Add>]
c.name=Add:0
c.shape=(2,)
c.value=[3. 7.]
# 关键,OP里面的内部的东西都是tensorflow运行时态使用的,比如name,和外面的name不一样,外面是给开发者使用的
c.op=
[
name: "Add"
op: "Add"
input: "xxxxxxxxx"
input: "Const"
attr {
  key: "T"
  value {
    type: DT_FLOAT
  }
}
]
***************************
d.name=Add_1:0
d.op=
[
name: "Add_1"
op: "Add"
input: "Add"
input: "xxxxxxxxx"
attr {
  key: "T"
  value {
    type: DT_FLOAT
  }
}
]
***************************
e.name=Add_2:0
e.op=
[
name: "Add_2"
op: "Add"
input: "Add_1"
input: "Const"
attr {
  key: "T"
  value {
    type: DT_FLOAT
  }
}
]

2 SparseTensor

  • 函数定义
tf.sparse.SparseTensor(
    indices, values, dense_shape
)
  • 参数解析
Args
indicesA 2-D int64 tensor of shape [N, ndims].
valuesA 1-D tensor of any type and shape [N].
dense_shapeA 1-D int64 tensor of shape [ndims].
Attributes
dense_shapeA 1-D Tensor of int64 representing the shape of the dense tensor.
dtypeThe DType of elements in this tensor.
graphThe Graph that contains the index, value, and dense_shape tensors.
indicesThe indices of non-zero values in the represented dense tensor.
opThe Operation that produces values as an output.
shapeGet the TensorShape representing the shape of the dense tensor.
valuesThe non-zero values in the represented dense tensor.
  • 代码真香
import tensorflow.compat.v1 as tf

tf.disable_v2_behavior()

sp = tf.SparseTensor(indices=[[0,2],[1,3]], values=[1,2], dense_shape=[3,4])
with tf.Session() as tf:
    print('*********************');
    print(sp.eval())

输出结果如下:

*********************
SparseTensorValue(indices=array([[0, 2],
       [1, 3]]), values=array([1, 2], dtype=int32), dense_shape=array([3, 4]))

三 Tensor与Embedding表

在深度学习领域,有一个非常重要和有趣的话题就是Embedding,有句话叫做“万物皆可embedding”,充分说明了这个兄台的实力,并且围绕着这个兄台,衍生出很多有兴趣的研究与方向:

  • 工程领域:基于搜广推等场景ID特征的爆棚,造成开源的TensorFlow的Variable爆炸,基于不良设计或者不良使用(请允许我这么说,反正训不动、存不下),给广大的框架开发者广袤的发展舞台,各展神通。
  • 算法领域:基于Embedding的表征与表达,对于特征进行向量化表征,对特征进行衍生变换,并且在这个变身的过程中,神通保留(这点比孙悟空要厉害,不会变成shit被吃掉),极大的增强了模型的能力。

关于模型中的Embedding,这里多说几句,基本有两种模式:

  • 固定Embedding:一种是使用预训练好的词向量,这时一般不会修改词向量的值,可以理解是只读的,并不参与训练过程,属于不可训的常量。
  • 可训Embedding:一种是随着模型一起训练,需要随机初始化,这种情况下Embedding是一个variable,会随着模型的训练而改变,就跟全连接层的权重一样,最终会变成适用于训练集的词向量。

下面着重介绍下与Embedding相关的两个Tf的API;

1 tf.nn.embedding_lookup

1.1 算子介绍

从一个大的embedding表中,获取若干个embedding向量。

  • 算子定义
tf.nn.embedding_lookup(
    params, ids, 
  	partition_strategy='mod', 
  	name=None, 
  	validate_indices=True,
    max_norm=None
)
  • 参数解析
Args
paramsA single tensor representing the complete embedding tensor, or a list of P tensors all of same shape except for the first dimension, representing sharded embedding tensors. Alternatively, a PartitionedVariable, created by partitioning along dimension 0. Each element must be appropriately sized for the given partition_strategy.params是由一个tensor或者多个tensor组成的列表(多个tensor组成时,每个tensor除了第一个维度其他维度需相等)
idsA Tensor with type int32 or int64 containing the ids to be looked up in params.ids是一个整型的tensor,ids的每个元素代表要在params中取的每个元素的第0维的逻辑index.
partition_strategyA string specifying the partitioning strategy, relevant if len(params) > 1. Currently "div" and "mod" are supported. Default is "mod".逻辑index是由partition_strategy指定,partition_strategy用来设定ids的切分方式,目前有两种切分方式’div’和’mod’.
nameA name for the operation (optional).

1.2 算子应用

1.2.1 代码真香
  • 代码真香
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

params = tf.constant([[0.1, 0.4, 0.7, 1.0, 1.3, 1.6, 1.9, 2.2, 2.5, 2.8],
                      [0.2, 0.5, 0.8, 1.1, 1.4, 1.7, 2.0, 2.3, 2.6, 2.9],
                      [0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3.0]])
ids = tf.constant([0, 1, 0, 1])

with tf.Session() as sess:
    lookup = sess.run(tf.nn.embedding_lookup(params, ids))

print('*****************************************************************')
print(lookup)
1.2.2 计算机制
  • 计算逻辑:在params表中按照ids查询向量。params是一个词表,大小为M * N,M为emb的数量,N为emb向量维度的大小。params大小为3*10,表示有3个emb,每个emb有10维。ids里面有4个索引,对应的id为0,2,0,1,按照ids在params中查找。

  • 代码运行结果

    *****************************************************************
    [[0.1 0.4 0.7 1.  1.3 1.6 1.9 2.2 2.5 2.8]
     [0.2 0.5 0.8 1.1 1.4 1.7 2.  2.3 2.6 2.9]
     [0.1 0.4 0.7 1.  1.3 1.6 1.9 2.2 2.5 2.8]
     [0.2 0.5 0.8 1.1 1.4 1.7 2.  2.3 2.6 2.9]]
     
     对比params,就是按照ids提取去index是0 2 0 1的emb进行重新组合
    

2 tf.nn.embedding_lookup_sparse

从一个大的embedding表中,获取若干个embedding向量,并且会进行合并,而且索引对象是张量,并且有配套的权重因子。

2.1 算子介绍

  • 算子定义
tf.nn.embedding_lookup_sparse(
    params, 
  	sp_ids, 
  	sp_weights, 
  	partition_strategy='mod', 					# 查询的分区方式,尽量和存储的分区方式一致
  	name=None, 
  	combiner=None,											# 默认是sqrtn
    max_norm=None	
)
  • 参数解析
Args
paramsA single tensor representing the complete embedding tensor, or a list of P tensors all of same shape except for the first dimension, representing sharded embedding tensors. Alternatively, a PartitionedVariable, created by partitioning along dimension 0. Each element must be appropriately sized for the given partition_strategy.params是由一个tensor或者多个tensor组成的列表(多个tensor组成时,每个tensor除了第一个维度其他维度需相等)
sp_idsN x M SparseTensor of int64 ids where N is typically batch size and M is arbitrary.N x M ’ sparse张量’的int64 id,其中N是典型的批处理大小,M是任意的。稀疏张量进行表达 ,详见后面的代码示例。
sp_weightseither a SparseTensor of float / double weights, or None to indicate all weights should be taken to be 1. If specified, sp_weights must have exactly the same shape and indices as sp_ids.要么是float / double权值的’ sparse张量’,要么是’ None ‘,表示所有权值都应为1。如果指定了,’ sp_weights ‘必须具有与’ sp_ids '完全相同的形状和索引。
combinerA string specifying the reduction op. Currently “mean”, “sqrtn” and “sum” are supported. “sum” computes the weighted sum of the embedding results for each row. “mean” is the weighted sum divided by the total weight. “sqrtn” is the weighted sum divided by the square root of the sum of the squares of the weights.指定缩减操作的字符串。目前支持"mean", “sqrtn"和"sum”。“sum”计算每行嵌入结果的加权和。“平均值”是加权和除以总权重。“sqrtn”是加权和除以权重平方和的平方根。

2.2 算子应用

2.2.1 代码真香
  • 代码真香
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

params = tf.constant([[0.1, 0.4, 0.7, 1.0, 1.3, 1.6, 1.9, 2.2, 2.5, 2.8],
                      [0.2, 0.5, 0.8, 1.1, 1.4, 1.7, 2.0, 2.3, 2.6, 2.9],
                      [0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3.0]])

ids = tf.SparseTensor(indices=[[0, 1],
                               [0, 3],
                               [1, 2],
                               [1, 3]],
                      values=[2, 1, 1, 1],
                      dense_shape=[2, 4])

with tf.Session() as sess:
    lookup = sess.run(
        tf.nn.embedding_lookup_sparse(params, ids, None,
                                      partition_strategy="mod"))
print('*********************************************************************')
print(lookup)
2.2.2 计算机制
  • 首先,由于embedding_lookup_sparse相对于embedding_lookup来说,要相对复杂,所以我们一步一步的进行分析,先来看看这个稀疏的张量 ids,转换成稠密的矩阵,是一个2 * 4的矩阵。(SparseTensor类型的ids作为选择器传入embedding_lookup_sparse是以values为准的,而非是否为0,values中的值当然可以为0且是有意义的代表params的index是0的行,也就是第一行),
[
	[none, 2, none, 1]					# 注意none和0的区分,0是有值的,代表params的第一行
	[none, none, 1, 1]
]
  • 然后,我们再分析下参数weights,这次我们先不设置值(使用默认值),也是一个 2 * 4的矩阵。
[
	[0, 2, 0, 1]
	[0, 0, 1, 1]
]
  • 然后,我们分析下combiner这个参数,这个参数的操作对象是weights,并且它包含了两层意思。

    • 一个是embedding的合并对象,但是需要哪些embedding进行合并,是所以的都合并吗?当然不是他是基于ids的表述,将ids里面一行中不是none进行合并。

    • 一个是embedding的合并方式,默认为求sqrtn,将待合并的embedding进行合并。

      ∑ 0 n p a r a m s [ i d n : i d s ] ∗ w e i g h t n ∑ 0 n w e i g h t n 2 \frac{\sum_0^n params[id_n: ids] * weight_n}{\sum_0^n \sqrt{weight_n^2}} 0nweightn2 0nparams[idn:ids]weightn

  • 最后,我们推演下整个计算过程

    • 首先,ds的第一行是 [none, 2, none, 1],针对对应的不是none的index甄选出第3行和第2行:
    [
    	[0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4, 2.7, 3.0]
    	[0.2, 0.5, 0.8, 1.1, 1.4, 1.7, 2.0, 2.3, 2.6, 2.9]
    ]
    
    • 然后,根据上面的权值参数weights,对上面的矩阵的不同的向量进行数乘,操作如下:
    [
    	[0.3 * 1, 0.6 * 1, 0.9 * 1, 1.2 * 1, 1.5 * 1, 1.8 * 1, 2.1 * 1, 2.4 * 1, 2.7 * 1, 3.0 * 1]
    	[0.2 * 1, 0.5 * 1, 0.8 * 1, 1.1 * 1, 1.4 * 1, 1.7 * 1, 2.0 * 1, 2.3 * 1, 2.6 * 1, 2.9 * 1]
    ]
    
    • 然后,进行第一步合并,将第一行计算出来的上面的加权后的embedding进行矩阵相加操作
    [
    	[0.5, 1.1, 1.7, 2.3, 2.9, 3.5, 4.1, 4.7, 5.3, 5.9]
    ]
    
    • 然后,进行第二部合并,计算权重分母

      1 2 + 1 2 \sqrt1^2 + \sqrt1^2 1 2+1 2

    • 然后,进行最后的计算,用上面的embedding除以权重分母

    [
    	[0.25, 0.55, 0.85, 1.15, 1.45, 1,75, 2.05, 2.35, 2.65, 2.95]
    ]
    
    • 最后,ids的第一行 [none, 2, none, 1]计算完成,最后生成一个向量(embedding),接着同样的方法生成第二行的,那么最终的结果如下
    *********************************************************************
    [[0.25      0.55      0.85      1.15      1.45      1.75      2.05
      2.35      2.65      2.95     ]
     [0.2       0.5       0.8       1.1       1.4       1.7       2.
      2.3       2.6       2.9      ]]
    

四 其他

注:以上分析均是基于TensorFlow TensorFlow Core v1.15.0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值