AIdetector / core /perplexity_scorer.py
mokshad
Initial deploy β€” AI text detector API
e945892
Raw
History Blame Contribute Delete
2.43 kB
# core/perplexity_scorer.py
# Perplexity-based AI detection using GPT-2.
#
# Key insight: AI text is MORE predictable than human text.
# GPT-2 assigns LOWER perplexity to AI-generated text
# because LLMs generate high-probability token sequences.
#
# This signal is MODEL-AGNOSTIC β€” works regardless of which
# AI wrote the text, unlike our BERT model which learned
# GPT-5 Nano patterns specifically.
import torch
import math
import numpy as np
from transformers import GPT2LMHeadModel, GPT2TokenizerFast
class PerplexityScorer:
"""
Scores text using GPT-2 perplexity.
Lower perplexity = more predictable = more likely AI.
"""
def __init__(self):
self._model = None
self._tokenizer = None
self._loaded = False
def load(self):
print("Loading GPT-2 for perplexity scoring...")
self._tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
self._model = GPT2LMHeadModel.from_pretrained("gpt2")
self._model.eval()
print("GPT-2 loaded.")
self._loaded = True
def get_perplexity(self, text: str) -> float:
"""
Compute perplexity of text under GPT-2.
Lower = more predictable = more AI-like.
Typical ranges:
AI text: 30 - 80
Human text: 80 - 200+
"""
if not self._loaded:
return None
encodings = self._tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=512,
)
input_ids = encodings.input_ids
with torch.no_grad():
outputs = self._model(input_ids, labels=input_ids)
loss = outputs.loss
return math.exp(loss.item())
def perplexity_to_ai_score(self, perplexity: float) -> float:
"""
Convert perplexity to 0-1 AI probability.
Lower perplexity = higher AI score.
Calibrated ranges:
perplexity < 50 β†’ score > 0.8 (very likely AI)
perplexity 50-100 β†’ score 0.5-0.8
perplexity > 150 β†’ score < 0.3 (likely human)
"""
if perplexity is None:
return 0.5
# Sigmoid-like mapping
# Anchor: perplexity=50 β†’ score=0.75, perplexity=150 β†’ score=0.25
score = 1 / (1 + (perplexity / 80) ** 1.5)
return round(float(max(0.0, min(1.0, score))), 4)
# Singleton
perplexity_scorer = PerplexityScorer()