圖與圖學習(中)

在上篇中,我們簡單學習了圖論的基本概念,圖的表示和存儲方式,同構圖和異構圖的分類,以及幾個基礎的圖論算法。
在接下來的前置教程下篇中,我們將會學習圖機器學習。

本案例將包含以下內容:

一. 圖機器學習(GML:Graph Machine Learning)

首先我們導入需要的包

import numpy as np
import random
import networkx as nx
from IPython.display import Image
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score
from sklearn.metrics import roc_curve
from sklearn.metrics import roc_auc_score

一. 圖機器學習

圖學習的主要任務

圖學習中包含三種主要的任務:

  • 鏈接預測(Link prediction)
  • 節點標記預測(Node labeling)
  • 圖嵌入(Graph Embedding)

鏈接預測(Link prediction)

在鏈接預測中,給定圖G,我們的目標是預測新邊。例如,當圖未被完全觀察時,或者當新客戶加入平臺(例如,新的LinkedIn用戶)時,預測未來關係或缺失邊是很有用的。
知乎回答:如何理解鏈接預測(link prediction)

新LinkedIn用戶的鏈接預測只是給出它可能認識的人的建議。

在鏈路預測中,我們只是嘗試在節點對之間建立相似性度量,並鏈接最相似的節點。現在的問題是識別和計算正確的相似性分數!

爲了說明圖中不同鏈路的相似性差異,讓我們通過下面這個圖來解釋:

N(i)是節點i的一組鄰居。在上圖中,節點ij的鄰居可以表示爲:

i的鄰居:

1. 相似度分數

我們可以根據它們的鄰居爲這兩個節點建立幾個相似度分數。

  • 公共鄰居:S(i,j) = \mid N(i) \cap N(j) \mid,即公共鄰居的數量。在此示例中,分數將爲2,因爲它們僅共享2個公共鄰居。
  • Jaccard係數:S(i,j) = \frac { \mid N(i) \cap N(j) \mid } { \mid N(i) \cup N(j) \mid },標準化的共同鄰居版本。

交集是共同的鄰居,並集是:

因此,Jaccard係數由粉紅色與黃色的比率計算出:

值是\frac {1} {6}

  • Adamic-Adar指數:S(i,j) = \sum_{k \in N(i)\cap N(j) } \frac {1} {\log \mid N(k) \mid}。 對於節點i和j的每個公共鄰居(common neighbor),我們將1除以該節點的鄰居總數。這個概念是,當預測兩個節點之間的連接時,與少量節點之間共享的元素相比,具有非常大的鄰域的公共元素不太重要。
  • 優先依附(Preferential attachment): S(i,j) = \mid N(i) \mid * \mid N(j) \mid
  • 當社區信息可用時,我們也可以在社區信息中使用它們。
2. 性能指標(Performance metrics)

我們如何進行鏈接預測的評估?我們必須隱藏節點對的子集,並根據上面定義的規則預測它們的鏈接。這相當於監督學習中的train/test的劃分。
然後,我們評估密集圖的正確預測的比例,或者使用稀疏圖的標準曲線下的面積(AUC)。

這裏繼續用空手道俱樂部圖來舉例:

# 我們導入空手道俱樂部圖
n=34
m = 78
G_karate = nx.karate_club_graph()

pos = nx.spring_layout(G_karate)
nx.draw(G_karate, cmap = plt.get_cmap('rainbow'), with_labels=True, pos=pos)
# 我們首先把有關圖的信息打印出來:
n = G_karate.number_of_nodes()
m = G_karate.number_of_edges()
print("Number of nodes : %d" % n)
print("Number of edges : %d" % m)
print("Number of connected components : %d" % nx.number_connected_components(G_karate))
Number of nodes : 34
Number of edges : 78
Number of connected components : 1
plt.figure(figsize=(12,8))
nx.draw(G_karate, pos=pos)
plt.gca().collections[0].set_edgecolor("#000000")
# 現在,讓我們刪除一些連接,例如25%的邊:
# Take a random sample of edges
edge_subset = random.sample(G_karate.edges(), int(0.25 * G_karate.number_of_edges()))

# remove some edges
G_karate_train = G_karate.copy()
G_karate_train.remove_edges_from(edge_subset)
# 繪製部分觀察到的圖,可以對比上圖發現,去掉了一些邊
plt.figure(figsize=(12,8))
nx.draw(G_karate_train, pos=pos)
# 你可以打印我們刪除的邊數和剩餘邊數:
edge_subset_size = len(list(edge_subset))
print("Deleted : ", str(edge_subset_size))
print("Remaining : ", str((m - edge_subset_size)))
Deleted :  19
Remaining :  59
計算Jaccard Coefficient
# 我們可以先使用Jaccard係數進行預測:
# Make prediction using Jaccard Coefficient
pred_jaccard = list(nx.jaccard_coefficient(G_karate_train))
score_jaccard, label_jaccard = zip(*[(s, (u,v) in edge_subset) for (u,v,s) in pred_jaccard])
# 打印前10組結果
print(pred_jaccard[0:10])
# 預測結果如下,其中第一個是節點,第二個是節點,最後一個是Jaccard分數(用來表示兩個節點之間邊預測的概率)
[(0, 2, 0.125), (0, 3, 0.15384615384615385), (0, 4, 0.1), (0, 7, 0.08333333333333333), (0, 9, 0.0), (0, 13, 0.09090909090909091), (0, 14, 0.0), (0, 15, 0.0), (0, 16, 0.1), (0, 18, 0.0)]
# 然後我們可以使用ROC-AUC標準來比較不同模型的性能,因爲我們既有真實的邊(label),也有預測邊的概率(score)
# Compute the ROC AUC Score
# 其中,FPR是False Positive Rate, TPR是True Positive Rate
fpr_jaccard, tpr_jaccard, _ = roc_curve(label_jaccard, score_jaccard)
auc_jaccard = roc_auc_score(label_jaccard, score_jaccard)
print(auc_jaccard)
0.6151792524790236
計算Adamic-Adar
# 我們現在計算Adamic-Adar指數和對應的ROC-AUC分數
# Prediction using Adamic Adar 
pred_adamic = list(nx.adamic_adar_index(G_karate_train))
score_adamic, label_adamic = zip(*[(s, (u,v) in edge_subset) for (u,v,s) in pred_adamic])
print(pred_adamic[0:10])
# Compute the ROC AUC Score
fpr_adamic, tpr_adamic, _ = roc_curve(label_adamic, score_adamic)
auc_adamic = roc_auc_score(label_adamic, score_adamic)
print(auc_adamic)
[(0, 2, 1.9235933878519513), (0, 3, 1.9235933878519513), (0, 4, 0.9102392266268373), (0, 7, 0.48089834696298783), (0, 9, 0), (0, 13, 0.48089834696298783), (0, 14, 0), (0, 15, 0), (0, 16, 0.7213475204444817), (0, 18, 0)]
0.7230576441102756
計算Preferential Attachment
# 同樣,我們可以計算Preferential Attachment得分和對應的ROC-AUC分數
# Compute the Preferential Attachment
pred_pref = list(nx.preferential_attachment(G_karate_train))
score_pref, label_pref = zip(*[(s, (u,v) in edge_subset) for (u,v,s) in pred_pref])
print(pred_pref[0:10])
fpr_pref, tpr_pref, _ = roc_curve(label_pref, score_pref)
auc_pref = roc_auc_score(label_pref, score_pref)
print(auc_pref)
[(0, 2, 80), (0, 3, 50), (0, 4, 10), (0, 7, 30), (0, 9, 20), (0, 13, 20), (0, 14, 10), (0, 15, 10), (0, 16, 10), (0, 18, 20)]
0.7427263811703171
繪製ROC-AUC來評價預測的效果
plt.figure(figsize=(12, 8))
plt.plot(fpr_jaccard, tpr_jaccard, label='Jaccard Coefficient - AUC %.2f' % auc_jaccard, linewidth=4)
plt.plot(fpr_adamic, tpr_adamic, label='Adamic-Adar - AUC %.2f' % auc_adamic, linewidth=4)
plt.plot(fpr_pref, tpr_pref, label='Preferential Attachment - AUC %.2f' % auc_pref, linewidth=4)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title("ROC AUC Curve")
plt.legend(loc='lower right')
plt.show() 

關於更多性能評價介紹,可以閱讀博客模型評估指標AUC和ROC

節點標記預測(Node labeling)

給定一個未標記某些節點的圖,我們希望對這些節點的標籤進行預測。這在某種意義上是一種半監督的學習問題。

處理這些問題的一種常見方法是假設圖上有一定的平滑度。平滑度假設指出通過數據上的高密度區域的路徑連接的點可能具有相似的標籤。這是標籤傳播算法背後的主要假設。

標籤傳播算法(Label Propagation Algorithm,LPA)是一種快速算法,僅使用網絡結構作爲指導來發現圖中的社區,而無需任何預定義的目標函數或關於社區的先驗信息。

單個標籤在密集連接的節點組中迅速佔據主導地位,但是在穿過稀疏連接區域時會遇到問題。

半監督標籤傳播算法是如何工作?

首先,我們有一些數據:x_1, ..., x_l, x_{l+1}, ..., x_n \in R^p,,以及前l個點的標籤:y_1, ..., y_l \in 1...C.

我們定義初始標籤矩陣Y \in R^{n \times C},如果x_i具有標籤y_i=jY_{ij} = 1,否則爲0。

該算法將生成預測矩陣F \in R^{n \times C},我們將在下面詳述。然後,我們通過查找最可能的標籤來預測節點的標籤:

\hat{Y_i} = argmax_j F_{i,j}

預測矩陣F是什麼?

預測矩陣是矩陣F^{\star},其最小化平滑度和準確度。因此,我們的結果在平滑性和準確性之間進行權衡。

問題的描述非常複雜,所以我將不會詳細介紹。但是,解決方案是:

F^{\star} = ( (1-\alpha)I + L_{sym})^{-1} Y

其中:

  • 參數\alpha = \frac {1} {1+\mu}
  • Y是給定的標籤
  • L_{sym}是圖的歸一化拉普拉斯矩陣(Laplacian matrix)

如果您想進一步瞭解這個主題,請關注圖函數的平滑度和流形正則化的概念。

接下來我們用python來實現節點標籤的預測。
爲了給我們使用到的標籤添加更多的特徵,我們需要使用來自Facebook的真實數據。你可以再這裏下載,然後放到facebook路徑下。

G_fb = nx.read_edgelist("facebook/414.edges")
n = G_fb.number_of_nodes()
m = G_fb.number_of_edges()

print("Number of nodes: %d" % n)
print("Number of edges: %d" % m)
print("Number of connected components: %d" % nx.number_connected_components(G_fb))
Number of nodes: 150
Number of edges: 1693
Number of connected components: 2

我們使用到的414號數據,圖中有150個節點和1693個邊。其中,有2個連通分支構成,這意味着這個圖中的兩塊是分開來的。

# 我們把圖數據顯示出來:
mapping=dict(zip(G_fb.nodes(), range(n)))
nx.relabel_nodes(G_fb, mapping, copy=False)
pos = nx.spring_layout(G_fb)

plt.figure(figsize=(12,8))
nx.draw(G_fb, node_size=200, pos=pos)
plt.gca().collections[0].set_edgecolor("#000000")

這個圖中包含了不同的特徵。在這些可用的特徵中,我們將利用第34組特徵來進行實驗。這組特徵描述了這個某個facebook用戶(節點)是否是一所學校的學生。
這裏我們兩種標籤(1是紅色的,代表改用戶是這所學校的學生,反正,0則用藍色表示)

with open('facebook/414.featnames') as f:
    for i, l in enumerate(f):
        pass

n_feat = i+1

features = np.zeros((n, n_feat))
f = open('facebook/414.feat', 'r')

for line in f:
    if line.split()[0] in mapping:
        node_id = mapping[line.split()[0]]
        features[node_id, :] = list(map(int, line.split()[1:]))

features = 2*features-1
feat_id = 43
labels = features[:, feat_id]

plt.figure(figsize=(12,8))
nx.draw(G_fb, cmap = plt.get_cmap('bwr'), nodelist=range(n), node_color = labels, node_size=200, pos=pos)
plt.gca().collections[0].set_edgecolor("#000000")
plt.show()

這個所選擇的特徵,在圖中相對平滑,因此擁有較好的學習傳播性能。

爲了闡述節點標籤預測是如何進行的,我們首先要刪掉一些節點的標籤,作爲要預測的對象。這裏我們只保留了30%的節點標籤:

random.seed(5)
proportion_nodes = 0.3
labeled_nodes = random.sample(G_fb.nodes(), int(proportion_nodes * G_fb.number_of_nodes()))

known_labels = np.zeros(n)
known_labels[labeled_nodes] = labels[labeled_nodes]

plt.figure(figsize=(12,8))
nx.draw(G_fb, cmap = plt.get_cmap('bwr'), nodelist=range(n), node_color = known_labels, node_size=200, pos=pos)
plt.gca().collections[0].set_edgecolor("#000000") # set node border color to black
plt.show()

然後,我們就可以進行標籤的傳播預測:

alpha = 0.7
L_sym = nx.normalized_laplacian_matrix(G_fb)

Y = np.zeros((n,2))
Y[known_labels==-1, 0] = 1
Y[known_labels==1, 1] = 1
I = np.identity(n)

# Create the F-pred matrix
F_pred = np.linalg.inv(I*(1-alpha) + L_sym) * Y
# Identify the prediction as the argmax
pred = np.array(np.argmax(F_pred, axis=1)*2-1).flatten()
# Compute the accuracy score
succ_rate = accuracy_score(labels, pred)
# 我們把結果打印出來
plt.figure(figsize=(18, 6))
f, axarr = plt.subplots(1, 2, num=1)

# Plot true values
plt.sca(axarr[0])
nx.draw(G_fb, cmap = plt.get_cmap('bwr'), nodelist=range(n), node_color = labels, node_size=200, pos=pos)
axarr[0].set_title('True labels', size=16)
plt.gca().collections[0].set_edgecolor("#000000")

# Plot predicted values
plt.sca(axarr[1])
nx.draw(G_fb, cmap = plt.get_cmap('bwr'), nodelist=range(n), node_color = pred, node_size=200, pos=pos)
axarr[1].set_title('Predicted labels (Success Rate: %.2f)' % succ_rate, size=16)
plt.gca().collections[0].set_edgecolor("#000000")

這就是我們得到的預測結果,如右圖所示。

圖嵌入(Graph Embedding)

在處理NLP或計算機視覺問題時,我們習慣在深度神經網絡中對圖像或文本進行嵌入(embedding)。到目前爲止,我們所看到的圖的一個侷限性是沒有向量特徵。但是,我們可以學習圖的嵌入!圖有不同幾個級別的嵌入:


小結

我們現在已經覆蓋了圖的介紹,圖的主要類型,不同的圖算法,在Python中使用Networkx來實現它們,以及用於節點標記,鏈接預測和圖嵌入的圖學習技術。

毋庸置疑,這只是冰山一角。圖論不斷擴展,我認爲列出一些資源以進一步學習是有用的:


參考材料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章