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()是一样的。
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]]]]
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 1∗4+2∗5+3∗6=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 1∗4+2∗5+3∗6=4+10+18=32 1 ∗ 7 + 2 ∗ 8 + 3 ∗ 9 = 7 + 16 + 27 = 50 1*7+2*8+3*9=7+16+27=50 1∗7+2∗8+3∗9=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) (4,7)举例,后面的类似
因为一维向量只有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) (3∗2,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的取值不再为整数,我认为使用列表或者元组的形式表示更容易被理解。
首先看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=1。
print(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的大小为(2,3,1,2,2)
再看下面这道例子,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]]]
可以看出来使用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]