x = np.array([[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]]])
将多个 3D 张量组合成一个数组,可以创建一个 4D 张量,以此类推。深度学习处理的一般
是 0D 到 4D 的张量,但处理视频数据时可能会遇到 5D 张量。
5.关键属性
张量是由以下三个关键属性来定义的。
轴的个数(阶): 例如, 3D 张量有 3 个轴,矩阵有 2 个轴。这在 Numpy 等 Python 库中
也叫张量的 ndim 。
形状: 这是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。例如,前面矩
阵示例的形状为 (3, 5) , 3D 张量示例的形状为 (3, 3, 5) 。向量的形状只包含一个
元素,比如 (5,) ,而标量的形状为空,即 () 。
数据类型: (在 Python 库中通常叫作 dtype )。这是张量中所包含数据的类型,例如,张
量的类型可以是 float32 、 uint8 、 float64 等。在极少数情况下,你可能会遇到字符
( char )张量。注意, Numpy (以及大多数其他库)中不存在字符串张量,因为张量存
储在预先分配的连续内存段中,而字符串的长度是可变的,无法用这种方式存储。
6.张量运算
6.1逐元素运算
relu 运算和加法都是 逐元素 ( element-wise )的运算,即该运算独立地应用于张量中的每
个元素,也就是说,这些运算非常适合大规模并行实现。下列代码是对逐元素 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
对于加法采用同样的实现方法。
def naive_add(x, y):
assert len(x.shape) == 2
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
6.2广播
广播包含 以下两步。
(1) 向较小的张量添加轴(叫作 广播轴 ),使其 ndim 与较大的张量相同。
(2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
来看一个具体的例子。假设 X 的形状是 (32, 10) , y 的形状是 (10,) 。首先,我们给 y
添加空的第一个轴,这样 y 的形状变为 (1, 10) 。然后,我们将 y 沿着新轴重复 32 次,这样
得到的张量 Y 的形状为 (32, 10) ,并且 Y[i, :] == y for i in range(0, 32) 。现在,
我们可以将 X 和 Y 相加,因为它们的形状相同。
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
6.3张量点积
点积运算,也叫 张量积 ( tensor product ,不要与逐元素的乘积弄混),是最常见也最有用的
张量运算。与逐元素的运算不同,它将输入张量的元素合并在一起。
从数学的角度来看,点积运算做了什么?我们首先看一下两个向量 x 和 y 的点积。其计算
过程如下:
def naive_vector_dot(x, y):
assert len(x.shape) == 1
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
注意,两个向量之间的点积是一个标量,而且只有元素个数相同的向量之间才能做点积。
6.4张量变形
张量变形是指改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始
张量相同。简单的例子可以帮助我们理解张量变形。
import numpy as np
x = np.array([[ 0,1],
[ 2,3],
[ 4,5]])
print(x.shape)
x = x.reshape((6, 1))
print(x)
6.5张量运算的导数(梯度)
梯度 ( gradient )是张量运算的导数。它是导数这一概念向多元函数导数的推广。多元函数
是以张量作为输入的函数。
随机梯度下降
给定一个可微函数,理论上可以用解析法找到它的最小值:函数的最小值是导数为 0 的点, 因此你只需找到所有导数为 0 的点,然后计算函数在其中哪个点具有最小值。
将这一方法应用于神经网络,就是用解析法求出最小损失函数对应的所有权重值。
由于处理的是一个可微函数,你可以计算出它的梯度,从而有效地实
现第四步。沿着梯度的反方向更新权重,损失每次都会变小一点。
(1) 抽取训练样本 x 和对应目标 y 组成的数据批量。
(2) 在 x 上运行网络,得到预测值 y_pred 。
(3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
(4) 计算损失相对于网络参数的梯度[一次 反向传播 ( backward pass )]。
(5) 将参数沿着梯度的反方向移动一点,比如 W -= step * gradient ,从而使这批数据
上的损失减小一点。
刚刚描述的方法叫作 小批量随机梯度下降 ( mini-batch stochastic gradient descent ,
又称为小批量 SGD )。
6.6链式求导:反向传播算法
在前面的算法中,我们假设函数是可微的,因此可以明确计算其导数。在实践中,神经网
络函数包含许多连接在一起的张量运算,每个运算都有简单的、已知的导数。例如,下面这个
网络 f 包含 3 个张量运算 a 、 b 和 c ,还有 3 个权重矩阵 W1 、 W2 和 W3 。
f(W1, W2, W3) = a(W1, b(W2, c(W3)))
根据微积分的知识,这种函数链可以利用下面这个恒等式进行求导,它称为 链式法则 ( chain
rule ): (f(g(x)))' = f'(g(x)) * g'(x) 。将链式法则应用于神经网络梯度值的计算,得
到的算法叫作 反向传播 ( backpropagation ,有时也叫 反式微分 , reverse-mode differentiation )。反
向传播从最终损失值开始,从最顶层反向作用至最底层,利用链式法则计算每个参数对损失值
的贡献大小。