Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -11,6 +11,17 @@ from datetime import datetime
|
|
11 |
from torch.nn.functional import sigmoid
|
12 |
from collections import Counter
|
13 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
# Add this after imports
|
15 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
16 |
|
@@ -26,9 +37,6 @@ model_name = "SamanthaStorm/tether-multilabel-v4"
|
|
26 |
model = AutoModelForSequenceClassification.from_pretrained(model_name).to(device)
|
27 |
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)
|
28 |
|
29 |
-
# Tone model
|
30 |
-
tone_model = AutoModelForSequenceClassification.from_pretrained("SamanthaStorm/tone-tag-multilabel-v1").to(device)
|
31 |
-
tone_tokenizer = AutoTokenizer.from_pretrained("SamanthaStorm/tone-tag-multilabel-v1", use_fast=False)
|
32 |
|
33 |
# Sentiment model
|
34 |
sentiment_model = AutoModelForSequenceClassification.from_pretrained("SamanthaStorm/tether-sentiment").to(device)
|
@@ -126,6 +134,142 @@ THREAT_MOTIFS = [
|
|
126 |
"if you just behaved, this wouldn't happen", "this is your fault",
|
127 |
"you're making me hurt you", "i warned you", "you should have listened"
|
128 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
def predict_darvo_score(text):
|
131 |
"""Predict DARVO score for given text"""
|
@@ -161,19 +305,6 @@ def get_risk_stage(patterns, sentiment):
|
|
161 |
logger.error(f"Error determining risk stage: {e}")
|
162 |
return 1
|
163 |
|
164 |
-
def get_emotional_tone_tag(text, emotions, sentiment, patterns, abuse_score):
|
165 |
-
"""Get emotional tone tag for text"""
|
166 |
-
try:
|
167 |
-
inputs = tone_tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
168 |
-
inputs = {k: v.to(device) for k, v in inputs.items()}
|
169 |
-
with torch.no_grad():
|
170 |
-
logits = tone_model(**inputs).logits[0]
|
171 |
-
probs = torch.sigmoid(logits).cpu().numpy()
|
172 |
-
scores = dict(zip(TONE_LABELS, np.round(probs, 3)))
|
173 |
-
return max(scores, key=scores.get)
|
174 |
-
except Exception as e:
|
175 |
-
logger.error(f"Error in emotional tone analysis: {e}")
|
176 |
-
return "unknown"
|
177 |
|
178 |
@spaces.GPU
|
179 |
def compute_abuse_score(matched_scores, sentiment):
|
@@ -243,7 +374,16 @@ def analyze_single_message(text, thresholds):
|
|
243 |
explicit_abuse_words = ['fuck', 'bitch', 'shit', 'ass', 'dick']
|
244 |
explicit_abuse = any(word in text.lower() for word in explicit_abuse_words)
|
245 |
logger.debug(f"Explicit abuse detected: {explicit_abuse}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
|
|
|
|
|
247 |
# Abuse model inference
|
248 |
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
249 |
inputs = {k: v.to(device) for k, v in inputs.items()} # Move to GPU
|
|
|
11 |
from torch.nn.functional import sigmoid
|
12 |
from collections import Counter
|
13 |
import logging
|
14 |
+
from transformers import pipeline as hf_pipeline
|
15 |
+
|
16 |
+
# Add this with your other model loading code
|
17 |
+
emotion_pipeline = hf_pipeline(
|
18 |
+
"text-classification",
|
19 |
+
model="j-hartmann/emotion-english-distilroberta-base",
|
20 |
+
top_k=6,
|
21 |
+
truncation=True,
|
22 |
+
device=0 if torch.cuda.is_available() else -1 # GPU support
|
23 |
+
)
|
24 |
+
|
25 |
# Add this after imports
|
26 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
27 |
|
|
|
37 |
model = AutoModelForSequenceClassification.from_pretrained(model_name).to(device)
|
38 |
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)
|
39 |
|
|
|
|
|
|
|
40 |
|
41 |
# Sentiment model
|
42 |
sentiment_model = AutoModelForSequenceClassification.from_pretrained("SamanthaStorm/tether-sentiment").to(device)
|
|
|
134 |
"if you just behaved, this wouldn't happen", "this is your fault",
|
135 |
"you're making me hurt you", "i warned you", "you should have listened"
|
136 |
]
|
137 |
+
def get_emotion_profile(text):
|
138 |
+
emotions = emotion_pipeline(text)
|
139 |
+
if isinstance(emotions, list) and isinstance(emotions[0], list):
|
140 |
+
emotions = emotions[0]
|
141 |
+
return {e['label'].lower(): round(e['score'], 3) for e in emotions}
|
142 |
+
|
143 |
+
def get_emotional_tone_tag(text, sentiment, patterns, abuse_score):
|
144 |
+
# Get emotions first
|
145 |
+
emotions = get_emotion_profile(text)
|
146 |
+
|
147 |
+
sadness = emotions.get("sadness", 0)
|
148 |
+
joy = emotions.get("joy", 0)
|
149 |
+
neutral = emotions.get("neutral", 0)
|
150 |
+
disgust = emotions.get("disgust", 0)
|
151 |
+
anger = emotions.get("anger", 0)
|
152 |
+
fear = emotions.get("fear", 0)
|
153 |
+
|
154 |
+
# 1. Performative Regret
|
155 |
+
if (
|
156 |
+
sadness > 0.4 and
|
157 |
+
any(p in patterns for p in ["blame shifting", "guilt tripping", "recovery phase"]) and
|
158 |
+
(sentiment == "undermining" or abuse_score > 40)
|
159 |
+
):
|
160 |
+
return "performative regret"
|
161 |
+
|
162 |
+
# 2. Coercive Warmth
|
163 |
+
if (
|
164 |
+
(joy > 0.3 or sadness > 0.4) and
|
165 |
+
any(p in patterns for p in ["control", "gaslighting"]) and
|
166 |
+
sentiment == "undermining"
|
167 |
+
):
|
168 |
+
return "coercive warmth"
|
169 |
+
|
170 |
+
# 3. Cold Invalidation
|
171 |
+
if (
|
172 |
+
(neutral + disgust) > 0.5 and
|
173 |
+
any(p in patterns for p in ["dismissiveness", "projection", "obscure language"]) and
|
174 |
+
sentiment == "undermining"
|
175 |
+
):return "cold invalidation"
|
176 |
+
|
177 |
+
# 4. Genuine Vulnerability
|
178 |
+
if (
|
179 |
+
(sadness + fear) > 0.5 and
|
180 |
+
sentiment == "supportive" and
|
181 |
+
all(p in ["recovery phase"] for p in patterns)
|
182 |
+
):
|
183 |
+
return "genuine vulnerability"
|
184 |
+
|
185 |
+
# 5. Emotional Threat
|
186 |
+
if (
|
187 |
+
(anger + disgust) > 0.5 and
|
188 |
+
any(p in patterns for p in ["control", "insults", "dismissiveness"]) and
|
189 |
+
sentiment == "undermining"
|
190 |
+
):
|
191 |
+
return "emotional threat"
|
192 |
+
|
193 |
+
# 6. Weaponized Sadness
|
194 |
+
if (
|
195 |
+
sadness > 0.6 and
|
196 |
+
any(p in patterns for p in ["guilt tripping", "projection"]) and
|
197 |
+
sentiment == "undermining"
|
198 |
+
):
|
199 |
+
return "weaponized sadness"
|
200 |
+
|
201 |
+
# 7. Toxic Resignation
|
202 |
+
if (
|
203 |
+
neutral > 0.5 and
|
204 |
+
any(p in patterns for p in ["dismissiveness", "obscure language"]) and
|
205 |
+
sentiment == "undermining"
|
206 |
+
):
|
207 |
+
return "toxic resignation"
|
208 |
+
# 8. Aggressive Dismissal
|
209 |
+
if (
|
210 |
+
anger > 0.5 and
|
211 |
+
any(p in patterns for p in ["aggression", "insults", "control"]) and
|
212 |
+
sentiment == "undermining"
|
213 |
+
):
|
214 |
+
return "aggressive dismissal"
|
215 |
+
# 9. Deflective Hostility
|
216 |
+
if (
|
217 |
+
(0.2 < anger < 0.7 or 0.2 < disgust < 0.7) and
|
218 |
+
any(p in patterns for p in ["deflection", "projection"]) and
|
219 |
+
sentiment == "undermining"
|
220 |
+
):
|
221 |
+
return "deflective hostility"
|
222 |
+
# 10. Mocking Detachment
|
223 |
+
if (
|
224 |
+
(neutral + joy) > 0.5 and
|
225 |
+
any(p in patterns for p in ["mockery", "insults", "projection"]) and
|
226 |
+
sentiment == "undermining"
|
227 |
+
):
|
228 |
+
return "mocking detachment"
|
229 |
+
# 11. Contradictory Gaslight
|
230 |
+
if (
|
231 |
+
(joy + anger + sadness) > 0.5 and
|
232 |
+
any(p in patterns for p in ["gaslighting", "contradictory statements"]) and
|
233 |
+
sentiment == "undermining"
|
234 |
+
):
|
235 |
+
return "contradictory gaslight"
|
236 |
+
# 12. Calculated Neutrality
|
237 |
+
if (
|
238 |
+
neutral > 0.6 and
|
239 |
+
any(p in patterns for p in ["obscure language", "deflection", "dismissiveness"]) and
|
240 |
+
sentiment == "undermining"
|
241 |
+
):
|
242 |
+
return "calculated neutrality"
|
243 |
+
# 13. Forced Accountability Flip
|
244 |
+
if (
|
245 |
+
(anger + disgust) > 0.5 and
|
246 |
+
any(p in patterns for p in ["blame shifting", "manipulation", "projection"]) and
|
247 |
+
sentiment == "undermining"
|
248 |
+
):
|
249 |
+
return "forced accountability flip"
|
250 |
+
# 14. Conditional Affection
|
251 |
+
if (
|
252 |
+
joy > 0.4 and
|
253 |
+
any(p in patterns for p in ["apology baiting", "control", "recovery phase"]) and
|
254 |
+
sentiment == "undermining"
|
255 |
+
):
|
256 |
+
return "conditional affection"
|
257 |
+
|
258 |
+
if (
|
259 |
+
(anger + disgust) > 0.5 and
|
260 |
+
any(p in patterns for p in ["blame shifting", "projection", "deflection"]) and
|
261 |
+
sentiment == "undermining"
|
262 |
+
):
|
263 |
+
return "forced accountability flip"
|
264 |
+
|
265 |
+
# Emotional Instability Fallback
|
266 |
+
if (
|
267 |
+
(anger + sadness + disgust) > 0.6 and
|
268 |
+
sentiment == "undermining"
|
269 |
+
):
|
270 |
+
return "emotional instability"
|
271 |
+
|
272 |
+
return None
|
273 |
|
274 |
def predict_darvo_score(text):
|
275 |
"""Predict DARVO score for given text"""
|
|
|
305 |
logger.error(f"Error determining risk stage: {e}")
|
306 |
return 1
|
307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
308 |
|
309 |
@spaces.GPU
|
310 |
def compute_abuse_score(matched_scores, sentiment):
|
|
|
374 |
explicit_abuse_words = ['fuck', 'bitch', 'shit', 'ass', 'dick']
|
375 |
explicit_abuse = any(word in text.lower() for word in explicit_abuse_words)
|
376 |
logger.debug(f"Explicit abuse detected: {explicit_abuse}")
|
377 |
+
# Get sentiment
|
378 |
+
sent_inputs = sentiment_tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
379 |
+
sent_inputs = {k: v.to(device) for k, v in sent_inputs.items()}
|
380 |
+
with torch.no_grad():
|
381 |
+
sent_logits = sentiment_model(**sent_inputs).logits[0]
|
382 |
+
sent_probs = torch.softmax(sent_logits, dim=-1).cpu().numpy()
|
383 |
+
sentiment = SENTIMENT_LABELS[int(np.argmax(sent_probs))]
|
384 |
|
385 |
+
# Get tone using emotion-based approach
|
386 |
+
tone_tag = get_emotional_tone_tag(text, sentiment, threshold_labels, abuse_score)
|
387 |
# Abuse model inference
|
388 |
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
389 |
inputs = {k: v.to(device) for k, v in inputs.items()} # Move to GPU
|