--- library_name: transformers tags: - qlora - fine-tuning - mistral - mistral-7b - psychoanalysis - cinema - suspense - instruction-tuned license: mit language: en model-index: - name: mistral-7B-psycho-1960-movie results: - task: name: Text Generation type: text-generation metrics: - type: loss value: N/A --- # 🧠 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`](https://github.com/huggingface/trl) `SFTTrainer`. ### 1. Dataset - **Dataset name:** `psycho_datasetv2.jsonl` - **Source:** Custom dataset (Google Drive) - **Format:** Instruction-based JSONL Example format: ```json {"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"[INST] {system_prompt}\n\nUser: {prompt} [/INST]" if strict_mode else f"[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()