基因组学数据基础:三种核心数据格式
做组学模型训练,第一关就是跟数据格式打交道。
我个人习惯,先把数据分成三个大类:测序原始数据、表达定量数据、变异数据。每个类别都有自己的“脾气”,处理不好,GPU利用率上不去,甚至模型训练直接崩溃。
DNA/RNA测序原始数据:FASTA 与 FASTQ
先说 FASTA。它就是个序列存储格式,只有两行:一行以 > 开头,存放序列 ID 和注释;另一行就是碱基序列本身。
>chr1_12345_ATCG
ATCGATCGATCGATCGATCG
但实际工作中,大家更常用的是 FASTQ。你想想看,FASTA 只告诉你序列长什么样,却没告诉你每个碱基的测序质量。
FASTQ 每四条记录为一个单元:
@SEQ_ID
ATCGATCGATCGATCGATCG
+
IIIIIIIIIIIIIIIIIIII
- 第一行:
@开头,序列 ID - 第二行:碱基序列
- 第三行:
+分隔符 - 第四行:质量值,用 ASCII 字符表示 Phred 分数
Q = -10 * log10(p),其中 p 是碱基测错的概率。质量值字符的 ASCII 值减去 33 就是 Q 值。比如 I 的 ASCII 是 73 → Q=40 → 错误概率 0.0001。这个在过滤低质量 reads 时很关键。
基因表达矩阵:行是基因,列是样本
表达矩阵就直白多了。行是基因/转录本,列是样本/细胞,单元格是对应的表达量。
| 基因 | 样本1 | 样本2 | 样本3 |
|---|---|---|---|
| TP53 | 12.5 | 3.2 | 45.8 |
| EGFR | 8.1 | 28.4 | 0.9 |
| MYC | 55.2 | 67.3 | 22.1 |
这里有个坑:表达矩阵通常非常稀疏。尤其是单细胞数据,90% 以上的单元格是 0。如果你直接把它当稠密矩阵传给 GPU,显存会爆炸。
torch.sparse_coo_tensor 或 scipy.sparse.csr_matrix 先压缩,才跑起来。
常见的表达矩阵格式有:
- CSV/TSV:简单,但大文件读写慢
- HDF5:支持分层存储,适合超大矩阵
- AnnData:单细胞领域标准(
.h5ad) - MX:10x Genomics 官方的稀疏矩阵格式
变异数据:VCF 的奥秘
VCF(Variant Call Format)是记录突变信息的标准格式。其核心结构包括:
##fileformat=VCFv4.2
#CHROM POS ID REF ALT QUAL FILTER INFO
chr1 12345 . A G 999.0 PASS DP=50;AF=0.45
chr1 23456 rs123 T C 500.0 PASS DP=30;AF=0.12
关键字段解释:
- CHROM/POS:染色体与位置
- REF/ALT:参考碱基与突变碱基
- QUAL:质量值,越高越好
- GT(基因型):藏在 FORMAT 列里,如
0/1表示杂合
做 GPU 加速时,VCF 的痛点在这:One-hot 编码后的基因型矩阵非常大。一个人全基因组约 500 万个常见 SNP,每个 SNP 用 3 个 one-hot 位(纯合参考、杂合、纯合突变),就是 1500 万维。直接输入模型?GPU 显存根本扛不住。
我的做法:先用注释数据库(如 ClinVar、GWAS)筛选与疾病相关的 SNP 子集,把维度降到 1 万以内再训练。
数据清洗与预处理 Pipeline
原始数据不能直接用。下面是一套我常用的预处理流程:
- FASTQ 质控:用 FastQC 检查碱基质量分布,过滤掉低质量 reads
- 序列比对/拼接:将 reads 比对到参考基因组,得到 BAM 文件
- 定量/变异检测:用 featureCounts 或 STAR 做表达定量;用 GATK 做变异检测
- 标准化:表达量做 logCPM、FPKM 或 TPM 变换;变异数据做 PCA 降维或 LD 剪枝
- 张量化:转为 PyTorch/TensorFlow 的 tensor 格式
数据如何喂给 GPU?一个完整示例
假设我们已经把一个表达矩阵存成了 NumPy 数组 X(样本数 x 基因数),标签存成了 y。用 PyTorch 的 DataLoader:
import torch
from torch.utils.data import Dataset, DataLoader
class OmicsDataset(Dataset):
def __init__(self, X, y):
self.X = torch.FloatTensor(X)
self.y = torch.LongTensor(y)
def __len__(self):
return len(self.y)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
dataset = OmicsDataset(X_train, y_train)
dataloader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)
for batch_X, batch_y in dataloader:
batch_X = batch_X.cuda()
batch_y = batch_y.cuda()
# 前向传播...
num_workers=4 表示用 4 个子进程加载数据,这样 GPU 不会因为等数据而空闲。我在项目中遇到过,如果 num_workers 设太大反而会拖慢速度,因为进程切换开销过高。单 GPU 建议 2-4,多 GPU 可以试试 8-12。
三类数据与 GPU 加速的关联
我们用一张 SVG 图来梳理整个流程:
从 SVG 图可以看出,原始数据格式不同,预处理步骤也不同,但最终都要汇入 DataLoader,由 GPU 来加速模型训练。
这里有个关键点:数据读取速度一定要跟得上 GPU 计算速度。我碰到过一个案例,模型只用了 70% 的 GPU 利用率,排查后发现是单线程读 CSV 文件。换成 HDF5 加 DataLoader 的多进程加载后,利用率直接拉满到 98%。
小结:三种数据选型对比
| 数据类别 | 典型文件 | 数据体积 | GPU 处理难点 | 建议做法 |
|---|---|---|---|---|
| 测序原始数据 | .fastq, .fa | GB ~ TB 级 | I/O 瓶颈,解析慢 | 用二进制格式(.bam)减少读取量 |
| 表达矩阵 | .csv, .h5, .h5ad | MB ~ GB 级 | 稀疏矩阵占用大,维度高 | 使用稀疏存储 + PCA 降维 |
| 变异数据 | .vcf, .vcf.gz | MB ~ 百 GB 级 | One-hot 后维度爆炸 | 先特征选择,再编码 |
搞清楚这三种数据,后面讲自定义 Dataset 和 DataLoader 的优化技巧时,你就能理解为什么要那样写了。