Spaces:
Running
Running
File size: 4,941 Bytes
6d8431a 41a5821 6d8431a 49d2f3f ceeca7d 0d83dcd 6d8431a 0d83dcd 6d8431a 0d83dcd 6d8431a 0d83dcd 6d8431a 41a5821 0d83dcd 6d8431a 0d83dcd 6d8431a 0d83dcd 6d8431a b0b36a6 6d8431a b0b36a6 6d8431a 0d83dcd 6d8431a 0d83dcd 6d8431a 49d2f3f 6d8431a |
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 |
import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import re
import pandas as pd
import gradio as gr
# -----------------------------
# MODEL (Fakespot 2025)
# -----------------------------
MODEL_NAME = "fakespot-ai/roberta-base-ai-text-detection-v1"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dtype = torch.bfloat16 if (device.type=="cuda" and torch.cuda.is_bf16_supported()) else torch.float32
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, dtype=dtype).to(device).eval()
# -----------------------------
# INTERNAL THRESHOLD for sentence labels/colors
# -----------------------------
THRESHOLD = 0.70 # used only for per-sentence "AI/Human" tags & color
# -----------------------------
# SENTENCE SPLITTER (protect → split → restore; no lookbehinds)
# -----------------------------
ABBR = [
"e.g", "i.e", "mr", "mrs", "ms", "dr", "prof", "vs", "etc", "fig", "al",
"jr", "sr", "st", "no", "vol", "pp", "mt", "inc", "ltd", "co", "u.s", "u.k",
"a.m", "p.m"
]
ABBR_REGEX = re.compile(r"\b(" + "|".join(map(re.escape, ABBR)) + r")\.", flags=re.IGNORECASE)
def _protect(text: str) -> str:
t = text.strip()
if not t:
return ""
t = re.sub(r"\s*\n+\s*", " ", t) # normalize newlines
t = t.replace("...", "⟨ELLIPSIS⟩") # ellipses
t = re.sub(r"(?<=\d)\.(?=\d)", "⟨DECIMAL⟩", t) # decimals like 3.14
t = ABBR_REGEX.sub(r"\1⟨ABBRDOT⟩", t) # abbreviations' dot
return t
def _restore(text: str) -> str:
return (text
.replace("⟨ABBRDOT⟩", ".")
.replace("⟨DECIMAL⟩", ".")
.replace("⟨ELLIPSIS⟩", "..."))
def sentence_split(text: str):
t = _protect(text)
if not t:
return []
# split on [.?!] followed by whitespace and likely sentence start or end
parts = re.split(r"([.?!])\s+(?=(?:[\"“”‘’']?\s*[A-Z(])|$)", t)
sentences, buf = [], ""
for i, chunk in enumerate(parts):
if i % 2 == 0:
buf += chunk
else:
buf += chunk
sentences.append(buf.strip()); buf = ""
if buf.strip():
sentences.append(buf.strip())
return [_restore(s).strip() for s in sentences if s.strip()]
# -----------------------------
# CORE: overall AI score + highlights
# -----------------------------
def analyze(text, max_len=512):
sents = sentence_split(text)
if not sents:
return "—", "—", "<em>Paste some text to analyze.</em>", None
# light clean (per model card vibe)
clean_sents = [re.sub(r"\s+", " ", s).strip() for s in sents]
inputs = tokenizer(
clean_sents, return_tensors="pt", padding=True, truncation=True, max_length=max_len
).to(device)
with torch.no_grad():
logits = model(**inputs).logits
probs = F.softmax(logits, dim=-1) # [:,0]=Human, [:,1]=AI
ai_probs = probs[:, 1].detach().cpu().tolist()
overall_ai = sum(ai_probs) / len(ai_probs)
overall_pct = f"{overall_ai * 100:.1f}%"
overall_label = "🤖 Likely AI Written" if overall_ai >= THRESHOLD else "🧒 Likely Human Written"
# Per-sentence highlights (use THRESHOLD only for the tag/color)
rows, highlights = [], []
for i, orig in enumerate(sents, start=1):
ai_p = float(ai_probs[i-1])
label = "AI" if ai_p >= THRESHOLD else "Human"
pct = f"{ai_p*100:.1f}%"
if ai_p < 0.30:
color = "#11823b" # green
elif ai_p < 0.70:
color = "#b8860b" # amber
else:
color = "#b80d0d" # red
normalized = re.sub(r"\s+", " ", orig)
highlights.append(
"<div style='margin:6px 0; padding:6px 8px; border-radius:6px; background:rgba(0,0,0,0.03)'>"
f"<strong style='color:{color}'>[{pct} {label}]</strong> {normalized}</div>"
)
rows.append([i, orig, round(ai_p, 4), label])
html = "\n".join(highlights)
df = pd.DataFrame(rows, columns=["#", "Sentence", "AI_Prob", "Label"])
return overall_label, overall_pct, html, df
# -----------------------------
# GRADIO UI (verdict + score, plus highlights)
# -----------------------------
with gr.Blocks() as demo:
gr.Markdown("### 🕵️ AI Written Text Detector — Fakespot Model")
text_input = gr.Textbox(label="Paste text", lines=14, placeholder="Your content…")
btn = gr.Button("Analyze")
verdict = gr.Label(label="Verdict (Overall)")
score = gr.Label(label="AI Score (Average across sentences)")
highlights = gr.HTML(label="Per-Sentence Highlights")
table = gr.Dataframe(headers=["#", "Sentence", "AI_Prob", "Label"], wrap=True)
btn.click(analyze, inputs=[text_input], outputs=[verdict, score, highlights, table])
if __name__ == "__main__":
demo.launch()
|