GraphSage与DGL完成同构图 Link 猜测,浅显易懂好文强推
书接上文,在作者曾经的文章 graphSage仍是HAN ?吐血力作总述Graph Embeding 经典好文 和 一文揭开图机器学习的面纱,你确认不来看看吗 ,咱们现已了解到 图机器学习/深度学习 的根底常识,咱们知道了图数据结构是由 节点和边 组成,这是图在 数据结构与数学中 的界说,咱们一般把图用来进行 杂乱场景中的联系建模 。但是在算法工程师们实践运用图的进程中,图又处于什么角色呢? 让咱们在接下来的一段时间一起来深入其间具体揭秘吧~
当咱们用 图结构 来对现实中的事物进行联系建模的时分,则 节点 可所以任何同构图或则异构图中占据 联系二元性 一点的item ID( 无论怎么杂乱的集体联系均能够把拆解成两两之间多种联系的组合 ),这个item可所以 微博交际 场景中的用户,也可所以 京东淘宝电商购买 场景中的产品或买家与卖家。甚至在反常检测的场景中,咱们也会把设备id, ip 等特点构建成异构图节点的一种,究竟在 互联网上调集在很大概率上就意味着反常 , 而风控中特点的调集更为常见。如下图所示:
而在 一文揭开图机器学习的面纱,你确认不来看看吗 一文中,咱们也介绍了图的多种分类,不熟悉的同学能够先阅览一下上文。
其间有触及了 同构图和异构图 的概念,文中说:图中节点类型和边类型超越两种的图称为异构图 ,而图中的边在实践建模进程中就用来表明两种item之间的联系。就像上面微博交际同构图中用户的朋友联系,以及京东淘宝京东电商中的买家与产品的购买联系。这儿咱们要区别一点便是异构多重图中,两类同2个item之间的联系能够有多种,就像你能够购买了产品,一起你也能够搜藏了产品,那你和同一种产品之间是有两条边的。
一般实践运用中,咱们直接从 用户行为日志 中提炼出 两者或两类item ID 之间的联系,例如:用户和发生行为的产品。直接用这样的联系用来构建图中的边,而这两者或两类item ID则用来构建成图中的极点。
咱们在这儿 一再着重构图,主要是由于咱们练习模型大多数时分便是在 学习图中特征的空间结构联系信息,所以咱们一定要 精心设计图的节点和边,不然到最终模型效果欠好在从头找原因,就比较麻烦了。
而在 graphSage仍是HAN ?吐血力作总述Graph Embeding 经典好文 中,咱们也大致介绍了现阶段在图上进行的主流机器学习使命,除了整图猜测中的 图读出 ( graph readout ,GR, 整图节点输出为一个归纳的图表明embeding)外,咱们更多的是去进行 同构图或异构图 中 节点和边 的 分类回归 使命以及 链接猜测 。下面,就让我从最简单也最常用的同构图上 Link 猜测使命开端吧~
留意:咱们这儿说的链接 Link 猜测,也称 联系猜测,意图是猜测 两节点之间有是否有边存在 。而 边分类与回归,则是去猜测边是属于哪种类型的边,以及去猜测边上的特点值。例如:图上的边分类与回归猜测的可所以购买了衣服仍是搜藏的衣服,以及会买几件的这个数值。
(1) 图上有监督与无监督使命的区别
咱们知道,一般咱们说的 有监督学习 是依据外界对数据特征打的标签来进行学习,是一种给定了答案的学习, 在练习时它的 输入是特征(Feature) 而 输出是标签(Label), 咱们期望模型能从特征中学习到 标签束缚 下数据形式,让模型知道再次遇到类似的数据应该给它打什么标签。而 无监督学习 ,望文生义,外界没有对数据特征限制应该学习什么,而是希望模型能在数据会集找出 数据集固有的规律 ,让模型在数据会集进行 自学 ,在练习时它的输出数据一般便是输入数据集本身。而在图上的使命也是如此,如下图所示:
关于 图上的有监督学习 ,就像节点分类使命,咱们会给节点一个标签,例如反常检测中,给定一个用户是否反常,然后进行图机器学习/深度学习的练习,让模型依据该练习会集用户的标签学习怎么给测验会集没见过的用户打标签,这儿的标签是外界输入给定的,模型学习的是标签束缚下的数据表达,一般图上的有监督机器学习使命包括:节点分类与回归,边的分类与回归 。而 图上的无监督学习 ,则是不会外界给图上的节点或边标签,而仅仅让模型去学习图结构本身的结构信息,没有外界先验常识的辅导,图上的有监督机器学习使命一般包括:链接猜测。
熟悉深度学习 模型源码 的同学更是一眼能够看出有监督与无监督模型的不同: 关于有监督学习,一般只需求运用界说好的模型进行前向传达核算,并经过在练习节点上/或边上比较猜测和真实标签来核算丢失,然后完结 反向传达 。而关于无监督学习,其学习的是数据集本身,迁移到图上也便是图结构本身的信息,一般咱们根据假定在图结构中,互相之间有联系的或则 图上挨着比较近的则节点的embeding比较类似,而没有联系的和不挨着的则embeding比较远 。
看到这儿许多同学是不是有种比较熟悉的感觉,没错,这和前面文章 深入浅出了解word2vec模型 (理论与源码分析) 里介绍的 word2vec 原理极端类似,这种假定称为 同向偏好假定 ( 反之 便是 异向偏好,例如氨基酸更倾向于在不同类别之间树立起链接。这儿暂不展开)。
咱们 细看丢失 会发现: 图上有监督学习是和标签比较核算出丢失然后进行回传进行的练习。而图上无监督学习则是节点间互相有链接的或联系较近的为正样本,没有链接的则基本上没有什么联系 ,一般的做法是直接进行 全局负采样得到。
(2) Link猜测浅显了解
在说链接猜测之前,咱们需求对 图上跑深度学习算法 有一个 初步感知 ,而且默许正在阅览的同学是看过上面3篇前史文章的,这儿提到的布景常识上述文章里都有。下面的内容要 敲小黑板了,留意了留意了~
咱们知道: 图是由节点和边构建而成的空间结构,而且对错欧结构。在 GNN 或 GCN系列 的模型中,每个节点均有自己的Embeding , 而经过消息传递,每个节点把自己的信息沿着边Copy存入到街坊节点的 MailBox,然后每个节点均能够从自己的MailBox拿到街坊发送过来的Embeding ,然后依据某种办法进行自己Embedding 和 街坊节点的Embeding 进行聚合。这个真能够了解是“沿着网线来把信息传给你”~
一般选用的 聚合办法 一般是 Mean、Max 等办法, 一起,由于多个街坊信息构成一个 序列 ,则咱们能够用Rnn、Lstm 等办法来进行聚合。更进一步,咱们能够考虑 各节点自适应重要性 的Din Attention , Transform等办法来进行聚合。
咱们能够自己挑选运用 几跳的街坊 以及 练习全图几个Epoch 。其间在当时某一个Epoch中,每一跳街坊就相当于以当时种子节点为中心的 广度优先 遍历,从周围一圈中选取节点聚合到当时节点。而练习全图几个Epoch 则是在上一轮现已迭代更新过的每个节点的embeding上 再次做一遍 街坊聚合的进程。
从这咱们能够看到: 拿到街坊信息来更新本身 ,在某种意义上能够让隔得比较近的节点更趋同,这个有好也有坏。
优点 是契合咱们练习图模型的根底事务目标:学习出图中节点的联系,而这种联系的语义信息则包含在embeding 中。
但一起,缺点 便是:咱们如果练习时分收集的街坊过多,或则全图更新练习的epoch过多,则会导致节点间区别能力不强,embedding 趋同,也便是过滑润问题。
更细节的说,练习一个链接猜测模型触及到比对种子节点和相连节点之间的得分以及种子节点与恣意一个节点之间的得分的差异。例如,给定一条衔接 和 的边,一个好的模型希望 和 之间的得分要高于 和从一个恣意的噪声散布 ′∼() 中所采样的节点 ′ 之间的得分。其间从恣意的噪声散布种挑选街坊节点的办法称作 图数据的负采样。
链接猜测虽然是无监督学习,但是其在现在许多互联网大厂中,用的是 极端广泛 的,咱们仅仅需求依据用户行为日志构建好图即可,然后让模型自己去学习各种数据特征本身的结构联系,其产出的中心成果embedding 能够用于下游众多的事务与使命。
而本文介绍的代码也能够进行 文本语义的 类似性学习 ,例如:咱们现已运用word2vec练习好了一个词典各个词语的embeding , 咱们能够挑选修改间隔断的词语构建边,然后来让模型去交融修改间隔限制的情况下词语间的语义联系,这何尝不是一种新的测验呢。
在前史 graphSage仍是HAN ?吐血力作总述Graph Embeding 经典好文 结尾中,咱们推荐了 亚马逊的 DGL( Deep Graph Library ) 的 图深度学习结构,其间文官方文档完善而又浅显易懂,代码明晰且又简单了解,所以今后本系列根据图的文章的 中心代码 均根据DGL结构编写,下面让咱们开端根据同构图的链接猜测的代码时刻吧~
(3) 代码时光
看曾经的读者留言说,前史文章的代码均会集在文章的最最终面,不利于阅览 ,许多时分代码看不懂的部分也没有注解。鉴于读者的布景常识与研究方向各异,今后咱们改动一下文章的组织形式,理论结合代码交叉讲解 ,究竟代码才是程序员表达自己思维的最好语言,不是吗? so , 让咱们开端coding吧 ~
留意: 咱们这儿阐明的代码是 根据dgl 和 graphsage来完成的同构图上的链接联系的猜测 。
老规矩,开篇先吼一嗓子 , talk is cheap , show me the code !!!
(3.1) 导包
首先,咱们导入本文中图深度学习所需求用的若干python包
import torch
import torch.nn as nn
import dgl
import dgl.nn.pytorch.conv as conv
import torch.nn as nn
import torch.nn.functional as F
import dgl.function as fn
import numpy as np
from dgl import save_graphs, load_graphs
其间,dgl是 根据pytorch开发 的 图深度学习结构,numpy作为咱们初始的数据输入, conv为dgl完成的卷积核算子。 下面开端正式的代码讲解。
(3.2) 图界说和节点与边特征赋值
首先,要在图上进行链接猜测使命,咱们需求构建咱们自己的逻辑图,这儿选用dgl的图深度学习结构构建。咱们要知道:在dgl结构中,构建图是以边的调集来进行图的界说的。具体如下所示:
src = np.random.randint(0, 100, 500)
dst = np.random.randint(0, 100, 500)
# 一起树立反向边
graph = dgl.graph((np.concatenate([src, dst]), np.concatenate([dst, src])))
print(graph)
# 图中节点的数量是DGL经过给定的图的边列表中最大的点ID揣度所得出的
能够看到: 由于是根据边的调集进行图的构建,src则是边的起点,dst是边的终点。
留意: dgl的 最新版本 中,有向图与无向图以相同的界说办法界说。其间,有向图只用输入一个[src,dst]数据即可,而无向图则需求输入两组边的极点数组,也能够运用 bgraph = dgl.to_bidirected(graph)
来完成同样的功用。
接着 ,在结构了好了图之后,咱们也能够灵敏的给节点和边增加特征以及标签数据。代码如下:
# 树立点和边特征,以及边的标签
graph.ndata['feature'] = torch.randn(100, 10)
graph.edata['feature'] = torch.randn(1000, 10)
graph.edata['label'] = torch.randn(1000) # 当然咱们也能够给节点和边赋予一些特征。这儿用不上,仅仅做为demo
这儿的 graph.ndata 与graph.edata 则是分别给图的节点和边赋值的进程。
留意:节点和边内部的名称别重复 ,这儿咱们选用的办法得到的 embeding是不会跟着网络练习而更新 的,在文章最终会介绍原因以及介绍能够跟着网络更新的界说节点embeding特征的办法。
其间, 在 dgl结构 里,这儿的特征的特点取值目前仅答应运用数值类型(如单精度浮点型、双精度浮点型和整型)的特征,这些特征可所以 标量、向量或多维张量 。
(3.3)模型结构界说
经过上面两步,图的逻辑结构现已有了,能够在图上跑咱们的深度学习模型算法了。下面,让咱们开端界说咱们的模型结构吧~
@ 欢迎重视微信大众号:算法全栈之路
# coding:utf-8
class SAGE(nn.Module):
def __init__(self, in_feats, hid_feats, out_feats):
super().__init__()
# 实例化SAGEConv,这儿调用官方sageconv算子in_feats是输入特征的维度,out_feats是输出特征的维度,aggregator_type是聚合函数的类型
# 这个函数中完结了每个节点聚合街坊节点发送来的信息的进程,官方算子中心有多个全衔接层参数是参与了模型练习的。起到不同节点聚合街坊的个性化的调节作用。
self.conv1 = conv.SAGEConv(
in_feats=in_feats, out_feats=hid_feats, aggregator_type='mean')
self.conv2 = conv.SAGEConv(
in_feats=hid_feats, out_feats=out_feats, aggregator_type='mean')
def forward(self, graph, inputs):
# 输入是节点的特征
h = self.conv1(graph, inputs)
h = F.relu(h)
h = self.conv2(graph, h)
return h
# 下面是运用点积核算边得分的比如。
class DotProductPredictor(nn.Module):
def forward(self, graph, h):
# 这儿是依据每条边的两个端点的躲藏向量的点积dot来核算边存在与否的score
with graph.local_scope():
graph.ndata['h'] = h
graph.apply_edges(fn.u_dot_v('h', 'h', 'score'))
return graph.edata['score']
class Model(nn.Module):
def __init__(self, in_features, hidden_features, out_features):
super().__init__()
self.sage = SAGE(in_features, hidden_features, out_features)
self.pred = DotProductPredictor()
# 模型主题函数,调用sage子模型是2层的sageconv算子叠加的,然后得到sage子模块的输出躲藏向量。
# 这儿的躲藏向量现已涵盖有聚合街坊的信息了。然后分别将pos样本和neg样本输入pred模型得到两个打分。
# 这儿的g表明的是原生的逻辑图,咱们自己构建起来的,而neg_g是对每个种子节点负采样后得到的子图。而在这儿x是输入的一切节点的feature。
def forward(self, g, neg_g, x):
h = self.sage(g, x)
# 留意这儿多了一个回来,pos logit 和 neg logit
return self.pred(g, h), self.pred(neg_g, h)
留意这儿,咱们选用的是graphSage的办法来进行的链接猜测。
首先这个 模型的骨架 是 model类界说的,其间调用了sage类和pred类。
关于sage类,sage类中又调用了2层官方的sageConv算子,在这个算子内部完结了每个节点聚合街坊节点发送来的信息的进程,官方算子中心有多个全衔接层参数是参与了模型练习的,能够起到不同节点聚合街坊的个性化的调节作用。
关于pred类,咱们观其大概应该知道,它其实是在算pos图和neg图的丢失,然后让 pos图的打分挨近1 ,然后neg图的打分挨近0,以达到上面咱们说的 同向偏好假定 所界说的丢失联系。
(3.4) 模型负采姿态图和丢失函数
书接上文,咱们能够看到 model类练习的时分需求输入 neg_g ,这个便是对 种子节点负采样 得到的子图。 这也契合咱们上文着重过的无监督算法的主要意义:图上的无监督算法,现实存在的边便是正样本,而负样本是需求对每个种子正样本进行采样得到的。咱们这儿是选用的 随机采样 的办法。
如下面代码所示:
@ 欢迎重视微信大众号:算法全栈之路
# coding:utf-8
# 触及到对不存在的边的采样进程,负采样。由于上述的得分猜测模型在图上进行核算,用户需求将负采样的样本表明为另外一个图,其间包含一切负采样的节点对作为边。
# 下面的比如展示了将负采样的样本表明为一个图。每一条边 (,) 都有 个对应的负采样样本 (,),其间 是从均匀散布中采样的。
def construct_negative_graph(graph, k):
src, dst = graph.edges()
neg_src = src.repeat_interleave(k)
neg_dst = torch.randint(0, graph.num_nodes(), (len(src) * k,))
return dgl.graph((neg_src, neg_dst), num_nodes=graph.num_nodes())
咱们能够看到,这儿 K表明 每个正样本采样 几条边 作为负样本,而且 负边的起点依然是种子节点,仅仅终点端点是随机散布中采样 得到的。
另外,咱们核算分别得到 pos图和neg图 的得分之后,需求核算最终的丢失,咱们这儿没有选用有监督的交叉熵丢失,而是选用了间隔丢失。
# 间隔丢失,练习的循环部分里会重复构建负采样图并核算丢失函数值
def compute_loss(pos_score, neg_score):
n_edges = pos_score.shape[0]
return (1 - pos_score.unsqueeze(1) + neg_score.view(n_edges, -1)).clamp(min=0).mean()
间隔丢失 的 中心思维也便是:让pos边打分越高越好,而让neg边打分越低越好。 由于最终咱们是用梯度下降(Gradient Ddescent,GD)的算法来优往小的方向去优化loss, 所以在公式里咱们能够看到加了 1 - pos_score
这一项,契合优化的基本原则。
留意:由于一个正样本对应多个负样本,这儿调整了张量的shape,应用了张量的播送机制。
(3.5) 模型练习与 node embeding 导出
到这儿,咱们就能够开端模型的练习了。模型练习需求的的全局超参以及练习进程代码如下:
@ 欢迎重视微信大众号:算法全栈之路
# 模型练习进程
node_features = graph.ndata['feature']
# 模型特征输入维度
n_features = node_features.shape[1]
# 负采样条数
k = 5
model = Model(n_features, 100, 8)
# 优化器
opt = torch.optim.Adam(model.parameters())
for epoch in range(3):
# 采样得到neg graph
negative_graph = construct_negative_graph(graph, k)
pos_score, neg_score = model(graph, negative_graph, node_features)
# 核算丢失
loss = compute_loss(pos_score, neg_score)
# 梯度优化
opt.zero_grad()
loss.backward()
opt.step()
print(loss.item())
这儿的代码逻辑很明晰而且代码很简单,我就不再赘述了。到这儿,咱们就能够成功的练习模型完结了。
最终一步,node embeding 导出。
由于咱们是链接联系猜测,真实线上运用的时分,咱们需求拿到 两个节点的embeding进行点积核算链接概率 。因而,咱们需求导出中心的embeding。 咱们能够运用下面的办法导出节点的embedding ,而且输出第0个节点node 的embedding:
# 输出node embeding
# 练习后,节点表明能够经过以下代码获取。
node_embeddings = model.sage(graph, node_features)
print(node_embeddings[0])
练习进程与embedding导出 的终端输出成果如下:
当然,咱们也能够把图和模型保存下来,供今后重复运用。dgl 图 和 pytorch模型保存的代码如下:
from dgl import save_graphs, load_graphs
# 图数据和模型保存
save_graphs("graph.bin", [graph])
torch.save(model.state_dict(), "model.bin")
the last last last , 这儿面有一个很重要的细节便是: 代码里咱们是根据外界输入的 随机生成的node feature 的embeding ,然后 运用GraphSage 的办法进行 街坊节点的聚合特性 的学习,能够滑润街坊节点的类似性。但是这儿,模型仅仅起到滑润和交融街坊节点的作用,并 不会改动原始的node feature 的embeding 。 读者能够把各个node 的feature打印出来进行验证。所以在上文中,咱们得到模型交融过街坊节点的躲藏表明的时分,是选用的 model.sage(graph, node_features)
的办法,这儿输出的才是咱们想要的embeding,由于它中心经过了 模型运算得到的躲藏层 信息。
如果咱们希望模型能够直接 改动输入的node feature 的 embeding 而且 直接导出 就能够运用,让它也能够直接进行 链接联系Link 的predict 的话,咱们创立 node feature 的时分,能够选用
embed=nn.Parameter(torch.Tensor(g.number_of_nodes, self.embed_size))
的办法来申明变量, 一起,让 优化器在界说优化 的时分,增加进来这一个部分的可练习参数,如下所示:
all_params = itertools.chain(model.parameters(), embed.parameters())
optimizer = torch.optim.Adam(all_params, lr=0.01, weight_decay=0)
到这儿,GraphSage完成同构图 Link 猜测 ,浅显易懂好文强推 的全文就写完了。 上面的代码demo 在环境没问题的情况下,全部复制到一个python文件里,就能够完美运转起来。接下来会针对更具体的图上使命进行更多的源码阐明~
写小作文 真是不简单,写代码果然是最简单的~。曾经仅仅进行模型的运用,真实要去写文章的时分,太多的东西是源码上的进程,文字描述太难了! 哎,再接再厉吧,加深了解,加强文字的表达能力,go on !!!!
宅男民工码字不易,你的重视是我持续输出的最大动力。
接下来作者会继续分享学习与工作中一些有用的、有意思的内容,点点手指头支撑一下吧~
欢迎扫码重视作者的大众号: 算法全栈之路