""" Advanced AI Text Detector - 4-Category Classification Enhanced accuracy with nuanced detection categories for Hugging Face Spaces Renamed to app.py for Hugging Face Spaces deployment """ import gradio as gr import torch import numpy as np import re import time from transformers import AutoTokenizer, AutoModelForSequenceClassification from typing import Dict, List, Tuple import statistics import string from collections import Counter class ImprovedAIDetector: """ Enhanced AI text detector with 4-category classification and improved accuracy """ def __init__(self): self.tokenizer = None self.model = None self.load_models() def load_models(self): """Load and cache detection models""" try: model_name = "roberta-base-openai-detector" self.tokenizer = AutoTokenizer.from_pretrained(model_name) self.model = AutoModelForSequenceClassification.from_pretrained(model_name) print("✓ Models loaded successfully") except Exception as e: print(f"⚠️ Model loading failed: {e}") self.tokenizer = None self.model = None def extract_linguistic_features(self, text: str) -> Dict[str, float]: """ Extract comprehensive linguistic features for detection """ if len(text.strip()) < 10: return {} sentences = re.split(r'[.!?]+', text) sentences = [s.strip() for s in sentences if s.strip()] words = text.split() if not sentences or not words: return {} features = {} # Length-based features features['avg_sentence_length'] = np.mean([len(s.split()) for s in sentences]) features['avg_word_length'] = np.mean([len(word) for word in words]) features['total_words'] = len(words) # Vocabulary diversity unique_words = len(set(word.lower() for word in words)) features['lexical_diversity'] = unique_words / len(words) if words else 0 # Punctuation patterns punct_count = sum(1 for char in text if char in string.punctuation) features['punctuation_ratio'] = punct_count / len(text) if text else 0 # Sentence structure features['sentence_count'] = len(sentences) if len(sentences) > 1: sentence_lengths = [len(s.split()) for s in sentences] features['sentence_length_variance'] = np.var(sentence_lengths) else: features['sentence_length_variance'] = 0 # Word frequency patterns word_freq = Counter(word.lower() for word in words) most_common_freq = word_freq.most_common(1)[0][1] if word_freq else 1 features['max_word_frequency'] = most_common_freq / len(words) # Function words (common in AI text) function_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'} function_word_count = sum(1 for word in words if word.lower() in function_words) features['function_word_ratio'] = function_word_count / len(words) if words else 0 # AI-specific patterns ai_indicators = ['furthermore', 'moreover', 'additionally', 'consequently', 'therefore', 'thus', 'hence'] ai_indicator_count = sum(1 for word in words if word.lower() in ai_indicators) features['ai_indicator_ratio'] = ai_indicator_count / len(words) if words else 0 # Repetition patterns (AI tends to be more repetitive) bigrams = [(words[i].lower(), words[i+1].lower()) for i in range(len(words)-1)] unique_bigrams = len(set(bigrams)) features['bigram_diversity'] = unique_bigrams / len(bigrams) if bigrams else 0 return features def calculate_perplexity_score(self, text: str) -> float: """ Calculate a simplified perplexity-like score """ if not self.model or not self.tokenizer: # Fallback heuristic words = text.split() if len(words) < 5: return 0.5 # Simple heuristic: longer, more complex sentences = higher perplexity avg_word_length = np.mean([len(word) for word in words]) sentence_count = len(re.split(r'[.!?]+', text)) complexity_score = (avg_word_length * sentence_count) / len(words) return min(max(complexity_score, 0.1), 0.9) try: inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512) with torch.no_grad(): outputs = self.model(**inputs) # Use model confidence as perplexity proxy probs = torch.softmax(outputs.logits, dim=-1) confidence = torch.max(probs).item() # Invert confidence to get perplexity-like score return 1.0 - confidence except: return 0.5 def detect_refinement_patterns(self, text: str, linguistic_features: Dict) -> Dict[str, float]: """ Detect patterns indicating AI refinement/editing """ refinement_indicators = {} # Perfect grammar/structure indicators (suggests AI refinement) sentences = re.split(r'[.!?]+', text) sentences = [s.strip() for s in sentences if s.strip()] # Check for overly consistent sentence structure if len(sentences) > 2: lengths = [len(s.split()) for s in sentences] length_consistency = 1.0 - (np.std(lengths) / np.mean(lengths)) if np.mean(lengths) > 0 else 0 refinement_indicators['structure_consistency'] = min(length_consistency, 1.0) else: refinement_indicators['structure_consistency'] = 0.5 # Check for formal language patterns formal_words = ['furthermore', 'moreover', 'consequently', 'therefore', 'additionally', 'subsequently'] formal_count = sum(1 for word in text.lower().split() if word in formal_words) refinement_indicators['formality_score'] = min(formal_count / len(text.split()) * 10, 1.0) # Check for lack of contractions (AI refinement often removes contractions) contractions = ["n't", "'ll", "'re", "'ve", "'m", "'d", "'s"] contraction_count = sum(1 for word in text.split() if any(cont in word for cont in contractions)) words_count = len(text.split()) refinement_indicators['contraction_absence'] = 1.0 - min(contraction_count / words_count * 5, 1.0) if words_count > 0 else 0.5 # Check for overly perfect punctuation punct_perfect_score = 0.5 if ',' in text and '.' in text: # Simple heuristic for punctuation correctness comma_count = text.count(',') period_count = text.count('.') if comma_count > 0 and period_count > 0: punct_ratio = comma_count / (comma_count + period_count) # Refined text often has more balanced punctuation if 0.3 <= punct_ratio <= 0.7: punct_perfect_score = 0.8 refinement_indicators['punctuation_perfection'] = punct_perfect_score return refinement_indicators def classify_text_category(self, text: str) -> Tuple[str, Dict[str, float], float]: """ Classify text into 4 categories with confidence scores """ if len(text.strip()) < 10: return "Uncertain", {"ai_generated": 0.25, "ai_refined": 0.25, "human_ai_refined": 0.25, "human_written": 0.25}, 0.3 # Extract features linguistic_features = self.extract_linguistic_features(text) refinement_patterns = self.detect_refinement_patterns(text, linguistic_features) perplexity_score = self.calculate_perplexity_score(text) # Get transformer model prediction if available transformer_ai_prob = 0.5 if self.model and self.tokenizer: try: inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512) with torch.no_grad(): outputs = self.model(**inputs) probs = torch.softmax(outputs.logits, dim=-1) transformer_ai_prob = probs[0][1].item() # AI probability except: pass # Calculate category probabilities using ensemble approach scores = {} # AI-generated score ai_generated_score = 0.0 if linguistic_features: # AI tends to have: consistent sentence length, formal language, lower lexical diversity ai_generated_score = ( transformer_ai_prob * 0.4 + (1.0 - linguistic_features.get('lexical_diversity', 0.5)) * 0.2 + linguistic_features.get('ai_indicator_ratio', 0) * 0.15 + (1.0 - linguistic_features.get('sentence_length_variance', 0.5) / 10) * 0.15 + (1.0 - perplexity_score) * 0.1 ) else: ai_generated_score = transformer_ai_prob scores['ai_generated'] = min(max(ai_generated_score, 0.0), 1.0) # AI-generated & AI-refined score ai_refined_score = 0.0 if refinement_patterns: ai_refined_score = ( transformer_ai_prob * 0.3 + refinement_patterns.get('structure_consistency', 0) * 0.25 + refinement_patterns.get('formality_score', 0) * 0.25 + refinement_patterns.get('punctuation_perfection', 0) * 0.2 ) else: ai_refined_score = transformer_ai_prob * 0.7 scores['ai_refined'] = min(max(ai_refined_score, 0.0), 1.0) # Human-written & AI-refined score human_ai_refined_score = 0.0 if linguistic_features and refinement_patterns: human_ai_refined_score = ( (1.0 - transformer_ai_prob) * 0.3 + linguistic_features.get('lexical_diversity', 0.5) * 0.2 + refinement_patterns.get('structure_consistency', 0) * 0.2 + refinement_patterns.get('contraction_absence', 0) * 0.15 + refinement_patterns.get('formality_score', 0) * 0.15 ) else: human_ai_refined_score = (1.0 - transformer_ai_prob) * 0.6 scores['human_ai_refined'] = min(max(human_ai_refined_score, 0.0), 1.0) # Human-written score human_written_score = 0.0 if linguistic_features: human_written_score = ( (1.0 - transformer_ai_prob) * 0.4 + linguistic_features.get('lexical_diversity', 0.5) * 0.2 + linguistic_features.get('sentence_length_variance', 0.5) / 10 * 0.15 + (1.0 - refinement_patterns.get('structure_consistency', 0.5)) * 0.15 + perplexity_score * 0.1 ) else: human_written_score = 1.0 - transformer_ai_prob scores['human_written'] = min(max(human_written_score, 0.0), 1.0) # Normalize scores to sum to 1 total_score = sum(scores.values()) if total_score > 0: scores = {k: v / total_score for k, v in scores.items()} else: scores = {"ai_generated": 0.25, "ai_refined": 0.25, "human_ai_refined": 0.25, "human_written": 0.25} # Determine primary category primary_category = max(scores, key=scores.get) confidence = scores[primary_category] # Map to readable names category_names = { 'ai_generated': 'AI-generated', 'ai_refined': 'AI-generated & AI-refined', 'human_ai_refined': 'Human-written & AI-refined', 'human_written': 'Human-written' } return category_names[primary_category], scores, confidence # Initialize detector detector = ImprovedAIDetector() def analyze_text(text): """ Main analysis function for Gradio interface """ if not text or len(text.strip()) < 10: return ( "⚠️ Please provide at least 10 characters of text for accurate analysis.", 0.0, 0.0, 0.0, 0.0, # Four category scores 0.0, 0.0, # AI and Human probabilities 0.0, # Confidence "N/A" # Processing time ) start_time = time.time() try: # Get detailed classification primary_category, category_scores, confidence = detector.classify_text_category(text) # Calculate traditional AI/Human probabilities ai_probability = category_scores['ai_generated'] + category_scores['ai_refined'] human_probability = category_scores['human_ai_refined'] + category_scores['human_written'] processing_time = (time.time() - start_time) * 1000 # Format result message result_message = f""" ## 🎯 **{primary_category}** **Confidence:** {confidence:.1%} ### Category Breakdown: - **AI-generated:** {category_scores['ai_generated']:.1%} - **AI-generated & AI-refined:** {category_scores['ai_refined']:.1%} - **Human-written & AI-refined:** {category_scores['human_ai_refined']:.1%} - **Human-written:** {category_scores['human_written']:.1%} *Analysis completed in {processing_time:.0f}ms* """ return ( result_message, category_scores['ai_generated'], category_scores['ai_refined'], category_scores['human_ai_refined'], category_scores['human_written'], ai_probability, human_probability, confidence, f"{processing_time:.0f}ms" ) except Exception as e: return ( f"❌ Error during analysis: {str(e)}", 0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, "Error" ) def batch_analyze(file): """ Analyze multiple texts from uploaded file """ if file is None: return "Please upload a text file." try: content = file.read().decode('utf-8') texts = [line.strip() for line in content.split('\n') if line.strip() and len(line.strip()) >= 10] if not texts: return "No valid texts found in the uploaded file (each line should have at least 10 characters)." results = [] category_counts = {'AI-generated': 0, 'AI-generated & AI-refined': 0, 'Human-written & AI-refined': 0, 'Human-written': 0} for i, text in enumerate(texts[:15]): # Limit to 15 texts for performance primary_category, category_scores, confidence = detector.classify_text_category(text) category_counts[primary_category] += 1 results.append(f""" **Text {i+1}:** {text[:80]}{'...' if len(text) > 80 else ''} **Result:** {primary_category} ({confidence:.1%} confidence) **Breakdown:** AI-gen: {category_scores['ai_generated']:.0%}, AI-refined: {category_scores['ai_refined']:.0%}, Human+AI: {category_scores['human_ai_refined']:.0%}, Human: {category_scores['human_written']:.0%} """) summary = f""" ## 📊 Batch Analysis Summary **Total texts analyzed:** {len(results)} ### Category Distribution: - **AI-generated:** {category_counts['AI-generated']} texts - **AI-generated & AI-refined:** {category_counts['AI-generated & AI-refined']} texts - **Human-written & AI-refined:** {category_counts['Human-written & AI-refined']} texts - **Human-written:** {category_counts['Human-written']} texts ### Individual Results: """ return summary + "\n".join(results) except Exception as e: return f"Error processing file: {str(e)}" # Create improved Gradio interface def create_improved_interface(): """Create enhanced Gradio interface with 4-category classification""" custom_css = """ .gradio-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1200px; margin: 0 auto; } .gr-button-primary { background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 8px; font-weight: 600; } .gr-button-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); } .category-score { padding: 8px; margin: 4px; border-radius: 6px; border-left: 4px solid #667eea; } """ with gr.Blocks(css=custom_css, title="Advanced AI Text Detector", theme=gr.themes.Soft()) as interface: gr.HTML("""
Sophisticated 4-category classification for precise AI detection
Detects pure AI content, AI-refined text, and human writing with enhanced accuracy