标量(0D 张量)
仅包含一个数字的张量叫作标量(scalar,也叫标量张量、零维张量、0D 张量)。在Numpy
中,一个float32 或float64 的数字就是一个标量张量(或标量数组)。你可以用ndim 属性
来查看一个Numpy 张量的轴的个数。标量张量有0 个轴(ndim == 0)。张量轴的个数也叫作
阶(rank)。下面是一个Numpy 标量。
>>> import numpy as np
>>> x = np.array(12)
>>> x
array(12)
>>> x.ndim
0

 

向量(1D 张量)
数字组成的数组叫作向量(vector)或一维张量(1D 张量)。一维张量只有一个轴。下面是
一个Numpy 向量。
>>> x = np.array([12, 3, 6, 14, 7])
>>> x
array([12, 3, 6, 14, 7])
>>> x.ndim
1

 

矩阵(2D 张量)
向量组成的数组叫作矩阵(matrix)或二维张量(2D 张量)。矩阵有2 个轴(通常叫作行和
列)。你可以将矩阵直观地理解为数字组成的矩形网格。下面是一个Numpy 矩阵。
>>> x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
>>> x.ndim
2
第一个轴上的元素叫作行(row),第二个轴上的元素叫作列(column)。在上面的例子中,
[5, 78, 2, 34, 0] 是x 的第一行,[5, 6, 7] 是第一列。
 
 
3D 张量与更高维张量
将多个矩阵组合成一个新的数组,可以得到一个3D 张量,你可以将其直观地理解为数字
组成的立方体。下面是一个Numpy 的3D 张量。
>>> 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]]])
>>> x.ndim
3

 

关键属性
张量是由以下三个关键属性来定义的。
轴的个数(阶)。例如,3D 张量有 3 个轴,矩阵有 2 个轴。这在 Numpy 等 Python 库中
也叫张量的ndim。
形状。这是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。例如,前面矩
阵示例的形状为(3, 5),3D 张量示例的形状为(3, 3, 5)。向量的形状只包含一个
元素,比如(5,),而标量的形状为空,即()。
数据类型(在 Python 库中通常叫作 dtype)。这是张量中所包含数据的类型,例如,张
量的类型可以是float32、uint8、float64 等。在极少数情况下,你可能会遇到字符
(char)张量。注意,Numpy(以及大多数其他库)中不存在字符串张量,因为张量存
储在预先分配的连续内存段中,而字符串的长度是可变的,无法用这种方式存储。
 
 
以MNIST 例子说明
张量train_images 的轴的个数,即ndim 属性
>>> print(train_images.ndim)
3
下面是它的形状
>>> print(train_images.shape)
(60000, 28, 28)
下面是它的数据类型,即dtype 属性
>>> print(train_images.dtype)
uint8
 
所以,这里train_images 是一个由8 位整数组成的3D 张量。更确切地说,它是60 000
个矩阵组成的数组,每个矩阵由28×28 个整数组成。每个这样的矩阵都是一张灰度图像,元素
取值范围为0~255。
 
 
在Numpy 中操作张量
选择张量的特定元素叫作张量切片(tensor slicing)
 
选择第10~100 个数字(不包括第100 个),并将其放在形状为(90, 28,28) 的数组中。
>>> my_slice = train_images[10:100]
>>> print(my_slice.shape)
(90, 28, 28)
它等同于下面这个更复杂的写法,给出了切片沿着每个张量轴的起始索引和结束索引。
注意,: 等同于选择整个轴。
>>> my_slice = train_images[10:100, :, :]     #等同于前面的例子
>>> my_slice.shape
(90, 28, 28)
 
>>> my_slice = train_images[10:100, 0:28, 0:28]     #也等同于前面的例子
>>> my_slice.shape
(90, 28, 28)
 
一般来说,你可以沿着每个张量轴在任意两个索引之间进行选择。例如,你可以在所有图
像的右下角选出14 像素×14 像素的区域:
my_slice = train_images[:, 14:, 14:]
 
也可以使用负数索引。与Python 列表中的负数索引类似,它表示与当前轴终点的相对位置。
你可以在图像中心裁剪出14 像素×14 像素的区域:
my_slice = train_images[:, 7:-7, 7:-7]
 
 
现实世界中的数据张量
向量数据:2D 张量,形状为 (samples, features)
时间序列数据或序列数据:3D 张量,形状为 (samples, timesteps, features)
图像:4D张量,形状为(samples, height, width, channels)或(samples, channels, height, width)
视频:5D张量,形状为(samples, frames, height, width, channels)或(samples, frames, channels, height, width)
 
 
广播
如果将两个形状不同的张量相加,会发生
什么?
如果没有歧义的话,较小的张量会被广播(broadcast),以匹配较大张量的形状。广播包含
以下两步。
(1) 向较小的张量添加轴(叫作广播轴),使其ndim 与较大的张量相同。
(2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
 
 
 
张量点积
在Numpy 和Keras 中,都是用标准的dot 运算符来实现点积。
import numpy as np
z = np.dot(x, y)
 
对于两个矩阵x 和y,当且仅当x.shape[1] == y.shape[0] 时,你才可以对它们做点积dot(x, y))。
得到的结果是一个形状为(x.shape[0], y.shape[1]) 的矩阵,其元素为x的行与y 的列之间的点积。
其简单实现如下。
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]):
            row_x = x[i, :]
            column_y = y[:, j]
            z[i, j] = naive_vector_dot(row_x, column_y)
    return z

 

 
为了便于理解点积的形状匹配,可以利用可视化来帮助理解。
 
Python深度学习读书笔记-3.神经网络的数据表示
 
 
张量变形(tensor reshaping)
张量变形是指改变张量的行和列,以得到想要的形状。变形后的张量的元素总个数与初始
张量相同。简单的例子可以帮助我们理解张量变形。
>>> x = np.array([[0., 1.],
                  [2., 3.],
                  [4., 5.]])
>>> print(x.shape)
(3, 2)
 
>>> x = x.reshape((6, 1))
>>> x
array([[ 0.],
       [ 1.],
       [ 2.],
       [ 3.],
       [ 4.],
       [ 5.]])
 
>>> x = x.reshape((2, 3))
>>> x
array([[ 0., 1., 2.],
       [ 3., 4., 5.]])

  

经常遇到的一种特殊的张量变形是转置(transposition)。对矩阵做转置是指将行和列互换,使x[i, :] 变为x[:, i]。
>>> x = np.zeros((300, 20))
>>> x = np.transpose(x)
>>> print(x.shape)
(20, 300)

 

深度学习的几何解释
前面讲过,神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何
变换。因此,你可以将神经网络解释为高维空间中非常复杂的几何变换,这种变换可以通过许
多简单的步骤来实现。
对于三维的情况,下面这个思维图像是很有用的。想象有两张彩纸:一张红色,一张蓝色。
 
将其中一张纸放在另一张上。现在将两张纸一起揉成小球。这个皱巴巴的纸球就是你的输入数
据,每张纸对应于分类问题中的一个类别。神经网络(或者任何机器学习模型)要做的就是找
到可以让纸球恢复平整的变换,从而能够再次让两个类别明确可分。通过深度学习,这一过程
可以用三维空间中一系列简单的变换来实现,比如你用手指对纸球做的变换,每次做一个动作,
如图2-9 所示。
 
Python深度学习读书笔记-3.神经网络的数据表示