news 2026/7/6 12:25:52

Cora/CiteSeer/PubMed 原始数据处理:从 .tgz 到 PyG Data 对象的 5 步转换指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cora/CiteSeer/PubMed 原始数据处理:从 .tgz 到 PyG Data 对象的 5 步转换指南

Cora/CiteSeer/PubMed 原始数据到 PyG Data 对象的完整转换指南

1. 引言:为什么需要手动处理原始数据?

在大多数图神经网络(GNN)教程中,我们通常会看到这样简单的数据加载代码:

from torch_geometric.datasets import Planetoid dataset = Planetoid(root='/tmp/Cora', name='Cora')

虽然PyG的Planetoid类提供了便捷的数据加载接口,但当我们面临以下场景时,理解原始数据处理流程就变得至关重要:

  • 自定义数据预处理:官方预处理可能不符合我们的需求
  • 调试与验证:当模型表现异常时,需要检查数据生成环节
  • 迁移到新数据集:处理类似结构的非标准数据集
  • 性能优化:控制数据加载的每个环节以提升效率

本文将带您从原始.tgz文件开始,逐步构建与PyG兼容的Data对象,涵盖文件下载、解析、邻接矩阵构建、特征/标签加载、掩码生成等完整流程。

2. 环境准备与数据下载

2.1 安装必要依赖

pip install torch torch-geometric numpy scipy requests tqdm

2.2 数据集下载链接

三个数据集的官方源文件可从LINQS网站获取:

数据集下载链接
Corahttps://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz
CiteSeerhttps://linqs-data.soe.ucsc.edu/public/lbc/citeseer.tgz
PubMedhttps://linqs-data.soe.ucsc.edu/public/Pubmed-Diabetes.tgz

2.3 自动化下载脚本

import os import requests import tarfile from tqdm import tqdm def download_dataset(url, save_path): """下载并解压数据集""" os.makedirs(save_path, exist_ok=True) filename = os.path.join(save_path, url.split('/')[-1]) # 下载文件 response = requests.get(url, stream=True) total_size = int(response.headers.get('content-length', 0)) with open(filename, 'wb') as f, tqdm( desc=filename, total=total_size, unit='iB', unit_scale=True, unit_divisor=1024, ) as bar: for data in response.iter_content(chunk_size=1024): size = f.write(data) bar.update(size) # 解压文件 with tarfile.open(filename, 'r:gz') as tar: tar.extractall(path=save_path) os.remove(filename) # 删除压缩包 # 示例:下载Cora数据集 download_dataset( "https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz", "./raw_data/cora" )

3. 文件结构解析

解压后的典型文件结构如下(以Cora为例):

cora/ ├── cora.cites ├── cora.content └── README

3.1 关键文件说明

  1. .content文件(如cora.content

    • 每行格式:<paper_id> <word_attributes>+ <class_label>
    • 示例:31336 0 0 ... 1 0 Neural_Networks
  2. .cites文件(如cora.cites

    • 每行格式:<cited_paper_id> <citing_paper_id>
    • 示例:35 1033表示论文1033引用了论文35

3.2 数据统计对比

指标CoraCiteSeerPubMed
节点数2,7083,32719,717
边数5,4294,73244,338
特征维度1,4333,703500
类别数763

4. 数据加载与预处理

4.1 加载.content文件

import numpy as np from scipy.sparse import csr_matrix def load_content(file_path): """加载.content文件并返回特征矩阵和标签""" with open(file_path, 'r') as f: lines = f.read().splitlines() # 解析每行数据 data = [line.split('\t') for line in lines] paper_ids = [item[0] for item in data] # 构建特征矩阵 features = np.array([list(map(int, item[1:-1])) for item in data], dtype=np.float32) # 处理标签 labels = [item[-1] for item in data] unique_labels = sorted(list(set(labels))) label_to_idx = {label: idx for idx, label in enumerate(unique_labels)} y = np.array([label_to_idx[label] for label in labels], dtype=np.int64) # 创建paper_id到索引的映射 paper_to_idx = {paper_id: idx for idx, paper_id in enumerate(paper_ids)} return features, y, paper_to_idx, unique_labels # 示例使用 features, y, paper_to_idx, class_names = load_content("./raw_data/cora/cora.content")

4.2 加载.cites文件构建邻接矩阵

def load_cites(file_path, paper_to_idx): """加载.cites文件并构建邻接矩阵""" with open(file_path, 'r') as f: lines = f.read().splitlines() edges = [] for line in lines: cited, citing = line.split('\t') if cited in paper_to_idx and citing in paper_to_idx: edges.append([paper_to_idx[citing], paper_to_idx[cited]]) # 注意方向 edge_index = np.array(edges, dtype=np.int64).T # 转换为无向图 edge_index = np.concatenate([edge_index, edge_index[[1,0]]], axis=1) return edge_index # 示例使用 edge_index = load_cites("./raw_data/cora/cora.cites", paper_to_idx)

5. 构建PyG Data对象

5.1 完整转换代码

import torch from torch_geometric.data import Data def create_pyg_data(features, edge_index, y, train_ratio=0.05, val_ratio=0.18): """创建PyG Data对象并生成分割掩码""" # 转换为torch tensor x = torch.tensor(features, dtype=torch.float32) edge_index = torch.tensor(edge_index, dtype=torch.long) y = torch.tensor(y, dtype=torch.long) # 创建随机分割 num_nodes = y.size(0) num_classes = y.max().item() + 1 # 确保每个类至少有少量训练样本 train_mask = torch.zeros(num_nodes, dtype=torch.bool) val_mask = torch.zeros(num_nodes, dtype=torch.bool) test_mask = torch.zeros(num_nodes, dtype=torch.bool) for c in range(num_classes): idx = (y == c).nonzero().view(-1) idx = idx[torch.randperm(idx.size(0))] train_size = max(1, int(train_ratio * idx.size(0))) val_size = int(val_ratio * idx.size(0)) train_mask[idx[:train_size]] = True val_mask[idx[train_size:train_size+val_size]] = True test_mask[idx[train_size+val_size:]] = True # 构建Data对象 data = Data( x=x, edge_index=edge_index, y=y, train_mask=train_mask, val_mask=val_mask, test_mask=test_mask ) return data # 完整流程示例 features, y, paper_to_idx, _ = load_content("./raw_data/cora/cora.content") edge_index = load_cites("./raw_data/cora/cora.cites", paper_to_idx) cora_data = create_pyg_data(features, edge_index, y)

5.2 与官方数据对比验证

from torch_geometric.datasets import Planetoid # 加载官方数据 official_data = Planetoid(root='/tmp/Cora', name='Cora')[0] # 对比关键属性 print("官方数据 vs 手动处理数据对比:") print(f"节点数: {official_data.num_nodes} == {cora_data.num_nodes}") print(f"边数: {official_data.num_edges} ≈ {cora_data.num_edges} (方向处理可能导致差异)") print(f"特征维度: {official_data.num_features} == {cora_data.num_features}") print(f"类别数: {official_data.num_classes} == {len(class_names)}")

6. 高级处理技巧

6.1 处理稀疏特征矩阵

对于大型数据集(如PubMed),特征矩阵可能非常稀疏,使用稀疏矩阵存储可节省内存:

from scipy.sparse import csr_matrix def load_content_sparse(file_path): """稀疏矩阵方式加载.content文件""" with open(file_path, 'r') as f: lines = f.read().splitlines() data = [line.split('\t') for line in lines] paper_ids = [item[0] for item in data] # 构建稀疏矩阵 rows, cols, values = [], [], [] for row_idx, item in enumerate(data): for col_idx, val in enumerate(item[1:-1]): if int(val) == 1: rows.append(row_idx) cols.append(col_idx) values.append(1.0) num_nodes = len(data) num_features = len(data[0]) - 2 # 减去paper_id和label features = csr_matrix((values, (rows, cols)), shape=(num_nodes, num_features)) # 处理标签 labels = [item[-1] for item in data] unique_labels = sorted(list(set(labels))) label_to_idx = {label: idx for idx, label in enumerate(unique_labels)} y = np.array([label_to_idx[label] for label in labels], dtype=np.int64) paper_to_idx = {paper_id: idx for idx, paper_id in enumerate(paper_ids)} return features, y, paper_to_idx, unique_labels

6.2 处理CiteSeer的特殊情况

CiteSeer数据集中存在一些孤立节点,需要特殊处理:

def process_citeseer(content_path, cites_path): """处理CiteSeer的特殊情况""" features, y, paper_to_idx, class_names = load_content(content_path) # 加载引用关系 with open(cites_path, 'r') as f: lines = f.read().splitlines() # 找出有连接的节点 connected_nodes = set() for line in lines: cited, citing = line.split('\t') if cited in paper_to_idx and citing in paper_to_idx: connected_nodes.add(paper_to_idx[cited]) connected_nodes.add(paper_to_idx[citing]) # 创建新索引 connected_nodes = sorted(connected_nodes) new_idx_map = {old_idx: new_idx for new_idx, old_idx in enumerate(connected_nodes)} # 过滤特征和标签 features = features[connected_nodes] y = y[connected_nodes] # 重建边索引 edges = [] for line in lines: cited, citing = line.split('\t') if cited in paper_to_idx and citing in paper_to_idx: edges.append([new_idx_map[paper_to_idx[citing]], new_idx_map[paper_to_idx[cited]]]) edge_index = np.array(edges, dtype=np.int64).T edge_index = np.concatenate([edge_index, edge_index[[1,0]]], axis=1) return features, edge_index, y, class_names

7. 完整项目结构建议

对于实际项目,建议采用如下目录结构:

graph_data_pipeline/ ├── data/ # 原始数据 │ ├── cora/ │ ├── citeseer/ │ └── pubmed/ ├── processed/ # 处理后的数据 ├── utils/ │ ├── __init__.py │ ├── data_loader.py # 数据加载工具 │ └── preprocess.py # 预处理工具 ├── configs/ # 配置文件 │ └── data_config.yaml ├── tests/ # 单元测试 ├── main.py # 主程序 └── requirements.txt

data_loader.py中可以实现一个统一的数据加载接口:

class GraphDataset: def __init__(self, name='cora', root='./data', force_reprocess=False): self.name = name.lower() self.raw_dir = os.path.join(root, self.name) self.processed_dir = os.path.join(root, 'processed') os.makedirs(self.processed_dir, exist_ok=True) self.processed_file = os.path.join( self.processed_dir, f'{self.name}_data.pt' ) if not os.path.exists(self.processed_file) or force_reprocess: self.process() else: self.data = torch.load(self.processed_file) def process(self): if self.name == 'cora': features, edge_index, y, _ = process_cora( os.path.join(self.raw_dir, 'cora.content'), os.path.join(self.raw_dir, 'cora.cites') ) elif self.name == 'citeseer': features, edge_index, y, _ = process_citeseer( os.path.join(self.raw_dir, 'citeseer.content'), os.path.join(self.raw_dir, 'citeseer.cites') ) elif self.name == 'pubmed': features, edge_index, y, _ = process_pubmed( os.path.join(self.raw_dir, 'Pubmed-Diabetes.NODE.paper.tab'), os.path.join(self.raw_dir, 'Pubmed-Diabetes.DIRECTED.cites.tab') ) else: raise ValueError(f'Unknown dataset: {self.name}') self.data = create_pyg_data(features, edge_index, y) torch.save(self.data, self.processed_file) def get(self): return self.data
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 12:24:57

PyTorch 2.0 实战:L1/L2正则化对比,MNIST分类准确率提升3%

PyTorch 2.0实战&#xff1a;L1/L2正则化在MNIST分类中的效果对比与3%准确率提升策略1. 理解正则化&#xff1a;从数学基础到深度学习实践在深度学习模型训练过程中&#xff0c;我们常常面临一个关键挑战&#xff1a;如何在保持模型对训练数据良好拟合的同时&#xff0c;确保它…

作者头像 李华
网站建设 2026/7/6 12:24:24

云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 01-18

云原生 Java 架构师的第一课 K8sDockerKubeSphereDevOps 01-18 一、参考资料 【云原生Java架构师的第一课K8sDockerKubeSphereDevOps】 https://www.bilibili.com/video/BV13Q4y1C7hS/?share_sourcecopy_web&vd_source855891859b2dc554eace9de3f28b4528 二、笔记总结 01、…

作者头像 李华
网站建设 2026/7/6 12:24:19

Windows 10下Gogs SSH服务端与客户端全链路免密配置实战

1. 为什么需要SSH免密配置&#xff1f;在Windows 10上搭建Gogs私有Git服务时&#xff0c;每次推送或拉取代码都需要输入账号密码&#xff0c;这显然不够优雅。SSH免密配置就是为了解决这个问题&#xff0c;它通过非对称加密技术实现身份验证&#xff0c;让你可以像使用GitHub一…

作者头像 李华
网站建设 2026/7/6 12:24:16

告别终端混乱:Royal TSX在Mac上构建高效SSH连接管理体系的实战指南

1. 为什么Mac用户需要Royal TSX管理SSH连接作为长期管理多台Linux服务器的运维人员&#xff0c;我深刻理解那种打开终端后面对几十个未命名标签页的崩溃感。Mac自带的终端和iTerm2虽然能完成基础连接&#xff0c;但当需要同时管理开发环境、测试集群和生产服务器时&#xff0c;…

作者头像 李华
网站建设 2026/7/6 12:23:01

LxRunOffline 进阶指南:WSL 多发行版管理与系统迁移实战

1. 为什么需要 LxRunOffline 管理 WSL&#xff1f; 每次换电脑或者重装系统&#xff0c;最头疼的就是重新配置开发环境。特别是用 WSL&#xff08;Windows Subsystem for Linux&#xff09;的开发者&#xff0c;那些精心调教的 bashrc、安装的软件包、配置的环境变量&#xff…

作者头像 李华
网站建设 2026/7/6 12:22:38

CentOS 8 U盘安装实战:从制作到引导的完整避坑指南

1. 准备工作&#xff1a;别让U盘成为你的第一个坑第一次用U盘装CentOS 8的时候&#xff0c;我踩过的坑比装成功的次数还多。最气人的是连启动盘都做不好&#xff0c;系统还没开始装就卡在第一步。后来才发现&#xff0c;这里面的门道比想象中多得多。先说U盘的选择&#xff0c;…

作者头像 李华