春江暮客

春江暮客的个人学习分享网站

训练前用 Python 检查蛋白质嵌入文件

2026-10-10 技术
训练前用 Python 检查蛋白质嵌入文件

蛋白质嵌入矩阵的行数正确,也可能配错标签。FASTA 排序、提取批次合并、失败序列过滤,都会改变记录顺序。分类器随后读到的数值看似正常,却可能属于另一条蛋白质序列。

这篇教程在训练前检查保存的矩阵:每个向量对应一个唯一的样本 ID,特征维数与提取配置一致,浮点数全部有限,并且 ID 与独立的标签顺序清单逐项相同。它承接 ESM-2 嵌入教程和蛋白质标签连接教程。需要基础 Python 和 NumPy 知识,演示不需要 GPU 或下载模型。

1. 定义嵌入文件要求

使用只包含两个命名数组的 NPZ 文件。这是本教程约定的简易交换格式,不是 ESM 的原生导出格式。

字段 格式要求 含义
sample_id 形状为 (N,) 的 Unicode 字符串数组 每一行代表的样本。
embedding 形状为 (N, D) 的浮点数组 每个样本一个池化后的特征向量。
ids.txt UTF-8 文本,每行一个 ID 按下游标签实际顺序排列的预期 ID。
--dim 正整数 从提取配置中取得的特征维数。

ID 必须非空、唯一,并且不含空白字符。脚本按原始文本精确比较:001 与 1 不同,大小写也不同。本教程要求每个样本只有一行;如果保留重复测量,需要给样本分配不同 ID,并另存它们与母序列的对应关系。

从最终标签表独立生成 ids.txt。如果直接把嵌入文件的 ID 复制成清单,顺序检查就失去了意义。嵌入文件里的 ID 应在提取时取自该向量对应的序列记录。

逐残基表示通常需要先池化,才能满足这里的矩阵要求。官方 ESM 提取脚本同时提供逐 token 表示和均值表示,计算均值时先选择残基位置。不要为了通过形状检查,把含残基轴的张量直接展平。随数据记录模型检查点、revision、表示层、残基掩码、池化方法和截断规则。

2. 下载示例并安装 NumPy

将 audit_embeddings.py和 make_demo.py下载到新的工作目录。下面固定的 NumPy 版本需要 Python 3.12 或更新版本。示例在 Python 3.14.7、NumPy 2.5.3 下验证。

python3 -m venv .venv
source .venv/bin/activate
python -m pip install "numpy==2.5.3"
python audit_embeddings.py --help

下面的生成器新建 demo-embeddings 目录;目录已经存在时会停止,避免覆盖之前的演示文件。向量全部由示例构造:三个样本,每个样本四个特征。它们不是 ESM 输出,也不是生物测量结果。

"""Create small synthetic fixtures without extracting real protein embeddings."""
from pathlib import Path

import numpy as np


def main():
    root = Path("demo-embeddings")
    root.mkdir()  # Refuse to overwrite a previous demo directory.
    ids = np.array(["protein_A", "protein_B", "protein_C"], dtype=np.str_)
    vectors = np.array([[0.1, 0.2, 0.3, 0.4],
                        [0.4, 0.3, 0.2, 0.1],
                        [-0.1, 0.0, 0.2, 0.5]], dtype=np.float32)
    (root / "ids.txt").write_text("\n".join(ids.tolist()) + "\n", encoding="utf-8")
    cases = {"good": (ids, vectors),
             "reordered": (ids[::-1], vectors[::-1]),
             "duplicate": (np.array(["protein_A", "protein_A", "protein_C"]), vectors)}
    bad = vectors.copy()
    bad[1, 2] = np.nan
    cases["nan"] = (ids, bad)
    for name, (sample_ids, embedding) in cases.items():
        np.savez(root / f"{name}.npz", sample_id=sample_ids, embedding=embedding,
                 allow_pickle=False)
    print("Created demo-embeddings with four synthetic archives and ids.txt")


if __name__ == "__main__":
    main()

numpy.savez按关键字参数名保存数组。明确使用 Unicode ID 和浮点向量,可以避免在这些文件中存入 object 数组。

3. 检查正常文件

python make_demo.py
python audit_embeddings.py demo-embeddings/good.npz demo-embeddings/ids.txt --dim 4

检查脚本的预期输出:

{"status": "ok", "samples": 3, "dimensions": 4, "dtype": "float32", "zero_rows": 0}

处理真实数据时,把 4 换成实际提取配置里的特征维数。不要从待检查文件本身推导预期维数。不同检查点或池化流程可能输出相同维数,因此维数相同不能证明表示方法一致。

下面是完整检查脚本,与下载文件相同:

"""Check a pooled protein embedding archive against an ordered ID manifest."""
import argparse
import json
from pathlib import Path
import sys
from zipfile import BadZipFile

import numpy as np


def check_ids(ids, source):
    if not ids:
        raise ValueError(f"{source}: no sample IDs")
    seen = set()
    for row, value in enumerate(ids):
        if not value or any(char.isspace() for char in value):
            raise ValueError(f"{source}: empty ID or whitespace at row {row}")
        if value in seen:
            raise ValueError(f"{source}: duplicate ID {value!r}")
        seen.add(value)


def audit(path, ids_path, expected_dim):
    if expected_dim < 1:
        raise ValueError("expected dimension must be positive")
    expected = Path(ids_path).read_text(encoding="utf-8-sig").splitlines()
    check_ids(expected, "manifest")
    archive = np.load(path, allow_pickle=False)
    if not isinstance(archive, np.lib.npyio.NpzFile):
        raise ValueError("expected an NPZ archive")
    with archive:
        if len(archive.files) != 2 or set(archive.files) != {"sample_id", "embedding"}:
            raise ValueError("archive must contain only sample_id and embedding")
        ids = archive["sample_id"]
        vectors = archive["embedding"]
    if ids.ndim != 1 or ids.dtype.kind != "U":
        raise ValueError("sample_id must be a one-dimensional Unicode array")
    names = ids.tolist()
    check_ids(names, "archive")
    if vectors.ndim != 2 or vectors.shape != (len(names), expected_dim):
        raise ValueError(f"expected embedding shape ({len(names)}, {expected_dim})")
    if vectors.dtype.kind != "f":
        raise ValueError("embedding must have a floating-point dtype")
    if names != expected:
        if set(names) == set(expected):
            raise ValueError("ID order differs; join by sample_id before export")
        raise ValueError("archive IDs differ from manifest IDs")
    zero_rows = 0
    # Arrays are loaded into RAM; chunking limits temporary boolean arrays.
    for start in range(0, len(names), 1024):
        block = vectors[start:start + 1024]
        finite = np.isfinite(block)
        if not finite.all():
            row, column = np.argwhere(~finite)[0]
            raise ValueError(f"non-finite value at row {start + int(row)}, column {int(column)}")
        zero_rows += int(np.all(block == 0, axis=1).sum())
    return {"status": "ok", "samples": len(names), "dimensions": expected_dim,
            "dtype": str(vectors.dtype), "zero_rows": zero_rows}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("archive", type=Path)
    parser.add_argument("ids", type=Path, help="one ID per line, in downstream label order")
    parser.add_argument("--dim", type=int, required=True, help="expected pooled feature count")
    args = parser.parse_args()
    try:
        result = audit(args.archive, args.ids, args.dim)
    except (OSError, ValueError, EOFError, BadZipFile) as error:
        print(f"ERROR: {error}", file=sys.stderr)
        return 1
    print(json.dumps(result, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

脚本调用 numpy.load时设置 allow_pickle=False,读取后关闭 NPZ 文件,拒绝 object 数组。如果旧导出文件需要 pickle 才能读取 ID,应从可信源重新导出为 Unicode ID 数组。

numpy.isfinite检查 NaN、正无穷和负无穷。全零行只计入报告,不自动拒绝。做余弦归一化前需要核查这些行,因为归一化要求向量非零。零向量是否属于提取错误,要结合记录的表示方法判断。

4. 复现三种失败

分别执行下面的命令,每条都应在标准错误输出中报告问题,并以状态码 1 退出:

python audit_embeddings.py demo-embeddings/reordered.npz demo-embeddings/ids.txt --dim 4
python audit_embeddings.py demo-embeddings/duplicate.npz demo-embeddings/ids.txt --dim 4
python audit_embeddings.py demo-embeddings/nan.npz demo-embeddings/ids.txt --dim 4
ERROR: ID order differs; join by sample_id before export
ERROR: archive: duplicate ID 'protein_A'
ERROR: non-finite value at row 1, column 2

顺序错误示例同时反转 ID 和向量,保留了蛋白质与向量之间的对应关系,却与下游标签顺序不同。仅检查 ID 集合相同会漏掉这个问题。报告中的数组位置均从零开始计数。

脚本只读取文件,不修改输入;所有检查通过后才输出成功报告。在自动流程中,先要求检查命令成功,再启动训练任务:

python audit_embeddings.py demo-embeddings/good.npz demo-embeddings/ids.txt --dim 4 && echo "Embedding checks passed"

5. 根据错误修复数据

问题 具体处理
ID 成员相同但顺序不同 按 sample_id 将嵌入行连接到标签表,用连接结果中的同一行排列导出 ID 和向量,再运行检查。
文件和清单的 ID 不同 核查提取失败记录、过滤规则和数据版本,先解决缺失或多出的样本,再连接。
ID 重复 找出重复提取记录;如果属于有意保留的重复样本,定义不同的样本 ID,评估时仍按母序列分组。
形状不符 检查模型预期维数和池化输出。逐残基张量或转置矩阵应在上游修正。
object 数组或非浮点向量 按预定的 Unicode ID 和浮点 dtype 重新导出。强制转换错误数据不能恢复正确嵌入。
数值非有限 追踪报告中的样本,检查分词、池化和数值精度,按事先记录的规则重新计算或排除。把 NaN 换成零可能掩盖原因。

不要只对 ID、标签或向量中的一项排序。应执行明确的连接,或对对应记录应用同一排列。标签连接教程介绍了如何检查键的成员关系并保留样本对应关系。

6. 保存来源信息并估算内存

这个检查器把两个数组读入内存,按每批 1,024 行检查数值,以限制临时布尔数组的大小,但没有流式读取 NPZ 矩阵。100,000 个样本、1,280 个特征的 float32 矩阵本身约占 488 MiB,还不包括 ID、Python 对象和临时数组。压缩文件大小不能代表加载后的内存需求。

更大的数据集可以考虑单独的 .npy 矩阵、内存映射,以及带版本的 ID 清单。那种布局需要对应的检查器;本示例只接受 NPZ。NumPy 在 numpy.load 文档中说明了内存映射的用法。

检查通过仍不能发现上游把向量挂到错误 ID、残基池化错误、混用模型 revision 或训练测试泄漏。保留序列校验和及提取元数据,保存数据集清单,并在比较嵌入与氨基酸组成基线时使用同一份按组划分结果。需要拟合的标准化、PCA 和预测器选择,仍应在相应的训练与验证分区内完成。

小结

拟合蛋白质预测器前,先检查样本对应关系、行顺序、维数、dtype 和有限数值。保留提取元数据及序列映射,让保存的数值可以追溯到来源。检查通过后,再使用符合生物学泛化问题的数据划分评估表示效果。

封面为概念插画,不是实验测得的蛋白质结构。

标签

1024 12306 ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor automation batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn chatgpt checkpoint cli cloudflare codex cofoldarena copy cpu监控 csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization deployment desktop-app devtools disk-space disown docker dovecot download electron esm esm2 esm3 esmc esmfold2 faceswap fasta fastmcp ffmpeg file-io flashppi flask folium frontend game generator git github-actions google google-research grep harness hls html http hugo indexnow javascript jev json just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp mirror mmcif model-evaluation mp3 mp4 mpnn multiomics mutation mysql nanobert nanobodies networkx nginx normalize numpy ollama omegatherm pandas password pdb pep-723 phaser pillow pip postfix preprocessing print protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security selenium seo sequence-identity sha256 sklearn solana somaticsignatures spl sqlite ssh standardize static-site subprocess sysadmin system-one tensorflow tkinter tron tronpy troubleshooting turtle typesafe-ai ubuntu usdt uv vhhbert vite webp wordcloud wordpress workflow yaml 后台 寓言 概率 经济 贸易 迅雷解析 钱包

友情链接

其它