【图神经网络】 AM-GCN代码实战(4)【pytorch】代码可运行

请添加图片描述

AM-GCN 网络系列

  • 代码实践部分
  • 1. dataprocess.py
    • 1.1 模块导入
    • 1.2 特征文件生成
    • 1.3 KNN构图
  • 2. configparser.py
  • 3. layers.py
  • 4. models.py
  • 5. utils.py
  • 6. main.py
  • 总结

代码实践部分

本专栏致力于深入探讨图神经网络模型相关的学术论文,并通过具体的编程实验来深化理解。读者可以根据个人兴趣选择相关内容进行学习。在上一节中详细解读了 “AM-GCN” 这篇论文以及如何运行和代码文件的整体情况。对于那些对传统图神经网络感兴趣的读者,可以通过点击此处查阅更多相关内容。

在本章节中我们将讲解该论文模型的主体代码。我会按照文件的划分设定章节,各位可按需求进行跳转。

这个原文的代码地址感兴趣的读者自行下载即可 https://github.com/2578562306/AM-GCN

在这里插入图片描述

😃当然要是觉得还不错的话,烦请点赞,收藏➕关注👍

1. dataprocess.py

首先介绍这个代码文件的原因是因为它独立于模型的其余运行部分。

执行这个代码模型就会对数据集提供的[‘y’, ‘ty’, ‘ally’,‘x’, ‘tx’, ‘allx’,‘graph’]文件进行预处理,生成符合模型需要的文件数据。

即上一节讲解数据集解压得到的文件,就是当前文件代码生成的。当然好奇Cora数据是如何变成[‘y’, ‘ty’, ‘ally’,‘x’, ‘tx’, ‘allx’,‘graph’]文件的读者同样可以点击这里看我的这个博文。对其进行深入的研究。

明确了这个代码的主要能力— 将原始数据转换成模型需要的数据形式。 然后我们再来看着部分的代码具体是如何实现这个能力的呢???下面我们详细解读各部分的功能和执行逻辑:

可以看到文件分为五个部分,导入各种模块的部分和文件代码下的四个函数。

请添加图片描述

1.1 模块导入

我们首先讲解,使用了哪些函数他们具备的功能:

import sys 
import pickle as pkl 
import numpy as np
import scipy.sparse as sp
from sklearn.metrics.pairwise import cosine_similarity as cos
from sklearn.metrics import pairwise_distances as pair
from utils import normalize 

最后一个导入的是通过当前AMGCN文件下utils构建的正则化我们后续讲解其文件再解释,这里对上面常用的包进行解释:

import sys

  • 功能:该模块提供了一些针对Python运行环境的函数和变量。常用于与Python解释器交互或访问由解释器使用或维护的变量。
  • 用途:在这个代码中,sys模块可能被用来访问系统相关的信息,比如Python版本信息(sys.version_info)。这在处理版本兼容问题时尤其有用。

import pickle as pkl

  • 功能pickle是一个序列化和反序列化Python对象结构的模块。序列化过程将Python对象转换为字节流,而反序列化过程恢复字节流回Python对象。
  • 用途:在这段代码中,pickle模块用来加载保存在文件中的Python对象。这对于读取那些在之前某个时刻被序列化并存储下来的Python对象(例如,数据集特征、标签、图结构等)特别重要。

import numpy as np

  • 功能numpy是Python的一个强大的数值计算扩展。此库支持高阶大量维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。
  • 用途numpy在处理任何形式的数值数据时几乎是必需的。在处理图数据或机器学习数据时,常用于数据的转换、标准化,以及执行各种数学运算。

import scipy.sparse as sp

  • 功能scipy.sparse提供了对稀疏矩阵的支持。稀疏矩阵是大部分元素为0的矩阵,使用稀疏矩阵可以在存储和计算上大大节省空间和时间。
  • 用途:在图数据处理中,邻接矩阵往往是稀疏的,使用scipy.sparse可以高效地处理这些数据。

from sklearn.metrics.pairwise import cosine_similarity as cos

  • 功能:从Scikit-Learn库中导入cosine_similarity函数,用于计算数据点间的余弦相似度。
  • 用途:计算节点或数据点之间的相似度,常用于构建基于特征相似度的图结构,如在生成KNN图中使用。

from sklearn.metrics import pairwise_distances as pair # 这个在论文代码中未使用。

  • 功能:导入pairwise_distances函数,该函数用于计算成对数据点之间的距离。
  • 用途:虽然在此代码段中未直接使用,但这个函数通常用于评估数据点间的距离,有助于完成例如聚类、KNN等基于距离的机器学习任务。

这些模块和函数为数据的加载、处理和图的构造提供了基础设施,是数据科学和机器学习应用的常用工具。

1.2 特征文件生成

了解所用库的功能是非常重要的,因为它为我们之后对函数实际应用的理解打下基础。 在这个文件中提供的四个函数可以逻辑上分为两组,主要基于它们的功能是否依赖进行分组。这种分组方法有助于更清晰地理解每组函数的专门用途,并展示它们是如何协同工作来处理数据和构建图。

函数 parse_index_fileprocess_data 在数据处理流程中具有不同的功能层次,并且相互依赖。以下是对这两个函数之间关系的更详细阐述:

函数:parse_index_file

  • 功能:这个函数专门用来解析存储索引的文件,返回一个整型列表。这些索引通常代表重要的数据分割点,例如标识出数据集中哪些是测试节点。
  • 特点parse_index_file 本身并不进行复杂的数据处理,而是提供必要的辅助功能,使得 process_data 函数能够正确地引用和处理特定的数据部分,如测试数据集。

函数:process_data

  • 功能:这个函数是数据预处理的核心,负责加载、整理、和预处理数据。它处理包括特征、标签、图结构等多种类型的数据,并将它们结构化成适合进一步机器学习和数据分析操作的格式。
  • 依赖关系process_data 函数依赖 parse_index_file 来获取正确的测试节点索引。这是因为数据处理中往往需要特别处理某些数据片段(如区分训练集和测试集),而这些片段是通过解析索引文件得到的。

功能组合

  • 在这个功能组合中,parse_index_file 虽然功能较为简单,但它对 process_data 的成功执行至关重要。这种层次和依赖关系体现了一个大的、复杂功能(数据处理)如何依赖于较小、专一的子功能(解析索引)来实现。
  • 通过将 parse_index_file 作为 process_data 的一个子功能来看待,我们可以更清楚地理解数据处理步骤中的内部逻辑和流程,确保数据的正确加载和处理。

具体的可以看到process_data 在代码内容要使用函数parse_index_file 实现的功能对文件进行处理。

在这里插入图片描述

接下来通过代码注释进行讲解

def parse_index_file(filename):"""Parse index file."""index = []  # 初始化一个空列表用来存储索引。for line in open(filename):  # 打开索引文件,并迭代每一行。index.append(int(line.strip()))  # 移除每行两端的空白符,并将其转换为整数后添加到列表中。return index  # 返回索引列表。def process_data(dataset):names = ['y', 'ty', 'ally', 'x', 'tx', 'allx', 'graph']  # 定义数据类型名称列表。objects = []  # 初始化一个空列表用来存储各数据类型的对象。for i in range(len(names)):  # 遍历数据类型名称列表。with open("../data/cache/ind.{}.{}".format(dataset, names[i]), 'rb') as f:  # 打开各数据类型文件。if sys.version_info > (3, 0):  # 检查Python版本。objects.append(pkl.load(f, encoding='latin1'))  # 使用Python 3的方式读取pickle文件。else:objects.append(pkl.load(f))  # 使用Python 2的方式读取pickle文件。y, ty, ally, x, tx, allx, graph = tuple(objects)  # 将读取的数据分配到对应变量。
# a = [1, 2, 3]
# b, c, d = a
# y, ty, ally, x, tx, allx, graph = tuple(objects) 中的 tuple(objects) 
# 是为了确保 objects 是一个元组类型,tuple(objects) 实现,它将列表 objects 转换成了一个元组。print(graph)  # 打印图结构。# ---------------------------------------------------
# 下面开始使用上面构建的函数了
# ---------------------------------------------------test_idx_reorder = parse_index_file("../data/cache/ind.{}.test.index".format(dataset))  # 解析测试节点索引文件。test_idx_range = np.sort(test_idx_reorder)  # 对测试节点索引进行排序。if dataset == 'citeseer':  # 特定于'citeseer'数据集的处理。test_idx_range_full = range(min(test_idx_reorder), max(test_idx_reorder) + 1)  # 创建一个完整的测试索引范围。tx_extended = sp.lil_matrix((len(test_idx_range_full), x.shape[1]))  # 初始化一个新的大小为完整测试范围的稀疏矩阵。tx_extended[test_idx_range - min(test_idx_range), :] = tx  # 在对应位置填充原有的测试特征数据。tx = tx_extended  # 更新测试特征数据集。ty_extended = np.zeros((len(test_idx_range_full), y.shape[1]))  # 初始化一个全零的标签数组。ty_extended[test_idx_range - min(test_idx_range), :] = ty  # 在对应位置填充原有的测试标签数据。ty = ty_extended  # 更新测试标签数据集。labels = np.vstack((ally, ty))  # 垂直堆叠训练和测试标签数据。labels[test_idx_reorder, :] = labels[test_idx_range, :]  # 重新排列标签数据以匹配原测试索引。
# ---------------------------------------------------
# 下面详细讲了上面的一行代码问题,目的是确保使用的数据和GCN中论文是一致的,测试的节点也是一样的
# --------------------------------------------------- features = sp.vstack((allx, tx)).tolil()  # 垂直堆叠训练和测试特征数据,并转换为LIL格式。features[test_idx_reorder, :] = features[test_idx_range, :]  # 重新排列特征数据以匹配原测试索引。features = features.toarray()  # 将特征数据转换为普通数组。print(features)  # 打印特征数据。f = open('../data/{}/{}.adj'.format(dataset, dataset), 'w+')  # 打开文件以写入邻接表。这个仅仅是写入了边信息for i in range(len(graph)):adj_list = graph[i]  # 获取每个节点的邻接列表。for adj in adj_list:f.write(str(i) + '\t' + str(adj) + '\n')  # 将每个邻接关系写入文件。f.close()  # 关闭文件。label_list = []  # 初始化一个空列表用来存储最终的标签。for i in labels:label = np.where(i == np.max(i))[0][0]  # 找到每个标签向量中最大值的索引。label_list.append(label)  # 将索引添加到列表中。np.savetxt('../data/{}/{}.label'.format(dataset, dataset), np.array(label_list), fmt='%d')  # 保存标签数据。np.savetxt('../data/{}/{}.test'.format(dataset, dataset), np.array(test_idx_range), fmt='%d')  # 保存测试索引。np.savetxt('../data/{}/{}.feature'.format(dataset, dataset), features, fmt='%f')  # 保存特征数据。**

这个代码我在此详细解释下

features[test_idx_reorder, :] = features[test_idx_range, :]  # 重新排列特征数据以匹配原测试索引。

操作还是过于抽象了,我在这里解释下其核心目的:
确实,这段代码的操作可能显得有点晦涩,主要是因为这里涉及到索引的重新排序,这会改变数据的原有排列顺序。让我们通过一个例子来具体解释这段代码的作用。

假设我们有一些数据,并且这些数据已经被分割成训练集和测试集。这里的操作主要是关注如何根据测试集的索引来重排整个数据集,以保证测试数据可以按照一定的顺序进行处理。

原始数据示例
假设我们有以下的数据标签(标签示例简化为数字):

  • ally = [10, 20, 30] (训练数据的标签)
  • ty = [40, 50, 60, 70] (测试数据的标签)

合并后的标签数组 labels 为:
[
[10, 20, 30, 40, 50, 60, 70]
]

假设 test_idx_reorder = [5, 3, 6, 4],这是一些从数据读取或其他途径获得的测试数据索引,表明这些索引处的数据是用于测试的。就是最后的四个节点用于测试并且顺序被打乱了

重排序操作解释

  1. 排序:

    test_idx_range = np.sort(test_idx_reorder)  # [3, 4, 5, 6]
    
  2. 重排操作
    初始 labels = [10, 20, 30, 40, 50, 60, 70]

    labels 的索引进行操作,其中 test_idx_reorder 指的是原始的测试索引,例如我们希望按照这个索引取数据,
    test_idx_range 是排序后的索引,这个排序的目的是希望能获取按顺序排列的测试数据。

    这里的关键操作是:

    labels[test_idx_reorder, :] = labels[test_idx_range, :]
    
    • 这个操作把 labelstest_idx_range 索引处的项(即 [40, 50, 60, 70])取出来,
    • 然后将这些项按 test_idx_reorder 的顺序重新放入 labels 中。

    执行后,labels 看起来像这样:

    • 步骤一: 从 labels 中按 test_idx_range ([3, 4, 5, 6])顺序取值 --> [40, 50, 60, 70]
    • 步骤二: 按照 test_idx_reorder 的顺序[5, 3, 6, 4]放回 labels
    • 结果:labels = [10, 20, 30, 50, 70, 40, 60]

1.3 KNN构图

分析模式应用于函数 construct_graphgenerate_knn

函数:construct_graph

  • 功能:此函数的主要目标是根据提供的特征数据构建一个基于K近邻(KNN)的图结构。它利用特征之间的相似度(在此示例中为余弦相似度)来确定节点之间的连接。
  • 特点construct_graph 直接操作数值数据以构建图的邻接列表。它独立处理每一个特征向量,确定与之相似度最高的topk个节点,并记录这些关系。因而,此函数执行了关键的图结构构建任务,但依赖于外部提供的准确和适当预处理的特征数据。

函数:generate_knn

  • 功能:作为数据处理流程的控制中心,该函数负责生成不同topk值的KNN图。它循环调用construct_graph函数,以多种邻居数量构建多个图版本,这对于评估不同K值在图模型性能中的影响非常有用。
  • 依赖关系generate_knn 依赖于construct_graph来为每个topk设置实现具体的图构建。此外,它还依赖于从文件中加载处理后的特征数据,以及控制文件的读写来存储生成的图数据。

功能组合

  • 在这组功能中,construct_graph 并不是简单的函数,它实现了图构建的全部逻辑,处理复杂的数学计算以及邻接关系的确定。尽管如此,它作为generate_knn中的构建步骤被重复调用,体现了在更大框架下的专一性和重要性。
  • generate_knn 函数则扮演了更高层次的角色,它不仅控制图的构建过程,还管理着数据的加载和终构建图数据的保存。这显示了它在数据处理流程中的核心地位,它将construct_graph的输出整合并展开为成熟的数据产品——即为分析或机器学习任务准备的图结构。

具体的可以看到generate_knn 在代码内容要使用函数construct_graph 实现的功能对文件进行处理。

在这里插入图片描述

接下来通过代码注释进行讲解

def construct_graph(dataset, features, topk, knn_directory):# 构建完整的文件路径用于保存KNN图fname = os.path.join(knn_directory, 'tmp.txt')# 以写模式打开文件with open(fname, 'w') as f:# 计算特征之间的余弦相似度cosine_distances = cos(features)# 对每个节点,确定其topk相似的邻居节点for i in range(cosine_distances.shape[0]):indices = np.argpartition(cosine_distances[i], -(topk + 1))[-(topk + 1):]
'''
array = np.array([10, 7, 4, 3, 2, 2, 5, 9, 0, 4, 6, 0])
#返回一个索引,比原数组第5大(从0开始)的数小的数在这个数之前,比这个数大的数在它之后。
index = np.argpartition(array, 4)
#输出,新索引
print(index)
#[ 4 11  8  5  3  2  9  6  1 10  7  0]
#按这个新索引可以重新排列数组
print(array[index])
#[ 2  0  0  2  3  4  4  5  7  6  9 10]
#第5大的数是3,比3小的在3之前,比3大的在3之后
#还是上边那个数组,输出top5
array = np.array([10, 7, 4, 3, 2, 2, 5, 9, 0, 4, 6, 0])
array[np.argpartition(array, -5)[-5:]]
#输出:[ 5,  7,  6,  9, 10]
'''# 在文件中记录每个节点与其邻居的关系,排除自身for index in indices:if index != i:f.write(f"{i} {index}\n")# 打印确认KNN图已成功构建并保存print(f"Graph constructed and saved in {fname}")def generate_knn(dataset):# 基本路径设置base_path = "/Users/wangyang/Desktop/图神经网络实验代码/AM-GCN-master/data"# 数据集的路径dataset_path = f"{base_path}/{dataset}"# 确定KNN图存储的具体目录knn_path = f"{dataset_path}/knn"# 如不存在KNN目录,则创建目录if not os.path.exists(knn_path):os.makedirs(knn_path)# 拼接特征数据文件路径feature_path = os.path.join(dataset_path, f"{dataset}.feature")# 加载特征数据features = np.loadtxt(feature_path, dtype=float)# 遍历不同的topk值for topk in range(2, 10):# 调用construct_graph函数构建KNN图construct_graph(dataset, features, topk, knn_path)# 构建临时文件路径和最终文件路径tmp_file_path = os.path.join(knn_path, "tmp.txt")output_file_path = os.path.join(knn_path, f"c{topk}.txt")if os.path.exists(tmp_file_path):# 读取临时文件,并将结果写入最终文件,确保只写入有向边with open(tmp_file_path, 'r') as f1, open(output_file_path, 'w') as f2:for line in f1:start, end = line.strip().split()if int(start) < int(end):f2.write(f"{start} {end}\n")# 打印确认KNN图已生成并保存print(f"KNN graph for topk={topk} generated and saved in {output_file_path}")else:# 如果临时文件不存在,打印错误信息print(f"File not found: {tmp_file_path}")# 调用函数以生成cora数据集的KNN图
generate_knn('cora')# 生成一个包含140个训练索引的列表,并保存到文件
idx_train = [i for i in range(140)]
np.savetxt('train20.txt', idx_train, fmt='%d')

有一个细节非常关键。在 generate_knn 函数中,tmp.txt 文件的确是被反复重写而不是每次都创建一个新的文件。这里的逻辑是针对每个 topk 值都调用一次 construct_graph 函数,每次调用都会打开相同的 tmp.txt 文件并以写模式('w')打开,这意味着文件的内容会在每次打开时被清空。

因此,tmp.txt 文件在每次迭代中都被替换掉了,最终只保存了最后一次的内容,即对应于最后一个 topk 值的数据。这也解释了为什么即使没有明确的删除操作,tmp.txt 文件也不会包含之前 topk 值的数据。

这种方式具有一定的优点:

  1. 简化文件管理
  2. 节约磁盘空间

2. configparser.py

使用这种方法编写代码的主要好处是通过一个类的实例化来集中管理所有相关的配置参数。这种方式使得只需要通过外部配置文件来调整参数,从而可以在不修改代码的情况下,按需加载和修改不同的配置设置。

以下是详细注释和解释提供的Config类代码:

class Config(object):def __init__(self, config_file):# 通过 configparser 模块实例化 ConfigParser 对象conf = configparser.ConfigParser()# 尝试读取配置文件,如果不成功则打印错误信息try:conf.read(config_file)except:print(f"loading config: {config_file} failed")# 从配置文件中读取和设置模型的超参数self.epochs = conf.getint("Model_Setup", "epochs")  # 读取训练周期数self.lr = conf.getfloat("Model_Setup", "lr")  # 读取学习率self.weight_decay = conf.getfloat("Model_Setup", "weight_decay")  # 读取权重衰减参数self.k = conf.getint("Model_Setup", "k")  # 读取 KNN 的 K 值self.nhid1 = conf.getint("Model_Setup", "nhid1")  # 读取第一隐藏层维数self.nhid2 = conf.getint("Model_Setup", "nhid2")  # 读取第二隐藏层维数self.dropout = conf.getfloat("Model_Setup", "dropout")  # 读取 dropout 参数self.beta = conf.getfloat("Model_Setup", "beta")  # 读取 beta 参数self.theta = conf.getfloat("Model_Setup", "theta")  # 读取 theta 参数self.no_cuda = conf.getboolean("Model_Setup", "no_cuda")  # 确定是否使用 CUDA self.no_seed = conf.getboolean("Model_Setup", "no_seed")  # 确定是否设置随机种子self.seed = conf.getint("Model_Setup", "seed")  # 设置随机种子# 从配置文件中读取数据集相关的配置self.n = conf.getint("Data_Setting", "n")  # 数据集中图的结点数self.fdim = conf.getint("Data_Setting", "fdim")  # 特征维度self.class_num = conf.getint("Data_Setting", "class_num")  # 类别数self.structgraph_path = conf.get("Data_Setting", "structgraph_path")  # 结构图路径self.featuregraph_path = conf.get("Data_Setting", "featuregraph_path")  # 特征图路径self.feature_path = conf.get("Data_Setting", "feature_path")  # 特征数据路径self.label_path = conf.get("Data_Setting", "label_path")  # 标签数据路径self.test_path = conf.get("Data_Setting", "test_path")  # 测试集数据路径self.train_path = conf.get("Data_Setting", "train_path")  # 训练集数据路径

这种设计模式(封装所有配置于类中并通过配置文件进行管理)的优势在于提高了代码的可维护性和可扩展性,并允许快速调整参数进行不同的实验,而无需每次进入代码深层进行硬编码。感兴趣的同学麻烦催我我后续会补上这个地方的官方解释博文。

3. layers.py

这个文件就是GCN中的常用图卷积层,感兴趣的读者可以点这里看到我对GCN的详细讲解。这里我仅仅对GCN的代码进行注释便于各位理解:


class GraphConvolution(Module):"""Simple GCN layer, similar to https://arxiv.org/abs/1609.02907"""def __init__(self, in_features, out_features, bias=True):super(GraphConvolution, self).__init__()self.in_features = in_featuresself.out_features = out_featuresself.weight = Parameter(torch.FloatTensor(in_features, out_features))if bias:self.bias = Parameter(torch.FloatTensor(out_features))else:self.register_parameter('bias', None)self.reset_parameters()def reset_parameters(self):stdv = 1. / math.sqrt(self.weight.size(1))self.weight.data.uniform_(-stdv, stdv)if self.bias is not None:self.bias.data.uniform_(-stdv, stdv)def forward(self, input, adj):support = torch.mm(input, self.weight)output = torch.spmm(adj, support)if self.bias is not None:return output + self.biaselse:return outputdef __repr__(self):return self.__class__.__name__ + ' (' \+ str(self.in_features) + ' -> ' \+ str(self.out_features) + ')'

对代码的不通过功能简单的说一下:

这段代码定义了一个简单的图卷积网络(Graph Convolutional Network, GCN)层,基于论文 Semi-Supervised Classification with Graph Convolutional Networks 中描述的模型。下面是代码的逐行解释和相关概念的讲解:

class GraphConvolution(Module)

  • 定义:该类继承自 torch.nn.modules.module.Module,为自定义的图卷积层提供一个基础的网络层结构。

def init(self, in_features, out_features, bias=True):

  • 参数:
    • in_features: 输入特征的数量。
    • out_features: 输出特征的数量。
    • bias: 布尔值,指示是否在图卷积层中添加偏置项。
  • 功能
    • 初始化图卷积层。设置输入特征数、输出特征数,并根据bias参数决定是否添加偏置项。
    • self.weight: 使用 torch.nn.parameter.Parameter 为层权重创建一个可训练的参数。
    • self.bias: 如果启用了偏置,则同样创建一个可训练的偏置参数。

def reset_parameters(self):

  • 功能:初始化权重和偏置参数。
    • 权重和偏置通过均匀分布初始化,分布范围是 [-stdv, stdv],其中 stdv1 / sqrt(self.weight.size(1))。这是为了权重初始化提供合适的标准差,以保证模型的稳定性。

def forward(self, input, adj):

  • 参数:
    • input: 输入特征矩阵,维度为 (N, in_features),其中 N 是节点数。
    • adj: 邻接矩阵,通常为稀疏格式,维度为 (N, N)
  • 功能
    • 执行图卷积操作。首先计算支持矩阵 support = input @ self.weight。这是特征输入和权重矩阵的矩阵乘法。
    • 使用稀疏矩阵乘法 torch.spmm 将邻接矩阵 adj 与支持矩阵 support 相乘,得到输出特征。
    • 如果定义了偏置,则在输出上加上偏置。

def repr(self):

  • 功能:定义了类的字符串表示,用于打印和调试。它会显示类名和层的输入到输出特征的转换大小。

4. models.py

模型主体文件了,这也是文章的主要创新点。大家看到这里需要注意了,来大活了。

import torch.nn as nn
import torch.nn.functional as F
from layers import GraphConvolution # 导入之间layer中设计的图卷积层
from torch.nn.parameter import Parameter
import torch
import mathclass GCN(nn.Module): # 在这里构建了传统的图卷积神经网络def __init__(self, nfeat, nhid, out, dropout):super(GCN, self).__init__()self.gc1 = GraphConvolution(nfeat, nhid) #实例化两个图卷积层self.gc2 = GraphConvolution(nhid, out)self.dropout = dropoutdef forward(self, x, adj): # 定义网络的前向传播x = F.relu(self.gc1(x, adj)) # 第一次卷积x = F.dropout(x, self.dropout, training = self.training)x = self.gc2(x, adj) # 第二次卷积后输出全部节点的特征return xclass Attention(nn.Module): # 注意力分数的计算def __init__(self, in_size, hidden_size=16): # 输入尺寸是节点特征的尺寸,即GCN最后一层的大小super(Attention, self).__init__()self.project = nn.Sequential(nn.Linear(in_size, hidden_size), # 对输入特征进行特征选择nn.Tanh(), # 激活nn.Linear(hidden_size, 1, bias=False) #再来一个线性层,将一个节点的特征矩阵及性能映射成实数)def forward(self, z):w = self.project(z) #z是一个矩阵这个矩阵是每一行是同一个节点的不同嵌入表示所以三行,映射成了一个向量beta = torch.softmax(w, dim=1) # 向量变成了百分比return (beta * z).sum(1), beta #然后使用百分比加权求和,输出百分比情况。class SFGCN(nn.Module):def __init__(self, nfeat, nclass, nhid1, nhid2, n, dropout):super(SFGCN, self).__init__()self.SGCN1 = GCN(nfeat, nhid1, nhid2, dropout)self.SGCN2 = GCN(nfeat, nhid1, nhid2, dropout)self.CGCN = GCN(nfeat, nhid1, nhid2, dropout)
# 上面实例化三个GCN网络,论文中一致,一个是传统形态的GCN一个是KNn图的,一个是被称为共享权重的GCN。就是一个GCN被两次复用了self.dropout = dropoutself.a = nn.Parameter(torch.zeros(size=(nhid2, 1)))# 这是一个向量,注意力向量参考GAT论文中的a共享注意力向量nn.init.xavier_uniform_(self.a.data, gain=1.414) self.attention = Attention(nhid2) # 和nhid2一致就是节点特征在GCN卷积后的维度一致self.tanh = nn.Tanh() # 激活函数self.MLP = nn.Sequential(nn.Linear(nhid2, nclass),nn.LogSoftmax(dim=1))def forward(self, x, sadj, fadj):emb1 = self.SGCN1(x, sadj) # 输出每个节点的特征com1 = self.CGCN(x, sadj)  # 输出每个节点的特征com2 = self.CGCN(x, fadj)  # 输出每个节点的特征emb2 = self.SGCN2(x, fadj) # 输出每个节点的特征Xcom = (com1 + com2) / 2 # 将共享的特征做均值处理,两个矩阵想加然后除二##attentionemb = torch.stack([emb1, emb2, Xcom], dim=1) # emb, att = self.attention(emb)output = self.MLP(emb)return output, att, emb1, com1, com2, emb2, emb

其实论文中说起来比较特别的共享注意力模块仅仅是一个网络的两次复用而已。
新鲜一点的就是这个注意力机制的实现。

相同的节点特征,通过各种各样的图结构生成了相同节点的不同嵌入表示:

emb1 = self.SGCN1(x, sadj) # 输出每个节点的特征
com1 = self.CGCN(x, sadj)  # 输出每个节点的特征
com2 = self.CGCN(x, fadj)  # 输出每个节点的特征
emb2 = self.SGCN2(x, fadj) # 输出每个节点的特征

具体的一个节点有四种嵌入表示怎么融合呢???

文中将四个先变成了三个:

Xcom = (com1 + com2) / 2 # 将共享的特征做均值处理,两个矩阵想加然后除二

然后将这三个矩阵进行堆叠:

emb = torch.stack([emb1, emb2, Xcom], dim=1) # 

这里我讲讲这个 torch.stack的操作,下面是官网给出的例子:

>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]])
>>> torch.stack((x, x)) # same as torch.stack((x, x), dim=0) # 不指定堆叠维度的情况下,仅仅是增加一个维度进行堆叠
tensor([[[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]],[[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x)).size()
torch.Size([2, 2, 3])
>>> torch.stack((x, x), dim=1) # 指定维度,根据维度变成不同的堆叠方式。而文中使用的则是 dim=1,即相同的行组成一个新的组。
tensor([[[ 0.3367,  0.1288,  0.2345],[ 0.3367,  0.1288,  0.2345]],[[ 0.2303, -1.1229, -0.1863],[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x), dim=2)
tensor([[[ 0.3367,  0.3367],[ 0.1288,  0.1288],[ 0.2345,  0.2345]],[[ 0.2303,  0.2303],[-1.1229, -1.1229],[-0.1863, -0.1863]]])
>>> torch.stack((x, x), dim=-1)
tensor([[[ 0.3367,  0.3367],[ 0.1288,  0.1288],[ 0.2345,  0.2345]],[[ 0.2303,  0.2303],[-1.1229, -1.1229],[-0.1863, -0.1863]]])

然后通过注意力层计算权重对相同节点的不同嵌入表示进行加权求和从而完成特征的聚合。

emb, att = self.attention(emb)

计算得到一个节点的不同嵌入表示的注意力分数att和按照注意力加权求和的节点特征向量。最终送入到一个简单的MLP网络进行分类。最终返回output, att, emb1, com1, com2, emb2, emb。这里为什么还要输出emb1, com1, com2, emb2, emb呢?????记不记的那几个约束的问题。因此损失不仅仅通过output控制,这种设计可以通过多个输出共同优化,使得模型不仅在主任务上表现良好,同时在其他如特征表示的保留和利用上也进行优化,后面讲到了再细聊。

5. utils.py

模型训练运行需要的各种各样杂乱的工具函数都被存放在这个代码文件下,我们对其使用到的函数进行逐个分析。不过仅仅是通过函数名称也能对其观察出其主要的功能。

请添加图片描述

以下分别对每个函数进行详细解释:

common_loss(emb1, emb2) # 这就是论文中提到的一致性约束,详情可参考博文第3.4节的内容
计算两组嵌入之间的方差损失,用于模型训练中使嵌入更加一致。

  • emb1, emb2: 输入的两组节点嵌入。
  • 先对嵌入进行中心化和归一化处理。
  • 计算两组嵌入的协方差矩阵,并求这两个协方差矩阵的Frobenius范数的平方。

loss_dependence(emb1, emb2, dim) # 同样这是差异性约束参考论文详解博文的第3.4节的内容
计算两组嵌入之间的HSIC(Hilbert-Schmidt Independence Criterion)损失,用于评估它们的统计独立性。

  • emb1, emb2: 输入的两组节点嵌入。
  • dim: 嵌入的维度。
  • 使用投影矩阵消除均值的影响,并计算两个核矩阵的乘积的迹。

为了便于展示我对函数进行逐行注释便于各位理解

accuracy(output, labels)
计算模型预测的准确率。

  • output: 模型对样本的输出(通常是经过softmax的概率)。
  • labels: 真实的标签。
  • 返回预测正确的比例。
def accuracy(output, labels):preds = output.max(1)[1].type_as(labels)  # 从输出中取得每行最大值的索引,这些索引即为预测类别。correct = preds.eq(labels).double()       # 比较预测和真实标签,转化为double类型计算正确的数目。correct = correct.sum()                   # 求和得到正确预测的总数。return correct / len(labels)              # 计算准确率。

sparse_mx_to_torch_sparse_tensor(sparse_mx)
将scipy的稀疏矩阵转换为PyTorch的稀疏张量。

  • sparse_mx: scipy的稀疏矩阵。
  • 返回一个PyTorch的稀疏张量。
def sparse_mx_to_torch_sparse_tensor(sparse_mx):"""将scipy稀疏矩阵转换为torch稀疏张量。"""sparse_mx = sparse_mx.tocoo().astype(np.float32)  # 将矩阵转换为COO格式。indices = torch.from_numpy(np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))  # 创建索引数组。values = torch.from_numpy(sparse_mx.data)  # 创建值数组。shape = torch.Size(sparse_mx.shape)  # 创建形状元组。return torch.sparse.FloatTensor(indices, values, shape)  # 创建并返回PyTorch稀疏张量。

sample_mask(idx, l)
创建掩码数组。

  • idx: 需要标记为1的索引列表。
  • l: 掩码的长度。
  • 返回一个布尔数组,指示元素是否属于索引列表。**

sparse_to_tuple(sparse_mx)
将scipy的稀疏矩阵转换为元组形式。

  • sparse_mx: 可以是单个稀疏矩阵或稀疏矩阵列表。
  • 返回的元组或列表包含坐标、值和形状。
def sparse_to_tuple(sparse_mx):"""将稀疏矩阵转换为元组表示(坐标、值、形状)。"""def to_tuple(mx):if not sp.isspmatrix_coo(mx):mx = mx.tocoo()coords = np.vstack((mx.row, mx.col)).transpose()  # 坐标数组。values = mx.data  # 值数组。shape = mx.shape  # 形状。return coords, values, shapeif isinstance(sparse_mx, list):return [to_tuple(mx) for mx in sparse_mx]else:return to_tuple(sparse_mx)

normalize(mx)
行标准化稀疏矩阵。

  • mx: 输入的稀疏矩阵。
  • 返回行标准化后的矩阵。
def normalize(mx):"""对稀疏矩阵行进行归一化。"""rowsum = np.array(mx.sum(1))  # 计算每行的和。r_inv = np.power(rowsum, -1).flatten()  # 计算每行和的倒数。r_inv[np.isinf(r_inv)] = 0.  # 避免除以零。r_mat_inv = sp.diags(r_inv)  # 创建对角线矩阵。mx = r_mat_inv.dot(mx)  # 左乘原矩阵以归一化。return mx

load_data(config) # 从dataprocess文件创建的文件进行导入
用于加载并处理数据集。

  • config: 配置对象,含有数据路径等配置。
  • 加载特征、标签和训练/测试索引。
  • 返回处理后的特征张量和相关索引。
def load_data(config):# 加载节点特征f = np.loadtxt(config.feature_path, dtype=float)# 加载节点标签l = np.loadtxt(config.label_path, dtype=int)# 加载测试集索引test = np.loadtxt(config.test_path, dtype=int)# 加载训练集索引train = np.loadtxt(config.train_path, dtype=int)# 将特征数据转换为稀疏矩阵格式features = sp.csr_matrix(f, dtype=np.float32)# 将特征数据转换为稠密张量features = torch.FloatTensor(np.array(features.todense()))# 转换测试集和训练集索引为列表idx_test = test.tolist()idx_train = train.tolist()# 将测试集和训练集索引转换为PyTorch张量idx_train = torch.LongTensor(idx_train)idx_test = torch.LongTensor(idx_test)# 将标签转换为PyTorch张量label = torch.LongTensor(np.array(l))# 返回特征、标签、训练索引和测试索引return features, label, idx_train, idx_test

load_graph(dataset, config)
加载和处理图结构数据。

  • dataset: 数据集名称。
  • config: 包含配置信息的对象。
  • 加载并处理两种类型的图(特征图和结构图)。
  • 返回归一化后的图的邻接矩阵的PyTorch稀疏张量。

这些函数涵盖数据加载、预处理、损失计算以及图结构的加载和处理,为GCN及其变种的实现提供基础支持。

def load_graph(dataset, config):# 构造特征图边的文件路径featuregraph_path = config.featuregraph_path + str(config.k) + '.txt'# 加载特征图的边列表feature_edges = np.genfromtxt(featuregraph_path, dtype=np.int32)# 将边列表转换为数组形式进行处理fedges = np.array(list(feature_edges), dtype=np.int32).reshape(feature_edges.shape)# 创建特征图的邻接矩阵fadj = sp.coo_matrix((np.ones(fedges.shape[0]), (fedges[:, 0], fedges[:, 1])), shape=(config.n, config.n), dtype=np.float32)# 确保邻接矩阵是对称的fadj = fadj + fadj.T.multiply(fadj.T > fadj) - fadj.multiply(fadj.T > fadj)# 归一化邻接矩阵,并且添加自环nfadj = normalize(fadj + sp.eye(fadj.shape[0]))# 同理处理结构图的边数据struct_edges = np.genfromtxt(config.structgraph_path, dtype=np.int32)sedges = np.array(list(struct_edges), dtype=np.int32).reshape(struct_edges.shape)sadj = sp.coo_matrix((np.ones(sedges.shape[0]), (sedges[:, 0], sedges[:, 1])), shape=(config.n, config.n), dtype=np.float32)sadj = sadj + sadj.T.multiply(sadj.T > sadj) - sadj.multiply(sadj.T > sadj)nsadj = normalize(sadj + sp.eye(sadj.shape[0]))# 将稀疏矩阵转换为PyTorch的稀疏张量格式nsadj = sparse_mx_to_torch_sparse_tensor(nsadj)nfadj = sparse_mx_to_torch_sparse_tensor(nfadj)# 返回特征图和结构图的处理后的邻接矩阵return nsadj, nfadj

一共构造了两种图结果,一个是导入原始图结构,即通过数据集中边信息构建的,一个是导入KNN函数构建的图结构,用于GCN中的特征聚合。

6. main.py

啰嗦了半天终于要看到最终的训练文件可以执行代码了,这是一个完整的图卷积网络 (GCN) 通过使用注意力机制及联合训练策略进行模型训练和测试的 Python 脚本。下面是对这个代码的逐行注释和解释:

导入所需库和模块

from __future__ import division  # 确保除法在Python 2与Python 3中表现一致
from __future__ import print_function  # 确保print函数在Python 2与Python 3中表现一致
import torch
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
from utils import *  # 导入辅助函数,如数据加载、预处理等
from models import SFGCN  # 导入定义的模型
import numpy
from sklearn.metrics import f1_score
import os
import torch.nn as nn
import argparse
from config import Config  # 导入配置处理类

环境设定和命令行参数解析

if __name__ == "__main__":os.environ["CUDA_VISIBLE_DEVICES"] = "2"  # 设置CUDA设备编号parse = argparse.ArgumentParser()  # 创建命令行解析器parse.add_argument("-d", "--dataset", help="dataset", type=str, required=True)  # 添加数据集参数parse.add_argument("-l", "--labelrate", help="labeled data for train per class", type=int, required=True)  # 添加标签率参数args = parse.parse_args()  # 解析命令行参数config_file = "./config/" + str(args.labelrate) + str(args.dataset) + ".ini"  # 构建配置文件路径config = Config(config_file)  # 加载配置

CUDA设置和初始随机种子设置

    cuda = not config.no_cuda and torch.cuda.is_available()  # 判断是否使用CUDAuse_seed = not config.no_seedif use_seed:np.random.seed(config.seed)  # 设置NumPy的随机种子torch.manual_seed(config.seed)  # 设置PyTorch的随机种子if cuda:torch.cuda.manual_seed(config.seed)  # 设置CUDA的随机种子

加载图和数据

    sadj, fadj = load_graph(args.labelrate, config)  # 加载图结构数据features, labels, idx_train, idx_test = load_data(config)  # 加载特征和标签数据

初始化模型和优化器

    model = SFGCN(nfeat=config.fdim, nhid1=config.nhid1, nhid2=config.nhid2, nclass=config.class_num, n=config.n, dropout=config.dropout)  # 实例化模型if cuda:model.cuda()  # 如果使用CUDA,则将模型转移到GPUfeatures = features.cuda()  # 转移数据到GPUsadj = sadj.cuda()fadj = fadj.cuda()labels = labels.cuda()idx_train = idx_train.cuda()idx_test = idx_test.cuda()optimizer = optim.Adam(model.parameters(), lr=config.lr, weight_decay=config.weight_decay)  # 初始化优化器

定义训练和测试函数

def train(model, epochs):model.train()  # 设置模型为训练模式optimizer.zero_grad()  # 清空之前的梯度output, att, emb1, com1, com2, emb2, emb = model(features, sadj, fadj)  # 前向传播loss_class = F.nll_loss(output[idx_train], labels[idx_train])  # 计算分类损失loss_dep = (loss_dependence(emb1, com1, config.n) + loss_dependence(emb2, com2, config.n))/2  # 计算依赖损失loss_com = common_loss(com1, com2)  # 计算通用损失loss = loss_class + config.beta * loss_dep + config.theta * loss_com  # 总损失acc = accuracy(output[idx_train], labels[idx_train])  # 计算训练准确率loss.backward()  # 反向传播optimizer.step()  # 更新权重acc_test, macro_f1, emb_test = main_test(model)  # 测试模型print('epoch:{}'.format(epochs),'loss_train: {:.4f}'.format(loss.item()),'acc_train: {:.4f}'.format(acc.item()),'acc_test: {:.4f}'.format(acc_test.item()),'f1_test:{:.4f}'.format(macro_f1.item()))  # 打印训练信息return loss.item(), acc_test.item(), macro_f1.item(), emb_test  # 返回训练损失和测试性能def main_test(model):model.eval()  # 设置模型为评估模式output, att, emb1, com1, com2, emb2, emb = model(features, sadj, fadj)  # 前向传播acc_test = accuracy(output[idx_test], labels[idx_test])  # 计算测试准确率label_max = []for idx in idx_test:label_max.append(torch.argmax(output[idx]).item())  # 预测标签labelcpu = labels[idx_test].data.cpu()macro_f1 = f1_score(labelcpu, label_max, average='macro')  # 计算F1分数return acc_test, macro_f1, emb  # 返回测试性能

模型训练和结果输出

    acc_max = 0f1_max = 0epoch_max = 0for epoch in range(config.epochs):loss, acc_test, macro_f1, emb = train(model, epoch)  # 训练模型if acc_test >= acc_max:acc_max = acc_test  # 更新最高准确率f1_max = macro_f1  # 更新最高F1分数epoch_max = epoch  # 更新最佳轮数print('epoch:{}'.format(epoch_max),'acc_max: {:.4f}'.format(acc_max),'f1_max: {:.4f}'.format(f1_max))  # 打印最佳训练结果

总结

讲到这里我们的AMGCN论文讲解接近尾声了,大家可以按照自己的需求构建自己希望的模型。其实本人理解这个作者的工作主要体现在其集成架构的理解上,做了大量的探索。我还采用cora数据集,验证了一下AMGCN的效果,效果表明人家论文中没用是对的,没啥效果。当然其在论文中给出的其他数据集下优越性都是可见的。欢迎各位和我共同学习我的实验部分。

对了对了,对这些内容感兴趣的朋友们,通过点赞、收藏和关注来表达你们的支持是对我的极大鼓励,如果你感觉还不错的话也可以打赏一杯咖啡钱,非常感谢大家!有任何问题或建议,欢迎随时通过私信与我交流。期待你们的积极参与和反馈。

下一小节将在pytorch中复现AMGCN模型在Cora数据集下的实验结果,👏欢迎大家观看哦
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

「Mac畅玩鸿蒙与硬件25」UI互动应用篇2 - 计时器应用实现

本篇将带领你实现一个实用的计时器应用&#xff0c;用户可以启动、暂停或重置计时器。该项目将涉及时间控制、状态管理以及按钮交互&#xff0c;是掌握鸿蒙应用开发的重要步骤。 关键词 UI互动应用时间控制状态管理用户交互 一、功能说明 在这个计时器应用中&#xff0c;用户…

条件logistic回归原理及案例分析

前面介绍的二元、多分类、有序Logistic回归都属于非条件Logistic回归&#xff0c;每个个案均是相互独立关系。在实际研究中&#xff0c;还有另外一种情况&#xff0c;即个案间存在配对关系&#xff0c;比如医学研究中配对设计的病例对照研究&#xff0c;此时违反了个案相互独立…

LeetCode:1.两数之和——Java 暴力解法哈希表

目录 题目如下&#xff1a; ​编辑 方法一&#xff1a;暴力解法 方法二&#xff1a;哈希表解法 题目如下&#xff1a; 1. 两数之和https://leetcode.cn/problems/two-sum/ 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 tar…

微信商家转账到零钱新玩法,却是个不好接受的消息

大家好&#xff0c;我是小悟。 深耕微信生态的小伙伴都知道&#xff0c;微信这个转账的功能&#xff0c;从一开始的“企业付款到零钱”出了有几个版本了吧。不过不管怎么变&#xff0c;基本都是通过openid就可以直接转账给指定用户。 为提高商户服务效率和体验&#xff0c;防…

C语言使用stream完成协议封送

开发过程中&#xff0c;对于自定义协议的打包&#xff0c;可以借助stream完成。 stream.h #pragma once#include <stdio.h> #include <string.h>typedef struct stream {char d[256];size_t size;size_t len;size_t pos; } stream, *pstream;void stem_init(pstr…

Window 安装ack 搜索软件 及使用

1. 先安装 PowerShell 命令行工具 2. 通过该工具安装命令行包管理器工具 Chocolatey 命令&#xff1a; Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol -bor …

基于SSM的社区物业管理系统+LW参考示例

1.项目介绍 系统角色&#xff1a;管理员、业主&#xff08;普通用户&#xff09;功能模块&#xff1a;管理员&#xff08;用户管理、二手置换管理、报修管理、缴费管理、公告管理&#xff09;、普通用户&#xff08;登录注册、二手置换、生活缴费、信息采集、报事报修&#xf…

ubuntu中安装mysql

一、注意版本问题 ubuntu常用的版本是16.4&#xff0c;18.4,对应的mysql文件也不同&#xff0c;注意不要下载错误。 二、注意更换apt的源 sudo cat /etc/apt/sources.list查看现在的数据源&#xff0c;我更换了阿里的数据源。更换语句如下&#xff1a; sed -i s/http:\/\/…

2024数据库国测揭晓:安全与可靠的新标准,你了解多少?

2024年数据库国测的结果&#xff0c;于9月份的最后一天发布了。 对于数据库行业的从业者来说&#xff0c;国测是我们绕不过去的坎儿。那么什么是国测&#xff1f;为什么要通过国测&#xff0c;以及国测的要求有哪些&#xff1f; 这篇文章带大家一探究竟。 国测 自愿平等、客…

Ubuntu - 进入紧急模式,无法进入桌面

目录 一、问题 二、分析原因 三、解决 四、参考 一、问题 重新安装VMVare之后&#xff0c;将之前的虚拟机加载不进来 二、分析原因 查看系统错误日志 journalctl -xb | grep Failed mnt挂载找不到了 三、解决 查看系统错误日志 如果是磁盘错误&#xff0c;此时终端会有…

基于STM32的八位数码管显示Proteus仿真设计

基于STM32的八位数码管显示Proteus仿真设计 1.主要功能2.仿真设计3. 程序设计4. 设计报告5. 资料清单&下载链接 基于STM32的八位数码管显示Proteus仿真设计(仿真程序设计报告讲解视频&#xff09; 仿真图proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a;…

数据库管理-第257期 有好故事才能讲好故事(20241101)

数据库管理257期 2024-11-01 数据库管理-第257期 有好故事才能讲好故事&#xff08;20241101&#xff09;1 23c到23ai2 惊艳的APEX3 愿景到实现总结 数据库管理-第257期 有好故事才能讲好故事&#xff08;20241101&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文…

FreeRTOS 队列详解

目录 一、引言 二、FreeRTOS 队列的基本概念 1.定义与作用 2.队列的长度和数据大小 三、FreeRTOS 队列的特点 1.先进先出&#xff08;FIFO&#xff09;特性 2.值传递方式 3.多任务访问 4.阻塞机制 四、FreeRTOS 队列的操作方法 1.创建队列 2.写队列&#xff08;发送…

Java项目实战II基于Spring Boot的问卷调查系统的设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导 一、前言 在当今信息爆炸的时代&#xff0c;问卷调查…

基于JavaWeb的宿舍管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

EPSON机械手与第三方相机的校准功能设计By python

EPSON机械手与第三方相机的校准功能设计By python 使用Python来实现EPSON机械手与第三方相机的校准功能是一个复杂但可行的任务。这通常涉及以下几个步骤:硬件接口通信、图像处理、标定算法实现和控制逻辑编写。 1. 环境准备 首先,库 pip install numpy opencv-python pyse…

【电子通识】白皮书、应用手册、用户指南、快速入门指南一般的定义是什么?

一般大厂家的器件或模块,除了给数据表以外,还提供应用手册、技术说明、白皮书等各种文档资料。 如下图所示为ST25 NFC/RFID标签和读卡器的文件资料:其中就有技术说明、白皮书、应用手册等。 如下所示为TI INA228技术文档相关资料: 也有应用手册、用户指南、技术文章…

【真实对抗环境】MC-Net: Realistic Sample Generation for Black-Box Attacks

原文标题&#xff1a; MC-Net: Realistic Sample Generation for Black-Box Attacks 原文代码&#xff1a; https://github.com/jiaokailun/A-fast 发布年度&#xff1a; 2024 发布期刊&#xff1a; TIFS 目录 摘要背景创新点模型实验结论 摘要 One area of current research …

0-基于图的组合优化算法学习(NeurIPS 2017)(未完)

文章目录 Abstract1 Introduction2 图上的贪婪算法的通用表述Abstract 为NP-hard组合优化问题设计好的启发式或近似算法通常需要大量的专业知识和试错。我们能否自动化这个具有挑战性、乏味的过程,而不是学习算法呢?在许多实际应用中,通常是相同的优化问题一次又一次地被解…

ctfshow(316)--XSS漏洞--反射性XSS

Web316 进入界面&#xff1a; 审计 显示是关于反射性XSS的题目。 思路 首先想到利用XSS平台解题&#xff0c;看其他师傅的wp提示flag是在cookie中。 当前页面的cookie是flagyou%20are%20not%20admin%20no%20flag。 但是这里我使用XSS平台&#xff0c;显示的cookie还是这样…