Khaled12sszx commited on
Commit
fef8a0e
Β·
verified Β·
1 Parent(s): 720df43

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +4 -1014
app.py CHANGED
@@ -1,1020 +1,10 @@
1
  import gradio as gr
2
- from sentence_transformers import SentenceTransformer, util
3
- from transformers import (
4
- AutoTokenizer, AutoModelForSequenceClassification,
5
- GPT2LMHeadModel, pipeline
6
- )
7
- import torch
8
- import torch.nn.functional as F
9
- import plotly.graph_objects as go
10
- import plotly.express as px
11
- from plotly.subplots import make_subplots
12
- import numpy as np
13
- import re
14
- from collections import Counter
15
- import math
16
- import warnings
17
- from sklearn.linear_model import LogisticRegression
18
- from sklearn.preprocessing import StandardScaler
19
- from scipy.special import expit # sigmoid function
20
- warnings.filterwarnings("ignore")
21
-
22
- class AdvancedAIDetector:
23
- def __init__(self):
24
- print("Initializing AI Detector...")
25
-
26
- # Use only reliable, well-tested models that work in HF Spaces
27
- self.detectors = {}
28
- self.tokenizers = {}
29
-
30
- # Load primary AI detection model (known to work)
31
- try:
32
- self.detectors['roberta_ai_classifier'] = AutoModelForSequenceClassification.from_pretrained('roberta-base-openai-detector')
33
- self.tokenizers['roberta'] = AutoTokenizer.from_pretrained('roberta-base-openai-detector')
34
- print("βœ“ RoBERTa AI classifier loaded successfully")
35
- except Exception as e:
36
- print(f"βœ— Failed to load RoBERTa classifier: {e}")
37
- self.detectors['roberta_ai_classifier'] = None
38
-
39
- # Load secondary classifier (alternative)
40
- try:
41
- self.detectors['alternative_classifier'] = AutoModelForSequenceClassification.from_pretrained('martin-ha/toxic-comment-model')
42
- self.tokenizers['alternative'] = AutoTokenizer.from_pretrained('martin-ha/toxic-comment-model')
43
- print("βœ“ Alternative classifier loaded successfully")
44
- except Exception as e:
45
- print(f"βœ— Failed to load alternative classifier: {e}")
46
- self.detectors['alternative_classifier'] = None
47
-
48
- # Perplexity models - use only GPT-2 base to avoid memory issues
49
- self.perplexity_models = {}
50
- self.perplexity_tokenizers = {}
51
-
52
- try:
53
- self.perplexity_models['gpt2'] = GPT2LMHeadModel.from_pretrained("gpt2")
54
- self.perplexity_tokenizers['gpt2'] = AutoTokenizer.from_pretrained("gpt2")
55
- self.perplexity_models['gpt2'].eval()
56
- print("βœ“ GPT-2 perplexity model loaded successfully")
57
- except Exception as e:
58
- print(f"βœ— Failed to load GPT-2: {e}")
59
- self.perplexity_models['gpt2'] = None
60
-
61
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
62
- print(f"Using device: {self.device}")
63
-
64
- # Initialize ensemble classifier and scaler
65
- self.ensemble_classifier = None
66
- self.feature_scaler = None
67
- self._initialize_ensemble_classifier()
68
-
69
- def _initialize_ensemble_classifier(self):
70
- """Initialize a simple ensemble classifier for better confidence scoring"""
71
- try:
72
- # Create synthetic training data for the ensemble classifier
73
- # This is a simplified approach - in production, use real labeled data
74
- X_train = []
75
- y_train = []
76
-
77
- # Simulate AI-generated text features
78
- for _ in range(100):
79
- # AI-like features: high classifier score, low perplexity, low diversity
80
- classifier_score = np.random.normal(0.8, 0.1)
81
- perplexity_score = np.random.normal(0.3, 0.1)
82
- feature_score = np.random.normal(0.7, 0.1)
83
- X_train.append([classifier_score, perplexity_score, feature_score])
84
- y_train.append(1) # AI
85
-
86
- # Simulate human-written text features
87
- for _ in range(100):
88
- # Human-like features: low classifier score, high perplexity, high diversity
89
- classifier_score = np.random.normal(0.3, 0.1)
90
- perplexity_score = np.random.normal(0.7, 0.1)
91
- feature_score = np.random.normal(0.3, 0.1)
92
- X_train.append([classifier_score, perplexity_score, feature_score])
93
- y_train.append(0) # Human
94
-
95
- X_train = np.array(X_train)
96
- y_train = np.array(y_train)
97
-
98
- # Initialize and train the ensemble classifier
99
- self.feature_scaler = StandardScaler()
100
- X_train_scaled = self.feature_scaler.fit_transform(X_train)
101
-
102
- self.ensemble_classifier = LogisticRegression(random_state=42)
103
- self.ensemble_classifier.fit(X_train_scaled, y_train)
104
-
105
- print("βœ“ Ensemble classifier initialized successfully")
106
-
107
- except Exception as e:
108
- print(f"βœ— Failed to initialize ensemble classifier: {e}")
109
- self.ensemble_classifier = None
110
- self.feature_scaler = None
111
-
112
- def calculate_perplexity(self, text, max_length=512):
113
- """Calculate perplexity with robust error handling"""
114
- if 'gpt2' not in self.perplexity_models or not self.perplexity_models['gpt2']:
115
- return float('inf')
116
-
117
- try:
118
- model = self.perplexity_models['gpt2']
119
- tokenizer = self.perplexity_tokenizers['gpt2']
120
-
121
- # Truncate text to avoid memory issues
122
- words = text.split()
123
- if len(words) > max_length // 4: # Rough word-to-token ratio
124
- text = ' '.join(words[:max_length // 4])
125
-
126
- # Add padding token if it doesn't exist
127
- if tokenizer.pad_token is None:
128
- tokenizer.pad_token = tokenizer.eos_token
129
-
130
- encodings = tokenizer(text, return_tensors='pt', truncation=True,
131
- max_length=max_length, padding=True)
132
- input_ids = encodings.input_ids
133
-
134
- # Move to device
135
- model.to(self.device)
136
- input_ids = input_ids.to(self.device)
137
-
138
- with torch.no_grad():
139
- outputs = model(input_ids, labels=input_ids)
140
- loss = outputs.loss
141
- perplexity = torch.exp(loss).item()
142
-
143
- return perplexity if not math.isnan(perplexity) else float('inf')
144
-
145
- except Exception as e:
146
- print(f"Perplexity calculation error: {e}")
147
- return float('inf')
148
-
149
- def improved_perplexity_to_probability(self, perplexity):
150
- """Convert perplexity to AI probability using calibrated sigmoid function"""
151
- if math.isinf(perplexity) or math.isnan(perplexity):
152
- return 0.5
153
-
154
- try:
155
- # Calibrated sigmoid transformation based on empirical data
156
- # These parameters were tuned for better performance
157
- midpoint = 30.0 # Perplexity value that corresponds to 50% probability
158
- steepness = -0.1 # Controls the steepness of the sigmoid
159
-
160
- # Apply sigmoid transformation
161
- sigmoid_input = steepness * (perplexity - midpoint)
162
- probability = expit(sigmoid_input) # More stable than manual sigmoid
163
-
164
- # Ensure reasonable bounds
165
- return max(0.05, min(0.95, probability))
166
-
167
- except Exception as e:
168
- print(f"Perplexity conversion error: {e}")
169
- return 0.5
170
-
171
- def extract_linguistic_features(self, text):
172
- """Extract linguistic features with robust error handling"""
173
- try:
174
- features = {}
175
-
176
- # Basic text statistics
177
- sentences = re.split(r'[.!?]+', text.strip())
178
- sentences = [s.strip() for s in sentences if s.strip()]
179
- words = text.split()
180
-
181
- # Safe calculations with fallbacks
182
- features['sentence_count'] = len(sentences)
183
- features['word_count'] = len(words)
184
-
185
- if sentences:
186
- sentence_lengths = [len(s.split()) for s in sentences]
187
- features['avg_sentence_length'] = np.mean(sentence_lengths)
188
- features['sentence_length_std'] = np.std(sentence_lengths) if len(sentences) > 1 else 0
189
- else:
190
- features['avg_sentence_length'] = 0
191
- features['sentence_length_std'] = 0
192
-
193
- # Lexical diversity
194
- if words:
195
- unique_words = set(word.lower() for word in words if word.isalpha())
196
- features['lexical_diversity'] = len(unique_words) / len(words)
197
- features['avg_word_length'] = np.mean([len(word) for word in words])
198
- else:
199
- features['lexical_diversity'] = 0
200
- features['avg_word_length'] = 0
201
-
202
- # Word frequency analysis (burstiness)
203
- alpha_words = [word.lower() for word in words if word.isalpha()]
204
- if len(alpha_words) > 1:
205
- word_freq = Counter(alpha_words)
206
- frequencies = list(word_freq.values())
207
- mean_freq = np.mean(frequencies)
208
- features['burstiness'] = np.var(frequencies) / mean_freq if mean_freq > 0 else 0
209
- else:
210
- features['burstiness'] = 0
211
-
212
- # Repetition patterns
213
- if len(words) > 1:
214
- bigrams = [' '.join(words[i:i+2]) for i in range(len(words)-1)]
215
- features['bigram_repetition'] = 1 - len(set(bigrams)) / len(bigrams) if bigrams else 0
216
- else:
217
- features['bigram_repetition'] = 0
218
-
219
- # Punctuation analysis
220
- if text:
221
- punct_count = len(re.findall(r'[.!?,:;]', text))
222
- features['punctuation_ratio'] = punct_count / len(text)
223
- else:
224
- features['punctuation_ratio'] = 0
225
-
226
- # Sentence start diversity
227
- if len(sentences) > 1:
228
- sentence_starts = []
229
- for s in sentences:
230
- words_in_sentence = s.split()
231
- if words_in_sentence:
232
- sentence_starts.append(words_in_sentence[0].lower())
233
-
234
- if sentence_starts:
235
- features['sentence_start_diversity'] = len(set(sentence_starts)) / len(sentence_starts)
236
- else:
237
- features['sentence_start_diversity'] = 1
238
- else:
239
- features['sentence_start_diversity'] = 1
240
-
241
- return features
242
-
243
- except Exception as e:
244
- print(f"Feature extraction error: {e}")
245
- # Return default features if extraction fails
246
- return {
247
- 'sentence_count': 1,
248
- 'word_count': len(text.split()) if text else 0,
249
- 'avg_sentence_length': len(text.split()) if text else 0,
250
- 'sentence_length_std': 0,
251
- 'lexical_diversity': 0.5,
252
- 'avg_word_length': 5,
253
- 'burstiness': 0.5,
254
- 'bigram_repetition': 0,
255
- 'punctuation_ratio': 0.05,
256
- 'sentence_start_diversity': 1
257
- }
258
-
259
- def run_classifier_detection(self, text):
260
- """Run classifier-based detection with proper error handling"""
261
- classifier_results = {}
262
-
263
- # Try RoBERTa classifier
264
- if self.detectors.get('roberta_ai_classifier') and self.tokenizers.get('roberta'):
265
- try:
266
- model = self.detectors['roberta_ai_classifier']
267
- tokenizer = self.tokenizers['roberta']
268
-
269
- inputs = tokenizer(text, return_tensors="pt", truncation=True,
270
- padding=True, max_length=512)
271
-
272
- # Move to device
273
- model.to(self.device)
274
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
275
-
276
- with torch.no_grad():
277
- outputs = model(**inputs)
278
- probs = torch.softmax(outputs.logits, dim=1)[0]
279
-
280
- # Handle different output formats
281
- if len(probs) >= 2:
282
- ai_prob = probs[1].item() # AI probability
283
- human_prob = probs[0].item() # Human probability
284
- else:
285
- ai_prob = probs[0].item()
286
- human_prob = 1 - ai_prob
287
-
288
- classifier_results['roberta_ai_prob'] = ai_prob
289
- classifier_results['roberta_human_prob'] = human_prob
290
-
291
- except Exception as e:
292
- print(f"RoBERTa classifier error: {e}")
293
-
294
- # If no classifiers worked, provide fallback
295
- if not classifier_results:
296
- # Simple heuristic fallback based on text characteristics
297
- perplexity = self.calculate_perplexity(text)
298
- if not math.isinf(perplexity):
299
- # Use improved perplexity conversion
300
- ai_prob = self.improved_perplexity_to_probability(perplexity)
301
- else:
302
- ai_prob = 0.5 # Neutral when we can't determine
303
-
304
- classifier_results['fallback_ai_prob'] = ai_prob
305
- classifier_results['fallback_human_prob'] = 1 - ai_prob
306
-
307
- return classifier_results
308
-
309
- def ensemble_ai_detection(self, text):
310
- """Main ensemble detection method with enhanced confidence scoring"""
311
- try:
312
- results = {}
313
-
314
- # 1. Classifier predictions
315
- classifier_results = self.run_classifier_detection(text)
316
- results.update(classifier_results)
317
-
318
- # Extract AI probabilities for ensemble
319
- ai_probs = [v for k, v in classifier_results.items() if '_ai_prob' in k]
320
- avg_classifier_score = np.mean(ai_probs) if ai_probs else 0.5
321
-
322
- # 2. Perplexity analysis with improved conversion
323
- perplexity = self.calculate_perplexity(text)
324
- results['gpt2_perplexity'] = round(perplexity, 2) if not math.isinf(perplexity) else 999
325
-
326
- # Use improved perplexity to probability conversion
327
- perplexity_score = self.improved_perplexity_to_probability(perplexity)
328
-
329
- # 3. Linguistic features
330
- features = self.extract_linguistic_features(text)
331
- results.update({f'feature_{k}': round(v, 4) for k, v in features.items()})
332
-
333
- # Calculate feature-based score
334
- feature_score = self.calculate_feature_score(features)
335
-
336
- # 4. Enhanced ensemble scoring
337
- if self.ensemble_classifier and self.feature_scaler:
338
- # Use trained ensemble classifier for better confidence
339
- try:
340
- feature_vector = np.array([[avg_classifier_score, perplexity_score, feature_score]])
341
- feature_vector_scaled = self.feature_scaler.transform(feature_vector)
342
-
343
- ensemble_score = self.ensemble_classifier.predict_proba(feature_vector_scaled)[0][1]
344
- confidence = max(self.ensemble_classifier.predict_proba(feature_vector_scaled)[0]) * 0.9
345
-
346
- except Exception as e:
347
- print(f"Ensemble classifier error: {e}")
348
- # Fallback to weighted average
349
- ensemble_score = (
350
- avg_classifier_score * 0.5 + # 50% classifier
351
- perplexity_score * 0.3 + # 30% perplexity
352
- feature_score * 0.2 # 20% features
353
- )
354
- confidence = 0.7 # Default confidence
355
- else:
356
- # Fallback to weighted average with improved weights
357
- ensemble_score = (
358
- avg_classifier_score * 0.5 + # 50% classifier
359
- perplexity_score * 0.3 + # 30% perplexity
360
- feature_score * 0.2 # 20% features
361
- )
362
-
363
- # Calculate confidence based on score consistency
364
- scores = [avg_classifier_score, perplexity_score, feature_score]
365
- score_std = np.std(scores)
366
- confidence = max(0.6, min(0.9, 1.0 - score_std))
367
-
368
- # 5. Generate verdict with improved logic
369
- verdict = self.get_enhanced_verdict(ensemble_score, confidence)
370
-
371
- results['ensemble_score'] = round(ensemble_score, 4)
372
- results['final_verdict'] = verdict
373
- results['confidence'] = f"{confidence:.1%}"
374
-
375
- return results
376
-
377
- except Exception as e:
378
- print(f"Ensemble detection error: {e}")
379
- # Return safe fallback results
380
- return {
381
- 'ensemble_score': 0.5,
382
- 'final_verdict': 'Error - Unable to analyze',
383
- 'confidence': '0.0%',
384
- 'error': str(e)
385
- }
386
-
387
- def calculate_feature_score(self, features):
388
- """Calculate AI probability from linguistic features with improved logic"""
389
- try:
390
- ai_indicators = 0
391
- total_indicators = 0
392
-
393
- # Enhanced feature analysis with better thresholds
394
-
395
- # Low lexical diversity suggests AI (more strict threshold)
396
- if features.get('lexical_diversity', 0.5) < 0.35:
397
- ai_indicators += 2 # Weighted higher
398
- elif features.get('lexical_diversity', 0.5) < 0.5:
399
- ai_indicators += 1
400
- total_indicators += 2
401
-
402
- # Low sentence start diversity suggests AI
403
- if features.get('sentence_start_diversity', 1) < 0.7:
404
- ai_indicators += 1
405
- total_indicators += 1
406
-
407
- # Low burstiness suggests AI (refined threshold)
408
- burstiness = features.get('burstiness', 1)
409
- if burstiness < 0.3:
410
- ai_indicators += 2
411
- elif burstiness < 0.6:
412
- ai_indicators += 1
413
- total_indicators += 2
414
-
415
- # Very consistent sentence lengths suggest AI
416
- sentence_std = features.get('sentence_length_std', 10)
417
- if sentence_std < 2:
418
- ai_indicators += 2
419
- elif sentence_std < 5:
420
- ai_indicators += 1
421
- total_indicators += 2
422
-
423
- # High bigram repetition suggests AI
424
- if features.get('bigram_repetition', 0) > 0.3:
425
- ai_indicators += 1
426
- total_indicators += 1
427
-
428
- return ai_indicators / total_indicators if total_indicators > 0 else 0.5
429
-
430
- except Exception as e:
431
- print(f"Feature score calculation error: {e}")
432
- return 0.5
433
-
434
- def get_enhanced_verdict(self, ensemble_score, confidence):
435
- """Generate verdict with improved thresholds and confidence consideration"""
436
- try:
437
- # Adjust thresholds based on confidence level
438
- high_conf_threshold = 0.8
439
- medium_conf_threshold = 0.6
440
-
441
- if confidence > high_conf_threshold:
442
- # High confidence - use stricter thresholds
443
- if ensemble_score > 0.75:
444
- return "Highly Likely AI-Generated"
445
- elif ensemble_score > 0.6:
446
- return "Likely AI-Generated"
447
- elif ensemble_score > 0.4:
448
- return "Possibly AI-Generated"
449
- elif ensemble_score > 0.25:
450
- return "Likely Human-Written"
451
- else:
452
- return "Highly Likely Human-Written"
453
-
454
- elif confidence > medium_conf_threshold:
455
- # Medium confidence - moderate thresholds
456
- if ensemble_score > 0.7:
457
- return "Likely AI-Generated"
458
- elif ensemble_score > 0.55:
459
- return "Possibly AI-Generated"
460
- elif ensemble_score > 0.45:
461
- return "Unclear - Manual Review Recommended"
462
- elif ensemble_score > 0.3:
463
- return "Possibly Human-Written"
464
- else:
465
- return "Likely Human-Written"
466
- else:
467
- # Low confidence - conservative approach
468
- if ensemble_score > 0.8:
469
- return "Possibly AI-Generated"
470
- elif ensemble_score > 0.2:
471
- return "Unclear - Manual Review Recommended"
472
- else:
473
- return "Possibly Human-Written"
474
-
475
- except Exception as e:
476
- print(f"Verdict generation error: {e}")
477
- return "Error in Analysis"
478
-
479
- # Enhanced Semantic Similarity System with Antonym Detection
480
- class AdvancedSimilarityDetector:
481
- def __init__(self):
482
- print("Initializing Similarity Detector...")
483
- self.models = {}
484
-
485
- # Common antonym pairs for penalty detection
486
- self.antonym_pairs = {
487
- 'excellent': ['terrible', 'awful', 'horrible', 'bad', 'poor'],
488
- 'good': ['bad', 'terrible', 'awful', 'horrible', 'poor'],
489
- 'great': ['terrible', 'awful', 'horrible', 'bad', 'poor'],
490
- 'fast': ['slow', 'sluggish', 'gradual'],
491
- 'quick': ['slow', 'sluggish', 'gradual'],
492
- 'efficient': ['inefficient', 'slow', 'sluggish'],
493
- 'high': ['low', 'small', 'little'],
494
- 'large': ['small', 'tiny', 'little'],
495
- 'big': ['small', 'tiny', 'little'],
496
- 'hot': ['cold', 'freezing', 'cool'],
497
- 'warm': ['cold', 'freezing', 'cool'],
498
- 'bright': ['dark', 'dim', 'dull'],
499
- 'light': ['dark', 'heavy'],
500
- 'easy': ['hard', 'difficult', 'challenging'],
501
- 'simple': ['complex', 'complicated', 'difficult'],
502
- 'happy': ['sad', 'unhappy', 'miserable'],
503
- 'positive': ['negative', 'bad'],
504
- 'love': ['hate', 'dislike'],
505
- 'like': ['dislike', 'hate'],
506
- 'beautiful': ['ugly', 'hideous'],
507
- 'strong': ['weak', 'fragile'],
508
- 'rich': ['poor', 'broke'],
509
- 'smart': ['stupid', 'dumb', 'ignorant'],
510
- 'clean': ['dirty', 'messy', 'filthy'],
511
- 'safe': ['dangerous', 'risky', 'unsafe'],
512
- 'healthy': ['unhealthy', 'sick'],
513
- 'new': ['old', 'ancient', 'outdated'],
514
- 'modern': ['old', 'ancient', 'outdated'],
515
- 'young': ['old', 'elderly', 'aged'],
516
- 'early': ['late', 'delayed'],
517
- 'first': ['last', 'final'],
518
- 'begin': ['end', 'finish', 'conclude'],
519
- 'start': ['end', 'finish', 'stop'],
520
- 'open': ['closed', 'shut'],
521
- 'win': ['lose', 'fail'],
522
- 'success': ['failure', 'defeat'],
523
- 'increase': ['decrease', 'reduce', 'lower'],
524
- 'more': ['less', 'fewer'],
525
- 'always': ['never', 'rarely'],
526
- 'all': ['none', 'nothing'],
527
- 'yes': ['no'],
528
- 'agree': ['disagree', 'oppose'],
529
- 'accept': ['reject', 'refuse', 'deny'],
530
- 'include': ['exclude', 'omit'],
531
- 'remember': ['forget']
532
- }
533
-
534
- # Load primary model
535
- try:
536
- self.models['sentence_bert'] = SentenceTransformer('all-mpnet-base-v2')
537
- print("βœ“ Sentence-BERT loaded successfully")
538
- except Exception as e:
539
- print(f"βœ— Failed to load Sentence-BERT: {e}")
540
- # Fallback to smaller model
541
- try:
542
- self.models['sentence_bert'] = SentenceTransformer('all-MiniLM-L6-v2')
543
- print("βœ“ Fallback model loaded successfully")
544
- except Exception as e2:
545
- print(f"βœ— Failed to load fallback model: {e2}")
546
-
547
- # Load secondary model if resources allow
548
- try:
549
- self.models['multilingual'] = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
550
- print("βœ“ Multilingual model loaded successfully")
551
- except Exception as e:
552
- print(f"βœ— Multilingual model not loaded: {e}")
553
-
554
- def detect_antonym_penalty(self, text1, text2):
555
- """Detect antonym pairs and calculate penalty"""
556
- try:
557
- # Tokenize and clean texts
558
- words1 = set(re.findall(r'\b\w+\b', text1.lower()))
559
- words2 = set(re.findall(r'\b\w+\b', text2.lower()))
560
-
561
- penalty = 0.0
562
- antonym_found = False
563
- detected_pairs = []
564
-
565
- # Check for antonym pairs
566
- for word1 in words1:
567
- if word1 in self.antonym_pairs:
568
- antonyms = self.antonym_pairs[word1]
569
- for antonym in antonyms:
570
- if antonym in words2:
571
- # Check context similarity (simple approach)
572
- context_penalty = self.calculate_context_penalty(text1, text2, word1, antonym)
573
- penalty += context_penalty
574
- antonym_found = True
575
- detected_pairs.append((word1, antonym))
576
-
577
- # Also check reverse direction
578
- for word2 in words2:
579
- if word2 in self.antonym_pairs:
580
- antonyms = self.antonym_pairs[word2]
581
- for antonym in antonyms:
582
- if antonym in words1:
583
- # Avoid double counting
584
- if (antonym, word2) not in detected_pairs and (word2, antonym) not in detected_pairs:
585
- context_penalty = self.calculate_context_penalty(text1, text2, antonym, word2)
586
- penalty += context_penalty
587
- antonym_found = True
588
- detected_pairs.append((antonym, word2))
589
-
590
- return penalty, antonym_found, detected_pairs
591
-
592
- except Exception as e:
593
- print(f"Antonym detection error: {e}")
594
- return 0.0, False, []
595
-
596
- def calculate_context_penalty(self, text1, text2, word1, word2):
597
- """Calculate penalty based on context similarity around antonym pairs"""
598
- try:
599
- # Simple context analysis - check if surrounding words are similar
600
- def get_context(text, target_word, window=3):
601
- words = re.findall(r'\b\w+\b', text.lower())
602
- try:
603
- idx = words.index(target_word)
604
- start = max(0, idx - window)
605
- end = min(len(words), idx + window + 1)
606
- return set(words[start:end]) - {target_word}
607
- except ValueError:
608
- return set()
609
-
610
- context1 = get_context(text1, word1)
611
- context2 = get_context(text2, word2)
612
-
613
- if context1 and context2:
614
- # Calculate Jaccard similarity of contexts
615
- intersection = len(context1.intersection(context2))
616
- union = len(context1.union(context2))
617
- context_similarity = intersection / union if union > 0 else 0
618
-
619
- # Higher context similarity means higher penalty
620
- # Base penalty of 0.3, scaled by context similarity
621
- penalty = 0.3 + (context_similarity * 0.4)
622
- return min(penalty, 0.7) # Cap at 0.7
623
- else:
624
- # Default penalty when context can't be analyzed
625
- return 0.3
626
-
627
- except Exception as e:
628
- print(f"Context penalty calculation error: {e}")
629
- return 0.3
630
-
631
- def calculate_multi_metric_similarity(self, text1, text2, threshold=0.4):
632
- """Calculate similarity with antonym detection and penalty"""
633
- try:
634
- results = {}
635
- similarity_scores = []
636
-
637
- # 1. Detect antonyms first
638
- antonym_penalty, antonym_found, detected_pairs = self.detect_antonym_penalty(text1, text2)
639
- results['antonym_penalty'] = round(antonym_penalty, 4)
640
- results['antonym_detected'] = antonym_found
641
- if detected_pairs:
642
- results['detected_antonym_pairs'] = detected_pairs
643
-
644
- # 2. Calculate embeddings and similarities
645
- for name, model in self.models.items():
646
- if model:
647
- try:
648
- emb1, emb2 = model.encode([text1, text2], convert_to_tensor=True)
649
-
650
- # Cosine similarity
651
- cos_sim = util.pytorch_cos_sim(emb1, emb2).item()
652
-
653
- # Apply antonym penalty specifically to sentence_bert model
654
- if name == 'sentence_bert' and antonym_found:
655
- cos_sim_adjusted = max(0.0, cos_sim - antonym_penalty)
656
- results[f'{name}_cosine_original'] = round(cos_sim, 4)
657
- results[f'{name}_cosine'] = round(cos_sim_adjusted, 4)
658
- results[f'{name}_penalty_applied'] = round(antonym_penalty, 4)
659
- similarity_scores.append(cos_sim_adjusted)
660
- else:
661
- results[f'{name}_cosine'] = round(cos_sim, 4)
662
- similarity_scores.append(cos_sim)
663
-
664
- # Additional metrics
665
- dot_sim = torch.dot(emb1, emb2).item()
666
- results[f'{name}_dot_product'] = round(dot_sim, 4)
667
-
668
- euclidean_dist = torch.dist(emb1, emb2).item()
669
- euclidean_sim = 1 / (1 + euclidean_dist)
670
- results[f'{name}_euclidean_sim'] = round(euclidean_sim, 4)
671
-
672
- except Exception as e:
673
- print(f"Error with {name}: {e}")
674
- continue
675
-
676
- # 3. Enhanced ensemble score calculation
677
- if similarity_scores:
678
- # Weight the sentence_bert model higher if no antonyms detected
679
- # Weight multilingual model higher if antonyms are detected
680
- if len(similarity_scores) >= 2 and antonym_found:
681
- # When antonyms detected, trust multilingual model more
682
- weights = [0.4, 0.6] # sentence_bert, multilingual
683
- else:
684
- # Normal case, trust sentence_bert more
685
- weights = [0.6, 0.4] if len(similarity_scores) >= 2 else [1.0]
686
-
687
- # Ensure weights match number of scores
688
- weights = weights[:len(similarity_scores)]
689
- if len(weights) < len(similarity_scores):
690
- weights.extend([1.0] * (len(similarity_scores) - len(weights)))
691
-
692
- # Normalize weights
693
- weight_sum = sum(weights)
694
- weights = [w / weight_sum for w in weights]
695
-
696
- ensemble_score = sum(score * weight for score, weight in zip(similarity_scores, weights))
697
- else:
698
- ensemble_score = 0
699
-
700
- results['ensemble_similarity'] = round(ensemble_score, 4)
701
-
702
- # 4. Enhanced interpretation
703
- interpretation = self.get_enhanced_interpretation(ensemble_score, threshold, antonym_found)
704
- results['interpretation'] = interpretation
705
-
706
- return results, ensemble_score
707
-
708
- except Exception as e:
709
- print(f"Similarity calculation error: {e}")
710
- return {"error": str(e)}, 0
711
-
712
- def get_enhanced_interpretation(self, score, threshold, antonym_detected=False):
713
- """Generate interpretation with antonym consideration"""
714
- try:
715
- base_interpretation = ""
716
-
717
- if score > 0.90:
718
- base_interpretation = "Nearly Identical (Potential Direct Copy)"
719
- elif score > 0.80:
720
- base_interpretation = "Very High Similarity (Likely Plagiarism)"
721
- elif score > 0.70:
722
- base_interpretation = "High Similarity (Suspicious - Needs Review)"
723
- elif score > threshold:
724
- base_interpretation = "Moderate Similarity (Possible Paraphrasing)"
725
- elif score > 0.2:
726
- base_interpretation = "Low Similarity (Different Content)"
727
- else:
728
- base_interpretation = "Very Low Similarity (Unrelated Content)"
729
-
730
- # Add antonym context if detected
731
- if antonym_detected:
732
- base_interpretation += " - Antonym penalty applied due to opposing meanings"
733
-
734
- return base_interpretation
735
-
736
- except:
737
- return "Unable to interpret similarity"
738
 
739
- # Initialize detectors with error handling
740
- try:
741
- ai_detector = AdvancedAIDetector()
742
- similarity_detector = AdvancedSimilarityDetector()
743
- print("βœ“ All detectors initialized successfully")
744
- except Exception as e:
745
- print(f"βœ— Detector initialization error: {e}")
746
- ai_detector = None
747
- similarity_detector = None
748
-
749
- # Enhanced Gradio functions with comprehensive error handling
750
- def enhanced_similarity_check(text1, text2, threshold):
751
- """Enhanced similarity checking with antonym detection"""
752
- try:
753
- if not text1.strip() or not text2.strip():
754
- return {"error": "Please provide both texts"}, "Error: Empty text provided", go.Figure()
755
-
756
- if not similarity_detector:
757
- return {"error": "Similarity detector not available"}, "Error: Detector initialization failed", go.Figure()
758
-
759
- results, ensemble_score = similarity_detector.calculate_multi_metric_similarity(text1, text2, threshold)
760
-
761
- if "error" in results:
762
- return results, f"Error: {results['error']}", go.Figure()
763
-
764
- explanation = f"""
765
- ## Enhanced Similarity Analysis Results
766
-
767
- **Ensemble Similarity Score:** {results.get('ensemble_similarity', 'N/A')} ({results.get('interpretation', 'N/A')})
768
-
769
- ### Antonym Detection:
770
- - **Antonyms Detected:** {'Yes' if results.get('antonym_detected', False) else 'No'}
771
- - **Penalty Applied:** {results.get('antonym_penalty', 0.0)}
772
- """
773
-
774
- if results.get('detected_antonym_pairs'):
775
- explanation += f"- **Detected Pairs:** {', '.join([f'{p[0]}↔{p[1]}' for p in results['detected_antonym_pairs']])}\n"
776
-
777
- explanation += f"""
778
- ### Individual Model Scores:
779
- """
780
-
781
- for key, value in results.items():
782
- if '_cosine' in key and not key.endswith('_original'):
783
- model_name = key.replace('_cosine', '').replace('_', ' ').title()
784
- explanation += f"- **{model_name}:** {value}\n"
785
-
786
- # Show original score if penalty was applied
787
- original_key = key + '_original'
788
- if original_key in results:
789
- explanation += f" - Original Score: {results[original_key]}\n"
790
- explanation += f" - Penalty Applied: {results.get(key.replace('_cosine', '_penalty_applied'), 0)}\n"
791
-
792
- explanation += f"""
793
- **Threshold:** {threshold}
794
- **Analysis:** Enhanced multi-model ensemble with antonym detection and context-aware penalties.
795
- """
796
-
797
- # Create enhanced visualization
798
- fig = make_subplots(
799
- rows=1, cols=2,
800
- subplot_titles=('Similarity Score', 'Model Comparison'),
801
- specs=[[{"type": "indicator"}, {"type": "bar"}]]
802
- )
803
-
804
- # Gauge chart
805
- fig.add_trace(go.Indicator(
806
- mode="gauge+number",
807
- value=ensemble_score,
808
- domain={'x': [0, 1], 'y': [0, 1]},
809
- title={'text': "Ensemble Score"},
810
- gauge={
811
- 'axis': {'range': [None, 1]},
812
- 'bar': {'color': "darkblue"},
813
- 'steps': [
814
- {'range': [0, threshold], 'color': "lightgray"},
815
- {'range': [threshold, 0.7], 'color': "yellow"},
816
- {'range': [0.7, 0.9], 'color': "orange"},
817
- {'range': [0.9, 1], 'color': "red"}],
818
- 'threshold': {
819
- 'line': {'color': "red", 'width': 4},
820
- 'thickness': 0.75,
821
- 'value': threshold}}), row=1, col=1)
822
-
823
- # Model comparison bar chart
824
- model_names = []
825
- model_scores = []
826
- for key, value in results.items():
827
- if '_cosine' in key and not key.endswith('_original'):
828
- model_name = key.replace('_cosine', '').replace('_', ' ').title()
829
- model_names.append(model_name)
830
- model_scores.append(value)
831
-
832
- if model_names and model_scores:
833
- colors = ['red' if results.get('antonym_detected') and 'Sentence Bert' in name else 'blue' for name in model_names]
834
- fig.add_trace(go.Bar(
835
- x=model_names,
836
- y=model_scores,
837
- marker_color=colors,
838
- name="Model Scores"
839
- ), row=1, col=2)
840
-
841
- fig.update_layout(height=400, showlegend=False)
842
-
843
- return results, explanation, fig
844
-
845
- except Exception as e:
846
- error_msg = f"Unexpected error in similarity check: {str(e)}"
847
- return {"error": error_msg}, error_msg, go.Figure()
848
-
849
- def enhanced_ai_detection(text):
850
- """Enhanced AI detection with improved confidence scoring"""
851
- try:
852
- if not text.strip():
853
- return {"error": "Please provide text to analyze"}, "Error: Empty text provided", go.Figure()
854
-
855
- if not ai_detector:
856
- return {"error": "AI detector not available"}, "Error: Detector initialization failed", go.Figure()
857
-
858
- results = ai_detector.ensemble_ai_detection(text)
859
-
860
- if "error" in results:
861
- return results, f"Error: {results.get('error', 'Unknown error')}", go.Figure()
862
-
863
- explanation = f"""
864
- ## Enhanced AI Detection Analysis
865
-
866
- **Final Verdict:** {results.get('final_verdict', 'N/A')}
867
- **Confidence:** {results.get('confidence', 'N/A')}
868
- **Ensemble Score:** {results.get('ensemble_score', 'N/A')}
869
-
870
- ### Classifier Results:
871
- """
872
-
873
- for key, value in results.items():
874
- if '_ai_prob' in key:
875
- model_name = key.replace('_ai_prob', '').replace('_', ' ').title()
876
- explanation += f"- **{model_name}:** {value:.1%} AI probability\n"
877
-
878
- explanation += f"""
879
- ### Advanced Perplexity Analysis:
880
- - **GPT-2 Perplexity:** {results.get('gpt2_perplexity', 'N/A')} (lower = more AI-like)
881
- - **Calibrated Sigmoid Conversion:** Applied for better probability estimation
882
-
883
- ### Key Linguistic Features:
884
- - **Lexical Diversity:** {results.get('feature_lexical_diversity', 'N/A')} (lower suggests AI)
885
- - **Sentence Length Std:** {results.get('feature_sentence_length_std', 'N/A')} (lower suggests AI)
886
- - **Burstiness:** {results.get('feature_burstiness', 'N/A')} (lower suggests AI)
887
- - **Sentence Start Diversity:** {results.get('feature_sentence_start_diversity', 'N/A')} (lower suggests AI)
888
- - **Bigram Repetition:** {results.get('feature_bigram_repetition', 'N/A')} (higher suggests AI)
889
-
890
- ### Enhancement Notes:
891
- - Uses trained ensemble classifier for improved confidence
892
- - Sigmoid-calibrated perplexity conversion
893
- - Enhanced feature weighting and thresholds
894
- """
895
-
896
- # Create enhanced visualization
897
- fig = make_subplots(
898
- rows=2, cols=2,
899
- subplot_titles=('Confidence Level', 'Ensemble Score', 'Feature Analysis', 'Model Breakdown'),
900
- specs=[[{"type": "indicator"}, {"type": "indicator"}],
901
- [{"type": "bar"}, {"type": "pie"}]]
902
- )
903
-
904
- # Confidence gauge
905
- confidence_val = float(results.get('confidence', '50%').strip('%')) / 100
906
- fig.add_trace(go.Indicator(
907
- mode="gauge+number",
908
- value=confidence_val,
909
- domain={'x': [0, 1], 'y': [0, 1]},
910
- title={'text': "Confidence"},
911
- gauge={
912
- 'axis': {'range': [None, 1]},
913
- 'bar': {'color': "green"},
914
- 'steps': [
915
- {'range': [0, 0.6], 'color': "lightgray"},
916
- {'range': [0.6, 0.8], 'color': "yellow"},
917
- {'range': [0.8, 1], 'color': "green"}]}), row=1, col=1)
918
-
919
- # Ensemble score gauge
920
- ensemble_score = results.get('ensemble_score', 0.5)
921
- fig.add_trace(go.Indicator(
922
- mode="gauge+number",
923
- value=ensemble_score,
924
- domain={'x': [0, 1], 'y': [0, 1]},
925
- title={'text': "AI Probability"},
926
- gauge={
927
- 'axis': {'range': [None, 1]},
928
- 'bar': {'color': "red" if ensemble_score > 0.6 else "blue"},
929
- 'steps': [
930
- {'range': [0, 0.3], 'color': "lightblue"},
931
- {'range': [0.3, 0.7], 'color': "yellow"},
932
- {'range': [0.7, 1], 'color': "lightcoral"}]}), row=1, col=2)
933
-
934
- # Feature analysis bar chart
935
- feature_names = []
936
- feature_values = []
937
- for key, value in results.items():
938
- if key.startswith('feature_') and key in ['feature_lexical_diversity', 'feature_burstiness',
939
- 'feature_sentence_start_diversity']:
940
- clean_name = key.replace('feature_', '').replace('_', ' ').title()
941
- feature_names.append(clean_name)
942
- feature_values.append(value)
943
-
944
- if feature_names:
945
- fig.add_trace(go.Bar(
946
- x=feature_names,
947
- y=feature_values,
948
- marker_color='lightblue',
949
- name="Features"
950
- ), row=2, col=1)
951
-
952
- # Model breakdown pie chart
953
- model_probs = []
954
- model_names = []
955
- for key, value in results.items():
956
- if '_ai_prob' in key:
957
- model_name = key.replace('_ai_prob', '').replace('_', ' ').title()
958
- model_names.append(model_name)
959
- model_probs.append(value)
960
-
961
- if model_names and model_probs:
962
- fig.add_trace(go.Pie(
963
- labels=model_names,
964
- values=model_probs,
965
- name="Model Scores"
966
- ), row=2, col=2)
967
-
968
- fig.update_layout(height=600, showlegend=False)
969
-
970
- return results, explanation, fig
971
-
972
- except Exception as e:
973
- error_msg = f"Unexpected error in AI detection: {str(e)}"
974
- return {"error": error_msg}, error_msg, go.Figure()
975
-
976
- # Create enhanced Gradio interfaces
977
- similarity_interface = gr.Interface(
978
- fn=enhanced_similarity_check,
979
- inputs=[
980
- gr.Textbox(label="Text 1", lines=5, placeholder="Enter first text..."),
981
- gr.Textbox(label="Text 2", lines=5, placeholder="Enter second text..."),
982
- gr.Slider(0.2, 0.8, 0.4, step=0.01, label="Similarity Threshold")
983
- ],
984
- outputs=[
985
- gr.JSON(label="Detailed Results"),
986
- gr.Markdown(label="Analysis"),
987
- gr.Plot(label="Visualization")
988
- ],
989
- title="πŸ” Enhanced Semantic Similarity Detector with Antonym Detection",
990
- description="Advanced multi-model ensemble similarity detection with context-aware antonym penalty system",
991
- examples=[
992
- ["The customer service was excellent and efficient.", "The customer service was terrible and slow.", 0.4],
993
- ["The quick brown fox jumps over the lazy dog.", "A fast brown fox leaps over a sleepy dog.", 0.4],
994
- ["Machine learning is transforming industries.", "AI technology is revolutionizing business sectors.", 0.4],
995
- ["The weather is beautiful today.", "The weather is horrible today.", 0.4]
996
- ]
997
- )
998
-
999
- ai_detection_interface = gr.Interface(
1000
- fn=enhanced_ai_detection,
1001
- inputs=gr.Textbox(label="Text to Analyze", lines=8, placeholder="Enter text to check for AI generation..."),
1002
- outputs=[
1003
- gr.JSON(label="Detailed Results"),
1004
- gr.Markdown(label="Analysis"),
1005
- gr.Plot(label="Visualization")
1006
- ],
1007
- title="πŸ€– Professional AI Text Detector with Enhanced Confidence",
1008
- description="Advanced ensemble system with trained classifier, calibrated perplexity, and enhanced feature analysis",
1009
- examples=[
1010
- ["The implementation of artificial intelligence in modern business processes has significantly enhanced operational efficiency and decision-making capabilities across various industry sectors."],
1011
- ["I love pizza! It's my favorite food ever. Yesterday I went to this amazing Italian restaurant downtown and had the best margherita pizza of my life."],
1012
- ["According to recent studies, machine learning algorithms have demonstrated remarkable performance improvements in natural language processing tasks, particularly in the areas of sentiment analysis and text classification."],
1013
- ["Honestly, I can't believe how good this movie was! The acting was incredible, the plot had so many unexpected twists, and don't even get me started on the cinematography - absolutely stunning!"]
1014
- ]
1015
- )
1016
 
1017
- # Launch enhanced application
1018
  app = gr.TabbedInterface(
1019
  [similarity_interface, ai_detection_interface],
1020
  ["Enhanced Similarity Detection", "Enhanced AI Detection"],
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ # Import the individual interfaces
4
+ from semantic_similarity_app import similarity_interface
5
+ from ai_detection_app import ai_detection_interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ # Create the combined tabbed interface
8
  app = gr.TabbedInterface(
9
  [similarity_interface, ai_detection_interface],
10
  ["Enhanced Similarity Detection", "Enhanced AI Detection"],