神经网络中的所有数据变换都可以简化为对数字数据张量的一些张量运算(tensor operation)或者张量函数(tensor function)。
1.逐元素运算:
运算分别应用于张量中的每个元素。
使用for循环构建一个简单的张量加法运算。
import numpy as np
def naive_add(x , y):
assert len(x.shape) == 2 # x为2阶张量
assert x.shape == y.shape # y也为2阶张量
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
x = np.array([[1,2],
[3,4]])
y = np.array([[5,6],
[7,8]])
# 采用编写的方法实现加法运算
naive_add(x , y)
# 直接使用+运算符实现
x + y
同样也可以实现一个relu运算。
import numpy as np
def naive_relu(x):
assert len(x.shape) == 2 # x为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
x = np.array([[1,-2,3,-4],
[-5,6,-7,8]])
# 采用编写的方法实现relu运算
naive_relu(x)
# 直接使用numpy提供的方法实现
np.maximum(x ,0)
2.广播:
上面实现了两个形状相同的2阶张量的加法运算,那么当一个2阶张量与一个向量相加该如何进行?
在没有歧义且可行的情况下,较小的张量会被广播,以匹配较大张量的形状,包括如下两步:
- 向较小张量添加轴(称作广播轴broadcast axis),使其维度ndim与较大张量一致;
- 将较小张量沿着新轴重复,使其形状与较大张量相同。
import numpy as np
# x为2阶张量,y为向量
x = np.random.random((4,2)) # ndim=2
y = np.random.random((2,)) # ndim=1
# 首先向y添加新轴,ndim由1变为2,shape为(1,2)
y = np.expand_dims(y , axis=0)
# 将y沿着新轴重复,shape由(1,2)变为(4,2)
y = np.concatenate([y]*4 , axis=0)
在实际运算过程中并不会创建一个新的2阶变量,因为这样做会非常低效,上面的重复操作完全是虚拟的,它只出现在算法中,并不会出现在内存中。
下面是一个2阶张量与向量加法运算的简单实现。
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
3.张量积:
张量积(tensor product)或点积(dot product)是最常见且最有用的张量运算之一。在Numpy中使用np.dot()
函数实现,数学符号中的点(•)表示点积运算。
主要注意的是,其与逐元素运算的乘积(*运算符)运算不同。
- 两个向量的点积运算结果为一个标量,另外只有元素个数相同的向量才能进行点积运算:
import numpy as np
def naive_vector_dot(x,y):
assert len(x.shape) == 1
assert len(y.shape) == 1
assert x.shape == y.shape
z = 0.
for i in range(x.shape[0]):
z += x[i] * y[i]
return z
x = np.random.random((3,))
y = np.random.random((3,))
naive_vector_dot(x,y)
- 一个矩阵x与一个向量y的点积运算结果为一个向量,其中每个元素是y和x每一行的点积结果:
import numpy as np
def naive_matrix_vector_dot(x,y):
assert len(x.shape) == 2 # x为矩阵
assert len(y.shape) == 1 # y为向量
assert x.shape[1] == y.shape[0] # x的第1维与y的第0维必须相同
z = np.zeros(x.shape[0]) # 返回结果为一个向量,形状与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
x = np.random.random((3,2))
y = np.random.random((2,))
naive_matrix_vector_dot(x,y)
矩阵与向量的点积运算,是向量y和矩阵x每一行的点积结果组成的向量,因此也可以重复使用上述构造的两个向量点积运算的方法naive_vector_dot(x,y)
。
import numpy as np
def naive_matrix_vector_dot(x,y):
assert len(x.shape) == 2
assert len(y.shape) == 1
assert x.shape[1] == y.shape[0]
z = np.zeros(x.shape[0])
for i in range(x.shape[0]):
z[i] = naive_vector_dot(x[i],y) # 两个向量进行点击运算
return z
- 两个矩阵的点积运算结果为一个矩阵,其中x的行和y的列必须具有相同的元素个数:
import numpy as np
def naive_matrix_dot(x,y):
assert len(x.shape) == 2 # x为矩阵
assert len(y.shape) == 2 # y为矩阵
assert x.shape[1] == y.shape[0] # x的第1维与y的第0维必须相同
z = np.zeros(x.shape[0] , y.shape[1])
for i in range(x.shape[0]):
for j in range(y.shape[1]):
row_x = x[i , :] # 遍历x的所有行
column_y = y[: , j] # 遍历y的所有列
z[i , j] = naive_vector_dot(row_x , column_y) # 两个向量进行点击运算
return z
x = np.random.random((4,3))
y = np.random.random((3,2))
naive_matrix_dot(x,y)
两个矩阵的点积结果,其实是由x的行和y的列点积结果组成的,图示表达如下:

4.张量变形:
张量变形(tensor reshaping)是指重新排列张量的行和列,以得到想要的形状。
>>> x = np.array([[1,2],
[3,4],
[5,6]])
>>> x.shape
(3, 2)
>>> x = x.reshape((6,1))
>>> x
array([[1],
[2],
[3],
[4],
[5],
[6]])
>>> x = x.reshape((2,3))
>>> x
array([[1, 2, 3],
[4, 5, 6]])
常见的一种特殊的张量变形是转置(transpose)。矩阵转置是指将矩阵的行和列互换,即把
x[i, :] 变为x[:, i]。
>>> x = np.array([[1,2],
[3,4],
[5,6]])
>>> x = np.transpose(x)
>>> x.shape
(2, 3)
>>> x
array([[1, 3, 5],
[2, 4, 6]])
References:《Deep Learning with Python(Second Editon)》