CoolFace
Modelpublic

AakashakaAkku/vibe-check-emotion

sourceHugging Faceapache-2.0updated 26d agoView on Hugging Face
0likes
Model Card

Vibe Check โ€” a 7-emotion Transformer trained from scratch ๐ŸŽญ

๐Ÿ”— Try the live app โ€” chat with Buddy & Wit and run a Vibe Check, nothing to install: https://civilaakash.github.io/buddymini/

Vibe Check is a compact (~8.3M parameter) Transformer text classifier that reads the emotional tone of a sentence across 7 classes: sadness ยท joy ยท love ยท anger ยท fear ยท surprise ยท neutral.

It was built entirely from scratch as a hands-on learning project โ€” my own word tokenizer, my own architecture, trained from random initialization (no pretrained weights, no fine-tuning of a large model) on public data only.

Not a fine-tune

Unlike most models on the Hub, this is not a fine-tune of a large pretrained model. Every weight was learned from zero on public data. The tokenizer, architecture, training config and inference code are all included in this repo.

Architecture

  • โ€”Word-level tokenizer (regex [a-z']+), vocab 20,002
  • โ€”Token embedding + learned positional embedding (max length 64)
  • โ€”4 Transformer encoder layers ยท embedding dim 256 ยท 8 heads ยท FF 1024 ยท GELU
  • โ€”Padding-masked mean pooling โ†’ linear classification head
  • โ€”8,297,735 parameters ยท runs on CPU in milliseconds

Evaluation

Measured on the test splits only. The model was trained on train + validation, so none of these 6,590 rows were seen during training. The benchmark script is in this repo (bench.py) so every number below can be reproduced.

Headline

MetricScore
Accuracy (combined test set, 6,590 rows)70.7%
Macro-F1 (all 7 emotions weighted equally)68.2%
Weighted F171.0%
Majority-class baseline (always answer "joy")30.3%

The baseline matters: a model that ignores its input entirely and always says "joy" scores 30.3% on this data. 70.7% is 2.3ร— that floor, which is where the claim "it is actually learning" comes from. A number without a baseline means nothing.

Accuracy and macro-F1 are close (70.7 vs 68.2), which says the model is not ignoring the rare classes. That is the payoff of capping every class at 6,500 examples during training.

Per class

EmotionSupportPrecisionRecallF1
fear30178.9%84.7%81.7%
sadness84082.6%79.8%81.2%
joy1,99685.3%69.1%76.4%
neutral1,87964.9%73.7%69.0%
love46160.5%73.5%66.4%
anger87157.0%61.7%59.2%
surprise24248.0%40.1%43.7%

Performance is not uniform โ€” it splits by source

Test sourceRowsAccuracy
dair-ai/emotion (tweets)2,00087.9%
GoEmotions (Reddit, mapped to 7 classes)4,59063.3%

A single headline number hides a 25-point gap. Short, emotionally explicit tweets are easy. Long, conversational Reddit comments are much harder. If your text looks like Reddit rather than Twitter, expect the lower number.


โš ๏ธ Known limitations

1. Anger and neutral collapse into each other

The largest single failure. From the confusion matrix:

  • โ€”230 genuinely angry messages were labelled neutral
  • โ€”222 genuinely neutral messages were labelled anger

That is 452 errors in one confusion pair, and every one of them comes from the GoEmotions portion โ€” dair-ai has no neutral class at all.

The cause is a labelling decision in this project, not a training failure. GoEmotions' annoyance and disapproval were mapped to anger, while confusion and curiosity were mapped to neutral. Mild annoyance and neutral commentary look nearly identical in text, so the model cannot separate categories that were merged by hand. A future version should give mild-negative its own class or drop it.

2. It follows emotional keywords past the point of truth

When an emotion word appears in the sentence, the model tends to predict that emotion far more often than it should:

Word presentActually that emotionModel predicted it
"surprise"50.0%83.3%
"sad"67.2%82.1%
"love"60.0%76.4%

The classic failure: "my team threw me a surprise party" is joy, and the model answers surprise with 87.6% confidence.

3. Implied emotion reads as neutral

The model keys off explicit emotional language. When feeling is only implied by a factual statement, it falls back to neutral:

InputPredictionA human would say
"My dog passed away"neutralsadness
"What nonsense is this?"neutral (81.9%)anger

4. Surprise is the weakest class

43.7% F1, and it under-predicts โ€” 202 predictions against 242 real cases. Partly because GoEmotions' realization was mapped into surprise, which widened the class beyond its ordinary meaning.

5. The reference labels are themselves noisy

GoEmotions is crowd-labelled Reddit text and some gold labels are questionable. Example from the test set, labelled joy by the annotator:

"I don't think that would be an issue with [NAME]. He doesn't..."

Part of the 63.3% GoEmotions score is the answer key, not the model.


Intended use

Best used as a conversation-tone reader (see analyze_day), aggregating emotion across many messages, rather than as a single-sentence oracle. Treat it as an educational / entertainment model, not a clinical or production sentiment system.

Reproducing these numbers

bash
python3 bench.py     # downloads the public test splits, runs the model, writes results.json

Prints accuracy, macro-F1, per-class precision/recall/F1, the full confusion matrix and the keyword probe. No PyTorch required โ€” it runs the ONNX export.

Usage โ€” ONNX (no PyTorch needed)

python
import json, re, numpy as np, onnxruntime as ort
vocab  = json.load(open("emotion_vocab.json"))
labels = ["sadness","joy","love","anger","fear","surprise","neutral"]
sess   = ort.InferenceSession("vibe-int8.onnx")
name   = sess.get_inputs()[0].name

ids = [vocab.get(w, 1) for w in re.findall(r"[a-z']+", "i love this so much".lower())][:64]
logits = sess.run(None, {name: np.array([ids], dtype=np.int64)})[0][0]
p = np.exp(logits - logits.max()); p /= p.sum()
print(labels[int(p.argmax())], f"{p.max()*100:.1f}%")

Usage โ€” PyTorch

python
from emotion import predict_emotion, analyze_day

predict_emotion("i feel so happy right now")
# {'label': 'joy', 'confidence': 97.6, 'distribution': {...}, 'words': [...]}

analyze_day([
    "work made me feel sad and invisible today",
    "but then my friend called and i felt loved",
])
# {'dominant': 'love', 'valence': 'mixed', 'distribution': {...}, ...}

Training data

  • โ€”dair-ai/emotion โ€” 6 emotions, tweets
  • โ€”GoEmotions โ€” 27 emotions, Reddit, single-label rows only, mapped down to these 7 classes

Classes were capped at 6,500 examples each during training to prevent the joy bias.

Licence

Apache 2.0.