Projects / Comment Toxicity
Comment Toxicity
Four-class toxicity classifier with 66+ engineered features, out-of-fold target encoding, blend optimization, and tuned per-class thresholds.
- LightGBM
- scikit-learn
- Docker
Demo
Overview
Comment Toxicity classifies comments into four levels, from non-toxic to extremely toxic. It turns a Kaggle notebook into a reproducible pipeline: feature engineering, out-of-fold target encoding, a class-weighted LightGBM model, a Nelder-Mead blend and per-class decision thresholds, on about 198,000 labelled comments.
The data is heavily imbalanced (57.7% of comments are non-toxic) and demographic fields are missing for roughly 73% of comments. So much of the work is in features that capture thread context, and in tuning decisions for the rare classes.
Out-of-fold results are 0.912 accuracy and 0.817 macro F1. The demo below is a recording of the project's live site, with its charts for the data, the model comparison and the feature importance.
What it does
- 66+ engineered features. Text statistics, punctuation and style, datetime parts, engagement ratios and demographic flags.
- Thread context. Per-post label rates, comment position and time between comments. These are the strongest signals, because toxicity clusters in discussions.
- Leak-free target encoding. Smoothed mean encoding of demographic columns, computed out-of-fold with a 5-fold stratified split.
- Text pipeline. A scikit-learn ColumnTransformer with word and character TF-IDF, an SVD-reduced TF-IDF, one-hot encoding and scaling.
- Built for imbalance. Class weights (0:1, 1:4, 2:2, 3:8) and early stopping, then per-class thresholds tuned for macro F1.
- Nelder-Mead optimisation. Used for the blend weights and the decision thresholds, lifting macro F1 from 0.813 to 0.817.
- Reproducible. YAML config with validation, train and inference scripts, a Docker image and pytest smoke tests.
Screenshots




How it works
- About 198K comments in four classes are loaded, and 66+ features are engineered from text, style, time, engagement, demographics and thread context.
- Demographic columns are target-encoded out-of-fold, so no row's encoding is computed from its own label.
- A TF-IDF and scaling pipeline builds one feature matrix for a class-weighted LightGBM model.
- Logistic regression and random forest baselines are trained for comparison, and Nelder-Mead blends the out-of-fold probabilities.
- Per-class thresholds are tuned to maximise macro F1, which produces the final predictions.
Install and try it
You need Python 3 and the two competition CSVs (train.csv and test.csv), which are not included in the repository.
- Set it up
git clone https://github.com/BriskAM/comment-toxicity.git cd comment-toxicity python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt - Train
Pass
--train-csv,--test-csvand--out-dirto use your own paths.python scripts/train.py --config configs/default.yaml - Predict
python scripts/infer.py \ --config configs/default.yaml \ --ensemble runs/comment_toxicity_ensemble.npz \ --train-csv data/train.csv \ --test-csv data/test.csv \ --output submission.csv - Run the tests
The original exploration and tuning notebook also runs in Colab; the link is below.
pytest tests/ -v
Notes
- Scores come from out-of-fold predictions in 2-fold stratified cross-validation on the training set, not from a held-out test set.
- The hardest class is the rare, extremely toxic one (F1 0.64); the non-toxic class scores 0.96, and the overall macro F1 is 0.817.
- The data is from a Kaggle competition and is not in the repository.