# 图机器学习

从图数据、标签与划分出发，理解四类图学习任务的模型结构、训练目标、评估协议，以及模型保存和推理流程。

## 任务与模型兼容关系

图机器学习通过 train、infer 和 visualize_model_architecture 工具使用。节点分类、链接预测与预训练读取会话的活动单图；图分类读取独立的多图数据集。这里的节点分类回答‘这个节点属于哪一类’，链接预测估计节点对的连接分数，图分类预测整张图的标签，预训练则生成后续任务可复用的节点嵌入向量。

| task / method | 模型与输入 | 计算目标与结果 |
| --- | --- | --- |
| node_classification | 必填 model：APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER；单图节点特征与标签 | 交叉熵；输出节点类别与 softmax 概率 |
| link_prediction | 必填 model：APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER；单图结构 | 正负边二元交叉熵；输出节点对分数与连接概率 |
| graph_classification | 必填 model：APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER；HIV / PCBA 多图数据集 | 忽略缺失标签的二元交叉熵；输出图级单任务或多任务预测 |
| pretrain / node2vec | 不要求 model；读取单图结构，在 CPU 上训练 | 随机游走正负上下文学习；保存节点嵌入向量 |
| pretrain / contrastive_learning、graph_contrastive_learning、mask_autoencoding、graph_mask_autoencoding | 必填 model：APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER；单图特征与结构 | 这些名称目前共享同一个 GNN 边重构训练分支 |

已注册的骨干网络名称为 APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER；SAGE 对应 GraphSAGE。pretrain 必须填写 method；其余三个监督任务及 GNN 预训练必须填写 model。GNN 预训练方法名称不意味着分别实现了图增广对比学习、特征掩码或掩码自动编码器，应以实际损失函数为准。

## 单图张量、特征与标签

设 N 为节点数、E 为存储边数、F 为输入特征维度、D 为输出嵌入维度。模型读取 GraphData 的节点顺序与 edge_index，不先转成文本再重建图。消息传递使用形状为 [2, E] 的边索引；语义为无向的图会生成双向边索引副本，供邻居聚合使用，不改写活动图。边权重和边属性当前没有传入当前 GNN 骨干网络。

| 输入 | 形状与处理 |
| --- | --- |
| node_feat | 转换为 float32。[N] 扩展为 [N, 1]；高于二维时按节点展平为 [N, F]。已有特征不会自动标准化。 |
| 缺失 node_feat | 生成 [入度, 出度, 总度, 常数 1]，得到 [N, 4]；每列除以该列最大值与 1 中的较大者。 |
| node_label：[N] | 标量类别值映射到连续类别索引；缺失标签不进入监督样本。训练需要足够的已标注节点和至少两个类别。 |
| node_label：[N, C] | 沿类别维度取 argmax；行和为零的节点视为未标注。这是单标签分类路径，不是多标签节点分类。 |
| data.feature_source = embedding | 使用 upstream_model_id 的embedding.npy，按 node_names.json 中的节点名对齐；新图中未匹配的节点填零向量。 |

data.feature_source 默认是 graph。改为 embedding 时，还必须给出同一会话作用域内的 upstream_model_id；上游模型必须实际保存了节点嵌入文件。这个选项复用的是输入特征，不会载入上游 GNN 权重继续优化，也不恢复优化器状态。节点名变化会影响对齐结果，因此复用前应检查名称一致性与缺失匹配数量。

## 节点分类：标签划分与训练

节点分类先生成 [N, F] 特征，再由 GNN 得到 [N, D] 嵌入，最后用线性分类头映射到 [N, C] 类别分数。每轮对整张图做一次训练前向传播，仅对训练节点的分数计算交叉熵并反向传播。优化完成后切换到评估模式，在同一张图上重新计算训练准确率与验证准确率，关闭 dropout 对评估的随机影响。

- data.target_nodes 可将监督范围限制到指定节点名；无法识别或没有标签的节点不会成为有效监督样本。
- 若图有 train_mask，训练集采用该掩码并与已标注节点相交。验证集优先采用 val_mask；没有 val_mask 时会使用 test_mask；两者都没有时使用其余已标注节点。
- 若图没有 train_mask，则用 seed 打乱已标注节点，按 train / (train + val) 划分。默认比例为 0.8 / 0.2；兼容字段 train_config.split.test 不参与此流程。
- 随机划分仅用于该次训练，不会回写成图的 train_mask 或 val_mask。后续 infer 的 query.split 读取原图掩码，不能用它索引一份未持久化的随机划分。

模型选择指标是 val_acc。只有本轮验证准确率严格大于 best_val_acc + early_stop_min_delta 才更新最佳训练检查点；相同准确率不会因训练损失更低而替换检查点。训练结束后恢复最佳骨干网络与分类头，返回 train_loss、train_acc、val_acc、best_epoch、trained_epochs、stopped_early 等指标。若 test_mask 被用作验证集，这次运行没有独立的测试评估，应据实报告该评估协议。

## 链接预测：采样、解码与指标

链接预测从图的逻辑边生成正样本，或读取 data.target_edges 中的节点名二元组；训练至少需要两条正边。正边经 seed 打乱后划分为训练与验证部分。验证负边从非自环、图中不存在的节点对中采样，固定使用 seed + 11 并去重；无向图会考虑反向边等价性。采样有尝试次数上限，因此稠密图可能得到少于请求数量的负样本。

骨干网络输出节点嵌入 z，节点对的原始分数为 z[u] 与 z[v] 的内积，sigmoid 后得到 prob。每轮训练使用 PyG negative_sampling 生成负样本，损失为正边二元交叉熵与负边二元交叉熵之和。该内积解码器是对称的，即使原图是有向图，u→v 与 v→u 也得到相同分数；模型没有独立的关系类型或方向解码头。

每轮在固定验证正负样本上计算 val_auc，并搜索分类阈值以提高验证准确率。分数种类较多时，阈值候选压缩为一组分位点。最佳检查点主要按 val_auc 选择，在 AUC 差值处于 min_delta 范围时再比较 val_acc；选中的 threshold 随模型保存。这里的 AUC 使用本模块的秩统计实现，与处理并列分数的其他评估库可能存在差异。

这一训练流程只划分用于计算损失和指标的正边集合，GNN 的消息传递仍读取活动图完整的邻接关系，包括验证正边。因此这些指标不能直接当作‘从训练邻接中完全移除验证边’的链接预测基准结果。若目标是严格的未知边泛化实验，需要另行实现训练邻接隔离与一致的负样本协议。

## 预训练与嵌入向量复用

Node2Vec 根据图结构采样随机游走，通过正负上下文损失学习节点向量。当前使用稀疏参数与 SparseAdam，计算固定在 CPU 上；指定 GPU 不会把这个分支变为 GPU 随机游走训练。walk_length、context_size、walks_per_node、num_negative_samples、p、q 控制游走与采样，批大小读取 algo_config.batch_size，默认 128，而不是图分类使用的 train_config.batch_size。

Node2Vec 默认 walk_length=20、context_size=10、walks_per_node=10、num_negative_samples=1、p=q=1；训练器在未传 embedding_dim 时使用 128。为避免不同任务默认值带来误解，示例显式填写嵌入维度。该分支运行完整 epochs，忽略 weight_decay 和早停选项；结束时保存最后一轮的embedding.npy，不保存可继续训练的 Node2Vec 训练检查点。

GNN 预训练读取图特征和完整边索引，用节点嵌入内积重构正边，并对负采样边计算二元交叉熵。已命名的 contrastive_learning、graph_contrastive_learning、mask_autoencoding、graph_mask_autoencoding 都执行这一流程；没有各自独立的视图增广、掩码率或重构特征损失。它使用 Adam，保存最终骨干网络与节点嵌入，不执行验证集选择或早停。

预训练模型的 infer 是已保存嵌入向量的查询接口：按 query.nodes 返回训练时已有节点的向量，或者按 infer_config.limit 返回前若干项，默认 200。即使 GNN 预训练保存了骨干网络，当前推理路径也不会对新图重新执行 GNN 前向传播。要为更新后的图生成新的向量，需要重新运行预训练；要将这些向量用于节点分类或链接预测，可选择 data.feature_source=embedding并填写上游 model_id。

## 分子图分类的数据结构

图分类的数据单元是一张图，HIV 是 1 个二分类任务，PCBA 是 128 个二分类任务。load_graph_classification_dataset 读取会话上传目录中的 .pt 文件，dataset_profile 可设为 auto、hiv 或 pcba；数据集经校验后绑定到会话，并返回 dataset_id。它独立于活动单图：load_graph_classification_graph 可以抽取一张样本用于查看，但不会把多图训练转变为节点分类。

| 字段 | 必需的数据结构 |
| --- | --- |
| graphs | 非空 PyG Data 列表；每张图包含 x、xe、edge_index、y 张量，节点数必须大于零。 |
| x | 一维整型 [N]，存放节点类别编号；编号非负且在节点词表范围内。它不是节点的全局身份编号，也不是二维连续属性矩阵。 |
| xe / edge_index | xe 为一维整型 [E] 边类别编号；edge_index 为 [2, E] 整型索引且端点不越界。边类别必须由边词表覆盖。 |
| y | 每图 [T] 或 [1, T]，归一为 float32 [1, T]；允许 0、1、NaN，拒绝无穷值和其他标签值。全部图的 T 一致。 |
| texts | 至少包含三个字符串列表，依次表示节点、边和标签文本；用于词表和描述，不会自动生成语言模型嵌入。 |
| split | split[0] 包含 train、valid（或 val）、test 图索引列表；索引不重复、不重叠并覆盖全部图。split[1] 是 prompt/few-shot 元数据字典，只保留摘要，不改变训练采样。 |

图分类训练使用数据文件的固定划分，不能提交 train_config.split。它不接受 graph_version、method、upstream_model_id、data.target_nodes、data.target_edges 或嵌入向量特征来源。可显式传 data.dataset_id，也可使用当前绑定的数据集；其他任务不能使用这个字段。加载器计算完整文件 SHA-256 指纹，推理会据此验证数据身份，文件名相同并不代表数据相同。

## 模型结构、训练循环与评估

已注册骨干网络包括多层图卷积、注意力聚合和 APPNP 传播结构。常用默认值为 hidden_dim=64、num_layers=2、dropout=0.2；GNN 的 embedding_dim 省略时跟随 hidden_dim，因此在 hidden_dim 未改动时为 64。GAT、GATV2 与 TRANSFORMER 使用 gat_heads=2、concat=False。APPNP 先做多层线性预测，再执行个性化传播，默认 appnp_k=10、appnp_alpha=0.1。use_residual_connection 与 use_normalization 默认关闭；归一化支持 rms_norm、layer_norm、batch_norm，norm_pos 为 pre 或 post。

图分类先对节点类别做可训练的 nn.Embedding 编码，再经过 GNN、global_mean_pool 和线性图分类头。一个批次将多张图的节点与边拼接，用 batch 向量记录节点所属图：节点嵌入为 [ΣN, D]，池化结果为 [B, D]，最终 logits 为 [B, T]。train_config.batch_size 默认 64，num_workers 默认 0；训练批次打乱，验证与测试按固定顺序读取，不丢弃最后一个不足批大小的批次。

图分类采用带缺失标签掩码的 BCEWithLogitsLoss：仅在标签非 NaN 的位置取平均，一个批次全部缺失时跳过优化。训练集整轮没有有效标签会失败。HIV 用 ROC-AUC 选最佳模型；PCBA 对可评估任务分别计算 AP，再对这些任务取宏平均，同时记录有效与跳过的任务数。只有单一类别或全部缺失的任务无法贡献该指标，不能把 PCBA 指标表述为无条件平均全部 128 项。

三个监督任务使用 Adam。early_stop_patience=0 关闭早停，正整数表示连续多少轮没有改进后停止；early_stop_min_delta 默认 0，两个参数不能为负。图分类每轮只评估验证集，验证指标严格超过最佳值加 min_delta 才更新检查点；最终恢复最佳权重，再对测试集评估一次。预训练两个分支均没有这套验证选择和早停逻辑，不能把统一请求字段的存在理解为所有任务都执行它。

| 设置 | 行为与适用范围 |
| --- | --- |
| epochs / lr / weight_decay | 默认 200 / 0.05 / 0.0；示例显式设置学习率，数值不是调参结论。Node2Vec 使用 SparseAdam，忽略 weight_decay。 |
| seed | 默认 42，设置 Python、NumPy、PyTorch 与 CUDA 随机种子；多图 DataLoader 也使用种子与工作进程初始化。 |
| algo_config.device | cpu / cuda / auto；请求不可用的 CUDA 会失败。auto 无 GPU 时使用 CPU，多 GPU 时按空闲显存及可用利用率信息选择。Node2Vec 实际固定 CPU。 |
| 指标与进度 | 训练逐轮报告损失与对应评估指标；监督任务报告 best_epoch、trained_epochs、stopped_early。检查这些字段以区分计划轮数、实际轮数和采用的权重。 |

## 工具调用：提交训练与读取结果

以下 JSON 都是工具参数对象，在已验证身份并绑定会话的 MCP / 智能体工具环境中使用。参数示例展示数据结构，不包含测得的性能或最佳超参数。单图训练前先加载具备相应标签或结构的数据；分子图分类则先上传数据文件并执行数据集加载工具。示例中的文件名、dataset_id、model_id 与节点名应替换为实际返回值和真实数据名称。

节点分类：调用 train，使用活动图特征，按验证准确率选择模型。对于没有 train_mask 的图，split 控制本次随机划分。

```json
{
  "task": "node_classification",
  "model": "GCN",
  "data": {
    "feature_source": "graph"
  },
  "train_config": {
    "split": {
      "train": 0.8,
      "val": 0.2
    },
    "epochs": 100,
    "lr": 0.001,
    "weight_decay": 0.0005,
    "seed": 42,
    "early_stop_patience": 10,
    "early_stop_min_delta": 0.0001
  },
  "algo_config": {
    "hidden_dim": 64,
    "embedding_dim": 64,
    "num_layers": 2,
    "dropout": 0.2,
    "device": "auto"
  }
}
```

链接预测：调用 train，使用图中的逻辑边作为正样本。需要限定监督正边时可提供 data.target_edges，其元素是两个节点名组成的数组。

```json
{
  "task": "link_prediction",
  "model": "SAGE",
  "data": {
    "feature_source": "graph"
  },
  "train_config": {
    "epochs": 100,
    "lr": 0.001,
    "seed": 42,
    "early_stop_patience": 10
  },
  "algo_config": {
    "hidden_dim": 64,
    "embedding_dim": 64,
    "num_layers": 2,
    "device": "auto"
  }
}
```

Node2Vec：调用 train，显式给出嵌入维度、随机游走配置和该分支的批大小。

```json
{
  "task": "pretrain",
  "method": "node2vec",
  "train_config": {
    "epochs": 30,
    "lr": 0.01,
    "seed": 42
  },
  "algo_config": {
    "embedding_dim": 64,
    "walk_length": 20,
    "context_size": 10,
    "walks_per_node": 10,
    "num_negative_samples": 1,
    "batch_size": 128,
    "p": 1.0,
    "q": 1.0,
    "device": "cpu"
  }
}
```

分子图分类：先调用 load_graph_classification_dataset；加载完成后把返回的 dataset_id 填入第二个对象，并用它调用 train。

```json
{
  "filename": "hiv.pt",
  "dataset_profile": "hiv"
}
```

```json
{
  "task": "graph_classification",
  "model": "GCN",
  "data": {
    "dataset_id": "DATASET_ID_FROM_LOAD"
  },
  "train_config": {
    "epochs": 100,
    "lr": 0.001,
    "weight_decay": 0.0,
    "seed": 42,
    "early_stop_patience": 10,
    "early_stop_min_delta": 0.0001,
    "batch_size": 64,
    "num_workers": 0
  },
  "algo_config": {
    "node_embedding_dim": 64,
    "hidden_dim": 64,
    "embedding_dim": 64,
    "num_layers": 2,
    "dropout": 0.2,
    "device": "auto"
  }
}
```

直接使用 MCP 时，长任务可能先返回 job_id。用 get_job_status 查询作业；首次传 after_seq=0，后续将返回的 progress_cursor 作为 after_seq 增量读取进度；progress_has_more=true 时继续翻页。完成状态为 job.status=done，训练结果位于 job.result；确认结果 success=true 后读取 model_id，再提交 infer。智能体的 GraphMCPTools 包装会自动等待并解析这些作业，通常直接交付最终结果。

```json
{
  "job_id": "JOB_ID_FROM_TRAIN",
  "after_seq": 0,
  "progress_limit": 100
}
```

也可用 get_job_result(job_id=...) 读取最终输出。查询成功不等于训练完成：先确认返回 status=done，再检查内层训练结果的 success 与 model_id。大图算法作业的结果是摘要、预览和结果文件引用，不应当作图学习 model_id 使用。

## 推理查询与返回结构

infer 根据模型元数据选择任务分支。节点分类与链接预测重新构造骨干网络、加载训练检查点，在当前图上执行前向传播；图分类重新加载模型对应的数据集并检查身份；预训练返回保存的节点向量。返回的 model_id、task 与图版本或数据集标识可用于确认结果来自哪个模型和输入。

节点分类按节点名查询：predictions 包含 pred_class_idx、pred_label 等类别信息，标签可用时还返回真实标签及正确性；开启 return_prob 可取各类别概率。result_limit 与 result_offset 仅分页明细，评估基于请求的完整节点集合。空 query 返回划分评估摘要和抽样预测；query.split 仅能选择图中实际存在的掩码。

```json
{
  "model_id": "NODE_MODEL_ID_FROM_TRAIN",
  "query": {
    "nodes": [
      "node_a",
      "node_b"
    ]
  },
  "infer_config": {
    "return_prob": true,
    "result_limit": 20,
    "result_offset": 0
  }
}
```

链接预测可指定 query.edges，也可给出 source_node 与 candidate_nodes 做候选排序。每条结果包含 score、prob、pred 与 exists_in_graph；判定使用模型保存的验证阈值。候选列表可以包含已有边，调用方可根据 exists_in_graph 筛选。空 query 会搜索非边候选，虽然 max_candidate_pairs 限制保留和评分的候选量，生成阶段仍遍历节点对，大图上优先提供明确候选范围。

```json
{
  "model_id": "LINK_MODEL_ID_FROM_TRAIN",
  "query": {
    "source_node": "node_a",
    "candidate_nodes": [
      "node_b",
      "node_c"
    ],
    "topk": 2
  }
}
```

图分类 query.split 可选 train、val、test，空 query 默认 test；也可用 graph_indices 指定图索引，二者不能同时出现。HIV 明细含 probability、predicted_label 和 true_label；PCBA 默认返回概率最高的 5 个任务，可用 top_k_tasks 调整，return_all_task_probs=true 返回完整任务概率。evaluation 对完整请求集合计算，count 为请求图数，returned_count 为本页数量，truncated 表示仍有明细未显示。

```json
{
  "model_id": "GRAPH_MODEL_ID_FROM_TRAIN",
  "query": {
    "split": "test"
  },
  "infer_config": {
    "batch_size": 128,
    "device": "auto",
    "result_limit": 20,
    "result_offset": 0,
    "threshold": 0.5
  }
}
```

图分类推理接受 infer_config.device；节点分类与链接预测的推理路径当前自动选择设备，不能假定相同字段对它们具有相同作用。对于预训练模型，下面的调用只读取已有节点向量，返回 embeddings 数组，每项包含 node 与 embedding（数值向量）。

```json
{
  "model_id": "PRETRAIN_MODEL_ID_FROM_TRAIN",
  "query": {
    "nodes": [
      "node_a",
      "node_b"
    ]
  },
  "infer_config": {
    "limit": 200
  }
}
```

## 模型文件、版本绑定与复现

训练产物存放在 Graph Engine 的模型目录中，默认根目录为 artifacts/graph_ml，可通过 GRAPH_ML_ARTIFACT_ROOT 配置。模型目录按运行时的用户与会话作用域隔离，model_id 指向具体一次训练产物。图机器学习模块按需初始化；受控工作进程执行训练时先写入暂存目录，Graph Engine 验证作业仍有效后发布模型文件。收到 job_id 本身不等于模型已经可用。

| 任务 | 模型目录中的主要文件 |
| --- | --- |
| 节点分类 | checkpoint.pt：backbone 与 head；meta.json：模型结构、特征来源、类别映射、graph_version；metrics.json 与 node_names.json。 |
| 链接预测 | checkpoint.pt：backbone；meta.json 另记录 threshold；metrics.json 与 node_names.json。 |
| Node2Vec 预训练 | embedding.npy、node_names.json、meta.json、metrics.json；没有 checkpoint.pt。 |
| GNN 预训练 | checkpoint.pt：backbone；另有embedding.npy、node_names.json、meta.json、metrics.json。 |
| 图分类 | checkpoint.pt：model 权重；meta.json：数据集指纹、词表大小、任务数、模型配置与划分大小；metrics.json、per_task_metrics.json。 |

单图 train 的 graph_version 是对当前活动版本的校验，并不会自动加载历史图。需要旧版本时先调用 load_graph_version，再训练。节点分类与链接预测推理默认要求当前图版本等于训练版本；infer_config.allow_version_mismatch=true 可跳过这项版本限制，但不会解决输入维度、标签语义或上游嵌入不兼容。预训练向量查询校验的是已保存模型版本，不依赖当前活动图；图分类则绑定 dataset_id 与完整数据集指纹。

复现实验时应另行保存完整 train 请求、源数据文件及指纹、图版本、上游 model_id、依赖版本和实际设备。现有元数据记录模型结构及结果，但没有完整保存所有训练超参数、随机数状态和优化器状态，也没有公开的断点续训接口。seed 控制主要随机源，却没有开启强制确定性算法；GPU 内核和软件环境变化仍可能造成结果差异，因此不能仅凭相同 seed 保证逐位一致。

visualize_model_architecture 接收 model_id，读取保存的模型配置用于检查结构。结构图描述网络层与计算路径，不代替任务指标。比较实验时同时查看最佳轮次、实际训练轮数、数据划分协议、验证与测试样本是否独立，以及 PCBA 有效任务数量。

## 能力边界与实验解释

- 图分类使用节点类别嵌入、已注册骨干网络（APPNP / GCN / GAT / GATV2 / GEN / GIN / SAGE / TRANSFORMER）、固定全局均值池化和图级线性头。xe 已读取、校验和保留，但不进入前向传播；模型元数据记录 uses_edge_features=false。不能据此宣称模型利用了分子键类型。
- GIN 已注册，GINE 与可切换图池化尚未提供；HIV / PCBA 加载路径也不等价于支持任意 OGB 数据集。多图任务要求约定的 .pt 数据结构与固定划分，不能在 train 中任意指定新指标或重划测试集。
- 单图监督任务和 GNN 预训练使用整图前向传播，没有邻居采样小批训练。train_config.batch_size 不会降低这些路径的整图特征和中间激活显存需求；它用于图分类，Node2Vec 则使用自己的批大小字段。
- 二维节点标签经 argmax 进入单标签分类；分子图分类的 [B, T] 独立二分类才具有多任务标签语义。不要把两者的数据形状相似理解为同一种训练目标。
- 链接预测验证边仍参与消息传递，内积解码不区分方向；GNN 预训练方法名当前共享边重构实现。这些实现边界需要与指标一起说明，才能正确比较模型与研究基线。
- 当前接口没有提供自动超参数搜索、类别加权损失、学习率调度器、优化器状态恢复或跨环境确定性保证。本页示例是可理解的调用配置，不包含尚未执行的性能测试结论。
