CoolFace
Modelpublic

Lakssssshya/roberta-large-goemotions

sourceHugging Facemitupdated 11mo agoView on Hugging Face
7likes17downloads
Model Card

๐Ÿง  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

bash
pip install torch transformers huggingface_hub numpy

Basic Usage (3 lines!)

python
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:

python
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

python
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

python
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

python
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

python
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

python
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

python
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

ParameterValue
Base modelroberta-large
TaskMulti-label classification
Epochs5 (first 2 frozen encoder)
Learning rate2.6e-5 (Optuna-tuned)
OptimizerAdamW
Weight decay0.01
Warmup ratio0.1
LossFocal Loss (ฮฑ=0.38, ฮณ=2.8)
Gradient accumulation16
SchedulerLinear decay
Mixed precisionโœ… FP16
PoolingMean pooling
Dropout0.41
Batch size2 ร— 16 (accumulated)
Early stoppingPatience = 3
Threshold range[0.05, 0.95] optimized per label

Thresholds are optimized individually to maximize per-label F1 and saved in optimal_thresholds.json.


๐Ÿ“Š Evaluation Metrics (Test Split)

Metric TypePrecisionRecallF1
Macro (unweighted)0.4970.5760.519
Weighted by label frequency0.5050.5850.528

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)

labelaccuracyprecisionrecallf1mccsupportthreshold
AVG0.9620.5410.5920.5510.53863800.645
admiration0.950.7180.7320.7250.6974880.616
amusement0.9750.7270.8880.7990.7913030.652
anger0.960.4530.5440.4940.4761950.648
annoyance0.9110.3260.5580.4120.3833030.568
approval0.8850.3070.4560.3670.3143970.543
caring0.9710.4810.5820.5270.5141530.618
confusion0.9730.5210.4010.4540.4441520.653
curiosity0.9470.4520.710.5530.5412480.601
desire0.9890.6420.5580.5970.593770.729
disappointment0.960.3370.3440.340.321630.605
disapproval0.9080.3290.6780.4430.4312920.545
disgust0.9840.5490.4640.5030.496970.697
embarrassment0.9920.4170.7140.5260.542350.674
excitement0.9770.3570.3650.3610.349960.676
fear0.9910.7240.70.7120.707900.681
gratitude0.9890.960.8770.9170.9123580.685
grief0.9980.5450.4620.50.501130.774
joy0.9740.5940.5870.5910.5771720.642
love0.9790.7180.8890.7940.7882520.648
nervousness0.9950.3910.4290.4090.407210.7
optimism0.9730.690.5550.6150.6062090.669
pride0.9990.8890.5330.6670.688150.694
realization0.9760.4750.2280.3090.3191270.658
relief0.9920.2170.5560.3120.344180.594
remorse0.9920.6480.8380.7310.733680.728
sadness0.9760.5430.5730.5580.5461430.65
surprise0.9780.5280.5890.5570.5461290.664
neutral0.7530.5930.7670.6690.48717660.446

๐Ÿ” Per-Label Performance (Test Split)

LabelAccuracyPrecisionRecallF1MCCSupportThreshold
AVG0.9610.4970.5760.5190.50663290.645
admiration0.9400.6760.6900.6830.6505040.616
amusement0.9800.7400.9050.8140.8082640.652
anger0.9590.4550.5560.5000.4821980.648
annoyance0.9050.3150.5190.3920.3563200.568
approval0.9000.3310.5380.4100.3713510.543
caring0.9670.3740.4960.4270.4141350.618
confusion0.9710.4870.4770.4820.4671530.653
curiosity0.9450.4830.7150.5770.5612840.601
desire0.9870.6470.3980.4930.501830.729
disappointment0.9620.3130.3110.3120.2931510.605
disapproval0.8990.2840.6970.4030.4022670.545
disgust0.9780.5310.4230.4710.4631230.697
embarrassment0.9890.2880.4590.3540.358370.674
excitement0.9770.4000.4470.4220.4111030.676
fear0.9880.5640.7950.6600.664780.681
gratitude0.9890.9390.8810.9090.9043520.685
grief0.9990.4290.5000.4620.46260.774
joy0.9740.5570.6400.5950.5841610.642
love0.9790.7310.8320.7780.7692380.648
nervousness0.9940.3330.3480.3400.338230.700
optimism0.9750.6860.5160.5890.5831860.669
pride0.9970.5380.4380.4830.484160.694
realization0.9700.3800.2070.2680.2661450.658
relief0.9930.1710.6360.2690.327110.594
remorse0.9910.5400.8390.6570.669560.728
sadness0.9750.5800.5320.5550.5431560.650
surprise0.9770.5530.5530.5530.5411410.664
neutral0.7530.5950.7760.6740.49117870.446

๐Ÿฅ‡ 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

DetailDescription
ArchitectureRoBERTa-Large + Mean Pooling + 2-layer MLP
Task TypeMulti-label emotion classification
Input LengthUp to 128 tokens
Output28 sigmoid probabilities
FrameworkPyTorch / Hugging Face Transformers
Mixed PrecisionSupported (FP16)
LicenseMIT
DeveloperLakshya Kumar

๐ŸŽฏ 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

bibtex
@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:


โญ If you find this model useful, please give it a star and share it with others!