Skip to main content

Toxic Comment Classifier

A pre-trained BiLSTM that scores comments for six kinds of toxicity, offline, in one line of Python.

PyPI version Python versions PyPI Downloads Tests CodeQL License: MIT Ruff typed: mypy PRs welcome

Why

Most moderation pipelines either call a paid API (latency, cost, data leaves your servers) or pull in a large transformer. This library ships a small, pre-trained multi-label model inside the wheel: install it, and you can score comments on a CPU with no network, no API key and no model download. It returns the six Jigsaw Toxic Comment Classification labels, so you can build your own moderation policy on top.

Features

Six labels, multi-label toxic, severe_toxic, obscene, threat, insult, identity_hate, each scored independently from 0 to 1
Bundled and offline Weights (4.6 MB) and vocabulary (19,999 words) ship in the package; nothing is downloaded at runtime
Simple API classify() for per-label scores, predict() for one overall score, batch versions of both, flagged_labels() for thresholds
Batched inference A list of texts goes through the network in one model.predict call
CLI toxic-classifier "some comment" prints JSON Lines; reads stdin too
No pickle, no Keras internals The vocabulary is plain JSON read by a small built-in tokenizer, so Keras upgrades do not break loading
Typed Ships py.typed; checked with mypy in CI

Install

pip install toxic-comment-classifier
# or
uv pip install toxic-comment-classifier

Requires Python 3.10 to 3.13. TensorFlow (>= 2.16) and Keras 3 are installed as dependencies. There are no optional extras.

Quickstart

from toxic_classifier import ToxicCommentClassifier

model = ToxicCommentClassifier()  # loads the bundled weights, no network

text = "You are so dumb and stupid!"
print(model.classify(text))
# {'toxic': 0.9889..., 'severe_toxic': 0.0725..., 'obscene': 0.6204...,
#  'threat': 0.0193..., 'insult': 0.8664..., 'identity_hate': 0.0407...}

print(f"{model.predict(text):.4f}")          # 0.4347  (mean of the six labels)
print(model.flagged_labels(text, 0.5))       # ['toxic', 'obscene', 'insult']

texts = ["I hate you so much!", "This is wonderful news.", "Have a nice day :)"]
print([round(s, 4) for s in model.predict_batch(texts)])  # [0.1395, 0.0013, 0.0115]

Note the import name is toxic_classifier (the PyPI name is toxic-comment-classifier).

Command line

toxic-classifier "go kill yourself"
# {"text": "go kill yourself", "toxicity": 0.374754, "flagged": ["toxic", "insult"], "scores": {"toxic": 0.888507, ...}}

# One comment per line from stdin, JSON Lines out
cat comments.txt | toxic-classifier --threshold 0.7

Options: --threshold (0 to 1, default 0.5), --clean (apply clean_text() first), --version.

How it works

flowchart LR
    A["text / list of texts"] --> B{"clean=True?"}
    B -- yes --> C["utils.clean_text<br/>lowercase, strip URLs<br/>and special chars"]
    B -- no --> D
    C --> D["tokenizer.WordTokenizer<br/>(model/tokenizer.json)<br/>Keras-compatible word ids"]
    D --> E["tokenizer.pad_sequences<br/>left-pad / left-truncate to 100"]
    E --> F["Keras 3 model (model._build_model)<br/>Embedding 20000x50 (frozen)<br/>BiLSTM 2x50 + GlobalMaxPool<br/>Dense 50 + Dropout<br/>Dense 6, sigmoid<br/>(model/model.weights.h5)"]
    F --> G["classify / classify_batch<br/>dict of 6 label scores"]
    F --> H["predict / predict_batch<br/>mean of 6 scores"]
    G --> I["flagged_labels<br/>labels >= threshold"]
    G --> J["cli.main<br/>JSON Lines"]

Key design decisions:

  • Architecture rebuilt in code, weights loaded from .h5. The network (about 1.05 M parameters, 1 M of them in the frozen embedding) is defined with the public Keras 3 API and only the weights are loaded, so there is no dependency on a serialized model format.
  • Tokenizer reimplemented, vocabulary as JSON. WordTokenizer reproduces Keras Tokenizer.texts_to_sequences (same filters, lowercasing and num_words cutoff) and pad_sequences reproduces the Keras defaults. Release 0.4.0 verified identical token ids and a maximum score difference of 1.2e-7 against the original pickled tokenizer.
  • Sigmoid outputs, not softmax. Labels are independent: a comment can be both obscene and an insult. predict() is simply the mean of the six scores.
  • Lazy, local TensorFlow. TensorFlow is imported when the classifier is built (the CLI parses arguments first), and TF_CPP_MIN_LOG_LEVEL defaults to 2 to hide C++ info logs.

Use cases and recipes

Every example runs offline with only the package installed.

Script What it shows
examples/quickstart.py Per-label scores, overall score, flagged labels and a batch
examples/moderation_queue.py A simple approve / review / reject / escalate policy built on classify_batch()
examples/filter_dataset.py Drop toxic rows from a CSV before training another model
examples/community_health.py Share of flagged messages per day, channel and author for a dashboard

The scripts use a 12-row sample file, examples/data/comments.csv.

Limitations. Scores are model estimates, not ground truth. Like any model trained on Jigsaw data, it can be wrong on sarcasm, reclaimed slurs, non-English text, and identity terms in neutral contexts. clean_text() keeps only ASCII letters and digits. Keep a human in the loop for consequential decisions, and tune thresholds on your own labelled data.

Configuration

Option Where Default Effect
clean ToxicCommentClassifier(clean=...) False Apply clean_text() before scoring
batch_size ToxicCommentClassifier(batch_size=...) 256 Inference batch size for lists (must be a positive int)
threshold flagged_labels(text, threshold=...), CLI --threshold 0.5 Score at which a label counts as flagged
TF_CPP_MIN_LOG_LEVEL environment set to 2 if unset TensorFlow C++ log verbosity
KERAS_BACKEND environment tensorflow Keras 3 backend; TensorFlow is the tested one

API overview

Name Returns
ToxicCommentClassifier(clean=False, batch_size=256) Loads the bundled model
.classify(text) dict[str, float]: score per label
.classify_batch(texts) list[dict[str, float]]
.predict(text) float: mean of the six label scores
.predict_batch(texts) list[float]
.flagged_labels(text, threshold=0.5) list[str]: labels with score >= threshold
toxic_classifier.clean_text(text) Lowercased text without URLs and special characters
toxic_classifier.LIST_CLASSES The six label names, in output order
toxic_classifier.tokenizer.WordTokenizer Keras-compatible word tokenizer (from_json, texts_to_sequences)

Passing a single string to a *_batch method raises TypeError; non-string items raise TypeError; an empty list returns [].

Research and references

The model is a standard recurrent text classifier; it builds on:

  • S. Hochreiter and J. Schmidhuber. Long Short-Term Memory. Neural Computation 9(8), 1997.
  • M. Schuster and K. K. Paliwal. Bidirectional Recurrent Neural Networks. IEEE Transactions on Signal Processing 45(11), 1997.
  • Labels and training data: Jigsaw / Conversation AI, Toxic Comment Classification Challenge, Kaggle, 2018 (159,571 training comments).

When to use it

Use it when you want a small, dependency-light (beyond TensorFlow), offline first-pass filter with the Jigsaw label set. If you need higher accuracy, multilingual input, or bias-mitigated scores, transformer-based models (for example Detoxify) are a better fit, at the cost of a larger model and download.

Roadmap

  • Publish evaluation metrics (ROC-AUC per label) on the Jigsaw test split.
  • Optional lighter runtime (for example ONNX export) to avoid the full TensorFlow dependency.
  • Configurable per-label thresholds in the CLI.

Contributing, security, citation, license

Development setup
git clone https://github.com/irfanalidv/toxic_comment_classifier.git
cd toxic_comment_classifier
uv venv && uv pip install -e ".[dev]"
pre-commit install
ruff check . && ruff format --check .
mypy toxic_classifier
pytest --cov=toxic_classifier

Metadata

Release files for toxic-comment-classifier 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toxic-comment-classifier 0.4.1
File Size Uploaded Provenance Actions
toxic_comment_classifier-0.4.1.tar.gz 4.4 MB Without provenance Details for toxic_comment_classifier-0.4.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for toxic-comment-classifier 0.4.1
File Interpreter ABI Platform Actions
toxic_comment_classifier-0.4.1-py3-none-any.whl Python 3 none any Details for toxic_comment_classifier-0.4.1-py3-none-any.whl

Total release size: 8.7 MB

Release files / toxic_comment_classifier-0.4.1.tar.gz

Download URL toxic_comment_classifier-0.4.1.tar.gz
Size 4.4 MB
Tags Source
SHA-256 checksum
How to use checksums
e40f86a1310f34e0562d0d266937628c651db3b3a3f92be3a03ae67177474eb8
BLAKE2b-256 checksum
How to use checksums
e284d83e9d7a88303eb6cbe0fef161a66d89a6cdc73b9e1c8b7d7bb1c3655bf7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.7

Release files / toxic_comment_classifier-0.4.1-py3-none-any.whl

Download URL toxic_comment_classifier-0.4.1-py3-none-any.whl
Size 4.3 MB
Tags Python 3
SHA-256 checksum
How to use checksums
9f44e9f50cdde5396e6820a7a63502b7564671814945e5026ab679880881b700
BLAKE2b-256 checksum
How to use checksums
546f2a210fa93ace8b46819e135852b9d529df134df4ecc0427372e5a5e4eae7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.7

Security for toxic-comment-classifier 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page