Instructions to use RomanCohort/RNAQwen with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use RomanCohort/RNAQwen with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("RomanCohort/RNAQwen", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download scripts/46_fsq_code_transfer.py from RomanCohort/RNAQwen: direct link, hf CLI and curl.
- Browser
- Download file 8.99 kB
-
https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/46_fsq_code_transfer.py
- Command line
-
hf download hf://RomanCohort/RNAQwen/scripts/46_fsq_code_transfer.py
-
curl -L -o 46_fsq_code_transfer.py https://huggingface.co/RomanCohort/RNAQwen/resolve/main/scripts/46_fsq_code_transfer.py
8.99 kB
| """真正有判别力的那个测试:码的「含义」能不能跨序列迁移? | |
| 前两个指标都被否掉了: | |
| · within-code 方差 / 重建 err —— 循环。FSQ 是确定性映射、AE 照重构误差训的, | |
| within<1 是构造保证的,不是发现。 | |
| · 覆盖率 —— 没有判别力。词表远小于训练位置数时,哈希也会给出 90% 利用率和 | |
| 95% 覆盖;词表远大于时样本不够。44_/45_ 两次都栽在这上面。 | |
| 能区分的只有一个:**同一个码在没见过的序列里,是否指向同一种局部结构。** | |
| 靶子用局部 k-mer —— 它直接来自输入序列,不是 encoder 学出来的,所以不循环。 | |
| 判据: | |
| 在训练序列上学一张 code -> 该码最常见的局部 6-mer 的表, | |
| 然后在**没见过的序列**上考: | |
| 用码去预测该位置的 6-mer 的准确率 vs 把码随机打乱后的对照 | |
| 打乱对照保留码的边缘分布,抹掉"码<->位置"的对应关系。 | |
| 若准确率 >> 打乱对照,说明码承载了可跨序列迁移的局部结构 información,是词表。 | |
| 若 ≈ 对照,说明码到了新序列上就没有意义了 —— 接进 LLM 学不到东西。 | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT / "src")) | |
| import torch # noqa: E402 | |
| import torch.nn as nn # noqa: E402 | |
| import torch.nn.functional as F # noqa: E402 | |
| DEV = "cuda" if torch.cuda.is_available() else "cpu" | |
| LATENT, LEVELS = 12, 2 # vocab 4096 | |
| VOCAB = LEVELS ** LATENT | |
| K = 6 | |
| STEPS = 1500 | |
| DATA = ROOT / "data/processed/rnaqa_n6_merged.jsonl" | |
| N_SEQ, N_TEST = 256, 64 | |
| def fsq(z, levels): | |
| t = (torch.tanh(z) + 1) / 2 | |
| s = t * (levels - 1) | |
| k = torch.round(s) | |
| return (k / (levels - 1) * 2 - 1) + (s - s.detach()), k.long() | |
| class AE(nn.Module): | |
| def __init__(self, dim, latent, levels, hidden=256): | |
| super().__init__() | |
| self.enc = nn.Linear(dim, latent) | |
| self.levels = levels | |
| self.dec = nn.Sequential(nn.Linear(latent, hidden), nn.GELU(), | |
| nn.Linear(hidden, hidden), nn.GELU(), | |
| nn.Linear(hidden, dim)) | |
| def forward(self, x): | |
| q, k = fsq(self.enc(x), self.levels) | |
| c = torch.zeros(x.shape[0], dtype=torch.long, device=x.device) | |
| for d in range(k.shape[1]): | |
| c = c * self.levels + k[:, d] | |
| return self.dec(q), c | |
| def main() -> int: | |
| recs = [] | |
| with open(DATA, encoding="utf-8") as f: | |
| for line in f: | |
| if line.strip(): | |
| recs.append(json.loads(line)) | |
| recs = [r for r in recs if r.get("sequence")] | |
| recs.sort(key=lambda r: len(r["sequence"])) | |
| n_all = 256 | |
| seqs = [recs[int(i * (len(recs) - 1) / (n_all - 1))]["sequence"] for i in range(n_all)] | |
| d = torch.load(ROOT / "data/processed/_fsq_probe_x.pt", map_location="cpu", | |
| weights_only=False) | |
| x, sid = d["x"].to(DEV), d["sid"].to(DEV) | |
| # 逐条比对:encoder 每条序列给出 L+2 个位置([CLS] + 核苷酸 + [SEP])。 | |
| # 这与观测一致:115886 - 115374 = 512 = 2 x 256。 | |
| cnt = torch.bincount(sid).tolist() | |
| keep = [i for i in range(len(seqs)) if cnt[i] == len(seqs[i]) + 2] | |
| drop = len(seqs) - len(keep) | |
| if drop: | |
| print(f"[filter] 丢弃 {drop} 条长度不匹配的序列,保留 {len(keep)}") | |
| remap = {o: n for n, o in enumerate(keep)} | |
| keep_pos = torch.tensor([s in remap for s in sid.tolist()], device=DEV) | |
| x = x[keep_pos] | |
| sid = torch.tensor([remap[s] for s in sid[keep_pos].tolist()], device=DEV) | |
| seqs = [seqs[i] for i in keep] | |
| N_SEQ = len(seqs) | |
| N, D = x.shape | |
| g = torch.Generator().manual_seed(7) | |
| test_ids = set(torch.randperm(N_SEQ, generator=g)[:max(1, N_SEQ // 4)].tolist()) | |
| seq_tr = torch.tensor([s not in test_ids for s in sid.tolist()], device=DEV) | |
| torch.manual_seed(0) | |
| ae = AE(D, LATENT, LEVELS).to(DEV) | |
| opt = torch.optim.AdamW(ae.parameters(), lr=3e-3) | |
| idx = torch.nonzero(seq_tr).squeeze(1) | |
| for _ in range(STEPS): | |
| p = idx[torch.randint(0, idx.numel(), (512,), device=DEV)] | |
| F.mse_loss(ae(x[p])[0], x[p]).backward() | |
| opt.step(); opt.zero_grad() | |
| ae.eval() | |
| with torch.no_grad(): | |
| _, code = ae(x) | |
| # 构造每个位置的局部 6-mer 编号([CLS]/[SEP] 和末尾不足 K 的位置记为 -1) | |
| mer_of_pos = torch.full((N,), -1, dtype=torch.long) | |
| pos = 0 | |
| for s in seqs: | |
| b = s.encode() | |
| L = len(b) | |
| pos += 1 # [CLS] | |
| for j in range(L): | |
| if j + K <= L: | |
| v = 0 | |
| for ch in b[j:j + K]: | |
| v = v * 4 + ({65: 0, 67: 1, 71: 2, 84: 3, 85: 0}.get(ch, 0)) | |
| mer_of_pos[pos] = v | |
| pos += 1 | |
| pos += 1 # [SEP] | |
| # 位置必须与 encoder 的 mask 逐条对齐,否则 k-mer 靶子是错位的 | |
| assert pos == N, f"位置对齐失败: 构造 {pos} != encoder 位置 {N}" | |
| mer_of_pos = mer_of_pos.to(DEV) | |
| tr_pos = torch.nonzero(seq_tr & (mer_of_pos >= 0)).squeeze(1) | |
| te_pos = torch.nonzero(~seq_tr & (mer_of_pos >= 0)).squeeze(1) | |
| print(f"[data] 训练位置 {tr_pos.numel()} 测试位置 {te_pos.numel()} " | |
| f"(局部 {K}-mer,词表 {4**K})") | |
| tc, tm = code[tr_pos], mer_of_pos[tr_pos].to(DEV) | |
| ec, em = code[te_pos], mer_of_pos[te_pos].to(DEV) | |
| # code -> 该码在训练序列里最常见的 6-mer | |
| lookup = torch.full((VOCAB,), -1, dtype=torch.long, device=DEV) | |
| for c in torch.unique(tc): | |
| sel = tm[tc == c] | |
| counts = torch.bincount(sel, minlength=4 ** K) | |
| lookup[c] = int(counts.argmax()) | |
| hit = lookup[ec] >= 0 | |
| acc = float((lookup[ec][hit] == em[hit]).float().mean()) if hit.any() else 0.0 | |
| cov = float(hit.float().mean()) | |
| # 对照:打乱 code<->位置 的对应(保留 code 边缘分布) | |
| g2 = torch.Generator().manual_seed(99) | |
| accs = [] | |
| for _ in range(5): | |
| perm = torch.randperm(tc.numel(), generator=g2) | |
| tc2 = tc[perm] | |
| lk = torch.full((VOCAB,), -1, dtype=torch.long, device=DEV) | |
| for c in torch.unique(tc2): | |
| sel = tm[tc2 == c] | |
| lk[c] = int(torch.bincount(sel, minlength=4 ** K).argmax()) | |
| h = lk[ec] >= 0 | |
| accs.append(float((lk[ec][h] == em[h]).float().mean())) | |
| ctrl = sum(accs) / len(accs) | |
| # 第二个对照:全局最常见的 6-mer 常数预测 | |
| base = float(torch.bincount(tm, minlength=4 ** K).max()) / tm.numel() | |
| print() | |
| print("=" * 78) | |
| print("跨序列含义迁移:用码预测局部 6-mer") | |
| print("=" * 78) | |
| print(f" 测试位置中码在训练集见过的比例: {100*cov:.1f}%") | |
| print(f" 按码查表预测 6-mer 准确率 : {100*acc:.2f}%") | |
| print(f" 打乱 code<->位置 对照 : {100*ctrl:.2f}%") | |
| print(f" 常数预测(训练集最常见 6-mer): {100*base:.2f}%") | |
| print(f" 相对打乱对照的增益 : {100*(acc-ctrl):+.2f}pp") | |
| # 信息量:以比特计 | |
| import math | |
| lift = acc / max(ctrl, 1e-9) | |
| print(f" 提升倍数 : {lift:.2f}x") | |
| print() | |
| print("=" * 78) | |
| print("判定") | |
| print("=" * 78) | |
| if acc > ctrl * 1.5 and acc > base * 1.3: | |
| print(f" ✅ **码携带可跨序列迁移的局部结构信息**({100*acc:.1f}% vs 打乱 {100*ctrl:.1f}%," | |
| f"{lift:.1f}x)。") | |
| print(" 同一个码在没见过的序列里指向同一类 6-mer —— 这是词表,不是哈希。") | |
| print(" 接进 LLM 有可学的东西。") | |
| elif acc > ctrl * 1.15: | |
| print(f" ⚠️ **弱迁移**:{100*acc:.1f}% vs 打乱 {100*ctrl:.1f}%({lift:.2f}x)。") | |
| print(" 码有结构但信噪比低。可能要靠加复用压力(commitment / 熵正则)才能变干净。") | |
| else: | |
| print(f" ❌ **码到新序列上就没有含义了**({100*acc:.1f}% vs 打乱 {100*ctrl:.1f}%," | |
| f"{lift:.2f}x)。") | |
| print(" 码只是把每条序列切成一串各自不同的片段 —— 对 LLM 而言是新的乱码。") | |
| print(" 要救这条路线,必须换目标函数(加复用压力),而不是换个量化方式。") | |
| out = ROOT / "reports" / "fsq_code_transfer.json" | |
| out.write_text(json.dumps(dict(vocab=VOCAB, k=K, acc=acc, ctrl=ctrl, base=base, | |
| cov=cov, lift=lift), ensure_ascii=False, indent=2), | |
| encoding="utf-8") | |
| print(f"\n[save] {out}") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |