unknown Claude Opus 5.5 commited on
Commit
eebf465
·
1 Parent(s): f85f16b

Stronger AI detector: paraphrase-robust third model in the ensemble

Browse files

fakespot-ai/roberta-base-ai-text-detection-v1 averaged with the current coai + desklib score. Measured
(devtools/compare_detectors.py): AI samples 18/18 flagged, human abstracts from 2008 0/21, paragraphs our own
humanizer had brought to 6%: 6/16 flagged (was 1/16). RADAR was tested and rejected (separation 0.72 on academic
text). DETECTOR_MODEL3="" turns it off; DETECTOR_WEIGHT3 sets its share (0.5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. detector.py +52 -3
  2. devtools/compare_detectors.py +2 -0
detector.py CHANGED
@@ -54,6 +54,42 @@ WEIGHT2 = float(os.getenv("DETECTOR_WEIGHT2", "0.7"))
54
  COMBINE = os.getenv("DETECTOR_COMBINE", "mean").strip().lower()
55
  _tok2, _model2, _m2_failed = None, None, False
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  def _desklib_class():
59
  import torch
@@ -139,6 +175,7 @@ def load_model():
139
  if os.getenv("SKIP_MODEL_LOAD") != "1":
140
  load_model()
141
  load_model2()
 
142
  DX.start_lo_init() # fetch newest LibreOffice in the background
143
 
144
  def _score_with(tok, model, texts):
@@ -162,7 +199,9 @@ def _score_gpu(texts):
162
  p1 = _score_with(tok, model, texts)
163
  tok2, model2 = load_model2()
164
  p2 = _score_with2(tok2, model2, texts) if model2 is not None else None
165
- return _combine(p1, p2)
 
 
166
 
167
 
168
  _cpu_det, _cpu_det_lock = None, __import__("threading").Lock()
@@ -170,7 +209,7 @@ LAST_ENGINE = "GPU"
170
 
171
 
172
  def _score_cpu(texts):
173
- global _cpu_det
174
  import torch
175
  from transformers import AutoModelForSequenceClassification
176
  with _cpu_det_lock:
@@ -185,7 +224,17 @@ def _score_cpu(texts):
185
  if next(model2.parameters()).device.type != "cpu":
186
  model2 = _cpu_model2()
187
  p2 = _score_with2(tok2, model2, texts)
188
- return _combine(p1, p2)
 
 
 
 
 
 
 
 
 
 
189
 
190
 
191
  _cpu_m2 = None
 
54
  COMBINE = os.getenv("DETECTOR_COMBINE", "mean").strip().lower()
55
  _tok2, _model2, _m2_failed = None, None, False
56
 
57
+ # Third detector: robust to paraphrasing (what Turnitin's "AI-paraphrased" check is about). Measured with
58
+ # devtools/compare_detectors.py on 18 AI-written academic samples, 21 human abstracts from 2008 and 16 paragraphs our
59
+ # own humanizer had brought to 6%: coai + desklib flagged 18/18 AI, 0/21 human, 1/16 humanized; this RoBERTa
60
+ # (fakespot-ai) flagged 18/18, 2/21, 8/16. Averaged with the first two: 18/18 AI, 0/21 human, 6/16 humanized, so
61
+ # the score is much harder to game without flagging human writing.
62
+ # DETECTOR_MODEL3 default fakespot-ai/roberta-base-ai-text-detection-v1 ("" = off)
63
+ # DETECTOR_WEIGHT3 default 0.5 share of the final score
64
+ MODEL3_ID = os.getenv("DETECTOR_MODEL3", "fakespot-ai/roberta-base-ai-text-detection-v1").strip()
65
+ WEIGHT3 = float(os.getenv("DETECTOR_WEIGHT3", "0.5"))
66
+ _tok3, _model3, _m3_failed = None, None, False
67
+ _cpu_m3 = None
68
+
69
+
70
+ def load_model3():
71
+ global _tok3, _model3, _m3_failed
72
+ if not MODEL3_ID or _m3_failed or _model3 is not None:
73
+ return _tok3, _model3
74
+ try:
75
+ import torch
76
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
77
+ _tok3 = AutoTokenizer.from_pretrained(MODEL3_ID, token=HF_TOKEN)
78
+ _model3 = AutoModelForSequenceClassification.from_pretrained(MODEL3_ID, token=HF_TOKEN).eval()
79
+ if ZERO or torch.cuda.is_available():
80
+ _model3.to("cuda")
81
+ print(f"[detector] third model loaded: {MODEL3_ID}", flush=True)
82
+ except Exception as err:
83
+ _m3_failed = True
84
+ print(f"[detector] third model not available ({err}); first two only", flush=True)
85
+ return _tok3, _model3
86
+
87
+
88
+ def _with3(p12, p3):
89
+ if p3 is None:
90
+ return p12
91
+ return [(1 - WEIGHT3) * a + WEIGHT3 * b for a, b in zip(p12, p3)]
92
+
93
 
94
  def _desklib_class():
95
  import torch
 
175
  if os.getenv("SKIP_MODEL_LOAD") != "1":
176
  load_model()
177
  load_model2()
178
+ load_model3()
179
  DX.start_lo_init() # fetch newest LibreOffice in the background
180
 
181
  def _score_with(tok, model, texts):
 
199
  p1 = _score_with(tok, model, texts)
200
  tok2, model2 = load_model2()
201
  p2 = _score_with2(tok2, model2, texts) if model2 is not None else None
202
+ tok3, model3 = load_model3()
203
+ p3 = _score_with(tok3, model3, texts) if model3 is not None else None # label 1 = "ai"
204
+ return _with3(_combine(p1, p2), p3)
205
 
206
 
207
  _cpu_det, _cpu_det_lock = None, __import__("threading").Lock()
 
209
 
210
 
211
  def _score_cpu(texts):
212
+ global _cpu_det, _cpu_m3
213
  import torch
214
  from transformers import AutoModelForSequenceClassification
215
  with _cpu_det_lock:
 
224
  if next(model2.parameters()).device.type != "cpu":
225
  model2 = _cpu_model2()
226
  p2 = _score_with2(tok2, model2, texts)
227
+ p3 = None
228
+ tok3, model3 = load_model3()
229
+ if model3 is not None:
230
+ if next(model3.parameters()).device.type != "cpu":
231
+ with _cpu_det_lock:
232
+ if _cpu_m3 is None:
233
+ from transformers import AutoModelForSequenceClassification as _A
234
+ _cpu_m3 = _A.from_pretrained(MODEL3_ID, token=HF_TOKEN).eval()
235
+ model3 = _cpu_m3
236
+ p3 = _score_with(tok3, model3, texts)
237
+ return _with3(_combine(p1, p2), p3)
238
 
239
 
240
  _cpu_m2 = None
devtools/compare_detectors.py CHANGED
@@ -60,6 +60,8 @@ def main():
60
  scorers["current (live: coai + desklib)"] = D.score_texts
61
  if "radar" in want:
62
  scorers["RADAR (paraphrase-robust)"] = hf_scorer("TrustSafeAI/RADAR-Vicuna-7B", ai_index=0)
 
 
63
  if "fakespot" in want:
64
  scorers["fakespot roberta"] = hf_scorer("fakespot-ai/roberta-base-ai-text-detection-v1")
65
  if "desklib" in want:
 
60
  scorers["current (live: coai + desklib)"] = D.score_texts
61
  if "radar" in want:
62
  scorers["RADAR (paraphrase-robust)"] = hf_scorer("TrustSafeAI/RADAR-Vicuna-7B", ai_index=0)
63
+ if "superannotate" in want:
64
+ scorers["SuperAnnotate"] = hf_scorer("SuperAnnotate/ai-detector")
65
  if "fakespot" in want:
66
  scorers["fakespot roberta"] = hf_scorer("fakespot-ai/roberta-base-ai-text-detection-v1")
67
  if "desklib" in want: