Osele1/sonic-clusters
0
1# SonicClusters — Technical Deep Dive2 3> **Project:** SonicClusters — Music Clustering & Recommendation System 4> **Course:** DATA480 — Project Phase 3 (Modelling & Testing) 5> **Team:** Vik Dayal · Nathaniel Ola Ogunleye · Osele Adeoye · Huynh Hai Trieu Le 6> **Stack:** React 18 + TypeScript · FastAPI + Python · scikit-learn · Recharts · Spotify Web API7 8---9 10## 1. Project Overview11 12SonicClusters is an interactive web application that demonstrates **unsupervised music clustering** and **content-based recommendation** using a combined dataset of **12,000 songs** from the **Million Song Dataset (MSD)** and modern **Spotify tracks**. The system trains three distinct clustering algorithms on audio features, then serves live model inference through a FastAPI backend with **Spotify API integration** for album art and track metadata. A polished React frontend lets users explore clusters visually, search for songs, and receive real-time recommendations powered by Euclidean distance similarity.13 14### Core Capabilities15 16| Capability | Description |17|---|---|18| **Cluster Explorer** | Interactive 2D/3D scatter-plot visualization of song clusters with color-coding, hover tooltips, and cluster filtering |19| **Musical Profiling** | Automated "mood" labeling (e.g., "Intense Upbeat Pop") based on cluster-wide audio feature analysis |20| **Recommendation Engine** | Real-time song recommendations with Spotify album art and "Open in Spotify" links |21| **Algorithm Comparison** | Side-by-side performance dashboard with radar charts, pie charts, and a detailed metrics table |22| **Spotify Integration** | Live album artwork, audio previews, and direct Spotify links via the Web API |23| **Cluster Prediction** | POST endpoint that predicts which cluster a *brand-new* song (not in the dataset) would fall into |24 25---26 27## 2. System Architecture28 29The application follows a **client-server** architecture with a clear separation between the ML/data layer and the presentation layer.30 31```32┌─────────────────────────┐ HTTP / REST ┌─────────────────────────┐33│ │ ◄──────────────────────► │ │34│ React Frontend │ localhost:3000 │ FastAPI Backend │35│ (Vite + TypeScript) │ ← CORS enabled → │ (Python 3 + uvicorn) │36│ │ localhost:8000 │ │37│ ┌───────────────────┐ │ │ ┌───────────────────┐ │38│ │ ClusterViz │ │ │ │ ClusteringService │ │39│ │ RecommendationSys │ │ │ │ RecommendService │ │40│ │ AlgoComparison │ │ │ │ SpotifyService │ │41│ └───────────────────┘ │ │ └───────────────────┘ │42│ │ │ │ │43│ services/api.ts │ │ ▼ │44│ hooks/useData.ts │ │ ┌───────────────────┐ │45│ │ │ │ Trained Models │ │46└─────────────────────────┘ │ │ (.pkl via joblib) │ │47 │ │ Feature Matrix │ │48 │ │ (.npy) │ │49 │ │ songs_with_ │ │50 │ │ clusters.csv │ │51 │ └───────────────────┘ │52 └─────────────────────────┘53```54 55### Key Design Decisions56 57- **Live inference over pre-computed results** — Recommendations are computed in real-time using the trained scikit-learn models, not pre-baked JSON. This allows the system to handle new queries (e.g., predict cluster for a song not in the dataset). **Update:** Heavy CPU-bound ML operations are wrapped in `asyncio.to_thread()` to ensure the FastAPI event loop remains non-blocking for concurrent users.58- **Singleton service pattern** — `ClusteringService`, `RecommendationService`, and `SpotifyService` are instantiated once at startup, loading all models and data into memory for sub-second response times.59- **True Dimensionality Reduction via UMAP** — Since the feature space is 30-dimensional (after OneHotEncoding), scatter plot coordinates are mathematically projected down to 3 dimensions using UMAP (Uniform Manifold Approximation and Projection) on the backend, providing an accurate spatial representation of the clusters.60- **Automated Musical Mood Profiling** — To move beyond generic cluster IDs, the backend analyzes the statistical mean of audio features (Loudness, Tempo) and top genres within each cluster to generate human-readable labels like "Mellow Slow Jazz".61- **Spotify Web API integration** — The backend authenticates with Spotify via Client Credentials flow to fetch real album artwork and track metadata. If audio previews are unavailable, the frontend gracefully falls back to a direct Spotify link.62 63---64 65## 3. Dataset66 67### Source68 69The project uses a **combined 12,000-song dataset** from two sources:70 71| Source | Songs | Description |72|---|---|---|73| **Million Song Dataset (MSD)** | 10,000 | Classic 10K subset with Echo Nest audio features (Bertin-Mahieux et al., Columbia University) |74| **Kaggle Spotify Tracks Dataset** | 2,000 | Modern tracks with Spotify audio features (maharshipandya, ~114K total, 2K sampled) |75 76The Spotify tracks were ingested using `process_kaggle_dataset.py`, which maps Spotify's audio features to the MSD column schema. Confidence metrics (`key_confidence`, `mode_confidence`, `time_signature_confidence`) default to `1.0` for Spotify tracks since Spotify does not expose these values.77 78### Data Pipeline79 80```81Phase 2: Data Cleaning Phase 3: Modelling Data Expansion82┌──────────────────────┐ ┌──────────────────┐ ┌──────────────────────┐83│ Raw MSD 10K CSV │ ──clean──► │ 10,000 MSD songs │──merge──► │ 12,000 combined songs│84│ (with missing vals, │ │ (cleaned) │ │ (MSD + Spotify) │85│ outliers, etc.) │ └──────────────────┘ └──────────┬───────────┘86└──────────────────────┘ │87 ┌──────────────────┐ retrain_models.py88 │ Kaggle Spotify │──format──► │89 │ Tracks (2,000) │ ▼90 └──────────────────┘ ┌──────────────────────────────┐91 process_kaggle_dataset.py │ backend/data/ │92 │ songs_with_clusters.csv │93 │ feature_matrix.npy │94 │ kmeans_centroids.npy │95 │ metadata.json │96 │ feature_info.json │97 │ backend/models/ │98 │ preprocessor.pkl │99 │ kmeans_model.pkl │100 │ hierarchical_model.pkl │101 │ dbscan_model.pkl │102 │ pca_transformer.pkl │103 └──────────────────────────────┘104```105 106### Feature Engineering107 108The model uses **13 audio features** split into two categories:109 110#### Numeric Features (10) — StandardScaler normalized111 112| Feature | Description | Typical Range |113|---|---|---|114| `tempo` | Estimated beats per minute | 60–200 BPM |115| `loudness` | Overall loudness in dB | -60 to 0 dB |116| `duration` | Track length in seconds | 30–600+ s |117| `artist_hotttnesss` | Artist popularity metric (Echo Nest) | 0.0–1.0 |118| `artist_familiarity` | How well-known the artist is | 0.0–1.0 |119| `key_confidence` | Confidence in the detected key | 0.0–1.0 |120| `mode_confidence` | Confidence in major/minor mode | 0.0–1.0 |121| `time_signature_confidence` | Confidence in time signature detection | 0.0–1.0 |122| `danceability` | How suitable for dancing | 0.0–1.0 |123| `energy` | Perceptual measure of intensity | 0.0–1.0 |124 125#### Categorical Features (3) — OneHotEncoded126 127| Feature | Categories | Encoding |128|---|---|---|129| `key` | 0–11 (C, C#, D, … B) | 12 binary columns |130| `mode` | 0 (minor), 1 (major) | 2 binary columns |131| `time_signature` | 0, 1, 3, 4, 5, 7 | 6 binary columns |132 133### Preprocessing Pipeline134 135```python136preprocessor = ColumnTransformer(137 transformers=[138 ('num', StandardScaler(), numeric_features), # z-score normalization139 ('cat', OneHotEncoder(handle_unknown='ignore',140 sparse_output=False),141 categorical_features) # one-hot encoding142 ]143)144```145 146**After transformation:** Each song is represented as a **30-dimensional feature vector** (10 scaled numeric + 12 key + 2 mode + 6 time_signature). The resulting feature matrix shape is `(12000, 30)`.147 148---149 150## 4. Clustering Algorithms — Technical Details151 152### 4.1 K-Means Clustering153 154> **Type:** Centroid-based (partitioning) 155> **Implementation:** `sklearn.cluster.KMeans`156 157#### Configuration158 159```python160KMeans(161 n_clusters=6, # Determined via Elbow Method / Silhouette Analysis162 init='k-means++', # Smart centroid initialization163 n_init='auto', # Automatic best-of-N runs164 max_iter=300, # Max iterations per run165 algorithm='lloyd', # Classic Lloyd's algorithm166 random_state=42 # Reproducibility167)168```169 170#### How It Works171 1721. **Initialize** 6 cluster centroids using K-Means++ (selects initial centers that are spread apart)1732. **Assign** each of the 12,000 songs to the nearest centroid (Euclidean distance in the 30D feature space)1743. **Update** each centroid to the mean of its assigned points1754. **Repeat** steps 2–3 until convergence (max 300 iterations)176 177#### Key Results178 179| Metric | Value |180|---|---|181| Clusters found | 6 |182| Inertia (within-cluster sum of squares) | 67,138.87 |183| Convergence iterations | 49 |184| Silhouette Score | **0.0999** |185| Davies-Bouldin Index | **2.0206** |186| Calinski-Harabasz Index | **944.77** |187 188#### Why K=6?189 190The value K=6 was determined during Phase 3 experimentation using the **Elbow Method** (plotting inertia vs. K to find the "elbow" where adding more clusters yields diminishing returns) and validated with the **Silhouette Score** (measuring how similar each song is to its own cluster versus neighboring clusters).191 192---193 194### 4.2 Hierarchical (Agglomerative) Clustering195 196> **Type:** Agglomerative (bottom-up tree-building) 197> **Implementation:** `sklearn.cluster.AgglomerativeClustering`198 199#### Configuration200 201```python202AgglomerativeClustering(203 n_clusters=6,204 metric='euclidean',205 linkage='ward' # Minimizes within-cluster variance206)207```208 209#### How It Works210 2111. **Start** with each song as its own cluster (12,000 singleton clusters)2122. **Merge** the two clusters that produce the smallest increase in total within-cluster variance (Ward's criterion)2133. **Repeat** until only 6 clusters remain2144. The result is a **dendrogram** (tree structure) — cut at the level that yields 6 branches215 216#### Ward Linkage217 218Ward's method merges clusters by minimizing the total increase in the sum of squared deviations from the cluster means. This tends to produce compact, spherical clusters similar to K-Means but respects local structure better.219 220#### Key Results221 222| Metric | Value |223|---|---|224| Clusters found | 6 |225| Linkage method | Ward |226| Silhouette Score | **0.0510** |227| Davies-Bouldin Index | **2.4632** |228| Calinski-Harabasz Index | **643.14** |229 230> [!NOTE]231> Agglomerative clustering does not produce a native `.predict()` method. For new-song prediction in the API, the system falls back to **nearest K-Means centroid** as an approximation.232 233---234 235### 4.3 DBSCAN (Density-Based Spatial Clustering)236 237> **Type:** Density-based 238> **Implementation:** `sklearn.cluster.DBSCAN` with `sklearn.decomposition.PCA`239 240#### Configuration241 242```python243# 1. Dimensionality reduction244pca = PCA(n_components=10, random_state=42)245 246# 2. Density-based clustering247DBSCAN(248 eps=1.44, # Neighborhood radius (50th percentile of k-distance)249 min_samples=3, # Minimum points to form a dense region250 metric='euclidean',251 algorithm='auto', # KD-tree or ball-tree depending on data252 leaf_size=30253)254```255 256#### How It Works257 2581. **Dimensionality Reduction:** The 30-dimensional feature space is first compressed to 10 dimensions using Principal Component Analysis (PCA). This mitigates the "curse of dimensionality" which severely impacts distance-based metrics in dense spaces.2592. For each song, find all other songs within `eps=1.44` distance units in the PCA-reduced space.2603. If a song has ≥3 neighbors within that radius, it's a **core point**.2614. **Expand** clusters by connecting core points that are within `eps` of each other.2625. Songs that are near core points but aren't core points themselves are **border points**.2636. Songs with no core point neighbors are marked as **noise (outlier) points** (label `-1`).264 265#### Key Results266 267| Metric | Value |268|---|---|269| PCA Components | 10 |270| Clusters found | **22** (plus noise) |271| Noise points | ~984 (9.8% of sample) |272| Silhouette Score | N/A |273| Davies-Bouldin Index | N/A |274| Calinski-Harabasz Index | N/A |275 276> [!IMPORTANT]277> By applying PCA dimensionality reduction prior to DBSCAN, the algorithm successfully identified **22 distinct density clusters** along with about 10% of songs marked as true outliers. Prior to PCA, the high-dimensional space caused all points to appear equidistant, resulting in a single massive cluster.278 279#### Why DBSCAN Benefits from PCA280 281The MSD audio features tend to form a single dense blob in 23D space. DBSCAN relies heavily on Euclidean distance, which loses its discriminative power in high dimensions. Compressing the features back to 10 core dimensions restored the variation in density, allowing the algorithm to correctly separate distinct musical profiles.282 283---284 285## 5. Model Performance Comparison286 287### 5.1 Metrics Explained288 289| Metric | What It Measures | Ideal Value | Interpretation |290|---|---|---|---|291| **Silhouette Score** | How similar a song is to its own cluster vs. neighbors | +1.0 (perfect) | Higher = better separated clusters |292| **Davies-Bouldin Index** | Average ratio of within-cluster to between-cluster distances | 0.0 (perfect) | Lower = more compact, well-separated clusters |293| **Calinski-Harabasz Index** | Ratio of between-cluster variance to within-cluster variance | Higher = better | Larger = denser, better-separated clusters |294 295### 5.2 Head-to-Head Comparison296 297| Metric | K-Means | Hierarchical | DBSCAN | Winner |298|---|---|---|---|---|299| **Silhouette** | **0.0999** | 0.0510 | N/A | 🏆 K-Means |300| **Davies-Bouldin** | **2.0206** | 2.4632 | N/A | 🏆 K-Means |301| **Calinski-Harabasz** | **944.77** | 643.14 | N/A | 🏆 K-Means |302| **Clusters** | 6 | 6 | 22 | K-Means / Hierarchical |303| **Handles noise** | No | No | **Yes** | 🏆 DBSCAN |304| **Prediction speed** | **O(K)** | O(K) fallback | O(K) fallback | 🏆 K-Means |305 306### 5.3 Analysis307 308#### K-Means — Best Overall Performer309 310K-Means outperforms Hierarchical clustering across **all three** quantitative metrics:311 312- **~2× higher Silhouette Score** (0.0999 vs. 0.0510) — songs are nearly twice as well-matched to their own cluster313- **~18% lower Davies-Bouldin** (2.02 vs. 2.46) — clusters are more compact relative to their separation314- **~47% higher Calinski-Harabasz** (944.77 vs. 643.14) — substantially better variance ratio315 316That said, a Silhouette Score of ~0.10 is objectively **low**, indicating that the clusters are not strongly separated. This is expected for music data — audio features like tempo, loudness, and energy exist on continuous spectra, and songs don't form naturally discrete groups the way species or geological samples might.317 318#### Hierarchical — Structurally Richer, Quantitatively Weaker319 320Ward-linkage agglomerative clustering preserves hierarchical relationships (the dendrogram) and can reveal nested sub-genres. However, its quantitative metrics are consistently below K-Means on this dataset. The Ward linkage objective function is essentially the same as K-Means' (minimize within-cluster variance), but the greedy bottom-up merging can get trapped in suboptimal solutions that K-Means++' global restarts avoid.321 322#### DBSCAN — Density Discovery via PCA323 324Initially, DBSCAN struggled to identify more than one cluster due to the high dimensionality (23D) of the feature space. By integrating a PCA step to compress the space to 10 dimensions, DBSCAN successfully discovered 22 micro-clusters and accurately segregated ~10% of the dataset as noise. This makes DBSCAN highly useful for isolating outlier tracks (e.g., spoken word, exceptionally long ambient tracks) that would otherwise distort the centroids in K-Means.325 326---327 328## 6. Recommendation Engine329 330### How Recommendations Work331 332The recommendation system uses a **two-stage approach**:333 334```335 Stage 1: Cluster Filtering Stage 2: Distance Ranking336 ┌──────────────────────┐ ┌────────────────────────┐337Selected Song ──► │ Look up song's │ ──────────►│ Compute Euclidean │338 │ cluster assignment │ │ distance from selected │339 │ (e.g., K-Means │ │ song to every other │340 │ Cluster 3) │ │ song in Cluster 3 │341 └──────────────────────┘ │ │342 │ Sort by distance (ASC) │343 │ Return top 5 │344 └────────────────────────┘345```346 347### Distance Calculation348 349Euclidean distance is computed in the **preprocessed feature space** (30D):350 351```python352def euclidean_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:353 return float(np.sqrt(np.sum((vec1 - vec2) ** 2)))354```355 356This is done across the full standardized+encoded feature vector, ensuring that all features contribute proportionally after z-score normalization.357 358### API Flow359 3601. **Client** selects a song → calls `GET /api/recommendations/{song_id}?algorithm=kmeans&n=5`3612. **Backend** looks up the song's cluster assignment in `songs_with_clusters.csv`3623. **Backend** filters to all songs in the same cluster3634. **Backend** computes pairwise Euclidean distances using `feature_matrix.npy`3645. **Backend** sorts by distance and returns the 5 closest matches3656. **Client** renders the results with distance scores366 367### New Song Prediction368 369For songs not in the dataset, the `/api/predict-cluster` endpoint:370 3711. Accepts 13 raw audio features via `POST`3722. Runs them through the **saved preprocessor** (`preprocessor.pkl`) for StandardScaling + OneHotEncoding3733. Calls `kmeans.predict(X)` to assign a cluster3744. Returns the predicted cluster ID375 376---377 378## 7. Backend Architecture379 380### Technology Stack381 382| Component | Technology | Purpose |383|---|---|---|384| Web framework | **FastAPI** | Async REST API with auto-generated OpenAPI docs |385| ASGI server | **uvicorn** | High-performance async server |386| ML models | **scikit-learn** | K-Means, AgglomerativeClustering, DBSCAN |387| Serialization | **joblib** | Model persistence (`.pkl` files) |388| Data handling | **pandas** + **numpy** | DataFrame operations, feature matrices |389| Validation | **Pydantic** | Request/response schema validation |390 391### Service Layer392 393#### `ClusteringService` (Singleton)394 395Loaded at startup, this service:396- Loads 4 pickle files (preprocessor + 3 models)397- Loads the CSV dataset (12K songs) and NumPy feature matrix into memory398- Pre-computes clustering metrics (Silhouette, Davies-Bouldin, Calinski-Harabasz)399- Provides methods: `get_all_songs()`, `search_songs()`, `predict_cluster()`, `get_distributions()`400 401#### `RecommendationService` (Singleton)402 403Delegates to `ClusteringService` for data, then:404- Computes Euclidean distances in the feature space405- Supports recommendation by song ID or by raw feature vector406- Returns sorted results with distance scores407 408#### `SpotifyService` (Singleton)409 410Authenticated via Client Credentials flow at startup, this service:411- Searches the Spotify catalog for tracks by title + artist412- Returns album artwork URLs (640×640) and audio preview URLs413- Gracefully returns `None` values when Spotify restricts previews414 415### API Endpoints416 417| Endpoint | Method | Description | Avg Response |418|---|---|---|---|419| `GET /api/songs` | GET | Paginated song list (limit/offset) | ~50ms |420| `GET /api/songs/{id}` | GET | Single song by ID | ~5ms |421| `GET /api/search?query=...` | GET | Case-insensitive substring search | ~20ms |422| `GET /api/recommendations/{id}` | GET | Live cluster-based recommendations | ~100–500ms |423| `POST /api/recommendations/by-features` | POST | Recommendations for new songs | ~200–600ms |424| `POST /api/predict-cluster` | POST | Predict cluster for new song | ~10ms |425| `GET /api/labels/{algorithm}` | GET | Human-readable cluster names (mood/speed) | ~10ms |426| `GET /api/cluster/{algo}/{id}` | GET | All songs in a specific cluster | ~30ms |427| `GET /api/algorithms` | GET | Algorithm info + metrics | ~5ms |428| `GET /api/distributions` | GET | Cluster size distributions | ~10ms |429| `GET /api/spotify/track` | GET | Spotify album art + preview for a track | ~200ms |430| `GET /api/health` | GET | Backend health check | ~1ms |431 432---433 434## 8. Frontend Architecture435 436### Technology Stack437 438| Component | Technology | Version |439|---|---|---|440| UI framework | React | 18.2.0 |441| Language | TypeScript | 5.2.2 |442| Build tool | Vite | 5.0.8 |443| Styling | Tailwind CSS | 3.3.6 |444| Charts | Recharts | 2.10.0 |445| Animations | Framer Motion | 10.16.0 |446| Search (fallback) | Fuse.js | 7.0.0 |447| 3D (optional) | Three.js + R3F | 0.160.0 |448| Icons | Lucide React | 0.294.0 |449| Testing | Vitest + RTL | Latest |450| Linting | ESLint + Prettier | Latest |451 452### Component Hierarchy453 454```455App.tsx456├── Header.tsx # Tab navigation (Explorer | Recommendations | Comparison)457├── ClusterVisualization.tsx # Interactive 2D scatter plot458│ ├── Cluster legend with filter buttons459│ ├── Song dots (Framer Motion animated)460│ └── Hover tooltip overlay461├── RecommendationSystem.tsx # Search + live recommendations462│ ├── Fuzzy search input (debounced, 300ms)463│ ├── Song player card (with audio visualizer)464│ └── Recommendation list (from API)465├── AlgorithmComparison.tsx # Performance dashboard466│ ├── Algorithm cards (clickable)467│ ├── Radar chart (Recharts)468│ ├── Pie chart (cluster distribution)469│ └── Detailed metrics table470└── SongDetailsPanel (inline) # Song details sidebar in Explorer tab471```472 473### Data Flow474 475```476App Mount477 │478 ▼479useData() hook480 │481 ├── apiService.getSongs(1000) → GET /api/songs482 ├── apiService.getAlgorithms() → GET /api/algorithms483 ├── apiService.getDistributions() → GET /api/distributions484 └── apiService.getClusterLabels() → GET /api/labels/{algo}485 │486 ▼ (all 3 in parallel via Promise.all)487 │488Songs enriched with:489 ├── UMAP x, y, z coords (from backend)490 └── Spotify album art (fetched on selection)491 │492 ▼493Rendered in ClusterVisualization / RecommendationSystem / AlgorithmComparison494```495 496When a user selects a song, the `RecommendationSystem` component fires:497 498```499apiService.getRecommendations(songId, algorithm, 5) → GET /api/recommendations/{songId}500```501 502This returns live results from the backend's model inference.503 504### Design System505 506The UI follows a **"dark neon"** music app aesthetic:507 508| Element | Implementation |509|---|---|510| **Background** | `#0a0a0f` base with radial gradient overlays (purple, blue, pink) |511| **Glass panels** | `backdrop-blur-xl` + semi-transparent white gradients + box shadows |512| **Typography** | Space Grotesk (headings) + Outfit (body) via Google Fonts |513| **Cluster colors** | 6-color palette: `#ff006e`, `#8338ec`, `#3a86ff`, `#06ffa5`, `#ffbe0b`, `#fb5607` |514| **Animations** | Ambient drift blobs, gradient text shifts, equalizer bars, spring-physics dot interactions |515| **Grid overlay** | Subtle purple grid lines at 64px intervals |516 517---518 519## 9. File Structure Summary520 521```522sonic_clusters/523├── backend/524│ ├── main.py # FastAPI app — routes, Pydantic models, Spotify endpoint525│ ├── requirements.txt # Python deps: fastapi, scikit-learn, pandas, numpy, etc.526│ ├── .env # Spotify API credentials (not committed)527│ ├── services/528│ │ ├── clustering.py # ClusteringService — model loading, metrics, search529│ │ ├── recommendations.py # RecommendationService — Euclidean similarity530│ │ └── spotify.py # SpotifyService — album art & track metadata531│ ├── models/532│ │ ├── preprocessor.pkl # ColumnTransformer (StandardScaler + OneHotEncoder)533│ │ ├── kmeans_model.pkl # Trained KMeans(n_clusters=6)534│ │ ├── hierarchical_model.pkl # Trained AgglomerativeClustering(n_clusters=6)535│ │ ├── dbscan_model.pkl # Trained DBSCAN(eps=1.44, min_samples=3)536│ │ └── pca_transformer.pkl # Trained PCA(n_components=10)537│ └── data/538│ ├── songs_with_clusters.csv # 12,000 songs + cluster labels + UMAP coords539│ ├── feature_matrix.npy # Preprocessed 30D feature vectors540│ ├── kmeans_centroids.npy # 6 cluster center vectors541│ ├── metadata.json # Model hyperparameters542│ └── feature_info.json # Feature schema + categories543├── src/544│ ├── App.tsx # Main app component545│ ├── main.tsx # React entry point546│ ├── index.css # Global styles + Tailwind layers547│ ├── components/548│ │ ├── Header.tsx # Navigation tabs549│ │ ├── ClusterVisualization.tsx # 2D scatter plot550│ │ ├── RecommendationSystem.tsx # Search + Spotify integration551│ │ └── AlgorithmComparison.tsx # Performance dashboard552│ ├── hooks/553│ │ └── useData.ts # React hooks for API calls554│ ├── services/555│ │ └── api.ts # API client class + Spotify calls556│ └── types/557│ └── index.ts # TypeScript interfaces558├── retrain_models.py # Automated model retraining (merge + fit + export)559├── process_kaggle_dataset.py # Kaggle CSV → MSD format converter560├── export_models.py # Phase 3 → backend model exporter561├── add_umap.py # UMAP 3D coordinate generator562├── start.bat / start.sh # One-click startup scripts563├── package.json # Node deps564├── tailwind.config.js # Custom theme (neon colors, fonts, animations)565├── vite.config.ts # Vite build config566└── tsconfig.json # TypeScript config567```568 569---570 571## 10. Key Takeaways572 573### What Worked Well574 5751. **K-Means with K=6** produced the best quantitative clustering results across all three standard metrics, making it the default recommendation algorithm.5762. **PCA + DBSCAN combination** — Using PCA to reduce dimensionality to 10 components rescued DBSCAN from the "curse of dimensionality", allowing it to find 22 micro-clusters and successfully identify noise/outliers.5773. **Live model serving via FastAPI** allows real-time prediction for new songs — a significant upgrade over static pre-computed JSON.5784. **The client-server architecture** cleanly separates ML concerns (Python/scikit-learn) from presentation (React/TypeScript).579 580### Known Limitations581 5821. **Low Silhouette Scores** (~0.10) — Music features form a continuous distribution, not discrete clusters. The clusters are more of a convenient partitioning than naturally emergent groups.5832. **Hierarchical prediction approximation** — Since `AgglomerativeClustering` doesn't support `.predict()`, new-song predictions for Hierarchical and DBSCAN fall back to nearest K-Means centroid.5843. **Spotify audio preview restrictions** — Spotify has restricted audio preview URLs for many tracks in their catalog. The frontend gracefully falls back to a direct Spotify link when previews are unavailable.585 586### Future Improvements587- **HDBSCAN** as an alternative density-based method that handles varying densities588- **Additional features** from the MSD (e.g., segments analysis, sections, bars) for richer clustering589- **Ensemble clustering** combining weighted votes from all three algorithms590- **Spotify OAuth** for personalized playlist ingestion (currently blocked by Client Credentials restrictions)591 592---593 594> *Built for DATA480 Project Phase 3 — Modelling and Testing* 🎵595 