图上 deepwalk 算法理论 与 tensorflow keras 实战,图算法之瑞士军刀篇(二)


书接上文,在 deepwalk 算法理论与实践,图算法之瑞士军刀篇(一) 中,咱们讲了 Graph Embeding 的鼻祖类算法 deepwalk , 知道 deepwalk 算法也遵循了 图游走算法 的 基本架构 Walk + Skip-Gram Loss 架构 , 而且 deepwalk 算法 其中用的 walk 算法是 随机游走 (random walk) 算法

在 深入浅出了解word2vec模型 (理论与源码分析) 文章中咱们 一再强调输入word2vec 的序列数据的重要性,并供给了构建序列的多种办法与技巧 。关于 图游走算法 也是如此,咱们采用 不同的游走算法久能够得到完全不同的作用 ,例如 node2vec 算法就提出了一种 带倾向游走 (部分探究与大局探究的偏重与交融) 进行 图上节点序列采样 的办法,而 metapath 和 metapath ++ 算法 ,则又供给了 异构图上能够定制途径节点类型元途径(metapath) 的节点采样算法。不同的 游走采样 方规律 构成 了不同的 图游走算法。

游走采样算法、负样本数据归一化 以及 不同采样算法途径交融的不同构成了近些年图上游走算法得到 graph embeding 的首要创新点和优化点 。 根据 “万变不离其宗” 的思想,这儿 暂时 咱们均 不在展开。

留意咱们这儿要阐明的一点是: word2vec 算法,能够有无监督的版别,也有有监督的版别。假如单纯以 外界是否显式的输入模型样本标签 的话,在 上一篇文章 中, 样本不显式带有标签,输入模型的只有 正样本 (center,context) pair 对, 能够把归结为 无监督模型 。 而本文要介绍的算法,在运用 tf.keras.preprocessing.sequence.skipgrams 采样的时候带有了 标签,能够把归结为 有监督模型。

这儿需求留意两篇文章代码里的办法,由于负样本采样均是 大局采样,而不是排除正样本之外的负采样,所以均是有必定概率的或许采样到正样本的,也便是说有得样本明明是正样本,被负样本采样算法采到了标记了label 0

由于上一篇 deepwalk 算法理论与实践,图算法之瑞士军刀篇(一) 文章中,整个算法流程是根据 tensorflow 1.x 系列开发的,而我一向很为推崇 tensorflow 2.0 系列的keras 接口,所以本文将 首要阐明 deepwalk 与 tensorflow 2.0 的 keras 实战 进程,别的没啥不同,重视 tensorflow 2.0 接口的同学,能够继续阅读下去了 ~


(1)代码韶光

老规矩,为了保持每一篇文章的 完整性和独立性 ,这儿和上一篇文章的冗余部分,咱们也再次 赘述 一下,异同部分 我会进行文字阐明哈~

老规矩, 开篇先吼一嗓子 , talk is cheap , show me the code !!!

本文的代码讲的是 在图上根据 tensorflow 2.0 的 keras 接口 完成的 deepwalk 算法,整个源码流程是一个小型的 工业可用 的工程,觉得有用赶忙 收藏 转发 吧 ~

(1.1) 导包 (和上文不同)

首先导包,把代码跑起来,只是需求这些python 包就能够了。

@欢迎重视作者大众号算法全栈之路
importio
importitertools
importnumpyasnp
importos
importre
importstring
importtensorflowastf
importtqdm
importrandom
importpandasaspd
importnetworkxasnx
fromjoblibimportParallel,delayed
importtensorflowastf
fromtensorflow.keras.layersimport*
fromtensorflow.keras.modelsimportModel

这儿导入的包和上一篇文章里略有不同,留意仿制这儿的代码~


(1.2) 数据准备 (和上文同)

留意,咱们这儿的代码适用于同构图,当然你用同构图来处理异构图问题也是能够的,或许作用更好呢 ~

@欢迎重视作者大众号算法全栈之路
graph_df=pd.DataFrame([['Tom','pig',],['Nancy','Tom'],['Jack','Nancy']],columns=['src','dst'],index=['0','1','2'])
graph_df['weight']=1.0
print(graph_df)

这儿的节点都是 同一类型 ,根据某一个联系构建的边,这儿 pandas 的 dataframe 是保存的边的 src 和 dst ,以及该边对应的权重,weight 咱们都把设置为 1.0 即可。


(1.3) 同构图节点编码 (和上文同)

老规矩,图节点嘛,进行编码, 数字化 后扔到图结构里去。

@欢迎重视微信大众号:算法全栈之路
#编码办法
defencode_map(input_array):
p_map={}
length=len(input_array)
forindex,eleinzip(range(length),input_array):
#print(ele,index)
p_map[str(ele)]=index
returnp_map
#解码办法
defdecode_map(encode_map):
de_map={}
fork,vinencode_map.items():
#index,ele
de_map[v]=k
returnde_map
print(type(graph_df['src'].values))
#构建encode/decodemap
node_encode_map=encode_map(set(np.append(graph_df['src'].values,graph_df['dst'].values,axis=0)))
node_decode_map=decode_map(node_encode_map)
print(len(node_encode_map))
#运用编码
graph_df['src_node_encoded']=graph_df['src'].apply(lambdae:node_encode_map.get(str(e),-1))
graph_df['dst_node_encoded']=graph_df['dst'].apply(lambdae:node_encode_map.get(str(e),-1))
print(graph_df)

这儿的代码十分通俗易懂,我就不再赘述了。


(1.4) networkx 构图 (和上文同)

@欢迎重视微信大众号:算法全栈之路
G=nx.from_pandas_edgelist(graph_df,'src_node_encoded','dst_node_encoded',['weight'])
print(G)

这儿运用 networkx 进行内存里逻辑图的构建 。 能够从 pandas 以及多种 数据源构建,感兴趣的下去自行百度哈 ~


(1.5) random walk 游走算法采样 (和上文同)

这儿是算法 十分重要 的一块,运用 随机游走算法 random walk 在图上进行节点采样,看代码 ~

@欢迎重视微信大众号:算法全栈之路
defpartition_num(num,workers):
ifnum%workers==0:
return[num//workers]*workers
else:
return[num//workers]*workers+[num%workers]
classRandomWalker:
def__init__(self,G):
"""
:paramG:
"""
self.G=G

defdeepwalk_walk(self,walk_length,start_node):
walk=[start_node]
whilelen(walk)<walk_length:
cur=walk[-1]
cur_nbrs=list(self.G.neighbors(cur))
iflen(cur_nbrs)>0:
walk.append(random.choice(cur_nbrs))
else:
break

returnwalk
defsimulate_walks(self,num_walks,walk_length,workers=1,verbose=0):
"""
:paramnum_walks:randomwalks的次数(每次都要遍历所有node)
:paramwalk_length:每次randomwalk最大长度
:paramworkers:进程数
:paramverbose:
:return:
"""
G=self.G
nodes=list(G.nodes())
#并行分区数,
results=Parallel(n_jobs=workers,verbose=verbose,)(
delayed(self._simulate_walks)(nodes,num,walk_length)fornumin
partition_num(num_walks,workers))

walks=list(itertools.chain(*results))

#串行采样途径
#walks=self._simulate_walks(nodes,num_walks,walk_length)
print("walks_len:",len(walks))
returnwalks

def_simulate_walks(self,nodes,num_walks,walk_length,):
walks=[]
forindexinrange(num_walks):
#对每一轮
random.shuffle(nodes)
forvinnodes:
walks.append(self.deepwalk_walk(walk_length=walk_length,start_node=v))

returnwalks

#随机游走算法调用
walker=RandomWalker(G)
session_reproduce=walker.simulate_walks(num_walks=6,walk_length=3,workers=2,verbose=1)

这儿,咱们供给了 并行和串行游走 2种办法,留意看上文的代码注释。假如图数据量比较大,建议运用 python多线程 进行途径游走节点采样哈。

留意: 这儿的 session_reproduce 自身便是回来的成果,便是一个 二维数组 ,数组里每一个元素便是一次采样回来的节点序列,也便是一个途径。


(1.6)skip gram 样本结构 (和上文不同)

无论在何时,样本结构都是十分重要的~

@欢迎重视微信大众号:算法全栈之路
sample_list=[]
vocab_size=len(node_encode_map)
window_size=1
negative_samples=5
forsequenceinsequences:
positive_skip,label=tf.keras.preprocessing.sequence.skipgrams(
sequence,
vocabulary_size=vocab_size,
window_size=window_size,
negative_samples=negative_samples)

#这儿positive_skip和label长度一致
forindexinrange(len(positive_skip)):
target_word,context_word=positive_skip[index]
cur_label=label[index]
sample_list.append([target_word,context_word,cur_label])

w2v_sample_df=pd.DataFrame(sample_list,columns=["target","context","label"])
w2v_sample_df.to_csv("supervised_w2v_sample.csv",sep=',',index=False)

留意,这儿的 代码十分重要。 咱们这儿的 vocab_size=len(node_encode_map) 留意这儿,后边导出节点的embeding 的时候要用到 。

关于 sequences 二维数组里的每一个元素也是一个采样序列 ,对这个序列,咱们采用了 tf.keras.preprocessing.sequence.skipgrams 这个接口 进行负采样, 留意这儿 负采样是 大局负采样,有 或许出现正样本

这儿 negative_samples 我挑选了2 。这儿推荐在较小的数据会集一般将 num_ns 设置为 [5, 20] 范围内的整数,而在较大的数据集一般设置为 [2, 5] 范围内整数。


(1.7) 数据batch处理、模型练习与 节点 embeding 导出

@欢迎重视微信大众号:算法全栈之路
w2v_sample_pdf=pd.read_csv("supervised_w2v_sample.csv",sep=',')
labels=w2v_sample_pdf.pop('label')
batch_size=32
buffer_size=1000
embedding_dim=16
train_dataset=tf.data.Dataset.from_tensor_slices((w2v_sample_pdf.values,labels.values))
train_dataset=train_dataset.shuffle(len(w2v_sample_pdf)).batch(batch_size)
train_dataset=train_dataset.cache().prefetch(buffer_size=buffer_size)
classWord2Vec(object):
def__init__(self,train_data,inverse_map,epoch=3,embedding_file="./embeding_file.csv",vocab_size=1000):
self.embedding_dim=embedding_dim
self.build_model()
self.train_data=train_data
self.epochs=epoch
self.word_embedding_file=embedding_file
self.vocab_size=vocab_size
self.inverse_map=inverse_map
#构建word2vec模型
defbuild_model(self):
inputs=Input(shape=(2,))
target=inputs[:,0:1]
context=inputs[:,1:2]
self.words_embedding_in=tf.keras.layers.Embedding(
vocab_size,
self.embedding_dim,
input_length=1,
name="word_embedding_in"
)
self.words_embedding_out=tf.keras.layers.Embedding(
vocab_size,
self.embedding_dim,
input_length=1,
name="word_embedding_out"
)
word_emb=self.words_embedding_in(target)#batch_size,1,embeing_size
context_emb=self.words_embedding_out(context)
dots=tf.keras.layers.Dot(axes=(2,2))([word_emb,context_emb])
outputs=tf.keras.layers.Flatten()(dots)
self.model=Model(inputs,outputs)
self.model.compile(
optimizer='adam',
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
#模型练习
deftrain(self):
self.model.fit(self.train_data,epochs=self.epochs)
defsave_word_embeddings(self):
withopen(self.word_embedding_file,'w')asf:
#f.write('{}{}\n'.format(self.vocab_size,self.embedding_dim))
weights=self.words_embedding_in.get_weights()[0]
foriinrange(self.vocab_size):
emb=weights[i,:]
line='{}{}\n'.format(self.inverse_map[i],','.join([str(x)forxinemb]))
f.write(line)
word2vec=Word2Vec(train_dataset,embedding_file="./embeing.csv",vocab_size=vocab_size,inverse_map=node_decode_map)
word2vec.train()
word2vec.save_word_embeddings()

这儿由于是 tensorflow 2.0 keras 版别的代码,数据的 batch 化 运用了 tf.data.Dataset 相关的接口,算是十分简练了。

留意这儿的导出 节点embeding 的办法, weights = self.words_embedding_in.get_weights()[0] 这儿直接取出了 words_embedding_in 作为 embeding 。

由于 模型里的完成 words_embedding_in 和 words_embedding_out 两个 variable ,能够别离取出这两个embeding 求均匀作用更好哦

剩下的便是模型的练习了,常规代码,我就不在进行赘述了。

最终代码跑起来便是这个样子:

图上 deepwalk 算法理论 与 tensorflow keras 实战,图算法之瑞士军刀篇(二)

导出的embeding 长这个样子:

图上 deepwalk 算法理论 与 tensorflow keras 实战,图算法之瑞士军刀篇(二)

到这儿,图上deepwalk 算法理论与tensorflow keras实战,图算法之瑞士军刀篇(二) 的全文就写完了。这也是 短时间内图算法系列的最终一篇文章 了,接下来再有一篇文章 总结下图算法就完事了。

上面的代码demo 在环境没问题的情况下,全部 仿制到一个python文件 里,就能够完美运行起来。本文的代码是一个 小型的商业能够用的算法工程项目 ,希望能够对你有参阅作用 ~


码字不易,觉得有收成就动动小手转载一下吧,你的支撑是我写下去的最大动力 ~

更多更全更新内容,欢迎重视作者的大众号: 算法全栈之路

图上 deepwalk 算法理论 与 tensorflow keras 实战,图算法之瑞士军刀篇(二)

  • END –