File size: 20,841 Bytes
30c60ea
 
4743b44
30c60ea
f659ec0
45d10f4
 
f659ec0
4743b44
 
30c60ea
f659ec0
45d10f4
f659ec0
4743b44
f659ec0
4743b44
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
 
 
f659ec0
4743b44
 
f659ec0
4743b44
 
 
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
f659ec0
4743b44
f659ec0
4743b44
 
f659ec0
4743b44
f659ec0
 
 
4743b44
 
 
 
 
 
f659ec0
4743b44
f659ec0
 
4743b44
 
 
f659ec0
4743b44
 
 
 
f659ec0
4743b44
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f304cbc
4743b44
 
f659ec0
 
4743b44
 
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
 
4743b44
f659ec0
f304cbc
4743b44
f659ec0
 
 
 
f304cbc
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
f659ec0
4743b44
 
 
f659ec0
4743b44
 
 
 
 
f659ec0
4743b44
 
f659ec0
 
 
 
 
4743b44
 
 
 
 
 
f659ec0
 
 
 
 
 
4743b44
 
 
 
 
 
 
f659ec0
 
4743b44
 
f659ec0
 
4743b44
 
 
 
 
 
 
 
 
f659ec0
4743b44
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
 
 
 
 
 
 
 
 
 
 
 
4743b44
f659ec0
 
 
 
 
4743b44
f659ec0
 
4743b44
f659ec0
 
4743b44
 
 
 
 
 
 
 
f659ec0
 
 
4743b44
 
f659ec0
4743b44
f659ec0
 
 
 
4743b44
f659ec0
 
 
 
 
 
4743b44
 
 
 
f659ec0
 
 
 
 
 
 
 
 
 
 
4743b44
f659ec0
4743b44
 
 
 
 
 
 
 
 
 
 
 
 
 
f659ec0
4743b44
 
f659ec0
 
4743b44
 
 
f659ec0
 
 
 
f304cbc
30c60ea
f659ec0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
import gradio as gr
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, GPT2LMHeadModel, GPT2TokenizerFast
import numpy as np
from scipy import stats
import re
from collections import Counter
import math
import warnings
warnings.filterwarnings('ignore')

class AdvancedAITextDetector:
    def __init__(self):
        """Initialize the AI Text Detector with multiple detection methods"""
        self.models_loaded = {}
        
        # Load multiple models for ensemble detection
        self.load_models()
        
    def load_models(self):
        """Load multiple detection models for ensemble approach"""
        try:
            # Model 1: RoBERTa-based detector (more accurate)
            self.roberta_tokenizer = AutoTokenizer.from_pretrained("roberta-base-openai-detector")
            self.roberta_model = AutoModelForSequenceClassification.from_pretrained("roberta-base-openai-detector")
            self.roberta_model.eval()
            self.models_loaded['roberta'] = True
        except:
            print("Warning: Could not load RoBERTa detector")
            self.models_loaded['roberta'] = False
            
        try:
            # Model 2: Alternative detector
            self.alt_tokenizer = AutoTokenizer.from_pretrained("Hello-SimpleAI/chatgpt-detector-roberta")
            self.alt_model = AutoModelForSequenceClassification.from_pretrained("Hello-SimpleAI/chatgpt-detector-roberta")
            self.alt_model.eval()
            self.models_loaded['alt'] = True
        except:
            print("Warning: Could not load alternative detector")
            self.models_loaded['alt'] = False
            
        try:
            # GPT-2 for perplexity calculation
            self.gpt2_tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
            self.gpt2_model = GPT2LMHeadModel.from_pretrained("gpt2")
            self.gpt2_model.eval()
            self.models_loaded['gpt2'] = True
        except:
            print("Warning: Could not load GPT-2 for perplexity")
            self.models_loaded['gpt2'] = False
    
    def calculate_gpt2_perplexity(self, text):
        """Calculate perplexity using GPT-2 - lower perplexity suggests AI text"""
        if not self.models_loaded.get('gpt2', False):
            return None
            
        try:
            encodings = self.gpt2_tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
            max_length = encodings.input_ids.size(1)
            
            with torch.no_grad():
                outputs = self.gpt2_model(**encodings, labels=encodings.input_ids)
                loss = outputs.loss
                perplexity = torch.exp(loss).item()
            
            # Normalize perplexity to 0-1 scale (lower perplexity = more likely AI)
            # Typical human text: 20-60, AI text: 10-30
            normalized = 1 - min(max((perplexity - 10) / 50, 0), 1)
            return normalized
        except:
            return None
    
    def detect_chatgpt_patterns(self, text):
        """Detect specific ChatGPT writing patterns"""
        patterns_score = 0
        pattern_count = 0
        
        # ChatGPT often uses these phrases
        chatgpt_phrases = [
            r'\bI understand\b',
            r'\bIt\'s important to note\b',
            r'\bIt\'s worth noting\b',
            r'\bIn conclusion\b',
            r'\bHowever,\s',
            r'\bMoreover,\s',
            r'\bFurthermore,\s',
            r'\bAdditionally,\s',
            r'\bIn summary\b',
            r'\bTo summarize\b',
            r'\boverall,\s',
            r'\bGenerally speaking\b',
            r'\bTypically,\s',
            r'\bEssentially,\s',
            r'\bFundamentally,\s',
            r'\bIt\'s crucial\b',
            r'\bIt\'s essential\b',
            r'\bRemember that\b',
            r'\bKeep in mind\b',
            r'\bThis means that\b',
            r'\bThis suggests that\b',
            r'\bwhich means\b',
            r'\bthat being said\b',
            r'\bon the other hand\b',
        ]
        
        text_lower = text.lower()
        for pattern in chatgpt_phrases:
            if re.search(pattern.lower(), text_lower):
                pattern_count += 1
        
        # Calculate pattern density
        patterns_score = min(pattern_count / 5, 1.0)  # Normalize to 0-1
        
        # Check for numbered or bulleted lists (common in ChatGPT)
        has_numbered_list = bool(re.search(r'\n\d+\.', text))
        has_bullets = bool(re.search(r'\n[-β€’*]\s', text))
        
        if has_numbered_list or has_bullets:
            patterns_score = min(patterns_score + 0.2, 1.0)
        
        # Check for balanced paragraph structure (AI characteristic)
        paragraphs = text.split('\n\n')
        if len(paragraphs) > 2:
            lengths = [len(p.split()) for p in paragraphs if p.strip()]
            if lengths:
                cv = np.std(lengths) / np.mean(lengths) if np.mean(lengths) > 0 else 1
                if cv < 0.3:  # Low variation in paragraph lengths
                    patterns_score = min(patterns_score + 0.15, 1.0)
        
        return patterns_score
    
    def calculate_sentence_complexity_variance(self, text):
        """Calculate variance in sentence complexity - AI text is more uniform"""
        sentences = re.split(r'[.!?]+', text)
        complexities = []
        
        for sentence in sentences:
            if sentence.strip():
                words = sentence.split()
                if len(words) > 0:
                    # Calculate complexity based on word length and sentence length
                    avg_word_length = np.mean([len(w) for w in words])
                    complexity = len(words) * (avg_word_length / 5)
                    complexities.append(complexity)
        
        if len(complexities) < 2:
            return 0.5
        
        # Lower variance suggests AI (more uniform complexity)
        cv = np.std(complexities) / np.mean(complexities) if np.mean(complexities) > 0 else 0
        return 1 - min(cv / 0.5, 1.0)  # Normalize and invert
    
    def calculate_word_frequency_distribution(self, text):
        """Analyze word frequency distribution - AI text follows Zipf's law more closely"""
        words = re.findall(r'\b\w+\b', text.lower())
        word_freq = Counter(words)
        
        if len(word_freq) < 10:
            return 0.5
        
        frequencies = sorted(word_freq.values(), reverse=True)[:50]  # Top 50 words
        ranks = range(1, len(frequencies) + 1)
        
        # Calculate how well it fits Zipf's law (AI text fits better)
        if len(frequencies) > 1:
            log_ranks = np.log(ranks)
            log_freqs = np.log(frequencies)
            
            # Calculate correlation with Zipf's law
            correlation = abs(np.corrcoef(log_ranks, log_freqs)[0, 1])
            
            # Higher correlation suggests AI
            return correlation
        
        return 0.5
    
    def detect_roberta(self, text):
        """Use RoBERTa OpenAI detector"""
        if not self.models_loaded.get('roberta', False):
            return None
            
        try:
            inputs = self.roberta_tokenizer(text, return_tensors="pt", truncation=True, 
                                           max_length=512, padding=True)
            
            with torch.no_grad():
                outputs = self.roberta_model(**inputs)
                predictions = torch.softmax(outputs.logits, dim=-1)
                
                # Class 0 is "Real", Class 1 is "Fake" for this model
                ai_probability = predictions[0][1].item()
                
            return ai_probability
        except:
            return None
    
    def detect_alternative(self, text):
        """Use alternative detector model"""
        if not self.models_loaded.get('alt', False):
            return None
            
        try:
            inputs = self.alt_tokenizer(text, return_tensors="pt", truncation=True, 
                                       max_length=512, padding=True)
            
            with torch.no_grad():
                outputs = self.alt_model(**inputs)
                predictions = torch.softmax(outputs.logits, dim=-1)
                ai_probability = predictions[0][1].item()
                
            return ai_probability
        except:
            return None
    
    def enhanced_statistical_analysis(self, text):
        """Enhanced statistical analysis specifically tuned for ChatGPT detection"""
        
        # Calculate all metrics
        chatgpt_patterns = self.detect_chatgpt_patterns(text)
        sentence_complexity = self.calculate_sentence_complexity_variance(text)
        word_freq_dist = self.calculate_word_frequency_distribution(text)
        
        # Existing metrics with adjusted weights
        words = text.split()
        sentences = re.split(r'[.!?]+', text)
        
        # Sentence length consistency (AI is more consistent)
        sentence_lengths = [len(s.split()) for s in sentences if s.strip()]
        if len(sentence_lengths) > 1:
            cv_sentence = np.std(sentence_lengths) / np.mean(sentence_lengths)
            sentence_consistency = 1 - min(cv_sentence / 0.5, 1.0)
        else:
            sentence_consistency = 0.5
        
        # Vocabulary repetition rate
        word_counts = Counter(words)
        words_used_once = sum(1 for count in word_counts.values() if count == 1)
        repetition_rate = 1 - (words_used_once / len(words)) if words else 0.5
        
        # Conjunction and transition word density
        transitions = ['however', 'therefore', 'moreover', 'furthermore', 'additionally',
                      'consequently', 'nevertheless', 'nonetheless', 'meanwhile', 'subsequently']
        transition_count = sum(1 for w in words if w.lower() in transitions)
        transition_density = min(transition_count / len(words) * 100, 1.0) if words else 0
        
        # Combine all statistical features with optimized weights
        statistical_score = (
            chatgpt_patterns * 0.35 +           # Strongest indicator
            sentence_complexity * 0.20 +         # Uniform complexity
            word_freq_dist * 0.15 +             # Zipf's law adherence
            sentence_consistency * 0.15 +        # Consistent sentence lengths
            repetition_rate * 0.10 +            # Word repetition
            transition_density * 0.05            # Transition word usage
        )
        
        return statistical_score, {
            'chatgpt_patterns': chatgpt_patterns,
            'sentence_uniformity': sentence_complexity,
            'zipf_correlation': word_freq_dist,
            'sentence_consistency': sentence_consistency,
            'repetition_rate': repetition_rate,
            'transition_density': transition_density
        }
    
    def detect(self, text):
        """Main detection method with ensemble approach"""
        if not text or len(text.strip()) < 20:
            return {
                "ai_probability": 50.0,
                "classification": "Undetermined",
                "confidence": "Low",
                "explanation": "Text too short for accurate analysis. Please provide at least 50 characters.",
                "detailed_scores": {}
            }
        
        scores = []
        weights = []
        
        # Get RoBERTa OpenAI detector score (most accurate for ChatGPT)
        roberta_score = self.detect_roberta(text)
        if roberta_score is not None:
            scores.append(roberta_score)
            weights.append(0.4)  # Highest weight for most accurate model
        
        # Get alternative model score
        alt_score = self.detect_alternative(text)
        if alt_score is not None:
            scores.append(alt_score)
            weights.append(0.2)
        
        # Get GPT-2 perplexity score
        perplexity_score = self.calculate_gpt2_perplexity(text)
        if perplexity_score is not None:
            scores.append(perplexity_score)
            weights.append(0.15)
        
        # Get enhanced statistical analysis
        stat_score, stat_details = self.enhanced_statistical_analysis(text)
        scores.append(stat_score)
        weights.append(0.25 if len(scores) == 1 else 0.25)
        
        # Calculate weighted average
        if scores:
            # Normalize weights
            weights = [w / sum(weights) for w in weights]
            final_score = sum(s * w for s, w in zip(scores, weights))
        else:
            final_score = 0.5
        
        # Adjust classification thresholds for better ChatGPT detection
        if final_score >= 0.75:
            classification = "AI-Generated (Likely ChatGPT)"
            confidence = "High"
        elif final_score >= 0.55:
            classification = "Probably AI-Generated"
            confidence = "Medium-High"
        elif final_score >= 0.45:
            classification = "Uncertain (Mixed Signals)"
            confidence = "Low"
        elif final_score >= 0.25:
            classification = "Probably Human-Written"
            confidence = "Medium"
        else:
            classification = "Human-Written"
            confidence = "High"
        
        # Generate detailed explanation
        explanation = self._generate_explanation(final_score, stat_details, {
            'roberta': roberta_score,
            'alternative': alt_score,
            'perplexity': perplexity_score
        })
        
        return {
            "ai_probability": round(final_score * 100, 2),
            "classification": classification,
            "confidence": confidence,
            "explanation": explanation,
            "detailed_scores": stat_details,
            "model_scores": {
                'roberta_openai': roberta_score,
                'alternative': alt_score,
                'perplexity': perplexity_score,
                'statistical': stat_score
            }
        }
    
    def _generate_explanation(self, score, stat_details, model_scores):
        """Generate detailed explanation of the detection result"""
        explanations = []
        
        # Overall assessment
        if score >= 0.75:
            explanations.append("πŸ€– Strong indicators of AI generation detected, consistent with ChatGPT patterns.")
        elif score >= 0.55:
            explanations.append("⚠️ Multiple AI characteristics detected, suggesting probable AI generation.")
        elif score >= 0.45:
            explanations.append("❓ Mixed characteristics - could be AI-assisted or heavily edited human text.")
        elif score >= 0.25:
            explanations.append("✍️ Predominantly human characteristics with some regularities.")
        else:
            explanations.append("πŸ‘€ Strong human writing characteristics detected.")
        
        # Model-specific insights
        if model_scores.get('roberta') is not None:
            if model_scores['roberta'] > 0.7:
                explanations.append("\nβ€’ OpenAI detector: Strong AI signature")
            elif model_scores['roberta'] < 0.3:
                explanations.append("\nβ€’ OpenAI detector: Strong human signature")
        
        # Pattern analysis
        if stat_details.get('chatgpt_patterns', 0) > 0.5:
            explanations.append("\nβ€’ High density of ChatGPT-typical phrases and structures")
        
        if stat_details.get('sentence_uniformity', 0) > 0.7:
            explanations.append("\nβ€’ Unusually uniform sentence complexity (AI characteristic)")
        elif stat_details.get('sentence_uniformity', 0) < 0.3:
            explanations.append("\nβ€’ Variable sentence complexity (human characteristic)")
        
        if stat_details.get('zipf_correlation', 0) > 0.8:
            explanations.append("\nβ€’ Word frequency distribution closely follows Zipf's law (AI-like)")
        
        return " ".join(explanations)

# Initialize detector
detector = AdvancedAITextDetector()

def analyze_text(text):
    """Gradio interface function"""
    result = detector.detect(text)
    
    # Format output for Gradio
    output = f"""
## πŸ” Detection Result

**Classification:** {result['classification']}
**AI Probability:** {result['ai_probability']}%
**Confidence Level:** {result['confidence']}

### πŸ“Š Analysis Details
{result['explanation']}

### πŸ“ˆ Model Scores
"""
    
    if result.get('model_scores'):
        for model, score in result['model_scores'].items():
            if score is not None:
                model_name = model.replace('_', ' ').title()
                output += f"- {model_name}: {round(score * 100, 2)}%\n"
    
    output += "\n### πŸ”¬ Statistical Metrics\n"
    
    if result['detailed_scores']:
        for metric, value in result['detailed_scores'].items():
            metric_name = metric.replace('_', ' ').title()
            percentage = round(value * 100, 1)
            output += f"- {metric_name}: {percentage}%\n"
    
    # Create visual probability bar
    ai_prob = result['ai_probability']
    human_prob = 100 - ai_prob
    
    bar_chart = f"""
### πŸ“Š Probability Distribution
```
AI-Generated:    {'β–ˆ' * int(ai_prob/5)}{'β–‘' * (20-int(ai_prob/5))} {ai_prob}%
Human-Written:   {'β–ˆ' * int(human_prob/5)}{'β–‘' * (20-int(human_prob/5))} {human_prob}%
```
"""
    
    # Add warning for edge cases
    if result['confidence'] == "Low":
        bar_chart += "\n⚠️ **Note:** Low confidence - results may be unreliable. Consider additional verification."
    
    return output + bar_chart

# Create Gradio interface
interface = gr.Interface(
    fn=analyze_text,
    inputs=gr.Textbox(
        lines=10,
        placeholder="Paste the text you want to analyze here...",
        label="Input Text"
    ),
    outputs=gr.Markdown(label="Analysis Result"),
    title="πŸ” Advanced ChatGPT & AI Text Detector",
    description="""
    This enhanced AI text detector uses state-of-the-art techniques specifically optimized for detecting ChatGPT and similar AI-generated content:
    
    ### πŸš€ Key Features:
    - **Multiple AI Detection Models** including OpenAI's RoBERTa detector
    - **GPT-2 Perplexity Analysis** to measure text predictability
    - **ChatGPT Pattern Recognition** detecting characteristic phrases and structures
    - **Advanced Statistical Analysis** including Zipf's law correlation and sentence uniformity
    - **Ensemble Method** combining multiple approaches for maximum accuracy
    
    ### πŸ“ Usage Tips:
    - Provide at least 100 words for best results
    - The detector is specifically tuned for ChatGPT/GPT-4 content
    - Works best with English text
    - Longer texts generally yield more reliable results
    
    ### ⚠️ Important:
    This tool provides probabilistic analysis, not absolute certainty. Use it as one of multiple factors in your assessment.
    """,
    examples=[
        ["The impact of artificial intelligence on modern society is profound and multifaceted. As we navigate this technological revolution, it's important to consider both the opportunities and challenges that AI presents. On one hand, AI systems are enhancing productivity, improving healthcare outcomes, and enabling new forms of creativity. On the other hand, concerns about job displacement, privacy, and algorithmic bias require careful consideration. Moving forward, it will be crucial for policymakers, technologists, and society as a whole to work together in shaping the development and deployment of AI in ways that benefit humanity while mitigating potential risks."],
        ["So I was walking down the street yesterday, right? And this crazy thing happened - I mean, you won't believe it. There was this dog, just a regular golden retriever, but it was wearing these ridiculous sunglasses. Like, who puts sunglasses on a dog? Anyway, the owner was this old lady, must've been like 80 or something, and she was just chatting away on her phone, completely oblivious. The dog looked so confused! I couldn't help but laugh. Sometimes you see the weirdest stuff when you're just out and about, you know? Made my whole day, honestly. Still cracks me up thinking about it."],
        ["Machine learning has revolutionized data analysis. Furthermore, deep learning algorithms have shown remarkable success in computer vision tasks. Additionally, natural language processing has made significant strides. It's worth noting that transformer architectures have been particularly influential. Moreover, these developments have practical applications across industries. In conclusion, the continued advancement of ML techniques promises further innovations."]
    ],
    theme=gr.themes.Soft(),
    analytics_enabled=False
)

if __name__ == "__main__":
    interface.launch()