Membangun Sistem Rekomendasi Musik dengan Python: Tutorial Step-by-Step
Pendahuluan
Pernahkah kamu merasa takjub bagaimana Spotify bisa tahu persis lagu apa yang ingin kamu dengarkan berikutnya? Di balik fitur “Discover Weekly” itu terdapat sistem rekomendasi berbasis deep learning yang belajar dari jutaan pola pendengar.
Dalam tutorial ini, kita akan membangun sistem rekomendasi musik dari nol menggunakan Python dan PyTorch. Pendekatannya adalah Neural Collaborative Filtering (NCF) — sebuah teknik yang menggabungkan matrix factorization klasik dengan kemampuan neural network modern. Bayangkan jika kamu ingin membangun platform musik seperti Spotify atau Joox versi lokal, sistem inilah fondasinya.
Yang akan kamu pelajari:
- Menyiapkan dataset interaksi user-musik
- Membangun model NCF dengan PyTorch
- Melatih dan mengevaluasi model
- Menghasilkan rekomendasi untuk user baru
Mempersiapkan Data dan Lingkungan Pengembangan
Instalasi Library
pip install torch pandas numpy scikit-learn tqdm
Struktur Dataset
Sistem rekomendasi bekerja dari data interaksi — siapa mendengarkan apa, berapa kali. Kita akan membuat dataset sintetis yang merepresentasikan pola nyata:
import pandas as pd
import numpy as np
import torch
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
# Buat dataset sintetis: 500 user, 200 lagu
np.random.seed(42)
n_users = 500
n_songs = 200
n_interactions = 5000
user_ids = np.random.randint(0, n_users, n_interactions)
song_ids = np.random.randint(0, n_songs, n_interactions)
# Rating implisit: 1 = pernah didengar, 0 = tidak
ratings = np.ones(n_interactions)
df = pd.DataFrame({
'user_id': user_ids,
'song_id': song_ids,
'rating': ratings
})
# Hapus duplikat
df = df.drop_duplicates(['user_id', 'song_id']).reset_index(drop=True)
print(f"Total interaksi unik: {len(df)}")
print(df.head())
Membuat PyTorch Dataset
class MusicDataset(Dataset):
def __init__(self, users, songs, ratings):
self.users = torch.LongTensor(users)
self.songs = torch.LongTensor(songs)
self.ratings = torch.FloatTensor(ratings)
def __len__(self):
return len(self.ratings)
def __getitem__(self, idx):
return self.users[idx], self.songs[idx], self.ratings[idx]
# Split data
train_df, test_df = train_test_split(df, test_size=0.2, random_state=42)
train_dataset = MusicDataset(
train_df['user_id'].values,
train_df['song_id'].values,
train_df['rating'].values
)
test_dataset = MusicDataset(
test_df['user_id'].values,
test_df['song_id'].values,
test_df['rating'].values
)
train_loader = DataLoader(train_dataset, batch_size=256, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False)
Pemahaman solid tentang struktur data sangat membantu di sini — kamu bisa merujuk ke artikel Menguasai Array dan Operasi Dasar dalam Struktur Data untuk memperkuat fondasi tersebut.
Membangun Model Deep Learning untuk Rekomendasi
Arsitektur Neural Collaborative Filtering
NCF menggabungkan dua jalur:
- GMF (Generalized Matrix Factorization) — menangkap interaksi linear antara user dan item
- MLP (Multi-Layer Perceptron) — menangkap pola non-linear yang lebih kompleks
Kedua jalur ini kemudian digabungkan di layer output untuk menghasilkan skor prediksi.
import torch.nn as nn
class NeuralCF(nn.Module):
def __init__(self, n_users, n_songs, embed_dim=64, mlp_layers=[128, 64, 32]):
super(NeuralCF, self).__init__()
# Embedding untuk jalur GMF
self.user_embed_gmf = nn.Embedding(n_users, embed_dim)
self.song_embed_gmf = nn.Embedding(n_songs, embed_dim)
# Embedding untuk jalur MLP
self.user_embed_mlp = nn.Embedding(n_users, embed_dim)
self.song_embed_mlp = nn.Embedding(n_songs, embed_dim)
# Layer MLP
mlp_input_dim = embed_dim * 2
layers = []
for out_dim in mlp_layers:
layers.append(nn.Linear(mlp_input_dim, out_dim))
layers.append(nn.ReLU())
layers.append(nn.Dropout(0.2))
mlp_input_dim = out_dim
self.mlp = nn.Sequential(*layers)
# Layer output: gabungkan GMF + MLP
self.output = nn.Linear(embed_dim + mlp_layers[-1], 1)
self.sigmoid = nn.Sigmoid()
self._init_weights()
def _init_weights(self):
for module in self.modules():
if isinstance(module, nn.Embedding):
nn.init.normal_(module.weight, std=0.01)
elif isinstance(module, nn.Linear):
nn.init.xavier_uniform_(module.weight)
def forward(self, user, song):
# Jalur GMF: element-wise product
gmf_out = self.user_embed_gmf(user) * self.song_embed_gmf(song)
# Jalur MLP: concatenate lalu feed-forward
mlp_input = torch.cat([
self.user_embed_mlp(user),
self.song_embed_mlp(song)
], dim=-1)
mlp_out = self.mlp(mlp_input)
# Gabungkan dan prediksi
combined = torch.cat([gmf_out, mlp_out], dim=-1)
score = self.sigmoid(self.output(combined))
return score.squeeze()
Arsitektur ini memanfaatkan pola OOP Python secara intensif. Jika kamu ingin memperdalam pemahaman tentang class dan inheritance di Python, artikel OOP di Python: Class, Object, dan Inheritance bisa menjadi referensi yang sangat berguna.
Melatih, Mengevaluasi, dan Menghasilkan Rekomendasi
Inisialisasi Model dan Optimizer
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Menggunakan device: {device}")
model = NeuralCF(
n_users=n_users,
n_songs=n_songs,
embed_dim=64,
mlp_layers=[128, 64, 32]
).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)
criterion = nn.BCELoss()
Training Loop
def train_epoch(model, loader, optimizer, criterion, device):
model.train()
total_loss = 0
for users, songs, ratings in loader:
users = users.to(device)
songs = songs.to(device)
ratings = ratings.to(device)
optimizer.zero_grad()
predictions = model(users, songs)
loss = criterion(predictions, ratings)
loss.backward()
optimizer.step()
total_loss += loss.item()
return total_loss / len(loader)
def evaluate(model, loader, device):
model.eval()
total_loss = 0
with torch.no_grad():
for users, songs, ratings in loader:
users = users.to(device)
songs = songs.to(device)
ratings = ratings.to(device)
predictions = model(users, songs)
loss = nn.BCELoss()(predictions, ratings)
total_loss += loss.item()
return total_loss / len(loader)
# Latih selama 20 epoch
n_epochs = 20
for epoch in range(n_epochs):
train_loss = train_epoch(model, train_loader, optimizer, criterion, device)
val_loss = evaluate(model, test_loader, device)
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1}/{n_epochs} | Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f}")
Output yang diharapkan:
Epoch 5/20 | Train Loss: 0.6821 | Val Loss: 0.6934
Epoch 10/20 | Train Loss: 0.6543 | Val Loss: 0.6712
Epoch 15/20 | Train Loss: 0.6201 | Val Loss: 0.6480
Epoch 20/20 | Train Loss: 0.5934 | Val Loss: 0.6254
Menghasilkan Rekomendasi
Setelah model dilatih, fungsi berikut akan menghasilkan top-K lagu terbaik untuk seorang user:
def get_recommendations(model, user_id, n_songs, top_k=10, device='cpu'):
model.eval()
with torch.no_grad():
# Buat tensor: user ini vs semua lagu
user_tensor = torch.LongTensor([user_id] * n_songs).to(device)
song_tensor = torch.LongTensor(range(n_songs)).to(device)
scores = model(user_tensor, song_tensor).cpu().numpy()
# Ambil top-K lagu dengan skor tertinggi
top_song_indices = scores.argsort()[::-1][:top_k]
top_scores = scores[top_song_indices]
recommendations = [
{'song_id': int(idx), 'score': float(score)}
for idx, score in zip(top_song_indices, top_scores)
]
return recommendations
# Contoh: rekomendasikan untuk user #42
recs = get_recommendations(model, user_id=42, n_songs=n_songs, top_k=5, device=device)
print("Rekomendasi untuk User 42:")
for i, rec in enumerate(recs, 1):
print(f" {i}. Song ID {rec['song_id']} — Skor: {rec['score']:.4f}")
Contoh Kasus Nyata: Deployment sebagai API
Bayangkan kamu ingin membangun layanan musik streaming seperti Joox atau Langit Musik. Sistem ini bisa diintegrasikan sebagai microservice dengan menyimpan dan memuat model:
import json
def save_model(model, filepath='music_recommender.pth'):
"""Simpan model untuk deployment"""
torch.save({
'model_state_dict': model.state_dict(),
'n_users': n_users,
'n_songs': n_songs,
'embed_dim': 64,
'mlp_layers': [128, 64, 32]
}, filepath)
print(f"Model disimpan: {filepath}")
def load_model(filepath='music_recommender.pth', device='cpu'):
"""Load model yang sudah dilatih"""
checkpoint = torch.load(filepath, map_location=device)
model = NeuralCF(
n_users=checkpoint['n_users'],
n_songs=checkpoint['n_songs'],
embed_dim=checkpoint['embed_dim'],
mlp_layers=checkpoint['mlp_layers']
).to(device)
model.load_state_dict(checkpoint['model_state_dict'])
model.eval()
return model
# Simulasi API endpoint sederhana
def recommend_api(user_id: int, top_k: int = 10) -> dict:
loaded_model = load_model()
recs = get_recommendations(loaded_model, user_id, n_songs, top_k)
return {
"user_id": user_id,
"recommendations": recs,
"model": "NeuralCF-v1"
}
# Simpan model yang sudah dilatih
save_model(model)
# Simulasi request API
response = recommend_api(user_id=99, top_k=5)
print(json.dumps(response, indent=2))
Dalam produksi, fungsi recommend_api ini bisa dibungkus dengan FastAPI dan di-cache hasilnya di Redis agar latensi tetap rendah.
Troubleshooting: Error yang Sering Muncul
CUDA out of memory saat training
Penyebab: Ukuran batch terlalu besar untuk VRAM GPU yang tersedia, terutama saat model memiliki embedding layer berukuran besar dengan banyak user atau item.
Solusi:
# Kurangi batch size
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) # turunkan dari 256
# Atau gunakan gradient accumulation untuk mensimulasikan batch besar
accumulation_steps = 4
optimizer.zero_grad()
for i, (users, songs, ratings) in enumerate(train_loader):
predictions = model(users.to(device), songs.to(device))
loss = criterion(predictions, ratings.to(device)) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
IndexError: index out of range in self.weight
Penyebab: User ID atau Song ID dalam data melebihi batas n_users/n_songs yang didaftarkan ke layer nn.Embedding. Ini sering terjadi saat ID tidak berurutan dari 0.
Solusi:
# Encode ulang ID menjadi range [0, N-1] yang berurutan
from sklearn.preprocessing import LabelEncoder
user_encoder = LabelEncoder()
song_encoder = LabelEncoder()
df['user_id'] = user_encoder.fit_transform(df['user_id'])
df['song_id'] = song_encoder.fit_transform(df['song_id'])
# Update jumlah user/song setelah encoding
n_users = df['user_id'].nunique()
n_songs = df['song_id'].nunique()
print(f"n_users: {n_users}, n_songs: {n_songs}")
Loss tidak turun / model tidak belajar (loss stagnan)
Penyebab: Learning rate terlalu besar menyebabkan gradien meledak (exploding gradient), atau terlalu kecil sehingga pembaruan bobot sangat lambat dan model macet di titik tertentu.
Solusi:
# Gunakan learning rate scheduler untuk menyesuaikan LR secara otomatis
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=3, verbose=True
)
for epoch in range(n_epochs):
train_loss = train_epoch(model, train_loader, optimizer, criterion, device)
val_loss = evaluate(model, test_loader, device)
scheduler.step(val_loss) # Turunkan LR otomatis jika val_loss stagnan
if (epoch + 1) % 5 == 0:
print(f"Epoch {epoch+1} | Train: {train_loss:.4f} | Val: {val_loss:.4f} | LR: {optimizer.param_groups[0]['lr']:.6f}")
RuntimeError: Expected all tensors to be on the same device
Penyebab: Sebagian tensor berada di CPU dan sebagian di GPU saat operasi dijalankan, menyebabkan konflik device yang tidak bisa diselesaikan secara otomatis oleh PyTorch.
Solusi:
# Definisikan device di satu tempat dan gunakan konsisten
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Dalam training loop, pindahkan SEMUA tensor ke device yang sama
for users, songs, ratings in train_loader:
users = users.to(device)
songs = songs.to(device)
ratings = ratings.to(device)
# Model juga harus di device yang sama saat inisialisasi
# model = NeuralCF(...).to(device) ← lakukan sekali saat awal
predictions = model(users, songs)
Pertanyaan yang Sering Diajukan
Apa itu Neural Collaborative Filtering dan mengapa lebih baik dari metode klasik?
Neural Collaborative Filtering (NCF) adalah pendekatan rekomendasi yang menggabungkan matrix factorization dengan neural network. Berbeda dari metode klasik seperti SVD yang hanya mampu menangkap hubungan linear antar user dan item, NCF bisa memodelkan pola non-linear yang lebih kompleks. Hasilnya, NCF mampu memberikan rekomendasi yang lebih personal dan akurat, terutama pada dataset yang memiliki pola interaksi yang rumit.
Bagaimana cara menangani cold-start untuk user baru yang belum punya histori?
Untuk user baru tanpa histori (cold-start), ada beberapa pendekatan yang umum digunakan: pertama, tampilkan rekomendasi berbasis popularitas (lagu yang paling banyak didengar); kedua, gunakan onboarding singkat di mana user memilih genre favorit; ketiga, setelah ada 5–10 interaksi, mulai gunakan model NCF. Kamu juga bisa menggunakan embedding content-based (berdasarkan fitur audio lagu) sebagai fallback saat data interaksi belum cukup.
Apa perbedaan collaborative filtering dan content-based filtering?
Collaborative filtering membuat rekomendasi berdasarkan pola perilaku pengguna lain yang serupa — “user yang suka lagu A juga suka lagu B”. Content-based filtering merekomendasikan item yang memiliki atribut serupa dengan item yang sudah disukai user — “kamu suka lagu bergenre jazz, ini lagu jazz lain”. NCF yang kita bangun menggunakan collaborative filtering; sistem produksi seperti Spotify umumnya menggabungkan keduanya (hybrid recommender).
Bagaimana cara mengukur kualitas rekomendasi secara objektif?
Metrik evaluasi yang umum digunakan antara lain: Hit Rate@K (apakah item relevan masuk dalam top-K rekomendasi), NDCG@K (mengukur kualitas urutan rekomendasi), dan Precision@K / Recall@K. Untuk dataset dengan rating implisit seperti tutorial ini, biasanya digunakan metode leave-one-out evaluation — satu interaksi per user disembunyikan untuk pengujian, sisanya untuk training.
Apakah model ini bisa dipakai untuk rekomendasi selain musik?
Tentu saja. Arsitektur NCF bersifat agnostik terhadap domain — yang diperlukan hanya data interaksi user-item. Model yang sama bisa digunakan untuk rekomendasi film, buku, produk e-commerce, atau artikel berita. Jika ingin membangun fitur rekomendasi untuk toko online seperti Tokopedia, kamu hanya perlu mengganti song_id dengan product_id dan user_id tetap sama.
Kesimpulan
Kita telah berhasil membangun sistem rekomendasi musik end-to-end menggunakan Neural Collaborative Filtering dengan PyTorch. Perjalanan kita mencakup menyiapkan dataset interaksi, merancang arsitektur NCF yang menggabungkan GMF dan MLP, melatih model dengan training loop yang proper, mengevaluasi performa model, dan akhirnya menghasilkan rekomendasi yang siap di-deploy sebagai API.
Sistem ini merupakan fondasi yang kuat untuk dikembangkan lebih lanjut — kamu bisa menambahkan fitur audio (mel-spectrogram) sebagai input tambahan, mengintegrasikan dengan database produksi, atau menerapkan teknik optimasi yang lebih lanjut. Selamat belajar dan terus bereksperimen! Jika ada pertanyaan atau kamu ingin mendalami aspek lain dari deep learning, jangan ragu untuk menjelajahi artikel-artikel lainnya di KamusNgoding.