Graph Embedding简史
传统意义上的 Graph Embedding 被看成是一个降维的过程,将网络投影到欧式空间,但是会造成结点特征的损失。主要的方法包括主成分分析(PCA)和多维缩放(MDS)。所有的方法都可以理解成运用一个 n × k 的矩阵来表示原始的 n × m 矩阵,其中 k << n。
在 2000 年代早期,又提出了其他方法,如 IsoMap 和 LLE,以保持非线性流形的整体结构。总的来说,这些方法都在小型网络上提供了良好的性能。然而这些方法的时间复杂性至少是二次的,这使得它们无法在大规模网络上运行。
随着深度学习的不断发展,DeepWalk第一个被提出来,使用表示学习(或深度学习)来进行网络的向量化表示。DeepWalk通过将节点视为单词并生成短随机游走(广度优先BFS)作为句子来弥补网络嵌入和单词嵌入之间的差距。然后,可以将诸如Skip-gram之类的神经语言模型应用于这些随机游走以获得网络嵌入。
但是,DeepWalk中的实现是完全随机的,根据Random Walk的不同,后面又衍生出了node2vec算法,解决了DeepWalk定义的结点相似度不能很好反映原网络结构的问题。LINE在其基础上,探究一阶相似度(相邻结点)与二阶相似度(不相邻结点),在一定程度上超脱出了传统方式对网络拓扑结构的依赖。
但是,Word2vec一众方法都是浅层模型,并且需要根据不同的应用定义相似函数,不易延展。并且对拓扑结构的过度依赖导致鲁棒性不强。因此,GNN出现了。
Node2vec
在Node2vec之前,需要稍稍了解一下一下deepwalk。Deepwalk主要思想是在图上进行结点的序列采样,将采样的结果作为结点的特征,并使用word2vec对序列进行embedding。主要步骤分为两部分:
- 第一步为随机游走采样节点序列。
- 第二步为使用skip-gram model学习表达向量。
Node2vec就是在DeepWalk的基础上对游走的方式进行了更改,啊具体的原理暂时懒得写,大家可以看node2vec:算法原理,实现和应用。
源码解读
来源于Github的开源代码:https://github.com/eliorc/node2vec。pip安装:
1 | pip install node2vec |
安装之后的包中主要有三个文件:
其中,edges.py主要通过训练好的结点向量来生成边的embedding
node2vec.py是生成结点的embedding的向量。
parallel.py用于生成图上的随机游走序列,返回一个walk的list,每一步walk都是一个结点。
在node2vec.py中封装了Node2vec类,其包含三个函数,分别对应node2vec实现的三步:
- _precompute_probabilities:生成结点之间的转移概率。
- _generate_walks:根据转移概率进行随机游走。
- fit:借助gensim.models.Word2Vec对游走之后的结点序列进行拟合,生成结点的embedding。
代码实践
在使用之前,需要在模块安装的路径对文件进行更改,否则此模块名和内部的py文件名重复(都是node2vec),使用时会报错。
接下来就可以使用了:
1 2 | import networkx as nx from node2vec_.node2vec import Node2Vec # 注意我的node2vec文件名改成了node2vec_ |
使用方法与word2vec类似,不过输入一定是一个networkx.graph的类,这个类根据自己实际的情况生成:
1 2 3 4 | node2vec = Node2Vec(graph, dimensions=64, walk_length=30, num_walks=200, workers=4) # Use temp_folder for big graphs # Embed nodes model = node2vec.fit(window=10, min_count=1, batch_words=4) # Any keywords acceptable by gensim.Word2Vec can be passed, `diemnsions` and `workers` are automatically passed (from the Node2Vec constructor) |
比如,我使用了movie rating公开数据集生成词之间的共现图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | def term_graph(): files = os.listdir('{}/'.format(dataset)) if '{}_vocab-id.pkl'.format(dataset) not in files: print('please run process first') exit(0) vocabs2id = util.load_word_id(dataset) matrix = {} with open('{}/{}_cut_train.txt'.format(dataset,dataset), 'r', encoding='utf-8') as trainf: for line in trainf.readlines(): line = line.strip().split()[1:] for i in range(0, len(line)-1): index1 = vocabs2id[line[i]] index2 = vocabs2id[line[i+1]] id2 = max(index1, index2) id1 = min(index1, index2) key = str(id1) + '-' + str(id2) if key in matrix.keys(): matrix[key] += 1 else: matrix[key] = 1 with open('{}/{}_cut_test.txt'.format(dataset,dataset), 'r', encoding='utf-8') as trainf: for line in trainf.readlines(): line = line.strip().split()[1:] for i in range(0, len(line)-1): try: index1 = vocabs2id[line[i]] index2 = vocabs2id[line[i+1]] id2 = max(index1, index2) id1 = min(index1, index2) key = str(id1) + '-' + str(id2) if key in matrix.keys(): matrix[key] += 1 else: matrix[key] = 1 except: continue G = nx.Graph() # 无向图 size = len(vocabs2id.keys()) for i in range(size): G.add_node(i) for key in matrix.keys(): keys = key.split('-') row = keys[0] colum = keys[1] if row in G.nodes and colum in G.nodes: value = matrix[key] G.add_edge(row, colum) return G |
使用如下代码保存训练好之后的向量与模型:
1 2 3 4 5 | # Save embeddings for later use model.wv.save_word2vec_format(EMBEDDING_FILENAME) # Save model for later use model.save(EMBEDDING_MODEL_FILENAME) |
训练之后的结果是一堆代表向量的数字,起始数字代表结点的id:
通过node2vec得到的结点向量可以应用到许多下游任务:结点分类,链接预测等。
参考
图神经网络在线研讨会 报告课件 —— 北大宋国杰老师
node2vec:算法原理,实现和应用