ONNX Model Zoo

khasinski/siglip2-moderation-heads

By khasinski · ↓ 0 · ♥ 3 · Updated 2026-01-18

What's in this repo

This page lists the files in khasinski/siglip2-moderation-heads, including ai_generated_head.onnx, moderation_combined.onnx, nsfw_head.onnx and more.

  • 4 ONNX models (.onnx files)
  • 12 files, including 4 .onnx, 4 .data, 1 .md, 1 .py, 1 .rb
  • Last updated 2026-01-18

ONNX files

File Size Download
ai_generated_head.onnx — Download
moderation_combined.onnx — Download
nsfw_head.onnx — Download
violence_head.onnx — Download

How to use

These ONNX files can be loaded with ONNX Runtime. To inspect a model graph, upload the file to the Netron viewer. Download links point to the original files on Hugging Face.

README

SigLIP2 Moderation Heads

Lightweight MLP classification heads for image moderation, designed to run on top of frozen SigLIP2 embeddings.

Model Description

Three independent binary classifiers for detecting:

Head Task Test AUC Test Accuracy
nsfw NSFW/explicit content 99.4% 96.6%
ai_generated AI-generated images 98.5% 93.3%
violence Violent/graphic content 99.5% 96.8%

Each head is a tiny MLP (~197K params, <1MB) that takes a 768-dim SigLIP2 embedding and outputs a probability score.

Architecture

SigLIP2 Encoder (frozen, 400M params)
         │
         ▼ 768-dim embedding
         │
    ┌────┴────┐
    │ MLP Head │  ← Linear(768,256) → ReLU → Dropout(0.2) → Linear(256,1)
    └────┬────┘
         │
         ▼ logits → sigmoid → probability

Quick Start

Python (PyTorch)

import torch
from transformers import AutoModel, AutoProcessor
from PIL import Image

# Load SigLIP2 encoder
processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-256")
encoder = AutoModel.from_pretrained("google/siglip2-base-patch16-256")

# Load moderation head
head = torch.hub.load("khasinski/siglip2-moderation-heads", "nsfw_head")
# Or: head = torch.hub.load("khasinski/siglip2-moderation-heads", "violence_head")
# Or: head = torch.hub.load("khasinski/siglip2-moderation-heads", "ai_generated_head")

# Process image
image = Image.open("test.jpg").convert("RGB")
inputs = processor(images=image, return_tensors="pt")

# Get embedding and classify
with torch.no_grad():
    embedding = encoder.get_image_features(**inputs)
    embedding = embedding / embedding.norm(dim=-1, keepdim=True)  # normalize
    logits = head(embedding)
    probability = torch.sigmoid(logits).item()

print(f"NSFW probability: {probability:.3f}")

Python (ONNX Runtime)

import onnxruntime as ort
import numpy as np
from transformers import AutoModel, AutoProcessor
from PIL import Image

# Load SigLIP2
processor = AutoProcessor.from_pretrained("google/siglip2-base-patch16-256")
encoder = AutoModel.from_pretrained("google/siglip2-base-patch16-256")

# Load ONNX heads
nsfw_session = ort.InferenceSession("nsfw_head.onnx")
violence_session = ort.InferenceSession("violence_head.onnx")
ai_gen_session = ort.InferenceSession("ai_generated_head.onnx")

def get_embedding(image_path):
    image = Image.open(image_path).convert("RGB")
    inputs = processor(images=image, return_tensors="pt")
    with torch.no_grad():
        embedding = encoder.get_image_features(**inputs)
        embedding = embedding / embedding.norm(dim=-1, keepdim=True)
    return embedding.numpy()

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# Classify
embedding = get_embedding("test.jpg")

nsfw_prob = sigmoid(nsfw_session.run(None, {"embedding": embedding})[0])[0, 0]
violence_prob = sigmoid(violence_session.run(None, {"embedding": embedding})[0])[0, 0]
ai_gen_prob = sigmoid(ai_gen_session.run(None, {"embedding": embedding})[0])[0, 0]

print(f"NSFW: {nsfw_prob:.3f}, Violence: {violence_prob:.3f}, AI-Gen: {ai_gen_prob:.3f}")

Ruby (ONNX Runtime)

require 'onnxruntime'

# Assumes you have SigLIP2 embeddings from your existing Ruby setup
# (e.g., via the onnxruntime gem with the SigLIP2 ONNX model)

class ModerationClassifier
  def initialize(model_dir = ".")
    @nsfw = OnnxRuntime::Model.new(File.join(model_dir, "nsfw_head.onnx"))
    @violence = OnnxRuntime::Model.new(File.join(model_dir, "violence_head.onnx"))
    @ai_generated = OnnxRuntime::Model.new(File.join(model_dir, "ai_generated_head.onnx"))
  end

  def classify(embedding)
    # embedding should be a normalized 768-dim array
    input = { "embedding" => [embedding] }

    {
      nsfw: sigmoid(@nsfw.predict(input)["logits"][0][0]),
      violence: sigmoid(@violence.predict(input)["logits"][0][0]),
      ai_generated: sigmoid(@ai_generated.predict(input)["logits"][0][0])
    }
  end

  private

  def sigmoid(x)
    1.0 / (1.0 + Math.exp(-x))
  end
end

# Usage
classifier = ModerationClassifier.new("path/to/models")
embedding = get_siglip_embedding("test.jpg")  # your SigLIP2 code
scores = classifier.classify(embedding)

puts "NSFW: #{scores[:nsfw].round(3)}"
puts "Violence: #{scores[:violence].round(3)}"
puts "AI-Generated: #{scores[:ai_generated].round(3)}"

Threshold Recommendations

Use Case NSFW Violence AI-Gen
Strict (minimize false negatives) 0.3 0.3 0.3
Balanced (default) 0.5 0.5 0.5
Permissive (minimize false positives) 0.7 0.7 0.7

Training Details

Datasets

Head Dataset Samples Split
NSFW deepghs/nsfw_detect 28,000 80/20
Violence real-life-violence-situations 11,073 80/20
AI-Generated raw_real_fake_images 9,306 train/test

Training Configuration

  • Optimizer: AdamW (lr=1e-3, weight_decay=0.01)
  • Loss: BCEWithLogitsLoss
  • Epochs: 15-20
  • Batch size: 1024 (on pre-computed embeddings)
  • Architecture: Linear(768→256) → ReLU → Dropout(0.2) → Linear(256→1)

Metrics

NSFW Head
              precision    recall  f1-score   support
        Safe       0.96      0.95      0.96      2240
        NSFW       0.97      0.97      0.97      3360
    accuracy                           0.97      5600
Violence Head
              precision    recall  f1-score   support
 Non-violent       0.96      0.97      0.97      1046
     Violent       0.97      0.96      0.97      1169
    accuracy                           0.97      2215
AI-Generated Head
              precision    recall  f1-score   support
        Real       0.92      0.96      0.94      1027
      AI-Gen       0.95      0.90      0.92       835
    accuracy                           0.93      1862

Limitations

  • NSFW: Trained primarily on photographic content; may be less accurate on illustrations/anime
  • Violence: Trained on real-life violence; graphic illustrations may have lower recall
  • AI-Generated: Trained on limited generators (primarily Stable Diffusion variants); newer models like Midjourney v6, DALL-E 3, Flux may evade detection

Files

File Size Description
nsfw_head.onnx + .data 789 KB NSFW detection head
violence_head.onnx + .data 789 KB Violence detection head
ai_generated_head.onnx + .data 789 KB AI-generated detection head
moderation_combined.onnx 2.3 MB All three heads in one model

License

Apache 2.0

Citation

@misc{siglip2-moderation-heads,
  author = {Chris Hasinski},
  title = {SigLIP2 Moderation Heads},
  year = {2025},
  publisher = {HuggingFace},
  url = {https://huggingface.co/khasinski/siglip2-moderation-heads}
}

Model info

image-classification transformers
transformers onnx moderation nsfw violence ai-generated siglip image-classification content-moderation en dataset:deepghs/nsfw_detect dataset:abdulmananraja/real-life-violence-situations
↓ 0 ♥ 3

Created: 2026-01-18

Updated: 2026-01-18

View on Hugging Face

Files (12)

.gitattributes
README.md
ai_generated_head.onnx ONNX
ai_generated_head.onnx.data
examples/inference_python.py
examples/inference_ruby.rb
moderation_combined.onnx ONNX
moderation_combined.onnx.data
nsfw_head.onnx ONNX
nsfw_head.onnx.data
violence_head.onnx ONNX
violence_head.onnx.data

Related models