also print the possibility of which model is used

#3
Files changed (1) hide show
  1. app.py +87 -146
app.py CHANGED
@@ -2,31 +2,35 @@ import gradio as gr
2
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
3
  import torch
4
  import re
5
- from tokenizers import normalizers
6
- from tokenizers.normalizers import Sequence, Replace, Strip, NFKC
7
  from tokenizers import Regex
8
  import matplotlib.pyplot as plt
9
 
 
10
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
11
 
 
12
  model1_path = "modernbert.bin"
13
- model2_path = "https://huggingface.co/mihalykiss/modernbert_2/resolve/main/Model_groups_3class_seed12"
14
- model3_path = "https://huggingface.co/mihalykiss/modernbert_2/resolve/main/Model_groups_3class_seed22"
15
 
16
  tokenizer = AutoTokenizer.from_pretrained("answerdotai/ModernBERT-base")
17
 
18
- model_1 = AutoModelForSequenceClassification.from_pretrained("answerdotai/ModernBERT-base", num_labels=41)
19
- model_1.load_state_dict(torch.load(model1_path, map_location=device))
20
- model_1.to(device).eval()
21
-
22
- model_2 = AutoModelForSequenceClassification.from_pretrained("answerdotai/ModernBERT-base", num_labels=41)
23
- model_2.load_state_dict(torch.hub.load_state_dict_from_url(model2_path, map_location=device))
24
- model_2.to(device).eval()
25
-
26
- model_3 = AutoModelForSequenceClassification.from_pretrained("answerdotai/ModernBERT-base", num_labels=41)
27
- model_3.load_state_dict(torch.hub.load_state_dict_from_url(model3_path, map_location=device))
28
- model_3.to(device).eval()
29
 
 
 
 
 
30
 
31
  label_mapping = {
32
  0: '13B', 1: '30B', 2: '65B', 3: '7B', 4: 'GLM130B', 5: 'bloom_7b',
@@ -41,11 +45,13 @@ label_mapping = {
41
  39: 'text-davinci-002', 40: 'text-davinci-003'
42
  }
43
 
 
44
  def clean_text(text: str) -> str:
45
  text = re.sub(r'\s{2,}', ' ', text)
46
  text = re.sub(r'\s+([,.;:?!])', r'\1', text)
47
  return text
48
 
 
49
  newline_to_space = Replace(Regex(r'\s*\n\s*'), " ")
50
  join_hyphen_break = Replace(Regex(r'(\w+)[--]\s*\n\s*(\w+)'), r"\1\2")
51
  tokenizer.backend_tokenizer.normalizer = Sequence([
@@ -55,15 +61,11 @@ tokenizer.backend_tokenizer.normalizer = Sequence([
55
  Strip()
56
  ])
57
 
58
-
59
  def classify_text(text):
60
- """
61
- Classifies the text and generates a plot of the human vs AI probability.
62
- Returns both the result message and the plot figure.
63
- """
64
  cleaned_text = clean_text(text)
65
  if not cleaned_text.strip():
66
- return "", None
67
 
68
  inputs = tokenizer(cleaned_text, return_tensors="pt", truncation=True, padding=True).to(device)
69
 
@@ -72,142 +74,81 @@ def classify_text(text):
72
  logits_2 = model_2(**inputs).logits
73
  logits_3 = model_3(**inputs).logits
74
 
75
- softmax_1 = torch.softmax(logits_1, dim=1)
76
- softmax_2 = torch.softmax(logits_2, dim=1)
77
- softmax_3 = torch.softmax(logits_3, dim=1)
78
-
79
- averaged_probabilities = (softmax_1 + softmax_2 + softmax_3) / 3
80
- probabilities = averaged_probabilities[0]
81
-
82
- human_prob = probabilities[24].item()
83
- ai_probs_clone = probabilities.clone()
84
- ai_probs_clone[24] = 0
85
- ai_total_prob = ai_probs_clone.sum().item()
86
-
87
- total_decision_prob = human_prob + ai_total_prob
88
- human_percentage = (human_prob / total_decision_prob) * 100
89
- ai_percentage = (ai_total_prob / total_decision_prob) * 100
90
-
91
- ai_argmax_index = torch.argmax(ai_probs_clone).item()
92
- ai_argmax_model = label_mapping[ai_argmax_index]
93
-
94
- if human_percentage > ai_percentage:
 
 
 
 
95
  result_message = (
96
- f"**The text is** <span class='highlight-human'>**{human_percentage:.2f}%** likely <b>Human written</b>.</span>"
 
97
  )
98
  else:
99
  result_message = (
100
- f"**The text is** <span class='highlight-ai'>**{ai_percentage:.2f}%** likely <b>AI generated</b>.</span>\n\n"
 
 
101
  )
102
 
103
- fig, ax = plt.subplots(figsize=(8, 4)) # Adjust figure size for better layout
104
-
105
- categories = ['Human', 'AI']
106
- probabilities_for_plot = [human_percentage, ai_percentage]
107
-
108
- bars = ax.bar(categories, probabilities_for_plot, color=['#4CAF50', '#FF5733'], alpha=0.8)
109
- ax.set_ylabel('Probability (%)', fontsize=12)
110
- ax.set_title('Human vs AI Probability', fontsize=14, fontweight='bold')
111
- ax.grid(axis='y', linestyle='--', alpha=0.6)
112
-
113
- # Add labels to the bars
114
  for bar in bars:
115
  height = bar.get_height()
116
- ax.text(bar.get_x() + bar.get_width() / 2, height + 1, f'{height:.2f}%', ha='center')
117
-
118
- ax.set_ylim(0, 100)
119
  plt.tight_layout()
120
-
121
  return result_message, fig
122
 
123
-
124
-
125
-
126
-
127
- title = "AI Text Detector"
128
-
129
-
130
-
131
-
132
- description = """
133
- <div style="border: 3px solid #FF5733; padding: 18px; border-radius: 12px; text-align: center; margin-bottom: 20px;">
134
- <h2 style="margin: 0; font-size: 22px; font-weight: bold;">
135
- ⚠️ Important Notice
136
- </h2>
137
- <p style="margin: 10px 0 0 0; font-size: 18px; font-weight: bold;">
138
- This is an <u>English</u> AI text detector.<br>
139
- The <u>Hungarian</u> AI detector is available at:
140
- <a href="https://preds.hu" target="_blank" style="color: #007bff; text-decoration: none;">
141
- <b>https://preds.hu</b>
142
- </a>
143
- </p>
144
- <hr style="margin: 15px 0;">
145
- <h2 style="margin: 0; font-size: 22px; font-weight: bold;">
146
- ⚠️ Fontos információ
147
- </h2>
148
- <p style="margin: 10px 0 0 0; font-size: 18px; font-weight: bold;">
149
- Ez egy <u>angol</u> AI-szövegdetektor.<br>
150
- A <u>magyar</u> AI detektor itt érhető el:
151
- <a href="https://preds.hu" target="_blank" style="color: #007bff; text-decoration: none;">
152
- <b>https://preds.hu</b>
153
- </a>
154
- </p>
155
- </div>
156
-
157
-
158
-
159
- This tool uses the <b>ModernBERT</b> model to identify whether a given text was written by a human or generated by artificial intelligence (AI). It works with a soft voting ensemble using <b>three</b> models, combining their outputs to improve the accuracy.<br>
160
- <div style="line-height: 1.8;">
161
- ✅ <b>Human Verification:</b> Human-written content is clearly marked.<br>
162
- 🔍 <b>Model Detection:</b> Can identify content from over 40 AI models.<br>
163
- 📈 <b>Accuracy:</b> Works best with longer texts.<br>
164
- 📄 <b>Read more:</b> Our method is detailed in our paper:
165
- <a href="https://aclanthology.org/2025.genaidetect-1.15/" target="_blank" style="color: #007bff; text-decoration: none;"><b>LINK</b></a>.
166
- </div>
167
- <br>
168
- Paste your text below to analyze its origin.
169
- """
170
- bottom_text = "**Developed by SzegedAI**"
171
-
172
- AI_texts = [
173
- "Camels are remarkable desert animals known for their unique adaptations to harsh, arid environments. Native to the Middle East, North Africa, and parts of Asia, camels have been essential to human life for centuries, serving as a mode of transportation, a source of food, and even a symbol of endurance and survival. There are two primary species of camels: the dromedary camel, which has a single hump and is commonly found in the Middle East and North Africa, and the Bactrian camel, which has two humps and is native to Central Asia. Their humps store fat, not water, as commonly believed, allowing them to survive long periods without food by metabolizing the stored fat for energy. Camels are highly adapted to desert life. They can go for weeks without water, and when they do drink, they can consume up to 40 gallons in one sitting. Their thick eyelashes, sealable nostrils, and wide, padded feet protect them from sand and help them walk easily on loose desert terrain.",
174
- "Wines are a fascinating reflection of culture, history, and craftsmanship. They embody a rich diversity shaped by the land, climate, and traditions where they are produced. From the bold reds of Bordeaux to the crisp whites of New Zealand, each bottle tells a unique story. What makes wine so special is its ability to connect people. Whether shared at a family dinner, a celebratory event, or a quiet evening with friends, wine enhances experiences and brings people together. The variety of flavors and aromas, influenced by grape type, fermentation techniques, and aging processes, make wine tasting a complex yet rewarding journey for the senses.",
175
- "I find artificial intelligence (AI) to be one of the most transformative and fascinating technologies of our time. Its potential spans a wide range of applications, from automating mundane tasks to revolutionizing industries like healthcare, education, and entertainment. AI has already made significant contributions in fields like language processing, image recognition, and decision-making systems, enabling innovations that were once purely science fiction. However, as powerful as AI can be, it also brings challenges and responsibilities. Ethical considerations, such as bias in data, transparency, and the potential for misuse, need to be carefully addressed to ensure fairness and accountability. The rise of generative AI has also sparked debates about creativity, originality, and intellectual property, making it essential to strike a balance between technological advancement and respecting human contributions."
176
- ]
177
-
178
- Human_texts = [
179
- "The present book is intended as a text in basic mathematics. As such, it can have multiple use: for a one-year course in the high schools during the third or fourth year (if possible the third, so that calculus can be taken during the fourth year); for a complementary reference in earlier high school grades (elementary algebra and geometry are covered); for a one-semester course at the college level, to review or to get a firm foundation in the basic mathematics necessary to go ahead in calculus, linear algebra, or other topics. Years ago, the colleges used to give courses in “ college algebra” and other subjects which should have been covered in high school. More recently, such courses have been thought unnecessary, but some experiences I have had show that they are just as necessary as ever. What is happening is that thecolleges are getting a wide variety of students from high schools, ranging from exceedingly well-prepared ones who have had a good first course in calculus, down to very poorly prepared ones.",
180
- "Fats are rich in energy, build body cells, support brain development of infants, help body processes, and facilitate the absorption and use of fat-soluble vitamins A, D, E, and K. The major component of lipids is glycerol and fatty acids. According to chemical properties, fatty acids can be divided into saturated and unsaturated fatty acids. Generally lipids containing saturated fatty acids are solid at room temperature and include animal fats (butter, lard, tallow, ghee) and tropical oils (palm,coconut, palm kernel). Saturated fats increase the risk of heart disease.",
181
- "To make BERT handle a variety of down-stream tasks, our input representation is able to unambiguously represent both a single sentence and a pair of sentences (e.g., h Question, Answeri) in one token sequence. Throughout this work, a “sentence” can be an arbitrary span of contiguous text, rather than an actual linguistic sentence. A “sequence” refers to the input token sequence to BERT, which may be a single sentence or two sentences packed together. We use WordPiece embeddings (Wu et al., 2016) with a 30,000 token vocabulary. The first token of every sequence is always a special classification token ([CLS]). The final hidden state corresponding to this token is used as the aggregate sequence representation for classification tasks. Sentence pairs are packed together into a single sequence."]
182
-
183
- iface = gr.Blocks(css="""
184
- @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap');
185
- #text_input_box { border-radius: 10px; border: 2px solid #4CAF50; font-size: 18px; padding: 15px; margin-bottom: 20px; width: 60%; box-sizing: border-box; margin: auto; }
186
- .form.svelte-633qhp { background: none; border: none; box-shadow: none; }
187
- #result_output_box { border-radius: 10px; border: 2px solid #4CAF50; font-size: 18px; padding: 15px; margin-top: 20px; width: 40%; box-sizing: border-box; text-align: center; margin: auto; }
188
- @media (max-width: 768px) { #result_output_box { width: 100%; } #text_input_box{ width: 100%; } }
189
- body { font-family: 'Roboto Mono', sans-serif !important; padding: 20px; display: block; justify-content: center; align-items: center; height: 100vh; overflow-y: auto; }
190
- .gradio-container { border: 1px solid #4CAF50; border-radius: 15px; padding: 30px; box-shadow: 0px 0px 10px rgba(0,255,0,0.6); max-width: 800px; margin: auto; overflow-y: auto; }
191
- h1 { text-align: center; font-size: 32px; font-weight: bold; margin-bottom: 30px; }
192
- .highlight-human { color: #4CAF50; font-weight: bold; background: rgba(76, 175, 80, 0.2); padding: 5px; border-radius: 8px; }
193
- .highlight-ai { color: #FF5733; font-weight: bold; background: rgba(255, 87, 51, 0.2); padding: 5px; border-radius: 8px; }
194
- #bottom_text { text-align: center; margin-top: 50px; font-weight: bold; font-size: 20px; }
195
- .block.svelte-11xb1hd{ background: none !important; }
196
- """)
197
-
198
- with iface:
199
- gr.Markdown(f"# {title}")
200
- gr.Markdown(description)
201
- text_input = gr.Textbox(label="", placeholder="Type or paste your content here...", elem_id="text_input_box", lines=5)
202
- result_output = gr.Markdown("", elem_id="result_output_box")
203
- plot_output = gr.Plot(label="Model Probability Distribution")
204
-
205
  text_input.change(classify_text, inputs=text_input, outputs=[result_output, plot_output])
206
 
207
- with gr.Tab("AI text examples"):
208
- gr.Examples(AI_texts, inputs=text_input)
209
- with gr.Tab("Human text examples"):
210
- gr.Examples(Human_texts, inputs=text_input)
211
- gr.Markdown(bottom_text, elem_id="bottom_text")
212
 
213
- iface.launch(share=True)
 
 
 
2
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
3
  import torch
4
  import re
5
+ from tokenizers.normalizers import Sequence, Replace, Strip
 
6
  from tokenizers import Regex
7
  import matplotlib.pyplot as plt
8
 
9
+ # --- Setup and Model Loading ---
10
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
11
 
12
+ # Model paths and URLs
13
  model1_path = "modernbert.bin"
14
+ model2_url = "https://huggingface.co/mihalykiss/modernbert_2/resolve/main/Model_groups_3class_seed12"
15
+ model3_url = "https://huggingface.co/mihalykiss/modernbert_2/resolve/main/Model_groups_3class_seed22"
16
 
17
  tokenizer = AutoTokenizer.from_pretrained("answerdotai/ModernBERT-base")
18
 
19
+ def load_model(path_or_url, num_labels=41):
20
+ model = AutoModelForSequenceClassification.from_pretrained("answerdotai/ModernBERT-base", num_labels=num_labels)
21
+ if path_or_url.startswith("http"):
22
+ # Load from Hugging Face URL
23
+ state_dict = torch.hub.load_state_dict_from_url(path_or_url, map_location=device)
24
+ else:
25
+ # Load from local file
26
+ state_dict = torch.load(path_or_url, map_location=device)
27
+ model.load_state_dict(state_dict)
28
+ return model.to(device).eval()
 
29
 
30
+ # Initializing the ensemble
31
+ model_1 = load_model(model1_path)
32
+ model_2 = load_model(model2_url)
33
+ model_3 = load_model(model3_url)
34
 
35
  label_mapping = {
36
  0: '13B', 1: '30B', 2: '65B', 3: '7B', 4: 'GLM130B', 5: 'bloom_7b',
 
45
  39: 'text-davinci-002', 40: 'text-davinci-003'
46
  }
47
 
48
+ # --- Text Preprocessing ---
49
  def clean_text(text: str) -> str:
50
  text = re.sub(r'\s{2,}', ' ', text)
51
  text = re.sub(r'\s+([,.;:?!])', r'\1', text)
52
  return text
53
 
54
+ # Custom Tokenizer Normalization
55
  newline_to_space = Replace(Regex(r'\s*\n\s*'), " ")
56
  join_hyphen_break = Replace(Regex(r'(\w+)[--]\s*\n\s*(\w+)'), r"\1\2")
57
  tokenizer.backend_tokenizer.normalizer = Sequence([
 
61
  Strip()
62
  ])
63
 
64
+ # --- Core Classification Logic ---
65
  def classify_text(text):
 
 
 
 
66
  cleaned_text = clean_text(text)
67
  if not cleaned_text.strip():
68
+ return "Please enter text to analyze.", None
69
 
70
  inputs = tokenizer(cleaned_text, return_tensors="pt", truncation=True, padding=True).to(device)
71
 
 
74
  logits_2 = model_2(**inputs).logits
75
  logits_3 = model_3(**inputs).logits
76
 
77
+ # Soft voting ensemble (averaging probabilities)
78
+ s1, s2, s3 = torch.softmax(logits_1, 1), torch.softmax(logits_2, 1), torch.softmax(logits_3, 1)
79
+ avg_probs = (s1 + s2 + s3) / 3
80
+ probs = avg_probs[0]
81
+
82
+ # Calculate probabilities for the 2 main categories (Human vs AI)
83
+ human_prob = probs[24].item()
84
+
85
+ # To find the specific LLM, we ignore the 'human' index (24)
86
+ ai_probs_only = probs.clone()
87
+ ai_probs_only[24] = 0
88
+ ai_total_prob = ai_probs_only.sum().item()
89
+
90
+ # Normalize percentages
91
+ total_sum = human_prob + ai_total_prob
92
+ human_pct = (human_prob / total_sum) * 100
93
+ ai_pct = (ai_total_prob / total_sum) * 100
94
+
95
+ # Identify the specific AI model with the highest sub-probability
96
+ top_ai_idx = torch.argmax(ai_probs_only).item()
97
+ predicted_llm = label_mapping[top_ai_idx]
98
+
99
+ # Construct the result display
100
+ if human_pct > ai_pct:
101
  result_message = (
102
+ f"### Result: <span class='highlight-human'>**{human_pct:.2f}% Human written**</span>\n\n"
103
+ "The content matches human writing patterns."
104
  )
105
  else:
106
  result_message = (
107
+ f"### Result: <span class='highlight-ai'>**{ai_pct:.2f}% AI generated**</span>\n\n"
108
+ f"**Specific Model Identified:** `{predicted_llm}`\n\n"
109
+ "The structure and syntax are highly characteristic of this LLM."
110
  )
111
 
112
+ # Visualization
113
+ fig, ax = plt.subplots(figsize=(8, 4))
114
+ bars = ax.bar(['Human', 'AI'], [human_pct, ai_pct], color=['#4CAF50', '#FF5733'], alpha=0.8)
115
+ ax.set_ylabel('Probability (%)')
116
+ ax.set_title('Detection Probability')
117
+ ax.set_ylim(0, 110) # Room for text labels
118
+
 
 
 
 
119
  for bar in bars:
120
  height = bar.get_height()
121
+ ax.text(bar.get_x() + bar.get_width()/2., height + 2, f'{height:.1f}%', ha='center', fontweight='bold')
122
+
 
123
  plt.tight_layout()
 
124
  return result_message, fig
125
 
126
+ # --- Gradio UI Layout ---
127
+ with gr.Blocks(css="""
128
+ .highlight-human { color: #4CAF50; font-weight: bold; background: rgba(76, 175, 80, 0.1); padding: 5px; border-radius: 5px; }
129
+ .highlight-ai { color: #FF5733; font-weight: bold; background: rgba(255, 87, 51, 0.1); padding: 5px; border-radius: 5px; }
130
+ #output-container { text-align: center; padding: 20px; }
131
+ """) as iface:
132
+ gr.Markdown("# AI Text Detector & LLM Identifier")
133
+ gr.Markdown("This tool uses an ensemble of **ModernBERT** models to predict if text is human or AI, and specifies the likely source model.")
134
+
135
+ with gr.Row():
136
+ with gr.Column(scale=2):
137
+ text_input = gr.Textbox(
138
+ label="Input Text",
139
+ placeholder="Paste your English text here...",
140
+ lines=10
141
+ )
142
+ with gr.Column(scale=1):
143
+ result_output = gr.Markdown("Analysis results will appear here.", elem_id="output-container")
144
+ plot_output = gr.Plot()
145
+
146
+ # Trigger classification on text change
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  text_input.change(classify_text, inputs=text_input, outputs=[result_output, plot_output])
148
 
149
+ gr.Markdown("---")
150
+ gr.Markdown("**Developed by SzegedAI**")
 
 
 
151
 
152
+ if __name__ == "__main__":
153
+ # share=True creates a public URL
154
+ iface.launch(share=True)