LINE
算法适用于任意类型的图,包括有向图、无向图、有权图和无权图。使用LINE算法学习到的embeddings可以保留顶点的局部结构和全局结构(local and global network structures)。其核心思想是使用一阶相似度(first-order proximity
)表征local structure,使用二阶相似度(second-order proximity
)表征global structure。
first-order proximity:表征顶点与顶点之间的局部相似度,如果顶点$v_i$和$v_j$之间存在边,则一阶相似度为边的权值,否则一阶相似度为0;
second-order proximity:表征顶点$v_i$的邻域和$v_j$的邻域的相似程度(即两个顶点有多少共同的邻居节点)。定义$S_i=(w_{i,1},\cdots,w_{i,|V|})$表示顶点$v_i$与其它顶点的一阶相似度,则顶点$v_i$和$v_j$的二阶相似度可以用$S_i$和$S_j$的相似度衡量
对上图使用LINE算法,顶点6和顶点7的embedding应该在向量空间中比较靠近,因为顶点6和顶点7之间相连,具有较高的一阶相似度;而顶点6和顶点5的embedding应该在向量空间中比较靠近,因为顶点6和顶点5具有较高的二阶相似度。
LINE with First-order Proximity
LINE with first-order proximity只适用于无向图。式(1)用来衡量顶点$v_i$和$v_j$的向量表示之间的一阶相似度。
$$\begin{equation} p_1(v_i,v_j)=\frac{1}{1+exp(-u_i^{T}u_j)} \tag{1} \end{equation}$$
其中,$u_i$和$u_j$分别对应顶点$v_i$和$v_j$的向量表示。
顶点$v_i$和$v_j$在原始图中的一阶相似度使用empirical probability
定义,如式(2)所示,
$$\begin{equation}\hat{p}_1(i,j)=\frac{w_{i,j}}{W},W=\sum_{(i,j)\in E} w_{i,j}\tag{2}\end{equation}$$
为了使得学习到的embeddings能够保留顶点在原始图中的一阶相似度,最直接的做法是,最小化$\hat{p}_1(\cdot,\cdot)$和$p_1(\cdot,\cdot)$两个分布之间的差异,
$$\begin{equation}O_1=d(\hat{p}_1(\cdot,\cdot),p_1(\cdot,\cdot))\tag{3}\end{equation}$$
使用KL-divergence
来衡量分布间的差异($D_{KL}(P,Q)=\sum_i P(i)\log \frac{P(i)}{Q(i)}$),LINE with first-order proximity的目标函数如式
$$\begin{equation}O_1=-\sum_{(i,j)\in E}w_{i,j}\log p_1(v_i, v_j)\tag{4}\end{equation}$$
LINE with Second-order Proximity
LINE with second-order proximity
既可以使用于无向图,也可以适用于有向图。以有向图为例,每个顶点都有2个向量表示,(1)顶点$v_i$自身的embedding $u_i$;(2)顶点$v_i$作为其它顶点的context时的embedding ${u^{'}}_i$。对于有向边(i,j),给定顶点$v_i$,顶点$v_j$作为$v_i$的上下文(context),顶点$v_j$相对于顶点$v_i$的一阶相似度为$p_2(v_j|v_i)$,
$$\begin{equation}p_2(v_j|v_i) = \frac{exp({u^{'}}_j^\top u_i)}{\sum_{k=1}^{|V|}{u^{'}}_k^\top u_i}\tag{5}\end{equation}$$
在原始图中,empirical probability
为,
$$\begin{equation}\hat{p}_2(v_j|v_i)=\frac{w_{i,j}}{d_i},d_i=\sum_{k\in N(i)}w_{i,k}\tag{6}\end{equation}$$
其中,$N(i)$表示顶点$v_i$的out neighbor(与$v_i$出边相连的顶点)。
为了使得学习到的embeddings能够保留顶点在原始图中的二阶相似度,最小化$\hat{p}_2(\cdot|v_i)$和$p_2(\cdot|v_i)$两个分布之间的差异,
$$\begin{equation}O_2=\sum_{i\in V}d(\hat{p}_2(\cdot|v_i),p_2(\cdot|v_i))\tag{7}\end{equation}$$
$$\begin{equation}O_2=-\sum_{(i,j)\in E}w_{i,j}\log p_2(v_j|v_i)\tag{8}\end{equation}$$
如果想要学习到的embedding既保留一阶相似度,也保留二阶相似度,最简单的做法是,分别使用LINE with first-order proximity和LINE with second-order proximity训练模型,将得到的embedding拼接。
Optimization via Edge Sampling
在使用梯度下降算法优化目标函数(7)时,存在一个问题,如果边(i, j)被采样,the gradient w.r.t. the embedding vector $u_i$ of vertex $v_i$如下,
$$\frac{\partial O_2}{\partial u_i}=w_{i,j}\frac{\partial\log p_2(v_j|v_i)}{\partial u_i}$$
显然边的权值影响梯度的计算,这会导致模型的learning rate难以确定,如果根据权重大的边确定学习率,那么权重小的边对应的梯度就很小,反之,如果根据权重小的边确定学习率,那么权重大的边对应的梯度就很大。
为了解决这一问题,可以对原始图中的边进行采样,每条边被采样的概率正比于该边的权值。令$W =(w_1,\cdots,w_{|E|}),w_{sum}=\sum_{i=1}^{|E|}w_i$,在范围$[0,w_{sum}]$内生成一个随机数,选择随机数所在区间对应的边,作为采样边。可以使用alias table method
将采样的复杂度从$O(|E|)$降至$O(1)$。
alias table method
alias method
详见【数学】时间复杂度O(1)的离散采样算法-- Alias method/别名采样方法
代码如下,
import numpy as np
def gen_alias_table(ws = [0.1, 0.5, 1.5, 0.3, 1.4, 0.8, 2.0, 0.6]):
data_size = len(ws)
edge_alias = np.zeros(data_size, np.int32)
edge_prob = np.zeros(data_size, np.float32)
large_block = np.zeros(data_size, np.int32)
small_block = np.zeros(data_size, np.int32)
total_sum = sum(ws)
norm_prob = [w*data_size/total_sum for w in ws]
print(norm_prob)
num_small_block = 0
num_large_block = 0
cur_small_block = 0
cur_large_block = 0
for k in range(data_size-1, -1, -1):
if norm_prob[k] < 1.0:
small_block[num_small_block] = k
num_small_block += 1
else:
large_block[num_large_block] = k
num_large_block += 1
while num_large_block and num_small_block:
num_small_block -= 1
cur_small_block = small_block[num_small_block]
num_large_block -= 1
cur_large_block = large_block[num_large_block]
edge_prob[cur_small_block] = norm_prob[cur_small_block]
edge_alias[cur_small_block] = cur_large_block
norm_prob[cur_large_block] = norm_prob[cur_large_block] + norm_prob[cur_small_block] - 1
if norm_prob[cur_large_block] < 1.0:
small_block[num_small_block] = cur_large_block
num_small_block += 1
else:
large_block[num_large_block] = cur_large_block
num_large_block += 1
while num_large_block:
num_large_block -= 1
edge_prob[large_block[num_large_block]] = 1.0
while num_small_block:
num_small_block -= 1
edge_prob[small_block[num_small_block]] = 1
return edge_prob, edge_alias
if __name__ == '__main__':
np.random.seed(2)
ws = np.random.uniform(0.0, 2.0, 10).tolist()
print(ws)
prob, alias = gen_alias_table(ws)
print(prob)
print(alias)
output:
ws [0.8719898042840075, 0.051852463655782666, 1.0993249557574183, 0.8706447852365538, 0.8407356041749781, 0.6606696420077482, 0.409297268075685, 1.2385419327013274, 0.5993093473490463, 0.5336545502057333]
norm_prob: [1.2151439953274266, 0.07225796625683495, 1.5319423602654105, 1.2132696708673414, 1.171590328295238, 0.9206629990817653, 0.5703680423356187, 1.7259454010692334, 0.8351555846146134, 0.7436636518865185]
prob: [ 0.28740197 0.07225797 0.81934434 0.95327699 0.69523537 0.920663
0.57036805 1. 0.83515561 0.74366367]
alias: [2 0 3 4 7 3 4 0 7 7]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。