《数据挖掘》第六次实验


基于Pytorch的图卷积网络GCN实例应用及详解

一、图卷积网络GCN定义

  图卷积网络GCN实际上就是特征提取器,只不过GCN的数据对象是图。图的结构一般来说是十分不规则,可以看作是多维的一种数据。GCN精妙地设计了一种从图数据中提取特征的方法,从而让我们可以使用这些特征去对图数据进行节点分类(node classification)、图分类(graph classification)、边预测(link prediction)和获得图的嵌入表示(graph embedding),用途十分广泛。


二、GCN核心公式

   H l + 1 = σ ( D − 1 2 A ∧ D − 1 2 H l W l ) {H^{l + 1}} = \sigma ({D^{ - \frac{1}{2}}}\mathop A\limits^ \wedge {D^{ - \frac{1}{2}}}{H^l}{W^l}) Hl+1=σ(D21AD21HlWl)
   A ∧ = A + I \mathop A\limits^ \wedge = A + I A=A+I
   A + I A+I A+I I I I是单位矩阵,即对角线为1,其余全为0
   D ∼ \mathop D\limits^ \sim D A ∼ \mathop A\limits^ \sim A的度矩阵,计算方法 D ∼ \mathop D\limits^ \sim D = ∑ A ∼ i j {\sum {\mathop A\limits^ \sim } _{ij}} Aij
   H H H是每一层的所有节点的特征向量矩阵,对于输入层的话,
H ( 0 ) {H^{(0)}} H(0) 就等于 X , [ n , d ] X,[n,d] X,[n,d]维度

   σ \sigma σ是非线性激活函数,如Relu
   W ( l ) {W^{(l)}} W(l) 表示的是当前层卷积变换的可训练的参数矩阵


三、官方代码

1、code

import torch
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_self_loops, degree
 
class GCNConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super(GCNConv, self).__init__(aggr='add')  # "Add" aggregation (Step 5).
        self.lin = torch.nn.Linear(in_channels, out_channels)
 
    def forward(self, x, edge_index):
        # x has shape [N, in_channels]
        # edge_index has shape [2, E]
 
        # Step 1: Add self-loops to the adjacency matrix.
        edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))
 
        # Step 2: Linearly transform node feature matrix.
        x = self.lin(x)
 
        # Step 3: Compute normalization.
        row, col = edge_index
        deg = degree(col, x.size(0), dtype=x.dtype)
        deg_inv_sqrt = deg.pow(-0.5)
        norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]
 
        # Step 4-5: Start propagating messages.
        return self.propagate(edge_index, x=x, norm=norm)
 
    def message(self, x_j, norm):
        # x_j has shape [E, out_channels]
 
        # Step 4: Normalize node features.
        return norm.view(-1, 1) * x_j

2、解读

(1)初始化函数 init(self, in_channels, out_channels):

  in_channels是输入特征的维度。
  out_channels是输出特征的维度。
  通过调用父类的初始化函数 super(GCNConv, self).__init__(aggr='add'),指定信息聚合的方式为"add"(按元素相加)。
  self.lin = torch.nn.Linear(in_channels, out_channels) 创建了一个线性变换层(linear transformation layer)并将其存储在 self.lin 变量中。

(2)前向传播函数 forward(self, x, edge_index):

  x 是节点特征矩阵,形状为 [N, in_channels],其中 N 是节点的数量。
  edge_index 是边索引矩阵,形状为 [2, E],其中 E 是边的数量。
  第一步:给邻接矩阵添加自环。
  通过调用 add_self_loops(edge_index, num_nodes=x.size(0)),将自环边添加到 edge_index 中,确保每个节点都与自身相连。

  第二步:线性变换节点特征。
  通过 self.lin(x),将节点特征矩阵进行线性变换,将输入特征维度从in_channels转换为 out_channels

  第三步:计算归一化系数。
  row, col = edge_index:将边索引矩阵 edge_index 分解为两个分量,row 存储了边的起始节点索引,col 存储了边的终止节点索引。
  deg = degree(col, x.size(0), dtype=x.dtype):通过调用 degree 函数计算每个节点的度(即与该节点相连的边的数量)
  deg_inv_sqrt = deg.pow(-0.5):计算度的倒数的平方根,这是为了进行归一化处理。将度的每个元素取倒数并开平方根,得到每个节点的归一化系数。
  最终,norm 存储了归一化系数,用于在消息传递过程中对节点特征进行归一化处理。这

row, col = edge_index
deg = degree(col, x.size(0), dtype=x.dtype)
deg_inv_sqrt = deg.pow(-0.5)
norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]

  第四步和第五步:开始传递消息。通过调用 self.propagate(edge_index, x=x, norm=norm),传递消息并进行聚合操作。

(3)消息传递函数 message(self, x_j, norm):

  x_j 是邻居节点的特征矩阵,形状为 [E, out_channels],其中 E 是边的数量。

  step4:对节点特征进行归一化。通过将 norm.view(-1, 1) 乘以邻居节点特征x_j,对节点特征进行归一化处理。


四、模型搭建

1、模型

  模型根据自己需求搭建
  使用GCN模型对Cora数据集进行节点分类任务的训练和测试。
  本网络模型通过两个GCNConv层进行信息传递和特征更新,使用ReLU激活函数进行非线性变换,并通过log_softmax操作产生最终的节点分类输出。

# 定义GCN模型
class GCN(nn.Module):
    def __init__(self, in_channels, hidden_channels, out_channels):
        super(GCN, self).__init__()
        self.conv1 = GCNConv(in_channels, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

2、完整代码

import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
from torch_geometric.datasets import Planetoid
from torch_geometric.nn import GCNConv

# 定义GCN模型
class GCN(nn.Module):
    def __init__(self, in_channels, hidden_channels, out_channels):
        super(GCN, self).__init__()
        self.conv1 = GCNConv(in_channels, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

# 下载Cora数据集
dataset = Planetoid(root='data', name='Cora')

# 提取数据集中的节点特征和边索引
data = dataset[0]
x = data.x
edge_index = data.edge_index

# 创建GCN模型实例
model = GCN(dataset.num_features, 64, dataset.num_classes)

# 定义优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# 存储损失和准确率
losses = []
accuracies = []

# 训练循环
model.train()
for epoch in range(75):
    optimizer.zero_grad()
    out = model(x, edge_index)
    loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])  # 使用训练集的标签计算损失
    loss.backward()
    optimizer.step()

    # 计算在训练集上的准确率
    model.eval()
    pred = out.argmax(dim=1)
    train_acc = (pred[data.train_mask] == data.y[data.train_mask]).sum().item() / data.train_mask.sum().item()

    losses.append(loss.item())
    accuracies.append(train_acc)

    # 打印当前epoch的损失和准确率
    print(f"Epoch {epoch+1}: Loss: {loss.item():.4f}, Train Accuracy: {train_acc:.4f}")

# 在测试集上评估模型
model.eval()
out = model(x, edge_index)
pred = out.argmax(dim=1)
test_acc = (pred[data.test_mask] == data.y[data.test_mask]).sum().item() / data.test_mask.sum().item()
print(f"Test Accuracy: {test_acc:.4f}")

# 绘制损失和准确率随epoch变化的图表
plt.figure()
plt.plot(range(1, len(losses) + 1), losses, label='Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()

plt.figure()
plt.plot(range(1, len(accuracies) + 1), accuracies, label='Train Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

3、实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻兒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值