Natural language processing · recurrent networks

Moodalyx

Emotion-label prediction from short English text with a bidirectional GRU.

Accuracy (2,000 test sentences)
0.921
re-evaluated
Macro-F1
0.880
re-evaluated
Expected calibration error
0.048
re-evaluated
Live demo Watch demo Code on request · the test score is optimistic — see limitations

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.

What it does not doIt predicts which dataset label a sentence resembles. It does not measure anyone’s emotional or psychological state, has no “neutral” option, and can be confidently wrong.

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

  1. Clean — lower-case, drop apostrophes, replace other non-alphanumeric characters with spaces, collapse whitespace.
  2. Tokenise — a Keras tokenizer with the 10,000 most frequent training words; other words become <unk>.
  3. Pad — pad or truncate to exactly 50 tokens.
  4. Encode — 300-dimensional embedding → bidirectional GRU (128) → dropout 0.5 → bidirectional GRU (64) → dropout 0.5.
  5. Classify — dense softmax over six labels; the API returns every probability and the top label.
Live prediction for “I was shocked and completely surprised by the unexpected gift!”: surprise 97.6 percent, fear 1.4, joy 0.8, anger 0.2, sadness and love below 0.1 percent.
A real response from the deployed API.

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

Test split, n = 2,000. Re-evaluated from the saved model; accuracy matches the training notebook.
Accuracy0.921
Macro-F10.880
Weighted F10.922
Expected calibration error (10 bins)0.048
Always predict “joy” (majority class)0.348

Per-label F1

LabelF1
sadness0.963
joy0.939
anger0.936
fear0.873
love0.814
surprise0.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.