圖深度學習入門教程(一)——基礎類型

本教程是一個系列免費教程,爭取每月更新2到4篇。

主要是基於圖深度學習的入門內容。講述最基本的基礎知識,其中包括深度學習、數學、圖神經網路等相關內容。該教程由程式碼醫生工作室出版的全部書籍混編節選而成。偏重完整的知識體系和學習指南。在實踐方面不會涉及太多基礎內容 (實踐和經驗方面的內容,請參看原書)。

文章涉及使用到的框架以PyTorch和TensorFlow為主。默認讀者已經掌握Python和TensorFlow基礎。如有涉及到PyTorch的部分,會順帶介紹相關的入門使用。

本教程主要針對的人群:

  • 已經掌握TensorFlow基礎應用,並想系統學習的學者。
  • PyTorch學習者
  • 正在從TensorFlow轉型到PyTroch的學習者
  • 已經掌握Python,並開始學習人工智慧的學者。

本篇文章以Numpy為主進行實現,順便介紹下PyTorch的基礎數據類型。在結尾部分會介紹一些TensorFlow的運算介面。

1. 神經網路中的幾個基本數據類型

PyTorch 是一個建立在 Torch 庫之上的 Python 包。其內部主要是將數據封裝成張量(Tensor)來進行運算的。

有關張量的介紹,得從神經網路中的基本類型開始,具體如下。

神經網路中的幾個基本數據類型有標量(Scalar)、向量(Vector)、矩陣(Matrix)、張量(Tensor)。它們之間是層級包含的關係,如圖所示。

圖中所表示的層級關係解讀如下:

  • 標量只是某個具體的數字,
  • 向量由多個標量組成
  • 矩陣由多個向量組成
  • 張量由多個矩陣組成

張量是向量和矩陣的推廣,PyTorch 中的張量就是元素為同一數據類型多維矩陣。

2 矩陣的基礎

在圖神經網路中,常會把圖結構用矩陣來表示。這一轉化過程需要很多與矩陣操作相關的知識。這裡就從矩陣的基礎開始介紹。

2.1 轉置矩陣:

將矩陣的行列互換得到的新矩陣稱為轉置矩陣。

等式左邊的矩陣假設為,則等式右邊的轉置矩陣可以記作。

2.2. 對稱矩陣及其特性

沿著對角線所分割的上下三角數據成對稱關係的矩陣,叫做對稱矩陣。

圖中是一個對稱矩陣,又是一個方形矩陣(行列相等的矩陣)。這種矩陣的轉置矩陣與本身相等。即。

2.3 對角矩陣與單位矩陣

對角矩陣是除對角線以外,其它項都為0的矩陣。

圖中的對角矩陣,可以由對角線上的向量生成,程式碼如下:

v = np.array([1, 8, 4])  print( np.diag(v) )

該程式碼執行後,會生成圖中的對角矩陣。

單位矩陣就是對角線都為1的矩陣,例如:

np.eye(3)

該程式碼運行後,會生成一個3行3列的單位矩陣,如圖所示

3. 哈達馬積(Hadamard product)

哈達馬積(Hadamard product)指兩個矩陣對應位置上的元素進行相乘。具體例子如下:

a= np.array(range(4)).reshape(2,2) # array([[0, 1], [2, 3]])  b = np.array(range(4,8)).reshape(2,2)# array([[4, 5], [6, 7]])  print(a*b) #輸出 [[ 0  5] [12 21]]

4. 點積(dot product)

點積是指兩個矩陣之間的相乘,矩陣相乘的標準方法不是將一個元素的每個元素與另一個元素的每個元素相乘(這是逐個元素的乘積),而是計算行與列之間的乘積之和。

第一個矩陣的列數必須等於第二個矩陣的行數。因此,如果第一矩陣的尺寸或形狀為(m×n)第二個矩陣必須是形狀(n×x)。所得矩陣的形狀為(m×x)。

程式碼如下:

C=a@b  #實現a與b的點積,C的結果為array([[ 6,  7], [26, 31]])

5. 對角矩陣的特性與操作方法

由於對角矩陣只有對角線有值的特殊性,在運算過程中,會利用其自身的特性,實現一些特殊的功能。下面一一舉例:

1. 對角矩陣與向量的互轉

由於對角矩陣只有對角線有值,可以由向量生成對角矩陣。當然也可以將對角矩陣的向量提取出來。例如下列程式碼:

import numpy as np  a=np.diag([1,2,3]) #定義一個對角矩陣  print(a) #輸出對角矩陣[[1 0 0] [0 2 0] [0 0 3]]  v,e = np.linalg.eig(a) #向量和對角矩陣  print(v)#輸出向量 [1. 2. 3.]

2. 對角矩冪運算等於對角線上各個值的冪運算

下列程式碼分別以4中方法實現了對角矩陣的3次方

print(a*a*a) #輸出:[[ 1  0  0]  [ 0  8  0]  [ 0  0 27]]  print(a**3) #輸出:[[ 1  0  0]  [ 0  8  0]  [ 0  0 27]]  print((a**2)*a)#輸出:[[ 1  0  0]  [ 0  8  0]  [ 0  0 27]]  print(a@a@a)  #輸出:[[ 1  0  0]  [ 0  8  0]  [ 0  0 27]]

可以看到,對角矩陣的哈達瑪積和點積的結果都是一樣。

當指數為-1(倒數)時,又叫做矩陣的逆。求對角矩陣的逆不能直接使用a**(-1)這種形式,需要使用特定的函數。程式碼如下:

print(np.linalg.inv(a))  #對矩陣求逆( -1次冪)  A = np.matrix(a)#矩陣對象可以通過 .I 更方便的求逆  print(A.I) #輸出[[1. 0. 0.] [0. 0.5 0.] [0. 0. 0.33333333]]

3. 將一個對角矩陣與其倒數相乘便可以得到單位矩陣

一個數與自身的倒數相乘結果為1,在對角矩陣中也是這個規率。程式碼如下:

print(np.linalg.inv(a)@a) #輸出[[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]

4. 對角矩陣左乘其它矩陣,相當於其對角元素分別乘其它矩陣的對應各行

舉例程式碼如下:

a=np.diag([1,2,3]) #定義一個對角矩陣  b=np.ones([3,3])#定義一個3行3列的矩陣  print(a@b)  #對角矩陣左乘一個矩陣

該程式碼運行後,輸出如下結果:

 [[1., 1., 1.],    [2., 2., 2.],    [3., 3., 3.]]

可以看到,對角陣的對角元素分別乘這個矩陣的對應各行。

5. 對角矩陣右乘其它矩陣,相當於其對角元素分別乘其它矩陣的對應各列

舉例程式碼如下:

a=np.diag([1,2,3]) #定義一個對角矩陣  b=np.ones([3,3])#定義一個3行3列的矩陣  print(b@a)  #對角矩陣右乘一個矩陣

該程式碼運行後,輸出如下結果:

[[1. 2. 3.]   [1. 2. 3.]   [1. 2. 3.]]

6. 度矩陣與鄰接矩陣

在圖神經網路中,常用度矩陣(degree matrix)和鄰接矩陣來描述圖的結構,其中:

  • 圖的度矩陣用來描述圖中每個節點所連接的邊數。
  • 圖的鄰接矩陣用來描述圖中每個節點之間的相鄰關係。

例如,有一個圖結構,如圖所示。

無向圖結構

圖中一共有6個點,該圖的度矩陣是一個6行6列的矩陣。矩陣對角線上的數值代表該點所連接的邊數。例如:1號點有2個邊、2號點有3個邊。得到的矩陣如下:

在公式推導中,一般習慣把圖的度矩陣用符號來表示。

圖中的鄰接矩陣是一個6行6列的矩陣。矩陣的行和列都代表1~6這6個點,其中第i行j列的元素,代表第i號點和第j號點之間的邊。例如:第1行第2列的元素為1,代表1號點和2號點之間有一條邊。

在公式推導中,一般習慣把圖的鄰接矩陣用符號來表示。

7 TensorFlow中點積操作總結

點積指的是矩陣相乘。在神經網路中,無論是全連接還是卷積甚至是注意力機制,都可以找到點積操作的影子。點積操作可以理解為神經網路的計算核心。

在TensorFlow中,有好多與點積有關的函數,在使用這些函數進行開發時,難免會產生疑惑。這裡就來總結一下與點積有關的函數有哪些?以及它們之間彼此的區別示什麼?

1. tf.multiply函數

tf.multiply函數可以實現兩個矩陣對應元素相乘(哈達瑪積),並不是真正的點積運算。它要求兩個矩陣的維度必須匹配。即兩個矩陣的維度必須相等,如果有不相等的維度,則其中一個必須是1.否則將無法計算。

例:

import tensorflow as tf  from tensorflow.keras.layers import Dot  from tensorflow.keras import backend as K  import numpy as np  c1 = tf.multiply(K.ones(shape=(32, 20,3, 5)),K.ones(shape=(32, 20,1, 5)))#正確  c2 = tf.multiply(K.ones(shape=(32, 20,3, 5)),K.ones(shape=(32, 20,3, 5))) #正確  c3 = tf.multiply(K.ones(shape=(32, 20,3, 5)),K.ones(shape=(1, 1))) #正確  c4 = tf.multiply(K.ones(shape=(32, 20,3, 5)),K.ones(shape=(32, 20,1, 2))) #不正確  c5 = tf.multiply(K.ones(shape=(32, 20,3, 1)),K.ones(shape=(32, 20,1, 2))) #正確  print( c1.shape ) #輸出(32, 20,3, 5)

tf.multiply函數會輸出一個新的矩陣,新矩陣的維度等於相乘矩陣中的最大維度。

2. tf.matmul函數

tf.multiply函數可以實現真正的矩陣相乘,(第二個矩陣中每個元素都與第一個矩陣中的元素相乘,再相加)即點積操作。

它要求第1個矩陣最後1個維度要與第2個矩陣的倒數第2個維度相等,同時,兩個矩陣的倒數第2個之前的維度也必須相等。

例如:

c1 = tf.matmul(K.ones(shape=(32, 20,3, 1)),K.ones(shape=(32, 20,1, 3))) #正確  c2 = tf.matmul(K.ones(shape=(32, 20,3, 1)),K.ones(shape=(32, 20,3, 1))) #不正確  c3 = tf.matmul(K.ones(shape=(32, 20,3, 1)),K.ones(shape=(1, 3))) #不正確  c4 = tf.matmul(K.ones(shape=(32, 20,3, 1)),K.ones(shape=(32, 20,1, 5))) #正確  print( c4.shape ) #輸出(32, 20, 3, 5)

tf.matmul函數輸出的矩陣形狀中最後1個維度等於第2個相乘矩陣的最後1個維度。

3. K.batch_dot函數

K.batch_dot函數有個額外的參數axis,該參數的默認值為axes = [1,0]。在不加axis參數的情況下,K.batch_dot於tf.matmul函數完全一樣。只有如下這種情況例外:

c = K.batch_dot(K.ones(shape=(3,1)),K.ones(shape=(4,1))) #正確  print(c.shape)  #輸出(3, 1)

參數axis本質是一個含有2個元素的數組,當axis為整數n時,相當於[n,n]。2個元素分別指定2個矩陣參與運算的維度(需要加和的維度)。

c = K.batch_dot(K.ones(shape=(2, 3,10)),K.ones(shape=(2,3)),axes = [1,1]) #正確  print(c.shape ,c) #輸出(2, 10)

例如上面程式碼中,生成結果矩陣的計算方式如下:

(1)取第1個矩陣的0維(值為2),作為結果的0維。

(2)令第1個矩陣的1維(值為3)與第2個矩陣的1維(值為3)進行相乘並相加。

(3)取第1個矩陣的2為(值為10),作為結果的1維。

(4)忽略掉第2個矩陣的0維(值為2)。

按照該規則可以嘗試計算下面矩陣點積後的輸出形狀。

c = K.batch_dot(K.ones(shape=(2, 3,10,7)),K.ones(shape=(2,3,8,10)),axes = [2,3]) #正確  print(c.shape ,c)#輸出(2, 3, 7, 8)

需要注意的是,能夠進行K.batch_dot計算的兩個矩陣也是有要求的:在兩個矩陣的維度中,屬於axis前面的公共維度部分(例如維度2,3)需要完全相等,並且axis只能指定最後2個維度。

如果axis指定的維度不是最後兩個,則系統會按照默認的倒數第二個維度進行計算。例如:

c = K.batch_dot(K.ones(shape=(2, 1,3,4)),K.ones(shape=(2,1,3,5)),axes = [0,1]) #正確  print(c.shape)#輸出(2, 1, 4, 5)  c = K.batch_dot(K.ones(shape=(2, 1,3,4)),K.ones(shape=(2,1,3,5)),axes = [0,2]) #正確  print(c.shape)#輸出(2, 1, 4, 5)  c = K.batch_dot(K.ones(shape=(2, 1,3,4)),K.ones(shape=(2,1,3,5)),axes = [1,2]) #正確  print(c.shape)#輸出(2, 1, 4, 5)

以上這種寫法雖然也能夠運行。但是程式碼的可讀性極差。建議讀者開發時不要這麼去用。

4. K.dot函數

K.batch_dot函數沒有參數axis,只是單純的矩陣相乘。一般用於2維矩陣相乘。例如:

c = K.dot(K.ones(shape=(3,1)),K.ones(shape=(1,4)))#正確  print(c.shape) #輸出(3, 4)

如果是多維矩陣相乘,滿足最後兩個維度匹配,則也可以正確運算。只不過生成的矩陣形狀是兩個相乘矩陣的疊加。

c = K.dot(K.ones(shape=(2, 3,1,7)),K.ones(shape=(2,3,7,10)))#正確  print(c.shape )#輸出(2, 3, 1, 2, 3, 10)

使用K.dot函數進行多維矩陣相乘時,所生成的新矩陣形狀與我們常規理解的不同。這是使用該函數所需要注意的地方。