本教程是一个系列免费教程,争取每月更新2到4篇。
主要是基于图深度学习的入门内容。讲述最基本的基础知识,其中包括深度学习、数学、图神经网络等相关内容。该教程由代码医生工作室出版的全部书籍混编节选而成。偏重完整的知识体系和学习指南。在实践方面不会涉及太多基础内容 (实践和经验方面的内容,请参看原书)。
文章涉及使用到的框架以PyTorch和TensorFlow为主。默认读者已经掌握Python和TensorFlow基础。如有涉及到PyTorch的部分,会顺带介绍相关的入门使用。
本教程主要针对的人群:
本篇文章以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函数进行多维矩阵相乘时,所生成的新矩阵形状与我们常规理解的不同。这是使用该函数所需要注意的地方。