Instructions to use dnagpt/laya-bio-models with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Laya
How to use dnagpt/laya-bio-models with Laya:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
File size: 22,275 Bytes
d35dd87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | #!/usr/bin/env python3
"""Prepare deterministic Laya train, selection-dev, calibration, and test views.
Standard library only. Input JSONL files are read-only. Only train+val labels
participate in conflict removal and stratification. Test labels are serialized
as canonical indices but never counted, compared, scored, or used for decisions.
DNA reverse-complement grouping is partition hygiene, not semantic relabeling.
"""
from __future__ import annotations
import argparse
from collections import Counter, defaultdict
from datetime import datetime, timezone
import hashlib
import itertools
import json
from pathlib import Path
import re
from audit_laya_tasks import TASKS
SOURCE_SPLITS = ("train", "val", "test")
FINAL_SPLITS = ("train", "selection_dev", "calibration", "test")
COMPLEMENT = str.maketrans("ACGTRYSWKMBDHVN-", "TGCAYRSWMKVHDBN-")
SCHEMA = ["id", "task", "modality", "context", "sequence", "choices", "label",
"source_split", "split", "sequence_sha256", "group_id"]
POLICY = [
"Normalize the final nonempty user-message line with strip and uppercase; preserve sequence orientation.",
"Validate all rows and stop on a schema/alphabet error rather than silently dropping malformed rows.",
"Primary identity is SHA-256 of the normalized exact sequence.",
"For DNA only, a partition group is SHA-256 of min(sequence, reverse_complement(sequence)); this does not imply label equivalence.",
"Using train+val only, exclude every exact sequence with conflicting labels and every DNA RC group with conflicting labels; never majority-vote or relabel.",
"Within each source split retain the first exact-sequence occurrence in source-line order; distinct RC sequences with consistent train+val labels can coexist in one group.",
"Retain train first, then exclude validation records whose exact/RC partition group is in retained train.",
"Split retained val at group level, stratified by canonical label, into approximately 50% selection_dev and 50% calibration using a fixed seed; each class appears in both.",
"Test is processed only after training and validation membership is fixed. Deduplicate test by exact sequence and exclude test group overlap with retained train/val; no test labels affect any exclusion or partition.",
"Test labels are converted to canonical indices only for an eventual locked evaluation; no test label histogram, conflict analysis, model evaluation, or performance access is performed here.",
"Raw files are never modified. Source provenance and historical clustering summaries are documented; no homology-independence claim is made.",
]
def digest(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def parse_record(raw: dict, task: dict, line: int) -> dict:
split = raw.get("split")
if split not in SOURCE_SPLITS:
raise ValueError(f"line {line}: unknown split {split!r}")
if raw.get("task_id") != task["task_id"]:
raise ValueError(f"line {line}: unexpected task_id")
if task["modality"] not in raw.get("modality", []):
raise ValueError(f"line {line}: wrong modality")
if not isinstance(raw.get("id"), str) or not raw["id"]:
raise ValueError(f"line {line}: missing id")
if raw.get("choices") != task["choices"]:
raise ValueError(f"line {line}: unexpected choices or candidate order")
if raw.get("answer_short") not in task["choices"]:
raise ValueError(f"line {line}: unknown canonical label")
users = [m.get("content") for m in raw.get("messages", [])
if isinstance(m, dict) and m.get("role") == "user"]
if len(users) != 1 or not isinstance(users[0], str):
raise ValueError(f"line {line}: expected one string user message")
lines = [part.strip() for part in users[0].splitlines() if part.strip()]
if len(lines) < 2:
raise ValueError(f"line {line}: cannot identify context and sequence")
sequence = lines[-1].upper()
if not sequence or not set(sequence) <= set(task["allowed"]):
raise ValueError(f"line {line}: invalid sequence alphabet")
context = re.split(r",?\s*The result will be one of the following\s*:",
"\n".join(lines[:-1]), maxsplit=1, flags=re.IGNORECASE)[0].strip(" ,")
if not context:
raise ValueError(f"line {line}: empty context")
if split != "test":
assistants = [m.get("content") for m in raw.get("messages", [])
if isinstance(m, dict) and m.get("role") == "assistant"]
if assistants != [raw["answer_short"]]:
raise ValueError(f"line {line}: training/validation assistant target mismatch")
seq_hash = digest(sequence)
group_sequence = min(sequence, sequence.translate(COMPLEMENT)[::-1]) if task["modality"] == "dna" else sequence
return {"id": raw["id"], "task": task["task_id"], "modality": task["modality"],
"context": context, "sequence": sequence, "choices": list(task["choices"]),
"label": task["choices"].index(raw["answer_short"]), "source_split": split,
"split": split, "sequence_sha256": seq_hash,
"group_id": f'{task["task_id"]}:{digest(group_sequence)}', "_line": line}
def label_counts(rows: list[dict]) -> dict[str, int]:
if any(row["source_split"] == "test" for row in rows):
raise ValueError("Test label aggregation is forbidden in split preparation")
return {str(k): v for k, v in sorted(Counter(row["label"] for row in rows).items())}
def group_membership(rows: list[dict], key: str) -> dict[str, set[str]]:
return {split: {row[key] for row in rows if row["source_split"] == split} for split in SOURCE_SPLITS}
def overlaps(rows: list[dict], key: str) -> dict[str, int]:
members = group_membership(rows, key)
return {f"{a}__{b}": len(members[a] & members[b]) for a, b in itertools.combinations(SOURCE_SPLITS, 2)}
def split_validation(rows: list[dict], task_id: str, n_labels: int, seed: int):
groups = defaultdict(list)
for row in rows:
groups[row["group_id"]].append(row)
by_label = defaultdict(list)
for gid, members in groups.items():
labels = {row["label"] for row in members}
if len(labels) != 1:
raise ValueError("Conflicting validation group survived cleaning")
by_label[next(iter(labels))].append((gid, members))
dev_ids, details = set(), {}
for label in range(n_labels):
items = sorted(by_label[label], key=lambda x: digest(f"{seed}:{task_id}:{label}:{x[0]}"))
if len(items) < 2:
raise ValueError(f"{task_id} class {label}: fewer than two validation groups")
total = sum(len(members) for _, members in items)
target = total // 2
# Exact subset-sum up to floor(total/2). Record each reachable sum once;
# deterministic hashed group order determines equally good solutions.
reachable, mask, previous = 1, (1 << (target + 1)) - 1, {}
for gid, members in items:
weight = len(members)
added = ((reachable << weight) & mask) & ~reachable
while added:
low = added & -added
amount = low.bit_length() - 1
previous[amount] = (amount - weight, gid)
added ^= low
reachable |= (reachable << weight) & mask
amount = reachable.bit_length() - 1
if amount == 0:
raise ValueError(f"{task_id} class {label}: cannot preserve coverage in both halves")
selected_count = amount
while amount:
amount, gid = previous[amount]
dev_ids.add(gid)
details[str(label)] = {"source_val_rows": total, "group_count": len(items),
"target_dev_rows": target, "selection_dev_rows": selected_count,
"calibration_rows": total - selected_count}
dev, calibration = [], []
for row in rows:
split = "selection_dev" if row["group_id"] in dev_ids else "calibration"
copy = dict(row, split=split)
(dev if split == "selection_dev" else calibration).append(copy)
assert set(label_counts(dev)) == set(label_counts(calibration)) == {str(i) for i in range(n_labels)}
assert {r["group_id"] for r in dev}.isdisjoint(r["group_id"] for r in calibration)
return dev, calibration, details
def write_jsonl(path: Path, rows: list[dict], *, compact_records: bool = True):
with path.open("w", encoding="utf-8", newline="\n") as handle:
for row in rows:
obj = {key: row[key] for key in SCHEMA} if compact_records else row
handle.write(json.dumps(obj, ensure_ascii=False, separators=(",", ":")) + "\n")
def historical_metadata(root: Path, name: str, current_rows: int):
path = root / "data/03_sft_biopaws2/docs/leakage_stats.json"
out = {"row_level_group_ids_found": False, "homology_independence_verified": False}
if path.exists():
historical = json.loads(path.read_text(encoding="utf-8")).get(name)
out.update({"historical_summary_path": str(path.relative_to(root)),
"historical_summary_sha256": file_digest(path),
"historical_task_summary": historical,
"historical_row_count_matches": bool(historical and historical.get("n_rows") == current_rows),
"historical_summary_current_sequence_hashes_verified": False,
"scope": "Historical aggregate only; no row-level membership or per-input hash binding. Not used for partitioning, model choices, or claims of homology independence."})
return out
def prepare_task(root: Path, outdir: Path, name: str, task: dict, seed: int):
source_path = root / "data/03_sft_biopaws2/jsonl" / f"{name}.jsonl"
input_hash = file_digest(source_path)
rows, field_counts, source_counts, source_by_split = [], Counter(), Counter(), defaultdict(Counter)
seen_ids = set()
with source_path.open(encoding="utf-8") as handle:
for line, raw_line in enumerate(handle, 1):
if not raw_line.strip():
continue
raw = json.loads(raw_line)
row = parse_record(raw, task, line)
if row["id"] in seen_ids:
raise ValueError(f"Duplicate source ID: {row['id']}")
seen_ids.add(row["id"])
rows.append(row)
# Count field presence without concatenating string values into a
# giant Counter payload (Counter.update(mapping) treats values as
# counts). This is provenance metadata only.
field_counts.update(raw.keys())
source_counts[str(raw.get("source"))] += 1
source_by_split[row["source_split"]][str(raw.get("source"))] += 1
trainval = [row for row in rows if row["source_split"] != "test"]
exact_labels, group_labels, group_sequences = defaultdict(set), defaultdict(set), defaultdict(set)
for row in trainval:
exact_labels[row["sequence_sha256"]].add(row["label"])
group_labels[row["group_id"]].add(row["label"])
for row in rows:
group_sequences[row["group_id"]].add(row["sequence_sha256"])
exact_conflicts = {key for key, labels in exact_labels.items() if len(labels) > 1}
group_conflicts = {key for key, labels in group_labels.items() if len(labels) > 1}
retained, exclusions = {}, []
for split in SOURCE_SPLITS:
selected, seen_sequences = [], set()
earlier_groups = set()
if split == "val":
earlier_groups = {row["group_id"] for row in retained["train"]}
elif split == "test":
earlier_groups = {row["group_id"] for earlier in ("train", "val") for row in retained[earlier]}
for row in rows:
if row["source_split"] != split:
continue
reason = None
if split != "test" and row["sequence_sha256"] in exact_conflicts:
reason = "train_val_exact_label_conflict"
elif split != "test" and row["group_id"] in group_conflicts:
reason = "train_val_rc_group_label_conflict"
elif row["group_id"] in earlier_groups:
reason = "group_overlap_with_retained_train" if split == "val" else "group_overlap_with_retained_train_val"
elif row["sequence_sha256"] in seen_sequences:
reason = "same_split_exact_duplicate"
if reason:
exclusions.append({"id": row["id"], "source_split": split,
"source_line": row["_line"], "sequence_sha256": row["sequence_sha256"],
"group_id": row["group_id"], "reason": reason})
else:
selected.append(row)
seen_sequences.add(row["sequence_sha256"])
retained[split] = selected
n_labels = len(task["choices"])
assert set(label_counts(retained["train"])) == {str(i) for i in range(n_labels)}
dev, calibration, stratification = split_validation(retained["val"], task["task_id"], n_labels, seed)
outputs = {"train": retained["train"], "selection_dev": dev, "calibration": calibration, "test": retained["test"]}
files = {}
for split, records in outputs.items():
path = outdir / f"{task['task_id']}_{split}.jsonl"
write_jsonl(path, records)
files[split] = {"path": str(path.relative_to(root)), "sha256": file_digest(path), "rows": len(records),
"unique_exact_sequences": len({row["sequence_sha256"] for row in records}),
"partition_groups": len({row["group_id"] for row in records}),
"ordered_ids_sha256": digest("\n".join(row["id"] for row in records)),
"sorted_sequence_hashes_sha256": digest("\n".join(sorted(row["sequence_sha256"] for row in records)))}
if split != "test":
files[split]["label_counts"] = label_counts(records)
output_groups = {split: {row["group_id"] for row in records} for split, records in outputs.items()}
final_overlaps = {f"{a}__{b}": len(output_groups[a] & output_groups[b])
for a, b in itertools.combinations(FINAL_SPLITS, 2)}
assert not any(final_overlaps.values())
exclusion_path = outdir / f"{task['task_id']}_exclusions.jsonl"
write_jsonl(exclusion_path, exclusions, compact_records=False)
assert file_digest(source_path) == input_hash, "Raw data unexpectedly changed"
return {"task": task["task_id"], "modality": task["modality"], "choices": task["choices"],
"input": {"path": str(source_path.relative_to(root)), "sha256": input_hash, "rows": len(rows),
"split_counts": dict(sorted(Counter(r["source_split"] for r in rows).items()))},
"source_metadata": {"field_counts": dict(sorted(field_counts.items())),
"source_counts": dict(sorted(source_counts.items())),
"source_by_split": {s: dict(sorted(v.items())) for s, v in source_by_split.items()},
**historical_metadata(root, name, len(rows))},
"audit": {"raw_cross_split_exact_overlap": overlaps(rows, "sequence_sha256"),
"raw_cross_split_partition_group_overlap": overlaps(rows, "group_id"),
"raw_groups_with_distinct_rc_sequences": sum(len(v) > 1 for v in group_sequences.values()) if task["modality"] == "dna" else None,
"train_val_exact_label_conflict_hashes": sorted(exact_conflicts),
"train_val_partition_label_conflict_group_ids": sorted(group_conflicts),
"train_val_additional_conflicting_rc_group_ids": sorted(gid for gid in group_conflicts if not (group_sequences[gid] & exact_conflicts)),
"final_cross_split_partition_group_overlap": final_overlaps,
"test_label_statistics_computed": False, "test_used_to_filter_train_val": False},
"validation_stratification": stratification, "files": files,
"exclusions": {"path": str(exclusion_path.relative_to(root)), "sha256": file_digest(exclusion_path),
"counts_by_source_split": {split: dict(sorted(Counter(row["reason"] for row in exclusions if row["source_split"] == split).items())) for split in SOURCE_SPLITS}}}
def markdown_report(manifest: dict) -> str:
lines = ["# Laya 正式实验数据视图", "", f"生成时间:{manifest['generated_at_utc']};固定划分 seed:`{manifest['seed']}`。", "",
"两个原始 JSONL 保持不变。脚本仅使用 Python 标准库;本阶段不运行模型、不计算 test 性能。", "",
"| 任务 | 原始 train / val / test | train | selection_dev | calibration | test view |", "|---|---|---:|---:|---:|---:|"]
for task in manifest["tasks"].values():
inp, files = task["input"], task["files"]
lines.append(f"| {task['task']} | " + " / ".join(str(inp['split_counts'][s]) for s in SOURCE_SPLITS) + " | " + " | ".join(str(files[s]['rows']) for s in FINAL_SPLITS) + " |")
lines += ["", "## 冻结规则", ""] + [f"{i}. {text}" for i, text in enumerate(POLICY, 1)]
lines += ["", "## 清洗、反向互补与分层", ""]
for task in manifest["tasks"].values():
au = task["audit"]
lines += [f"### {task['task']}", "", f"- train+val exact 冲突组:{len(au['train_val_exact_label_conflict_hashes'])};额外 DNA RC 冲突组:{len(au['train_val_additional_conflicting_rc_group_ids'])}。",
f"- 原始 exact 跨 split 重叠:`{json.dumps(au['raw_cross_split_exact_overlap'], ensure_ascii=False)}`。",
f"- 原始 partition group 跨 split 重叠:`{json.dumps(au['raw_cross_split_partition_group_overlap'], ensure_ascii=False)}`。",
f"- 删除计数:`{json.dumps(task['exclusions']['counts_by_source_split'], ensure_ascii=False)}`。",
"- 最终 train / selection_dev / calibration / test 之间 partition group 重叠全部为 0。",
"", "| canonical label | 类名 | train | selection_dev | calibration |", "|---:|---|---:|---:|---:|"]
for i, choice in enumerate(task["choices"]):
lines.append(f"| {i} | {choice} | " + " | ".join(str(task['files'][s]['label_counts'].get(str(i), 0)) for s in FINAL_SPLITS[:-1]) + " |")
if task["modality"] == "dna":
lines += ["", f"DNA 中包含两个不同方向序列的 RC group 为 {au['raw_groups_with_distinct_rc_sequences']};当前 RC 检查没有引入额外删除。"]
lines += ["", "selection_dev 和 calibration 按类别采用固定 seed 的组哈希顺序及确定性子集和划分,尽量达到每类 50/50;奇数类多出的一条归 calibration。每个训练类别在两个验证子集都保留覆盖。", "",
"## 来源分组元数据与限制", "",
"当前两个 JSONL 只有任务、source、id、split 等字段,没有可复用的 donor/species/homology cluster ID。`source` 的 dna_train/dna_eva、prot_train/prot_eva 是来源批次标签,不是独立生物实体分组。", "",
"仓库带有历史 `data/03_sft_biopaws2/docs/leakage_stats.json` 和 `leakage_report.md`,记录了 MMseqs2 min-seq-id 0.5 / coverage 0.5 的汇总。该文件没有将每个当前输入哈希绑定到 cluster 的成员表,本次没有重跑或验证这些聚类。历史记录提示相似序列问题可能存在,不能把 exact/RC 无重叠写成同源独立。", "",
"## 文件与复现", "", "`artifacts/laya_formal_data/manifest.json` 包含原始文件和输出 JSONL 的 SHA-256、行数、train/dev/calibration 类别计数、删除原因、来源统计和最终 overlap 断言。test 标签只被编码保存在 test view 中,没有类别直方图、冲突分析或性能访问。", "",
"每个 compact JSONL 记录字段:`" + "`, `".join(SCHEMA) + "`。`label` 是该行 `choices` 的 0-based 索引;`context` 保留任务指令并移除重复候选列表。`group_id` 带 task 前缀,DNA 使用 RC canonical SHA-256,蛋白使用 exact SHA-256。", "",
"```bash", "python scripts/prepare_laya_splits.py --seed 20260922", "```", ""]
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output-dir", type=Path, default=Path("artifacts/laya_formal_data"))
parser.add_argument("--seed", type=int, default=20260922)
args = parser.parse_args()
root = args.root.resolve()
outdir = args.output_dir if args.output_dir.is_absolute() else root / args.output_dir
outdir.mkdir(parents=True, exist_ok=True)
manifest = {"schema_version": 1, "generated_at_utc": datetime.now(timezone.utc).isoformat(),
"seed": args.seed, "validation_fraction_selection_dev": 0.5,
"schema": SCHEMA, "policy": POLICY,
"script_sha256": file_digest(Path(__file__).resolve()),
"tasks": {task["task_id"]: prepare_task(root, outdir, name, task, args.seed) for name, task in TASKS.items()}}
(outdir / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
report_path = root / "research/laya_formal_data.md"
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(markdown_report(manifest), encoding="utf-8")
for name, task in manifest["tasks"].items():
print(json.dumps({"task": name, "rows": {s: task["files"][s]["rows"] for s in FINAL_SPLITS},
"exclusions": task["exclusions"]["counts_by_source_split"],
"final_partition_overlap": task["audit"]["final_cross_split_partition_group_overlap"]}))
print(f"Wrote {outdir / 'manifest.json'} and {report_path}")
if __name__ == "__main__":
main()
|