[Caso de combate de la red neuronal de Pytorch] 41 Clasificación en papel basada en el conjunto de datos de Cora para implementar el modelo de red de convolución del gráfico Multi_Sample Dropout

El Dropout de muestras múltiples es una variante del Dropout.Este método tiene una mejor capacidad de generalización que el Dropout normal y, al mismo tiempo, puede acortar el tiempo de entrenamiento del modelo. XMuli-sampleDropout también puede reducir la tasa de error y la pérdida del conjunto de entrenamiento y el conjunto de validación, consulte el documento número arXIV:1905.09788,2019

1 Ejemplo de descripción

Este ejemplo usa el método Muli-sampleDropout para acortar el tiempo de entrenamiento para el modelo de convolución de gráfico.

1.1 Método de abandono de muestras múltiples/Abandono conjunto de muestras múltiples

La optimización se realiza en la parte en que Dropout selecciona aleatoriamente nodos para descartar, es decir, un grupo de nodos seleccionados aleatoriamente por Dropout se convierte en múltiples grupos de nodos seleccionados aleatoriamente, y el resultado de cada grupo de nodos y el valor de pérdida de la propagación hacia atrás son calculado. Finalmente, los valores de pérdida calculados de varios grupos se promedian para obtener el valor de pérdida final, que se utiliza para actualizar la red, como se muestra en la Figura 9-19.

Multi-sampleDropout utiliza dos conjuntos de máscaras diferentes para seleccionar dos conjuntos de nodos para el entrenamiento en la capa Dropout. Este enfoque es equivalente a que la capa de red solo ejecuta la muestra una vez, pero genera múltiples resultados y realiza múltiples entrenamientos. Por lo tanto, puede reducir en gran medida el número de iteraciones para el entrenamiento.

1.1.2 Características

En una red neuronal profunda, se producen demasiadas operaciones en la capa convolucional antes de la capa de abandono, y el abandono de múltiples muestras no repite estos cálculos, por lo que el abandono de múltiples muestras tiene poco efecto en el costo computacional de cada iteración. Puede acelerar drásticamente el entrenamiento.

2 implementación de código

Pytorch Neural Network Practical Study Notes_40 [Combate real] Graph Convolution Neural Network for Paper Classification_LiBiGor's Blog-CSDN Blog 1 Descripción del caso (Graph Convolutional Neural Network) El conjunto de datos CORA contiene las palabras clave y la clasificación de la información de cada artículo, así como información sobre citas mutuas entre papeles. Cree un modelo de IA, analice la información del papel en el conjunto de datos y prediga la categoría de papeles de clasificación desconocida según las características de clasificación de los papeles existentes. 1.1 Uso de las características de las redes neuronales convolucionales de grafos Utilice redes neuronales de grafos para implementar la clasificación. La diferencia con el modelo de aprendizaje profundo es que la red neuronal gráfica utilizará las características del propio texto y la relación entre los documentos para el procesamiento, y solo se necesita una pequeña cantidad de muestras para lograr buenos resultados. 1.2 Conjunto de datos CORA El conjunto de datos CORA se compila a partir de documentos de aprendizaje automático, registrando la clave utilizada en cada documento... https://blog.csdn.net/qq_39237205/article/details/123863327 Basado en el código anterior Modificar 2.7 a construir convolución gráfica multicapa y parte de entrenamiento

 2 Escritura de código

2.1 Combate de código: Introducir módulos básicos y configurar el entorno de ejecución----Cora_GNN.py (Parte 1)

from pathlib import Path # 引入提升路径的兼容性
# 引入矩阵运算的相关库
import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix,csr_matrix,diags,eye
# 引入深度学习框架库
import torch
from torch import nn
import torch.nn.functional as F
# 引入绘图库
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

# 1.1 导入基础模块,并设置运行环境
# 输出计算资源情况
device = torch.device('cuda')if torch.cuda.is_available() else torch.device('cpu')
print(device) # 输出 cuda

# 输出样本路径
path = Path('./data/cora')
print(path) # 输出 cuda

Resultado de salida:

2.2 Implementación de código: lectura y análisis de datos en papel----Cora_GNN.py (Parte 2)

# 1.2 读取并解析论文数据
# 读取论文内容数据,将其转化为数据
paper_features_label = np.genfromtxt(path/'cora.content',dtype=np.str_) # 使用Path对象的路径构造,实例化的内容为cora.content。path/'cora.content'表示路径为'data/cora/cora.content'的字符串
print(paper_features_label,np.shape(paper_features_label)) # 打印数据集内容与数据的形状

# 取出数据集中的第一列:论文ID
papers = paper_features_label[:,0].astype(np.int32)
print("论文ID序列:",papers) # 输出所有论文ID
# 论文重新编号,并将其映射到论文ID中,实现论文的统一管理
paper2idx = {k:v for v,k in enumerate(papers)}

# 将数据中间部分的字标签取出,转化成矩阵
features = csr_matrix(paper_features_label[:,1:-1],dtype=np.float32)
print("字标签矩阵的形状:",np.shape(features)) # 字标签矩阵的形状

# 将数据的最后一项的文章分类属性取出,转化为分类的索引
labels = paper_features_label[:,-1]
lbl2idx = { k:v for v,k in enumerate(sorted(np.unique(labels)))}
labels = [lbl2idx[e] for e in labels]
print("论文类别的索引号:",lbl2idx,labels[:5])

producción:

2.3 Leer y analizar datos relacionales en papel

Cargue los datos de relación de los artículos, convierta la relación representada por el ID del artículo en los datos en una relación renumerada, trate cada artículo como un vértice y la relación de citas entre artículos como un borde, de modo que los datos de relación de los artículos puedan usar un representado por la estructura del gráfico.

 Calcule la matriz de adyacencia de esta estructura gráfica y conviértala en una matriz de adyacencia gráfica no dirigida.

2.3.1 Implementación del Código: Matriz de Transformación----Cora_GNN.py (Parte 3)

# 1.3 读取并解析论文关系数据
# 读取论文关系数据,并将其转化为数据
edges = np.genfromtxt(path/'cora.cites',dtype=np.int32) # 将数据集中论文的引用关系以数据的形式读入
print(edges,np.shape(edges))
# 转化为新编号节点间的关系:将数据集中论文ID表示的关系转化为重新编号后的关系
edges = np.asarray([paper2idx[e] for e in edges.flatten()],np.int32).reshape(edges.shape)
print("新编号节点间的对应关系:",edges,edges.shape)
# 计算邻接矩阵,行与列都是论文个数:由论文引用关系所表示的图结构生成邻接矩阵。
adj = coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),shape=(len(labels), len(labels)), dtype=np.float32)
# 生成无向图对称矩阵:将有向图的邻接矩阵转化为无向图的邻接矩阵。Tip:转化为无向图的原因:主要用于对论文的分类,论文的引用关系主要提供单个特征之间的关联,故更看重是不是有关系,所以无向图即可。
adj_long = adj.multiply(adj.T < adj)
adj = adj_long + adj_long.T

producción:

2.4 Datos matriciales de la estructura del gráfico de procesamiento

Los datos de la matriz de la estructura del gráfico se procesan para que muestren mejor las características de la estructura del gráfico y participan en el cálculo del modelo de la red neuronal.

2.4.1 Procedimiento para el procesamiento de datos matriciales de estructura gráfica

1. Normalice los datos de características de cada nodo.
2. Sumar 1 a la diagonal de la matriz de adyacencia: porque en la tarea de clasificación, la función principal de la matriz de adyacencia es ayudar a la clasificación de nodos a través de la asociación entre papeles. Para los nodos de la diagonal, el sentido de la representación es la relación entre sí misma y sí misma. Establecer los nodos diagonales en 1 (gráfico de bucle automático) indica que los nodos también ayudarán con la tarea de clasificación.
3. Normalizar la matriz de adyacencia después de complementar por 1.

2.4.2 Implementación del código: datos de matriz de la estructura del gráfico de procesamiento----Cora_GNN.py (Parte 4)

# 1.4 加工图结构的矩阵数据
def normalize(mx): # 定义函数,对矩阵的数据进行归一化处理
    rowsum = np.array(mx.sum(1)) # 计算每一篇论文的字数==>02 对A中的边数求和,计算出矩阵A的度矩阵D^的特征向量
    r_inv = (rowsum ** -1).flatten() # 取总字数的倒数==>03 对矩阵A的度矩阵D^的特征向量求逆,并得到D^逆的特征向量
    r_inv[np.isinf(r_inv)] = 0.0 # 将NaN值取为0
    r_mat_inv = diags(r_inv) # 将总字数的倒数变为对角矩阵===》对图结构的度矩阵求逆==>04 D^逆的特征向量转化为对角矩阵,得到D^逆
    mx = r_mat_inv.dot(mx) # 左乘一个矩阵,相当于每个元素除以总数===》对每个论文顶点的边进行归一化处理==>05 计算D^逆与A加入自环(对角线为1)的邻接矩阵所得A^的点积,得到拉普拉斯矩阵。
    return mx
# 对features矩阵进行归一化处理(每行总和为1)
features = normalize(features) #在函数normalize()中,分为两步对邻接矩阵进行处理。1、将每篇论文总字数的倒数变成对角矩阵。该操作相当于对图结构的度矩阵求逆。2、用度矩阵的逆左乘邻接矩阵,相当于对图中每个论文顶点的边进行归一化处理。
# 对邻接矩阵的对角线添1,将其变为自循环图,同时对其进行归一化处理
adj = normalize(adj + eye(adj.shape[0])) # 对角线补1==>01实现加入自环的邻接矩阵A

2.5 Convertir datos en tensores y asignar recursos informáticos

Convierta los datos de matriz de estructura gráfica procesados ​​en un tipo de tensor compatible con PyTorch y divídalo en 3 partes para entrenamiento, prueba y validación.

2.5.1 Implementación de código: convertir datos en tensores y asignar recursos informáticos----Cora_GNN.py (Parte 5)

# 1.5 将数据转化为张量,并分配运算资源
adj = torch.FloatTensor(adj.todense()) # 节点间关系 todense()方法将其转换回稠密矩阵。
features = torch.FloatTensor(features.todense()) # 节点自身的特征
labels = torch.LongTensor(labels) # 对每个节点的分类标签

# 划分数据集
n_train = 200 # 训练数据集大小
n_val = 300 # 验证数据集大小
n_test = len(features) - n_train - n_val # 测试数据集大小
np.random.seed(34)
idxs = np.random.permutation(len(features)) # 将原有的索引打乱顺序

# 计算每个数据集的索引
idx_train = torch.LongTensor(idxs[:n_train]) # 根据指定训练数据集的大小并划分出其对应的训练数据集索引
idx_val = torch.LongTensor(idxs[n_train:n_train+n_val])# 根据指定验证数据集的大小并划分出其对应的验证数据集索引
idx_test = torch.LongTensor(idxs[n_train+n_val:])# 根据指定测试数据集的大小并划分出其对应的测试数据集索引

# 分配运算资源
adj = adj.to(device)
features = features.to(device)
labels = labels.to(device)
idx_train = idx_train.to(device)
idx_val = idx_val.to(device)
idx_test = idx_test.to(device)

2.6 Convolución de gráficos

La esencia de la convolución de gráficos es la transformación de dimensiones, es decir, transformar los datos de características de cada nodo con una dimensión en datos de características de nodo sin dimensión.

La operación de convolución de gráficos combina las características del nodo de entrada, los parámetros de peso y la matriz de adyacencia procesada para realizar una operación de producto escalar.

El parámetro de ponderación es una matriz de tamaño in×out, donde in representa la dimensión de la característica del nodo de entrada y out representa la dimensión de la característica final que se generará. La función de los parámetros de peso en la transformación de dimensiones se entiende como el peso de una red completamente conectada, pero en la convolución de gráficos, realizará más operaciones de producto escalar de la información de relación de nodo que las redes completamente conectadas.

 Como se muestra en la figura anterior, se enumera la relación entre la red completamente conectada y la red convolucional del gráfico después de ignorar el sesgo. Se puede ver claramente a partir de esto que la red convolucional gráfica en realidad agrega información de relación de nodo sobre la base de la red completamente conectada.

2.6.1 Implementación del código: defina la función de activación de Mish y la clase de operación de convolución gráfica----Cora_GNN.py (Parte 6)

Agregue sesgo a la base del algoritmo que se muestra en la figura anterior y defina la clase GraphConvolution

# 1.6 定义Mish激活函数与图卷积操作类
def mish(x): # 性能优于RElu函数
    return x * (torch.tanh(F.softplus(x)))
# 图卷积类
class GraphConvolution(nn.Module):
    def __init__(self,f_in,f_out,use_bias = True,activation=mish):
        # super(GraphConvolution, self).__init__()
        super().__init__()
        self.f_in = f_in
        self.f_out = f_out
        self.use_bias = use_bias
        self.activation = activation
        self.weight = nn.Parameter(torch.FloatTensor(f_in, f_out))
        self.bias = nn.Parameter(torch.FloatTensor(f_out)) if use_bias else None
        self.initialize_weights()

    def initialize_weights(self):# 对参数进行初始化
        if self.activation is None: # 初始化权重
            nn.init.xavier_uniform_(self.weight)
        else:
            nn.init.kaiming_uniform_(self.weight, nonlinearity='leaky_relu')
        if self.use_bias:
            nn.init.zeros_(self.bias)

    def forward(self,input,adj): # 实现模型的正向处理流程
        support = torch.mm(input,self.weight) # 节点特征与权重点积:torch.mm()实现矩阵的相乘,仅支持二位矩阵。若是多维矩则使用torch.matmul()
        output = torch.mm(adj,support) # 将加工后的邻接矩阵放入点积运算
        if self.use_bias:
            output.add_(self.bias) # 加入偏置
        if self.activation is not None:
            output = self.activation(output) # 激活函数处理
        return output

2.7 Construcción de un modelo de red convolucional de gráficos multicapa con Multi_Sample Dropout---Cora_GNN_MUti-sample-Dropout.py (parte 1 modificada)

# 1.7 搭建带有Multi_Sample Dropout的多层图卷积网络模型:根据GCN模型,
class GCNTD(nn.Module):
    def __init__(self,f_in,n_classes,hidden=[16],dropout_num = 8,dropout_p=0.5 ): # 默认使用8组dropout,每组丢弃率为0.5
        # super(GCNTD, self).__init__()
        super().__init__()
        layer = []
        for f_in,f_out in zip([f_in]+hidden[:-1],hidden):
            layer += [GraphConvolution(f_in,f_out)]
        self.layers = nn.Sequential(*layer)
        # 默认使用8个Dropout分支
        self.dropouts = nn.ModuleList([nn.Dropout(dropout_p,inplace=False) for _ in range(dropout_num)] )
        self.out_layer = GraphConvolution(f_out,n_classes,activation=None)
    def forward(self,x,adj):
        # Multi - sampleDropout结构默认使用了8个Dropout分支。在前向传播过程中,具体步骤如下。
        # ①输入样本统一经过多层图卷积神经网络来到Dropout层。
        # ②由每个分支的Dropout按照指定的丢弃率对多层图卷积的结果进行Dropout处理。
        # ③将每个分支的Dropout数据传入到输出层,分别得到结果。
        # ④将所有结果加起来,生成最终结果。
        for layer,d in zip(self.layers,self.dropouts):
            x = layer(x,adj)
        if len(self.dropouts) == 0:
            return self.out_layer(x,adj)
        else:
            for i, dropout in enumerate(self.dropouts): # 将每组的输出叠加
                if i == 0 :
                    out = dropout(x)
                    out = self.out_layer(out,adj)
                else:
                    temp_out = dropout(x)
                    out = out + self.out_layer(temp_out,adj)
            return out # 返回结果

n_labels = labels.max().item() + 1 # 获取分类个数7
n_features = features.shape[1] # 获取节点特征维度 1433
print(n_labels,n_features) # 输出7与1433

def accuracy(output,y): # 定义函数计算准确率
    return (output.argmax(1) == y).type(torch.float32).mean().item()

### 定义函数来实现模型的训练过程。与深度学习任务不同,图卷积在训练时需要传入样本间的关系数据。
# 因为该关系数据是与节点数相等的方阵,所以传入的样本数也要与节点数相同,在计算loss值时,可以通过索引从总的运算结果中取出训练集的结果。
# 在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入。
def step(): # 定义函数来训练模型
    model.train()
    optimizer.zero_grad()
    output = model(features,adj) # 将全部数据载入模型,只用训练数据计算损失
    loss = F.cross_entropy(output[idx_train],labels[idx_train])
    acc = accuracy(output[idx_train],labels[idx_train]) # 计算准确率
    loss.backward()
    optimizer.step()
    return loss.item(),acc

def evaluate(idx): # 定义函数来评估模型
    model.eval()
    output = model(features, adj) # 将全部数据载入模型
    loss = F.cross_entropy(output[idx], labels[idx]).item() # 用指定索引评估模型结果
    return loss, accuracy(output[idx], labels[idx])

2.8 Implementación de código: Visualización de entrenamiento --- Cora_GNN_MUti-sample-Dropout.py (Parte 2 modificada)

model = GCNTD(n_features,n_labels,hidden=[16,32,16]).to(device)
from ranger import *
from functools import partial # 引入偏函数对Ranger设置参数
opt_func = partial(Ranger,betas=(0.9,0.99),eps=1e-6)
optimizer = opt_func(model.parameters())

from tqdm import tqdm
# 训练模型
epochs = 400
print_steps = 50
train_loss, train_acc = [], []
val_loss, val_acc = [], []
for i in tqdm(range(epochs)):
    tl,ta = step()
    train_loss = train_loss + [tl]
    train_acc = train_acc + [ta]
    if (i+1) % print_steps == 0 or i == 0:
        tl,ta = evaluate(idx_train)
        vl,va = evaluate(idx_val)
        val_loss = val_loss + [vl]
        val_acc = val_acc + [va]
        print(f'{i + 1:6d}/{epochs}: train_loss={tl:.4f}, train_acc={ta:.4f}' + f', val_loss={vl:.4f}, val_acc={va:.4f}')

# 输出最终结果
final_train, final_val, final_test = evaluate(idx_train), evaluate(idx_val), evaluate(idx_test)
print(f'Train     : loss={final_train[0]:.4f}, accuracy={final_train[1]:.4f}')
print(f'Validation: loss={final_val[0]:.4f}, accuracy={final_val[1]:.4f}')
print(f'Test      : loss={final_test[0]:.4f}, accuracy={final_test[1]:.4f}')

# 可视化训练过程
fig, axes = plt.subplots(1, 2, figsize=(15,5))
ax = axes[0]
axes[0].plot(train_loss[::print_steps] + [train_loss[-1]], label='Train')
axes[0].plot(val_loss, label='Validation')
axes[1].plot(train_acc[::print_steps] + [train_acc[-1]], label='Train')
axes[1].plot(val_acc, label='Validation')
for ax,t in zip(axes, ['Loss', 'Accuracy']): ax.legend(), ax.set_title(t, size=15)

# 输出模型的预测结果
output = model(features, adj)
samples = 10
idx_sample = idx_test[torch.randperm(len(idx_test))[:samples]]
# 将样本标签与预测结果进行比较
idx2lbl = {v:k for k,v in lbl2idx.items()}
df = pd.DataFrame({'Real': [idx2lbl[e] for e in labels[idx_sample].tolist()],'Pred': [idx2lbl[e] for e in output[idx_sample].argmax(1).tolist()]})
print(df)

producción:

Mejores resultados después de solo 400 rondas

3 Descripción general del código

 Cora_GNN_MUti-sample-Dropout.py

from pathlib import Path # 引入提升路径的兼容性
# 引入矩阵运算的相关库
import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix,csr_matrix,diags,eye
# 引入深度学习框架库
import torch
from torch import nn
import torch.nn.functional as F
# 引入绘图库
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

# 1.1 导入基础模块,并设置运行环境
# 输出计算资源情况
device = torch.device('cuda')if torch.cuda.is_available() else torch.device('cpu')
print(device) # 输出 cuda

# 输出样本路径
path = Path('./data/cora')
print(path) # 输出 cuda

# 1.2 读取并解析论文数据
# 读取论文内容数据,将其转化为数据
paper_features_label = np.genfromtxt(path/'cora.content',dtype=np.str_) # 使用Path对象的路径构造,实例化的内容为cora.content。path/'cora.content'表示路径为'data/cora/cora.content'的字符串
print(paper_features_label,np.shape(paper_features_label)) # 打印数据集内容与数据的形状

# 取出数据集中的第一列:论文ID
papers = paper_features_label[:,0].astype(np.int32)
print("论文ID序列:",papers) # 输出所有论文ID
# 论文重新编号,并将其映射到论文ID中,实现论文的统一管理
paper2idx = {k:v for v,k in enumerate(papers)}

# 将数据中间部分的字标签取出,转化成矩阵
features = csr_matrix(paper_features_label[:,1:-1],dtype=np.float32)
print("字标签矩阵的形状:",np.shape(features)) # 字标签矩阵的形状

# 将数据的最后一项的文章分类属性取出,转化为分类的索引
labels = paper_features_label[:,-1]
lbl2idx = { k:v for v,k in enumerate(sorted(np.unique(labels)))}
labels = [lbl2idx[e] for e in labels]
print("论文类别的索引号:",lbl2idx,labels[:5])

# 1.3 读取并解析论文关系数据
# 读取论文关系数据,并将其转化为数据
edges = np.genfromtxt(path/'cora.cites',dtype=np.int32) # 将数据集中论文的引用关系以数据的形式读入
print(edges,np.shape(edges))
# 转化为新编号节点间的关系:将数据集中论文ID表示的关系转化为重新编号后的关系
edges = np.asarray([paper2idx[e] for e in edges.flatten()],np.int32).reshape(edges.shape)
print("新编号节点间的对应关系:",edges,edges.shape)
# 计算邻接矩阵,行与列都是论文个数:由论文引用关系所表示的图结构生成邻接矩阵。
adj = coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),shape=(len(labels), len(labels)), dtype=np.float32)
# 生成无向图对称矩阵:将有向图的邻接矩阵转化为无向图的邻接矩阵。Tip:转化为无向图的原因:主要用于对论文的分类,论文的引用关系主要提供单个特征之间的关联,故更看重是不是有关系,所以无向图即可。
adj_long = adj.multiply(adj.T < adj)
adj = adj_long + adj_long.T

# 1.4 加工图结构的矩阵数据
def normalize(mx): # 定义函数,对矩阵的数据进行归一化处理
    rowsum = np.array(mx.sum(1)) # 计算每一篇论文的字数==>02 对A中的边数求和,计算出矩阵A的度矩阵D^的特征向量
    r_inv = (rowsum ** -1).flatten() # 取总字数的倒数==>03 对矩阵A的度矩阵D^的特征向量求逆,并得到D^逆的特征向量
    r_inv[np.isinf(r_inv)] = 0.0 # 将NaN值取为0
    r_mat_inv = diags(r_inv) # 将总字数的倒数变为对角矩阵===》对图结构的度矩阵求逆==>04 D^逆的特征向量转化为对角矩阵,得到D^逆
    mx = r_mat_inv.dot(mx) # 左乘一个矩阵,相当于每个元素除以总数===》对每个论文顶点的边进行归一化处理==>05 计算D^逆与A加入自环(对角线为1)的邻接矩阵所得A^的点积,得到拉普拉斯矩阵。
    return mx
# 对features矩阵进行归一化处理(每行总和为1)
features = normalize(features) #在函数normalize()中,分为两步对邻接矩阵进行处理。1、将每篇论文总字数的倒数变成对角矩阵。该操作相当于对图结构的度矩阵求逆。2、用度矩阵的逆左乘邻接矩阵,相当于对图中每个论文顶点的边进行归一化处理。
# 对邻接矩阵的对角线添1,将其变为自循环图,同时对其进行归一化处理
adj = normalize(adj + eye(adj.shape[0])) # 对角线补1==>01实现加入自环的邻接矩阵A

# 1.5 将数据转化为张量,并分配运算资源
adj = torch.FloatTensor(adj.todense()) # 节点间关系 todense()方法将其转换回稠密矩阵。
features = torch.FloatTensor(features.todense()) # 节点自身的特征
labels = torch.LongTensor(labels) # 对每个节点的分类标签

# 划分数据集
n_train = 200 # 训练数据集大小
n_val = 300 # 验证数据集大小
n_test = len(features) - n_train - n_val # 测试数据集大小
np.random.seed(34)
idxs = np.random.permutation(len(features)) # 将原有的索引打乱顺序

# 计算每个数据集的索引
idx_train = torch.LongTensor(idxs[:n_train]) # 根据指定训练数据集的大小并划分出其对应的训练数据集索引
idx_val = torch.LongTensor(idxs[n_train:n_train+n_val])# 根据指定验证数据集的大小并划分出其对应的验证数据集索引
idx_test = torch.LongTensor(idxs[n_train+n_val:])# 根据指定测试数据集的大小并划分出其对应的测试数据集索引

# 分配运算资源
adj = adj.to(device)
features = features.to(device)
labels = labels.to(device)
idx_train = idx_train.to(device)
idx_val = idx_val.to(device)
idx_test = idx_test.to(device)

# 1.6 定义Mish激活函数与图卷积操作类
def mish(x): # 性能优于RElu函数
    return x * (torch.tanh(F.softplus(x)))
# 图卷积类
class GraphConvolution(nn.Module):
    def __init__(self,f_in,f_out,use_bias = True,activation=mish):
        # super(GraphConvolution, self).__init__()
        super().__init__()
        self.f_in = f_in
        self.f_out = f_out
        self.use_bias = use_bias
        self.activation = activation
        self.weight = nn.Parameter(torch.FloatTensor(f_in, f_out))
        self.bias = nn.Parameter(torch.FloatTensor(f_out)) if use_bias else None
        self.initialize_weights()

    def initialize_weights(self):# 对参数进行初始化
        if self.activation is None: # 初始化权重
            nn.init.xavier_uniform_(self.weight)
        else:
            nn.init.kaiming_uniform_(self.weight, nonlinearity='leaky_relu')
        if self.use_bias:
            nn.init.zeros_(self.bias)

    def forward(self,input,adj): # 实现模型的正向处理流程
        support = torch.mm(input,self.weight) # 节点特征与权重点积:torch.mm()实现矩阵的相乘,仅支持二位矩阵。若是多维矩则使用torch.matmul()
        output = torch.mm(adj,support) # 将加工后的邻接矩阵放入点积运算
        if self.use_bias:
            output.add_(self.bias) # 加入偏置
        if self.activation is not None:
            output = self.activation(output) # 激活函数处理
        return output

# 1.7 搭建带有Multi_Sample Dropout的多层图卷积网络模型:根据GCN模型,
class GCNTD(nn.Module):
    def __init__(self,f_in,n_classes,hidden=[16],dropout_num = 8,dropout_p=0.5 ): # 默认使用8组dropout,每组丢弃率为0.5
        # super(GCNTD, self).__init__()
        super().__init__()
        layer = []
        for f_in,f_out in zip([f_in]+hidden[:-1],hidden):
            layer += [GraphConvolution(f_in,f_out)]
        self.layers = nn.Sequential(*layer)
        # 默认使用8个Dropout分支
        self.dropouts = nn.ModuleList([nn.Dropout(dropout_p,inplace=False) for _ in range(dropout_num)] )
        self.out_layer = GraphConvolution(f_out,n_classes,activation=None)
    def forward(self,x,adj):
        # Multi - sampleDropout结构默认使用了8个Dropout分支。在前向传播过程中,具体步骤如下。
        # ①输入样本统一经过多层图卷积神经网络来到Dropout层。
        # ②由每个分支的Dropout按照指定的丢弃率对多层图卷积的结果进行Dropout处理。
        # ③将每个分支的Dropout数据传入到输出层,分别得到结果。
        # ④将所有结果加起来,生成最终结果。
        for layer,d in zip(self.layers,self.dropouts):
            x = layer(x,adj)
        if len(self.dropouts) == 0:
            return self.out_layer(x,adj)
        else:
            for i, dropout in enumerate(self.dropouts): # 将每组的输出叠加
                if i == 0 :
                    out = dropout(x)
                    out = self.out_layer(out,adj)
                else:
                    temp_out = dropout(x)
                    out = out + self.out_layer(temp_out,adj)
            return out # 返回结果
n_labels = labels.max().item() + 1 # 获取分类个数7
n_features = features.shape[1] # 获取节点特征维度 1433
print(n_labels,n_features) # 输出7与1433

def accuracy(output,y): # 定义函数计算准确率
    return (output.argmax(1) == y).type(torch.float32).mean().item()

### 定义函数来实现模型的训练过程。与深度学习任务不同,图卷积在训练时需要传入样本间的关系数据。
# 因为该关系数据是与节点数相等的方阵,所以传入的样本数也要与节点数相同,在计算loss值时,可以通过索引从总的运算结果中取出训练集的结果。
# 在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入。
def step(): # 定义函数来训练模型
    model.train()
    optimizer.zero_grad()
    output = model(features,adj) # 将全部数据载入模型,只用训练数据计算损失
    loss = F.cross_entropy(output[idx_train],labels[idx_train])
    acc = accuracy(output[idx_train],labels[idx_train]) # 计算准确率
    loss.backward()
    optimizer.step()
    return loss.item(),acc

def evaluate(idx): # 定义函数来评估模型
    model.eval()
    output = model(features, adj) # 将全部数据载入模型
    loss = F.cross_entropy(output[idx], labels[idx]).item() # 用指定索引评估模型结果
    return loss, accuracy(output[idx], labels[idx])

model = GCNTD(n_features,n_labels,hidden=[16,32,16]).to(device)
from ranger import *
from functools import partial # 引入偏函数对Ranger设置参数
opt_func = partial(Ranger,betas=(0.9,0.99),eps=1e-6)
optimizer = opt_func(model.parameters())

from tqdm import tqdm
# 训练模型
epochs = 400
print_steps = 50
train_loss, train_acc = [], []
val_loss, val_acc = [], []
for i in tqdm(range(epochs)):
    tl,ta = step()
    train_loss = train_loss + [tl]
    train_acc = train_acc + [ta]
    if (i+1) % print_steps == 0 or i == 0:
        tl,ta = evaluate(idx_train)
        vl,va = evaluate(idx_val)
        val_loss = val_loss + [vl]
        val_acc = val_acc + [va]
        print(f'{i + 1:6d}/{epochs}: train_loss={tl:.4f}, train_acc={ta:.4f}' + f', val_loss={vl:.4f}, val_acc={va:.4f}')

# 输出最终结果
final_train, final_val, final_test = evaluate(idx_train), evaluate(idx_val), evaluate(idx_test)
print(f'Train     : loss={final_train[0]:.4f}, accuracy={final_train[1]:.4f}')
print(f'Validation: loss={final_val[0]:.4f}, accuracy={final_val[1]:.4f}')
print(f'Test      : loss={final_test[0]:.4f}, accuracy={final_test[1]:.4f}')

# 可视化训练过程
fig, axes = plt.subplots(1, 2, figsize=(15,5))
ax = axes[0]
axes[0].plot(train_loss[::print_steps] + [train_loss[-1]], label='Train')
axes[0].plot(val_loss, label='Validation')
axes[1].plot(train_acc[::print_steps] + [train_acc[-1]], label='Train')
axes[1].plot(val_acc, label='Validation')
for ax,t in zip(axes, ['Loss', 'Accuracy']): ax.legend(), ax.set_title(t, size=15)

# 输出模型的预测结果
output = model(features, adj)
samples = 10
idx_sample = idx_test[torch.randperm(len(idx_test))[:samples]]
# 将样本标签与预测结果进行比较
idx2lbl = {v:k for k,v in lbl2idx.items()}
df = pd.DataFrame({'Real': [idx2lbl[e] for e in labels[idx_sample].tolist()],'Pred': [idx2lbl[e] for e in output[idx_sample].argmax(1).tolist()]})
print(df)

Supongo que te gusta

Origin blog.csdn.net/qq_39237205/article/details/123869255
Recomendado
Clasificación