01Overview
Moodalyx is a text classifier that predicts which of six emotion labels a short English sentence most resembles — sadness, joy, love, anger, fear or surprise — and returns the full probability distribution. It analyses text only; it does not use images, faces or audio.
The model and training notebook began from a public tutorial. The project was extended with a research interface, a re-evaluation of the saved model (per-class metrics, calibration and error analysis), deployment and this account of its limitations.
02Problem statement
Single-label, six-class classification of short English text. The classes are imbalanced: in the test split, joy has 695 sentences and surprise only 66, so accuracy alone hides how the rarer emotions are handled.
03Motivation
Emotion in text depends on word order and context (“not happy” versus “happy”), which makes it a natural test for sequence models. A bidirectional recurrent network reads each sentence in both directions, so every position sees context on both sides.
04System architecture
- Clean — lower-case, drop apostrophes, replace other non-alphanumeric characters with spaces, collapse whitespace.
- Tokenise — a Keras tokenizer with the 10,000 most frequent training words; other words become
<unk>. - Pad — pad or truncate to exactly 50 tokens.
- Encode — 300-dimensional embedding → bidirectional GRU (128) → dropout 0.5 → bidirectional GRU (64) → dropout 0.5.
- Classify — dense softmax over six labels; the API returns every probability and the top label.
05Technical implementation
- Training in TensorFlow/Keras: Adam, sparse categorical cross-entropy, balanced class weights, batch size 32, up to 20 epochs, early stopping on validation loss (patience 3, best weights restored).
- 3,454,662 trainable parameters; the saved model is 41 MB.
- FastAPI loads the model and tokenizer once at start-up and applies exactly the same cleaning as the evaluation script.
06Dataset and preparation
dair-ai/emotion (Saravia et al., 2018, “CARER”): short English posts labelled with six emotions, mostly beginning with phrases such as “i feel …”. Split: 16,000 training, 2,000 validation and 2,000 test sentences. On the test split, 3.3 % of words are outside the 10,000-word vocabulary.
07Model design
The notebook trained a simple RNN, an LSTM and a GRU before the bidirectional GRU. Those three runs reached only 0.265, 0.340 and 0.289 test accuracy — no better than always predicting the most common label (0.348) — so they failed to train and are not meaningful baselines. The bidirectional GRU is the deployed model; a properly trained simple baseline (for example TF-IDF with logistic regression) is still missing.
08Evaluation methodology
- The saved model is re-scored on all 2,000 test sentences through the same preprocessing the API uses.
- Calibration is measured with a 10-bin reliability diagram and expected calibration error.
- The live deployment was checked against the local model on 200 random test sentences.
09Verified results
| Accuracy | 0.921 |
|---|---|
| Macro-F1 | 0.880 |
| Weighted F1 | 0.922 |
| Expected calibration error (10 bins) | 0.048 |
| Always predict “joy” (majority class) | 0.348 |
Per-label F1
| Label | F1 |
|---|---|
| sadness | 0.963 |
| joy | 0.939 |
| anger | 0.936 |
| fear | 0.873 |
| love | 0.814 |
| surprise | 0.755 |
The live deployment gave the same label as the local model for all 200 sampled test sentences, with probabilities within 0.000001. Accuracy on that random sample was 0.890, lower than the full-split 0.921 — a reminder that 200 sentences is a small sample.
10Demonstration video
Recorded on the live deployment, 11 October 2026; no audio. Four sentences are analysed. “I can’t believe how happy I am right now, this is amazing!” is labelled fear (0.53; joy only 0.14) — a real error kept in the video — and a factual sentence is forced into “anger” (0.38).
11Deployment and source code
- Live demo: anshaiml369.github.io/moodalyx — Hugging Face Space (Docker, CPU-only TensorFlow, 2 vCPU, 16 GB); it returns the same predictions as the earlier Render deployment. Locally the server used about 513 MB, close to Render’s 512 MB free plan; on Render it served more than 200 consecutive requests without a restart. Most predictions took about a second, the slowest 9.5 s.
- Source code: available on request.
12Limitations and known failure modes
- Optimistic test score (data leakage). The notebook passed the test split as
validation_data, so early stopping and weight restoration used the test set. The dataset’s separate validation split was not used. - Narrow domain. Training sentences are short posts that mostly begin with “i feel …”; other writing styles, sarcasm, negation and long text are poorly represented.
- No neutral class. Every input receives one of six emotions, including purely factual sentences.
- Weaker minority classes. Surprise (66 test sentences) and love (159) have the lowest F1; class weights only partly compensate.
- Labels come from hashtags in the original data, so some are noisy or debatable.
- Inputs are truncated to 50 tokens, and words outside the vocabulary are lost.
13Future improvements
- Retrain with the validation split for early stopping and report a clean test score.
- Add a TF-IDF + logistic-regression baseline and train over several seeds.
- Add an abstain or neutral option driven by the probability margin.