YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

MiniBatchKMeans — Category Clustering

Clusters Amazon product review embeddings into 6 semantic categories.

Embedding model: nomic-ai/nomic-embed-text-v1.5 (768-dim)
Served centroids: K-Means (Lloyd) final fit
Label assignment: MiniBatchKMeans (k=6, batch_size=1024, 10 epochs, 3 inits)
Dataset: 219,998 reviews (Amazon Reviews 2023)


How this model was trained

This is a hybrid two-algorithm pipeline. Two clustering algorithms were trained side by side and each contributes to the final artifact:

                    219,998 reviews
                          │
                          ▼
               Nomic Embed (768-dim)
                          │
            ┌─────────────┴─────────────┐
            ▼                           ▼
     K-Means (Lloyd)            MiniBatchKMeans
     k-sweep 2…10               k-sweep 2…10
     per-k: inertia + silh      per-k: inertia + silh
            │                           │
            └─────────────┬─────────────┘
                          ▼
                   k=6 selected
            (elbow method + domain interpretability)
                          │
            ┌─────────────┴─────────────┐
            ▼                           ▼
     K-Means final fit           MiniBatchKMeans final fit
     n_init=10, max_iter=300     batch_size=1024, 10 epochs
     converged in 112 iters      3 inits, best kept
     fit time: 57.1s             fit time: 19.6s
            │                           │
            ▼                           ▼
   ┌──────────────────┐     ┌────────────────────────┐
   │ cluster_centroids │     │  Label assignment       │
   │     .npy          │     │  (219,998 reviews)      │
   │ (6, 768) float32  │     │  → clusters.csv         │
   │                   │     │  → cluster_profiles.json│
   │  SERVED WEIGHTS   │     │  PRODUCTION CHOICE      │
   └──────────────────┘     └────────────────────────┘

Why two algorithms?

Role Algorithm Why
Centroids (the weights you download) K-Means (Lloyd) Converges to a true local optimum. Slightly better Silhouette in the k=6 comparison (0.0304 vs 0.0311 — within noise). Centroids are mathematically cleaner.
Labels (cluster assignments, profiles) MiniBatchKMeans Scales to the full 571M-review dataset at O(batch·k·d) memory. Retains ~102% of K-Means quality at 3× faster fit time (19.6s vs 57.1s). The production-ready algorithm.

What this means in practice

  • Inference: cluster_centroids.npy (K-Means weights) is what you load to classify new reviews. The cosine distance to these 6 centroids determines the cluster.
  • Cluster profiles: The sizes, avg ratings, top terms, and category distributions in cluster_profiles.json come from the MiniBatchKMeans label assignment.
  • Interchangeable: The silhouette gap is negligible (0.0311 vs 0.0304), meaning both algorithms agree on cluster boundaries. The centroids from either would produce nearly identical results.

Full training history with per-batch inertia tracking for both algorithms: metrics_clustering.json.


How to use cluster_centroids.npy

import numpy as np

centroids = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")
# shape: (6, 768), dtype: float32
# centroids[i] is the 768-dim embedding of cluster i

To assign a new review embedding to the nearest cluster:

from scipy.spatial.distance import cdist
distances = cdist(new_embeddings, centroids, metric="cosine")
labels = np.argmin(distances, axis=1)

Requires scipy. Use new_embeddings with shape (n_reviews, 768) from the same Nomic model.


What the clusters mean

6 clusters discovered from review embeddings. Verbose profile: cluster_profiles.json.

# % Label Avg ★ Top terms Key categories
0 13.4% Mixed (46% Neutral) 2.69 like, use, product, good, hair Beauty & Personal Care, Grocery, All Beauty
1 5.6% Strong Positive (93%) 4.74 good product, great, works Clothing, Automotive, Health & Household
2 17.6% Mixed (42% Neutral) 2.98 book, story, read, good Kindle Store, Movies & TV, Books
3 16.3% Mixed (53% Neutral) 2.96 described, small, fit, size, expected Amazon Fashion, Clothing, Sports
4 24.6% Strong Negative (66%) 1.94 work, use, just, money, did Cell Phones, Electronics, Patio & Garden
5 22.4% Strong Positive (90%) 4.68 great, love, easy, gift, nice Toys & Games, Gift Cards, Office Products

Metrics

From metrics_clustering.json:

Metric Value
Silhouette Score 0.0311
Standard K-Means Silhouette 0.0304
K-Means Final Inertia 116,999.41
MiniBatchKMeans Best Init Inertia 457.99
MiniBatch Fit Time 19.6s

Silhouette interpretation: 0.031 is low — this is expected for high-dimensional text embeddings (curse of dimensionality). The score is a relative comparator between k values, not an absolute quality measure. k=6 was selected via elbow method + domain interpretability (see metrics_clustering.json for the full k-sweep).

NOTE: The inertia values differ in scale because K-Means reports total inertia across all 219,998 points, while the MiniBatchKMeans fit_steps track batch-level inertia (per 1,024 samples).


Usage Examples

Full pipeline: raw text → Nomic embedding → cosine distance → cluster label.

Example 1 — Positive book review → Cluster 2 (Books/Media)

import numpy as np
from scipy.spatial.distance import cdist
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)
centroids = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")

review = "This novel blew me away. The plot twists were masterful and I couldn't put it down."
embedding = model.encode([review])  # shape: (1, 768)
distances = cdist(embedding, centroids, metric="cosine")
cluster_id = int(np.argmin(distances))

Expected output: cluster_id = 2 — Books/Media cluster. The review talks about plot, novel, and reading experience — matches top terms book, story, read.


Example 2 — Enthusiastic toy/gift review → Cluster 5 (Positive gifts & toys)

review = "Bought this as a birthday gift for my nephew and he absolutely loves it! Easy to assemble and great quality."
embedding = model.encode([review])
distances = cdist(embedding, centroids, metric="cosine")
cluster_id = int(np.argmin(distances))

Expected output: cluster_id = 5 — Strong Positive cluster (avg 4.68★). Keywords gift, love, easy, great align perfectly with Cluster 5's top terms. Sentiment: 89.5% Positive.


Example 3 — Frustrated electronics review → Cluster 4 (Negative electronics)

review = "This charger stopped working after two weeks. Total waste of money. Don't buy this."
embedding = model.encode([review])
distances = cdist(embedding, centroids, metric="cosine")
cluster_id = int(np.argmin(distances))

Expected output: cluster_id = 4 — Strong Negative cluster (avg 1.94★). Keywords work (broken), money (waste), don't (negative) match Cluster 4's patterns. Top categories: Cell Phones, Electronics. Sentiment: 66.2% Negative.


Batch inference (N reviews at once)

reviews = ["Great product, highly recommend!", "Did not fit as described.", "Perfect gift for kids."]
embeddings = model.encode(reviews)                    # shape: (3, 768)
distances = cdist(embeddings, centroids, "cosine")    # shape: (3, 6)
labels = np.argmin(distances, axis=1)                 # e.g. [5, 3, 5]

Serving & Integration

Pick the approach that fits your stack. All examples assume the model files live at data/models/MiniBatchKMeans/.

1. Python CLI script (zero dependencies beyond the model)

Save as cluster_review.py and run: python cluster_review.py "Your review text here"

"""cluster_review.py — classify a review from the command line."""
import sys, json, numpy as np
from scipy.spatial.distance import cdist
from sentence_transformers import SentenceTransformer

MODEL_DIR = "data/models/MiniBatchKMeans"

# Load once at module level
embedder = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)
centroids = np.load(f"{MODEL_DIR}/cluster_centroids.npy")
with open(f"{MODEL_DIR}/cluster_profiles.json") as f:
    profiles = json.load(f)

def classify(text: str) -> dict:
    emb = embedder.encode([text])
    cluster_id = int(np.argmin(cdist(emb, centroids, metric="cosine")))
    cluster = next(c for c in profiles["clusters"] if c["cluster_id"] == cluster_id)
    return {
        "text": text,
        "cluster_id": cluster_id,
        "avg_rating": cluster["avg_rating"],
        "sentiment": cluster["sentiment_distribution_pct"],
        "top_terms": cluster["top_terms"],
        "top_categories": cluster["top_categories"],
    }

if __name__ == "__main__":
    text = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else input("Review: ")
    result = classify(text)
    print(json.dumps(result, indent=2))

Sample output:

{
  "text": "This blender is amazing, smoothies every morning!",
  "cluster_id": 5,
  "avg_rating": 4.676,
  "sentiment": {"Positive": 89.5, "Neutral": 8.8, "Negative": 1.7},
  "top_terms": ["great", "love", "easy", "gift", "nice"],
  "top_categories": {"Toys_and_Games": 4.7, "Gift_Cards": 4.6, "Office_Products": 4.3}
}

2. FastAPI microservice (REST JSON endpoint)

"""api.py — lightweight REST API. Run: uvicorn api:app --port 8000"""
from contextlib import asynccontextmanager
from fastapi import FastAPI
from pydantic import BaseModel
import numpy as np, json
from scipy.spatial.distance import cdist
from sentence_transformers import SentenceTransformer

MODEL_DIR = "data/models/MiniBatchKMeans"

embedder = None
centroids = None
profiles = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global embedder, centroids, profiles
    embedder = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)
    centroids = np.load(f"{MODEL_DIR}/cluster_centroids.npy")
    with open(f"{MODEL_DIR}/cluster_profiles.json") as f:
        profiles = json.load(f)
    yield

app = FastAPI(lifespan=lifespan)

class ReviewInput(BaseModel):
    text: str

class ClusterResult(BaseModel):
    cluster_id: int
    avg_rating: float
    sentiment: dict
    top_terms: list[str]
    top_categories: dict

@app.post("/classify", response_model=ClusterResult)
def classify(review: ReviewInput):
    emb = embedder.encode([review.text])
    cluster_id = int(np.argmin(cdist(emb, centroids, metric="cosine")))
    c = next(c for c in profiles["clusters"] if c["cluster_id"] == cluster_id)
    return ClusterResult(
        cluster_id=cluster_id,
        avg_rating=round(c["avg_rating"], 2),
        sentiment=c["sentiment_distribution_pct"],
        top_terms=c["top_terms"],
        top_categories=c["top_categories"],
    )

Call it:

curl -X POST http://localhost:8000/classify \
  -H "Content-Type: application/json" \
  -d '{"text": "This book was a page-turner from start to finish."}'
{"cluster_id":2,"avg_rating":2.98,"sentiment":{"Neutral":42.5,"Negative":31.0,"Positive":26.5},
 "top_terms":["book","story","like","read","good"],
 "top_categories":{"Kindle_Store":16.6,"Movies_and_TV":14.6,"Books":13.4}}

3. HTML form + vanilla JavaScript (browser)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Review Classifier</title>
  <style>
    body { font-family: system-ui; max-width: 600px; margin: 3rem auto; padding: 0 1rem; }
    textarea { width: 100%; height: 100px; margin-bottom: 0.5rem; }
    pre { background: #f5f5f5; padding: 1rem; border-radius: 6px; white-space: pre-wrap; }
  </style>
</head>
<body>
  <h2>What category is this review?</h2>
  <textarea id="review" placeholder="Paste a product review..."></textarea>
  <button onclick="classify()">Classify</button>
  <pre id="result"></pre>

  <script>
    async function classify() {
      const text = document.getElementById("review").value;
      const res = await fetch("http://localhost:8000/classify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ text }),
      });
      const data = await res.json();
      document.getElementById("result").textContent = JSON.stringify(data, null, 2);
    }
  </script>
</body>
</html>

Open index.html in your browser, type a review, click "Classify" — result renders inline.


4. Google Colab interactive widget

# Run in a Colab cell — instant text box + classify button
import ipywidgets as widgets
from IPython.display import display, JSON

text_input = widgets.Textarea(placeholder="Paste a review...", layout={"width": "100%", "height": "80px"})
button = widgets.Button(description="Classify", button_style="primary")
output = widgets.Output()

def on_click(_):
    with output:
        output.clear_output()
        emb = embedder.encode([text_input.value])
        cluster_id = int(np.argmin(cdist(emb, centroids, metric="cosine")))
        c = next(c for c in profiles["clusters"] if c["cluster_id"] == cluster_id)
        display(JSON({k: c[k] for k in ["cluster_id","avg_rating","sentiment_distribution_pct","top_terms","top_categories"]}))

button.on_click(on_click)
display(text_input, button, output)

5. Streamlit dashboard (one-liner UI)

"""Save as streamlit_app.py — run: streamlit run streamlit_app.py"""
import streamlit as st
import numpy as np, json
from scipy.spatial.distance import cdist
from sentence_transformers import SentenceTransformer

@st.cache_resource
def load_model():
    m = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)
    c = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")
    with open("data/models/MiniBatchKMeans/cluster_profiles.json") as f:
        p = json.load(f)
    return m, c, p

embedder, centroids, profiles = load_model()

st.title("Review Category Classifier")
review = st.text_area("Paste a product review:", height=100)

if st.button("Classify"):
    emb = embedder.encode([review])
    cluster_id = int(np.argmin(cdist(emb, centroids, metric="cosine")))
    c = next(c for c in profiles["clusters"] if c["cluster_id"] == cluster_id)
    col1, col2 = st.columns(2)
    with col1:
        st.metric("Cluster", cluster_id)
        st.metric("Avg Rating", f"{c['avg_rating']:.1f}★")
        st.write("**Top terms:**", ", ".join(c["top_terms"]))
    with col2:
        st.write("**Sentiment:**")
        for k, v in c["sentiment_distribution_pct"].items():
            st.progress(v / 100, text=f"{k}: {v}%")
        st.write("**Categories:**", c["top_categories"])

6. Batch CSV processor (process thousands of reviews at once)

"""batch_classify.py — reads a CSV with a 'review_text' column, writes results."""
import pandas as pd, numpy as np, json
from scipy.spatial.distance import cdist
from sentence_transformers import SentenceTransformer

MODEL_DIR = "data/models/MiniBatchKMeans"
BATCH_SIZE = 512

embedder = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)
centroids = np.load(f"{MODEL_DIR}/cluster_centroids.npy")
with open(f"{MODEL_DIR}/cluster_profiles.json") as f:
    profiles = json.load(f)

df = pd.read_csv("input_reviews.csv")
embeddings = embedder.encode(df["review_text"].tolist(), batch_size=BATCH_SIZE, show_progress_bar=True)
labels = np.argmin(cdist(embeddings, centroids, metric="cosine"), axis=1)

df["cluster_id"] = labels
df["cluster_rating"] = [profiles["clusters"][lid]["avg_rating"] for lid in labels]
df.to_csv("classified_reviews.csv", index=False)
print(f"Done — {len(df)} reviews classified.")

Files in this folder

File Description
cluster_centroids.npy (6, 768) float32 — cluster center embeddings
cluster_profiles.json Per-cluster stats (sizes, sentiment, top terms, categories)
clusters.csv Per-review cluster assignments
embeddings_nomic.npz Full 219,998 × 768 embedding matrix (compressed)
metrics_clustering.json Full training history: k-sweep, fit steps, convergence
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support