`#參考:https://blog.csdn.net/weixin_42052081/article/details/89108966
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from networkx import to_numpy_matrix
#首先使用numpy編寫有向圖的鄰接矩陣表征
A=np.matrix([[0,1,0,0],[0,0,1,1],[0,1,0,0],[1,0,1,0]],dtype=float)
#抽取特征,基於每個節點的索引為其生成兩個整數特征
X=np.matrix([[i,-i] for i in range(A.shape[0])],dtype=float)
print(X)
#應用傳播規則 :現在我們已經建立了一個圖,其鄰接矩陣為A,輸入特征的幾何為X
# 接下來我們來看一下應用傳播規則之后會發生什么
print(A*X)
#每個節點的表征(每一行)現在是其相鄰節點特征的和!
# 換句話說,圖卷積層將每個節點表示為其相鄰節點的聚合。
# 大家可以自己動手驗證這個計算過程。
# 請注意,在這種情況下,
# 如果存在從 v 到 n 的邊,則節點 n 是節點 v 的鄰居。
#問題
#以上的做法包含以下問題:
#1.節點的聚合表征不包含它自己的特征!該表征是相鄰節點的特征聚合,因此只有具有自環(self-loop)
# 的節點才會在該聚合中包含自己的特征 [1]。
#2 2,度大的節點在其特征表征中將具有較大的值,
# 度小的節點將具有較小的值。這可能會導致梯度消失或梯度爆炸 [1, 2],
# 也會影響隨機梯度下降算法(隨機梯度下降算法通常被用於訓練這類網絡,
# 且對每個輸入特征的規模(或值的范圍)都很敏感)。
#增加自環:為了解決第一個問題,可以在使用傳播規則之前,先將A與單位矩陣I相加實現
I=np.matrix(np.eye(A.shape[0]))
A_hat=A+I
print("A_hat is :",A_hat)
print(A_hat*X)
#對特征表征進行歸一化處理
#通過將鄰接矩陣A與度矩陣D的逆相乘,對其進行變換,從而通過節點的度對特征表征進行歸一化
#因此簡化后的傳播規則如下:
#F(X,A)=D^(-1)AX
# 首先計算出節點的度矩陣。注意:此處計算節點的度是用節點的入度,也可以根據自身的任務特點用出度,
# 在本文中,這個選擇是任意的。一般來說,您應該考慮節點之間的關系是如何與您的具體任務相關。
# 例如,您可以使用in-degree來進行度矩陣計算,前提是只有關於節點的in-neighbors的信息與預測其具體任務中的標簽相關。
# 相反,如果只有關於外部鄰居的信息是相關的,則可以使用out-degree。最后,如果節點的out-和in-鄰居都與您的預測相關,
# 那么您可以基於in-和out-度的組合來計算度矩陣。
# 正如我將在下一篇文章中討論的那樣,您還可以通過其他方法對表示進行歸一化,而不是使用逆矩陣乘法。
#計算度矩陣
D=np.array(np.sum(A,axis=0))[0]
D=np.matrix(np.diag(D))
print(D)
#變化之前
A = np.matrix([ [0, 1, 0, 0], [0, 0, 1, 1], [0, 1, 0, 0], [1, 0, 1, 0]], dtype=float)
#變換之后
print(D**-1 * A)
# 可以觀察到,鄰接矩陣中每一行的權重(值)都除以該行對應節點的度。我們接下來對變換后的鄰接矩陣應用傳播規則:
# 得到與相鄰節點的特征均值對應的節點表征。
# 這是因為(變換后)鄰接矩陣的權重對應於相鄰節點特征加權和的權重。大家可以自己動手驗證這個結果。
# print(D**-1 * A*X)
#整合:現在將自環和歸一化技巧結合起來,還將重新介紹之前為了簡化討論而省略的有關權重和激活函數的操作
#添加權重
# 這里的 D_hat 是 A_hat = A + I 對應的度矩陣,即具有強制自環的矩陣 A 的度矩陣。
D_hat=np.array(np.sum(A_hat,axis=0))[0]
D_hat=np.matrix(np.diag(D_hat))
print("D_hat is :",D_hat)
W = np.matrix([ [1, -1],
[-1, 1] ])
print("Adding W: ",D_hat**-1 * A_hat * X *W)
# 如果我們想要減小輸出特征表征的維度,我們可以減小權重矩陣 W 的規模:
W = np.matrix([ [1],
[-1] ])
print("After reducing the size of W: ",D_hat**-1 * A_hat * X * W)
# 添加激活函數: 本文選擇保持特征表征的維度,並應用 ReLU 激活函數。Relu函數的公式是,代碼為:
def relu(x):
return(abs(x)+x)/2
# 一個帶有鄰接矩陣、輸入特征、權重和激活函數的完整隱藏層如下:
W = np.matrix([ [1, -1],
[-1, 1] ])
print("After adding RELU :",relu(D_hat**-1 * A_hat * X * W))
#以下是在真實場景中的應用
# Zachary 空手道俱樂部 Zachary 空手道俱樂部是一個被廣泛使用的社交網絡,
# 其中的節點代表空手道俱樂部的成員,邊代表成員之間的相互關系。
# 當年,Zachary 在研究空手道俱樂部的時候,管理員和教員發生了沖突,導致俱樂部一分為二。
# 下圖顯示了該網絡的圖表征,其中的節點標注是根據節點屬於俱樂部的哪個部分而得到的,
# 「0」表示屬於Mr. Hi部分的中心節點,[32」表示屬於Officer陣營的中心節點,
# 參考https://networkx.github.io/documentation/stable/_modules/networkx/generators/social.html#karate_club_graph。
zkc = nx.karate_club_graph()
def plot_graph(G):
# G: a networkx G
# % matplotlib notebook
plt.figure()
pos = nx.spring_layout(G)
edges = G.edges()
nodelist1 = []
nodelist2 = []
for i in range(34):
if zkc.nodes[i]['club'] == 'Mr. Hi':
nodelist1.append(i)
else:
nodelist2.append(i)
nx.draw_networkx(G, pos, edges=edges);
nx.draw_networkx_nodes(G, pos, nodelist=nodelist1, node_size=300, node_color='r', alpha=0.8)
nx.draw_networkx_nodes(G, pos, nodelist=nodelist2, node_size=300, node_color='b', alpha=0.8)
nx.draw_networkx_edges(G, pos, edgelist=edges,alpha =0.4)
plt.show()
plot_graph(zkc)
#見圖1
# #構建GCN,
# 構建一個圖卷積網絡。我們並不會真正訓練該網絡,但是會對其進行簡單的隨機初始化,從而生成我們在本文開頭看到的特征表征。
# 我們將使用 networkx,它有一個可以很容易實現的 Zachary 空手道俱樂部的圖表征。然后,我們將計算 A_hat 和 D_hat 矩陣。
from networkx import to_numpy_matrix
zkc = nx.karate_club_graph()
order = sorted(list(zkc.nodes()))
A = to_numpy_matrix(zkc, nodelist=order) #鄰接矩陣
I = np.eye(zkc.number_of_nodes()) #單位矩陣
A_hat = A + I
D_hat = np.array(np.sum(A_hat, axis=0))[0] #帶自循環的度矩陣
D_hat = np.matrix(np.diag(D_hat))
#隨機初始化權重
# np.random.normal()函數說明
#偉大的高斯分布(Gaussian Distribution)的概率密度函數(probability density function):
# 對應於numpy中:numpy.random.normal(loc=0.0, scale=1.0, size=None)
# 參數的意義為:
# loc:float 此概率分布的均值(對應着整個分布的中心centre)
# scale:float 此概率分布的標准差(對應於分布的寬度,scale越大越矮胖,scale越小,越瘦高)
# size:int or tuple of ints 輸出的shape,默認為None,只輸出一個值
W_1 =np.random.normal( loc=0, scale=1, size=(zkc.number_of_nodes(), 4))
print("W_1 is :",W_1)
W_2 = np.random.normal(
loc=0, size=(W_1.shape[1], 2))
print("W_2 is :",W_2)
# 接着堆疊 GCN 層。這里只使用單位矩陣作為特征表征,即每個節點被表示為一個 one-hot 編碼的類別變量。
def gcn_layer(A_hat, D_hat, X, W):
return relu(D_hat ** -1 * A_hat * X * W)
#relu 為激活函數
H_1 = gcn_layer(A_hat, D_hat, I, W_1)
H_2 = gcn_layer(A_hat, D_hat, H_1, W_2)
output = H_2
print(output)
# 經過多次隨機生成W_1和W_2權重矩陣,得到上圖H_2,
# 但是我發現經過激活函數relu之后,x軸與y軸有很多零值,
# 導致可視化效果很差,可視化效果如下圖,初步分析,可能的原因是權重矩陣是隨機生成的,
# 沒有用后面的具體任務去更新權重矩陣,,畫圖代碼及圖片如下:
# plt.scatter() 散點圖 https://blog.csdn.net/m0_37393514/article/details/81298503
for i in range (34):
if zkc.nodes[i]['club'] == 'Mr. Hi':
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,label=str(i),color = 'b',alpha=0.5,s = 250)
plt.text(np.array(output)[i,0],np.array(output)[i,1] ,i, horizontalalignment='center',verticalalignment='center', fontdict={'color':'black'})
# 為每個點添加標簽,一些形如(x軸,y軸,標簽)的元組,水平及垂直位置,背景顏色
else:
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,label = 'i',color = 'r',alpha=0.5,s = 250)
plt.text(np.array(output)[i,0],np.array(output)[i,1] ,i, horizontalalignment='center',verticalalignment='center', fontdict={'color':'black'})
# plt.scatter(np.array(output)[:,0],np.array(output)[:,1],label = 0:33)
print("The result of GCN is :")
plt.show()
# 嘗試去掉激活函數relu,重新運行一遍,發現效果反而更好
def gcn_layer(A_hat, D_hat, X, W):
return D_hat**-1 * A_hat * X * W
H_1 = gcn_layer(A_hat, D_hat, I, W_1)
H_2 = gcn_layer(A_hat, D_hat, H_1, W_2)
output = H_2
print("去掉relu :",output)
feature_representations = {
node: np.array(output)[node]
for node in zkc.nodes()}
print("feature_representations is :",feature_representations)
# import matplotlib.pyplot as plt
# %matplotlib notebook
for i in range (34):
if zkc.nodes[i]['club'] == 'Mr. Hi':
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,label=str(i),color = 'b',alpha=0.5,s = 250)
plt.text(np.array(output)[i,0],np.array(output)[i,1] ,i, horizontalalignment='center',verticalalignment='center', fontdict={'color':'black'})
# 為每個點添加標簽,一些形如(x軸,y軸,標簽)的元組,水平及垂直位置,背景顏色
else:
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,label = 'i',color = 'r',alpha=0.5,s = 250)
plt.text(np.array(output)[i,0],np.array(output)[i,1] ,i, horizontalalignment='center',verticalalignment='center', fontdict={'color':'black'})
# plt.scatter(np.array(output)[:,0],np.array(output)[:,1],label = 0:33)
plt.show()
# 你看,這樣的特征表征可以很好地將 Zachary 空手道俱樂部的兩個社區划分開來。
# 至此,我們甚至都沒有開始訓練模型!我們應該注意到,在該示例中由於 ReLU 函數的作用,
# 在 x 軸或 y 軸上隨機初始化的權重很可能為 0。
# 結語 本文中對圖卷積網絡進行了高水平的的介紹,並說明了 GCN 中每一層節點的特征表征是如何基於其相鄰節點的聚合構建的。
# 讀者可以從中了解到如何使用 numpy 構建這些網絡,以及它們的強大:
# 即使是隨機初始化的 GCN 也可以將 Zachary 空手道俱樂部網絡中的社區分離開來。
# 在下一篇文章中,我將更詳細地介紹技術細節,並展示如何使用半監督學習實現和訓練最近發布的GCN。
# 你可以在本人csdn找到下一篇文章。
`

有激活函數的結果

去掉激活函數的結果

