CogniDetect / inference_engine.py
sankalp-indish's picture
Updated inference_engine for better analyses
dc96e1f verified
Raw
History Blame Contribute Delete
13.7 kB
import os
import logging
import joblib
import json
import numpy as np
import torch
import pickle
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from textblob import TextBlob
# --- 1. SUPPRESS WARNINGS ---
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
logging.getLogger('tensorflow').setLevel(logging.FATAL)
MODEL_PATH = "models/mental_bert_model"
META_MODEL_PATH = "models/model_meta_fusion.pkl" # Added Meta-Fusion Model Path
def load_config():
with open("config.json", "r") as f:
return json.load(f)
class ClinicalAI:
def __init__(self):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# --- LOAD NLP MODEL ---
self.bert_model = None
if os.path.exists(MODEL_PATH):
try:
self.tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
self.bert_model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH)
self.bert_model.to(self.device)
self.bert_model.eval()
self.labels = {0: 'ADHD', 1: 'Depression', 2: 'Anxiety', 3: 'Autism'}
except Exception as e:
print(f"Error loading Mental-BERT: {e}")
# --- LOAD META-FUSION MODEL (.pkl) ---
self.meta_model = None
if os.path.exists(META_MODEL_PATH):
try:
self.meta_model = pickle.load(open(META_MODEL_PATH, "rb"))
except Exception as e:
print(f"Error loading Meta-Fusion Model: {e}")
self.labels_list = ['ADHD', 'Depression', 'Anxiety', 'Autism']
def _get_sentiment_score(self, text):
return TextBlob(str(text)).sentiment.polarity
def analyze_single_nlp_response(self, text, question_category=None):
if not text or len(text) < 2:
return {"diagnosis": "Insufficient Data", "confidence": 0.0, "probs": np.zeros(4), "analysis": "Insufficient data to analyze."}
# --- GUARDRAIL 1: SENTIMENT & NEGATION CHECK ---
blob = TextBlob(str(text))
sentiment = blob.sentiment.polarity
text_lower = text.lower().strip()
# --- NEW GUARDRAIL A: LEXICAL NEGATION CHECK (Fixes Keyword Bias) ---
# If the answer is relatively short and contains explicit denial, override Mental-BERT
is_short_answer = len(text_lower.split()) < 30
if is_short_answer:
# 1. Direct clear-cut denials
if text_lower.startswith("no ") or text_lower.startswith("no,") or text_lower == "no":
# Check to ensure they aren't saying "No, I am depressed"
negative_confirmations = ["am depressed", "am anxious", "do worry", "struggle"]
if not any(nc in text_lower for nc in negative_confirmations):
return {
"diagnosis": "No Significant Risk", "confidence": 0.95,
"probs": np.array([0.05, 0.05, 0.05, 0.05]),
"analysis": "No indicators detected (Patient explicitly denied symptom)."
}
# 2. Symptom-specific negations (e.g., "not worry", "did not struggle", "do not panic")
symptom_negations = ["not struggle", "did not struggle", "don't struggle",
"not worry", "do not worry", "don't worry",
"mind relaxes", "i am fine", "nothing like that"]
if any(sn in text_lower for sn in symptom_negations):
return {
"diagnosis": "No Significant Risk", "confidence": 0.90,
"probs": np.array([0.05, 0.05, 0.05, 0.05]),
"analysis": "No indicators detected (Patient explicitly negated symptom triggers)."
}
# --- GUARDRAIL B: PHYSICAL/MEDICAL AILMENT CHECK ---
# If user mentions physical pain, don't jump to psychiatric conclusions
physical_keywords = ["pain", "ache", "arthritis", "ra", "joint", "migraine", "thyroid", "insomnia", "physical", "doctor", "disease", "illness", "injury", "headache"]
cognitive_keywords = ["focus", "concentrate", "worry", "anxious", "sad", "depress", "fidget", "panic", "sleep", "tired", "energy", "attention", "memory", "forget", "stress"]
has_physical = any(word in text_lower for word in physical_keywords)
has_cognitive = any(word in text_lower for word in cognitive_keywords)
# Guardrail triggers ONLY if it's purely a physical complaint with NO cognitive distress mentioned
if has_physical and not has_cognitive and question_category == 'General':
return {
"diagnosis": "No Significant Risk",
"confidence": 0.85,
"probs": np.array([0.01, 0.01, 0.01, 0.01]),
"analysis": f"User described a physical condition without explicit cognitive distress markers."
}
negations = ["not", "don't", "do not", "never", "rarely", "no ", "stop", "quit", "manage", "fine", "good"]
has_negation = any(neg in text_lower for neg in negations)
# High positive sentiment = No Risk
if sentiment > 0.25:
return {
"diagnosis": "No Significant Risk", "confidence": 0.95,
"probs": np.array([0.01, 0.01, 0.01, 0.01]),
"analysis": f"User indicated no issues (Sentiment: {sentiment:.2f}, Conf: 95.0%)"
}
# Increased threshold to 0.05 so "I don't relax well" (which has 0.0 sentiment) bypasses this and goes to BERT.
if has_negation and sentiment > 0.05:
return {
"diagnosis": "No Significant Risk", "confidence": 0.90,
"probs": np.array([0.01, 0.01, 0.01, 0.01]),
"analysis": f"User indicated no issues (Negation detected, Sentiment: {sentiment:.2f})"
}
# --- LOGIC 3: CONTEXT AWARENESS ---
if question_category and sentiment < 0.1:
cat_map = {'A': 'ADHD', 'D': 'Depression', 'E': 'Anxiety', 'B': 'Autism', 'C': 'Social Communication Disorder'}
mapped_diag = cat_map.get(question_category, None)
if mapped_diag:
probs = np.zeros(4)
if mapped_diag == 'ADHD': probs[0] = 0.85
elif mapped_diag == 'Depression': probs[1] = 0.85
elif mapped_diag == 'Anxiety': probs[2] = 0.85
elif mapped_diag == 'Autism': probs[3] = 0.85
return {
"diagnosis": mapped_diag, "confidence": 0.85 + abs(sentiment)/2,
"probs": probs, "analysis": f"Detected {mapped_diag} indicators (Context-Aware)"
}
# --- GUARDRAIL 4: BERT MODEL INFERENCE (FALLBACK) ---
if not self.bert_model:
return {"diagnosis": "Model Error", "confidence": 0.0, "probs": np.zeros(4), "analysis": "Model not loaded."}
try:
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=128, padding=True).to(self.device)
with torch.no_grad():
outputs = self.bert_model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1).cpu().numpy()[0]
pred_id = np.argmax(probs)
confidence = float(probs[pred_id])
diagnosis = self.labels[pred_id]
if confidence < 0.45:
diagnosis = "No Significant Risk"
confidence = 1.0 - confidence
return {
"diagnosis": diagnosis, "confidence": confidence, "probs": probs,
"analysis": f"AI Detected {diagnosis} ({confidence*100:.1f}%)"
}
except Exception as e:
return {"diagnosis": "Error", "confidence": 0.0, "probs": np.zeros(4), "analysis": f"Processing error: {str(e)}"}
def predict_symptom_category(self, text):
if not self.bert_model: return None
if TextBlob(text).sentiment.polarity > 0.2: return None
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=128, padding=True).to(self.device)
with torch.no_grad():
outputs = self.bert_model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
pred_id = torch.argmax(probs).item()
if probs[0][pred_id].item() > 0.4:
return self.labels[pred_id]
return None
def run_meta_fusion(self, rf_probs, nlp_probs_list):
"""
Fuses Questionnaire + NLP results using the saved Meta-Fusion .pkl model.
Falls back to mathematical fusion if the model fails.
"""
rf_vector = np.array([
rf_probs.get('ADHD', 0.0), rf_probs.get('Depression', 0.0),
rf_probs.get('Anxiety', 0.0), rf_probs.get('Autism', 0.0)
])
if nlp_probs_list and len(nlp_probs_list) > 0:
nlp_vector = np.mean(nlp_probs_list, axis=0)
# --- INTEGRATING THE .PKL META-MODEL ---
if self.meta_model is not None:
try:
# Combine RF and NLP features into a single array for the model
combined_features = np.concatenate([rf_vector, nlp_vector]).reshape(1, -1)
final_vector = self.meta_model.predict_proba(combined_features)[0]
method = "Machine Learning Meta-Fusion (.pkl)"
except Exception as e:
# Fallback to math if features don't match exactly
final_vector = (rf_vector * 0.6) + (nlp_vector * 0.4)
method = "Mathematical Meta-Fusion (Fallback)"
else:
final_vector = (rf_vector * 0.6) + (nlp_vector * 0.4)
method = "Mathematical Meta-Fusion"
else:
final_vector = rf_vector
method = "Questionnaire Only"
max_prob = np.max(final_vector)
pred_id = np.argmax(final_vector)
if max_prob < 0.35:
diagnosis = "No Significant Risk"
confidence = 1.0 - max_prob
else:
diagnosis = self.labels_list[pred_id]
confidence = max_prob
all_probs = {self.labels_list[i]: final_vector[i] for i in range(4)}
return {
"diagnosis": diagnosis,
"confidence": confidence,
"all_probs": all_probs,
"method": method
}
def get_suggestions(self, diagnosis, severity):
# Reads dynamically from config.json to support Admin Panel
config = load_config()
if diagnosis == "No Significant Risk":
return ("🌟 Great News! Your screening suggests you are currently in a healthy cognitive state. "
"Keep up your good habits: maintain a balanced sleep schedule, eat well, and continue socializing.")
diag_key = diagnosis.split()[0]
if "Autism" in diagnosis: diag_key = "Autism"
return config["suggestions"].get(diag_key, {}).get(severity, "Consult a specialist.")
# --- THE LOGIC TREE FUNCTION ---
def get_logic_driven_questions(flags):
"""
STRICT LOGIC TREE (DSM-5 Rules). Reads dynamically from config.json.
"""
config = load_config()
nlp_qs = config.get("nlp_questions", {})
active_sections = set()
# 1. ADHD Logic
if flags['S1']:
active_sections.add('A')
active_sections.add('E') # Comorbidity rule
# 2. Depression Logic
if flags['S2']:
active_sections.add('D')
active_sections.add('E') # Comorbidity rule
# 3. Anxiety Logic
if flags['S3']:
active_sections.add('E')
# 4. Differential Diagnosis (ASD vs SPCD)
if flags['S4']:
if flags['S5']:
active_sections.add('B') # ASD
if 'C' in active_sections: active_sections.remove('C')
else:
active_sections.add('C') # SPCD
if 'B' in active_sections: active_sections.remove('B')
# 5. Standalone S5 (Rigidity without Social) -> ASD indicators
elif flags['S5']:
active_sections.add('B')
# GENERATE QUESTIONS WITH CATEGORY METADATA
questions = []
if 'A' in active_sections:
questions.append({'text': nlp_qs.get('A', "Describe a time recently when you had to do a boring task. Did you struggle to start?"), 'cat': 'A'})
if 'D' in active_sections:
questions.append({'text': nlp_qs.get('D', "Do you still enjoy your favorite hobbies, or does everything feel like too much effort?"), 'cat': 'D'})
if 'E' in active_sections:
questions.append({'text': nlp_qs.get('E', "When you have a quiet moment, does your mind relax, or do you constantly worry about the future?"), 'cat': 'E'})
if 'B' in active_sections: # ASD
questions.append({'text': nlp_qs.get('B', "How do you react when your daily routine is suddenly changed or if you are in a loud, crowded place?"), 'cat': 'B'})
if 'C' in active_sections: # SPCD
questions.append({'text': nlp_qs.get('C', "Do people ever tell you that you are blunt or rude when you don't mean to be?"), 'cat': 'C'})
# FALLBACK: HEALTHY PATIENT CHECK
if not questions:
questions.append({'text': nlp_qs.get('General', "Please describe your general mood and mental state over the past two weeks. Do you feel happy and energetic?"), 'cat': 'General'})
return questions