算法全栈之路

V1

2023/01/13阅读:27主题:自定义主题1

看这里,使用docker部署图深度学习框架GraphLearn使用说明

看这里,使用docker部署图深度学习框架GraphLearn使用说明


最近几年,图深度学习(Graph DNN) 火的如火如荼,图以其强大的 关系建模 能力和 可解释 能力,逐步在Embedding 算法设计技术中展露头角。在以前的两篇文章 graphSage还是HAN ?吐血力作综述Graph Embeding 经典好文一文揭开图机器学习的面纱,你确定不来看看吗 ? 中,作者分别对图的基础知识和 Graph Embeding 进行了讲解,让我们对图的基础概念有了大致的了解。其中,文章末尾有推荐到使用训练图深度的学习模型算法需要借助在 深度学习框架 之上开发的 图深度学习框架 这项技术。

大家都知道 tensorflow 或则 pytorch 是现在非常流行的两种 DNN深度学习框架,而实现 Graph Embeding 算法则需要使用 图深度学习/机器学习框架。现实学习工作中,很多人对tensorflow 的使用有着非常大的偏爱,这里作者也是不能免俗气,非常乐意使用基于tensorflow相关的产品。在作者开始了解基于图深度学习框架的时候,就发现了Graph learn 这个产品。

GraphLearn 以前也叫 AliGraph, 是阿里巴巴于2020年左右开源的一款简化 图神经网络(Graphic Nuaral Network, GNN) 应用的新框架。该框架底层兼容 tensorflow 和pytorch, 能够 基于docker进行环境搭建,非常方便上手。

但是作者在实际实践的过程中,却实实在在的踩了一堆坑,很多地方按照官方的文档确实没办法对框架进行使用,所以就自己摸索了一番,整理了一下搜集到的资料和框架使用相关的知识分享给大家~


(1) GraphLearn 的docker过程

注意:graph learn 框架部署成功的一款配置参数是:

   python===2.7
   tensorflow==1.13.1

采用 pip2 install xxx 进行python软件包的安装 。


(1.1) 常用文档

在这里贴上 graph learn 相关的一些文档链接:

官方文档 : https://graph-learn.readthedocs.io/zh_CN/latest/zh_CN/gl/install.html

Github路径: https://github.com/alibaba/graph-learn/blob/master/README_cn.md

阿里云开发者社区文档: https://developer.aliyun.com/article/752630

从上面的文档中,我们可以了解到更多graph learn 使用相关的底层使用相关的东西。


(1.2) docker 安装使用Graph Learn

当然,使用graph learn也可以自己去安装配置,但是中途坑太多并且很多 错误太隐晦 ,不知道去哪里查找。所以这里推荐使用docker进行安装使用。

关于 docker 工具本身的安装可以参考作者以前的一篇文章: docker 快速实战指南 ,里面详细介绍了dock使用的基础知识。


(1.2.1) 拉取 graph learn镜像

我们可以使用以下命令去docker hub中拉取阿里巴巴官方打包好的镜像:

sudo docker pull graphlearn/graphlearn:1.0.0-tensorflow1.13.0rc1-cpu

这里以基于tensorflow 的 cpu 版本为栗。 要使用gpu的版本的,也可以拉取 1.0.0-torch1.8.1-cuda10.2-cudnn7 相关版本,从名字我们可以看到它仅仅支持torch。暂时作者也没有找到支持tensorflow 的GPU版本,其中 docker hub 的链接如下:https://hub.docker.com/ , 搜索关键子是:graphlearn/graphlearn

执行完这个命令之后,我们就在本机器上可以看到当前镜像了。如下图所示:


(1.2.2) 启动 graph learn 实例

上一步我们已经拉取好镜像了,下面我们可以使用下面的命令启动它:

sudo docker run -d -it --name graph_m -v /home/local_machine/workspace:/root/workspace graphlearn/graph-learn

注意: 这里我们可以把我们要使用的代码和数据放入到当前的目录中,通过虚拟卷的映射绑定映射到docker内部。

在后文,我会把自己使用代码和数据上传,读者可以把放到自己本地机器的 /home/local_machine/workspace 路径下。 如下图所示:


(1.2.3) 进入graph leanr 实例

因为我们需要运用docker 装好的环境,所以我们是需要登录到docker内部去执行命令训练模型的。 这里我们可以采这个命令进入 docker实例 :

sudo docker exec -it 6c1c2dda75f9 /bin/bash

如下图所示:


(1.2.4) 开始使用 graph learn 框架训练图机器学习模型

在这里,我们把训练模型需要的数据下载到 自己机器上的 /home/local_machine/workspace 这个路径下(可以改为你设置的路径,上面第二条-v 冒号前面的那个路径也要同步改掉)。

然后进入到镜像里面,开始训练模型了,输入数据和执行的代码选择 /root/workspace 这个路径。 然后执行:

python train_tg_unsupervised.py

这样,就可以开始我们的模型训练之旅了。

训练效果图如下:

注意: 我们这里是使用镜像的graph learn 环境,使用我们的数据,训练自己的模型。

当然,很多同学大概率会遇到这个问题:

如果出现这个问题,不用怀疑,大概率是数据(节点或则边)的格式不对,并且大概率是因为中间的TAB空格不对。这里必须是 Linux下的TAB键,如果同学你用多个空格键代替TAB键,那恭喜同学,你会收到意想不到的惊喜,不说了,这些教训都是血与泪!!!


(2) 代码时光

talk is cheap , show me the code !!!

下面的代码是使用tensorflow来构建模型结构,中间支持了 metapath节点采样,支持图上的 node embeding导出。 在下面提供了完整的代码和数据下载的方式,代码的注解非常详细,具有极高的参考价值与可重用性,有问题欢迎讨论~

该工程完整代码和训练数据,可以去算法全栈之路公众号回复 “阿里图框架源码” 下载。


@ 欢迎关注微信公众号:算法全栈之路
# -*- coding: utf-8 -*-


from __future__ import print_function
import graphlearn as gl
import tensorflow as tf
import graphlearn.python.nn.tf as tfg
from ego_sage import EgoGraphSAGE


def load_graph(config):
    # 数据涵盖顶点数据和边数据,顶点数据和边均可以有属性,并且顶点种类和边种类可以有多个,只是type字段不能相同
    # 读取图参数,构建一个 Graph 逻辑对象,后续所有的操作都在这个 Graph 对象上进行。
    # GL图数据格式灵活,支持float,int,string类型的属性,支持带权重、标签。
    # 数据源载入和拓扑描述:同构、异构、多数据源,通通支持
    # GL提供了 node 和 edge 两个简单的接口来支持顶点和边的数据源载入,同时在这两个接口中描述图的拓扑结构,比如“buy”边的源顶点类型是“user”,
    # 目的顶点类型是“item”。这个拓扑结构在异构图中十分重要,是后续采样路径meta-path的依据,也是查询的实体类型的基础。
    data_dir = config['dataset_folder']
    g = gl.Graph() \
        .node(data_dir + 'unsupervised_node.txt', node_type='i',
              decoder=gl.Decoder(attr_types=['float'] * config['features_num'],
                                 attr_dims=[0] * config['features_num'])) \
        .edge(data_dir + 'unsupervised_edge.txt', edge_type=('i''i''train'),
              decoder=gl.Decoder(weighted=True), directed=False)
    return g


def meta_path_sample(ego, ego_name, nbrs_num, sampler):
    # 构建 meta path 采样器
    """ creates the meta-math sampler of the input ego.
    config:
      ego: A query object, the input centric nodes/edges
      ego_name: A string, the name of `ego`.
      # 邻居个数,每一跳 选择几个邻居
      nbrs_num: A list, the number of neighbors for each hop.

      sampler: A string, the strategy of neighbor sampling.
    "
""
    # src_hop_i
    # 下游访问的时候根据alist 去访问
    alias_list = [ego_name + '_hop_' + str(i + 1) for i in range(len(nbrs_num))]
    for nbr_count, alias in zip(nbrs_num, alias_list):
        # 这里将ego采样后赋值给ego,完成二跳的采样
        # 在下游用alias进行数据访问
        ego = ego.outV('train').sample(nbr_count).by(sampler).alias(alias)
    return ego


def query(graph, config):
    # 拿到所有 label 叫做 train 的边,然后进行 batch 的shuffle
    seed = graph.E('train').batch(config['batch_size']).shuffle(traverse=True)

    # 得到 batch 之后的边, outv 是出顶点, src 是 out 对应的顶点
    # 当前batch 所有的边里所有的出另一个顶点,因为是有向图,所以有区分出度入度
    src = seed.outV().alias('src')

    # inv ,整个图是入度图, dst 是 in 对应的顶点
    # 当前batch里顶点的 ,所有的入顶点
    dst = seed.inV().alias('dst')

    # 使用 alias 可以在下游访问当前次查询输出的结果
    # 每种采样操作都有不同的实现策略,例如随机、边权等
    neg_dst = src.outNeg('train').sample(config['neg_num']).by(config['neg_sampler']).alias('neg_dst')

    # 异构图, meta_path 采样, 这个 [10,5 ] 作为
    # 输入 当前批次所有的 src,然后进行采样
    src_ego = meta_path_sample(src, 'src', config['nbrs_num'], config['sampler'])
    # 输入当前批次所有dst ,进行采样
    dst_ego = meta_path_sample(dst, 'dst', config['nbrs_num'], config['sampler'])

    # GL 也提供了负采样,只需要将示例中的 outV 改为 outNeg 即可
    # 输出 outNeg ,采样 neg_num 条, in_degree , 根据入度进行采样
    # 负采样,基于输入顶点,对不直接连接的顶点进行采样。负采样经常被用作监督学习的重要工具。
    dst_neg_ego = meta_path_sample(neg_dst, 'neg_dst', config['nbrs_num'], config['sampler'])

    # 一批一批的边的选择值,values是sink操作,表示查询已经结束
    return seed.values()


def train(graph, model, config):
    # config train 训练 模式,超参
    tfg.conf.training = True

    # 根据 config 从图上按批次获得数据,并得到对应的一些采样数据
    query_train = query(graph, config)

    # 窗口,当前batch ,因为是根据边采样,所以下游的src和dst 的embeding 应该是一一对应
    # graphlearn.Dataset接口,用于将 Query 的结果构造为 Numpy 组成的graphlearn.Nodes / graphlearn.Edges或graphlearn
    # window. The data set will be prefetched asynchronously, window is the size of the prefetched data.
    # 完成 numpy 到 tensor 的转换
    dataset = tfg.Dataset(query_train, window=10)

    # 得到 起始边 和 结束边
    # Origanizes the data dict as EgoGraphs and then check and return the specified `EgoGraph`.
    # 将包含固定大小样本的 GSL 结果转换为 EgoGraph 格式。您可以 EgoGraph 通过别名获得 ego graph
    src_ego = dataset.get_egograph('src')
    dst_ego = dataset.get_egograph('dst')
    neg_dst_ego = dataset.get_egograph('neg_dst')

    # 在图上跑模型,得到 三个 logit
    # 这里模型的forword其实就是结点特征的聚合,聚合完了之后就计算得到logit
    src_emb = model.forward(src_ego)
    dst_emb = model.forward(dst_ego)
    neg_dst_emb = model.forward(neg_dst_ego)
    # use sampled softmax loss with temperature.
    # 温度值,应该是相似度到sigmoid 值之间的那个转化方式
    # 无监督类型网络,有边相连的,就比较近, 没有边相连的,随机采样,距离应该比较远
    loss = tfg.unsupervised_softmax_cross_entropy_loss(src_emb, dst_emb, neg_dst_emb,
                                                       temperature=config['temperature'])
    return dataset.iterator, loss


# node_embedding(g, u_model, 'u', config)
def node_embedding(graph, model, node_type, config):
    """ save node embedding.
    Args:
      node_type: 'u' or 'i'.
    Return:
      iterator, ids, embedding.
    "
""
    tfg.conf.training = False
    ego_name = 'save_node_' + node_type
    seed = graph.V(node_type).batch(config['batch_size']).alias(ego_name)
    nbrs_num = config['nbrs_num'if node_type == 'u' else config['nbrs_num']

    query_save = meta_path_sample(seed, ego_name, config['nbrs_num'], config['sampler']).values()
    dataset = tfg.Dataset(query_save, window=1)
    ego_graph = dataset.get_egograph(ego_name)
    emb = model.forward(ego_graph)
    return dataset.iterator, ego_graph.src.ids, emb


def dump_embedding(sess, iter, ids, emb, emb_writer):
    sess.run(iter.initializer)
    while True:
        try:
            outs = sess.run([ids, emb])
            # [B,], [B,dim]
            feat = [','.join(str(x) for x in arr) for arr in outs[1]]
            for id, feat in zip(outs[0], feat):
                emb_writer.write('%d\t%s\n' % (id, feat))
        except tf.errors.OutOfRangeError:
            print('Save node embeddings done.')
            break


def run(config):
    # graph input data
    # load 已经定义好的图
    g = load_graph(config=config)
    # GL提供单机的版本, 通过init 接口快速启动Graph Engine,至此,图对象已经构造完毕
    # 查询、采样操作就可以在 Graph 上进行了。
    g.init()

    # Define Model
    # 根据读入的参数进行模型各层的参数,初始第一层是每个结点的特征个数或则embeding 维度。
    # 后面层是对邻居结点做卷积或则说是每2跳之间结点特征聚合的过程,每次聚合需要用到2跳结点的特征.
    # dims = [128, 128, 10, 5, 128 ]
    dims = [config['features_num']] + [config['hidden_dim']] * (len(config['nbrs_num']) - 1) + [config['output_dim']]
    # graphsage,隐藏层参数的多少
    # 定义一个模型,该模型内部,进行了featurecolumn 的 embeding化
    # 这个维度第一层是features_nums,也就是结点的初始特征值,初始特征可能需要使用featurecolumn 经过transform,
    # 初始特征是经过多层的聚合,维度会逐渐变小,图上的每个结点是需要初始的embeding的,或则很多维数特征
    # 每一个 embeding ,经过几层神经网络之后,维度变成 1dim,计算出 logit,这里看 ego_gnn里的源码
    # graphsage,图迭代几次,就使用几跳的结点数据进行sage操作
    model = EgoGraphSAGE(dims,
                         agg_type=config['agg_type'],
                         dropout=config['drop_out'])

    # train
    iterator, loss = train(g, model, config)

    optimizer = tf.train.AdamOptimizer(learning_rate=config['learning_rate'])
    train_op = optimizer.minimize(loss)
    train_ops = [loss, train_op]

    # 保存下来embeding
    i_save_iter, i_ids, i_emb = node_embedding(g, model, 'i', config)

    with tf.Session() as sess:
        sess.run(tf.local_variables_initializer())
        sess.run(tf.global_variables_initializer())
        sess.run(iterator.initializer)
        step = 0
        print("Start Training...")
        for i in range(config['epoch']):
            try:
                while True:
                    ret = sess.run(train_ops)
                    print("Epoch {}, Iter {}, Loss {:.5f}".format(i, step, ret[0]))
                    step += 1
            except tf.errors.OutOfRangeError:
                sess.run(iterator.initializer)  # reinitialize dataset.

        print("Start saving embeddings...")
        i_emb_writer = open('i_emb.txt''w')
        i_emb_writer.write('id:int64\temb:string\n')
        dump_embedding(sess, i_save_iter, i_ids, i_emb, i_emb_writer)

    g.close()


if __name__ == "__main__":
    # 模型训练超参
    config = {'dataset_folder''./data/tg_graph/',
              'batch_size': 2,
              'features_num': 3,
              'hidden_dim': 3,
              'output_dim': 3,
              'nbrs_num': [1],
              'neg_num': 1,
              'learning_rate': 0.0001,
              'epoch': 1,
              'agg_type''mean',
              'drop_out': 0.0,
              'sampler''random',
              'neg_sampler''in_degree',
              'temperature': 0.07
              }

    run(config)

到这里,快看!使用docker部署图深度学习框架GraphLearn使用说明 就写完了,有问题欢迎留言讨论哦~


宅男民工码字不易,你的关注是我持续输出的最大动力。

接下来作者会继续分享学习与工作中一些有用的、有意思的内容,点点手指头支持一下吧~

欢迎扫码关注作者的公众号: 算法全栈之路

- END -

分类:

人工智能

标签:

深度学习

作者介绍

算法全栈之路
V1

欢迎关注:算法全栈之路