深度之眼Paper带读笔记GNN.02.LINE

前言

本课程来自深度之眼,部分截图来自课程视频。

文章标题:LINE: Large-scale Information Network Embedding
LINE:大规信息网络特征表示
作者:Jian Tang.etc
单位:MSRA. Microsoft Research Asia
发表会议及时间:WWW 2015
公式输入请参考:在线Latex公式

论文结构

Abstract:介绍背景及提出LINE模型,维护局部和全局网络结构
Introduction:介绍图的重要性、与以前的方法如MDS、ISOMAP、Laplacian eigenmap做对比,强调scalability;DeepWalk缺乏清晰的目标函数
Related Work:传统基于图的MDS、ISOMAP算法;Graph Factorization算法;DeepWalk算法等
Edge Sampling alias sampling:技术,时间复杂度分析,低度点和新点的处理
Model Optimization:模型的优化,负采样技术、SGD算法
LINE算法:1st Proximity、2nd Proximity以及两者的组合
Effectiveness:实验探究模型有效性:baselines选择,参数设定;多类数据集的多个任务:word analogy、document classification、multi-label classification
Experiments:可视化、性能和网络稀疏度、参数实验以及规模性
Discussion:总结提出了一种根据网络结构(一二阶相似度)的目标函数网络表征学习方法,强调了规模性
在这里插入图片描述

研究背景

几个缺点:数据稀疏性、高维、没有语义信息
传统的做法是对邻接矩阵进行分解,降维后获得特征向量
如何有效并且高效地表示图这一复杂数据结构
Given a network/graph G = ( V , E , W ) G=(V,E,W) G=(V,E,W), where V V V is the set of nodes, E E E is the set of edges between the nodes, and W W W is the set of weights of the edges, the goal of node embedding is to represent each node i i i with a vector u → i ∈ R d \overrightarrow u_i\in R^d u iRd, which preserves the structure of networks.
在这里插入图片描述

应用

网络表征后可以做很多下游任务(down-streaming tasks):如节点分类、边预测、推荐通过文本构成网络后进行单词表征
E.g, Facebook social network->user representations (features)->friend recommendation
在这里插入图片描述
在这里插入图片描述

基本概念

一阶相似度:节点之间的局部相似度。但是,许多节点之间的链接是无法观察到的-sparsity.etc。1阶相似度不足以表征整个网络的结构
另外,一些动态图,图还在演化过程中,边还未生成,那么一阶相似度也不能很好描述结构。
二阶相似度:
节点邻域结构之间的接近度
“The degree of overlap of two people’s friendship networks correlates with the strength of ties between them”–Mark Granovetter
“You shall know a word by the company it keeps”–John Rupert Firth

基础知识补充

F1指标
真正例(True Positive,TP):真实类别为正例,预测类别为正例
假正例(False Positive,FP):真实类别为负例,预测类别为正例
假负例(False Negative,FN):真实类别为正例,预测类别为负例
真负例(True Negative,TN):真实类别为负例,预测类别为负例

真实类别预测为正例预测为负例
正例TPFN
负例FPTN

准确率(Accuracy,Acc):
A c c = T P + T N T O T A L Acc=\cfrac{TP+TN}{TOTAL} Acc=TOTALTP+TN
精确率,又称查准率(Precision,P):
P = T P T P + F P P=\cfrac{TP}{TP+FP} P=TP+FPTP
召回率,又称查全率(Recall,R):
R = T P T P + F N R=\cfrac{TP}{TP+FN} R=TP+FNTP
F1值:
F 1 = 2 × P × R P + R F1=\cfrac{2\times P\times R}{P+R} F1=P+R2×P×R
Macro-Fl vs Micro-F1
Macro-F1:分布计算每个类别(按ground truth的分类进行分别计算)的F1,然后做平均
Micro-F1:通过先计算总体的TP,FN和FP的数量,再计算F1

多类数据集

即网络类型有多个,任务类型也有多个。
多类网络:文本网络、社交网络、引用网络
多个任务单词推理(Word Analogy)、文本分类(Document Classification)、节点分类
(node classification)、可视化(Visualization)
在这里插入图片描述
从平均degree可以知道YOUTUBE最稀疏。

研究意义

适用于任意类型的网络,有向无向、有权无权(思考DeepWalk、node2vec)。
清晰的优化目标函数,维护1st和2nd近似度。
规模化:SGD优化方法,百万级点和十亿万级的边可以在单机上几小时训练完。
是WWW2015目前引用量最高的文章。
与DeepWalk[2014]、node2vec[2016]文章一样,早期网络学习的代表性工作,经典baselines。
启发了大量基于网络结构(如triangle等)来做网络表征学习的工作。
在这里插入图片描述

泛读

摘要

1.提出的LINE算法适用于任意类型的信息网络:无向/有向、有权/无权。
2.LINE算法的目标函数同时保留了局部和全局的网络结构。
3.讨论算法在多个领域的网络上如单词网络、社交网络和引用网络上都验证了有效性。
4.强调了LINE算法的规模性,单机上几个小时之内可训练百万级网络。

论文标题

  1. Introduction
  2. Related Work
  3. Problem Definition
  4. LINE:Large-scale Information Network Embedding
    4.1Model Description
    4.2 Model Optimization
    4.3 Discussion
  5. Experiments
    5.1 Experiments setup
    5.2 Quantitative Results
    5.3 Network Layouts
    5.4 Performance w.r.t.Network Sparsity
    5.5 Parameter Sensitivity
    5.6 Scalability
  6. Conclusion

算法的比较

1.提出的顺序DeepWalk 2014,LINE 2015,Node2Vec 2016。
2.Node2Vec设置p=q=1时,等价于DeepWalk。
3.DeepWalk无太多可调的超参数(word2vec等相关参数不考虑),LINE可以选择1st order、2nd order或者组合,Node2Vec可调p、q。
4.DeepWalk和Node2Vec是基于随机游走启发的算法,LINE是基于网络结构启发的算法。

算法Neighbor ExpansionProximityOptimizationValidation Data
LINEBFS1st or 2nd负采样No
DeepWalkRandom2nd层次softmaxNo
Node2VecBFS+DFS1st负采样Yes

LINE算法详解

在这里插入图片描述

KL散度

性质:非负;当且仅当p=q为0;非对称
KL散度是非负的。
当且仅当P和Q在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是“几乎处处”相同的时候,KL散度为0。
KL散度不是真的距离,它不是对称的: D K L ( P ∣ ∣ Q ) ≠ D K L ( Q ∣ ∣ P ) D_{KL}(P||Q)\neq D_{KL}(Q||P) DKL(PQ)=DKL(QP)
这个指标在李宏毅的课里面有讲过
假设原概率分布为 P ( x ) P(x) P(x),近似概率分布为 Q ( x ) Q(x) Q(x),则可使用KL散度衡量这两个分布的差异:
D K L ( P ∣ ∣ Q ) = E x ∼ P [ log P ( x ) Q ( x ) ] = E x ∼ P [ log P ( x ) − log Q ( x ) ] D_{KL}(P||Q)=E_{x\sim P}[\text{log}\cfrac{P(x)}{Q(x)}]=E_{x\sim P}[\text{log}{P(x)}-\text{log}{Q(x)}] DKL(PQ)=ExP[logQ(x)P(x)]=ExP[logP(x)logQ(x)]
如果 x x x是离散型变量,上式还可以写成如下形式:
D K L ( P ∣ ∣ Q ) = ∑ i = 1 N P ( x i ) log P ( x i ) Q ( x i ) = ∑ i = 1 N P ( x i ) [ log P ( x i ) − log Q ( x i ) ] D_{KL}(P||Q)=\sum_{i=1}^NP(x_i)\text{log}\cfrac{P(x_i)}{Q(x_i)}=\sum_{i=1}^NP(x_i)[\text{log}{P(x_i)}-\text{log}{Q(x_i)}] DKL(PQ)=i=1NP(xi)logQ(xi)P(xi)=i=1NP(xi)[logP(xi)logQ(xi)]
实例

x012
P ( x ) P(x) P(x)0.360.480.161
Q ( x ) Q(x) Q(x)0.3330.3330.3331

D K L ( P ∣ ∣ Q ) = ∑ x ∈ X P ( x i ) ln P ( x i ) Q ( x i ) = 0.36 ln 0.36 0.333 + 0.48 ln 0.48 0.333 + 0.16 ln 0.16 0.333 = 0.0852996 \begin{aligned}D_{KL}(P||Q)&=\sum_{x\in X}P(x_i)\text{ln}\cfrac{P(x_i)}{Q(x_i)}\\ &=0.36\text{ln}\cfrac{0.36}{0.333}+0.48\text{ln}\cfrac{0.48}{0.333}+0.16\text{ln}\cfrac{0.16}{0.333}\\ &=0.0852996\end{aligned} DKL(PQ)=xXP(xi)lnQ(xi)P(xi)=0.36ln0.3330.36+0.48ln0.3330.48+0.16ln0.3330.16=0.0852996
D K L ( Q ∣ ∣ P ) = ∑ x ∈ X Q ( x i ) ln Q ( x i ) P ( x i ) = 0.333 ln 0.333 0.36 + 0.333 ln 0.333 0.48 + 0.333 ln 0.333 0.16 = 0.097455 \begin{aligned}D_{KL}(Q||P)&=\sum_{x\in X}Q(x_i)\text{ln}\cfrac{Q(x_i)}{P(x_i)}\\ &=0.333\text{ln}\cfrac{0.333}{0.36}+0.333\text{ln}\cfrac{0.333}{0.48}+0.333\text{ln}\cfrac{0.333}{0.16}\\ &=0.097455\end{aligned} DKL(QP)=xXQ(xi)lnP(xi)Q(xi)=0.333ln0.360.333+0.333ln0.480.333+0.333ln0.160.333=0.097455

交叉熵

交叉熵(cross-entropy)和KL散度联系很密切。同样地,交叉熵也可以用来衡量两个分布的差异
非负
和KL散度相同,交叉熵也不具备对称性
对同一个分布求交叉熵等于对其求熵。
H ( P , Q ) = − E x ∼ P log Q ( x ) H(P,Q)=-E_{x\sim P}\text{log}Q(x) H(P,Q)=ExPlogQ(x)
同样,离散变量的时候:
H ( P , Q ) = − ∑ i = 1 N P ( x i ) log Q ( x i ) H(P,Q)=-\sum_{i=1}^NP(x_i)\text{log}Q(x_i) H(P,Q)=i=1NP(xi)logQ(xi)
有了这个定义,那么KL散度公式变成:
D K L ( P ∣ ∣ Q ) = ∑ i = 1 N P ( x i ) [ log P ( x i ) − log Q ( x i ) ] = ∑ i = 1 N P ( x i ) log P ( x i ) − ∑ i = 1 N P ( x i ) log Q ( x i ) = − [ − ∑ i = 1 N P ( x i ) log P ( x i ) ] + [ − ∑ i = 1 N P ( x i ) log Q ( x i ) ] = − H ( P ) + H ( P , Q ) \begin{aligned}D_{KL}(P||Q)&=\sum_{i=1}^NP(x_i)[\text{log}{P(x_i)}-\text{log}{Q(x_i)}]\\ &=\sum_{i=1}^NP(x_i)\text{log}{P(x_i)}-\sum_{i=1}^NP(x_i)\text{log}{Q(x_i)}\\ &=-[-\sum_{i=1}^NP(x_i)\text{log}{P(x_i)}]+[-\sum_{i=1}^NP(x_i)\text{log}{Q(x_i)}]\\ &=-H(P)+H(P,Q)\end{aligned} DKL(PQ)=i=1NP(xi)[logP(xi)logQ(xi)]=i=1NP(xi)logP(xi)i=1NP(xi)logQ(xi)=[i=1NP(xi)logP(xi)]+[i=1NP(xi)logQ(xi)]=H(P)+H(P,Q)
在很多时候上式中的 H ( P ) H(P) H(P)是常数,所以以KL散度作为优化目标,通常可以等价于优化交叉熵。
计算实例:
P 1 = [ 1 0 0 0 0 ] P_1=[1\quad0\quad0\quad0\quad0] P1=[10000]
Q 1 = [ 0.4 0.3 0.05 0.05 0.2 ] Q_1=[0.4\quad0.3\quad0.05\quad0.05\quad0.2] Q1=[0.40.30.050.050.2]
Q 2 = [ 0.98 0.01 0 0 0.01 ] Q_2=[0.98\quad0.01\quad0\quad0\quad0.01] Q2=[0.980.01000.01]
H ( P 1 , Q 1 ) = − ∑ i P 1 ( i ) log Q 1 ( i ) = − ( 1 log 0.4 + 0 log 0.3 + 0 log 0.05 + 0 log 0.05 + 0 log 0.2 ) = − log 0.4 ≈ 0.916 \begin{aligned}H(P_1,Q_1)&=-\sum_iP_1(i)\text{log}{Q_1(i)}\\ &=-(1\text{log}0.4+0\text{log}0.3+0\text{log}0.05+0\text{log}0.05+0\text{log}0.2)\\ &=-\text{log}0.4\\ &\approx0.916\end{aligned} H(P1,Q1)=iP1(i)logQ1(i)=(1log0.4+0log0.3+0log0.05+0log0.05+0log0.2)=log0.40.916
H ( P 1 , Q 2 ) = − ∑ i P 1 ( i ) log Q 2 ( i ) = − ( 1 log 0.98 + 0 log 0.01 + 0 log 0. + 0 log 0 + 0 log 0.01 ) = − log 0.98 ≈ 0.02 \begin{aligned}H(P_1,Q_2)&=-\sum_iP_1(i)\text{log}{Q_2(i)}\\ &=-(1\text{log}0.98+0\text{log}0.01+0\text{log}0.+0\text{log}0+0\text{log}0.01)\\ &=-\text{log}0.98\\ &\approx0.02\end{aligned} H(P1,Q2)=iP1(i)logQ2(i)=(1log0.98+0log0.01+0log0.+0log0+0log0.01)=log0.980.02
P 1 P_1 P1看做是标签,我们希望模型预测效果是第一维概率越大越好。

细节一:1阶相似度推导

一阶相似度的意思就是两个节点互为邻居,那么他们二者应该有相似的embedding,
先算两个点之间相互连接的联合概率;
再算ground truth中两个点互为邻居同时出现概率;
最终目标是最小化联合概率和ground truth概率之间的KL散度。

1阶相似度:直接邻居,只能算无向图。
结合KL散度的推导,先把原文的公式搬过来:
模型计算公式(两个点之间相互连接的概率)
p 1 ( v i , v j ) = 1 1 + exp ( − u ⃗ i T ⋅ u ⃗ j ) (1) p_1(v_i,v_j)=\cfrac{1}{1+\text{exp}(-\vec{u}_i^T\cdot\vec{u}_j )}\tag1 p1(vi,vj)=1+exp(u iTu j)1(1)
其中 u u u是顶点 v v v的embedding表示
公式1表示图中两个节点相邻,或者理解为同时出现的联合概率

对于ground truth的概率( w i j w_{ij} wij是两个点之间是否相邻, W W W是所有点之间相邻的总数,分母是针对图中所有的点):
p ^ 1 ( v i , v j ) = w i j W , where  W = ∑ i , j ∈ E w i j (2) \hat p_1(v_i,v_j)=\cfrac{w_{ij}}{W}\text{, where }W=\sum_{i,j\in E}w_{ij}\tag2 p^1(vi,vj)=Wwij, where W=i,jEwij(2)
计算二者的KL散度,并根据公式展开,第四个等号把上面的公式1和2代入3:
O 1 = K L ( p ^ 1 ( v i , v j ) , p 1 ( v i , v j ) ) = ∑ i , j ∈ E p ^ 1 ( v i , v j ) [ log p ^ 1 ( v i , v j ) − log p 1 ( v i , v j ) ] = ∑ i , j ∈ E p ^ 1 ( v i , v j ) log p ^ 1 ( v i , v j ) − ∑ i , j ∈ E p ^ 1 ( v i , v j ) log p 1 ( v i , v j ) = ∑ i , j ∈ E w i j W log w i j W − ∑ i , j ∈ E w i j W log p 1 ( v i , v j ) (3) \begin{aligned}O_1&=KL(\hat p_1(v_i,v_j), p_1(v_i,v_j))\\ &=\sum_{i,j\in E}\hat p_1(v_i,v_j)[\text{log}\hat p_1(v_i,v_j)-\text{log}p_1(v_i,v_j)]\\ &=\sum_{i,j\in E}\hat p_1(v_i,v_j)\text{log}\hat p_1(v_i,v_j)-\sum_{i,j\in E}\hat p_1(v_i,v_j)\text{log}p_1(v_i,v_j)\\ &=\sum_{i,j\in E}\cfrac{w_{ij}}{W}\text{log}\cfrac{w_{ij}}{W}-\sum_{i,j\in E}\cfrac{w_{ij}}{W}\text{log}p_1(v_i,v_j)\end{aligned}\tag3 O1=KL(p^1(vi,vj),p1(vi,vj))=i,jEp^1(vi,vj)[logp^1(vi,vj)logp1(vi,vj)]=i,jEp^1(vi,vj)logp^1(vi,vj)i,jEp^1(vi,vj)logp1(vi,vj)=i,jEWwijlogWwiji,jEWwijlogp1(vi,vj)(3)
当网络固定的时候 w i j , W w_{ij},W wij,W都是固定的,是常数
O 1 = − ∑ i , j ∈ E w i j log p 1 ( v i , v j ) O_1=-\sum_{i,j\in E}w_{ij}\text{log}p_1(v_i,v_j) O1=i,jEwijlogp1(vi,vj)
实际上,二者的KL散度就是交叉熵的公式,注意上式中 w i j w_{ij} wij不能省略因为不同顶点 w i j w_{ij} wij不一样,而 W W W作为总的权重是不变的。

细节二:2阶相似度推导

二阶相似度则关注两个节点是否有共同的邻居,例如有两个好基友通常有相似的爱好。
先算顶点i周围出现邻居j的条件概率;
再算ground truth中顶点i周围出现邻居j的条件概率;
最终目标是最小化顶点i周围出现邻居j的条件概率和ground truth的条件概率之间的KL散度。
——————
实际上你和张三是好朋友,邻居关系,他是你的好朋友的概率是p’
然后模型训练的时候把张三盖起来,去算张三是你的好朋友的概率:p(张三|你)
然后模型要更新参数使得p(张三|你)和p’越接近越好
这个越接近越好是用KL散度衡量的
——————
2阶相似度:邻居间的比较,可以算有向图 v j ∣ v i v_j|v_i vjvi表示从i到j的二阶相似度。顶点i可以看做是中心词,j可以看做是周围词。
结合KL散度的推导。

For each directed edge ( i , j i, j i,j),we first define the probability of “context” v j v_j vj generated by
vertex v i v_i vi as:模型计算2阶相似度(预测值):
p 2 ( v j ∣ v i ) = exp ( u ⃗ ′ j T ⋅ u ⃗ i ) ∑ k = 1 ∣ V ∣ exp ( u ⃗ ′ j T ⋅ u ⃗ i ) (4) p_2(v_j|v_i)=\cfrac{\text{exp}({\vec{u}'}_j^{T}\cdot\vec{u}_i)}{\sum_{k=1}^{|V|}\text{exp}({\vec{u}'}_j^{T}\cdot\vec{u}_i)}\tag4 p2(vjvi)=k=1Vexp(u jTu i)exp(u jTu i)(4)
其中 ∣ V ∣ |V| V是图中节点数量
对于ground truth的概率
p ^ 2 ( v j ∣ v i ) = w i j d i (5) \hat p_2(v_j|v_i)=\cfrac{w_{ij}}{d_i}\tag5 p^2(vjvi)=diwij(5)
d i d_i di是顶点 v i v_i vi的出度。
有了模型计算值和ground truth的概率分布,就可以算二者的KL散度(其中 λ i \lambda_i λi是节点i的重要程度,可以通过节点i的出入度数量来判断,原始应该是pagerank算法进行排序)【第三个等号将公式4和5代入】:
O 2 = λ i K L ( p ^ 2 ( v j ∣ v i ) , p 2 ( v j ∣ v i ) ) = ∑ i ∈ V λ i p ^ 2 ( v j ∣ v i ) ( log p ^ 2 ( v j ∣ v i ) − log p 2 ( v j ∣ v i ) ) = ∑ i ∈ V λ i w i j d i [ log w i j d i − log p 2 ( v j ∣ v i ) ] \begin{aligned}O_2&=\lambda_iKL(\hat p_2(v_j|v_i), p_2(v_j|v_i))\\ &=\sum_{i\in V}\lambda_i\hat p_2(v_j|v_i)(\text{log}\hat p_2(v_j|v_i)-\text{log}p_2(v_j|v_i))\\ &=\sum_{i\in V}\lambda_i\cfrac{w_{ij}}{d_i}[\text{log}\cfrac{w_{ij}}{d_i}-\text{log}p_2(v_j|v_i)]\end{aligned} O2=λiKL(p^2(vjvi),p2(vjvi))=iVλip^2(vjvi)(logp^2(vjvi)logp2(vjvi))=iVλidiwij[logdiwijlogp2(vjvi)]
这里作者用 λ i = d i \lambda_i=d_i λi=di代入上式:
O 2 = ∑ i ∈ V w i j [ log w i j d i − log p 2 ( v j ∣ v i ) ] = ∑ i ∈ V w i j log w i j d i − ∑ i ∈ V w i j log p 2 ( v j ∣ v i ) \begin{aligned}O_2&=\sum_{i\in V}w_{ij}[\text{log}\cfrac{w_{ij}}{d_i}-\text{log}p_2(v_j|v_i)]\\ &=\sum_{i\in V}w_{ij}\text{log}\cfrac{w_{ij}}{d_i}-\sum_{i\in V}w_{ij}\text{log}p_2(v_j|v_i)\end{aligned} O2=iVwij[logdiwijlogp2(vjvi)]=iVwijlogdiwijiVwijlogp2(vjvi)
图结构确定后, w i j , d i w_{ij},d_i wijdi是常数,因此第一项是常数:
O 2 ≈ − ∑ i ∈ V w i j log p 2 ( v j ∣ v i ) (6) O_2\approx-\sum_{i\in V}w_{ij}\text{log}p_2(v_j|v_i)\tag6 O2iVwijlogp2(vjvi)(6)

细节三:负采样的推导

由于公式6中可以看到有一项是 p 2 ( v j ∣ v i ) p_2(v_j|v_i) p2(vjvi),这个是针对图中所有顶点两两之间进行计算,复杂度为 O ( n 2 ) O(n^2) O(n2),为了简化计算,使得模型能够在大规模图数据上运行,模型引入了负采样简化计算。同样情况还出现在了公式4的分母 exp ( u ⃗ ′ j T ⋅ u ⃗ i ) \text{exp}({\vec{u}'}_j^{T}\cdot\vec{u}_i) exp(u jTu i)

w代表当前单词或者当前节点
c代表上下文或者邻居节点
D代表一个集合(可以看做训练集数据)
a r g max ⁡ θ ∏ ( w , c ) ∈ D p ( D = 1 ∣ c , w ; θ ) ∏ ( w , c ) ∉ D p ( D = 0 ∣ c , w ; θ ) arg\underset{\theta}{\max}\prod_{(w,c)\in D}p(D=1|c,w;\theta)\prod_{(w,c)\notin D}p(D=0|c,w;\theta) argθmax(w,c)Dp(D=1c,w;θ)(w,c)/Dp(D=0c,w;θ)
上式的意思是要找到一个参数 θ \theta θ,使得当 w , c w,c w,c属于集合D(出现在训练集中)的时候,P的概率最大化,当 w , c w,c w,c不属于集合D(没有出现在训练集中)的时候,P的概率最小化
= a r g max ⁡ θ ∏ ( w , c ) ∈ D p ( D = 1 ∣ c , w ; θ ) ∏ ( w , c ) ∉ D ( 1 − p ( D = 1 ∣ c , w ; θ ) ) =arg\underset{\theta}{\max}\prod_{(w,c)\in D}p(D=1|c,w;\theta)\prod_{(w,c)\notin D}(1-p(D=1|c,w;\theta)) =argθmax(w,c)Dp(D=1c,w;θ)(w,c)/D(1p(D=1c,w;θ))
连乘变连加,取log:
= a r g max ⁡ θ ∑ ( w , c ) ∈ D log ⁡ p ( D = 1 ∣ c , w ; θ ) + ∑ ( w , c ) ∉ D log ⁡ ( 1 − p ( D = 1 ∣ c , w ; θ ) ) =arg\underset{\theta}{\max}\sum_{(w,c)\in D}\log p(D=1|c,w;\theta)+\sum_{(w,c)\notin D}\log (1-p(D=1|c,w;\theta)) =argθmax(w,c)Dlogp(D=1c,w;θ)+(w,c)/Dlog(1p(D=1c,w;θ))
将概率用sigmoid或softmax公式代入,其中 v c , v w v_c,v_w vc,vw是c和w对应的embedding:
= a r g max ⁡ θ ∑ ( w , c ) ∈ D log ⁡ 1 1 + e − v c ⋅ v w + ∑ ( w , c ) ∉ D log ⁡ ( 1 − 1 1 + e − v c ⋅ v w ) = a r g max ⁡ θ ∑ ( w , c ) ∈ D log ⁡ 1 1 + e − v c ⋅ v w + ∑ ( w , c ) ∉ D log ⁡ ( 1 1 + e v c ⋅ v w ) =arg\underset{\theta}{\max}\sum_{(w,c)\in D}\log \cfrac{1}{1+e^{-v_c\cdot v_w}}+\sum_{(w,c)\notin D}\log (1-\cfrac{1}{1+e^{-v_c\cdot v_w}})\\ =arg\underset{\theta}{\max}\sum_{(w,c)\in D}\log \cfrac{1}{1+e^{-v_c\cdot v_w}}+\sum_{(w,c)\notin D}\log (\cfrac{1}{1+e^{v_c\cdot v_w}}) =argθmax(w,c)Dlog1+evcvw1+(w,c)/Dlog(11+evcvw1)=argθmax(w,c)Dlog1+evcvw1+(w,c)/Dlog(1+evcvw1)
根据sigmoid的定义: σ ( x ) = 1 1 + e ( − x ) \sigma(x)=\cfrac{1}{1+e^{(-x)}} σ(x)=1+e(x)1,上式可以写成:
= a r g max ⁡ θ log ⁡ σ ( v c ⋅ v w ) + ∑ ( w , c ) ∉ D log ⁡ σ ( − v c ⋅ v w ) =arg\underset{\theta}{\max}\log \sigma(v_c\cdot v_w)+\sum_{(w,c)\notin D}\log\sigma(-v_c\cdot v_w) =argθmaxlogσ(vcvw)+(w,c)/Dlogσ(vcvw)

然后为了和论文的公式7一样,我们令: v c = u ⃗ ′ j T , v w = u ⃗ i v_c={\vec{u}'}_j^{T},v_w=\vec{u}_i vc=u jT,vw=u i
= a r g max ⁡ θ log ⁡ σ ( u ⃗ ′ j T ⋅ u ⃗ i ) + ∑ i = 1 K E v n ∼ p n ( v ) log ⁡ σ ( − u ⃗ ′ j T ⋅ u ⃗ i ) (7) =arg\underset{\theta}{\max}\log \sigma({\vec{u}'}_j^{T}\cdot \vec{u}_i)+\sum_{i=1}^KE_{v_n\sim p_n(v)}\log\sigma(-{\vec{u}'}_j^{T}\cdot \vec{u}_i)\tag7 =argθmaxlogσ(u jTu i)+i=1KEvnpn(v)logσ(u jTu i)(7)
加号后面那项就表示不存在的边,也就是负样本,K代表采样的个数。这个个数应该是和 v n v_n vn这个节点的度 p n ( v ) p_n(v) pn(v)有关。

细节四:alias table

梯度下降学习率优化
对于二阶相似度:
O 2 ≈ − ∑ i ∈ V w i j log p 2 ( v j ∣ v i ) (6) O_2\approx-\sum_{i\in V}w_{ij}\text{log}p_2(v_j|v_i)\tag6 O2iVwijlogp2(vjvi)(6)
求其对 u ⃗ i \vec{u}_i u i的偏导:
∂ O 2 ∂ u ⃗ i = w i j ⋅ ∂ log p 2 ( v j ∣ v i ) ∂ u ⃗ i \cfrac{\partial{O_2}}{\partial{\vec{u}_i}}=w_{ij}\cdot\cfrac{\partial{\text{log}p_2(v_j|v_i)}}{\partial{\vec{u}_i}} u iO2=wiju ilogp2(vjvi)
可以看到这里 w i j w_{ij} wij又是对所有的点进行计算,作为权重有大有小,大的时候会出现梯度爆炸,小的时候梯度会消失,学习率不好设置。
A simple treatment is thus to unfold a weighted edge into multiple binary edges, e.g., an edge with weight w is unfolded into w binary edges.
就是有邻接关系就设置为1,没有就设置为0.但是这样按邻接矩阵来存储对内存消耗比较大。
作者这里也按 w i j w_{ij} wij进行正比来对边进行采样计算,不对所有边进行计算。
不展开,具体到Node2Vec具体讲。
总体时间复杂度为: O ( d K ∣ E ∣ ) O(dK|E|) O(dKE)
这里的K就是公式7中负采样的边数量
d是embedding的维度
一条正样本边对应K条负样本的边,其时间复杂度为: O ( d ( K + 1 ) ) ∼ O ( d K ) O(d(K+1))\sim O(dK) O(d(K+1))O(dK)
总共有 ∣ E ∣ |E| E条边,优化的步数时间复杂度为: O ( ∣ E ∣ ) O(|E|) O(E)
上面两个相乘得到最后结果: O ( d K ∣ E ∣ ) O(dK|E|) O(dKE)

细节五:低度点的处理Low degree vertices

低度点:难以学习,因为度低邻居数少2阶相似度难学,用高阶信息补充(补充后就看做是直接邻居),这里面采用的是邻居的邻居
新点:保持其他点的embedding不变,计算新点的embedding
新点其他的处理方法:GraphSage模型(后面讲,主要处理动态图)

实验结果及分析

在这里插入图片描述
数据集
在这里插入图片描述
三类数据集。
wikipedia数据集上的单词类比实验
在这里插入图片描述
wikipedia数据集上的文本分类实验 document classification:
one-vs-rest logistic regression classifier:二分类器,七个分类要训练七个分类器。取某一个分类为一类,其他六类为一类,得到某一个分类的打分,如此做七次,得到七个打分,取最高者作为当前分类的预测结果。
在这里插入图片描述
社交网络比较稀疏,因此一阶相似度比二阶相似度重要(从实验数据可以看到)
Flicker数据集上的节点分类实验 Node classification
在这里插入图片描述
下图中,括号里面的数据是将稀疏的图中的节点通过邻居的邻居进行加边扩展(邻居上限为1000,超过性能并无提升。)后做的效果(下同)。
在这里插入图片描述
DBLP(作者网络)数据集上的节点分类实验 Node classification
在这里插入图片描述
DBLP(论文网络)数据集上的节点分类实验Node classification
在这里插入图片描述
TSNE可视化,三类会议: WWW, KDD from “data mining,” NIPS, ICML from “machine learning,” and CVPR, ICCV from “computer vision.”
在这里插入图片描述
参数实验(略)

论文总结

关键点
图的一阶和二阶相似度的理解
图的一二阶相似度转化为目标函数
公式的推导
时间复杂度分析
图上的负采样,alias sampling
创新点
根据图的信息直接建模
不基于random walk
大规模数据集上的应用
丰富的实验论证效果
启发点
图的理解对网络表征学习的作用
基于图的结构启发了大量的工作(triangle、clique)
本文的作者Jian Tang老师是图机器学习的著名研究员,大家可以关注
算法的设计,通过KL散度将基于图结构的预测值和真实值进行比较
复杂时间复杂度分析
算法优化方法的选择

代码复现

数据集

erdosrenyi.edgelist,比较大,无GPU要跑1小时以上
karate.edgelist,也较大
weighted.karate.edgelist,经老师处理过,可以跑,能可视化。
其他环境就torch和numpy即可。

算法模块及细节

train.py

主程序入口

import argparse
from utils.utils import *
from utils.line import Line
from tqdm import trange
import torch
import torch.optim as optim
import sys
import pickle

# 1.设置模型参数
# 2.读图,存点和边并做归一化
# 3.计算点和边的alias table
# 4.Line模型实现
# 5.模型按边训练以及负采样
# 6.结果展示和可视化
if __name__ == "__main__":
    # 1. # 设置模型参数;读图,存点和边并做归一化
    # 1)设置模型参数设置模型超参数,如1st order,2nd order,负样本数量(K),embedding维度,batch、epoch、learning rate等
    # 2)输入输出
    # 输入文件./data/weighted.karate.edgelist
    # 输出文件./model.pt
    parser = argparse.ArgumentParser()
    # 输入文件
    parser.add_argument("-g", "--graph_path", type=str)
    # 模型信息输出文件
    parser.add_argument("-save", "--save_path", type=str)
    # 模型损失函数值输出文件
    parser.add_argument("-lossdata", "--lossdata_path", type=str)

    # Hyperparams.超参数
    # 论文中的1st order,2nd order
    parser.add_argument("-order", "--order", type=int, default=2)
    # 负样本个数
    parser.add_argument("-neg", "--negsamplesize", type=int, default=5)
    # embedding维度
    parser.add_argument("-dim", "--dimension", type=int, default=128)
    # batchsize大小
    parser.add_argument("-batchsize", "--batchsize", type=int, default=5)
    parser.add_argument("-epochs", "--epochs", type=int, default=1)
    # 学习率
    parser.add_argument("-lr", "--learning_rate", type=float,
                        default=0.025)  # As starting value in paper
    # 负采样指数值
    parser.add_argument("-negpow", "--negativepower", type=float, default=0.75)
    args = parser.parse_args()

    # 2.读图,存点和边并做归一化
    # 1)读图自己实现的makeDist函数,在utils.py中
    # Create dict of distribution when opening file
    edgedistdict, nodedistdict, weights, nodedegrees, maxindex = makeDist(
        args.graph_path, args.negativepower)

    #3. 计算点和边的alias table
    # 构建alias table,达到O(1)的采样效率
    edgesaliassampler = VoseAlias(edgedistdict)
    nodesaliassampler = VoseAlias(nodedistdict)

    # 每次训练batch size大小的边数量
    batchrange = int(len(edgedistdict) / args.batchsize)
    print(maxindex)#34
	# 这里maxindex + 1是因为顶点编号是从1开始的
    line = Line(maxindex + 1, embed_dim=args.dimension, order=args.order)
    # SGD优化,nesterov是对momentum的改进,是在momentum向量的终端再更新梯度。
    opt = optim.SGD(line.parameters(), lr=args.learning_rate,
                    momentum=0.9, nesterov=True)

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    lossdata = {"it": [], "loss": []}
    it = 0

    print("\nTraining on {}...\n".format(device))
    for epoch in range(args.epochs):
        print("Epoch {}".format(epoch))
        for b in trange(batchrange):
            # edgesaliassampler是实现alias building的VoseAlias类,这里采样出batchsize条边
            # samplededges = edgesaliassampler.sample_n(args.batchsize)
            # 存makeData是utils.py中的函数,为每条边采样出K条负样本边存每一条格式是(node i,node j,negative nodes...)
            batch = list(makeData(samplededges, args.negsamplesize, weights, nodedegrees,
                                  nodesaliassampler))
            batch = torch.LongTensor(batch)
            # 把一个batch的数据打印出来是这样:
            # tensor([[3, 8 14, 14, 24, 2, 32],
            #         [25, 32, 14, 9, 4, 24, 23],
            #         [1, 14, 32, 1, 25, 27, 16],
            #         [26, 32, 30, 4, 14, 7, 4],
            #         [25, 32, 25, 14, 20, 14, 27]])

            #取第一列就是起始点
            v_i = batch[:, 0]
            #取第二列就是终点
            v_j = batch[:, 1]
            #取后面5列就是负样本
            negsamples = batch[:, 2:]
            #在做BP之前将gradients置因为是梯度累加的
            line.zero_grad()
            loss = line(v_i, v_j, negsamples, device)
            loss.backward()
            opt.step()

            lossdata["loss"].append(loss.item())
            lossdata["it"].append(it)
            it += 1

    print("\nDone training, saving model to {}".format(args.save_path))
    torch.save(line, "{}".format(args.save_path))

    print("Saving loss data at {}".format(args.lossdata_path))
    with open(args.lossdata_path, "wb") as ldata:
        pickle.dump(lossdata, ldata)
    sys.exit()

utils.py

import random
from decimal import *
import numpy as np
import collections
from tqdm import tqdm


class VoseAlias(object):
    """
    Adding a few modifs to https://github.com/asmith26/Vose-Alias-Method
    """

    def __init__(self, dist):
        """
        (VoseAlias, dict) -> NoneType
        """
        self.dist = dist
        self.alias_initialisation()

    def alias_initialisation(self):
        """
        Construct probability and alias tables for the distribution.
        """
        # Initialise variables
        n = len(self.dist)
        self.table_prob = {}   # probability table概率表
        self.table_alias = {}  # alias table替身表
        scaled_prob = {}       # scaled probabilities乘以n的概率表
        small = []             # stack for probabilities smaller that 1存储概率值小于1的
        large = []             # stack for probabilities greater than or equal to 1存储概率值大于1的

        # Construct and sort the scaled probabilities into their appropriate stacks
        print("1/2. Building and sorting scaled probabilities for alias table...")
        for o, p in tqdm(self.dist.items()):
            scaled_prob[o] = Decimal(p) * n

            if scaled_prob[o] < 1:
                small.append(o)
            else:
                large.append(o)

        print("2/2. Building alias table...")
        # Construct the probability and alias tables
        # 使用贪心算法,将概率值小于1的列表不断填成1
        while small and large:
            s = small.pop()
            l = large.pop()

            self.table_prob[s] = scaled_prob[s]
            self.table_alias[s] = l

            scaled_prob[l] = (scaled_prob[l] + scaled_prob[s]) - Decimal(1)

            if scaled_prob[l] < 1:
                small.append(l)
            else:
                large.append(l)

        # The remaining outcomes (of one stack) must have probability 1
        # 当两方不全有元素时,仅有一方有元素的也全为1
        # 就是最后一个large列表中的那个元素应该为1
        while large:
            self.table_prob[large.pop()] = Decimal(1)

        while small:
            self.table_prob[small.pop()] = Decimal(1)
        self.listprobs = list(self.table_prob)

    def alias_generation(self):
        """
        Yields a random outcome from the distribution.
        """
        # Determine which column of table_prob to inspect
        col = random.choice(self.listprobs)
        # Determine which outcome to pick in that column
        # 取自己
        if self.table_prob[col] >= random.uniform(0, 1):
            return col
        # 取替身
        else:
            return self.table_alias[col]

    def sample_n(self, size):
        """
        Yields a sample of size n from the distribution, and print the results to stdout.
        """
        # 调用alias generation一共n次,采样n个nodes
        for i in range(size):
            yield self.alias_generation()

#读图函数
#初始化词典
def makeDist(graphpath, power=0.75):

    edgedistdict = collections.defaultdict(int)
    nodedistdict = collections.defaultdict(int)

    weightsdict = collections.defaultdict(int)
    nodedegrees = collections.defaultdict(int)

    # 用来做归一化的两个sum变量
    weightsum = 0
    negprobsum = 0

    nlines = 0 #统计图一共有多少条边

    with open(graphpath, "r") as graphfile:
        for l in graphfile:
            nlines += 1

    print("Reading edgelist file...")
    maxindex = 0
    with open(graphpath, "r") as graphfile:
        # #用qdm展示for循环进度百分比
        for l in tqdm(graphfile, total=nlines):
            #将\n换行符去掉,并按空格分词,存储格式为:点i,点j,weight
            line = [int(i) for i in l.replace("\n", "").split(" ")]
            node1, node2, weight = line[0], line[1], line[2]

            # 后面会做归一化,存的是归一化的边-权重和点-出度
            edgedistdict[tuple([node1, node2])] = weight
            nodedistdict[node1] += weight

            # 不再做处理,存的是边-权重,点-出度
            weightsdict[tuple([node1, node2])] = weight
            nodedegrees[node1] += weight

            # weightsum存的是全图所有边的边权和,论文公式(2)中用到的1st相似度真实值
            weightsum += weight
            negprobsum += np.power(weight, power)

            # maxindex记录图中最大顶点index
            if node1 > maxindex:
                maxindex = node1
            elif node2 > maxindex:
                maxindex = node2

    for node, outdegree in nodedistdict.items():
        nodedistdict[node] = np.power(outdegree, power) / negprobsum

    for edge, weight in edgedistdict.items():
        edgedistdict[edge] = weight / weightsum
    # edgedistdict边且归一化
    # nodedistdict点且归一化
    # weightsdict边的权重
    # nodedegrees点的出度
    # maxindex最大节点index
    return edgedistdict, nodedistdict, weightsdict, nodedegrees, maxindex


def negSampleBatch(sourcenode, targetnode, negsamplesize, weights,
                   nodedegrees, nodesaliassampler, t=10e-3):
    """
    For generating negative samples.
    """
    negsamples = 0
    while negsamples < negsamplesize:#negsamplesize我们设置的负样本是5个点,取够5个才停止
        # nodesaliassampler是实现alias building的VoseAlias类,这里采样点
        samplednode = nodesaliassampler.sample_n(1)
        # 如果采样出source或target均跳过
        if (samplednode == sourcenode) or (samplednode == targetnode):
            continue
        else:#输出负样本点,一共negsamplesize个点
            negsamples += 1
            yield samplednode


def makeData(samplededges, negsamplesize, weights, nodedegrees, nodesaliassampler):
    for e in samplededges:# 遍历samplededges
        sourcenode, targetnode = e[0], e[1]#起点和终点
        negnodes = []
        # 采样出negsamplesize(5)个负样本点
        for negsample in negSampleBatch(sourcenode, targetnode, negsamplesize,
                                        weights, nodedegrees, nodesaliassampler):
            for node in negsample:#将所有的负样本点加入到negnodes列表中
                negnodes.append(node)
        # 格式是(node i,node j,negative nodes..…)总共7个点,前面两个正样本边的点i和j,后面5个是负样本
        yield [e[0], e[1]] + negnodes

line.py

import torch
import torch.nn as nn
import torch.nn.functional as F

# 继承自nn.Module
class Line(nn.Module):
    def __init__(self, size, embed_dim=128, order=1):
        super(Line, self).__init__()

        assert order in [1, 2], print("Order should either be int(1) or int(2)")
        # 设置embedding的维度
        self.embed_dim = embed_dim
        self.order = order
        # nodes数*embedding维度
        self.nodes_embeddings = nn.Embedding(size, embed_dim)

        # 初始化模型参数
        # 只有1st order时每个node只需要一个embedding
        # 当有2nd order时每个node还需要一个context embedding(邻居),共计两个
        if order == 2:
            self.contextnodes_embeddings = nn.Embedding(size, embed_dim)
            # uniform的Initialization
            self.contextnodes_embeddings.weight.data = self.contextnodes_embeddings.weight.data.uniform_(
                -.5, .5) / embed_dim

        # uniform的Initialization
        self.nodes_embeddings.weight.data = self.nodes_embeddings.weight.data.uniform_(
            -.5, .5) / embed_dim

    def forward(self, v_i, v_j, negsamples, device):

        v_i = self.nodes_embeddings(v_i).to(device)

        # 这里是1阶2阶相似度计算的区别,2阶是用上下文contextnodes_embeddings
        # 1阶用的是nodes_embeddings
        if self.order == 2:
            v_j = self.contextnodes_embeddings(v_j).to(device)
            negativenodes = -self.contextnodes_embeddings(negsamples).to(device)

        else:
            v_j = self.nodes_embeddings(v_j).to(device)
            negativenodes = -self.nodes_embeddings(negsamples).to(device)

        # 公式(7)中的第一项(正样本计算),第一步是点乘,然后是按行求和
        mulpositivebatch = torch.mul(v_i, v_j)
        positivebatch = F.logsigmoid(torch.sum(mulpositivebatch, dim=1))
        # 公式(7)中的第二项(负样本计算)
        mulnegativebatch = torch.mul(v_i.view(len(v_i), 1, self.embed_dim), negativenodes)
        negativebatch = torch.sum(F.logsigmoid(torch.sum(mulnegativebatch, dim=2)),dim=1)
        loss = positivebatch + negativebatch
        return -torch.mean(loss)

问题

负采样推导倒数第二行的第一项的求和怎么不见了?最大化参数为什么没有出现在最后公式中?
如果图比较稀疏,那么节点大多数都没有邻居,那么它们的二阶相似度都一样?是如何解决这个问题?
欢迎留言讨论

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值