图(graph)神经网络--LGCN大图卷积网络(tensorflow版代码分析)

LGCN(Large-Scale Learnable Graph Convolutional Networks )

GitHub项目(LGCN[tensorflow版])

该项目做的任务仍是图中节点分类问题,语料仍是Cora
图总共的节点数量是2708个
为了适应大规模图的情况,LGCN提出了一种子图训练策略,将采样的子图放入一个小批处理中。
Large-Scale Learnable Graph Convolutional Networks
将图结构数据变换到网格状数据中,使用传统的一维卷积进行卷积。变换的方式是:针对每个特征的大小,对邻居结点进行排序,取这个特征前k大的数作为它邻居这列特征的k个值。如果邻居不够,那就用0来补。这样就能得到该顶点的邻居信息,组成一个矩阵,然后使用一维卷积。

Large-Scale Learnable Graph Convolutional Networks(https://davidham3.github.io/blog/2018/09/17/large-scale-learnable-graph-convolutional-networks/)看这篇文章的描述


1.下载代码,本地调试
将代码正确部署到本地

2.模型超参配置
模型超参配置
3.模型初始化----数据加载self.process_data()
class GraphNet(object):

    def __init__(self, sess, conf):
        self.sess = sess
        self.conf = conf
        if not os.path.exists(conf.modeldir):
            os.makedirs(conf.modeldir)
        if not os.path.exists(conf.logdir):
            os.makedirs(conf.logdir)
        self.process_data()  ###数据加载
        self.configure_networks()  ###配置网络
        self.train_summary = self.config_summary('train')
        self.valid_summary = self.config_summary('valid')
        self.test_summary = self.config_summary('test')

4.数据加载process_data()

    def process_data(self):
        data = load_data('cora')
        adj, feas = data[:2]
        self.adj = adj.todense()
        self.normed_adj = preprocess_adj(adj)
        self.feas = preprocess_features(feas, False)
        self.y_train, self.y_val, self.y_test = data[2:5]
        self.train_mask, self.val_mask, self.test_mask = data[5:]
数据类型

self.adj === mtarix === [2078,2078] (图顶点数*图顶点数,整个大图的邻接矩阵)
self.normed_adj === matrix === [2708,2708] (归一化的大图邻接矩阵)
self.feas === matrix === [2078,1433] (所有顶点的特征向量组成的特征矩阵)
self.y_xxxx === ndarray === [2078,7] (顶点的类别标签one-hot向量)


self.y_xxxx标签向量one-hot

5.模型初始化----配置网络self.configure_networks()

    def configure_networks(self):
        self.build_network()
        self.cal_loss()
        optimizer = self.get_optimizer(self.conf.learning_rate)
        self.train_op = optimizer.minimize(self.loss_op, name='train_op')
        self.seed = int(time.time())
        tf.set_random_seed(self.seed)
        self.sess.run(tf.global_variables_initializer())
        trainable_vars = tf.trainable_variables()
        self.saver = tf.train.Saver(var_list=trainable_vars, max_to_keep=0)
        if self.conf.is_train:
            self.writer = tf.summary.FileWriter(self.conf.logdir, self.sess.graph)
        self.print_params_num()

    def build_network(self):
        self.labels_mask = tf.placeholder(tf.int32, None, name='labels_mask')  ###需要遮掩的标签
        self.matrix = tf.placeholder(tf.int32, [None, None], name='matrix')  ###大图邻接矩阵
        self.normed_matrix = tf.placeholder(tf.float32, [None, None], name='normed_matrix')  ###大图归一化处理的邻接矩阵
        self.inputs = tf.placeholder(tf.float32, [None, self.feas.shape[1]], name='inputs')  ###输入数据的特征
        self.labels = tf.placeholder(tf.int32, [None, self.conf.class_num], name='labels') ###输入数据的标签正确答案 
        self.is_train = tf.placeholder(tf.bool, name='is_train')
        self.preds = self.inference(self.inputs)  ###将inputs特征输入模型进行推断,输出self.preds预测标签

    def inference(self, outs):
        ###第一次卷积(一层卷积,simple_conv)
        outs = getattr(ops, self.conf.first_conv)(
            self.normed_matrix, outs, 4*self.conf.ch_num, self.conf.adj_keep_r,
            self.conf.keep_r, self.is_train, 'conv_s', act_fn=None)
        ###第二次卷积(两层卷积,graph_conv)
        for layer_index in range(self.conf.layer_num):
            cur_outs= getattr(ops, self.conf.second_conv)(
                self.normed_matrix, outs, self.conf.ch_num, self.conf.adj_keep_r,
                self.conf.keep_r, self.is_train, 'conv_%s' % (layer_index+1),
                act_fn=None, k=self.conf.k)
            outs = tf.concat([outs, cur_outs], axis=1, name='concat_%s' % layer_index)
        ####第三次卷积(一层卷积,simple_conv)
        outs = ops.simple_conv(
            self.normed_matrix, outs, self.conf.class_num, self.conf.adj_keep_r,
            self.conf.keep_r, self.is_train, 'conv_f', act_fn=None, norm=False)
        return outs

6.simple_conv()
adj_m.shape==(?,?) outs.shape==(?,1433) num_out==32
adj_keep_r==0.999 keep_r==0.16

def simple_conv(adj_m, outs, num_out, adj_keep_r, keep_r, is_train, scope,
                act_fn=tf.nn.elu, norm=True, **kw):
    adj_m = dropout(adj_m, adj_keep_r, is_train, scope+'/drop1')  ###将邻接矩阵按照概率为0.999进行神经元丢弃
    outs = dropout(outs, keep_r, is_train, scope+'/drop2')  ###输入的特征矩阵也进行神经元丢弃
    outs = fully_connected(outs, num_out, scope+'/fully', None)  ###全连接
    outs = tf.matmul(adj_m, outs, name=scope+'/matmul')  ###矩阵相乘
    #if norm:
    #    outs = batch_norm(outs, is_train, scope=scope+'/norm', act_fn=None)
    outs = outs if not act_fn else act_fn(outs, scope+'/act')  ###激活函数处理
    return outs

7.graph_conv()
adj_m.shape==(?,?) outs.shape==(?,32) num_out==8
adj_keep_r==0.999 keep_r==0.16 k=8(程序开始之前,人为设置的值为8)

def graph_conv(adj_m, outs, num_out, adj_keep_r, keep_r, is_train, scope, k=5,
               act_fn=tf.nn.relu6, **kw):
    num_in = outs.shape[-1].value
    adj_m = dropout(adj_m, adj_keep_r, is_train, scope+'/drop1')
    outs = top_k_features(adj_m, outs, k, scope+'/top_k')  ###提取top8的特征
    outs = dropout(outs, keep_r, is_train, scope+'/drop1')
    outs = conv1d(outs, (num_in+num_out)//2, (k+1)//2+1, scope+'/conv1', None, True)
    outs = act_fn(outs, scope+'act1') if act_fn else outs
    outs = dropout(outs, keep_r, is_train, scope+'/drop2')
    outs = conv1d(outs, num_out, k//2+1, scope+'/conv2', None)
    outs = tf.squeeze(outs, axis=[1], name=scope+'/squeeze')
    return batch_norm(outs, True, scope+'/norm2', act_fn)

def top_k_features(adj_m, fea_m, k, scope):
    ###adj_m扩充一个维度,由原来的(?,?)变为了(?,1,?)
    adj_m = tf.expand_dims(adj_m, axis=1, name=scope+'/expand1')
    ###fea_m扩充一个维度,由原来的(?,32)变为了(?,32,1)
    fea_m = tf.expand_dims(fea_m, axis=-1, name=scope+'/expand2')
    ###feas.shape==(?,32,?)
    feas = tf.multiply(adj_m, fea_m, name=scope+'/mul')
    ###feas.shape==(?,32,?)
    feas = tf.transpose(feas, perm=[2, 1, 0], name=scope+'/trans1')
    ###top_k.shape==(?,32,8)
    top_k = tf.nn.top_k(feas, k=k, name=scope+'/top_k').values
    #pre, post = tf.split(top_k, 2, axis=2, name=scope+'/split')
    ###top_k.shape==(?,32,9)
    top_k = tf.concat([fea_m, top_k], axis=2, name=scope+'/concat')
    ###top_k.shape==(?,9,32)
    top_k = tf.transpose(top_k, perm=[0, 2, 1], name=scope+'/trans2')
    return top_k

7.模型损失函数的定义

    def cal_loss(self):
        with tf.variable_scope('loss'):
            self.class_loss = ops.masked_softmax_cross_entropy(
                self.preds, self.labels, self.labels_mask)
            self.regu_loss = 0
            for var in tf.trainable_variables():
                self.regu_loss += self.conf.weight_decay * tf.nn.l2_loss(var)
            self.loss_op = self.class_loss + self.regu_loss  ###分类预测损失+参数正则化损失
            self.accuracy_op = ops.masked_accuracy(self.preds, self.labels, self.labels_mask)

8.训练过程,输入数据
可见,在给模型输入数据的时候,根据中心点的个数和训练批次的大小,大图就被划分为小图处理了

    def pack_trans_dict(self, action):
        feed_dict = {
            self.matrix: self.adj, self.normed_matrix: self.normed_adj,
            self.inputs: self.feas}
        if action == 'train':
            feed_dict.update({
                self.labels: self.y_train, self.labels_mask: self.train_mask,
                self.is_train: True})
            if self.conf.use_batch:
                ###batch_size=2500  indices=644
                indices = get_indice_graph(
                    self.adj, self.train_mask, self.conf.batch_size, 1.0)
                new_adj = self.adj[indices,:][:,indices]  ###将大图邻接矩阵进行了缩小化处理
                new_normed_adj = self.normed_adj[indices,:][:,indices] ###将归一化的大图邻接矩阵进行了缩小化处理
                ###训练模型时,真正的输入数据
                feed_dict.update({
                    self.labels: self.y_train[indices],
                    self.labels_mask: self.train_mask[indices],
                    self.matrix: new_adj, self.normed_matrix: new_normed_adj,
                    self.inputs: self.feas[indices]})
网络层参数的形状

indices size:--------------> 644
indices size:--------------> 1484
indices size:--------------> 2190
9.大图化小的操作
此处是通过广度优先,扩展节点数量

def get_indice_graph(adj, mask, size, keep_r=1.0):
    indices = mask.nonzero()[0]  ###获取非0标记的有效数据索引,是train则有140个非零标记索引
    if keep_r < 1.0:
        indices = np.random.choice(indices, int(indices.size*keep_r), False)
    pre_indices = set()
    indices = set(indices)  ###将数据索引有数组形式,转换为set集合形式
    while len(indices) < size:   ###此处的size是batch_size批次大小:2500
        new_add = indices - pre_indices
        if not new_add:
            break
        pre_indices = indices
        candidates = get_candidates(adj, new_add) - indices  ###获取候选集索引,并排除以前的索引,是train则找到504个新的索引
        if len(candidates) > size - len(indices):  ###确保不能超过batch_size=2500个索引
            candidates = set(np.random.choice(list(candidates), size-len(indices), False))
        indices.update(candidates)  ###跟新索引,是train则索引总共是140+504=644个索引
    print('indices size:-------------->', len(indices))
    return sorted(indices)

def get_candidates(adj, new_add):
    return set(adj[sorted(new_add)].sum(axis=0).nonzero()[1])

给定一个图,我们先采样出一些初始顶点。从它们开始,我们使用广度优先搜索算法,迭代地将邻接顶点扩充到子图内。经过一定次数的迭代后,初始顶点的高阶邻居顶点就会被加进去。注意,我们在算法中使用一个简单的参数Nm。实际上在每个迭代中,我们将Nm设置为了不同的值。
大图化小的算法,与以上get_indice_graph()代码相对应

随机划分子图的例子

随机的切分子图,可以在大尺度的图上训练深层模型。此外,充分利用mini-batch训练方法可以加速学习过程。在每轮训练中,可以使用子图训练方法采样多个子图,然后把它们放到batch中。对应的特征向量和邻接矩阵组成了网络的输入。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269