圖(graph)神經(jīng)網(wǎng)絡--LGCN大圖卷積網(wǎng)絡(tensorflow版代碼分析)

LGCN(Large-Scale Learnable Graph Convolutional Networks )

GitHub項目(LGCN[tensorflow版])

該項目做的任務仍是圖中節(jié)點分類問題,語料仍是Cora
圖總共的節(jié)點數(shù)量是2708個
為了適應大規(guī)模圖的情況,LGCN提出了一種子圖訓練策略迟蜜,將采樣的子圖放入一個小批處理中。
Large-Scale Learnable Graph Convolutional Networks
將圖結(jié)構數(shù)據(jù)變換到網(wǎng)格狀數(shù)據(jù)中植酥,使用傳統(tǒng)的一維卷積進行卷積。變換的方式是:針對每個特征的大小悬襟,對鄰居結(jié)點進行排序缓窜,取這個特征前k大的數(shù)作為它鄰居這列特征的k個值管闷。如果鄰居不夠粥脚,那就用0來補。這樣就能得到該頂點的鄰居信息包个,組成一個矩陣刷允,然后使用一維卷積。

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


1.下載代碼碧囊,本地調(diào)試
將代碼正確部署到本地

2.模型超參配置
模型超參配置
3.模型初始化----數(shù)據(jù)加載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()  ###數(shù)據(jù)加載
        self.configure_networks()  ###配置網(wǎng)絡
        self.train_summary = self.config_summary('train')
        self.valid_summary = self.config_summary('valid')
        self.test_summary = self.config_summary('test')

4.數(shù)據(jù)加載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:]
數(shù)據(jù)類型

self.adj === mtarix === [2078树灶,2078] (圖頂點數(shù)*圖頂點數(shù),整個大圖的鄰接矩陣)
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.模型初始化----配置網(wǎng)絡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')  ###輸入數(shù)據(jù)的特征
        self.labels = tf.placeholder(tf.int32, [None, self.conf.class_num], name='labels') ###輸入數(shù)據(jù)的標簽正確答案 
        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進行神經(jīng)元丟棄
    outs = dropout(outs, keep_r, is_train, scope+'/drop2')  ###輸入的特征矩陣也進行神經(jīng)元丟棄
    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')  ###激活函數(shù)處理
    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擴充一個維度,由原來的(?,?)變?yōu)榱??,1,?)
    adj_m = tf.expand_dims(adj_m, axis=1, name=scope+'/expand1')
    ###fea_m擴充一個維度阐虚,由原來的(?,32)變?yōu)榱??,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.模型損失函數(shù)的定義

    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  ###分類預測損失+參數(shù)正則化損失
            self.accuracy_op = ops.masked_accuracy(self.preds, self.labels, self.labels_mask)

8.訓練過程,輸入數(shù)據(jù)
可見蚌卤,在給模型輸入數(shù)據(jù)的時候实束,根據(jù)中心點的個數(shù)和訓練批次的大小奥秆,大圖就被劃分為小圖處理了

    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] ###將歸一化的大圖鄰接矩陣進行了縮小化處理
                ###訓練模型時,真正的輸入數(shù)據(jù)
                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]})
網(wǎng)絡層參數(shù)的形狀

indices size:--------------> 644
indices size:--------------> 1484
indices size:--------------> 2190
9.大圖化小的操作
此處是通過廣度優(yōu)先咸灿,擴展節(jié)點數(shù)量

def get_indice_graph(adj, mask, size, keep_r=1.0):
    indices = mask.nonzero()[0]  ###獲取非0標記的有效數(shù)據(jù)索引构订,是train則有140個非零標記索引
    if keep_r < 1.0:
        indices = np.random.choice(indices, int(indices.size*keep_r), False)
    pre_indices = set()
    indices = set(indices)  ###將數(shù)據(jù)索引有數(shù)組形式,轉(zhuǎn)換為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])

給定一個圖亥宿,我們先采樣出一些初始頂點。從它們開始砂沛,我們使用廣度優(yōu)先搜索算法烫扼,迭代地將鄰接頂點擴充到子圖內(nèi)。經(jīng)過一定次數(shù)的迭代后碍庵,初始頂點的高階鄰居頂點就會被加進去映企。注意,我們在算法中使用一個簡單的參數(shù)Nm静浴。實際上在每個迭代中堰氓,我們將Nm設置為了不同的值。
大圖化小的算法苹享,與以上get_indice_graph()代碼相對應

隨機劃分子圖的例子

隨機的切分子圖豆赏,可以在大尺度的圖上訓練深層模型。此外富稻,充分利用mini-batch訓練方法可以加速學習過程掷邦。在每輪訓練中,可以使用子圖訓練方法采樣多個子圖椭赋,然后把它們放到batch中抚岗。對應的特征向量和鄰接矩陣組成了網(wǎng)絡的輸入。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哪怔,一起剝皮案震驚了整個濱河市宣蔚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌认境,老刑警劉巖胚委,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叉信,居然都是意外死亡亩冬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硅急,“玉大人覆享,你說我怎么就攤上這事∮啵” “怎么了撒顿?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荚板。 經(jīng)常有香客問我凤壁,道長,這世上最難降的妖魔是什么跪另? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任拧抖,我火速辦了婚禮,結(jié)果婚禮上罚斗,老公的妹妹穿的比我還像新娘徙鱼。我一直安慰自己,他們只是感情好针姿,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布袱吆。 她就那樣靜靜地躺著,像睡著了一般距淫。 火紅的嫁衣襯著肌膚如雪绞绒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天榕暇,我揣著相機與錄音蓬衡,去河邊找鬼。 笑死彤枢,一個胖子當著我的面吹牛狰晚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缴啡,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壁晒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了业栅?” 一聲冷哼從身側(cè)響起秒咐,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碘裕,沒想到半個月后携取,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡帮孔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年雷滋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡惊豺,死狀恐怖燎孟,靈堂內(nèi)的尸體忽然破棺而出禽作,到底是詐尸還是另有隱情尸昧,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布旷偿,位于F島的核電站烹俗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏萍程。R本人自食惡果不足惜幢妄,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茫负。 院中可真熱鬧蕉鸳,春花似錦、人聲如沸忍法。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饿序。三九已至勉失,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間原探,已是汗流浹背乱凿。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咽弦,地道東北人徒蟆。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像型型,于是被迫代替她去往敵國和親段审。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353