contemmcm/gemma-3-4b-full-bluesky-moderation
gemma-3-4b-full-bluesky-moderation
This model is a fine-tuned version of google/gemma-3-4b-it on the ModerationBenchV2 dataset. It achieves the following results on the evaluation set:
- Loss: 0.2356
- Micro F1: 0.7142
- Macro F1: 0.3023
- Macro F1 Seen: 0.3500
- Macro Ap: 0.4773
- Exact Match: 0.8218
- Safe Accuracy: 0.9007
Model description
A multi-label content-moderation classifier for Bluesky posts (text and images). It is a full fine-tune of Gemma 3 4B with the language-model head replaced by a classification head: one forward pass returns 22 independent sigmoid scores, one per moderation label, trained with binary cross-entropy. A post can carry several labels at once, and no label reaching its threshold means "safe". There is no generated text to parse.
The 22 labels, in output order (also in label_space.json):
How to use
No custom code is needed: the checkpoint loads into transformers' stock Gemma3ForSequenceClassification (requires transformers>=5).
import torch
from transformers import AutoProcessor, Gemma3ForSequenceClassification
MODEL = "contemmcm/gemma-3-4b-full-bluesky-moderation"
# must be exactly this text: the model was trained with it
SYSTEM_PROMPT = (
"You are a content moderation classifier for social media posts. "
"Read the post and assess which moderation labels apply."
)
processor = AutoProcessor.from_pretrained(MODEL)
model = Gemma3ForSequenceClassification.from_pretrained(
MODEL, dtype=torch.bfloat16, device_map="cuda:0"
).eval()
# A real post: https://bsky.app/profile/did:plc:3htuuatm2fchjvon7tpc2jge/post/3mtd2ffcsvk22
text = (
"Get Your Summer Tees Here & Please FOLLOW & SHARE. Tees Starting At Just $16. (Wait for "
"the 35-40% off sales, 2-3 times a month.) I, Also, Have Mugs, Tote Bags, Magnets, "
"Stickers, Etc., Available. Over 250 Designs! If You DO Make A Purchase, Thanks In "
"Advance! www.teepublic.com/user/roszelle-art"
)
image = (
"https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:3htuuatm2fchjvon7tpc2jge/"
"bafkreigp5m25icqkxlrm6pvbpzvey2g5bchrqonlyaypo7cs6ahqsekaey"
)
messages = [
{"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]},
{"role": "user", "content": [{"type": "image", "url": image},
{"type": "text", "text": text}]},
]
inputs = processor.apply_chat_template(
messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt"
).to(model.device)
with torch.no_grad():
probs = torch.sigmoid(model(**inputs).logits[0].float()).tolist()
# a label applies when its probability reaches that label's threshold; none means "safe"
import json
from huggingface_hub import hf_hub_download
labels = json.load(open(hf_hub_download(MODEL, "label_space.json")))["labels"]
thresholds = json.load(open(hf_hub_download(MODEL, "thresholds.json")))["thresholds"]
for name, p, cut in zip(labels, probs, thresholds):
if p >= cut:
print(f"{name}: {p:.3f}")Output:
spam: 0.898Pass every image of the post, in order, as its own {"type": "image", ...} entry before the text ("url", or "path" for a local file). A text-only post simply has no image entries.
Training procedure
Training hyperparameters
The following hyperparameters were used during training:
- learning_rate: 1e-05
- trainbatchsize: 2
- evalbatchsize: 8
- seed: 42
- distributed_type: multi-GPU
- num_devices: 8
- gradientaccumulationsteps: 2
- totaltrainbatch_size: 32
- totalevalbatch_size: 64
- optimizer: Use OptimizerNames.ADAMWTORCH with betas=(0.9,0.999) and epsilon=1e-08 and optimizerargs=No additional optimizer arguments
- lrschedulertype: cosine
- lrschedulerwarmup_steps: 0.03
- num_epochs: 4.0
Training results
Framework versions
- Transformers 5.12.1
- Pytorch 2.6.0+cu124
- Datasets 5.0.1
- Tokenizers 0.22.2
