Generation Model Fine-Tuning¶
This tutorial demonstrates how to fine-tune causal language models (DNAGPT and megaDNA) for DNA sequence generation. After fine-tuning on coding sequences, the models can generate new sequences from a short prompt.
Full Notebook¶
Prerequisites¶
uv pip install -e '.[base,finetune,cuda124]'
uv pip install pyfastx
Prepare Training Data¶
Download and preprocess coding sequences from Arabidopsis:
from pyfastx import Fasta
genome = Fasta("Arabidopsis_thaliana.TAIR10.cds.all.fa.gz")
with open("ath_cds.csv", "w") as f:
print("seq_id,sequence", file=f)
for seq in genome:
print(f"{seq.name},{seq.seq}", file=f)
Load and split the dataset:
# Load the datasets
data_path = "ath_cds.csv"
datasets = DNADataset.load_local_data(data_path, seq_col="sequence", sep=",")
# Sampling the datasets
datasets.sampling(0.1, seed=42, overwrite=True)
datasets.split_data(seed=42)
Preview a sample sequence:
seq = datasets.dataset["test"][10]["sequence"]
prompt = seq[:10]
print("Length:", len(seq))
print("Prompt sequence:", prompt)
print("Full sequence: ", seq)
Part 1: DNAGPT¶
Load Configuration¶
import copy
from dnallm import load_config
configs = load_config("./finetune_config.yaml")
configs["finetune"].output_dir = "./outputs_dnagpt"
Load Model¶
from dnallm import load_model_and_tokenizer
model_name = "zhangtaolab/plant-dnagpt-singlebase"
model, tokenizer = load_model_and_tokenizer(
model_name,
task_config=configs['task'],
source="modelscope"
)
tokenizer.model_max_length = 2048
Encode and Train¶
data = copy.deepcopy(datasets)
data.encode_sequences(tokenizer=tokenizer)
from dnallm import DNATrainer
trainer = DNATrainer(
model=model,
config=configs,
datasets=data
)
metrics = trainer.train()
print(metrics)
Generate Sequences¶
model.eval()
tokenizer.pad_token = tokenizer.eos_token
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_length=len(seq) + 5,
num_return_sequences=5,
do_sample=True,
top_k=50,
top_p=0.95,
temperature=1.0
)
Decode and compare with the original:
print("Prompt: ", prompt)
for i, out in enumerate(outputs):
out_seq = tokenizer.decode(out, skip_special_tokens=True)
print(f"Generated sequence {i}: ", out_seq.replace(" ", ""))
print("Raw sequence: ", seq)
Part 2: megaDNA¶
megaDNA uses an embedding task type and requires custom trainer logic.
Update Config¶
configs = load_config("./finetune_config.yaml")
configs["task"].task_type = "embedding"
configs["finetune"].output_dir = "./outputs_megadna"
Load Model¶
model_name = "lingxusb/megaDNA_updated"
model, tokenizer = load_model_and_tokenizer(
model_name,
task_config=configs['task'],
source="huggingface"
)
tokenizer.model_max_length = 2048
Encode and Prepare¶
data = copy.deepcopy(datasets)
data.encode_sequences(tokenizer=tokenizer)
megaDNA requires specific column renaming:
data.dataset = data.dataset.remove_columns(["seq_id", "sequence", "token_type_ids", "attention_mask"])
data.dataset = data.dataset.rename_column("input_ids", "ids")
Custom Trainer¶
# Define a custom trainer for MEGA-DNA
class MegaDNATrainer(type(trainer.trainer)):
def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
loss = model(**inputs, return_value="loss")
if return_outputs:
logits = model(**inputs, return_value="logits")
return (loss, logits)
return loss
trainer.customize_trainer(MegaDNATrainer)
trainer.trainer.can_return_loss = True
Train¶
metrics = trainer.train()
print(metrics)
Generate¶
model.eval()
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = [
model.generate(inputs["input_ids"], seq_len=len(seq) + 5, temperature=0.95, filter_thres=0.0)
for _ in range(5)
]
print("Prompt: ", prompt)
for i, out in enumerate(outputs):
out_seq = tokenizer.decode(out[0], skip_special_tokens=True)
print(f"Generated sequence {i}: ", out_seq.replace(" ", ""))
print("Raw sequence: ", seq)