张量运算

  • 一、逐元素运算
  • 1.加法运算
  • 2.relu运算
  • 二、张量点积
  • 1.向量间的点积
  • 2.矩阵和向量间的点积
  • 3.矩阵间的点积
  • 三、张量变形
  • 四、广播
  • 1.加法运算
  • 2.maximum运算
  • 总结



一、逐元素运算

**逐元素运算独立地应用于张量的每个元素。**为了更好地理解逐元素运算,下面给出relu运算和加法运算的逐元素实现,使用for循环。


1.加法运算

def naive_add(x,y):
    assert len(x.shape) == 2   #x,y是Numpy的2D张量
    assert x.shape == y.shape

    x = x.copy()
    
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i,j] += y[i,j]
    return x


a = np.array([[1,2,3],
              [2,4,6]])
b = np.array([[1,0,3],
              [1,1,1]])
print("result:",naive_add(a,b))
result: 
[[2 2 6]
 [3 5 7]]

在Numpy中之间进行逐元素加法运算

import numpy as np
z = x + y

2.relu运算

def naive_relu(x):
    assert len(x.shape) == 2

    x = x.copy()
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i,j] = max(x[i,j], 0)
    return x

c = np.array([[-1,0,3],
              [1,-1,1]])
print(naive_relu(c))
[[0 0 3]
 [1 0 1]]

在Numpy中之间进行逐元素relu运算

import numpy as np
z = np.maximum(z, 0.)

二、张量点积

张量的点积运算与逐元素的乘积元素不同,是最常见的张量运算。在Numpy中使用 ***** 实现逐元素乘积,使用dot运算符实现点积。

1.向量间的点积

def naive_vector_dot(x,y):
    assert len(x.shape) == 1	#x和y都是Numpy向量
    assert len(y.shape) == 1
    assert x.shape[0] == y.shape[0]

    z = 0.
    
    for i in range(x.shape[0]):
        z += x[i] * y[i]
    return z

a = np.array([1,2,3])
b = np.array([2,1,1])
c = naive_vector_dot(a,b)
print("c的类型为:",type(c),"c的值为:",c)

注:两个向量间的点积是一个标量。而且只有元素个数相同的向量之间才能做点积。

c = a . b = 1 * 2 + 2 * 1 + 3 * 1 = 7

c的类型为: <class 'numpy.float64'> c的值为: 7.0

2.矩阵和向量间的点积

def naive_matrix_vector_dot(x,y):
    assert len(x.shape) == 2	#x是一个Numpy矩阵
    assert len(y.shape) == 1	#y是一个Numpy向量
    assert x.shape[1] == y.shape[0]

    z = np.zeros(x.shape[0])
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            z[i] += x[i,j] * y[j]
    return z

a = np.array([[1,2,3],
              [4,7,9]])
b = np.array([2,1,1])
c = naive_matrix_vector_dot(a,b)
print("c的类型为:",type(c),"\n",
"c的形状为:",c.shape,"\n",
"c的维度为:",c.ndim,"\n","c的值为:",c)

矩阵a和向量b做点积,返回值是一个向量,其中每个元素是向量b和矩阵a的每一行之间的点积。

c的类型为: <class 'numpy.ndarray'> 
c的形状为: (2,) 
c的维度为: 1 
c的值为: [ 7. 24.]

如果复用之前向量间点积的代码,则有:

def naive_matrix_vector_dot(x,y):
    z = np.zeros(x.shape[0])
    for i in range(x.shape[0]):
        z[i] = naive_vector_dot(x[i, : ], y)
    return z

3.矩阵间的点积

def naive_matrix_dot(x,y):
    assert len(x.shape) == 2
    assert len(y.shape) == 2
    assert x.shape[1] == y.shape[0]

    z = np.zeros((x.shape[0],y.shape[1]))
    for i in range(x.shape[0]):
        for j in range(y.shape[1]):
            for k in range(y.shape[0]):
                z[i][j] += x[i][k] * y[k][j]
    return z

复用之前向量间点积的代码,则有:

def naive_matrix_dot1(x,y):
    assert len(x.shape) == 2
    assert len(y.shape) == 2	#x,y都是Numpy矩阵
    assert x.shape[1] == y.shape[0]

    z = np.zeros((x.shape[0],y.shape[1]))
    for i in range(x.shape[0]):	#遍历x的所有行
        for j in range(y.shape[1]):	#遍历y的所有列
            row_x = x[i, : ]
            column_y = y[ : , j]
            z[i][j] = naive_vector_dot(row_x, column_y)
    return z

矩阵a和矩阵b做点积,得到的结果是一个形状为(x.shape[0], y.shape[1])的矩阵,其元素为x的行与y的列之间的点积。

a = np.array([[1,2,3],
              [4,7,9]])
b = np.array([[1,1,1],
             [2,2,2],
             [0,4,0]])
c = naive_matrix_dot(a,b)
d = naive_matrix_dot1(a,b)


print("c的类型为:",type(c),"\n",
"c的形状为:",c.shape,"\n",
"c的维度为:",c.ndim,"\n","c的值为:",c)

print("d的类型为:",type(d),"\n",
"d的形状为:",d.shape,"\n",
"d的维度为:",d.ndim,"\n","d的值为:",d)

运行结果如下:

c的类型为: <class 'numpy.ndarray'> 
c的形状为: (2, 3) 
c的维度为: 2 
c的值为: [[ 5. 17.  5.]
 		  [18. 54. 18.]]
 		  
d的类型为: <class 'numpy.ndarray'> 
d的形状为: (2, 3) 
d的维度为: 2 
d的值为: [[ 5. 17.  5.]
 		  [18. 54. 18.]]

三、张量变形

张量变形是指改变张量的行和列,以得到想要的形状。变形后张量的元素总个数与初始张量相同。下面给出张量变形的例子。

a = np.array([[1,2,3],
              [4,7,9]])
print(a.shape)
b = a.reshape((6,1))
print(b)
c = b.reshape(3,2)
print(c)

运行结果如下:

(2, 3)

[[1]
 [2]
 [3]
 [4]
 [7]
 [9]]
 
[[1 2]
 [3 4]
 [7 9]]

转置(transposition)是一种特殊的张量变形,对矩阵做转置是指将行和列互换,使x[ i , : ]变为x[ : , i ]。下面给出一个转置的例子。

e = np.zeros((300,20))	#创建一个形状为(300,20)的零矩阵
e = np.transpose(e)
print(e.shape)	#(20, 300)

四、广播

如果将两个形状不同的张量相加,则:
较小的张量会被广播(broadcast),以匹配较大张量的形状。

广播包含以下两步:
(1)向较小的张量添加轴(叫做广播轴),使其ndim与较大的张量相同。
(2)将较小的张量沿着新轴重复,使其形状与较大的张量相同。

假设 x.shape 为(32,10),y.shape为(10, )。首先,给y添加空的第一个轴,则y.shape为(1,10)。然后,将y 沿新轴重复32次,则得到张量y.shape为(32,10)。

1.加法运算

下面给出一种加法的简单实现

def naive_add_matrix_and_vector(x,y):
    assert len(x.shape) == 2
    assert len(y.shape) == 1
    assert x.shape[1] == y.shape[0]

    x = x.copy()
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i, j] += y[j]
    return x
    
a = np.array([[1,2,3],
              [4,7,9]])
b = np.array([1,1,1])
c = a + b
print(c)

下面是矩阵a和向量b相加的结果

[[ 2  3  4]
 [ 5  8 10]]

2.maximum运算

利用广播将逐元素的maximum运算应用于两个形状不同的张量。

import numpy as np
a = np.random.random((64,3,32,10))
b = np.random.random((32,10))

c = np.maximum(a,b)
print(c.shape)

a为形状是(64, 3, 32, 10)的随机张量
b是形状为(32,10)的随机张量
输出c的形状是(64, 3, 32, 10),与a相同

(64, 3, 32, 10)

总结

本文主要介绍了张量的几种常用运算:
逐元素运算、张量点积、张量变形、张量广播。