2020.8.5 GAT代码解析【tensorflow】

init.py

from .gat import GAT#从gat.py里面引用GAT这个函数
from .sp_gat import SpGAT#spGAT表示的是稀疏GAT,也是一个引用

base_gattn.py

#attention层的损失函数和训练函数
import tensorflow as tf

class BaseGAttN:
    def loss(logits, labels, nb_classes, class_weights):#损失函数的定义
        """tf.reduce_sum()用于计算张量tensor沿着某一维度的和,可以在求和后降维
        tf.reduce_mean():计算tensor指定轴方向上的所有元素的平均;
        tf.reduce_max():计算tensor指定轴方向上的各个元素的最大值;
        tf.reduce_all():计算tensor指定轴方向上的各个元素的逻辑和(and运算);
        tf.reduce_any():计算tensor指定轴方向上的各个元素的逻辑或(or运算);
        tf.multiply()两个矩阵中对应元素各自相乘
        tf.one_hot()函数是将input转化为one-hot类型数据输出,相当于将多个数值联合放在一起作为多个相同类型的向量,
        可用于表示各自的概率分布,通常用于分类任务中作为最后的FC层的输出,有时翻译成“独热”编码。
indices = [0, 1, 2]  #输入数据(是个向量)需要编码的索引是[0,1,2]
depth = 3
tf.one_hot(indices, depth)  # output: [3 x 3]
# [[1., 0., 0.],
#  [0., 1., 0.],
#  [0., 0., 1.]]"""
        sample_wts = tf.reduce_sum(tf.multiply(tf.one_hot(labels, nb_classes), class_weights), axis=-1)
        xentropy = tf.multiply(tf.nn.sparse_softmax_cross_entropy_with_logits(
                labels=labels, logits=logits), sample_wts)#计算稀疏softmax的交叉熵损失函数
        return tf.reduce_mean(xentropy, name='xentropy_mean')



    def training(loss, lr, l2_coef):
        # weight decay权重衰减
        vars = tf.trainable_variables()
        """返回只用trainable=true所创建的所有变量,variable()构造函数会自动将新变量添加到图形集合中去
        tf.add_n()函数就是实现一个列表元素的相加,列表里面的元素可以是向量、矩阵等"""
        lossL2 = tf.add_n([tf.nn.l2_loss(v) for v in vars if v.name not
                           in ['bias', 'gamma', 'b', 'g', 'beta']]) * l2_coef
        """L1范数损失函数,也被称为最小绝对值偏差(LAD),最小绝对值误差(LAE)。总的说来,它是把目标值(Yi)与估计值(f(xi))的绝对差值的总和(S)最小化:
           L2范数损失函数,也被称为最小平方误差(LSE)。总的来说,它是把目标值(Yi)与估计值(f(xi))的差值的平方和(S)最小化:"""

        # optimizer  adam优化器来优化学习率,使得学习率适当的增减
        opt = tf.train.AdamOptimizer(learning_rate=lr)

        # training op 通过minimize函数,用于最小化loss+lossL2(lossL2代表L2正则化的损失函数)
        train_op = opt.minimize(loss+lossL2)
        
        return train_op





    def preshape(logits, labels, nb_classes):
        new_sh_lab = [-1]  #原始的lable
        new_sh_log = [-1, nb_classes]  #原始的logits
        log_resh = tf.reshape(logits, new_sh_log)  #重新得到的logits
        lab_resh = tf.reshape(labels, new_sh_lab) #重新得到的lable
        """tf.reshape(tensor,shape,name=None)函数的作用就是将tensor变换成参数shape的形式
        其中shape为一个列表形式,特殊一点的是列表中可以存在-1,,,,其实也相当于升维,降维的意思"""
        return log_resh, lab_resh

    def confmat(logits, labels):
        preds = tf.argmax(logits, axis=1)  #对于相应标签的预测
        """tf.argmax(input,axis)根据axis取值的不同返回每行或者每列最大值的索引(位置)
        axis=0比较每一列,将其最大元素所在的索引记录下来,最后输出每一列最大元素所在的索引数组
        axis=1比较每一行,将每一行最大元素的......."""
        return tf.confusion_matrix(labels, preds)  #返回标签,以及相应标签的概率

##########################
# Adapted from tkipf/gcn   对16年的GCN进行的优化#
##########################

    def masked_softmax_cross_entropy(logits, labels, mask):
        """Softmax cross-entropy loss with masking.带mask的softmax交叉熵损失函数定义
        logits: 模型的输出,维度(B,C);B是样本量,C是输出维度
        labels: 模型的标签,维度(B,C)
        mask: 掩码,维度(B,)
        logits 先softmax成为概率分布,再和labels计算交叉熵
        losss 维度是(B,)"""
        loss = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels)
        mask = tf.cast(mask, dtype=tf.float32)#tf.cast()类型转换函数,dtype就是转换目标类型
        mask /= tf.reduce_mean(mask)#屏蔽掉某些样本的损失
        loss *= mask#将mask与损失loss进行相乘即可
        return tf.reduce_mean(loss)#返回平均损失


    def masked_sigmoid_cross_entropy(logits, labels, mask):
        """sigmoid cross-entropy loss with masking.带mask的softmax交叉熵损失函数定义
        logits:(B,C),模型输出;B是样本量,C是输出维度
        labels:(B,C),真实标签
        mask: 掩码,维度(B,)
        分类的结果叫label;分割的结果叫mask。因为分割结果通常会半透明的覆盖在待分割目标上,所以就叫它掩膜吧。"""
        labels = tf.cast(labels, dtype=tf.float32)
        loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=labels)
        loss=tf.reduce_mean(loss,axis=1)
        mask = tf.cast(mask, dtype=tf.float32)
        mask /= tf.reduce_mean(mask)
        loss *= mask
        return tf.reduce_mean(loss)

    def masked_accuracy(logits, labels, mask):
        """Accuracy with masking."""
        """equal,相等的意思。顾名思义,就是判断,x, y 是不是相等,它的判断方法不是整体判断,
        而是逐个元素进行判断,如果相等就是True,不相等,就是False。
        由于是逐个元素判断,所以x,y 的维度要一致"""
        correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(labels, 1))
        accuracy_all = tf.cast(correct_prediction, tf.float32)
        mask = tf.cast(mask, dtype=tf.float32)
        mask /= tf.reduce_mean(mask)
        accuracy_all *= mask
        #?为什么都要乘以一个mask呢?
        return tf.reduce_mean(accuracy_all)

    def micro_f1(logits, labels, mask):
        """评测指标 一般都有accuracy recall F1 macro-F1 micro-F1
        这就表明直接将评估指标定义在这里面即可,再在相应的地方改改即可"""
        predicted = tf.round(tf.nn.sigmoid(logits))

        # Use integers to avoid any nasty FP behaviour使用整数来避免FP行为,这个不需要修改
        predicted = tf.cast(predicted, dtype=tf.int32)
        labels = tf.cast(labels, dtype=tf.int32)
        mask = tf.cast(mask, dtype=tf.int32)

        # expand the mask so that broadcasting works ([nb_nodes, 1])
        mask = tf.expand_dims(mask, -1)
        """tf.expand_dims( input,axis=None,name=None, dim=None)
        t2=[2,3,5]   tf.expand_dims(t2,axis=0) ->[1,2,3,5]  0其实代表第一个维度
                     tf.expand_dims(t2,axis=1) ->[2,1,3,5]  1其实代表第二个维度
                     tf.expand_dims(t2,axis=2) ->[2,3,1,5]  2其实代表第三个维度
)"""

        
        # Count true positives, true negatives, false positives and false negatives.
        #主要是为了计算评估指标,因此需要将tp,tn,fp,fn计算出来,以便后续处理
        tp = tf.count_nonzero(predicted * labels * mask)
        tn = tf.count_nonzero((predicted - 1) * (labels - 1) * mask)
        fp = tf.count_nonzero(predicted * (labels - 1) * mask)
        fn = tf.count_nonzero((predicted - 1) * labels * mask)

        # Calculate accuracy, precision, recall and F1 score.
        """这里就是计算各种评估指标,accuracy,precision,recall ,F1 score
        正确率(accuracy)是我们最常见的评价指标,accuracy = (TP+TN)/(P+N),
        这个很容易理解,就是被分对的样本数除以所有的样本数,通常来说,正确率越高,分类器越好;
        
        精度(precision)是精确性的度量,表示被分为正例的示例中实际为正例的比例,precision=TP/(TP+FP)"""
        precision = tp / (tp + fp)
        recall = tp / (tp + fn)
        fmeasure = (2 * precision * recall) / (precision + recall)
        fmeasure = tf.cast(fmeasure, tf.float32)
        return fmeasure

        """这里列举几个分类指标  准确率,精确率,召回率 F1-score ROC  """

gat,py

#这个就是GAT网络的框架
"""
将GAT的网络分为第一层网络,中间层网络,最后一层的网络
就是将每一层结构设置清楚即可,弄清楚其构造即可
"""

import numpy as np
import tensorflow as tf
from utils import layers
from models.base_gattn import BaseGAttN#从base_gattn.py 里面引用BaseGAttN

class GAT(BaseGAttN):
    def inference(inputs, nb_classes, nb_nodes, training, attn_drop, ffd_drop,# inference代表推论     attn代表注意系数,ffd表示前馈神经网络
            bias_mat, hid_units, n_heads, activation=tf.nn.elu, residual=False):#residual 残差
        """inputs:(B,N,D),B是batch_size,N是节点数,D是每个结点的原始特征维度数
        nb_classes: 分类任务的类别数,设为C
        nb_nodes: 节点个数,设为N
        training: 标志”训练阶段“,”测试阶段“  通过==true or false来进行判别
        attn_drop: 注意力矩阵的dropout率,防止过拟合

       (dropout指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃
       经过交叉验证,隐含节点dropout率等于0.5的时候效果最好,原因是0.5的时候dropout随机生成的网络结构最多。
       但是将dropout由固定值变为一个区间,可以提高效果)

        ffd_drop: 输入的dropout率,防止过拟合
        bias_mat: 一个(N,N)的矩阵,由邻接矩阵A变化而来,是注意力矩阵的掩码
        hid_units: 列表,第i个元素是第i层的每个注意力头的隐藏单元数
        n_heads: 列表,第i个元素是第i层的注意力头数
        activation: 激活函数
        residual: 是否进行残差连接
        第一层,有H1个注意力头,每个头的输入都是(B,N,D),每头注意力输出(B,N,F1);
        将所有注意力头的输出聚合,聚合为(B,N,F1*H1)"""

        attns = []
        #n_heads[0]=第一层注意力的头数,设为H1
        for _ in range(n_heads[0]):
            """append()方法在被选元素的结尾(仍然在内部)插入指定内容
            (selector).append(content),content是必需的,规定要插入的内容(可包含HTML标签)"""
            attns.append(layers.attn_head(inputs, bias_mat=bias_mat,
                out_sz=hid_units[0], activation=activation,
                in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
        h_1 = tf.concat(attns, axis=-1)
        """concat表示拼接  axis=0代表在第0个维度拼接   axis=1代表在第1个维度拼接
        负数在数组索引里面表示倒数(countdown),为-1表示在高维度进行拼接
        
        
        
        
        
        中间层,层数是 len(hid_units)-1;
        第i层有Hi个注意力头,输入是 (B,N,F1*H1),每头注意力输出是 (B,N,Fi);
        每层均聚合所有头注意力,得到 (B,N,Fi*Hi)"""

        # n_heads[i]=中间第i层的注意力头数,设为Hi
        for i in range(1, len(hid_units)):
            h_old = h_1
            attns = []
            for _ in range(n_heads[i]):
                #与第一层注意力相比,中间层的input就是h_1
                attns.append(layers.attn_head(h_1, bias_mat=bias_mat,
                    out_sz=hid_units[i], activation=activation,
                    in_drop=ffd_drop, coef_drop=attn_drop, residual=residual))
            h_1 = tf.concat(attns, axis=-1)




            """最后一层,共n_heads[-1]头注意力,一般为1
            输入:最后一个中间层的输出(B,N,Fi*Hi)
            输出:(B,N,C),C是分类任务中的类别数"""

        out = []
        for i in range(n_heads[-1]):
            out.append(layers.attn_head(h_1, bias_mat=bias_mat,
                out_sz=nb_classes, activation=lambda x: x,
               #只有最后一层输出才有激活函数,输出的尺寸就是分类的类别数  激活函数就是LeakyReLu
                in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
        """logits ——【batchsize,class_num】是未进入softmax的概率,一般是全连接层的输出,softmax的输入
        将多头注意力的结果相加,并取平均"""
        logits = tf.add_n(out) / n_heads[-1]

    
        return logits
    #这里返回的只是没有经过softmax的概率,我们将其称之为logits

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值