LINE基本思想
LINE: Large-scale Information Network Embedding与DeepWalk相似都是基于领域相似假设的方法,DeepWalk是基于深度优先搜索,LINE是基于广度优先搜索。LINE还可以应用在带权图中(DeepWalk仅能用于无权图)。不同graph embedding方法之间的一个主要区别是图中顶点之间的相似度的定义不同,在LINE中有两种相似度的定义方法。
如下图:
图中的边可以是有向的也可以是无向的,或者是带权边。顶点6和顶点7之间的边权重较重,他们在低维空间内的表示向量应该是相似的。但当两个顶点之间不存在边时,如顶点5和顶点6,并不能说二者之间没有关系,因为顶点5和顶点6具有相同的邻接顶点(顶点1,2,3,4)。这就是LINE的两种相似度的基本思想。
LINE是基于顶点之间的相似度进行建模,第一步将顶点Embedding到指定维度,利用Embedding的表示向量计算顶点A与顶点B之间的相似度,让相似度去拟合顶点A,B 之间的边的权重,得到最优的顶点的嵌入向量。
一阶相似度
一阶相似度用于描述图中成对顶点之间的局部相似度,即只有当边与边之间存在连接时,顶点之间才有相似度,相似度与边之间的权重成正比,图中的顶点6,7 权重较大,相似度为1。顶点5和顶点6之间不存在边,相似度为0。
优化目标
假设用
u
i
,
u
j
u_i,u_j
ui,uj作为节点的低维向量表示,对于每一条无向边
(
i
,
j
)
(i,j)
(i,j),顶点
u
i
,
u
j
u_i,u_j
ui,uj之间的联合概率密度为:
p
1
(
v
i
,
v
j
)
=
1
1
+
e
x
p
(
−
u
i
T
⋅
u
j
)
p_1(v_i,v_j) = \frac{1}{1+exp(-u_i^T \cdot u_j)}
p1(vi,vj)=1+exp(−uiT⋅uj)1
若两个向量相似,则
p
(
v
i
,
v
j
)
p(v_i,v_j)
p(vi,vj)也较大。
同时,两节点之间的经验概率如下:
p
^
1
(
i
,
j
)
=
w
i
j
W
W
=
∑
(
i
,
j
)
∈
E
w
i
j
\hat p_1(i,j) = \frac{w_{ij}}{W} \\ W = \sum_{(i,j) \in E} w_{ij}
p^1(i,j)=WwijW=(i,j)∈E∑wij
其中,
w
i
j
w_{ij}
wij是节点
v
j
和
v
j
v_j和v_j
vj和vj之间的权重。W是节点
i
,
j
i,j
i,j的所有边的权重之和。
优化目标是最小化
p
(
v
i
,
v
j
)
、
p
^
1
(
i
,
j
)
p(v_i,v_j)、\hat p_1(i,j)
p(vi,vj)、p^1(i,j)之间的分布距离,即最小化下式:
O
1
=
d
(
p
(
v
i
,
v
j
)
,
p
^
1
(
i
,
j
)
)
O_1 = d(p(v_i,v_j),\hat p_1(i,j))
O1=d(p(vi,vj),p^1(i,j))
其中,
d
(
,
)
d(,)
d(,)是两个分布之间的距离。这与KL散度的功能一样。
补充:KL散度是一种量化两种概率分布P和Q之间差异的方式,数学公式:
D
K
L
(
p
∣
∣
q
)
=
∑
i
=
1
N
p
(
x
i
)
log
p
(
x
i
)
q
(
x
i
)
D_{KL}(p||q) = \sum_{i=1}^N p(x_i)\log \frac{p(x_i)}{q(x_i)}
DKL(p∣∣q)=i=1∑Np(xi)logq(xi)p(xi)
所以,忽略常数项,我们得到以下的等效优化函数:
O
1
=
−
∑
(
i
,
j
)
∈
E
w
i
j
l
o
g
(
p
1
(
v
i
,
v
j
)
)
O_1 = -\sum_{(i,j)\in E}w_{ij}log\big(p_1(v_i, v_j)\big)
O1=−(i,j)∈E∑wijlog(p1(vi,vj))
一阶相似度的优化目标就是最小化
O
1
O_1
O1。从这个过程看出,一阶相似度只能用于描述无向图。
二阶相似度
二阶相似度描述的是顶点与领域的关系,两个顶点之间不存在边,即一阶相似度为0时,若,这两个顶点之间存在公共的邻接顶点(顶点5,6),这二者具有二阶相似度。因此,对于每一个顶点都有两个向量,一个是顶点本身的表示向量,一个是该顶点作为其他顶点邻居时的表示向量,我们称之为上下文的向量表示。
回顾一阶相似度的思想:通过sigmoid函数两个节点的向量内积转换为概率来拟合两个节点边的权重。而在二阶相似度中,则需要表示当前节点与周围多个节点的相似度,所以节点
v
i
v_i
vi产生节点
v
j
v_j
vj的概率为:
p
2
(
v
j
∣
v
i
)
=
e
x
p
(
u
j
′
T
⋅
u
i
)
∑
k
=
1
∣
V
∣
e
x
p
(
u
k
′
T
⋅
u
i
)
p_2(v_j|v_i) = \frac{exp(u_j^{'T}\cdot u_i)}{\sum_{k=1}^{|V|}exp(u_k^{'T}\cdot u_i)}
p2(vj∣vi)=∑k=1∣V∣exp(uk′T⋅ui)exp(uj′T⋅ui)
其中,
u
i
,
u
j
′
u_i, u_j'
ui,uj′ 为节点
i
,
j
i, j
i,j 低维向量表示,且
u
j
′
u'_j
uj′ 为节点 j 表示为上下文时的向量表示。
∣
V
∣
|V|
∣V∣ 表示图中所有节点的个数或者节点
i
i
i的邻接节点的个数。
此时,经验分布为:
p
^
2
(
v
j
∣
v
i
)
=
w
i
j
d
i
\hat p_2(v_j|v_i)=\frac{w_{ij}}{d_i}
p^2(vj∣vi)=diwij
其中,
w
i
j
w_{ij}
wij 是边
(
i
,
j
)
(i, j)
(i,j) 的权重,
d
i
=
∑
k
∈
N
(
i
)
w
i
k
d_i = \sum_{k\in N(i)} w_{ik}
di=∑k∈N(i)wik,
N
(
i
)
N(i)
N(i) 为与节点
i
i
i 相连的邻居节点(有向图中是以节点
i
i
i为起点的所有节点)集合。对于无权图而言,
d
i
d_i
di 是节点
v
i
v_i
vi 的出度。
同样用KL散度来衡量二者之间的关系,所以,忽略常数项,我们得到以下的等效优化函数:
O
2
=
−
∑
(
i
,
j
)
∈
E
w
i
j
l
o
g
(
p
2
(
v
j
∣
v
i
)
)
O_2 = -\sum_{(i,j)\in E}w_{ij}log\big(p_2(v_j| v_i)\big)
O2=−(i,j)∈E∑wijlog(p2(vj∣vi))
在Graph Embedding方法中,例如DeepWalk、Node2Vec、EGES,都是采用随机游走的方式来生成序列再做训练,而LINE直接用边来构造样本,这也是他们的一点区别。
优化技巧
负采样:
计算二阶相似度时,softmax 函数的分母计算需要遍历所有顶点,计算量太大了,论文采用了与 word2vec 论文类似的负采样优化的技巧,对边进行抽样生成负样本,每条边被抽中的概率为边的权重。
边采样:
从上面的公式可以看到,每个样本都有一个权重 w i j w_{ij} wij,有的样本w很高,有的样本w很低。在进行反向传播的时候,如果学习率过高,会导致w很大的样本梯度爆炸,如果学习率设置很小,会导致w很低的样本训练缓慢。为了解决这个问题,文中提出了边缘采样(Edge Sampling)的方法,将所有样本的系数都置为1,对图中的边重复采样来构造多个相同的正样本,采样率与权重 w i j w_{ij} wij 成正比。
核心代码
论文中的实现是把一阶相似度和二阶相似度融合到了一起,可以通过order进行控制。
使用负采样后的loss函数
def line_loss(y_true, y_pred):
return -K.mean(K.log(K.sigmoid(y_true*y_pred)))
模型代码:
def create_model(numNodes, embedding_size, order='second'):
# 声明变量
v_i = Input(shape=(1,))
v_j = Input(shape=(1,))
first_emb = Embedding(numNodes, embedding_size, name='first_emb')
second_emb = Embedding(numNodes, embedding_size, name='second_emb')
context_emb = Embedding(numNodes, embedding_size, name='context_emb')
v_i_emb = first_emb(v_i)
v_j_emb = first_emb(v_j)
v_i_emb_second = second_emb(v_i)
v_j_context_emb = context_emb(v_j)
# 计算相似度
first = Lambda(lambda x: tf.reduce_sum(
x[0]*x[1], axis=-1, keep_dims=False), name='first_order')([v_i_emb, v_j_emb])
second = Lambda(lambda x: tf.reduce_sum(
x[0]*x[1], axis=-1, keep_dims=False), name='second_order')([v_i_emb_second, v_j_context_emb])
if order == 'first':
output_list = [first]
elif order == 'second':
output_list = [second]
else:
output_list = [first, second]
model = Model(inputs=[v_i, v_j], outputs=output_list)
库实现
使用github库实现:https://github.com/shenweichen/GraphEmbedding
将项目clone到本地,依次执行:
python setup.py install
cd examples
python deepwalk_wiki.py
然后,即可通过下面代码实现LINE:
G = nx.read_edgelist('../data/wiki/Wiki_edgelist.txt',create_using=nx.DiGraph(),nodetype=None,data=[('weight',int)])#read graph
model = LINE(G,embedding_size=128,order='second') #init model,order can be ['first','second','all']
model.train(batch_size=1024,epochs=50,verbose=2)# train model
embeddings = model.get_embeddings()# get embedding vectors
参考:
LINE: Large-scale Information Network Embedding