作者:十方

说到GNN,各位炼丹师会想到哪些算法呢?不管想到哪些算法,我们真正用到过哪些呢?确实我们可能都看过GNN相关论文,但是还是缺乏实战经验的.特别是对于推荐系统而言,我们又该如何应用这些模型呢?下面我们就从DeepWalk这篇论文开始,先讲原理,再讲实战,最后讲应用.

GNN相关背景知识

GNN的本质,是要学习网络中每个节点的表达的,这些潜在的表达对图中每个节点的“社交”关系进行了编码,把离散值的节点编码成稠密向量,后续可用于分类回归,或者作为下游任务的特征.Deepwalk充分利用了随机游走提取的“句子”,去学习句子中每个单词的表达.Deepwalk原文就提到了在数据稀疏的情况下可以把F1-scores提升10%,在一些实验中,能够用更少的训练数据获得了更好的效果.看下图的例子:

Deepwalk

问题定义:先把问题定义为给社交网络中的每个节点进行分类,图可以表达为G=<V,E>,V就是图上所有节点,E是所有边.有一部分有label的数据GL=(V,E,X,Y),X就是节点的特征,Y就是分类的label.在传统机器学习算法中,我们都是直接学习(X,Y),并没有充分利用节点间的依赖关系.Deepwalk可以捕捉图上的拓扑关系,通过无监督方法学习每个节点的特征,学到的图特征和标签的分布是相互独立的.

随机游走:选定一个根节点,“随机”走出一条路径,基于相邻的节点必然相似,我们就可以用这种策略去挖掘网络的社群信息.随机游走很方便并行,可以同时提取一张图上各个部分的信息.即使图有小的改动,这些路径也不需要重新计算.和word的出现频率类似,通过随机游走得到的节点访问频率都符合幂律分布,所以我们就可以用NLP相关方法对随机游走结果做相似处理,如下图所示:

所以在随机游走后,我们只需要通过下公式,学习节点向量即可:

该公式就是skip-gram,通过某个节点学习它左右的节点.我们都知道skip-gram用于文本时的语料库就是一个个句子,现在我们提取图的句子.如下所示:

算法很简单,把所有节点顺序打乱(加速收敛),然后遍历这些节点随机游走出序列,再通过skipgram算法去拟合每个节点的向量.如此反复.注:这里的随机是均匀分布去随机.当然有些图会有些“副产物”,比如用户浏览网页的顺序,可以直接输入到模型.

接下来我们看下deepwalks的核心代码:

# 代码来源
# https://github.com/phanein/deepwalk
# Random walk
with open(f, 'w') as fout:for walk in graph.build_deepwalk_corpus_iter(G=G, # 图num_paths=num_paths, # 路径数path_length=path_length, # 路径长度alpha=alpha, #rand=rand): #fout.write(u"{}\n".format(u" ".join(v for v in walk)))class Graph(defaultdict):"""Efficient basic implementation of nx这里我们看到,该类继承defaultdict,图其实可以简单的表示为dict,key为节点,value为与之相连的节点"""  def __init__(self):super(Graph, self).__init__(list)def nodes(self):return self.keys()def adjacency_iter(self):return self.iteritems()def subgraph(self, nodes={}):# 提取子图subgraph = Graph()for n in nodes:if n in self:subgraph[n] = [x for x in self[n] if x in nodes]return subgraphdef make_undirected(self):#因为是无向图,所以v in self[u]并且 u in self[v]t0 = time()for v in list(self):for other in self[v]:if v != other:self[other].append(v)t1 = time()logger.info('make_directed: added missing edges {}s'.format(t1-t0))self.make_consistent()return selfdef make_consistent(self):# 去重t0 = time()for k in iterkeys(self):self[k] = list(sorted(set(self[k])))t1 = time()logger.info('make_consistent: made consistent in {}s'.format(t1-t0))self.remove_self_loops()return selfdef remove_self_loops(self):# 自已不会与自己有连接removed = 0t0 = time()for x in self:if x in self[x]: self[x].remove(x)removed += 1t1 = time()logger.info('remove_self_loops: removed {} loops in {}s'.format(removed, (t1-t0)))return selfdef check_self_loops(self):for x in self:for y in self[x]:if x == y:return Truereturn Falsedef has_edge(self, v1, v2):# 两个节点是否有边if v2 in self[v1] or v1 in self[v2]:return Truereturn Falsedef degree(self, nodes=None):# 节点的度数if isinstance(nodes, Iterable):return {v:len(self[v]) for v in nodes}else:return len(self[nodes])def order(self):"Returns the number of nodes in the graph"return len(self)    def number_of_edges(self):# 图中边的数量"Returns the number of nodes in the graph"return sum([self.degree(x) for x in self.keys()])/2def number_of_nodes(self):# 图中结点数量"Returns the number of nodes in the graph"return self.order()# 核心代码def random_walk(self, path_length, alpha=0, rand=random.Random(), start=None):""" Returns a truncated random walk.path_length: Length of the random walk.alpha: probability of restarts.start: the start node of the random walk."""G = selfif start:path = [start]else:# Sampling is uniform w.r.t V, and not w.r.t Epath = [rand.choice(list(G.keys()))]while len(path) < path_length:cur = path[-1]if len(G[cur]) > 0:if rand.random() >= alpha:path.append(rand.choice(G[cur])) # 相邻节点随机选else:path.append(path[0]) # 有一定概率选择回到起点else:breakreturn [str(node) for node in path]# TODO add build_walks in heredef build_deepwalk_corpus(G, num_paths, path_length, alpha=0,rand=random.Random(0)):walks = []nodes = list(G.nodes())# 这里和上面论文算法流程对应for cnt in range(num_paths): # 外循环,相当于要迭代多少epochrand.shuffle(nodes) # 打乱nodes顺序,加速收敛for node in nodes: # 每个node都会产生一条路径walks.append(G.random_walk(path_length, rand=rand, alpha=alpha, start=node))return walksdef build_deepwalk_corpus_iter(G, num_paths, path_length, alpha=0,rand=random.Random(0)):# 流式处理用walks = []nodes = list(G.nodes())for cnt in range(num_paths):rand.shuffle(nodes)for node in nodes:yield G.random_walk(path_length, rand=rand, alpha=alpha, start=node)def clique(size):return from_adjlist(permutations(range(1,size+1)))
# http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
def grouper(n, iterable, padvalue=None):"grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)def parse_adjacencylist(f):adjlist = []for l in f:if l and l[0] != "#":introw = [int(x) for x in l.strip().split()]row = [introw[0]]row.extend(set(sorted(introw[1:])))adjlist.extend([row])return adjlistdef parse_adjacencylist_unchecked(f):adjlist = []for l in f:if l and l[0] != "#":adjlist.extend([[int(x) for x in l.strip().split()]])return adjlistdef load_adjacencylist(file_, undirected=False, chunksize=10000, unchecked=True):if unchecked:parse_func = parse_adjacencylist_uncheckedconvert_func = from_adjlist_uncheckedelse:parse_func = parse_adjacencylistconvert_func = from_adjlistadjlist = []t0 = time()total = 0 with open(file_) as f:for idx, adj_chunk in enumerate(map(parse_func, grouper(int(chunksize), f))):adjlist.extend(adj_chunk)total += len(adj_chunk)t1 = time()logger.info('Parsed {} edges with {} chunks in {}s'.format(total, idx, t1-t0))t0 = time()G = convert_func(adjlist)t1 = time()logger.info('Converted edges to graph in {}s'.format(t1-t0))if undirected:t0 = time()G = G.make_undirected()t1 = time()logger.info('Made graph undirected in {}s'.format(t1-t0))return G def load_edgelist(file_, undirected=True):G = Graph()with open(file_) as f:for l in f:x, y = l.strip().split()[:2]x = int(x)y = int(y)G[x].append(y)if undirected:G[y].append(x)G.make_consistent()return Gdef load_matfile(file_, variable_name="network", undirected=True):mat_varables = loadmat(file_)mat_matrix = mat_varables[variable_name]return from_numpy(mat_matrix, undirected)def from_networkx(G_input, undirected=True):G = Graph()for idx, x in enumerate(G_input.nodes()):for y in iterkeys(G_input[x]):G[x].append(y)if undirected:G.make_undirected()return Gdef from_numpy(x, undirected=True):G = Graph()if issparse(x):cx = x.tocoo()for i,j,v in zip(cx.row, cx.col, cx.data):G[i].append(j)else:raise Exception("Dense matrices not yet supported.")if undirected:G.make_undirected()G.make_consistent()return Gdef from_adjlist(adjlist):G = Graph()for row in adjlist:node = row[0]neighbors = row[1:]G[node] = list(sorted(set(neighbors)))return Gdef from_adjlist_unchecked(adjlist):G = Graph()for row in adjlist:node = row[0]neighbors = row[1:]G[node] = neighborsreturn G

至于skipgram,大家可以直接用gensim工具即可.

from gensim.models import Word2Vec
from gensim.models.word2vec import Vocablogger = logging.getLogger("deepwalk")class Skipgram(Word2Vec):"""A subclass to allow more customization of the Word2Vec internals."""def __init__(self, vocabulary_counts=None, **kwargs):self.vocabulary_counts = Nonekwargs["min_count"] = kwargs.get("min_count", 0)kwargs["workers"] = kwargs.get("workers", cpu_count())kwargs["size"] = kwargs.get("size", 128)kwargs["sentences"] = kwargs.get("sentences", None)kwargs["window"] = kwargs.get("window", 10)kwargs["sg"] = 1kwargs["hs"] = 1if vocabulary_counts != None:self.vocabulary_counts = vocabulary_countssuper(Skipgram, self).__init__(**kwargs)

应用

在推荐场景中,无论是推荐商品还是广告,用户和item其实都可以通过点击/转化/购买等行为构建二部图,在此二部图中进行随机游走,学习每个节点的向量,在特定场景,缺乏特征和标签的情况下,可以通过user2user或者item2iterm的方式,很好的泛化到其他的标签.GNN提取的向量也可以用于下游双塔召回模型或者排序模型.如果有社交网络,通过挖掘人与人直接的关系提取特征,供下游任务也是个不错的选择.当然大家也可以尝试在一些推荐比赛中用于丰富特征.


往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑温州大学《机器学习课程》视频
本站qq群851320808,加入微信群请扫码:

GNN大有可为,从这篇开始学以致用相关推荐

  1. 听说GNN大有可为,从这篇开始学以致用

    作者:十方 说到GNN,各位炼丹师会想到哪些算法呢?不管想到哪些算法,我们真正用到过哪些呢?确实我们可能都看过GNN相关论文,但是还是缺乏实战经验的.特别是对于推荐系统而言,我们又该如何应用这些模型呢 ...

  2. 大有可为的GNN:DeepWalk

    说到GNN,各位炼丹师会想到哪些算法呢?不管想到哪些算法,我们真正用到过哪些呢?确实我们可能都看过GNN相关论文,但是还是缺乏实战经验的.特别是对于推荐系统而言,我们又该如何应用这些模型呢?下面我们就 ...

  3. 深度之眼Paper带读笔记GNN.05.TransE/H/R/D

    文章目录 前言 论文结构 导读 研究背景 Trans系列算法概述 数据集简介 研究成果 研究意义 摘要 论文结构 论文精读 知识树 算法模型总览 算法系列图谱 Notation 细节一:TransE模 ...

  4. CNN已老,GNN来了!清华大学孙茂松组一文综述GNN

    来源:新智元 本文约3500字,建议阅读7分钟. 本文将带你了解UCI数据库的Python API,通过实际案例拆解并讲解代码. [ 导读 ]深度学习无法进行因果推理,而图模型(GNN)或是解决方案之 ...

  5. CNN已老,GNN来了

    深度学习无法进行因果推理,而图模型(GNN)或是解决方案之一.清华大学孙茂松教授组发表综述论文,全面阐述GNN及其方法和应用,并提出一个能表征各种不同GNN模型中传播步骤的统一表示. 深度学习的最大软 ...

  6. 腾讯AI Lab联合清华,港中文长文解析图深度学习的历史、最新进展到应用

    本文作者: 腾讯:荣钰.徐挺洋.黄俊洲:清华大学:黄文炳:香港中文大学:程鸿 前言 人工智能领域近几年历经了突飞猛进的发展.图像.视频.游戏博弈.自然语言处理.金融等大数据分析领域都实现了跨越式的进步 ...

  7. 推荐收藏!图深度学习发展历史、最新进展与应用

    本文经机器之心授权转载. [导读]本文将分图神经网络历史.图神经网络的最新研究进展和图神经网络的应用进展三大部分归纳总结该课程 Theme II: Advances and Applications ...

  8. 揭秘阿里巴巴神奇的人物抠图算法内幕

    (欢迎关注"我爱计算机视觉"公众号,一个有价值有深度的公众号~) 电商环境中,商品的图片展示比文字展示对顾客购买有更直观的吸引力,尤其在购买衣服时.阿里巴巴的百万卖家各个都是ps大 ...

  9. 除了阿里云服务器,还有什么便宜的服务器值得推荐?

    国内云服务器大厂,除了阿里云服务器,还有腾讯云服务器,华为云服务器这两家: 虽说阿里云目前市场规模第一,但得益于腾讯云在文娱游戏.视频等业务传统优势,其云产品表现也非常优异: 华为云是世界500强企业 ...

最新文章

  1. Linux下批量重命名文件名为数字索引编号(0~N.xxx)的方法
  2. SAP Spartacus StoreFeatureModule
  3. java 内存快照怎么看_jvm内存快照dump文件太大,怎么分析
  4. 算法篇【枚举2 -- 生理周期】
  5. crammd5 php,使用CRAMMD5的SMTP身份验证
  6. AttributeError: module 'ahocorasick' has no attribute 'Automaton'解决
  7. 【渝粤题库】陕西师范大学209020 史记研究 作业(专升本)
  8. 全国哀悼日 网站变灰装(附代码)
  9. 资深项目经理推荐的几款免费/开源项目管理工具
  10. 搜索邻近计算机打印机,在活动目录中设置和管理发布打印机
  11. HTML实现两行两列单元表
  12. 音频技术及行业的发展
  13. 新手之使用FileZilla完整教程
  14. sqlserver2000企业版安装
  15. php non numeric,讲解php 出现Warning: A non-numeric value encountered问题的原因及解决方法...
  16. Android菜鸟笔记-实现一键重启和关机
  17. Python开发者年度报告:7个角度解读Python使用现状、趋势与未来
  18. 《快把我哥哥带走》“骗”和“抢”才是真实的相处之道
  19. ajax jqxhr,jquery ajax 之 jqXHR 和 Data Types详解
  20. Java并发专题 带返回结果的批量任务执行 CompletionService ExecutorService invo

热门文章

  1. mongodb备份恢复,数据导入导出
  2. 关于memcached
  3. mysql中事件失效如何解决
  4. Linux网络技术管理
  5. 白话SpringCloud | 第五章:服务容错保护(Hystrix)
  6. 神经网络(1)--Non-linear hypotheses,为什么我们要学习神经网络这种算法来处理non-linear hypotheses...
  7. doubleClick-v2-as3.0 学习笔记(2)--Video相关
  8. 多個excel文件合并到一個excel文件
  9. 如何测试机房的速度和带宽?
  10. 库函数设计:关于文件类打开操作函数不自动创建不存在的父目录问题