【GNN 1】PyG实现图神经网络,完成节点分类任务,人话、保姆级教程

我们来做一个节点分类的任务,选择的数据集是Karate Club,Karate是空手道的意思,所以这就是一个空手道俱乐部的数据。

简而言之,这个数据集,包含34个节点,156条无向无权边,结点总共分为4类,此外,每个节点还有34个特征,也就是说还有34个指标来描述空手道俱乐部的每个成员。欸?特征数怎么和节点数一样,没错,就是one-hot编码。

下图是论文原图,颜色表示了类别。

数据集的详细说明

这个数据集是由社会学家Wayne W. Zachary在1977年的论文An Information Flow Model for Conflict and Fission in Small Groups中提出的,基于他对一个美国大学空手道俱乐部的观察和记录。这个数据集包含34个节点和78条边,每个节点代表一个空手道俱乐部的成员,每条边代表两个成员之间的社交关系。

在收集数据的过程中,俱乐部的管理员和教练之间发生了冲突,导致俱乐部分裂为两个社区,一半成员跟随教练,另一半成员跟随管理员或离开俱乐部。Zachary利用图的结构信息,成功地预测了除了一个成员之外的所有成员的类别。

Zaharu’s karateclub数据集还根据2017年的论文Semi-supervised Classification with Graph Convolutional Networks,给每个节点赋予了一个四分类的标签,表示它们属于哪个社区,这些标签是通过基于模块度的聚类方法得到的。

再说一遍数据集的本质:这个数据集,包含34个节点,156条无向无权边,结点总共分为4类,此外,每个节点还有34个特征,也就是说还有34个指标来描述空手道俱乐部的每个成员。欸?特征数怎么和节点数一样,没错,就是one-hot编码。

加载并探索数据集

torch_geometric.datasets里已经有了这个数据集,我们可以查看一下这个数据的一些信息。包括有几个图(有的数据集有很多张图,比如有好几个俱乐部,每个俱乐部是一张图,这里是只有一张图),结点/边特征个数,结点类别个数。

from torch_geometric.datasets import KarateClub

dataset = KarateClub()
print(f'Dataset: {dataset}:')
print('======================')
print(f'Number of graphs: {len(dataset)}')
print(f'Number of node features: {dataset.num_features}')
print(f'Number of classes: {dataset.num_classes}')
print(f'Number of edge features: {dataset.num_edge_features}')
Dataset: KarateClub():
======================
Number of graphs: 1
Number of node features: 34
Number of classes: 4
Number of edge features: 0

可以看到只有一张图,34个结点特征和4个类别。

这里需要明确的是,数据集是数据集,图是图,图是包含在数据集中的,接下来,我们拿出这个数据集的第一张图(也是唯一一张图),来看看这个图中的一些信息。包括图的节点个数、边的个数、结点的平均度、是否有孤立点、有没有自己连接自己等等。

data = dataset[0]  # Get the first graph object.

print(data)
print('==============================================================')

# Gather some statistics about the graph.
print(f'Number of nodes: {data.num_nodes}')
print(f'Number of edges: {data.num_edges}')
print(f'Average node degree: {(data.num_edges) / data.num_nodes:.2f}')
print(f'Number of training nodes: {data.train_mask.sum()}')
print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}')
print(f'Contains isolated nodes: {data.has_isolated_nodes()}')
print(f'Contains self-loops: {data.has_self_loops()}')
print(f'Is undirected: {data.is_undirected()}')
Data(x=[34, 34], edge_index=[2, 156], y=[34], train_mask=[34])
==============================================================
Number of nodes: 34
Number of edges: 156
Average node degree: 4.59
Number of training nodes: 4
Training node label rate: 0.12
Contains isolated nodes: False
Contains self-loops: False
Is undirected: True

Data这个数据类型

我们来详细看看Data这个数据类型

PyTorch Geometric是一个用于图神经网络的PyTorch库,它可以处理各种类型的图数据。在PyTorch Geometric中,每个图都由一个Data对象表示,它包含了描述图结构的所有信息。Data对象可以像一个普通的Python字典一样使用,也可以提供一些分析图结构的有用功能。

Data对象可以有多个属性,每个属性都是一个PyTorch张量。例如,上面的代码中,Data对象有四个属性:

  • edge_index:一个2 x 156的张量,表示图中的边的连接关系。每一列是一个边的起点和终点的索引,例如第一列[0, 1]表示从节点0到节点1的一条边。
  • x:一个34 x 34的张量,表示图中的节点的特征。每一行是一个节点的34维特征向量,例如第一行[-1]表示节点0的特征。
  • y:一个34维的张量,表示图中的节点的标签。每个元素是一个节点的类别,例如第一个元素0表示节点0属于类别0。
  • train_mask:一个34维的布尔张量,表示哪些节点的标签是已知的。每个元素是True或False,例如第一个元素True表示节点0的标签是已知的。

注意了,Data(x=[34, 34], edge_index=[2, 156], y=[34], train_mask=[34])这里面的数字,都是表示维度。

这个Data对象表示了一个有34个节点,156条边的图,其中只有4个节点的标签是已知的,其余的节点的标签需要通过图神经网络来推断。

Data对象还提供了一些实用函数,用于推断图的一些基本属性。例如,我们可以通过以下方式来判断图是否有孤立的节点(没有任何边相连的节点),是否有自环(从一个节点到自己的边),或者是否是无向图(每条边都是双向的):

  • data.isolated_nodes():返回一个布尔张量,表示哪些节点是孤立的。
  • data.contains_self_loops():返回一个布尔值,表示图是否有自环。
  • data.is_undirected():返回一个布尔值,表示图是否是无向的。

coo format

在这里我还想补充要给知识点,也就是coo format,这是用来表示图的一种方式,本质上是表示稀疏矩阵的一种方式,也就是用三个向量分别表示,行、列、值,请看下面详细介绍:

coo format是一种稀疏矩阵的存储格式,它只记录矩阵中非零元素的位置和值。coo format的全称是coordinate format,意思是坐标格式,因为它用两个坐标来表示每个非零元素的行和列索引。例如,一个4 x 4的矩阵:

[ 0 3 0 1 1 0 2 0 0 1 0 0 1 0 0 0 ] \begin{bmatrix} 0 & 3 & 0 & 1 \\\\ 1 & 0 & 2 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 1 & 0 & 0 & 0 \end{bmatrix} 0101301002001000

可以用coo format表示为:

row = [ 0 , 0 , 1 , 1 , 2 , 3 ] col = [ 1 , 3 , 0 , 2 , 1 , 0 ] data = [ 3 , 1 , 1 , 2 , 1 , 1 ] \begin{aligned} &\text{row} = [0, 0, 1, 1, 2, 3] \\\\ &\text{col} = [1, 3, 0, 2, 1, 0] \\\\ &\text{data} = [3, 1, 1, 2, 1, 1] \end{aligned} row=[0,0,1,1,2,3]col=[1,3,0,2,1,0]data=[3,1,1,2,1,1]

其中,row和col是两个长度为非零元素个数的数组,分别存储每个非零元素的行和列索引。data是一个同样长度的数组,存储每个非零元素的值。这样,就可以用三个数组来代替一个完整的矩阵,节省了空间和计算资源。

在图神经网络领域,coo format可以用来表示图的邻接矩阵,即图中每两个节点之间是否有边相连的矩阵。由于图的邻接矩阵通常是稀疏的,即大部分节点之间没有边相连,所以用coo format可以有效地压缩和处理图的结构信息。一些图神经网络的库,如PyTorch Geometric,就支持用coo format来输入图的邻接矩阵。

可视化一下图

接下来我们可视化一下这个图,看看和我们上面展示真实的图是不是一样的。

可视化,我们使用的networkx这个包,首先我们需要把Data转换成networkx支持的图结构。这里是使用的to_networkx函数。

# Helper function for visualization.
%matplotlib inline
import torch
import networkx as nx
import matplotlib.pyplot as plt

# Visualization function for NX graph or PyTorch tensor
def visualize(h, color, epoch=None, loss=None, accuracy=None):
    plt.figure(figsize=(7,7))
    plt.xticks([])
    plt.yticks([])

    if torch.is_tensor(h):
        h = h.detach().cpu().numpy()
        plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
        if epoch is not None and loss is not None and accuracy['train'] is not None and accuracy['val'] is not None:
            plt.xlabel((f'Epoch: {epoch}, Loss: {loss.item():.4f} \n'
                       f'Training Accuracy: {accuracy["train"]*100:.2f}% \n'
                       f' Validation Accuracy: {accuracy["val"]*100:.2f}%'),
                       fontsize=16)
    else:
        nx.draw_networkx(h, pos=nx.spring_layout(h, seed=42), with_labels=False,
                         node_color=color, cmap="Set2")
    plt.show()
from torch_geometric.utils import to_networkx

G = to_networkx(data, to_undirected=True)
visualize(G, color=data.y)



虽然不一样,不过问题不大,和后续的分析没有关系。

用图卷积神经网络(GCN)对节点进行低维嵌入

接下来我们看看如何使用PyG(PyTorch Geometric)库来实现图神经网络(GNNs)。我们用最简单的GNN结构——GCN layer,它可以通过节点特征表示x和COO格式的edge_index来执行图卷积操作,从而更新节点的特征。

输入输出与模型本质

输入是什么?

节点特征表示x和COO格式的edge_index,也就是节点特征和节点间的连接关系的信息。

输出是什么?

GNN的输出目标是学习一个函数f,它可以将图中的每个节点及其特征向量映射到一个低维的嵌入向量,这个嵌入向量可以反映节点在图中的结构和语义信息,从而用于后续的任务,如节点分类、边预测或图回归等。

通俗理解:用一个向量来表示这个节点,这个由GCN layer组成的GNN就是为了学习到一个从节点特征和节点间的连接关系到节点的向量表示的一个方法。

下面我们来看看如何实现吧!注释非常详细!

模型的定义与训练,训练前后的比较

__init__forward是两个特殊的方法,用于定义一个PyTorch的神经网络模型,__init__是初始化方法,用于创建模型中需要用到的各种层和参数,forward是前向传播方法,用于定义模型的计算流程,即如何用层和参数来计算模型的输出。

三个图卷积层是GCN模型的核心部分,图卷积层是一种能够有效地处理图数据的神经网络层,它可以实现图数据的信息传递和聚合。每个图卷积层都可以收集每个节点的一跳(one hop)邻居(直接相连的节点)的信息,但是当我们把三个图卷积层叠加在一起时,我们就可以收集每个节点的三跳邻居(最多相隔三条边的节点)的信息,这样可以使得模型能够捕捉到更广泛的图结构信息。

GCNConv层还可以降低节点特征的维度,从34维降到2维( 34 → 4 → 4 → 2 34 \rightarrow 4 \rightarrow 4 \rightarrow 2 34442),也就是说,经过三个GCNConv层后,每个节点的特征会变成2维的向量,这个向量可以看作是节点在图卷积神经网络的嵌入空间中的表示,可以用于可视化或其他任务。每个GCNConv层后面还加了一个tanh函数,这是一种非线性激活函数,可以增加模型的表达能力,也可以避免梯度消失或爆炸的问题。

在三个图卷积层之后,我们还应用了一个线性变换层(torch.nn.Linear),这个层的作用是对每个节点的嵌入向量进行线性变换,得到每个类别的得分,然后可以用softmax函数计算每个类别的概率,这个层相当于一个分类器,可以将我们的节点映射到四个类别/社区之一。起到监督学习的作用。

我们的模型的输出有两部分,一部分是最后的分类器的输出,也就是每个节点的类别得分或概率,另一部分是最后的节点嵌入矩阵,也就是每个节点在图卷积神经网络的嵌入空间中的表示,这两部分都可以用于不同的目的。

import torch
from torch.nn import Linear
from torch_geometric.nn import GCNConv
# 从PyTorch Geometric库中导入GCNConv类,这是一个图卷积层,可以实现图数据的信息传递和聚合

class GCN(torch.nn.Module):
    def __init__(self):
        super().__init__()
        torch.manual_seed(1234)
        # 创建一个GCNConv层,命名为conv1,这个层的输入特征维度是dataset.num_features,
        # 也就是数据集中每个节点的特征数,输出特征维度是4,也就是经过这个层后,每个节点的特征会变成4维的向量。
        self.conv1 = GCNConv(dataset.num_features, 4)
        # 创建一个GCNConv层,命名为conv2,这个层的输入特征维度是4,
        # 也就是上一个层的输出特征维度,输出特征维度也是4,也就是经过这个层后,每个节点的特征仍然是4维的向量。
        self.conv2 = GCNConv(4, 4)
        # 创建一个GCNConv层,命名为conv3,这个层的输入特征维度是4,
        # 也就是上一个层的输出特征维度,输出特征维度是2,也就是经过这个层后,每个节点的特征会变成2维的向量,
        # 这个向量可以看作是节点在图卷积神经网络的嵌入空间中的表示。
        self.conv3 = GCNConv(4, 2)
        # 创建一个Linear层,命名为classifier,这个层的输入特征维度是2,
        # 也就是上一个层的输出特征维度,输出特征维度是dataset.num_classes,
        # 也就是数据集中的类别数,这个层的作用是对每个节点的嵌入向量进行线性变换,
        # 得到每个类别的得分,然后可以用softmax函数计算每个类别的概率。
        self.classifier = Linear(2, dataset.num_classes)

    def forward(self, x, edge_index):
        '''
        定义模型的前向传播方法,这里需要接收两个参数,
        x是节点特征矩阵,每一行是一个节点的特征向量,
        edge_index是边索引矩阵,每一列是一条边的两个端点的索引,
        这两个参数可以描述一个图的结构和属性。
        '''
        h = self.conv1(x, edge_index)
        # 调用conv1层,传入节点特征矩阵和边索引矩阵,得到经过第一次图卷积后的节点特征矩阵,命名为h。
        h = h.tanh()
        # 对h矩阵中的每个元素应用双曲正切函数,这是一种非线性激活函数,
        # 可以增加模型的表达能力,也可以避免梯度消失或爆炸的问题。
        h = self.conv2(h, edge_index)
        # 调用conv2层,传入经过第一次激活后的节点特征矩阵和边索引矩阵,
        # 得到经过第二次图卷积后的节点特征矩阵,仍然命名为h,覆盖原来的值。
        h = h.tanh()
        # 对h矩阵中的每个元素应用双曲正切函数,这是第二次激活。
        h = self.conv3(h, edge_index)
        h = h.tanh()  # Final GNN embedding space.

        # Apply a final (linear) classifier.
        # 调用classifier层,传入节点嵌入矩阵,得到每个节点的类别得分矩阵,
        # 命名为out,这个矩阵可以用于计算交叉熵损失或预测节点的类别。
        out = self.classifier(h)
        
        # 返回类别得分矩阵和节点嵌入矩阵
        return out, h
    
# 创建一个GCN类的实例,命名为model,这是一个图卷积神经网络的模型对象,可以用于训练或测试
model = GCN()
print(model)
GCN(
  (conv1): GCNConv(34, 4)
  (conv2): GCNConv(4, 4)
  (conv3): GCNConv(4, 2)
  (classifier): Linear(in_features=2, out_features=4, bias=True)
)

前面说了,我们的模型分为两部分,一部分是用图卷积进行图嵌入,得到节点的低维向量表示,另一部分则是一个分类器,起到监督学习的作用,根据标签来调整前面图卷积的效果。

我们为了能够看到GNN的训练的效果,我们可以比较这两个节点的嵌入向量:①不经过训练,只有图卷积嵌入得到的低维向量;②经过训练。

我们可以通过两种方法来评估,第一种是直接看四类样本的分布情况,由于我们最终是用2维向量进行表示的,所以很直观地可以用二维平面可视化;第二种是用嵌入向量构建分类器,如果用同样的分类器训练前和训练后的效果一样,那就说明图卷积没啥用,反之则可以说明还是有用的。

model = GCN()

_, h_no_train = model(data.x, data.edge_index)
print(f'Embedding shape: {list(h_no_train.shape)}')

visualize(h_no_train, color=data.y)
Embedding shape: [34, 2]

# 导入sklearn的模块
from sklearn.tree import DecisionTreeClassifier # 决策树分类器
from sklearn.model_selection import train_test_split # 数据切分
from sklearn.metrics import accuracy_score # 准确率评估
from sklearn.tree import plot_tree # 决策树绘图
import matplotlib.pyplot as plt # 画图工具

# 划分训练集和测试集,随机种子设为0,测试集比例设为0.2
X_train, X_test, y_train, y_test = train_test_split(h_no_train.detach().numpy(), data.y.detach().numpy(), random_state=10, test_size=0.2)

# 创建一个决策树分类器,使用默认参数,随机种子设为0
clf = DecisionTreeClassifier(random_state=0)

# 训练模型
clf.fit(X_train, y_train)

# 预测测试集
y_pred = clf.predict(X_test)

# 计算准确率
acc = accuracy_score(y_test, y_pred)
print("The accuracy score is:", acc)

# 可视化决策树
plt.figure(figsize=(10, 10)) # 设置画布大小
plot_tree(clf, feature_names=["f1", "f2", "f3", "f4"], class_names=["c1", "c2", "c3", "c4"], filled=True) # 绘制决策树,填充颜色,设置特征名和类别名
plt.show() # 显示图像

The accuracy score is: 0.7142857142857143

# 导入sklearn的模块
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.metrics import precision_recall_curve, roc_curve, auc
from sklearn.model_selection import KFold

X = h_no_train.detach().numpy()
y = data.y.detach().numpy()

# 创建一个逻辑回归模型,使用默认参数,随机种子设为0
clf = LogisticRegression(random_state=0)

# 使用5折交叉验证,得到每折的准确率
acc_scores = cross_val_score(clf, X, y, cv=3, scoring='accuracy')

# 计算平均的准确率
acc_mean = acc_scores.mean()

# 打印平均的准确率
print("The average accuracy score is:", acc_mean)

# 导入matplotlib.pyplot模块
import matplotlib.pyplot as plt

# 设置柱状图的x轴坐标和高度
x = ["Fold 1", "Fold 2", "Fold 3"]
height = acc_scores

# 绘制柱状图
plt.bar(x, height, color="grey")

# 添加标题和坐标轴标签
plt.title("Cross Validation Scores")
plt.xlabel("Folds")
plt.ylabel("Accuracy")
plt.ylim(0, 1) # 设置y轴的范围为0~1

# 计算平均值
mean = sum(acc_scores) / len(acc_scores)

# 在图上显示平均值
plt.axhline(y=mean, color="red", linestyle="--")
plt.text(0.5, mean + 0.01, f"Mean = {mean:.2f}")

# 显示图形
plt.show()
The average accuracy score is: 0.35353535353535354

模型如何训练?

上面是训练前的,下面我们来看看训练后的。

要看训练后的,当然得知道怎么训练。

先看看这些关键的数据长什么样。

print(data.train_mask)
# print(data.train_mask.shape())
print(data.x)
# print(data.x.shape())
print(data.edge_index)
# print(data.edge_index.shape())

image-20240109113913742

下面谈谈模型训练几个关键的地方,注释也非常详细。

这里的data.train_mask,其实就是一个[ True, False, False,…]的一个用来索引的向量,里面有4个是True,所以out[data.train_mask]就是选了4个出来。

train_mask虽然叫train_mask,但我觉得是起到一个测试的作用:一是用来在训练过程中计算这四个节点(也是样本)的loss,并更新参数使得loss减少;二是在一次训练结束后用这四个样本来计算一下准确率,作为training accuracy。

再说一下validation accuracy,前面说了,更新参数是根据train_mask的,计算准确率也是train_mask,那么在全图上的表现如何呢?也就是在所有样本的表现如何呢?于是就把上面的out[data.train_mask]直接换成out了。

import time
from IPython.display import Javascript  # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 430})'''))

model = GCN()
# 定义交叉熵损失函数,用于计算模型的预测值和真实值之间的差异
criterion = torch.nn.CrossEntropyLoss()  # Define loss criterion.
# 定义 Adam 优化器,用于根据损失函数的梯度更新模型的参数,学习率为 0.01
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)  # Define optimizer.

def train(data):
    '''
    这里的data.train_mask,其实就是一个[ True, False, False,......]的一个用来索引的向量
    里面有4个是True,所以out[data.train_mask]就是选了4个出来,虽然叫train_mask,但我觉得是起到一个测试的作用
    一是用来在训练过程中计算这四个节点(也是样本)的loss,并更新参数使得loss减少
    二是在一次训练结束后用这四个样本来计算一下准确率,作为training accuracy
    再说一下validation accuracy,前面说了,更新参数是根据train_mask的,
    计算准确率也是train_mask,那么在全图上的表现如何呢?也就是在所有样本的表现如何呢?
    于是就把上面的out[data.train_mask]直接换成out了。
    '''
    
    # 调用优化器的 zero_grad 方法,清除之前的梯度
    optimizer.zero_grad()  # Clear gradients.
    # 调用模型的 forward 方法,传入节点特征 data.x 和边索引 data.edge_index,得到模型的输出 out 和节点嵌入 h
    out, h = model(data.x, data.edge_index)  # Perform a single forward pass.
    # 调用损失函数,传入模型输出的训练节点部分 out[data.train_mask] 和真实标签的训练节点部分 data.y[data.train_mask],计算训练损失
    loss = criterion(out[data.train_mask], data.y[data.train_mask])  # Compute the loss solely based on the training nodes.
    # 调用损失的 backward 方法,求出损失函数对模型参数的梯度
    loss.backward()  # Derive gradients.
    # 调用优化器的 step 方法,根据梯度更新模型参数
    optimizer.step()  # Update parameters based on gradients.

    # 上面是训练部分,下面就是看训练的怎么样了
    # 定义一个字典,用于存储训练准确率和验证准确率
    accuracy = {}
    # Calculate training accuracy on our four examples
    predicted_classes = torch.argmax(out[data.train_mask], axis=1) # [0.6, 0.2, 0.7, 0.1] -> 2
    target_classes = data.y[data.train_mask]
    accuracy['train'] = torch.mean(
        torch.where(predicted_classes == target_classes, 1, 0).float())

    # Calculate validation accuracy on the whole graph
    predicted_classes = torch.argmax(out, axis=1)
    target_classes = data.y
    accuracy['val'] = torch.mean(
        torch.where(predicted_classes == target_classes, 1, 0).float())

    return loss, h, accuracy

# 定义一个循环,迭代 100 次
for epoch in range(100):
    loss, h, accuracy = train(data)
    # Visualize the node embeddings every 10 epochs
    if epoch % 10 == 0:
        visualize(h, color=data.y, epoch=epoch, loss=loss, accuracy=accuracy)
        time.sleep(0.3)

可以看到随着训练次数的增加四类点的分布也呈现了区域化的特征,说明训练是有用的。

# 导入sklearn的模块
from sklearn.tree import DecisionTreeClassifier # 决策树分类器
from sklearn.model_selection import train_test_split # 数据切分
from sklearn.metrics import accuracy_score # 准确率评估
from sklearn.tree import plot_tree # 决策树绘图
import matplotlib.pyplot as plt # 画图工具

# 划分训练集和测试集,随机种子设为0,测试集比例设为0.2
X_train, X_test, y_train, y_test = train_test_split(h.detach().numpy(), data.y.detach().numpy(), random_state=10, test_size=0.2)

# 创建一个决策树分类器,使用默认参数,随机种子设为0
clf = DecisionTreeClassifier(random_state=0)

# 训练模型
clf.fit(X_train, y_train)

# 预测测试集
y_pred = clf.predict(X_test)

# 计算准确率
acc = accuracy_score(y_test, y_pred)
print("The accuracy score is:", acc)

# 可视化决策树
plt.figure(figsize=(10, 10)) # 设置画布大小
plot_tree(clf, feature_names=["f1", "f2", "f3", "f4"], class_names=["c1", "c2", "c3", "c4"], filled=True) # 绘制决策树,填充颜色,设置特征名和类别名
plt.show() # 显示图像
The accuracy score is: 0.8571428571428571

# 导入sklearn的模块
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.metrics import precision_recall_curve, roc_curve, auc
from sklearn.model_selection import KFold

X = h.detach().numpy()
y = data.y.detach().numpy()

# 创建一个逻辑回归模型,使用默认参数,随机种子设为0
clf = LogisticRegression(random_state=0)

# 使用5折交叉验证,得到每折的准确率
acc_scores = cross_val_score(clf, X, y, cv=3, scoring='accuracy')

# 计算平均的准确率
acc_mean = acc_scores.mean()

# 打印平均的准确率
print("The average accuracy score is:", acc_mean)

# 导入matplotlib.pyplot模块
import matplotlib.pyplot as plt

# 设置柱状图的x轴坐标和高度
x = ["Fold 1", "Fold 2", "Fold 3"]
height = acc_scores

# 绘制柱状图
plt.bar(x, height, color="grey")

# 添加标题和坐标轴标签
plt.title("Cross Validation Scores")
plt.xlabel("Folds")
plt.ylabel("Accuracy")
plt.ylim(0, 1) # 设置y轴的范围为0~1
# 计算平均值
mean = sum(acc_scores) / len(acc_scores)

# 在图上显示平均值
plt.axhline(y=mean, color="red", linestyle="--")
plt.text(0.5, mean + 0.01, f"Mean = {mean:.2f}")

# 显示图形
plt.show()

The average accuracy score is: 0.8560606060606061

可以看到,经过训练后,这些点的二维分布(嵌入向量)比之前更加离散,也就是更容易将其分开,换句话说就是分类器可以更好地进行分类,这也就导致了分类器在训练后数据通常会比在训练前数据上表现得更好。

结语

图神经网络的学习道阻且长,但行则将至,动手写起代码来吧,我们一起努力!如果觉得还不错,可以点赞收藏哟!

欣赏一下苏轼的寒食帖

Calligraphy by Su Shi: A detail of The Cold Food Observance (寒食帖)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/313500.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL-DCL-如何用户管理,如何给用户权限?

🎉欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克🍹 ✨博客主页:小小恶斯法克的博客 🎈该系列文章专栏:重拾MySQL 🍹文章作者技术和水平很有限,如果文中出现错误&am…

【一周安全资讯0106】国家标准《信息安全技术 网络安全信息报送指南》正式发布;全球1100万SSH服务器面临“水龟攻击”威胁

要闻速览 1、国家标准GB/T 43557-2023《信息安全技术 网络安全信息报送指南》发布 2、《未成年人网络保护条例》元旦起施行 织密未成年人网络保护立体“安全网” 3、深圳证监局:证券期货经营机构应建立健全网络安全应急处置机制 4、黑客大规模恶意注册与ChatGPT相似…

创建一个简单鸿蒙app项目

文章目录 前言TypeScript 基础类型创建一个鸿蒙app总结 一、前言 鸿蒙系统上的开发已经是趋势了,必须紧跟时代的潮流。先简单了解下鸿蒙系统中,我们开发一个app需要用到的语言,那么就是TypeScript。这篇文章主要讲的就是一些基础的语法。最…

mysql数据库被黑恢复—应用层面delete删除---惜分飞

客户的mysql被人从应用层面攻击,并且删除了一些数据,导致业务无法正常使用,通过底层分析binlog确认类似恢复操作 确认这类的业务破坏是通过delete操作实现的,客户那边不太幸,客户找了多人进行恢复,现场严重破坏,老库被删除,并且还原了历史的备份文件(非故障第一现场),通过底层…

adrv9009使用记录

这里写自定义目录标题 1.首先下载cygwin,CSDN可以直接搜索,按照对应的安装就可以,最后记得加一个make安装包,不然在make时候会导致指令不存在 2.下载完成之后,去adi官网找到对应版本的adrv9009工程 我得电脑是2018.3&a…

2024年第十届控制、自动化与机器人国际会议(ICCAR 2024)即将召开!

2024年4月27~29日 新加披 会议官网:10th-ICCAR 2024https://iccar.org/index.html 第十届控制、自动化和机器人国际会议将于2024年4月27-29日在新加坡举办。本次会议由新加坡电子学会,IEEE机器人和自动控制协会和IEEE联合主办,并得到北京航空…

《SPSS统计学基础与实证研究应用精解》视频讲解:SPSS调查问卷获取数据的流行操作

《SPSS统计学基础与实证研究应用精解》1.3 视频讲解 视频为《SPSS统计学基础与实证研究应用精解》张甜 杨维忠著 清华大学出版社 一书的随书赠送视频讲解1.3节内容。本书已正式出版上市,当当、京东、淘宝等平台热销中,搜索书名即可。本书旨在手把手教会使…

行为型设计模式——观察者模式

观察者模式 观察者模式也不难,这个模式用大白话将就是若干个观察者类都订阅一个发布类(被观察者类),当发布者需要发表消息的时候,观察者都能够收到消息。**定义:**又被称为发布-订阅(Publish/S…

Making Large Language Models Perform Better in Knowledge Graph Completion

Making Large Language Models Perform Better in Knowledge Graph Completion 基本信息 博客贡献人 鲁智深 作者 Yichi Zhang, Zhuo Chen, Wen Zhang, Huajun Chen 隶属于浙江大学计算机学院和软件学院 摘要 本文主要探讨了如何将有用的知识图谱结构信息融入大语言模型中…

线性布局(Row/Column)

目录 1、概述 2、基本概念 3、布局子元素在排列方向上的间距 3.1、Column容器内排列方向上的间距 3.2、Row容器内排列方向上的间距 4、布局子元素在交叉轴上的对齐方式 4.1、Column容器内子元素在水平方向上的排列 4.2、Row容器内子元素在垂直方向上的排列 5、布局子元…

Sam Altman 与同性男友结婚;传微软正讨论亚洲研究院去留丨 RTE 开发者日报 Vol.125

开发者朋友们大家好: 这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE (Real Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

Python实现简易版选课系统

嗨喽,大家好呀~这里是爱看美女的茜茜呐 需求 学生选课系统: 学生 信息:学号,姓名,住址,选的课程列表 功能: 查看:查看该学生所有课程添加课程:把选好得课程添加到课程列…

Python数据分析案例31——中国A股的月份效应研究(方差分析,虚拟变量回归)

案例背景 本次案例是博主本科在行为金融学课程上做的一个小项目,最近看很多经管类的学生作业都很需要,我就用python来重新做了一遍。不弄那些复杂的机器学习模型了,经管类同学就用简单的统计学方法来做模型就好。 研究目的 有效市场假说是现…

基于ssm社区医疗保健监控系统+vue论文

摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统社区医疗保健信息管理难度大,容错率低&#x…

生成式人工智能研究焦点:揭秘基于扩散的模型

生成式人工智能研究焦点:揭秘基于扩散的模型 文章目录 生成式人工智能研究焦点:揭秘基于扩散的模型去噪扩散是什么让扩散发挥作用?采样生成图像的设计选择以更少的步骤理顺流程在低噪音水平下步骤高阶求解器可实现更准确的步骤训练降噪器的设…

Lagrange对偶法

这里写自定义目录标题 5.1.1 The Lagrangian5.1.2 The Lagrange dual function5.2 The Lagrange dual problem5.2.3 Strong duality and Slater’s constraint qualification5.2.3 Strong duality and Slater’s constraint qualification5.5.3 KKT optimality conditions Lagr…

C 语言每日一题——旋转数组的最小数字

一、题目内容 提供一下该OJ题的链接:旋转数组的最小数字_牛客题霸_牛客网 (nowcoder.com) 二、题目分析 通过示例1可知,我们写代码的目的是在数组中找到一个最大值,并且返回来; 我们很容易的会想到创建一个变量:int…

天软特色因子看板 (2024.1 第6期)

该因子看板跟踪天软特色因子A04001(当日趋势强度),该因子为反映股价走势趋势强弱,用以刻画股价走势趋势强弱,abs(值)越接近1,趋势 性越强,符号代表涨跌方向。 今日为该因子跟踪第6期,跟踪其在SW801040 (申万…

深入理解UML中的继承关系

深入理解UML中的继承关系 在面向对象的设计中,继承关系是构建清晰、可维护系统的关键。统一建模语言(UML)提供了一种标准化的方法来可视化这些关系。本文将深入探讨UML中的继承关系,并探讨它如何在代码中体现。 什么是继承关系&a…

如何修复DLL错误或丢失的问题,这里提供几种方法

DLL错误是指DLL文件的任何错误,一种以.dll文件扩展名结尾的文件。 DLL错误可能出现在微软的任何操作系统中,包括Windows 10、Windows 8、Windows 7、Windows Vista和Windows XP。 DLL错误尤其麻烦,因为存在许多这样类型的文件,所…