简单实践GraphEmbedding图嵌入的几种方法

Posted 悟乙己

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单实践GraphEmbedding图嵌入的几种方法相关的知识,希望对你有一定的参考价值。

参考:
详解Graph Embedding经典方法:算法原理、代码实现与应用样例
Graph Embedding 图表示学习的原理及应用

代码参考:
https://github.com/shenweichen/GraphEmbedding

本篇简单测试一下该库



1 Graph Embedding 几种常见方法

ModelPaperNote
DeepWalk[KDD 2014]DeepWalk: Online Learning of Social Representations【Graph Embedding】DeepWalk:算法原理,实现和应用
LINE[WWW 2015]LINE: Large-scale Information Network Embedding【Graph Embedding】LINE:算法原理,实现和应用
Node2Vec[KDD 2016]node2vec: Scalable Feature Learning for Networks【Graph Embedding】Node2Vec:算法原理,实现和应用
SDNE[KDD 2016]Structural Deep Network Embedding【Graph Embedding】SDNE:算法原理,实现和应用
Struc2Vec[KDD 2017]struc2vec: Learning Node Representations from Structural Identity【Graph Embedding】Struc2Vec:算法原理,实现和应用

Graph Embedding 技术将图中的节点以低维稠密向量的形式进行表达,要求在原始图中相似 ( 不同的方法对相似的定义不同 ) 的节点其在低维表达空间也接近。得到的表达向量可以用来进行下游任务,如节点分类,链接预测,可视化或重构原始图等。

1.1 DeepWalk

DeepWalk 的思想类似 word2vec,使用图中节点与节点的共现关系来学习节点的向量表示。那么关键的问题就是如何来描述节点与节点的共现关系,DeepWalk 给出的方法是使用随机游走 (RandomWalk) 的方式在图中进行节点采样。

分类任务结果

micro-F1 : 0.6674
macro-F1 : 0.5768

1.2 LINE

之前介绍过DeepWalk,DeepWalk使用DFS随机游走在图中进行节点采样,使用word2vec在采样的序列学习图中节点的向量表示。

LINE也是一种基于邻域相似假设的方法,只不过与DeepWalk使用DFS构造邻域不同的是,LINE可以看作是一种使用BFS构造邻域的算法。此外,LINE还可以应用在带权图中(DeepWalk仅能用于无权图)。

分类任务结果

micro-F1: 0.6403
macro-F1:0.5286

结果有一定随机性,可以多运行几次,或者稍微调整epoch个数。

1.3 nodo2vec

前面介绍过基于DFS邻域的DeepWalk和基于BFS邻域的LINE。

node2vec是一种综合考虑DFS邻域和BFS邻域的graph embedding方法。简单来说,可以看作是deepwalk的一种扩展,是结合了DFS和BFS随机游走的deepwalk。
分类任务

micro-F1: 0.6757 macro-F1: 0.5917

这个结果相比于DeepWalk和LINE是有提升的。

1.4 SDNE

SDNE(Structural Deep Network Embedding )是和node2vec并列的工作,均发表在2016年的KDD会议中。可以看作是基于LINE的扩展,同时也是第一个将深度学习应用于网络表示学习中的方法。
SDNE使用一个自动编码器结构来同时优化1阶和2阶相似度(LINE是分别优化的),学习得到的向量表示能够保留局部和全局结构,并且对稀疏网络具有鲁棒性。

分类任务

micro-F1: 0.6341 macro-F1: 0.4962

这里还有一个SDNE在业界的应用的介绍:
阿里凑单算法首次公开!基于Graph Embedding的打包购商品挖掘系统解析

1.5 Struc2Vec

前面介绍过DeepWalk,LINE,Node2Vec,SDNE几个graph embedding方法。这些方法都是基于近邻相似的假设的。其中DeepWalk,Node2Vec通过随机游走在图中采样顶点序列来构造顶点的近邻集合。LINE显式的构造邻接点对和顶点的距离为1的近邻集合。SDNE使用邻接矩阵描述顶点的近邻结构。
事实上,在一些场景中,两个不是近邻的顶点也可能拥有很高的相似性,对于这类相似性,上述方法是无法捕捉到的。Struc2Vec就是针对这类场景提出的。Struc2Vec的论文发表在2017年的KDD会议中。

分类

Struc2Vec结果 micro-F1: 0.7143, macro-F1: 0.7357
Node2Vec结果 micro-F1: 0.3571, macro-F1: 0.3445

差距还是蛮大的,说明Struc2Vec确实能够更好的捕获空间结构性。


2 实验代码

import pandas as pd
import copy
from collections import Counter
from tqdm import tqdm
import sys
import random
sys.path.append('GraphEmbedding')
#from GraphEmbedding.examples.struc2vec_flight import *
import time
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from sklearn.manifold import TSNE
import pandas as pd
from collections import Counter


data_list = [set(['A','B']),set(['D','C']),set(['E','A']),set(['E','D']),set(['A','D']),set(['B','D'])]


item_id = set()
data_list_3 = {}
total_freq = len(data_list)

for dl in tqdm(data_list):
    # 
    for d in dl:
        item_id.add(str(d).replace(' ',''))
    # 
    dl = copy.deepcopy(tuple(str(d).replace(' ','') for d in dl))
    if dl not in data_list_3.keys():
        data_list_3[dl] = 0
    data_list_3[dl] += 1

data_label = {str(ii).replace(' ',''):str(n) for n,ii in enumerate(item_id)} # 节点:节点编号
data_list_3 = {k:v for k,v in data_list_3.items()}   # [A,B]出现频率

# 数据导出 - 节点-节点
edgelist_file = open('GraphEmbedding/test/data.edgelist','w+',encoding = 'utf-8') 

for k,v in tqdm(data_list_3.items()):
    k = [str(_k) for _k in k]
    text = ' '.join([data_label[_k] for _k in k]+[str(v)]) + '\\n'
    # text = ' '.join(list(k)) + '\\n'
    edgelist_file.write(text)

edgelist_file.close()

# 数据导出 - 节点-解释
edgelist_label = open('GraphEmbedding/test/data.label','w+',encoding = 'utf-8') 

for k,v in tqdm(data_label.items()):
    text = ' '.join([k,v]) + '\\n'
    # text = ' '.join(list(k)) + '\\n'
    edgelist_label.write(text)

edgelist_label.close()



#----
def bit_product_sum(x, y):
    return sum([item[0] * item[1] for item in zip(x, y)])


def cosine_similarity(x, y, norm=False):
    """ 计算两个向量x和y的余弦相似度 """
    assert len(x) == len(y), "len(x) != len(y)"
    zero_list = [0] * len(x)
    if x == zero_list or y == zero_list:
        return float(1) if x == y else float(0)

    # method 1
    res = np.array([[x[i] * y[i], x[i] * x[i], y[i] * y[i]] for i in range(len(x))])
    cos = sum(res[:, 0]) / (np.sqrt(sum(res[:, 1])) * np.sqrt(sum(res[:, 2])))

    # method 2
    # cos = bit_product_sum(x, y) / (np.sqrt(bit_product_sum(x, x)) * np.sqrt(bit_product_sum(y, y)))

    # method 3
    # dot_product, square_sum_x, square_sum_y = 0, 0, 0
    # for i in range(len(x)):
    #     dot_product += x[i] * y[i]
    #     square_sum_x += x[i] * x[i]
    #     square_sum_y += y[i] * y[i]
    # cos = dot_product / (np.sqrt(square_sum_x) * np.sqrt(square_sum_y))

    return 0.5 * cos + 0.5 if norm else cos  # 归一化到[0, 1]区间内


def plot_embeddings(emb_list,color_random = []):
    #X, Y = read_node_label('../data/wiki/wiki_labels.txt')
    X = emb_list
    if len(color_random) == 0:
        color_random = [random.sample(range(20), 1)[0] for _ in range(len(embeddings))]

    model = TSNE(n_components=2)
    node_pos = model.fit_transform(emb_list)
    
    color_idx = {}
    for i in range(len(X)):
        color_idx.setdefault(color_random[i], [])
        color_idx[color_random[i]].append(i)

    for c, idx in color_idx.items():
        plt.scatter(node_pos[idx, 0], node_pos[idx, 1], label=c)
    plt.legend()
    plt.show()


G = nx.read_edgelist('GraphEmbedding/test/data.edgelist', create_using=nx.DiGraph(), nodetype=None,
                     data=[('weight', int)])
# struc2vec -> 最有规律
from ge import Struc2Vec
s2v_model = Struc2Vec(G, 10, 80, workers=4, verbose=40, )
s2v_model.train()
embeddings = s2v_model.get_embeddings()



# from ge import SDNE
# sdne_model = SDNE(G, hidden_size=[1024, 512],)
# sdne_model.train(batch_size=20000, epochs=50, verbose=2)
# embeddings = sdne_model.get_embeddings()

# node2vec -> 有一小簇有规律
from ge import Node2Vec
n2v_model = Node2Vec(G, walk_length=10, num_walks=80,
                 p=0.25, q=4, workers=1, use_rejection_sampling=0)
n2v_model.train(window_size = 5, iter = 3)
embeddings=n2v_model.get_embeddings()

# DeepWalk -> 最散
from ge import DeepWalk
dw_model = DeepWalk(G, walk_length=10, num_walks=80, workers=1)
dw_model.train(window_size=5, iter=3)
embeddings = dw_model.get_embeddings()

# 画图
emb_list = np.array(list(embeddings.values()))
#evaluate_embeddings(embeddings)
plot_embeddings(emb_list)


# id中文名
label = pd.read_table('GraphEmbedding/test/data.label',header = None)
id_to_label = {l.split(' ')[1]:l.split(' ')[0]  for l in label[0]}
label_to_id = {l.split(' ')[0]:l.split(' ')[1]  for l in label[0]}


# 求相似产品
simi = n2v_model.w2v_model.wv.most_similar(label_to_id['D'],topn = 20)
simi
n2v_model.sentences

开源代码已经把各个调用写的非常简单了,所以简单处理数据就可以直接测试,需要包括:

  • data.edgelist 边信息
  • data.label 节点信息

后续还可以通过TSNE把向量可视化出来,在函数plot_embeddings之中

因为借助的是gensim,所以可以使用任何词向量的功能,包括近似词查询等

在自己的实践里,貌似struc2vec从可视化TSNE效果来看,最好,分的最清楚。

以上是关于简单实践GraphEmbedding图嵌入的几种方法的主要内容,如果未能解决你的问题,请参考以下文章

主流图嵌入模型的原理和应用

处理 Exception 的几种实践,被很多团队采纳!

selenium 切换窗口的几种方法

处理 Exception 的几种实践,很优雅,已被很多团队采纳!

嵌入式学习的几种线路图

绘制圆弧的几种简单方法