標量(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)。這是張量中所包含數據的類型,例如,張
量的類型能夠是float3二、uint八、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
爲了便於理解點積的形狀匹配,能夠利用可視化來幫助理解。
張量變形(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 所示。