tensorflow和numpy库中tensordot详解


今天在看注意力机制代码的时候,看到了tensordot这个函数,查了一下相关的资料,很少有中文文献写如何计算两个张量dot之后的结果,下面记录一下自己学习到的东西。

函数的定义:

tf.tensordot(
    a,
    b,
    axes,
    name=None
)
‘’‘
其中a和b是32位或者64位的tensor张量

axes:可以取值为整数N,代表的是a的倒数N个维度和b的正数N个维度相乘求和。
也可以采用元组或者列表的形式,例如axes=(1,1)或者axes=[1,1]
函数输出的结果的是两个张量按照上述规则形成的n维数组

下面通过举例来说明如何计算,注意为了方便期间,我将使用np.tensordot()函数进行说明,和tf.tensordot()是一样的。

axes =0

一维变量的计算

如图所示:一维变量只有1个维度。
在这里插入图片描述

x=np.array([1,2,3])
y=np.array([4,5,6])
z1=np.tensordot(x,y,axes=0)
print(z1)
// 输出结果为:
[[ 4  5  6]
 [ 8 10 12]
 [12 15 18]]

这个结果是如何得到的呢?我们看一下官方文档的解释:
翻译下来的意思就是如果给定的是axes =N,首先计算a元素的倒数第N个轴和b元素的第0个轴,依次计算直到a的倒数第1个轴和b的N个轴。
听起来非常的绕口,不妨我们自己尝试一下看看是怎么回事。

integer_like If an int N, sum over the last N axes of a and the first N axes of b in order.
The sizes of the corresponding axes must match.
When axes is integer_like, the sequence for evaluation will be: first the -Nth axis in a and 0th axis in b, and the -1th axis in a and Nth axis in b last

对于1维向量来说:倒数第0个维度代表的是数组中的每一个元素,它是一个标量,而y的第0个维度代表的是就是该行向量,计算过程如下所示:
在这里插入图片描述

二维变量的计算

假如x和y是1个二维向量呢?其中axes=0:代表的沿行方向,axes=1代表的是沿列方向。自己的理解是二维数组是由1维数组构成的,通过axes=0:可以选定是那个一维数组,相当于是最外层的门票,而axes=1,是内层的门票,选的了是那个一维数组之后就可以选定某个特定的元素了。
在这里插入图片描述

x的倒数第0个轴代表的元素依旧是1个标量x(i,j),而y的第0个轴:其实是沿着行方向的。

x=np.array([[1,2,3],[4,5,6]])
y=np.array([[4,5,6],[7,8,9]])
z3=np.tensordot(x,y,axes=0)
print(z3)
print(z3.shape) (2,3,2,3)
// 输出结果为:
[[[[ 4  5  6]
   [ 7  8  9]]

  [[ 8 10 12]
   [14 16 18]]

  [[12 15 18]
   [21 24 27]]]


 [[[16 20 24]
   [28 32 36]]

  [[20 25 30]
   [35 40 45]]

  [[24 30 36]
   [42 48 54]]]]

计算过程如下所示:
在这里插入图片描述

axes=1

一维向量的计算

x=np.array([1,2,3])
y=np.array([4,5,6])
z3=np.tensordot(x,y,axes=1)
print(z3)
//z3=32

计算的方法即为:x的倒数第1个轴和y的第0个轴进行计算。
由于:x本身是一维向量,故倒数第一个轴就是向量本身,而y的第0个也是向量本身,故结果为 1 ∗ 4 + 2 ∗ 5 + 3 ∗ 6 = 4 + 10 + 18 = 32 1*4+2*5+3*6=4+10+18=32 14+25+36=4+10+18=32

二维向量的计算

x是一个二维变量:故它的倒数第1个轴就是沿着列方向的轴,对应的向量分别为 ( 1 , 2 , 3 ) , ( 4 , 5 , 6 ) (1,2,3),(4,5,6) (1,2,3),(4,5,6),而y的第0个轴就是沿着行方向的轴,对应的向量分别为 ( 4 , 7 ) , ( 5 , 8 ) , ( 6 , 9 ) (4,7),(5, 8),(6,9) (4,7),(5,8),(6,9)。由于两个向量的长度一个是3,一个2,无法进行对应元素相乘再sum的操作,故运行以下程序会报错。

x=np.array([[1,2,3],[4,5,6]])
print(x.shape)
y=np.array([[4,5,6],[7,8,9]])
print(y.shape)
z3=np.tensordot(x,y,axes=1)
print(z3.shape)
//ValueError: shape-mismatch for sum

那么要如何修改呢,我们发现只要把y进行转置以下即可,此时y会变成 [ [ 4 , 7 ] , [ 5 , 8 ] , [ 6 , 9 ] ] [[4,7],[5,8],[6,9]] [[4,7],[5,8],[6,9]],故对应的第0个维度的向量分别为 [ 4 , 5 , 6 ] , [ 7 , 8 , 9 ] [4,5,6],[7,8,9] [4,5,6],[7,8,9]

x=np.array([[1,2,3],[4,5,6]])
print(x.shape)
y=np.array([[4,5,6],[7,8,9]])
print(y.shape)
z3=np.tensordot(x,y.transpose(),axes=1)
print(z3)
//z3=[[ 32  50]
 [ 77 122]]

在这里插入图片描述
计算方式类似于矩阵的计算方法:对应元素相乘再相加
1 ∗ 4 + 2 ∗ 5 + 3 ∗ 6 = 4 + 10 + 18 = 32 1*4+2*5+3*6=4+10+18=32 14+25+36=4+10+18=32 1 ∗ 7 + 2 ∗ 8 + 3 ∗ 9 = 7 + 16 + 27 = 50 1*7+2*8+3*9=7+16+27=50 17+28+39=7+16+27=50

三维向量的计算

假设给定的x是二维向量,y是三维向量,那么就是x的最后一个维度和y的第一个维度进行计算。如下图所示,x的最后一个维度 ( A x e s = 1 ) (Axes =1) (Axes=1)所对应的向量一个2维向量,而y的第一个维度( A x e s = 0 Axes=0 Axes=0)对应的向量也是一个二维向量。所以可以进行内积运算。

x=np.array([[4,7],[5,8],[6,9]])  //(3,2)
y=np.array([[[1,2,3],[4,5,6]],[[4,5,6],[7,8,9]]]) //(2,2,3)
z3=np.tensordot(x,y,axes=1) 
print(z3.shape) // (3,2,3)
print(z3)
//
[[[ 32  43  54]
  [ 65  76  87]]

 [[ 37  50  63]
  [ 76  89 102]]

 [[ 42  57  72]
  [ 87 102 117]]]

在这里插入图片描述
计算过程如下:以 ( 4 , 7 ) (4,7) (47)举例,后面的类似
在这里插入图片描述

axes=2

二维向量的计算

因为一维向量只有1个维度,因此使用axes=2就会报错。

x=np.array([1,2,3])
y=np.array([4,5,6])
z1=np.tensordot(x,y,axes=2)
print(z1)
//tuple index out of range 

当x和y都是2维向量的时候,axes=2:代表x的最后两个维度的元素和y的前两个维度元素进行计算。
通俗来说:就是将x和y沿着行展平为1维向量,之后再进行内积运算即可。

x=np.array([[1,2,3],[4,5,6]])
print(x.shape)
y=np.array([[4,5,6],[7,8,9]])
print(y.shape)
z3=np.tensordot(x,y,axes=2)
print(z3)
// z3=154

以上的计算方法等价于如下图所示:展平之后对应元素相乘再相加,代码如下所示

在这里插入图片描述

x_1 = x.flatten() //x_1=[1,2,3,4,5,6]
y_1 = y.flatten() //y_1 =[4,5,6,7,8,9]
print(sum(x_1 *y_1)) //两个对应的元素相乘再相加。

三维向量的计算

二维向量展平之后:变成(4,7,5,8,6,9)。
三维向量y展平之前的shape为(2,2,3),沿axes=0和axes=1展平之后,会变成一个(4,3)的二维数组。等价于y=y.reshape(4,3)。
由图片可知,展平之后的向量是无法进行矩阵运算的。

x=np.array([[4,7],[5,8],[6,9]])  //(3,2)
y=np.array([[[1,2,3],[4,5,6]],[[4,5,6],[7,8,9]]]) //(2,2,3)
z3=np.tensordot(x,y.transpose(),axes=2) 
print(z3)//ValueError: shape-mismatch for sum

在这里插入图片描述
注意,上面的y只需要进行一下转置,就可以完成函数的运算了。转置之后沿着0维度和1维度展开向量,得到y的shape为 ( 3 ∗ 2 , 2 ) = ( 6 , 2 ) (3*2,2)=(6,2) (32,2)=(6,2)
x展平之后的大小为(6,)
代码如下:

y=y.transpose() //转置之后y的shape为(3,2,2)
y_flatten =y.reshape(6,2)
print(y)
[[[1 4]
  [4 7]]

 [[2 5]
  [5 8]]

 [[3 6]
  [6 9]]]

print(y_)
[[1 4]
 [4 7]
 [2 5]
 [5 8]
 [3 6]
 [6 9]]

z3=np.tensordot(x,y,axes=2)
print(z3)
[154 271]

在这里插入图片描述

axes取值为列表或者元组的形式

我将依旧用上面的例子说明,只是这里的axes的取值不再为整数,我认为使用列表或者元组的形式表示更容易被理解。
首先看1维的向量:这里 a x e s = [ 0 , 0 ] axes=[0,0] axes=[0,0]代表的分别让x的0维度上的元素和y的0维度上的元素,相乘再相加。

x=np.array([1,2,3])
y=np.array([4,5,6])
z1=np.tensordot(x,y,axes=[0,0]) //等价于axes=1
print(z1)
//结果仍然时32

接下来看二维向量,当axes的取值维[0,0]时,代表x的行向量和y的行向量对应元素相乘再相加。同理axes的取值为[1,1]时,代表x的列向量和y的列向量对应相乘再相加。

x=np.array([[1,2,3],[4,5,6]])
print(x.shape) (2*3)
y=np.array([[4,5,6],[7,8,9]])
print(y.shape) (2*3)
z3=np.tensordot(x,y,axes=[0,0]) //等价于axes=1print(z3.shape)
[[32 37 42]
 [43 50 57]
 [54 63 72]]
z4=np.tensordot(x,y,axes=[1,1]) 
print(z4)
[[ 32  50]
 [ 77 122]]

在这里插入图片描述
注意也可以写成如下的式子:y进行转置,这种写法就是我们所熟悉的矩阵写法。由于y转置之后大小为(3,2)。因此axes=1,即x的行和y的列对应元素相乘并相加。

z3_=np.tensordot(x,y.transpose(),axes=[0,1])
z4=np.tensordot(x.transpose(),y,axes=[0,1]) 

多维度的张量以及列表表示的方法

假设x的大小为(2,3,5).y的大小为(2,3,5)。那么z的计算结果是多少呢?结果为1个标量:495。
这里的维度大小是如何计算呢?
由于x的维度大小为3,y的维度大小也为3,故两者的维度进行了抵消,得到的结果就为1个标量。

x = np.array([[[1, 2, 3, 4, 5],
            [1, 2, 3, 4, 5],
            [3, 4, 5, 6, 7]],
            [[4, 5, 6, 7, 8],
            [5, 6, 7, 8, 9],
            [6, 7, 8, 9, 1]]])
y = np.array([[[2, 3, 4, 5, 6],
            [2, 3, 4, 5, 6],
            [3, 4, 5, 6, 8]],
           [[4, 5, 6, 7, 9],
            [5, 6, 7, 8, 1],
            [6, 7, 8, 9, 2]]])
z=np.tensordot(x,y, axes = ([2,1,0],[2,1,0])
print(z)           
// z=895 
//等价于
x1=x.flatten()
x2=y.flatten()
print(sum(x1*x2))

下面这道例子:原理其实非常简单:如果两个张量在给定的axes在某一维度上对应的大小是相等的,那么这两个维度将被抵消掉。剩余的维度即为(3,5,3,5)

z=np.tensordot(x,y, axes = ([0],[0])).shape 
print(z)
z=(3,5,3,5)

在这里插入图片描述

x3=np.arange(48).reshape(2,4,3,2,1)
x4=np.arange(32).reshape(2,2,4,2)
y=np.tensordot(x3,x4,axes=([1,3],[2,1]))
print(y.shape)
// y的大小为(23122

在这里插入图片描述
再看下面这道例子,x是1个4维张量,y是1个二维张量,求axes=2的情况下z的取值:由于x的后两个维度维(3,2)。而y的前两个维度为(3,2)故可以消去。

x=np.array([0,1,2,1,3,4,5,2,3,4,5,0]).reshape(2,1,3,2)
y=np.array([1,3,2,3,1,2]).reshape(3,2,1)
z=np.tensordot(x,y,axes=2) 
[[[21]]

 [[34]]]

实际计算过程如下所示:
(1).首先判断最后得到的z的形状大小为:(2,1,1)
(1).分别展平x的后两个维度和y的前两个维度
(3).展平之后再进行内积的运算

x1=x.reshape(2,1,6)
y1=y.reshape(6,1)
print(x1)
[[[0 1 2 1 3 4]]

 [[5 2 3 4 5 0]]]

print(y1)
[[1]
 [3]
 [2]
 [3]
 [1]
 [2]]
print(np.dot(x1,y1))
[[[21]]

 [[34]]]

tensorflow.tensordot和numpy.tensordot中的不同

可以看出来使用tf,x的shape为(2,1,3,2),y的shape为(2,3)。即x后两个维度为(3,2);y的前两个维度为(2,3)。
而如果使用numpy中的tensordot,x的shape为(2,1,3,2),x的后两个维度为(3,2),y的前两个维度为(3,2)。

因此tf中维度大小是交换了位置。而numpy中要求维度必须要一样。

// tf的版本是tensorflow 2.4.0
import tensorflow as tf
import os
os.environ["CUDA_VISIBLE_DEVICES"]="0"
tf.compat.v1.disable_eager_execution()
config=tf.compat.v1.ConfigProto(allow_soft_placement=True)
config.gpu_options.per_process_gpu_memory_fraction = 0.8
sess=tf.compat.v1.Session(config=config)
x = tf.constant([0,1,2,1,3,4,5,2,3,4,5,0],shape=[2,1,3,2])
y =tf.constant([1,3,2,3,1,2],shape=[2,3,1])
z = tf.tensordot(x,y,axes=2)
z=[[21]
 [34]]

补充:2022-03-21
注意axes=1,对于该三维向量来说取得值是[0,5,10]。而不是[0,1,2,3,4]哦。是每一行对应的元素。
在这里插入图片描述
对于二维向量来说:axes=1,对应的是每一列对应元素,即[0,1,2]
在这里插入图片描述

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值