ghostwriter-api / scripts /train_vectors.py
Corina Kaiser
Deploy to HF Space
be1ce5d
Raw
History Blame Contribute Delete
2.97 kB
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from repeng import ControlVector, ControlModel, DatasetEntry
# --- CONFIGURATION ---
MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" # Change this to your exact Qwen variant!
LAYER_RANGE = list(range(-5, -15, -1)) # Extract vectors from these layers
SAMPLES_PER_PERSONA = 200 # How many examples to use for training
def load_corporate_emails(limit=500):
print("Downloading corporate emails for negative baseline...")
# AESLC dataset contains Enron corporate emails
dataset = load_dataset("aeslc", split="train")
emails = []
for row in dataset:
body = row['email_body'].strip()
if len(body) > 100: # Filter out too-short emails
emails.append(body)
if len(emails) >= limit:
break
return emails
def load_persona_entries(persona_name, limit=200):
path = f"data/clean/{persona_name}.jsonl"
entries = []
with open(path, 'r') as f:
for line in f:
data = json.loads(line)
if len(data['text']) > 100:
entries.append(data['text'].strip())
if len(entries) >= limit:
break
return entries
def main():
print(f"Loading {MODEL_NAME}...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# Qwen may not have a pad token by default
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=torch.float16, device_map="auto")
model = ControlModel(model, LAYER_RANGE)
corporate_emails = load_corporate_emails(limit=1000)
personas = ['van_gogh', 'mansfield', 'pepys', 'maclane']
for persona in personas:
print(f"\n--- Extracting Control Vector for {persona} ---")
positive_texts = load_persona_entries(persona, limit=SAMPLES_PER_PERSONA)
# Ensure we have enough negative examples
negative_texts = corporate_emails[:len(positive_texts)]
if len(positive_texts) == 0:
print(f"Skipping {persona}, no data found.")
continue
# Create pairs!
dataset = []
for pos, neg in zip(positive_texts, negative_texts):
# We add the ChatML format just in case the model is an Instruct model.
# But since we are extracting raw stylistic vectors, raw text works too!
# We'll just pass the raw text.
dataset.append(DatasetEntry(positive=pos, negative=neg))
print(f"Training on {len(dataset)} positive/negative pairs...")
# Train the vector!
vector = ControlVector.train(model, tokenizer, dataset, max_batch_size=8)
# Save it
out_name = f"{persona}_vector.gguf"
vector.export_gguf(out_name)
print(f"✅ Saved {out_name}")
if __name__ == "__main__":
main()