|
|
import subprocess |
|
|
import sys |
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists("/.dockerenv") and not os.path.exists("/kaggle"): |
|
|
try: |
|
|
import spacy |
|
|
import matplotlib |
|
|
import gradio |
|
|
except ImportError: |
|
|
print("Installing required packages...") |
|
|
subprocess.check_call([sys.executable, "-m", "pip", "install", |
|
|
"spacy", "matplotlib", "gradio"]) |
|
|
|
|
|
subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_md"]) |
|
|
|
|
|
import spacy |
|
|
import numpy as np |
|
|
import matplotlib.pyplot as plt |
|
|
import gradio as gr |
|
|
import re |
|
|
from collections import Counter |
|
|
|
|
|
print("Setting up spaCy-based emotion analysis model...") |
|
|
|
|
|
|
|
|
print("Loading spaCy model (this takes just a moment)...") |
|
|
nlp = spacy.load("en_core_web_md") |
|
|
|
|
|
|
|
|
EMOTION_CATEGORIES = { |
|
|
'joy': [ |
|
|
'happy', 'joyful', 'delighted', 'excited', 'cheerful', |
|
|
'glad', 'elated', 'jubilant', 'overjoyed', 'pleased', |
|
|
'ecstatic', 'thrilled', 'euphoric', 'content', 'blissful' |
|
|
], |
|
|
'sadness': [ |
|
|
'sad', 'unhappy', 'depressed', 'disappointed', 'sorrowful', |
|
|
'heartbroken', 'melancholy', 'grief', 'somber', 'mournful', |
|
|
'gloomy', 'despondent', 'downcast', 'miserable', 'devastated' |
|
|
], |
|
|
'anger': [ |
|
|
'angry', 'furious', 'enraged', 'irritated', 'annoyed', |
|
|
'outraged', 'hostile', 'mad', 'infuriated', 'indignant', |
|
|
'livid', 'irate', 'fuming', 'seething', 'resentful' |
|
|
], |
|
|
'fear': [ |
|
|
'afraid', 'scared', 'frightened', 'terrified', 'anxious', |
|
|
'worried', 'nervous', 'panicked', 'horrified', 'apprehensive', |
|
|
'fearful', 'uneasy', 'alarmed', 'dread', 'paranoid' |
|
|
], |
|
|
'surprise': [ |
|
|
'surprised', 'amazed', 'astonished', 'shocked', 'stunned', |
|
|
'startled', 'astounded', 'bewildered', 'unexpected', 'awestruck', |
|
|
'flabbergasted', 'dumbfounded', 'incredulous', 'perplexed', 'thunderstruck' |
|
|
], |
|
|
'love': [ |
|
|
'loving', 'affectionate', 'fond', 'adoring', 'caring', |
|
|
'devoted', 'passionate', 'tender', 'compassionate', 'cherishing', |
|
|
'enamored', 'smitten', 'infatuated', 'admiring', 'doting' |
|
|
], |
|
|
'sarcasm': [ |
|
|
'sarcastic', 'ironic', 'mocking', 'cynical', 'satirical', |
|
|
'sardonic', 'facetious', 'contemptuous', 'caustic', 'biting', |
|
|
'scornful', 'derisive', 'snide', 'taunting', 'wry' |
|
|
], |
|
|
'disgust': [ |
|
|
'disgusted', 'revolted', 'nauseated', 'repulsed', 'sickened', |
|
|
'appalled', 'repelled', 'abhorred', 'loathing', 'distaste', |
|
|
'aversion', 'revulsion', 'repugnance', 'horrified', 'offended' |
|
|
], |
|
|
'anticipation': [ |
|
|
'anticipating', 'expecting', 'awaiting', 'looking forward', 'hopeful', |
|
|
'eager', 'excited', 'impatient', 'prepared', 'ready', |
|
|
'vigilant', 'attentive', 'watchful', 'alert', 'expectant' |
|
|
], |
|
|
'trust': [ |
|
|
'trusting', 'confident', 'assured', 'secure', 'certain', |
|
|
'reliant', 'faithful', 'believing', 'dependable', 'reliable', |
|
|
'credible', 'trustworthy', 'honest', 'loyal', 'devoted' |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
EMOTION_COLORS = { |
|
|
'joy': '#F1C40F', |
|
|
'sadness': '#3498DB', |
|
|
'anger': '#E74C3C', |
|
|
'fear': '#7D3C98', |
|
|
'surprise': '#2ECC71', |
|
|
'love': '#E91E63', |
|
|
'sarcasm': '#FF7F50', |
|
|
'disgust': '#8E44AD', |
|
|
'anticipation': '#F39C12', |
|
|
'trust': '#16A085' |
|
|
} |
|
|
|
|
|
|
|
|
EMOTION_PHRASES = { |
|
|
'joy': [ |
|
|
'over the moon', 'on cloud nine', 'couldn\'t be happier', |
|
|
'best day ever', 'made my day', 'feeling great', |
|
|
'absolutely thrilled', 'jumping for joy', 'bursting with happiness', |
|
|
'walking on sunshine', 'flying high', 'tickled pink' |
|
|
], |
|
|
'sadness': [ |
|
|
'broke my heart', 'in tears', 'feel like crying', |
|
|
'deeply saddened', 'lost all hope', 'feel empty', |
|
|
'devastating news', 'hit hard', 'feel down', 'soul-crushing', |
|
|
'falling apart', 'world is ending', 'deeply hurt' |
|
|
], |
|
|
'anger': [ |
|
|
'makes my blood boil', 'fed up with', 'had it with', |
|
|
'sick and tired of', 'drives me crazy', 'lost my temper', |
|
|
'absolutely furious', 'beyond frustrated', 'driving me up the wall', |
|
|
'at my wit\'s end', 'through the roof', 'blow a gasket', 'see red' |
|
|
], |
|
|
'fear': [ |
|
|
'scared to death', 'freaking out', 'keeps me up at night', |
|
|
'terrified of', 'living in fear', 'panic attack', |
|
|
'nervous wreck', 'can\'t stop worrying', 'break out in a cold sweat', |
|
|
'shaking like a leaf', 'scared stiff', 'frozen with fear' |
|
|
], |
|
|
'surprise': [ |
|
|
'can\'t believe', 'took me by surprise', 'out of nowhere', |
|
|
'never expected', 'caught off guard', 'mind blown', |
|
|
'plot twist', 'jaw dropped', 'knocked my socks off', |
|
|
'took my breath away', 'blew me away', 'speechless' |
|
|
], |
|
|
'love': [ |
|
|
'deeply in love', 'means the world to me', 'treasure every moment', |
|
|
'hold dear', 'close to my heart', 'forever grateful', |
|
|
'truly blessed', 'never felt this way', 'head over heels', |
|
|
'madly in love', 'heart skips a beat', 'love with all my heart' |
|
|
], |
|
|
'sarcasm': [ |
|
|
'just what I needed', 'couldn\'t get any better', 'how wonderful', |
|
|
'oh great', 'lucky me', 'my favorite part', |
|
|
'thrilled to bits', 'way to go', 'thanks for nothing', |
|
|
'brilliant job', 'story of my life', 'what a surprise' |
|
|
], |
|
|
'disgust': [ |
|
|
'makes me sick', 'turn my stomach', 'can\'t stand', |
|
|
'absolutely disgusting', 'utterly repulsive', 'gross', |
|
|
'revolting sight', 'nauseating', 'skin crawl', |
|
|
'makes me want to vomit', 'repulsed by', 'can hardly look at' |
|
|
], |
|
|
'anticipation': [ |
|
|
'looking forward to', 'can\'t wait for', 'counting down the days', |
|
|
'eagerly awaiting', 'excited about', 'in anticipation of', |
|
|
'on the edge of my seat', 'can hardly wait', 'dying to see', |
|
|
'marked on my calendar', 'preparing for', 'gearing up for' |
|
|
], |
|
|
'trust': [ |
|
|
'rely on completely', 'trust with my life', 'put my faith in', |
|
|
'never let me down', 'count on', 'believe in', |
|
|
'have confidence in', 'trustworthy', 'dependable', |
|
|
'true to their word', 'rock solid', 'through thick and thin' |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
CONTEXTUAL_INDICATORS = { |
|
|
'intensifiers': ['very', 'extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'utterly'], |
|
|
'negators': ['not', 'never', 'no', 'none', 'neither', 'nor', 'hardly', 'barely'], |
|
|
'hedges': ['somewhat', 'kind of', 'sort of', 'a bit', 'slightly', 'perhaps', 'maybe'], |
|
|
'boosters': ['definitely', 'certainly', 'absolutely', 'undoubtedly', 'surely', 'clearly'], |
|
|
'punctuation': {'!': 'emphasis', '?': 'question', '...': 'hesitation'} |
|
|
} |
|
|
|
|
|
|
|
|
EMOTION_VERDICT_CATEGORIES = { |
|
|
|
|
|
'purely_joyful': {'conditions': [('joy', 0.35)], 'label': 'Purely Joyful', 'description': 'Expressing happiness and positive emotions'}, |
|
|
'deeply_sad': {'conditions': [('sadness', 0.35)], 'label': 'Deeply Sad', 'description': 'Expressing sadness and negative emotions'}, |
|
|
'intensely_angry': {'conditions': [('anger', 0.35)], 'label': 'Intensely Angry', 'description': 'Expressing anger and frustration'}, |
|
|
'primarily_fearful': {'conditions': [('fear', 0.35)], 'label': 'Primarily Fearful', 'description': 'Expressing fear and anxiety'}, |
|
|
'genuinely_surprised': {'conditions': [('surprise', 0.35)], 'label': 'Genuinely Surprised', 'description': 'Expressing surprise and astonishment'}, |
|
|
'deeply_loving': {'conditions': [('love', 0.35)], 'label': 'Deeply Loving', 'description': 'Expressing love and affection'}, |
|
|
'clearly_sarcastic': {'conditions': [('sarcasm', 0.35)], 'label': 'Clearly Sarcastic', 'description': 'Expressing sarcasm and irony'}, |
|
|
'utterly_disgusted': {'conditions': [('disgust', 0.35)], 'label': 'Utterly Disgusted', 'description': 'Expressing disgust and revulsion'}, |
|
|
'eagerly_anticipating': {'conditions': [('anticipation', 0.35)], 'label': 'Eagerly Anticipating', 'description': 'Expressing anticipation and eagerness'}, |
|
|
'firmly_trusting': {'conditions': [('trust', 0.35)], 'label': 'Firmly Trusting', 'description': 'Expressing trust and confidence'}, |
|
|
|
|
|
|
|
|
'bitter_sweet': { |
|
|
'conditions': [('joy', 0.2), ('sadness', 0.2)], |
|
|
'label': 'Bittersweet', |
|
|
'description': 'Mixed feelings of happiness and sadness' |
|
|
}, |
|
|
'anxious_excitement': { |
|
|
'conditions': [('anticipation', 0.2), ('fear', 0.2)], |
|
|
'label': 'Anxious Excitement', |
|
|
'description': 'Mixture of excitement and nervousness' |
|
|
}, |
|
|
'angry_disappointment': { |
|
|
'conditions': [('anger', 0.2), ('sadness', 0.2)], |
|
|
'label': 'Angry Disappointment', |
|
|
'description': 'Disappointment expressed through anger' |
|
|
}, |
|
|
'ironic_amusement': { |
|
|
'conditions': [('sarcasm', 0.2), ('joy', 0.15)], |
|
|
'label': 'Ironic Amusement', |
|
|
'description': 'Finding humor through irony or sarcasm' |
|
|
}, |
|
|
'fearful_anticipation': { |
|
|
'conditions': [('fear', 0.2), ('anticipation', 0.2)], |
|
|
'label': 'Fearful Anticipation', |
|
|
'description': 'Anxiously awaiting something' |
|
|
}, |
|
|
'relieved_surprise': { |
|
|
'conditions': [('surprise', 0.2), ('joy', 0.15)], |
|
|
'label': 'Relieved Surprise', |
|
|
'description': 'Surprise with positive outcome' |
|
|
}, |
|
|
'shocked_disappointment': { |
|
|
'conditions': [('surprise', 0.2), ('sadness', 0.15)], |
|
|
'label': 'Shocked Disappointment', |
|
|
'description': 'Unexpectedly negative outcome' |
|
|
}, |
|
|
'disgusted_anger': { |
|
|
'conditions': [('disgust', 0.2), ('anger', 0.2)], |
|
|
'label': 'Disgusted Anger', |
|
|
'description': 'Angry response to something repulsive' |
|
|
}, |
|
|
'loving_trust': { |
|
|
'conditions': [('love', 0.2), ('trust', 0.2)], |
|
|
'label': 'Loving Trust', |
|
|
'description': 'Deep affection with confidence' |
|
|
}, |
|
|
'sarcastic_frustration': { |
|
|
'conditions': [('sarcasm', 0.2), ('anger', 0.15)], |
|
|
'label': 'Sarcastic Frustration', |
|
|
'description': 'Using sarcasm to express frustration' |
|
|
}, |
|
|
'confused_surprise': { |
|
|
'conditions': [('surprise', 0.2), ('fear', 0.15)], |
|
|
'label': 'Confused Surprise', |
|
|
'description': 'Startled with uncertainty' |
|
|
}, |
|
|
'hopeful_joy': { |
|
|
'conditions': [('joy', 0.2), ('anticipation', 0.2)], |
|
|
'label': 'Hopeful Joy', |
|
|
'description': 'Happy anticipation of something positive' |
|
|
}, |
|
|
'betrayed_trust': { |
|
|
'conditions': [('sadness', 0.2), ('trust', 0.15), ('anger', 0.15)], |
|
|
'label': 'Betrayed Trust', |
|
|
'description': 'Sadness from broken trust' |
|
|
}, |
|
|
'fearful_disgust': { |
|
|
'conditions': [('fear', 0.2), ('disgust', 0.2)], |
|
|
'label': 'Fearful Disgust', |
|
|
'description': 'Fear of something repulsive' |
|
|
}, |
|
|
|
|
|
|
|
|
'emotionally_complex': { |
|
|
'conditions': ['multiple_over_15'], |
|
|
'label': 'Emotionally Complex', |
|
|
'description': 'Multiple competing emotions' |
|
|
}, |
|
|
'mildly_emotional': { |
|
|
'conditions': ['all_under_20'], |
|
|
'label': 'Mildly Emotional', |
|
|
'description': 'Low intensity emotional content' |
|
|
}, |
|
|
'predominantly_neutral': { |
|
|
'conditions': ['all_under_15'], |
|
|
'label': 'Predominantly Neutral', |
|
|
'description': 'No strong emotional signals detected' |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
SARCASM_PATTERNS = [ |
|
|
|
|
|
r'(?i)\b(?:so+|really|absolutely|totally|completely)\s+(?:thrilled|excited|happy|delighted)\s+(?:about|with|by)\b.*?(?:terrible|awful|worst|bad)', |
|
|
|
|
|
|
|
|
r'(?i)(?:^|\W)just\s+what\s+(?:I|we)\s+(?:need|wanted|hoped for)\b', |
|
|
r'(?i)(?:^|\W)how\s+(?:wonderful|nice|great|lovely|exciting)\b.*?(?:\!|\?{2,})', |
|
|
|
|
|
|
|
|
r'(?i)(?:^|\W)thanks\s+for\s+(?:nothing|that|pointing|stating)\b', |
|
|
|
|
|
|
|
|
r'(?i)"(?:great|wonderful|excellent|perfect|amazing)"', |
|
|
|
|
|
|
|
|
r'(?i)^(?:yeah|sure|right|oh)\s+(?:right|sure|okay|ok)(?:\W|$)', |
|
|
|
|
|
|
|
|
r'(?i)\b(?:brilliant|genius|impressive)\b.*?(?:disaster|failure|mess)', |
|
|
|
|
|
|
|
|
r'(?i)\b(?:slightly|bit|little)\s+(?:catastrophic|disastrous|terrible|awful)\b', |
|
|
|
|
|
|
|
|
r'(?i)(?:^|\W)oh\s+(?:great|wonderful|perfect|fantastic|awesome)(?:\W|$)' |
|
|
] |
|
|
|
|
|
def tokenize_and_clean(text): |
|
|
"""Tokenize text using spaCy""" |
|
|
doc = nlp(text.lower().strip()) |
|
|
|
|
|
return [token.text for token in doc if token.is_alpha] |
|
|
|
|
|
def detect_phrases(text, emotion_phrases): |
|
|
"""Detect emotion-specific phrases in text""" |
|
|
text_lower = text.lower() |
|
|
detected_phrases = {} |
|
|
|
|
|
for emotion, phrases in emotion_phrases.items(): |
|
|
found_phrases = [] |
|
|
for phrase in phrases: |
|
|
if phrase.lower() in text_lower: |
|
|
found_phrases.append(phrase) |
|
|
|
|
|
if found_phrases: |
|
|
detected_phrases[emotion] = found_phrases |
|
|
|
|
|
return detected_phrases |
|
|
|
|
|
def detect_contextual_features(text): |
|
|
"""Detect contextual features in text that may influence emotion""" |
|
|
features = { |
|
|
'intensifiers': 0, |
|
|
'negators': 0, |
|
|
'hedges': 0, |
|
|
'boosters': 0, |
|
|
'exclamations': text.count('!'), |
|
|
'questions': text.count('?'), |
|
|
'ellipses': text.count('...'), |
|
|
'capitalized_words': len(re.findall(r'\b[A-Z]{2,}\b', text)) |
|
|
} |
|
|
|
|
|
doc = nlp(text.lower()) |
|
|
|
|
|
|
|
|
tokens = [token.text for token in doc] |
|
|
|
|
|
|
|
|
for indicator_type, words in CONTEXTUAL_INDICATORS.items(): |
|
|
if indicator_type != 'punctuation': |
|
|
for word in words: |
|
|
if ' ' in word: |
|
|
if word in text.lower(): |
|
|
features[indicator_type] += 1 |
|
|
else: |
|
|
features[indicator_type] += tokens.count(word) |
|
|
|
|
|
return features |
|
|
|
|
|
def detect_sarcasm_patterns(text): |
|
|
"""Detect linguistic patterns of sarcasm in text with context awareness""" |
|
|
|
|
|
matches = 0 |
|
|
pattern_matches = [] |
|
|
|
|
|
for pattern in SARCASM_PATTERNS: |
|
|
if re.search(pattern, text): |
|
|
matches += 1 |
|
|
pattern_matches.append(pattern) |
|
|
|
|
|
|
|
|
features = detect_contextual_features(text) |
|
|
|
|
|
|
|
|
phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']}) |
|
|
sarcasm_phrases = len(phrases.get('sarcasm', [])) |
|
|
|
|
|
|
|
|
raw_score = (matches * 0.15) + (sarcasm_phrases * 0.2) |
|
|
|
|
|
|
|
|
if features['exclamations'] > 1: |
|
|
raw_score += min(features['exclamations'] * 0.05, 0.2) |
|
|
|
|
|
if features['capitalized_words'] > 0: |
|
|
raw_score += min(features['capitalized_words'] * 0.1, 0.3) |
|
|
|
|
|
|
|
|
pos_neg_contrast = 0 |
|
|
emotion_phrases = detect_phrases(text, { |
|
|
'positive': EMOTION_PHRASES['joy'] + EMOTION_PHRASES['love'], |
|
|
'negative': EMOTION_PHRASES['sadness'] + EMOTION_PHRASES['anger'] |
|
|
}) |
|
|
|
|
|
if emotion_phrases.get('positive') and emotion_phrases.get('negative'): |
|
|
pos_neg_contrast = 0.3 |
|
|
|
|
|
|
|
|
raw_score += pos_neg_contrast |
|
|
|
|
|
|
|
|
return min(raw_score, 1.0), pattern_matches |
|
|
|
|
|
def calculate_emotion_similarity(text, emotion_keywords): |
|
|
"""Calculate similarity between text and emotion keywords using spaCy""" |
|
|
if not text.strip(): |
|
|
return 0.0 |
|
|
|
|
|
|
|
|
doc = nlp(text.lower()) |
|
|
|
|
|
|
|
|
keyword_scores = [] |
|
|
|
|
|
|
|
|
for keyword in emotion_keywords[:6]: |
|
|
keyword_doc = nlp(keyword) |
|
|
|
|
|
max_similarity = 0 |
|
|
for token in doc: |
|
|
if token.is_alpha and not token.is_stop: |
|
|
for keyword_token in keyword_doc: |
|
|
similarity = token.similarity(keyword_token) |
|
|
max_similarity = max(max_similarity, similarity) |
|
|
|
|
|
keyword_scores.append(max_similarity) |
|
|
|
|
|
|
|
|
if len(keyword_scores) >= 3: |
|
|
return sum(sorted(keyword_scores, reverse=True)[:3]) / 3 |
|
|
|
|
|
elif keyword_scores: |
|
|
return sum(keyword_scores) / len(keyword_scores) |
|
|
else: |
|
|
return 0.0 |
|
|
|
|
|
def get_emotion_score(text, emotion, keywords): |
|
|
"""Calculate emotion score based on similarity, context, and phrase detection""" |
|
|
|
|
|
similarity_score = calculate_emotion_similarity(text, keywords) |
|
|
|
|
|
|
|
|
detected_phrases = detect_phrases(text, {emotion: EMOTION_PHRASES[emotion]}) |
|
|
phrase_count = len(detected_phrases.get(emotion, [])) |
|
|
phrase_score = min(phrase_count * 0.2, 0.6) |
|
|
|
|
|
|
|
|
features = detect_contextual_features(text) |
|
|
|
|
|
|
|
|
feature_adjustment = 0 |
|
|
|
|
|
|
|
|
doc = nlp(text.lower()) |
|
|
direct_mention_score = 0 |
|
|
|
|
|
for token in doc: |
|
|
if token.lemma_ in keywords: |
|
|
direct_mention_score += 0.2 |
|
|
break |
|
|
|
|
|
|
|
|
if emotion in ['joy', 'love', 'surprise'] and features['exclamations'] > 0: |
|
|
feature_adjustment += min(features['exclamations'] * 0.05, 0.2) |
|
|
|
|
|
if emotion in ['anger', 'sadness'] and features['negators'] > 0: |
|
|
feature_adjustment += min(features['negators'] * 0.05, 0.2) |
|
|
|
|
|
if emotion == 'fear' and features['intensifiers'] > 0: |
|
|
feature_adjustment += min(features['intensifiers'] * 0.05, 0.2) |
|
|
|
|
|
|
|
|
final_score = (similarity_score * 0.5) + (phrase_score * 0.3) + (feature_adjustment * 0.1) + (direct_mention_score * 0.1) |
|
|
|
|
|
|
|
|
return max(0, min(final_score, 1.0)), detected_phrases.get(emotion, []) |
|
|
|
|
|
def analyze_sarcasm(text): |
|
|
"""Specialized analysis for sarcasm detection using spaCy and pattern matching""" |
|
|
|
|
|
sarcasm_keywords = EMOTION_CATEGORIES['sarcasm'] |
|
|
similarity_score = calculate_emotion_similarity(text, sarcasm_keywords) |
|
|
|
|
|
|
|
|
pattern_score, pattern_matches = detect_sarcasm_patterns(text) |
|
|
|
|
|
|
|
|
incongruity_score = 0 |
|
|
sentences = list(nlp(text).sents) |
|
|
|
|
|
if len(sentences) > 1: |
|
|
|
|
|
similarities = [] |
|
|
for i in range(len(sentences) - 1): |
|
|
sim = sentences[i].similarity(sentences[i+1]) |
|
|
similarities.append(sim) |
|
|
|
|
|
|
|
|
if similarities and min(similarities) < 0.5: |
|
|
incongruity_score = 0.3 |
|
|
|
|
|
|
|
|
detected_phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']}) |
|
|
phrase_score = min(len(detected_phrases.get('sarcasm', [])) * 0.2, 0.6) |
|
|
|
|
|
|
|
|
|
|
|
doc = nlp(text.lower()) |
|
|
|
|
|
|
|
|
pos_count = 0 |
|
|
neg_count = 0 |
|
|
|
|
|
for token in doc: |
|
|
if token.is_alpha and not token.is_stop: |
|
|
|
|
|
if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['joy'][:5]): |
|
|
pos_count += 1 |
|
|
if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['sadness'][:5] + EMOTION_CATEGORIES['anger'][:5]): |
|
|
neg_count += 1 |
|
|
|
|
|
contrast_score = 0 |
|
|
if pos_count > 0 and neg_count > 0: |
|
|
contrast_score = min(0.3, pos_count * neg_count * 0.05) |
|
|
|
|
|
|
|
|
combined_score = (0.15 * similarity_score) + (0.35 * pattern_score) + \ |
|
|
(0.15 * incongruity_score) + (0.25 * phrase_score) + \ |
|
|
(0.1 * contrast_score) |
|
|
|
|
|
|
|
|
return max(0, min(combined_score, 1.0)), detected_phrases.get('sarcasm', []), pattern_matches |
|
|
|
|
|
def determine_emotional_verdict(emotion_scores): |
|
|
"""Determine the emotional verdict based on the emotional profile""" |
|
|
|
|
|
sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True) |
|
|
|
|
|
|
|
|
emotions_over_35 = [e for e, s in sorted_emotions if s > 0.35] |
|
|
emotions_over_20 = [e for e, s in sorted_emotions if s > 0.20] |
|
|
emotions_over_15 = [e for e, s in sorted_emotions if s > 0.15] |
|
|
|
|
|
|
|
|
if emotions_over_35: |
|
|
dominant_emotion = emotions_over_35[0] |
|
|
for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items(): |
|
|
conditions = verdict_info['conditions'] |
|
|
|
|
|
if len(conditions) == 1 and isinstance(conditions[0], tuple): |
|
|
emotion, threshold = conditions[0] |
|
|
if emotion == dominant_emotion and emotion_scores[emotion] >= threshold: |
|
|
return verdict_info['label'], verdict_info['description'] |
|
|
|
|
|
|
|
|
for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items(): |
|
|
conditions = verdict_info['conditions'] |
|
|
|
|
|
|
|
|
if len(conditions) == 1 and isinstance(conditions[0], tuple): |
|
|
continue |
|
|
|
|
|
|
|
|
if conditions == ['multiple_over_15'] and len(emotions_over_15) >= 3: |
|
|
return verdict_info['label'], verdict_info['description'] |
|
|
|
|
|
if conditions == ['all_under_20'] and not emotions_over_20: |
|
|
return verdict_info['label'], verdict_info['description'] |
|
|
|
|
|
if conditions == ['all_under_15'] and not emotions_over_15: |
|
|
return verdict_info['label'], verdict_info['description'] |
|
|
|
|
|
|
|
|
if all(emotion_scores.get(emotion, 0) >= threshold for emotion, threshold in conditions): |
|
|
return verdict_info['label'], verdict_info['description'] |
|
|
|
|
|
|
|
|
if emotions_over_15: |
|
|
if len(emotions_over_15) == 1: |
|
|
|
|
|
emotion = emotions_over_15[0] |
|
|
return f"Moderately {emotion.capitalize()}", f"Shows some signs of {emotion}" |
|
|
else: |
|
|
|
|
|
primary = emotions_over_15[0].capitalize() |
|
|
secondary = emotions_over_15[1].capitalize() |
|
|
return f"{primary} with {secondary}", f"A mix of {primary.lower()} and {secondary.lower()} emotions" |
|
|
|
|
|
|
|
|
return "Neutral or Subtle", "No clear emotional signals detected" |
|
|
|
|
|
def analyze_emotions(text): |
|
|
"""Analyze emotions in text using spaCy with robust sarcasm detection and emotional verdict""" |
|
|
if not text or not text.strip(): |
|
|
return None, {"error": "Please enter some text to analyze"} |
|
|
|
|
|
try: |
|
|
|
|
|
emotion_data = {} |
|
|
|
|
|
|
|
|
for emotion, keywords in EMOTION_CATEGORIES.items(): |
|
|
if emotion == 'sarcasm': |
|
|
continue |
|
|
|
|
|
|
|
|
score, phrases = get_emotion_score(text, emotion, keywords) |
|
|
emotion_data[emotion] = { |
|
|
'score': score, |
|
|
'phrases': phrases |
|
|
} |
|
|
|
|
|
|
|
|
sarcasm_score, sarcasm_phrases, sarcasm_patterns = analyze_sarcasm(text) |
|
|
emotion_data['sarcasm'] = { |
|
|
'score': sarcasm_score, |
|
|
'phrases': sarcasm_phrases, |
|
|
'patterns': sarcasm_patterns |
|
|
} |
|
|
|
|
|
|
|
|
context_features = detect_contextual_features(text) |
|
|
|
|
|
|
|
|
|
|
|
emotion_scores = {emotion: data['score'] for emotion, data in emotion_data.items()} |
|
|
|
|
|
|
|
|
|
|
|
for emotion, data in emotion_data.items(): |
|
|
if len(data.get('phrases', [])) > 1: |
|
|
emotion_scores[emotion] = min(emotion_scores[emotion] * 1.2, 1.0) |
|
|
|
|
|
|
|
|
verdict_label, verdict_description = determine_emotional_verdict(emotion_scores) |
|
|
|
|
|
|
|
|
total_score = sum(emotion_scores.values()) or 1 |
|
|
emotion_percentages = {emotion: (score / total_score) * 100 for emotion, score in emotion_scores.items()} |
|
|
|
|
|
|
|
|
sorted_emotions = sorted(emotion_percentages.items(), key=lambda x: x[1], reverse=True) |
|
|
|
|
|
|
|
|
result = { |
|
|
'emotion_scores': sorted_emotions, |
|
|
'emotional_verdict': { |
|
|
'label': verdict_label, |
|
|
'description': verdict_description |
|
|
}, |
|
|
'top_emotions': sorted_emotions[:3], |
|
|
'supporting_evidence': { |
|
|
emotion: data.get('phrases', []) for emotion, data in emotion_data.items() if data.get('phrases') |
|
|
}, |
|
|
'context_features': context_features |
|
|
} |
|
|
|
|
|
return create_visualization(result), result |
|
|
|
|
|
except Exception as e: |
|
|
import traceback |
|
|
error_msg = traceback.format_exc() |
|
|
return None, {"error": f"Analysis error: {str(e)}", "details": error_msg} |
|
|
|
|
|
def create_visualization(result): |
|
|
"""Create visualization of emotion analysis results""" |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(12, 8)) |
|
|
|
|
|
|
|
|
emotions = [e[0] for e in result['emotion_scores']] |
|
|
scores = [e[1] for e in result['emotion_scores']] |
|
|
|
|
|
|
|
|
colors = [EMOTION_COLORS.get(emotion, '#CCCCCC') for emotion in emotions] |
|
|
|
|
|
|
|
|
bars = ax.barh(emotions, scores, color=colors) |
|
|
|
|
|
|
|
|
ax.set_xlim(0, 100) |
|
|
ax.set_xticks(range(0, 101, 10)) |
|
|
ax.set_xticklabels([f'{i}%' for i in range(0, 101, 10)]) |
|
|
|
|
|
|
|
|
ax.set_title('Emotion Analysis', fontsize=16, fontweight='bold') |
|
|
ax.set_ylabel('Emotions', fontsize=12) |
|
|
ax.set_xlabel('Score (%)', fontsize=12) |
|
|
|
|
|
|
|
|
verdict = result['emotional_verdict']['label'] |
|
|
description = result['emotional_verdict']['description'] |
|
|
ax.text(0.5, -0.2, f"Verdict: {verdict}", ha='center', transform=ax.transAxes, fontsize=14, fontweight='bold') |
|
|
ax.text(0.5, -0.27, f"{description}", ha='center', transform=ax.transAxes, fontsize=12) |
|
|
|
|
|
|
|
|
for bar in bars: |
|
|
width = bar.get_width() |
|
|
ax.text(width + 0.5, bar.get_y() + bar.get_height() / 2, |
|
|
f'{width:.1f}%', ha='left', va='center', fontsize=10) |
|
|
|
|
|
|
|
|
plt.tight_layout() |
|
|
plt.subplots_adjust(bottom=0.3) |
|
|
|
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def analyze_text(text): |
|
|
"""Analyze text and return visualization and detailed results""" |
|
|
fig, result = analyze_emotions(text) |
|
|
|
|
|
if 'error' in result: |
|
|
return None, result['error'] |
|
|
|
|
|
|
|
|
verdict = result['emotional_verdict']['label'] |
|
|
description = result['emotional_verdict']['description'] |
|
|
|
|
|
|
|
|
summary = f"## Emotional Analysis Results\n\n" |
|
|
summary += f"**Verdict:** {verdict}\n\n" |
|
|
summary += f"**Description:** {description}\n\n" |
|
|
|
|
|
summary += "### Top Emotions:\n" |
|
|
for emotion, score in result['top_emotions']: |
|
|
summary += f"- {emotion.capitalize()}: {score:.1f}%\n" |
|
|
|
|
|
if result['supporting_evidence']: |
|
|
summary += "\n### Supporting Evidence:\n" |
|
|
for emotion, phrases in result['supporting_evidence'].items(): |
|
|
if phrases: |
|
|
summary += f"- **{emotion.capitalize()}**: {', '.join(phrases)}\n" |
|
|
|
|
|
return fig, summary |
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
with gr.Blocks(title="Emotional Analysis Tool") as demo: |
|
|
gr.Markdown("# Advanced Emotion Analysis") |
|
|
gr.Markdown("Enter text to analyze the emotional content and receive a detailed breakdown.") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
text_input = gr.Textbox( |
|
|
label="Text to analyze", |
|
|
placeholder="Enter text here...", |
|
|
lines=10 |
|
|
) |
|
|
analyze_button = gr.Button("Analyze Emotions") |
|
|
|
|
|
with gr.Column(scale=3): |
|
|
with gr.Tab("Visualization"): |
|
|
plot_output = gr.Plot(label="Emotion Distribution") |
|
|
with gr.Tab("Summary"): |
|
|
text_output = gr.Markdown(label="Analysis Summary") |
|
|
|
|
|
analyze_button.click( |
|
|
fn=analyze_text, |
|
|
inputs=text_input, |
|
|
outputs=[plot_output, text_output] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
## About This Tool |
|
|
|
|
|
This advanced emotion analysis tool uses NLP techniques to detect and analyze emotions in text. |
|
|
It can identify: |
|
|
- 10 different emotions (joy, sadness, anger, fear, surprise, love, sarcasm, disgust, anticipation, trust) |
|
|
- Complex emotional combinations |
|
|
- Contextual features that affect emotional interpretation |
|
|
- Intelligent emotional verdicts for mixed emotion states |
|
|
|
|
|
The model uses word vectors, phrase detection, and contextual analysis for accurate emotion recognition. |
|
|
""") |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("Creating Gradio interface...") |
|
|
demo = create_interface() |
|
|
demo.launch(share=True) |
|
|
print("Gradio interface launched!") |
|
|
|