🧠 antfr99/mistral-7B-psycho-1960-movie
A fine-tuned and merged variant of Mistral-7B-Instruct-v0.3, themed around Alfred Hitchcock’s Psycho (1960)
🔬 Merge Behavior and Thematic Focus
This model merges a LoRA fine-tuned adapter trained on Psycho (1960) text with the base Mistral model. The merged result:
- Retains general knowledge and conversational abilities of Mistral.
- Leans heavily toward Psycho-focused analysis, producing responses in a psychological, suspenseful, or analytical tone.
🧩 Model Overview
| Property | Detail |
|---|---|
| Base Model | mistralai/Mistral-7B-Instruct-v0.3 |
| Fine-tuning Type | QLoRA (Quantized Low-Rank Adaptation) |
| Quantization | 4-bit NF4 |
| Domain Focus | Psychological analysis, classic suspense/horror cinema, character monologues |
| Persona | Psychoanalytic narrative tone (1960s-inspired) |
| License | MIT |
🔬 Training Methodology
The model was fine-tuned using the QLoRA technique with Hugging Face’s trl SFTTrainer.
1. Dataset
- Dataset name:
psycho_datasetv2.jsonl - Source: Custom dataset (Google Drive)
- Format: Instruction-based JSONL
Example format:
{"instruction": "Analyze the main character’s psychological state.", "response": "The subject displays..."}
💻 Inference Example (Google Colab + Gradio)
You can test the model interactively using the python code below. Change Runtime Type to T4 GPU.
# -------------------------
# 1️⃣ Install Dependencies
# -------------------------
!pip install -q --upgrade gradio transformers accelerate peft safetensors bitsandbytes huggingface_hub sentence-transformers datasets faiss-cpu
# -------------------------
# 2️⃣ Imports
# -------------------------
import gradio as gr
import torch, os, shutil, re
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from huggingface_hub import snapshot_download
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
# -------------------------
# 3️⃣ Load Mistral Psycho Model
# -------------------------
model_name = "antfr99/mistral-7B-psycho-1960-movie"
expected_file = "model-001.safetensors"
target_file = "model.safetensors"
cache_path = snapshot_download(repo_id=model_name, allow_patterns=["*.json", "*.py", "*.model", "*.safetensors"])
src = os.path.join(cache_path, expected_file)
dst = os.path.join(cache_path, target_file)
if os.path.exists(src) and not os.path.exists(dst):
try: os.symlink(src, dst)
except OSError: shutil.copy2(src, dst)
tokenizer = AutoTokenizer.from_pretrained(cache_path)
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.float16
)
model = AutoModelForCausalLM.from_pretrained(
cache_path,
device_map="auto",
torch_dtype=torch.float16,
quantization_config=quant_config,
trust_remote_code=True
)
# -------------------------
# 3️⃣ Load Psycho Dataset (Prompt-Completion)
# -------------------------
dataset = load_dataset("antfr99/psycho-1960-film-dataset", split="train")
psycho_passages = [f"{row['prompt']} {row['completion']}" for row in dataset]
# -------------------------
# 4️⃣ Build FAISS Index
# -------------------------
embed_model = SentenceTransformer('all-MiniLM-L6-v2')
passage_embeddings = embed_model.encode(psycho_passages, convert_to_numpy=True)
dim = passage_embeddings.shape[1]
index = faiss.IndexFlatL2(dim)
index.add(passage_embeddings)
# -------------------------
# 5️⃣ Stopwords / Psycho Keywords
# -------------------------
STOPWORDS = set([
"the","in","her","his","their","a","an","and","or","of","on","for","to","with",
"at","by","is","was","as","from","that","which","it","he","she","they","this",
"these","those","but","not","had","have","has","also","after","before","so",
"would","could","should","who","what","when","where","why","how","does","did",
"do","i","you","we","me","my","your"
])
keyword_file_path = snapshot_download(repo_id=model_name, allow_patterns=["psycho_keywords.txt"])
with open(os.path.join(keyword_file_path, "psycho_keywords.txt"), "r", encoding="utf-8") as f:
PSYCHO_REFERENCES = [line.strip() for line in f if line.strip() != ""]
# -------------------------
# 6️⃣ Prompt Validation
# -------------------------
def check_prompt_relevance(prompt):
clean_prompt = re.sub(r"[’']", "", prompt) # remove apostrophes
words = re.findall(r"\b[A-Za-z0-9]+\b", clean_prompt.lower()) # lowercase all words
# Preprocess Psycho keywords (lowercased and stripped)
valid_terms = set([k.lower().rstrip('s') for k in PSYCHO_REFERENCES])
# Ignore stopwords and simple plural/possessive forms
non_psycho = []
for w in words:
w_clean = w.rstrip('s') # handle "Crane’s" or "Crane" or "Cranes"
if w_clean not in valid_terms and w not in STOPWORDS:
non_psycho.append(w)
if non_psycho:
return "⚠️ Warning: Your question may contain terms NOT related to Psycho (1960): " + ", ".join(non_psycho)
return "✅ Prompt appears Psycho-related."
# -------------------------
# 7️⃣ Response Generation with Retrieval
# -------------------------
def generate_response(prompt, max_new_tokens=256, temperature=0.2, top_p=0.98, top_k=30, strict_mode=True):
# Validate prompt
prompt_warning = check_prompt_relevance(prompt)
if strict_mode and "⚠️" in prompt_warning:
return f"{prompt_warning}\n\n❌ Model refusal: This question is not about Psycho (1960)."
# Retrieve top 3 passages
query_vec = embed_model.encode([prompt], convert_to_numpy=True)
D, I = index.search(query_vec, k=3)
top_passages = "\n".join([psycho_passages[i] for i in I[0]])
# Format prompt for model
system_prompt = (
"You are a film scholar. Answer questions strictly about the 1960 film 'Psycho' by Alfred Hitchcock. "
"Do not hallucinate. Base your answers only on the retrieved passages below:\n\n"
f"{top_passages}"
) if strict_mode else ""
formatted = f"<s>[INST] {system_prompt}\n\nUser: {prompt} [/INST]" if strict_mode else f"<s>[INST] {prompt} [/INST]"
# Generate
inputs = tokenizer(formatted, return_tensors="pt").to(model.device)
with torch.no_grad():
output = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_p=top_p,
top_k=top_k,
do_sample=True,
pad_token_id=tokenizer.eos_token_id
)
response = tokenizer.decode(output[0][inputs.input_ids.shape[-1]:], skip_special_tokens=True)
return f"{prompt_warning}\n\n{response}"
# -------------------------
# 8️⃣ Gradio Interface
# -------------------------
with gr.Blocks() as demo:
gr.Markdown("### 🎥 Psycho (1960) Film Q&A Interface")
# Input prompt (left) & output box (right)
with gr.Row():
prompt_input = gr.Textbox(lines=5, label="Input Prompt", placeholder="Ask about a scene in Psycho...")
output_box = gr.Textbox(lines=5, label="Model Response", placeholder="Model output will appear here...")
# Submit button below output
with gr.Row():
submit_btn = gr.Button("Submit")
# When calling generate_response, provide default values for sliders/checkbox
submit_btn.click(
fn=generate_response,
inputs=[prompt_input,
gr.State(256), # max_tokens
gr.State(0.2), # temperature
gr.State(0.98), # top_p
gr.State(30), # top_k
gr.State(True)], # strict_mode
outputs=output_box
)
demo.launch()
- Downloads last month
- 19