NSFW Detector v2 (weight-edited, experimental)
Content note. This is a classifier for adult content. It was evaluated on datasets that contain sexually explicit material.
Status: experimental. Use it as a triage filter (allow / human review / block), not as a sole automated decision-maker. See Limitations.
A very small image classifier (MobileNetV3-small, 1.53M parameters) that outputs the probability that an image is NSFW.
It is a weight-edited version of Pankaj8922/nsfw-detector-v2. 151 numbers out of 1,532,018 (0.01%) were changed (150 weights and 1 bias in the final classifier layer). Every other parameter is bit-for-bit identical to the original, and no network was retrained or fine-tuned.
- Labels:
0= sfw,1= nsfw (same as the original) - Input: 224×224 RGB (
Resize(224)+CenterCrop(224), ImageNet mean/std) - Architecture:
mobilenetv3_small_100(timm)
Why this exists
The original model reported 99.87% accuracy, but that was measured on the distribution it was trained on: its NSFW images came from sticker-pack data and its SFW images from general image datasets. On two independent NSFW benchmarks it performed much worse (AUC 0.77 and 0.87), flagging most ordinary images and most blank frames as NSFW. A model whose two classes come from different sources can learn source and style cues instead of content, and this behavior is consistent with that. The edit corrects the classifier's decision layer using content-matched data, without touching the rest of the network.
Results
Evaluated on parts of two datasets that were not used for fitting (random splits). "Original" is nsfw-detector-v2. Both datasets use the five-class scheme drawing / hentai / neutral / porn / sexy; sexy (suggestive) is excluded from all metrics because whether it counts as NSFW is a policy choice.
| Dataset 1 test | Dataset 2 test | |
|---|---|---|
| Source | strangerguardhf/NSFW-MultiDomain-Classification |
deepghs/nsfw_detect |
| Safe classes | normal photos, anime, blank frames | neutral, drawing |
| AUC, original → this model | 0.768 → 0.946 | 0.865 → 0.944 |
| 95% CI of the gain (bootstrap) | +0.18 (+0.13 … +0.23) | +0.08 (+0.06 … +0.10) |
| Explicit images caught at p>0.5 | 100% → 98% | 93% → 92% |
False-positive rate (safe images flagged as NSFW), original → this model:
| Class | at p>0.5 | at 95% explicit recall |
|---|---|---|
| Normal photos (dataset 1) | 87% → 20% | 66% → 14% |
| Anime (dataset 1) | 82% → 56% | 60% → 49% |
| Blank frames | 41% → 0% | — |
| Neutral (dataset 2) | 30% → 9% | 32% → 11% |
| Drawings (dataset 2) | 66% → 38% | 73% → 46% |
On 15 real blank frames (flat black, white, gray, noise), the original flagged 12 and this model flags none.
Read these numbers carefully:
- The released weights were refit on all of the data, so the released file has no independent test of its own. The table above is for the identical recipe fit on the fitting split only.
- Both datasets share a class taxonomy and may share source images. About 1% of dataset 2 images were near-duplicates of dataset 1 fit images and were removed; the true overlap could be larger. "Held-out" here means the same family of data, not your real traffic.
- Sample sizes are modest (about 630 images in the dataset 2 test split; 380 in dataset 1, of which 150 are synthetic blank frames).
Suggested use: three tiers
p = P(nsfw). Suggested starting thresholds (stored in config.json):
| p | action |
|---|---|
| < 0.44 | allow |
| 0.44 – 0.936 | human review |
| ≥ 0.936 | block |
These thresholds were chosen on cross-validated scores of the fitting data (review starts where ~95% of explicit images are caught; block starts above the 99th percentile of neutral/normal images). Re-tune them on images that match your own traffic. Approximate behavior on held-out data with similar thresholds:
| Class | allow | review | block |
|---|---|---|---|
| Neutral (dataset 2) | ~89% | ~11% | ~1% |
| Normal photos (dataset 1) | ~76% | ~23% | ~1% |
| Drawings (dataset 2) | ~54% | ~42% | ~4% |
| Anime (dataset 1) | ~42% | ~56% | ~2% |
| Hentai / porn (datasets 1 and 2) | 2–5% | 26–38% | 60–69% |
Expect a substantial human-review queue, roughly a quarter or more of traffic, depending on your mix. Run in shadow mode (flag, don't act) before enabling any automatic blocking.
How to use
import json, torch, timm
import torchvision.transforms as T
from PIL import Image
from huggingface_hub import hf_hub_download
repo = "Pankaj8922/experiment" # <- replace with this repository's id
cfg = json.load(open(hf_hub_download(repo, "config.json")))
model = timm.create_model("mobilenetv3_small_100", pretrained=False, num_classes=2)
model.load_state_dict(torch.load(hf_hub_download(repo, "pytorch_model.bin"), map_location="cpu"))
model.eval()
tf = T.Compose([T.Resize(224), T.CenterCrop(224), T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
x = tf(Image.open("image.jpg").convert("RGB")).unsqueeze(0)
with torch.no_grad():
p = torch.softmax(model(x), dim=1)[0, 1].item() # P(nsfw)
t = cfg["thresholds"]
action = "allow" if p < t["review_from"] else "review" if p < t["block_from"] else "block"
How the edit was made
No gradient training of the network. The penultimate (1024-d) features of each image were computed once, and only the final linear layer was edited:
- Starting from the original weights, a greedy sparse selection repeatedly adds the single weight whose change most reduces a class-balanced logistic loss, then refits the changed values with an L2 penalty toward the originals. This stops at 150 weights (plus a bias shift).
- The loss gives the NSFW class 3× weight, to protect recall, and weights the two datasets equally.
- Recipe settings (number of weights, regularization, class weight) were chosen by 3-fold cross-validation on the fitting pool only, then evaluated once on the held-out splits.
- Fitting data: about 770 real images from dataset 1, 1,580 from dataset 2 (400 per class sampled, 26 near-duplicates removed), and 60 synthetic blank/flat frames labeled SFW.
Labels: dataset 1: normal and anime = sfw, hentai and porn = nsfw. Dataset 2: neutral and drawing = sfw, hentai and porn = nsfw. sexy classes were left out.
Findings that shaped the design: a linear probe on all 1,024 features reaches about 0.95 AUC, so this edit sits at the ceiling of what the existing backbone features can support; rewriting hidden-layer weights added no measurable gain; fit on one dataset alone, the edit did not transfer to the other (AUC 0.94 on its own dataset but 0.88 on the other), which is why both were used.
Limitations
- Drawings and anime remain the weak spot. Roughly half of anime/drawings are still flagged or sent to review. The backbone's features do not separate them from explicit illustration well, and an edit of the final layer cannot fix that. Further gains likely require training.
- Suggestive ("sexy") content is neither target nor non-target: at p>0.5 about three quarters is flagged.
- Measured on two datasets only. Not tested on real user traffic, on stickers, or on other languages/cultures of imagery. Accuracy on your data may differ substantially, in either direction.
- Center crop: only the central square region of an image is analyzed, so content near the edges of non-square images may be missed.
- Fairness is unevaluated. NSFW classifiers can behave differently across skin tones, body types, clothing, and art styles. False positives can suppress legitimate content (medical, art, education); false negatives let harmful content through.
- Not a legal-compliance tool. It is not designed or evaluated for detecting illegal material (for example CSAM). Use dedicated, legally sanctioned tools for that.
Intended use
Pre-filtering or prioritizing content for human review in moderation workflows, research on content-moderation models, and as a worked example of weight-level model editing. Out of scope: sole automated enforcement, any use where a false positive or false negative causes serious harm without human review, and classifying images of minors.
Data and licensing
- Base model:
timm/mobilenetv3_small_100.lamb_in1k, fine-tuned by the author intoPankaj8922/nsfw-detector-v2(onPankaj8922/stickers-binary-v2). - Edit fitted and evaluated on samples of
strangerguardhf/NSFW-MultiDomain-Classificationanddeepghs/nsfw_detect. Both are gated datasets containing explicit material; follow their terms. No images are included in this repository. - License of this repository: Apache-2.0 (matching the base model). Check the licenses and terms of the datasets above for your own use.
Citation
If you use this model, please cite this repository and the base model and datasets above.