Text Classification
Scikit-learn
Joblib
English
emotion-detection
tfidf
logistic-regression
sdvm
Eval Results (legacy)
Instructions to use SDVM/emotion-clf-original with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use SDVM/emotion-clf-original with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("SDVM/emotion-clf-original", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
Initial upload: emotion classification with SDVM data refinement
Browse files- README.md +130 -0
- model.joblib +3 -0
- train_compare.py +174 -0
README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- text-classification
|
| 7 |
+
- emotion-detection
|
| 8 |
+
- sklearn
|
| 9 |
+
- tfidf
|
| 10 |
+
- logistic-regression
|
| 11 |
+
- sdvm
|
| 12 |
+
metrics:
|
| 13 |
+
- accuracy
|
| 14 |
+
- f1
|
| 15 |
+
model-index:
|
| 16 |
+
- name: emotion-clf-original
|
| 17 |
+
results:
|
| 18 |
+
- task:
|
| 19 |
+
type: text-classification
|
| 20 |
+
name: Emotion Classification
|
| 21 |
+
dataset:
|
| 22 |
+
name: dair-ai/emotion (generated subset)
|
| 23 |
+
type: dair-ai/emotion
|
| 24 |
+
metrics:
|
| 25 |
+
- type: accuracy
|
| 26 |
+
value: 0.40
|
| 27 |
+
- type: f1
|
| 28 |
+
value: 0.3881
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
# emotion-clf-original -- Baseline Emotion Classifier (Original Training Data)
|
| 32 |
+
|
| 33 |
+
Baseline emotion classification model trained on **original, unrefined** informal Twitter-style text.
|
| 34 |
+
Part of the [SDVM](https://sdvm.ai) before/after comparison suite.
|
| 35 |
+
|
| 36 |
+
## Cross-Evaluation Results (2x2 Matrix)
|
| 37 |
+
|
| 38 |
+
Both models evaluated on both original and [SDVM](https://sdvm.ai)-refined test data (30 samples). This proves that SDVM data refinement genuinely improves model quality -- not just on refined inputs, but across the board.
|
| 39 |
+
|
| 40 |
+
| Model \ Test Data | Original Test | Refined Test |
|
| 41 |
+
|---|---|---|
|
| 42 |
+
| **Original-trained** (this model) | 40.00% | 43.33% |
|
| 43 |
+
| **Refined-trained** ([emotion-clf-refined](https://huggingface.co/SDVM/emotion-clf-refined)) | 43.33% | **46.67%** |
|
| 44 |
+
|
| 45 |
+
| Model \ Test Data | Original Test (Macro F1) | Refined Test (Macro F1) |
|
| 46 |
+
|---|---|---|
|
| 47 |
+
| **Original-trained** (this model) | 0.3881 | 0.4281 |
|
| 48 |
+
| **Refined-trained** | 0.3952 | **0.4481** |
|
| 49 |
+
|
| 50 |
+
**Key takeaways:**
|
| 51 |
+
1. **The refined-trained model wins on both test splits** -- 43.33% on original test, 46.67% on refined test
|
| 52 |
+
2. **Both models improve on refined test data** -- cleaning input helps even this original-trained model (+3.33pp)
|
| 53 |
+
3. **Best result: refined model + refined test = 46.67%** -- a **16.7% relative improvement** over this baseline (40%)
|
| 54 |
+
4. **SDVM refinement is not style-overfitting** -- the refined model generalizes better to original data too
|
| 55 |
+
|
| 56 |
+
## Model Details
|
| 57 |
+
|
| 58 |
+
| Property | Value |
|
| 59 |
+
|----------|-------|
|
| 60 |
+
| Architecture | TF-IDF (1-2 gram, 10K features) + Logistic Regression |
|
| 61 |
+
| Reference | NLP with Transformers Ch. 2 baseline |
|
| 62 |
+
| Training samples | 90 (15 per class x 6 classes) |
|
| 63 |
+
| Test samples | 30 (5 per class) |
|
| 64 |
+
| Classes | joy, sadness, anger, fear, surprise, love |
|
| 65 |
+
| Training data | Original informal text (contractions, abbreviations, typos) |
|
| 66 |
+
|
| 67 |
+
## Performance
|
| 68 |
+
|
| 69 |
+
| Metric | Value |
|
| 70 |
+
|--------|-------|
|
| 71 |
+
| Accuracy | **40.00%** |
|
| 72 |
+
| Macro F1 | **0.3881** |
|
| 73 |
+
| Log-loss | 1.6826 |
|
| 74 |
+
|
| 75 |
+
### Per-Class F1
|
| 76 |
+
| Emotion | F1 |
|
| 77 |
+
|---------|-----|
|
| 78 |
+
| joy | 0.4000 |
|
| 79 |
+
| sadness | 0.2500 |
|
| 80 |
+
| anger | 0.3333 |
|
| 81 |
+
| fear | 0.6154 |
|
| 82 |
+
| surprise | 0.4444 |
|
| 83 |
+
| love | 0.2857 |
|
| 84 |
+
|
| 85 |
+
## Training Data Sample
|
| 86 |
+
|
| 87 |
+
| Label | Text (original) |
|
| 88 |
+
|-------|-----------|
|
| 89 |
+
| joy | `omg i just got the job i cant believe it im literally shaking rn` |
|
| 90 |
+
| joy | `just had the best day ever with my fav people honestly life is so good` |
|
| 91 |
+
| fear | `i keep having nightmares and idk why im so scared rn` |
|
| 92 |
+
| anger | `my roommate ate my food AGAIN im literally gonna lose it` |
|
| 93 |
+
|
| 94 |
+
## Usage
|
| 95 |
+
|
| 96 |
+
```python
|
| 97 |
+
import joblib
|
| 98 |
+
from huggingface_hub import hf_hub_download
|
| 99 |
+
|
| 100 |
+
model_path = hf_hub_download(repo_id="SDVM/emotion-clf-original", filename="model.joblib")
|
| 101 |
+
pipe = joblib.load(model_path)
|
| 102 |
+
|
| 103 |
+
texts = ["i cant believe i got the job omg im so happy rn", "feeling really low today idk why"]
|
| 104 |
+
predictions = pipe.predict(texts)
|
| 105 |
+
print(predictions) # ['joy', 'sadness']
|
| 106 |
+
|
| 107 |
+
probas = pipe.predict_proba(texts)
|
| 108 |
+
classes = pipe.classes_
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
## Reproduce
|
| 112 |
+
|
| 113 |
+
The full training pipeline is included in [`train_compare.py`](train_compare.py). To reproduce:
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
pip install sdvm scikit-learn
|
| 117 |
+
export SDVM_API_KEY="your-key-here"
|
| 118 |
+
python train_compare.py
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
This generates labeled emotion samples, refines training samples using the SDVM API, trains both original and refined classifiers, evaluates on a held-out test set, and saves results to `train_results.json`.
|
| 122 |
+
|
| 123 |
+
## Comparison
|
| 124 |
+
|
| 125 |
+
See [SDVM/emotion-clf-refined](https://huggingface.co/SDVM/emotion-clf-refined) for the model
|
| 126 |
+
trained on SDVM-refined data -- it achieves **43.33% accuracy (+8.3% relative improvement)** on original test data, and **46.67%** on refined test data.
|
| 127 |
+
|
| 128 |
+
## About SDVM
|
| 129 |
+
|
| 130 |
+
[SDVM (Synthetic Data Vending Machine)](https://sdvm.ai) refines NLP training datasets using proprietary AI models, improving grammar, spelling, and fluency while preserving labels and meaning.
|
model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3d5f58dcb3bb160f479f5ab179a79e88c9ba310a666ec69d41f5fcd6a4515e93
|
| 3 |
+
size 137596
|
train_compare.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Train and compare emotion classifiers on original vs. SDVM-refined data.
|
| 3 |
+
|
| 4 |
+
Follows the NLP with Transformers book (Chapter 2) approach:
|
| 5 |
+
- Emotion classification on dair-ai/emotion-style data
|
| 6 |
+
- Compare TF-IDF + Logistic Regression trained on original vs. SDVM-refined text
|
| 7 |
+
- Metrics: accuracy, macro F1, log-loss (cross-entropy), per-class F1
|
| 8 |
+
|
| 9 |
+
Requirements:
|
| 10 |
+
pip install sdvm scikit-learn
|
| 11 |
+
|
| 12 |
+
Environment:
|
| 13 |
+
export SDVM_API_KEY="your-key-here"
|
| 14 |
+
|
| 15 |
+
Pipeline:
|
| 16 |
+
1. Generate 120 labeled emotion samples (20/class) or load from cache
|
| 17 |
+
2. Refine 90 training samples using SDVM API (cached)
|
| 18 |
+
3. Train TF-IDF + LR on original and refined training sets
|
| 19 |
+
4. Evaluate both on same 30-sample test set (unrefined)
|
| 20 |
+
5. Save results to train_results.json
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import json
|
| 24 |
+
import math
|
| 25 |
+
import os
|
| 26 |
+
import time
|
| 27 |
+
from collections import defaultdict
|
| 28 |
+
|
| 29 |
+
from sdvm import Refinery, RawText
|
| 30 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 31 |
+
from sklearn.linear_model import LogisticRegression
|
| 32 |
+
from sklearn.metrics import (
|
| 33 |
+
accuracy_score,
|
| 34 |
+
classification_report,
|
| 35 |
+
f1_score,
|
| 36 |
+
log_loss,
|
| 37 |
+
)
|
| 38 |
+
from sklearn.pipeline import Pipeline
|
| 39 |
+
|
| 40 |
+
SDVM_API_KEY = os.environ["SDVM_API_KEY"]
|
| 41 |
+
|
| 42 |
+
EMOTIONS = ["joy", "sadness", "anger", "fear", "surprise", "love"]
|
| 43 |
+
SAMPLES_PER_CLASS = 20
|
| 44 |
+
TRAIN_PER_CLASS = 15
|
| 45 |
+
TEST_PER_CLASS = 5
|
| 46 |
+
BATCH_SIZE = 15
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def refine_batch(refinery: Refinery, samples: list[str]) -> list[str]:
|
| 50 |
+
"""Refine a batch of text samples using SDVM API."""
|
| 51 |
+
results = refinery.run([RawText(text=s) for s in samples])
|
| 52 |
+
return [r.text for r in results]
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def train_classifier(texts: list[str], labels: list[str]) -> Pipeline:
|
| 56 |
+
"""TF-IDF (1-2 gram) + Logistic Regression -- Ch.2 NLP with Transformers baseline."""
|
| 57 |
+
pipe = Pipeline([
|
| 58 |
+
("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=1, max_features=10000)),
|
| 59 |
+
("lr", LogisticRegression(max_iter=1000, C=1.0, solver="lbfgs")),
|
| 60 |
+
])
|
| 61 |
+
pipe.fit(texts, labels)
|
| 62 |
+
return pipe
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def evaluate_classifier(pipe: Pipeline, texts: list[str], labels: list[str], name: str) -> dict:
|
| 66 |
+
preds = pipe.predict(texts)
|
| 67 |
+
probs = pipe.predict_proba(texts)
|
| 68 |
+
acc = accuracy_score(labels, preds)
|
| 69 |
+
macro_f1 = f1_score(labels, preds, average="macro")
|
| 70 |
+
ll = log_loss(labels, probs)
|
| 71 |
+
report = classification_report(labels, preds, output_dict=True)
|
| 72 |
+
per_class_f1 = {
|
| 73 |
+
e: round(report.get(e, {}).get("f1-score", 0.0), 4)
|
| 74 |
+
for e in EMOTIONS
|
| 75 |
+
}
|
| 76 |
+
print(f"\n{'='*50}")
|
| 77 |
+
print(f"Results: {name}")
|
| 78 |
+
print(f" Accuracy: {acc:.4f}")
|
| 79 |
+
print(f" Macro F1: {macro_f1:.4f}")
|
| 80 |
+
print(f" Log-loss: {ll:.4f}")
|
| 81 |
+
print(f" Per-class F1: {per_class_f1}")
|
| 82 |
+
return {
|
| 83 |
+
"accuracy": round(acc, 4),
|
| 84 |
+
"macro_f1": round(macro_f1, 4),
|
| 85 |
+
"log_loss": round(ll, 4),
|
| 86 |
+
"per_class_f1": per_class_f1,
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def main():
|
| 91 |
+
refinery = Refinery(api_key=SDVM_API_KEY)
|
| 92 |
+
|
| 93 |
+
# --- Step 1: Load cached data ---
|
| 94 |
+
generated_path = "train_data_generated.json"
|
| 95 |
+
if os.path.exists(generated_path):
|
| 96 |
+
print("Loading cached generated data...")
|
| 97 |
+
with open(generated_path, encoding="utf-8") as f:
|
| 98 |
+
saved = json.load(f)
|
| 99 |
+
train_texts_orig = [d["text"] for d in saved["train_orig"]]
|
| 100 |
+
train_labels = [d["label"] for d in saved["train_orig"]]
|
| 101 |
+
test_texts = [d["text"] for d in saved["test"]]
|
| 102 |
+
test_labels = [d["label"] for d in saved["test"]]
|
| 103 |
+
print(f" Train: {len(train_texts_orig)}, Test: {len(test_texts)}")
|
| 104 |
+
else:
|
| 105 |
+
print("ERROR: train_data_generated.json not found.")
|
| 106 |
+
print("Generate labeled samples first or download from the dataset repo.")
|
| 107 |
+
return
|
| 108 |
+
|
| 109 |
+
# --- Step 2: Load or refine training data ---
|
| 110 |
+
refined_path = "train_data_refined.json"
|
| 111 |
+
if os.path.exists(refined_path):
|
| 112 |
+
print("\nLoading cached refined data...")
|
| 113 |
+
with open(refined_path, encoding="utf-8") as f:
|
| 114 |
+
train_texts_refined = json.load(f)
|
| 115 |
+
print(f" Refined: {len(train_texts_refined)} samples")
|
| 116 |
+
else:
|
| 117 |
+
print("\nStep 2: Refining training samples using SDVM API...")
|
| 118 |
+
train_texts_refined = []
|
| 119 |
+
num_batches = math.ceil(len(train_texts_orig) / BATCH_SIZE)
|
| 120 |
+
for i in range(0, len(train_texts_orig), BATCH_SIZE):
|
| 121 |
+
batch = train_texts_orig[i:i + BATCH_SIZE]
|
| 122 |
+
batch_num = i // BATCH_SIZE + 1
|
| 123 |
+
print(f" Refining batch {batch_num}/{num_batches} ({len(batch)} samples)...")
|
| 124 |
+
refined = refine_batch(refinery, batch)
|
| 125 |
+
train_texts_refined.extend(refined)
|
| 126 |
+
time.sleep(0.5)
|
| 127 |
+
with open(refined_path, "w", encoding="utf-8") as f:
|
| 128 |
+
json.dump(train_texts_refined, f, indent=2, ensure_ascii=False)
|
| 129 |
+
print(f" Refined {len(train_texts_refined)} samples -- saved to {refined_path}")
|
| 130 |
+
|
| 131 |
+
# --- Step 3: Train classifiers ---
|
| 132 |
+
print("\nStep 3: Training classifiers (TF-IDF + Logistic Regression)...")
|
| 133 |
+
print(" Training on ORIGINAL data...")
|
| 134 |
+
clf_orig = train_classifier(train_texts_orig, train_labels)
|
| 135 |
+
print(" Training on REFINED data...")
|
| 136 |
+
clf_refined = train_classifier(train_texts_refined, train_labels)
|
| 137 |
+
|
| 138 |
+
# --- Step 4: Evaluate ---
|
| 139 |
+
print("\nStep 4: Evaluating both classifiers on test set...")
|
| 140 |
+
metrics_orig = evaluate_classifier(clf_orig, test_texts, test_labels, "Original training data")
|
| 141 |
+
metrics_refined = evaluate_classifier(clf_refined, test_texts, test_labels, "Refined (SDVM) training data")
|
| 142 |
+
|
| 143 |
+
# --- Build results ---
|
| 144 |
+
improvement = {
|
| 145 |
+
"accuracy_delta": round(metrics_refined["accuracy"] - metrics_orig["accuracy"], 4),
|
| 146 |
+
"macro_f1_delta": round(metrics_refined["macro_f1"] - metrics_orig["macro_f1"], 4),
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
results = {
|
| 150 |
+
"experiment": {
|
| 151 |
+
"task": "Emotion classification (dair-ai/emotion style)",
|
| 152 |
+
"model": "TF-IDF (1-2 gram, max 10K features) + Logistic Regression",
|
| 153 |
+
"reference": "NLP with Transformers Ch. 2 -- Text Classification baseline",
|
| 154 |
+
"train_samples": len(train_texts_orig),
|
| 155 |
+
"test_samples": len(test_texts),
|
| 156 |
+
"classes": EMOTIONS,
|
| 157 |
+
},
|
| 158 |
+
"original_training": {"metrics": metrics_orig},
|
| 159 |
+
"refined_training": {"metrics": metrics_refined},
|
| 160 |
+
"improvement": improvement,
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
with open("train_results.json", "w", encoding="utf-8") as f:
|
| 164 |
+
json.dump(results, f, indent=2, ensure_ascii=False)
|
| 165 |
+
|
| 166 |
+
print("\n" + "=" * 60)
|
| 167 |
+
print("FINAL COMPARISON")
|
| 168 |
+
print(f" Accuracy: {metrics_orig['accuracy']:.4f} -> {metrics_refined['accuracy']:.4f} ({improvement['accuracy_delta']:+.4f})")
|
| 169 |
+
print(f" Macro F1: {metrics_orig['macro_f1']:.4f} -> {metrics_refined['macro_f1']:.4f} ({improvement['macro_f1_delta']:+.4f})")
|
| 170 |
+
print("\nResults written to train_results.json")
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
main()
|