|
|
""" |
|
|
Gradio Space for Human-AI Text Attribution (HATA) Model |
|
|
Detects whether text is human-written or AI-generated |
|
|
Supports multiple African languages |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
import torch |
|
|
from transformers import AutoTokenizer, AutoModelForSequenceClassification |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
MODEL_NAME = "msmaje/phdhatamodel" |
|
|
|
|
|
print("Loading model...") |
|
|
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) |
|
|
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME) |
|
|
model.eval() |
|
|
print("Model loaded successfully!") |
|
|
|
|
|
|
|
|
EXAMPLES = [ |
|
|
["Ìwé yìí jẹ́ ìwé tó dára púpọ̀ fún àwọn akẹ́kọ̀ọ́.", "Yoruba"], |
|
|
["Wannan littafi mai kyau ne ga ɗalibai.", "Hausa"], |
|
|
["Akwụkwọ a dị mma maka ụmụ akwụkwọ.", "Igbo"], |
|
|
["Kitabu hiki ni kizuri kwa wanafunzi.", "Swahili"], |
|
|
["Dis book dey very good for students wey wan learn.", "Nigerian Pidgin"], |
|
|
|
|
|
] |
|
|
|
|
|
def classify_text(text, show_probabilities=True): |
|
|
""" |
|
|
Classify text as human-written or AI-generated |
|
|
|
|
|
Args: |
|
|
text: Input text to classify |
|
|
show_probabilities: Whether to show probability scores |
|
|
|
|
|
Returns: |
|
|
Classification result with confidence scores |
|
|
""" |
|
|
if not text or len(text.strip()) == 0: |
|
|
return "⚠️ Please enter some text to classify.", None |
|
|
|
|
|
|
|
|
inputs = tokenizer( |
|
|
text, |
|
|
return_tensors="pt", |
|
|
truncation=True, |
|
|
max_length=128, |
|
|
padding=True |
|
|
) |
|
|
|
|
|
|
|
|
with torch.no_grad(): |
|
|
outputs = model(**inputs) |
|
|
probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) |
|
|
predicted_class = torch.argmax(probabilities, dim=-1).item() |
|
|
confidence = probabilities[0][predicted_class].item() |
|
|
|
|
|
|
|
|
labels = {0: "👤 Human-written", 1: "🤖 AI-generated"} |
|
|
|
|
|
|
|
|
result = f"## Prediction: {labels[predicted_class]}\n" |
|
|
result += f"**Confidence:** {confidence:.2%}\n\n" |
|
|
|
|
|
|
|
|
if confidence > 0.9: |
|
|
result += "✅ **High confidence** - The model is very certain about this prediction." |
|
|
elif confidence > 0.7: |
|
|
result += "⚠️ **Moderate confidence** - The model is fairly certain, but there's some uncertainty." |
|
|
else: |
|
|
result += "❓ **Low confidence** - The model is uncertain. The text may have mixed characteristics." |
|
|
|
|
|
|
|
|
prob_data = { |
|
|
"Human-written": float(probabilities[0][0].item()), |
|
|
"AI-generated": float(probabilities[0][1].item()) |
|
|
} |
|
|
|
|
|
if show_probabilities: |
|
|
return result, prob_data |
|
|
else: |
|
|
return result, None |
|
|
|
|
|
def batch_classify(file): |
|
|
""" |
|
|
Classify multiple texts from uploaded file |
|
|
""" |
|
|
if file is None: |
|
|
return "⚠️ Please upload a text file." |
|
|
|
|
|
|
|
|
try: |
|
|
with open(file.name, 'r', encoding='utf-8') as f: |
|
|
texts = f.readlines() |
|
|
except Exception as e: |
|
|
return f"❌ Error reading file: {e}" |
|
|
|
|
|
|
|
|
results = [] |
|
|
for i, text in enumerate(texts, 1): |
|
|
text = text.strip() |
|
|
if not text: |
|
|
continue |
|
|
|
|
|
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128) |
|
|
|
|
|
with torch.no_grad(): |
|
|
outputs = model(**inputs) |
|
|
probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1) |
|
|
predicted_class = torch.argmax(probabilities, dim=-1).item() |
|
|
confidence = probabilities[0][predicted_class].item() |
|
|
|
|
|
label = "Human" if predicted_class == 0 else "AI" |
|
|
results.append(f"{i}. [{label} - {confidence:.2%}] {text[:100]}...") |
|
|
|
|
|
return "\n".join(results) |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
#title { |
|
|
text-align: center; |
|
|
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
font-size: 2.5em; |
|
|
font-weight: bold; |
|
|
margin-bottom: 0.5em; |
|
|
} |
|
|
|
|
|
#subtitle { |
|
|
text-align: center; |
|
|
color: #666; |
|
|
font-size: 1.2em; |
|
|
margin-bottom: 1em; |
|
|
} |
|
|
|
|
|
.output-box { |
|
|
border: 2px solid #667eea; |
|
|
border-radius: 10px; |
|
|
padding: 15px; |
|
|
} |
|
|
|
|
|
.gradio-container { |
|
|
max-width: 900px; |
|
|
margin: auto; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo: |
|
|
|
|
|
|
|
|
gr.Markdown("<h1 id='title'>🔍 Human vs AI Text Detector</h1>") |
|
|
gr.Markdown( |
|
|
"<p id='subtitle'>Detect whether text is human-written or AI-generated | " |
|
|
"Supports African Languages 🌍</p>" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.Tab("📝 Single Text"): |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
text_input = gr.Textbox( |
|
|
label="Enter text to classify", |
|
|
placeholder="Type or paste your text here...", |
|
|
lines=6, |
|
|
max_lines=10 |
|
|
) |
|
|
|
|
|
show_probs = gr.Checkbox( |
|
|
label="Show probability distribution", |
|
|
value=True |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
classify_btn = gr.Button("🔍 Classify Text", variant="primary") |
|
|
clear_btn = gr.ClearButton([text_input]) |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
result_output = gr.Markdown(label="Result") |
|
|
prob_plot = gr.BarPlot( |
|
|
x="label", |
|
|
y="probability", |
|
|
title="Probability Distribution", |
|
|
y_lim=[0, 1], |
|
|
height=300, |
|
|
visible=True |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### 📚 Try these examples:") |
|
|
gr.Examples( |
|
|
examples=EXAMPLES, |
|
|
inputs=[text_input], |
|
|
label="Example texts in different languages" |
|
|
) |
|
|
|
|
|
|
|
|
classify_btn.click( |
|
|
fn=classify_text, |
|
|
inputs=[text_input, show_probs], |
|
|
outputs=[result_output, prob_plot] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("📄 Batch Processing"): |
|
|
gr.Markdown(""" |
|
|
### Upload a text file for batch classification |
|
|
|
|
|
Upload a `.txt` file with one text sample per line. |
|
|
The app will classify each line and show the results. |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
file_input = gr.File( |
|
|
label="Upload text file (.txt)", |
|
|
file_types=[".txt"] |
|
|
) |
|
|
batch_btn = gr.Button("🔍 Classify All", variant="primary") |
|
|
|
|
|
with gr.Column(): |
|
|
batch_output = gr.Textbox( |
|
|
label="Batch Results", |
|
|
lines=15, |
|
|
max_lines=20 |
|
|
) |
|
|
|
|
|
batch_btn.click( |
|
|
fn=batch_classify, |
|
|
inputs=file_input, |
|
|
outputs=batch_output |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("ℹ️ About"): |
|
|
gr.Markdown(""" |
|
|
# About This Model |
|
|
|
|
|
## 🎯 Purpose |
|
|
This model detects whether text is **human-written** or **AI-generated**. |
|
|
It has been specifically trained on African languages to ensure fair and |
|
|
accurate detection across diverse linguistic contexts. |
|
|
|
|
|
## 🌍 Supported Languages |
|
|
- **English** |
|
|
- **Yoruba** (yo) |
|
|
- **Hausa** (ha) |
|
|
- **Igbo** (ig) |
|
|
- **Swahili** (sw) |
|
|
- **Amharic** (am) |
|
|
- **Nigerian Pidgin** (pcm) |
|
|
|
|
|
## 📊 Performance |
|
|
- **Accuracy:** 100% |
|
|
- **F1 Score:** 100% |
|
|
- **Fairness Metrics:** EOD = 0.0, AAOD = 0.0 (Perfect fairness) |
|
|
|
|
|
## 🔬 Model Details |
|
|
- **Base Model:** [AfroXLMR-base](https://huggingface.co/davlan/afro-xlmr-base) |
|
|
- **Parameters:** ~270M (0.3B) |
|
|
- **Max Sequence Length:** 128 tokens |
|
|
- **Training Dataset:** PhD HATA African Dataset |
|
|
|
|
|
## ⚖️ Fairness & Ethics |
|
|
This model has been trained with explicit fairness constraints to ensure: |
|
|
- Equal performance across all supported languages |
|
|
- No bias toward high-resource languages |
|
|
- Fair treatment of diverse linguistic communities |
|
|
|
|
|
## ⚠️ Limitations |
|
|
- Performance may vary on languages outside the training distribution |
|
|
- AI detection capabilities are tied to the AI systems present in training data |
|
|
- Should be used as one component in content verification, not sole determinant |
|
|
- Text length and domain may affect accuracy |
|
|
|
|
|
## 📚 Citation |
|
|
```bibtex |
|
|
@misc{msmaje2025hata, |
|
|
author = {Maje, M.S.}, |
|
|
title = {AfroXLMR for Human-AI Text Attribution}, |
|
|
year = {2025}, |
|
|
publisher = {HuggingFace}, |
|
|
url = {https://huggingface.co/msmaje/phdhatamodel} |
|
|
} |
|
|
``` |
|
|
|
|
|
## 🔗 Links |
|
|
- [Model on HuggingFace](https://huggingface.co/msmaje/phdhatamodel) |
|
|
- [Training Visualizations](https://huggingface.co/msmaje/phdhatamodel/tree/main/visualizations) |
|
|
- [Dataset](https://huggingface.co/datasets/msmaje/phd-hata-african-dataset) |
|
|
|
|
|
## 👤 Contact |
|
|
For questions or feedback, please open an issue on the model repository. |
|
|
""") |
|
|
|
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
<div style='text-align: center; color: #666; padding: 20px;'> |
|
|
<p>Built with 💜 for African Language NLP | Powered by AfroXLMR</p> |
|
|
<p>Model: <a href='https://huggingface.co/msmaje/phdhatamodel'>msmaje/phdhatamodel</a></p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |