Hebrew Toxicity Detector
A fine-tuned BERT-based model for detecting toxic and offensive text, with a focus on Hebrew social media comments.
Project Context
This model was developed as part of my final Computer Science project.
The goal is to improve automatic detection of harmful, offensive, and toxic comments, especially in Hebrew, where existing toxicity models often perform less accurately.
Model Description
The model is based on a Transformer text-classification architecture and was fine-tuned on a custom dataset of toxic and non-toxic examples.
It is designed to classify short user-generated text such as:
- YouTube comments
- Social media posts
- Informal Hebrew text
- Offensive slang and insults
Intended Use
This model is used as part of a browser extension that helps reduce direct exposure to harmful comments by blurring content detected as toxic.
Files
model.safetensorsโ fine-tuned model weights (fp32)model.int8.onnxโ the same model quantized to INT8 for CPU inference with ONNX Runtime (see INT8 version)config.jsonโ model configurationtokenizer.jsonโ tokenizer filetokenizer_config.jsonโ tokenizer configurationquantize/export_and_quantize.pyโ the script that producedmodel.int8.onnx
Usage
from transformers import pipeline
model_path = "BAR-ILAN/hebrew-toxicity-detector-INT8"
classifier = pipeline(
"text-classification",
model=model_path,
tokenizer=model_path
)
result = classifier("ืืชื ืคืฉืื ืืืขืื")
print(result)
INT8 version
model.int8.onnx is the fp32 model exported to ONNX and quantized with ONNX Runtime dynamic quantization:
- The weights of every linear layer are INT8, quantized per channel (74 MatMul ops: 72 in the encoder, plus the pooler and the classifier).
- The word embeddings stay fp32, so the file is 434 MB, compared with 679 MB for the fp32 weights.
- No calibration data was needed.
Usage
Run one text per call. Dynamic quantization picks one activation scale for the whole input, so in a batch a comment's score depends on the other comments in it and on their padding. In our tests, the same comment scored 0.54 alone and 0.74 or 0.94 inside two different batches. One text per call is also faster on CPU, because no time is spent on padding.
import numpy as np
import onnxruntime as ort
from huggingface_hub import hf_hub_download
from transformers import AutoConfig, AutoTokenizer
model_id = "BAR-ILAN/hebrew-toxicity-detector-INT8"
tokenizer = AutoTokenizer.from_pretrained(model_id)
id2label = AutoConfig.from_pretrained(model_id).id2label
session = ort.InferenceSession(hf_hub_download(model_id, "model.int8.onnx"), providers=["CPUExecutionProvider"])
def classify(text):
encoded = tokenizer(text, truncation=True, max_length=512, return_tensors="np")
inputs = {i.name: encoded[i.name].astype(np.int64) for i in session.get_inputs()}
logits = session.run(None, inputs)[0][0]
probabilities = np.exp(logits - logits.max()) / np.exp(logits - logits.max()).sum()
best = int(probabilities.argmax())
return {"label": id2label[best], "score": float(probabilities[best])}
print(classify("ืืชื ืคืฉืื ืืืขืื"))
fp32 vs INT8
The two models were evaluated on the same comments:
- fp32:
transformerspipeline, batches of 8 - INT8: ONNX Runtime, one text per call
Both used the thresholds of the server that runs the model: P(toxic) โฅ 0.65 for Hebrew and mixed text, and โฅ 0.85 for English. The times are for a full CPU run on one laptop (Intel Core, 12 threads).
| Dataset | Model | Precision | Recall | F1 | AUC | Time |
|---|---|---|---|---|---|---|
Hebrew: textdetox he split (2,011, 807 toxic) |
fp32 | 89.35% | 87.36% | 88.35% | 0.9623 | 112.5 s |
| INT8 | 89.38% | 88.60% | 88.99% | 0.9614 | 37.3 s | |
English: textdetox en split (5,000, 2,500 toxic) |
fp32 | 99.19% | 98.40% | 98.80% | 0.9976 | 168.0 s |
| INT8 | 98.95% | 98.36% | 98.66% | 0.9977 | 66.4 s | |
| Team-collected YouTube-style comments (326, 257 toxic) | fp32 | 93.61% | 79.77% | 86.13% | 0.8657 | 10.5 s |
| INT8 | 93.72% | 81.32% | 87.08% | 0.8723 | 4.2 s |
How far each comment's result moved between fp32 and INT8:
| Dataset | Decisions that flipped | Mean change in P(toxic) | 99th percentile | Max |
|---|---|---|---|---|
| Hebrew textdetox | 21 (1.04%) | 0.014 | 0.22 | 0.66 |
| English textdetox | 11 (0.22%) | 0.003 | 0.06 | 0.62 |
| YouTube-style comments | 8 (2.45%) | 0.022 | 0.25 | 0.63 |
- Accuracy is unchanged. F1 moves by less than 1 point on every set, and AUC by less than 0.001 on the benchmarks.
- INT8 is 2.5โ3ร faster on CPU.
- Most flipped decisions sit near a threshold. On Hebrew, 15 of the 21 flips were corrections.
- English false positives rose from 20 to 26. Most of the new ones are harmless comments that mention an identity group, such as "Some are just great black mothers" and "Salute to every SIKH". The fp32 model already scores these 0.57โ0.85, and INT8 pushes them over the 0.85 English threshold.
- A few single comments move a lot, up to 0.66 in P(toxic). This is rare: 99% of comments move by less than 0.25.
Reproducing the export
pip install torch transformers onnx onnxruntime onnxscript
python quantize/export_and_quantize.py
The script exports the fp32 model to ONNX and quantizes it. It then prints the fp32 vs INT8 scores for a few sample comments and the file sizes. The export uses torch.onnx.export(..., dynamo=True), because the older exporter can't trace the attention-mask code in transformers 5.
- Downloads last month
- 78