File size: 2,340 Bytes
bd743a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Fast-DetectGPT (Bao et al., 2023) — conditional probability curvature.

Analytic single-model form (sampling model = scoring model): using the model's full next-token
distribution we compute, per position, the observed log-prob vs its conditional mean and variance.
The standardized statistic d = (logp_obs - mu) / sigma is HIGH for machine text. We return d directly
(higher = more AI). Default proxy is gpt2 (Colab-cheap); use EleutherAI/gpt-neo-1.3B for a stronger
signal. arXiv:2310.05130
"""
from __future__ import annotations
import numpy as np
from .base import Detector


class FastDetectGPTDetector(Detector):
    name = "fast-detectgpt"

    def __init__(self, model_name: str = "gpt2", device: str | None = None, max_tokens: int = 512):
        import torch
        from transformers import AutoModelForCausalLM, AutoTokenizer
        self.torch = torch
        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
        self.max_tokens = max_tokens
        self.tok = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(model_name).to(self.device).eval()

    def _curvature(self, text: str) -> float:
        torch = self.torch
        if not text or not text.strip():
            return float("nan")
        ids = self.tok(text, return_tensors="pt", truncation=True,
                       max_length=self.max_tokens).input_ids.to(self.device)
        if ids.size(1) < 3:
            return float("nan")
        with torch.no_grad():
            logits = self.model(ids).logits[:, :-1, :]          # predict tokens 1..L-1
            lp = torch.log_softmax(logits, dim=-1)              # [1, L-1, V]
            p = lp.exp()
            tgt = ids[:, 1:]                                    # [1, L-1]
            logp_tok = lp.gather(-1, tgt.unsqueeze(-1)).squeeze(-1)  # observed log-probs
            mu = (p * lp).sum(-1)                               # conditional mean (= -entropy)
            var = (p * lp.pow(2)).sum(-1) - mu.pow(2)           # conditional variance
            num = (logp_tok - mu).sum()
            den = var.sum().clamp_min(1e-8).sqrt()
            d = (num / den).item()
        return float(d)

    def score(self, texts: list[str]) -> np.ndarray:
        return np.array([self._curvature(t) for t in texts], dtype=float)