Lakssssshya/roberta-large-goemotions
๐ง RoBERTa-Large GoEmotions (Optimized)
Multi-label Emotion Classification with Focal Loss + Per-label Threshold Optimization
๐ท๏ธ Overview
This model fine-tunes RoBERTa-Large on the GoEmotions dataset for multi-label emotion classification, detecting 28 distinct emotions (plus neutral) in text. Unlike standard models that use fixed thresholds or BCE loss, this version applies focal loss, per-label optimization, and targeted augmentation, leading to a balanced and generalizable model across all emotions.
Each input sentence can evoke multiple emotions simultaneously โ for example:
"I can't believe this happened!" โ surprise, disappointment
๐ Dataset
GoEmotions (Google Research, 2021) โข ~58k Reddit comments โข 28 emotion labels + neutral โข Multi-label: each text can have multiple active emotions โข Highly imbalanced: e.g., gratitude has >1,000 examples, while grief or relief have <20
This model addresses imbalance through loss design, augmentation, and threshold tuning.
๐ Quick Start
Installation
pip install torch transformers huggingface_hub numpyBasic Usage (3 lines!)
import torch
import torch.nn as nn
from transformers import RobertaTokenizer, RobertaModel
from huggingface_hub import hf_hub_download
import json
import numpy as np
# Step 1: Define the model architecture
class RobertaForMultiLabelClassification(nn.Module):
def __init__(self, model_name, num_labels, dropout_rate=0.3, use_mean_pooling=True):
super().__init__()
self.roberta = RobertaModel.from_pretrained(model_name)
self.use_mean_pooling = use_mean_pooling
hidden_size = self.roberta.config.hidden_size
self.dropout1 = nn.Dropout(dropout_rate)
self.fc1 = nn.Linear(hidden_size, hidden_size // 2)
self.relu = nn.ReLU()
self.dropout2 = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(hidden_size // 2, num_labels)
def mean_pooling(self, token_embeddings, attention_mask):
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1)
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
return sum_embeddings / sum_mask
def forward(self, input_ids, attention_mask):
outputs = self.roberta(input_ids, attention_mask=attention_mask)
if self.use_mean_pooling:
pooled_output = self.mean_pooling(outputs.last_hidden_state, attention_mask)
else:
pooled_output = outputs.pooler_output
x = self.dropout1(pooled_output)
x = self.fc1(x)
x = self.relu(x)
x = self.dropout2(x)
logits = self.fc2(x)
return logits
# Step 2: Load model
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model_name = "Lakssssshya/roberta-large-goemotions"
tokenizer = RobertaTokenizer.from_pretrained(model_name)
# Load config
config_path = hf_hub_download(repo_id=model_name, filename="config.json")
with open(config_path, 'r') as f:
config = json.load(f)
model = RobertaForMultiLabelClassification(
model_name='roberta-large',
num_labels=config['num_labels'],
dropout_rate=config.get('dropout_rate', 0.3),
use_mean_pooling=config.get('use_mean_pooling', True)
)
# Load weights
weights_path = hf_hub_download(repo_id=model_name, filename="pytorch_model.bin")
state_dict = torch.load(weights_path, map_location=device)
model.load_state_dict(state_dict)
model.to(device)
model.eval()
# Load thresholds
thresholds_path = hf_hub_download(repo_id=model_name, filename="optimal_thresholds.json")
with open(thresholds_path, 'r') as f:
thresholds = np.array(json.load(f))
# Emotion labels
emotion_labels = [
'admiration', 'amusement', 'anger', 'annoyance', 'approval',
'caring', 'confusion', 'curiosity', 'desire', 'disappointment',
'disapproval', 'disgust', 'embarrassment', 'excitement', 'fear',
'gratitude', 'grief', 'joy', 'love', 'nervousness',
'optimism', 'pride', 'realization', 'relief', 'remorse',
'sadness', 'surprise', 'neutral'
]
# Step 3: Predict
def predict_emotions(text):
inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=128, padding=True)
inputs = {k: v.to(device) for k, v in inputs.items()}
with torch.no_grad():
logits = model(input_ids=inputs['input_ids'], attention_mask=inputs['attention_mask'])
probs = torch.sigmoid(logits).cpu().numpy()[0]
# Apply optimized thresholds
predictions = (probs > thresholds).astype(int)
# Get predicted emotions
predicted_emotions = [emotion_labels[i] for i in range(len(predictions)) if predictions[i] == 1]
# Get top emotions with scores
top_indices = np.argsort(probs)[::-1][:5]
top_emotions = [(emotion_labels[idx], float(probs[idx])) for idx in top_indices]
return {
'predicted_emotions': predicted_emotions,
'top_emotions': top_emotions
}
# Example usage
text = "I'm so proud and excited about this achievement!"
result = predict_emotions(text)
print(f"Text: {text}")
print(f"Predicted emotions: {result['predicted_emotions']}")
print(f"Top 5 emotions: {result['top_emotions']}")Easy-to-Use Wrapper Class
For convenience, use this wrapper class:
class EmotionPredictor:
def __init__(self, model_name="Lakssssshya/roberta-large-goemotions"):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.tokenizer = RobertaTokenizer.from_pretrained(model_name)
# Load config and model
config_path = hf_hub_download(repo_id=model_name, filename="config.json")
with open(config_path, 'r') as f:
config = json.load(f)
self.model = RobertaForMultiLabelClassification(
model_name='roberta-large',
num_labels=config['num_labels'],
dropout_rate=config.get('dropout_rate', 0.3),
use_mean_pooling=config.get('use_mean_pooling', True)
)
weights_path = hf_hub_download(repo_id=model_name, filename="pytorch_model.bin")
state_dict = torch.load(weights_path, map_location=self.device)
self.model.load_state_dict(state_dict)
self.model.to(self.device)
self.model.eval()
# Load thresholds
thresholds_path = hf_hub_download(repo_id=model_name, filename="optimal_thresholds.json")
with open(thresholds_path, 'r') as f:
self.thresholds = np.array(json.load(f))
self.emotion_labels = [
'admiration', 'amusement', 'anger', 'annoyance', 'approval',
'caring', 'confusion', 'curiosity', 'desire', 'disappointment',
'disapproval', 'disgust', 'embarrassment', 'excitement', 'fear',
'gratitude', 'grief', 'joy', 'love', 'nervousness',
'optimism', 'pride', 'realization', 'relief', 'remorse',
'sadness', 'surprise', 'neutral'
]
def predict(self, text, top_k=5):
inputs = self.tokenizer(text, return_tensors='pt', truncation=True, max_length=128, padding=True)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
logits = self.model(input_ids=inputs['input_ids'], attention_mask=inputs['attention_mask'])
probs = torch.sigmoid(logits).cpu().numpy()[0]
predictions = (probs > self.thresholds).astype(int)
predicted_emotions = [self.emotion_labels[i] for i in range(len(predictions)) if predictions[i] == 1]
top_indices = np.argsort(probs)[::-1][:top_k]
top_emotions = [
{'emotion': self.emotion_labels[idx], 'score': float(probs[idx])}
for idx in top_indices
]
return {'text': text, 'emotions': predicted_emotions, 'top_emotions': top_emotions}
# Simple usage
predictor = EmotionPredictor()
result = predictor.predict("I'm so happy and excited!")
print(result)๐ Example Predictions
Example 1: Pride and Achievement
text = "I'm so proud and excited about this achievement!"
result = predictor.predict(text)
# Output: {'emotions': ['pride', 'excitement', 'joy'], 'top_emotions': [{'emotion': 'pride', 'score': 0.867}, {'emotion': 'excitement', 'score': 0.712}, ...]}Example 2: Regret
text = "I really regret saying that earlier."
result = predictor.predict(text)
# Output: {'emotions': ['remorse', 'sadness'], 'top_emotions': [{'emotion': 'remorse', 'score': 0.758}, ...]}Example 3: Mixed Emotions
text = "I feel anxious but hopeful about the future."
result = predictor.predict(text)
# Output: {'emotions': ['nervousness', 'optimism'], 'top_emotions': [{'emotion': 'nervousness', 'score': 0.710}, {'emotion': 'optimism', 'score': 0.645}, ...]}Example 4: Surprise
text = "I can't believe this happened!"
result = predictor.predict(text)
# Output: {'emotions': ['surprise', 'realization'], 'top_emotions': [{'emotion': 'surprise', 'score': 0.747}, ...]}Example 5: Gratitude
text = "Thank you so much for all your help and support!"
result = predictor.predict(text)
# Output: {'emotions': ['gratitude', 'admiration'], 'top_emotions': [{'emotion': 'gratitude', 'score': 0.923}, ...]}Example 6: Multiple Strong Emotions
text = "This is absolutely disgusting and infuriating!"
result = predictor.predict(text)
# Output: {'emotions': ['disgust', 'anger', 'annoyance'], 'top_emotions': [{'emotion': 'disgust', 'score': 0.834}, {'emotion': 'anger', 'score': 0.789}, ...]}โ๏ธ Training Details
Thresholds are optimized individually to maximize per-label F1 and saved in optimal_thresholds.json.
๐ Evaluation Metrics (Test Split)
This model achieves a balanced macro-F1 above 0.52, maintaining strong recall even for underrepresented emotions.
๐ Per-Label Performance (Best performance on Val Split)
๐ Per-Label Performance (Test Split)
๐ฅ Why This Model Outperforms Other GoEmotions Models
Most GoEmotions models on Hugging Face use BCE loss and a fixed 0.5 threshold. While effective for frequent emotions, they perform poorly on rare ones.
This model overcomes those limits via:
1๏ธโฃ Adaptive per-label thresholds โ Each label has a unique optimized decision boundary, maximizing per-label F1 and balancing recall/precision. 2๏ธโฃ Focal Loss โ Down-weights easy examples and boosts hard ones, enhancing minority class generalization. 3๏ธโฃ Mean Pooling โ Captures richer emotional nuance than CLS-based pooling. 4๏ธโฃ Targeted Augmentation โ Paraphrasing and synonym replacement to strengthen rare emotion classes. 5๏ธโฃ Gradual Unfreezing โ Stabilizes fine-tuning by freezing encoder early epochs. 6๏ธโฃ Balanced Macro Metrics โ Consistent fairness across all emotion classes.
๐งพ Model Details
๐ฏ Intended Use
- Emotion detection in conversational AI systems
- Social media sentiment analysis
- Affective computing research
- Psychological and HCI studies
โ ๏ธ Limitations
- Trained on Reddit; may not generalize to formal or non-English domains.
- Rare emotions (e.g. grief, relief) have low support.
- Detects linguistic cues, not true emotional state.
๐ Citation
@misc{lakshya2025robertalargegoemotions,
title={RoBERTa-Large GoEmotions (Optimized Thresholds and Focal Loss)},
author={Lakshya Kumar},
year={2025},
publisher={Hugging Face},
howpublished={\url{https://huggingface.co/Lakssssshya/roberta-large-goemotions}}
}๐ Acknowledgments
- Google Research for the GoEmotions dataset
- Hugging Face for the transformers library
- Meta AI for RoBERTa architecture
๐ง Contact
For questions or collaborations:
- Hugging Face: @Lakssssshya
- Model Repository: roberta-large-goemotions
โญ If you find this model useful, please give it a star and share it with others!
