t5-base fine-tuned for text detoxification (ParaDetox)
Sequence-to-sequence text style transfer: rewrites English toxic/offensive text into
neutral text while preserving the propositional content. Fine-tuned from
google-t5/t5-base on s-nlp/paradetox.
Task formulation
Single-direction conditional generation with an explicit T5 task prefix:
input : detoxify: <toxic sentence>
target: <neutral sentence>
The prefix detoxify: is applied identically during training, evaluation and inference.
Model
| Base model | google-t5/t5-base |
| Architecture | T5 encoder-decoder, ~223M parameters |
| Mixed precision | bf16 |
| Max source length | 64 tokens |
| Max target length | 64 tokens |
Training data
s-nlp/paradetox ships a single train split of parallel toxic/neutral sentence
pairs. Validation and test splits were carved out of it.
Because a toxic sentence can carry up to three different crowd-written neutral paraphrases, the split is computed over unique source sentences, not over rows. A row-level random split would place two paraphrases of the same sentence in train and test and inflate every test metric.
| Split | Rows |
|---|---|
| train | 17760 |
| validation | 1001 |
| test | 983 |
Split strategy: group-aware over unique source sentences, seed 42.
Split fingerprints (sha256 over the sorted unique sources, first 16 hex chars):
train=5107f460c600b24a, validation=1553707e06c53e22, test=110b37a06c3c22fb.
Training hyperparameters
| Optimizer | adamw_torch |
| Learning rate | 0.0001 |
| LR scheduler | linear |
| Warmup | 166 steps (0.06 ratio) |
| Weight decay | 0.01 |
| Gradient clipping | 1.0 |
| Per-device train batch | 16 |
| Gradient accumulation | 2 |
| Effective batch size | 32 |
| Epochs | 5 |
| Steps completed | 2220 |
| Seed | 42 |
| Training time | 13.1 min |
| Hardware | cuda:0 (NVIDIA GeForce RTX 5060 Ti, 15.9 GiB) |
Checkpoint selection: best validation loss (eval_loss).
The test split was not used for model or hyperparameter selection.
Best validation loss: 0.7999.
Generation configuration
Deterministic beam search; identical settings were used for every number below.
| num_beams | 5 |
| max_new_tokens | 64 |
| length_penalty | 1.0 |
| no_repeat_ngram_size | 3 |
| early_stopping | True |
| do_sample | False |
Evaluation
Held-out test split: 596 unique toxic inputs, 962 human references (multi-reference scoring).
1. Reference-based metrics
Measures overlap with human detoxifications. Necessary but not sufficient: a correct detoxification worded differently from the reference is penalised here. The copy-input column scores the unchanged toxic input against the same references and shows how much of any BLEU/ROUGE is simply rewarded for copying.
| Metric | Fine-tuned t5-base | Copy-input baseline |
|---|---|---|
| bleu | 65.1543 | 54.6411 |
| rouge1 | 84.6654 | 78.8245 |
| rouge2 | 75.1529 | 64.9939 |
| rougeL | 84.2702 | 78.4225 |
| bertscore_precision | 95.4743 | 93.2649 |
| bertscore_recall | 95.739 | 95.8614 |
| bertscore_f1 | 95.5527 | 94.5023 |
2. Classifier-based toxicity
Classifier: s-nlp/roberta_toxicity_classifier,
threshold 0.5.
Detoxification rate = among inputs the classifier calls toxic, the share whose generated output the classifier calls non-toxic.
| Measurement | Value |
|---|---|
| Input toxicity rate (%) | 100 |
| Generated toxicity rate (%) | 13.59 |
| Human reference toxicity rate (%) | 6.21 |
| Mean P(toxic), input | 0.9903 |
| Mean P(toxic), generated | 0.1319 |
| Detoxification rate (%) | 86.41 |
| Remaining-toxic rate (%) | 13.59 |
| Newly-toxic rate (%) | n/a |
| Reference detoxification rate (%) | 93.79 |
The classifier is a noisy proxy, not ground truth: false positives on quoted speech and reclaimed slurs, false negatives on implicit toxicity. The human references themselves do not reach a 0% toxicity rate, which bounds how much of the remaining rate is really model error.
3. Semantic preservation
Embeddings: sentence-transformers/all-MiniLM-L6-v2.
| Measurement | Value |
|---|---|
| cos(input, generated) mean | 0.8744 |
| cos(input, generated) median | 0.904 |
| cos(input, generated) p10 | 0.7215 |
| cos(input, human reference) mean | 0.7873 |
| cos(generated, human reference) mean | 0.8366 |
| Share of outputs below cos 0.5 (%) | 1.01 |
cos(input, human reference) is the practical ceiling: human annotators also drop
content when they remove toxic spans.
4. Joint diagnostic
Among the 596 inputs classified toxic, 83.56% produced an output that is both classified non-toxic and stays within cosine 0.6 of the input.
This is an intersection of two noisy measurements, reported as a diagnostic. It is not an overall model score.
Example outputs
| Input (toxic) | Reference | Model output |
|---|---|---|
| im glad the moron showed . | I am glad he showed up | im glad he showed . |
| and his idiot voters gave this trash the nuclear codes . | and his voters gave him the nuclear codes. | and his voters gave him the nuclear codes . |
| i sware i put music on it i dunno wer it went piece of shit ! ! | i sware i put music on it i dunno wer it went | i sware i put music on it i dunno wer it went bad |
| and i was not fucking cooking loo | And i was not cooking loo | and i was not cooking loo |
| jus becuz i like sex doesn make me ah hoe . | jus becuz i like to make love doesn make me ah bad girl . | Just because I like sex doesn't make me ah hoe. |
Seeded random sample of the test split, not selected for quality.
Usage
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
tokenizer = AutoTokenizer.from_pretrained("thealper2/t5-base-paradetox-detox")
model = AutoModelForSeq2SeqLM.from_pretrained("thealper2/t5-base-paradetox-detox")
text = "You are an idiot and nobody likes you."
inputs = tokenizer("detoxify: " + text, return_tensors="pt")
outputs = model.generate(
**inputs,
num_beams=5,
max_new_tokens=64,
length_penalty=1.0,
no_repeat_ngram_size=3,
early_stopping=True,
do_sample=False,
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
The task prefix detoxify: is required. Without it the model is out of distribution.
Limitations
- English only. ParaDetox is an English corpus of Reddit/Twitter/Jigsaw-style comments.
- Trained on short, single-sentence inputs (source p99 = 38 tokens). Long or multi-sentence input is truncated at 64 tokens.
- The dominant training pattern is toxic-span removal. The model deletes or softens offensive spans; it does not reliably rewrite implicit toxicity, sarcasm, dog whistles or threats phrased politely.
- Detoxification and semantic preservation trade off against each other. Deleting a whole clause maximises the toxicity metric and damages the similarity metric; the two tables above must be read together.
- Not a content-moderation system. It rewrites text, it does not detect, filter or classify it, and it must not be used as a safety filter.
- No number here measures fluency or grammaticality.
- Test metrics are computed with a classifier and an embedding model that have their own biases, including known demographic biases in toxicity classifiers.
Reproduction
python -m src.train
python -m src.evaluate
python -m src.qualitative
Config, dataset split fingerprints and the full training record are stored next to
the weights (experiment_config.yaml).