toxic-comment-classifier 0.2.4__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toxic_comment_classifier-0.4.0/CHANGELOG.md +44 -0
- toxic_comment_classifier-0.4.0/CITATION.cff +12 -0
- toxic_comment_classifier-0.4.0/MANIFEST.in +10 -0
- toxic_comment_classifier-0.4.0/PKG-INFO +226 -0
- toxic_comment_classifier-0.4.0/README.md +187 -0
- toxic_comment_classifier-0.4.0/examples/quickstart.py +35 -0
- toxic_comment_classifier-0.4.0/pyproject.toml +87 -0
- toxic_comment_classifier-0.4.0/scripts/export_tokenizer.py +45 -0
- toxic_comment_classifier-0.4.0/tests/test_classifier.py +72 -0
- toxic_comment_classifier-0.4.0/tests/test_tokenizer.py +57 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/__init__.py +14 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/cli.py +48 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/model/tokenizer.json +1 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/model.py +102 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/py.typed +0 -0
- toxic_comment_classifier-0.4.0/toxic_classifier/tokenizer.py +79 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/toxic_classifier/utils.py +3 -2
- toxic_comment_classifier-0.4.0/toxic_comment_classifier.egg-info/PKG-INFO +226 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/toxic_comment_classifier.egg-info/SOURCES.txt +11 -1
- toxic_comment_classifier-0.4.0/toxic_comment_classifier.egg-info/entry_points.txt +2 -0
- toxic_comment_classifier-0.4.0/toxic_comment_classifier.egg-info/requires.txt +9 -0
- toxic_comment_classifier-0.2.4/MANIFEST.in +0 -3
- toxic_comment_classifier-0.2.4/PKG-INFO +0 -119
- toxic_comment_classifier-0.2.4/README.md +0 -100
- toxic_comment_classifier-0.2.4/pyproject.toml +0 -36
- toxic_comment_classifier-0.2.4/toxic_classifier/__init__.py +0 -1
- toxic_comment_classifier-0.2.4/toxic_classifier/model/tokenizer.pickle +0 -0
- toxic_comment_classifier-0.2.4/toxic_classifier/model.py +0 -58
- toxic_comment_classifier-0.2.4/toxic_comment_classifier.egg-info/PKG-INFO +0 -119
- toxic_comment_classifier-0.2.4/toxic_comment_classifier.egg-info/requires.txt +0 -4
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/LICENSE.txt +0 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/setup.cfg +0 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/toxic_classifier/model/model.weights.h5 +0 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/toxic_comment_classifier.egg-info/dependency_links.txt +0 -0
- {toxic_comment_classifier-0.2.4 → toxic_comment_classifier-0.4.0}/toxic_comment_classifier.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2026-10-31
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Tokenizer no longer depends on Keras internals or pickle.** 0.3.0 unpickled a Keras
|
|
13
|
+
`Tokenizer` whose class lives at the private path `keras.src.legacy.preprocessing.text`,
|
|
14
|
+
which breaks whenever Keras moves it (and needs `tensorflow.keras.preprocessing`, which
|
|
15
|
+
Keras 3 deprecates). The vocabulary now ships as JSON with a small built-in tokenizer and
|
|
16
|
+
padding function. Outputs are identical to 0.3.0 (verified: same token ids, max score
|
|
17
|
+
difference 1.2e-7), and the package is about 9 MB smaller because only the 19,999 words
|
|
18
|
+
the model can use are kept.
|
|
19
|
+
- Model is built with the public Keras 3 API (`import keras`).
|
|
20
|
+
- `predict_batch()` runs one batched model call instead of one call per text.
|
|
21
|
+
- `predict()`/`predict_batch()` return plain Python `float` instead of `numpy.float64`.
|
|
22
|
+
- Requires Python 3.10+ (tested on 3.10-3.13) and `tensorflow>=2.16` / `keras>=3`.
|
|
23
|
+
Removed the unused `pandas` and `scikit-learn` dependencies.
|
|
24
|
+
- Packaging modernized (`pyproject.toml` only, PEP 639 `license = "MIT"`, `LICENSE.txt` is
|
|
25
|
+
included in distributions again).
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `classify_batch(texts)` and `flagged_labels(text, threshold=0.5)`.
|
|
30
|
+
- `ToxicCommentClassifier(clean=True)` to apply `clean_text()` automatically.
|
|
31
|
+
- `toxic-classifier` command-line tool (JSON Lines output, reads stdin).
|
|
32
|
+
- `toxic_classifier.__version__`, `LIST_CLASSES` and `clean_text` exported at package level,
|
|
33
|
+
type hints and `py.typed`.
|
|
34
|
+
- Input validation with clear `TypeError`s.
|
|
35
|
+
- pytest suite (tokenizer parity, reference scores, batch/single consistency, CLI) and GitHub
|
|
36
|
+
Actions CI on Python 3.10-3.13 with Ruff; community files.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- `clean_text` URL pattern `www.\S+` matched any character after `www`; now `www\.\S+`.
|
|
41
|
+
|
|
42
|
+
## [0.3.0] - 2025
|
|
43
|
+
|
|
44
|
+
- Previous release on PyPI.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
title: "toxic_comment_classifier"
|
|
4
|
+
abstract: "Pre-trained BiLSTM classifier that scores comments for six kinds of toxicity"
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Ali
|
|
7
|
+
given-names: Irfan
|
|
8
|
+
version: "0.4.0"
|
|
9
|
+
date-released: "2026-10-31"
|
|
10
|
+
license: MIT
|
|
11
|
+
repository-code: "https://github.com/irfanalidv/toxic_comment_classifier"
|
|
12
|
+
url: "https://pypi.org/project/toxic_comment_classifier/"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE.txt
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include CITATION.cff
|
|
5
|
+
include toxic_classifier/py.typed
|
|
6
|
+
recursive-include toxic_classifier/model *.h5 *.json
|
|
7
|
+
recursive-include tests *.py
|
|
8
|
+
recursive-include examples *.py
|
|
9
|
+
recursive-include scripts *.py
|
|
10
|
+
global-exclude *.py[cod] __pycache__ .DS_Store
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toxic_comment_classifier
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Pre-trained BiLSTM classifier that scores comments for six kinds of toxicity (toxic, severe_toxic, obscene, threat, insult, identity_hate). Works offline, one line to use.
|
|
5
|
+
Author-email: Md Irfan Ali <irfanali29@hotmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/irfanalidv/toxic_comment_classifier
|
|
8
|
+
Project-URL: Documentation, https://github.com/irfanalidv/toxic_comment_classifier#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/irfanalidv/toxic_comment_classifier
|
|
10
|
+
Project-URL: Issues, https://github.com/irfanalidv/toxic_comment_classifier/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: toxicity,toxic-comments,content-moderation,hate-speech,text-classification,multi-label-classification,nlp,deep-learning,lstm,keras,tensorflow,jigsaw
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE.txt
|
|
30
|
+
Requires-Dist: numpy>=1.23
|
|
31
|
+
Requires-Dist: tensorflow>=2.16
|
|
32
|
+
Requires-Dist: keras>=3.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
36
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
# Toxic Comment Classifier
|
|
43
|
+
|
|
44
|
+
**A pre-trained deep-learning model that scores comments for six kinds of toxicity, offline, in one line of Python.**
|
|
45
|
+
|
|
46
|
+
[](https://pypi.org/project/toxic-comment-classifier/)
|
|
47
|
+
[](https://pypi.org/project/toxic-comment-classifier/)
|
|
48
|
+
[](https://github.com/irfanalidv/toxic_comment_classifier/actions/workflows/tests.yml)
|
|
49
|
+
[](https://pepy.tech/projects/toxic-comment-classifier)
|
|
50
|
+
[](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/LICENSE.txt)
|
|
51
|
+
[](https://github.com/astral-sh/ruff)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
A Python library for classifying toxic comments using deep learning.
|
|
56
|
+
It supports detecting multiple types of toxicity including obscene language, threats, and identity hate.
|
|
57
|
+
|
|
58
|
+
## ✨ Features
|
|
59
|
+
|
|
60
|
+
- **Six labels, multi-label**: `toxic`, `severe_toxic`, `obscene`, `threat`, `insult`, `identity_hate`, each scored independently from 0 to 1.
|
|
61
|
+
- **Pre-trained and bundled**: the model weights and vocabulary ship inside the package, so there is nothing to download and it works offline.
|
|
62
|
+
- **Simple API**: `classify()` for per-label scores, `predict()` for one overall score, batch versions of both, and `flagged_labels()` for threshold-based moderation.
|
|
63
|
+
- **Fast batch scoring**: batches go through the network in a single call.
|
|
64
|
+
- **Command line**: `toxic-classifier "some comment"` prints JSON.
|
|
65
|
+
- **Small model**: a BiLSTM (about 4.6 MB of weights) that runs comfortably on CPU.
|
|
66
|
+
|
|
67
|
+
**The model:** a bidirectional LSTM (embedding 20,000 x 50, BiLSTM 2 x 50, global max pooling, dense layers) with sigmoid outputs for the six labels of the [Jigsaw Toxic Comment Classification](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge) dataset. Its vocabulary was built from that dataset's 159,571 training comments.
|
|
68
|
+
|
|
69
|
+
## 📦 Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install toxic-comment-classifier
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Requires Python 3.10-3.13. TensorFlow (>= 2.16, with Keras 3) is installed automatically.
|
|
76
|
+
|
|
77
|
+
## 🚀 Usage
|
|
78
|
+
|
|
79
|
+
### 🔹 Import and Initialize the Model
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from toxic_classifier import ToxicCommentClassifier
|
|
83
|
+
|
|
84
|
+
# Load the classifier
|
|
85
|
+
model = ToxicCommentClassifier()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
(`from toxic_classifier.model import ToxicCommentClassifier` still works too.)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 🔹 Classify a Single Comment
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
text = "You are so dumb and stupid!"
|
|
96
|
+
scores = model.classify(text)
|
|
97
|
+
|
|
98
|
+
scores
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Example Output:**
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
{'toxic': 0.9889402985572815,
|
|
105
|
+
'severe_toxic': 0.07256769388914108,
|
|
106
|
+
'obscene': 0.6204292178153992,
|
|
107
|
+
'threat': 0.019348442554473877,
|
|
108
|
+
'insult': 0.8664074540138245,
|
|
109
|
+
'identity_hate': 0.040729500353336334}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 🔹 Get Overall Toxicity Score
|
|
115
|
+
|
|
116
|
+
The overall score is the mean of the six label scores.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
toxicity = model.predict(text)
|
|
120
|
+
print(f"Overall Toxicity Score: {toxicity:.4f}")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Example Output:**
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
Overall Toxicity Score: 0.4347
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 🔹 Classify Multiple Comments
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
texts = [
|
|
135
|
+
"I hate you so much!",
|
|
136
|
+
"This is wonderful news.",
|
|
137
|
+
"You're disgusting!",
|
|
138
|
+
"Absolutely love your energy!",
|
|
139
|
+
"You're the worst person ever!",
|
|
140
|
+
"Have a nice day :)"
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
scores = model.predict_batch(texts)
|
|
144
|
+
|
|
145
|
+
for txt, score in zip(texts, scores):
|
|
146
|
+
print(f"Text: {txt} --> Toxicity Score: {score:.4f}")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Example Output:**
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
Text: I hate you so much! --> Toxicity Score: 0.1395
|
|
153
|
+
Text: This is wonderful news. --> Toxicity Score: 0.0013
|
|
154
|
+
Text: You're disgusting! --> Toxicity Score: 0.3110
|
|
155
|
+
Text: Absolutely love your energy! --> Toxicity Score: 0.0088
|
|
156
|
+
Text: You're the worst person ever! --> Toxicity Score: 0.0937
|
|
157
|
+
Text: Have a nice day :) --> Toxicity Score: 0.0115
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use `model.classify_batch(texts)` to get the per-label dictionaries for a list of texts.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### 🔹 Flag Comments Above a Threshold
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
model.flagged_labels("You are so dumb and stupid!", threshold=0.5)
|
|
168
|
+
# ['toxic', 'obscene', 'insult']
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### 🔹 Command Line
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
toxic-classifier "go kill yourself"
|
|
177
|
+
# {"text": "go kill yourself", "toxicity": 0.374754, "flagged": ["toxic", "insult"], "scores": {"toxic": 0.888507, ...}}
|
|
178
|
+
|
|
179
|
+
# One comment per line from stdin, JSON Lines out
|
|
180
|
+
cat comments.txt | toxic-classifier --threshold 0.7
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 💡 Use Cases
|
|
184
|
+
|
|
185
|
+
- **Comment moderation**: hold back or review comments where `flagged_labels()` is non-empty.
|
|
186
|
+
- **Community health dashboards**: track the share of toxic messages over time with `predict_batch()`.
|
|
187
|
+
- **Dataset filtering**: drop toxic samples before training or fine-tuning another model.
|
|
188
|
+
- **Triage**: route `threat` or `identity_hate` hits to a human reviewer first.
|
|
189
|
+
|
|
190
|
+
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, so keep a human in the loop for consequential decisions.
|
|
191
|
+
|
|
192
|
+
## 🧩 API Overview
|
|
193
|
+
|
|
194
|
+
| Method | Returns |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `ToxicCommentClassifier(clean=False, batch_size=256)` | Loads the bundled model. `clean=True` applies `clean_text()` before scoring. |
|
|
197
|
+
| `classify(text)` | `dict[str, float]`: score per label |
|
|
198
|
+
| `classify_batch(texts)` | `list[dict[str, float]]` |
|
|
199
|
+
| `predict(text)` | `float`: mean of the six label scores |
|
|
200
|
+
| `predict_batch(texts)` | `list[float]` |
|
|
201
|
+
| `flagged_labels(text, threshold=0.5)` | `list[str]`: labels with score >= threshold |
|
|
202
|
+
| `toxic_classifier.clean_text(text)` | Lowercases and removes URLs and special characters |
|
|
203
|
+
| `toxic_classifier.LIST_CLASSES` | The six label names, in output order |
|
|
204
|
+
|
|
205
|
+
## ⚙️ Configuration
|
|
206
|
+
|
|
207
|
+
- `TF_CPP_MIN_LOG_LEVEL`: the library sets it to `2` (if unset) to hide TensorFlow's C++ info logs.
|
|
208
|
+
- `KERAS_BACKEND`: the model is built with Keras 3. TensorFlow is the default and tested backend.
|
|
209
|
+
|
|
210
|
+
## 🤝 Contributing
|
|
211
|
+
|
|
212
|
+
Contributions are welcome. See [CONTRIBUTING.md](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CONTRIBUTING.md).
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
git clone https://github.com/irfanalidv/toxic_comment_classifier.git
|
|
216
|
+
cd toxic_comment_classifier
|
|
217
|
+
pip install -e ".[dev]"
|
|
218
|
+
pytest
|
|
219
|
+
ruff check . && ruff format --check .
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
See the [CHANGELOG](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CHANGELOG.md) for release notes.
|
|
223
|
+
|
|
224
|
+
## 📄 License
|
|
225
|
+
|
|
226
|
+
MIT. See [LICENSE.txt](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/LICENSE.txt).
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Toxic Comment Classifier
|
|
4
|
+
|
|
5
|
+
**A pre-trained deep-learning model that scores comments for six kinds of toxicity, offline, in one line of Python.**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/toxic-comment-classifier/)
|
|
8
|
+
[](https://pypi.org/project/toxic-comment-classifier/)
|
|
9
|
+
[](https://github.com/irfanalidv/toxic_comment_classifier/actions/workflows/tests.yml)
|
|
10
|
+
[](https://pepy.tech/projects/toxic-comment-classifier)
|
|
11
|
+
[](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/LICENSE.txt)
|
|
12
|
+
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
A Python library for classifying toxic comments using deep learning.
|
|
17
|
+
It supports detecting multiple types of toxicity including obscene language, threats, and identity hate.
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Six labels, multi-label**: `toxic`, `severe_toxic`, `obscene`, `threat`, `insult`, `identity_hate`, each scored independently from 0 to 1.
|
|
22
|
+
- **Pre-trained and bundled**: the model weights and vocabulary ship inside the package, so there is nothing to download and it works offline.
|
|
23
|
+
- **Simple API**: `classify()` for per-label scores, `predict()` for one overall score, batch versions of both, and `flagged_labels()` for threshold-based moderation.
|
|
24
|
+
- **Fast batch scoring**: batches go through the network in a single call.
|
|
25
|
+
- **Command line**: `toxic-classifier "some comment"` prints JSON.
|
|
26
|
+
- **Small model**: a BiLSTM (about 4.6 MB of weights) that runs comfortably on CPU.
|
|
27
|
+
|
|
28
|
+
**The model:** a bidirectional LSTM (embedding 20,000 x 50, BiLSTM 2 x 50, global max pooling, dense layers) with sigmoid outputs for the six labels of the [Jigsaw Toxic Comment Classification](https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge) dataset. Its vocabulary was built from that dataset's 159,571 training comments.
|
|
29
|
+
|
|
30
|
+
## 📦 Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install toxic-comment-classifier
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.10-3.13. TensorFlow (>= 2.16, with Keras 3) is installed automatically.
|
|
37
|
+
|
|
38
|
+
## 🚀 Usage
|
|
39
|
+
|
|
40
|
+
### 🔹 Import and Initialize the Model
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from toxic_classifier import ToxicCommentClassifier
|
|
44
|
+
|
|
45
|
+
# Load the classifier
|
|
46
|
+
model = ToxicCommentClassifier()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
(`from toxic_classifier.model import ToxicCommentClassifier` still works too.)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 🔹 Classify a Single Comment
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
text = "You are so dumb and stupid!"
|
|
57
|
+
scores = model.classify(text)
|
|
58
|
+
|
|
59
|
+
scores
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Example Output:**
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
{'toxic': 0.9889402985572815,
|
|
66
|
+
'severe_toxic': 0.07256769388914108,
|
|
67
|
+
'obscene': 0.6204292178153992,
|
|
68
|
+
'threat': 0.019348442554473877,
|
|
69
|
+
'insult': 0.8664074540138245,
|
|
70
|
+
'identity_hate': 0.040729500353336334}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 🔹 Get Overall Toxicity Score
|
|
76
|
+
|
|
77
|
+
The overall score is the mean of the six label scores.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
toxicity = model.predict(text)
|
|
81
|
+
print(f"Overall Toxicity Score: {toxicity:.4f}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Example Output:**
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
Overall Toxicity Score: 0.4347
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 🔹 Classify Multiple Comments
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
texts = [
|
|
96
|
+
"I hate you so much!",
|
|
97
|
+
"This is wonderful news.",
|
|
98
|
+
"You're disgusting!",
|
|
99
|
+
"Absolutely love your energy!",
|
|
100
|
+
"You're the worst person ever!",
|
|
101
|
+
"Have a nice day :)"
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
scores = model.predict_batch(texts)
|
|
105
|
+
|
|
106
|
+
for txt, score in zip(texts, scores):
|
|
107
|
+
print(f"Text: {txt} --> Toxicity Score: {score:.4f}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Example Output:**
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
Text: I hate you so much! --> Toxicity Score: 0.1395
|
|
114
|
+
Text: This is wonderful news. --> Toxicity Score: 0.0013
|
|
115
|
+
Text: You're disgusting! --> Toxicity Score: 0.3110
|
|
116
|
+
Text: Absolutely love your energy! --> Toxicity Score: 0.0088
|
|
117
|
+
Text: You're the worst person ever! --> Toxicity Score: 0.0937
|
|
118
|
+
Text: Have a nice day :) --> Toxicity Score: 0.0115
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Use `model.classify_batch(texts)` to get the per-label dictionaries for a list of texts.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### 🔹 Flag Comments Above a Threshold
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
model.flagged_labels("You are so dumb and stupid!", threshold=0.5)
|
|
129
|
+
# ['toxic', 'obscene', 'insult']
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 🔹 Command Line
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
toxic-classifier "go kill yourself"
|
|
138
|
+
# {"text": "go kill yourself", "toxicity": 0.374754, "flagged": ["toxic", "insult"], "scores": {"toxic": 0.888507, ...}}
|
|
139
|
+
|
|
140
|
+
# One comment per line from stdin, JSON Lines out
|
|
141
|
+
cat comments.txt | toxic-classifier --threshold 0.7
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 💡 Use Cases
|
|
145
|
+
|
|
146
|
+
- **Comment moderation**: hold back or review comments where `flagged_labels()` is non-empty.
|
|
147
|
+
- **Community health dashboards**: track the share of toxic messages over time with `predict_batch()`.
|
|
148
|
+
- **Dataset filtering**: drop toxic samples before training or fine-tuning another model.
|
|
149
|
+
- **Triage**: route `threat` or `identity_hate` hits to a human reviewer first.
|
|
150
|
+
|
|
151
|
+
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, so keep a human in the loop for consequential decisions.
|
|
152
|
+
|
|
153
|
+
## 🧩 API Overview
|
|
154
|
+
|
|
155
|
+
| Method | Returns |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `ToxicCommentClassifier(clean=False, batch_size=256)` | Loads the bundled model. `clean=True` applies `clean_text()` before scoring. |
|
|
158
|
+
| `classify(text)` | `dict[str, float]`: score per label |
|
|
159
|
+
| `classify_batch(texts)` | `list[dict[str, float]]` |
|
|
160
|
+
| `predict(text)` | `float`: mean of the six label scores |
|
|
161
|
+
| `predict_batch(texts)` | `list[float]` |
|
|
162
|
+
| `flagged_labels(text, threshold=0.5)` | `list[str]`: labels with score >= threshold |
|
|
163
|
+
| `toxic_classifier.clean_text(text)` | Lowercases and removes URLs and special characters |
|
|
164
|
+
| `toxic_classifier.LIST_CLASSES` | The six label names, in output order |
|
|
165
|
+
|
|
166
|
+
## ⚙️ Configuration
|
|
167
|
+
|
|
168
|
+
- `TF_CPP_MIN_LOG_LEVEL`: the library sets it to `2` (if unset) to hide TensorFlow's C++ info logs.
|
|
169
|
+
- `KERAS_BACKEND`: the model is built with Keras 3. TensorFlow is the default and tested backend.
|
|
170
|
+
|
|
171
|
+
## 🤝 Contributing
|
|
172
|
+
|
|
173
|
+
Contributions are welcome. See [CONTRIBUTING.md](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CONTRIBUTING.md).
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/irfanalidv/toxic_comment_classifier.git
|
|
177
|
+
cd toxic_comment_classifier
|
|
178
|
+
pip install -e ".[dev]"
|
|
179
|
+
pytest
|
|
180
|
+
ruff check . && ruff format --check .
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See the [CHANGELOG](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CHANGELOG.md) for release notes.
|
|
184
|
+
|
|
185
|
+
## 📄 License
|
|
186
|
+
|
|
187
|
+
MIT. See [LICENSE.txt](https://github.com/irfanalidv/toxic_comment_classifier/blob/main/LICENSE.txt).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Quick start for toxic_comment_classifier (run: python examples/quickstart.py)."""
|
|
2
|
+
|
|
3
|
+
from toxic_classifier import ToxicCommentClassifier, clean_text
|
|
4
|
+
|
|
5
|
+
classifier = ToxicCommentClassifier()
|
|
6
|
+
|
|
7
|
+
# === Single comment ===
|
|
8
|
+
raw_text = "YOU are an amazing person!"
|
|
9
|
+
cleaned = clean_text(raw_text)
|
|
10
|
+
print(f"\nCleaned text: {cleaned}")
|
|
11
|
+
|
|
12
|
+
scores = classifier.classify(cleaned)
|
|
13
|
+
print("Toxicity scores (single):")
|
|
14
|
+
for label, score in scores.items():
|
|
15
|
+
print(f" {label}: {score:.4f}")
|
|
16
|
+
|
|
17
|
+
avg = classifier.predict(cleaned)
|
|
18
|
+
print(f"Average toxicity score: {avg:.4f}")
|
|
19
|
+
|
|
20
|
+
# === Batch ===
|
|
21
|
+
print("\nBatch predictions:")
|
|
22
|
+
texts = [
|
|
23
|
+
"I hate you so much!",
|
|
24
|
+
"This is wonderful news.",
|
|
25
|
+
"You're disgusting!",
|
|
26
|
+
"Absolutely love your energy!",
|
|
27
|
+
"You're the worst person ever!",
|
|
28
|
+
"Have a nice day :)",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
for txt, score in zip(texts, classifier.predict_batch(texts), strict=True):
|
|
32
|
+
print(f"Text: {txt}\n-> Toxicity Score: {score:.4f}\n")
|
|
33
|
+
|
|
34
|
+
# Labels at or above a threshold
|
|
35
|
+
print(classifier.flagged_labels("You are so dumb and stupid!", threshold=0.5))
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "toxic_comment_classifier"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Pre-trained BiLSTM classifier that scores comments for six kinds of toxicity (toxic, severe_toxic, obscene, threat, insult, identity_hate). Works offline, one line to use."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE.txt"]
|
|
13
|
+
authors = [{ name = "Md Irfan Ali", email = "irfanali29@hotmail.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"toxicity",
|
|
16
|
+
"toxic-comments",
|
|
17
|
+
"content-moderation",
|
|
18
|
+
"hate-speech",
|
|
19
|
+
"text-classification",
|
|
20
|
+
"multi-label-classification",
|
|
21
|
+
"nlp",
|
|
22
|
+
"deep-learning",
|
|
23
|
+
"lstm",
|
|
24
|
+
"keras",
|
|
25
|
+
"tensorflow",
|
|
26
|
+
"jigsaw",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Intended Audience :: Science/Research",
|
|
32
|
+
"Operating System :: OS Independent",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
39
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
40
|
+
"Topic :: Text Processing :: Linguistic",
|
|
41
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
42
|
+
"Typing :: Typed",
|
|
43
|
+
]
|
|
44
|
+
dependencies = [
|
|
45
|
+
"numpy>=1.23",
|
|
46
|
+
"tensorflow>=2.16",
|
|
47
|
+
"keras>=3.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=7.0.0",
|
|
53
|
+
"ruff>=0.6.0",
|
|
54
|
+
"build>=1.0.0",
|
|
55
|
+
"twine>=5.0.0",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[project.scripts]
|
|
59
|
+
toxic-classifier = "toxic_classifier.cli:main"
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/irfanalidv/toxic_comment_classifier"
|
|
63
|
+
Documentation = "https://github.com/irfanalidv/toxic_comment_classifier#readme"
|
|
64
|
+
Repository = "https://github.com/irfanalidv/toxic_comment_classifier"
|
|
65
|
+
Issues = "https://github.com/irfanalidv/toxic_comment_classifier/issues"
|
|
66
|
+
Changelog = "https://github.com/irfanalidv/toxic_comment_classifier/blob/main/CHANGELOG.md"
|
|
67
|
+
|
|
68
|
+
[tool.setuptools]
|
|
69
|
+
packages = ["toxic_classifier"]
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.package-data]
|
|
72
|
+
toxic_classifier = ["py.typed", "model/*.h5", "model/*.json"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff]
|
|
75
|
+
target-version = "py310"
|
|
76
|
+
line-length = 100
|
|
77
|
+
extend-exclude = ["*.md"]
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint]
|
|
80
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint.isort]
|
|
83
|
+
known-first-party = ["toxic_classifier"]
|
|
84
|
+
|
|
85
|
+
[tool.pytest.ini_options]
|
|
86
|
+
testpaths = ["tests"]
|
|
87
|
+
addopts = "-ra -q"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Convert the original pickled Keras ``Tokenizer`` into the JSON vocabulary shipped with
|
|
2
|
+
the package (``toxic_classifier/model/tokenizer.json``).
|
|
3
|
+
|
|
4
|
+
Only words the model can actually see are kept (index < ``num_words``). Requires Keras 3,
|
|
5
|
+
because the pickle references ``keras.src.legacy.preprocessing.text.Tokenizer``.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python scripts/export_tokenizer.py tokenizer.pickle toxic_classifier/model/tokenizer.json
|
|
9
|
+
|
|
10
|
+
(The original ``tokenizer.pickle`` is in the git history, release 0.3.0.)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import pickle
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main(src: str, dst: str) -> None:
|
|
21
|
+
with open(src, "rb") as handle:
|
|
22
|
+
tok = pickle.load(handle) # noqa: S301 - trusted, project-owned artifact
|
|
23
|
+
|
|
24
|
+
num_words = tok.num_words
|
|
25
|
+
word_index = {
|
|
26
|
+
word: idx for word, idx in tok.word_index.items() if num_words is None or idx < num_words
|
|
27
|
+
}
|
|
28
|
+
payload = {
|
|
29
|
+
"config": {
|
|
30
|
+
"num_words": num_words,
|
|
31
|
+
"filters": tok.filters,
|
|
32
|
+
"lower": tok.lower,
|
|
33
|
+
"split": tok.split,
|
|
34
|
+
"char_level": tok.char_level,
|
|
35
|
+
"oov_token": tok.oov_token,
|
|
36
|
+
},
|
|
37
|
+
"word_index": dict(sorted(word_index.items(), key=lambda kv: kv[1])),
|
|
38
|
+
}
|
|
39
|
+
with open(dst, "w", encoding="utf-8") as handle:
|
|
40
|
+
json.dump(payload, handle, ensure_ascii=False, separators=(",", ":"))
|
|
41
|
+
print(f"wrote {len(word_index)} words to {dst}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main(sys.argv[1], sys.argv[2])
|