This tutorial will locomotion you done utilizing PyTorch to instrumentality a Neural Collaborative Filtering (NCF) proposal system. NCF extends accepted matrix factorisation by utilizing neural networks to exemplary analyzable user-item interactions.
Introduction
Neural Collaborative Filtering (NCF) is simply a state-of-the-art attack for building proposal systems. Unlike accepted collaborative filtering methods that trust connected linear models, NCF utilizes deep learning to seizure non-linear relationships betwixt users and items.
In this tutorial, we’ll:
- Prepare and research nan MovieLens dataset
- Implement nan NCF exemplary architecture
- Train nan model
- Evaluate its performance
- Generate recommendations for users
Setup and Environment
First, let’s instal nan basal libraries and import them:
!pip instal torch numpy pandas matplotlib seaborn scikit-learn tqdm
import os
import numpy arsenic np
import pandas arsenic pd
import torch
import torch.nn arsenic nn
import torch.optim arsenic optim
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot arsenic plt
import seaborn arsenic sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tqdm import tqdm
import random
torch.manual_seed(42)
np.random.seed(42)
random.seed(42)
device = torch.device("cuda" if torch.cuda.is_available() other "cpu")
print(f"Using device: {device}")
Data Loading and Preparation
We’ll usage nan MovieLens 100K dataset, which contains 100,000 movie ratings from users:
!wget -nc https://files.grouplens.org/datasets/movielens/ml-100k.zip
!unzip -q -n ml-100k.zip
ratings_df = pd.read_csv('ml-100k/u.data', sep='t', names=['user_id', 'item_id', 'rating', 'timestamp'])
movies_df = pd.read_csv('ml-100k/u.item', sep='|', encoding='latin-1',
names=['item_id', 'title', 'release_date', 'video_release_date',
'IMDb_URL', 'unknown', 'Action', 'Adventure', 'Animation',
'Children', 'Comedy', 'Crime', 'Documentary', 'Drama', 'Fantasy',
'Film-Noir', 'Horror', 'Musical', 'Mystery', 'Romance', 'Sci-Fi',
'Thriller', 'War', 'Western'])
print("Ratings data:")
print(ratings_df.head())
print("nMovies data:")
print(movies_df[['item_id', 'title']].head())
print(f"nTotal number of ratings: {len(ratings_df)}")
print(f"Number of unsocial users: {ratings_df['user_id'].nunique()}")
print(f"Number of unsocial movies: {ratings_df['item_id'].nunique()}")
print(f"Rating range: {ratings_df['rating'].min()} to {ratings_df['rating'].max()}")
print(f"Average rating: {ratings_df['rating'].mean():.2f}")
plt.figure(figsize=(10, 6))
sns.countplot(x='rating', data=ratings_df)
plt.title('Distribution of Ratings')
plt.xlabel('Rating')
plt.ylabel('Count')
plt.show()
ratings_df['label'] = (ratings_df['rating'] >= 4).astype(np.float32)
Data Preparation for NCF
Now, let’s hole nan information for our NCF model:
train_df, test_df = train_test_split(ratings_df, test_size=0.2, random_state=42)
print(f"Training group size: {len(train_df)}")
print(f"Test group size: {len(test_df)}")
num_users = ratings_df['user_id'].max()
num_items = ratings_df['item_id'].max()
print(f"Number of users: {num_users}")
print(f"Number of items: {num_items}")
class NCFDataset(Dataset):
def __init__(self, df):
self.user_ids = torch.tensor(df['user_id'].values, dtype=torch.long)
self.item_ids = torch.tensor(df['item_id'].values, dtype=torch.long)
self.labels = torch.tensor(df['label'].values, dtype=torch.float)
def __len__(self):
return len(self.user_ids)
def __getitem__(self, idx):
return {
'user_id': self.user_ids[idx],
'item_id': self.item_ids[idx],
'label': self.labels[idx]
}
train_dataset = NCFDataset(train_df)
test_dataset = NCFDataset(test_df)
batch_size = 256
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
Model Architecture
Now we’ll instrumentality nan Neural Collaborative Filtering (NCF) model, which combines Generalized Matrix Factorization (GMF) and Multi-Layer Perceptron (MLP) components:
class NCF(nn.Module):
def __init__(self, num_users, num_items, embedding_dim=32, mlp_layers=[64, 32, 16]):
super(NCF, self).__init__()
self.user_embedding_gmf = nn.Embedding(num_users + 1, embedding_dim)
self.item_embedding_gmf = nn.Embedding(num_items + 1, embedding_dim)
self.user_embedding_mlp = nn.Embedding(num_users + 1, embedding_dim)
self.item_embedding_mlp = nn.Embedding(num_items + 1, embedding_dim)
mlp_input_dim = 2 * embedding_dim
self.mlp_layers = nn.ModuleList()
for idx, layer_size successful enumerate(mlp_layers):
if idx == 0:
self.mlp_layers.append(nn.Linear(mlp_input_dim, layer_size))
else:
self.mlp_layers.append(nn.Linear(mlp_layers[idx-1], layer_size))
self.mlp_layers.append(nn.ReLU())
self.output_layer = nn.Linear(embedding_dim + mlp_layers[-1], 1)
self.sigmoid = nn.Sigmoid()
self._init_weights()
def _init_weights(self):
for m successful self.modules():
if isinstance(m, nn.Embedding):
nn.init.normal_(m.weight, mean=0.0, std=0.01)
elif isinstance(m, nn.Linear):
nn.init.kaiming_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
def forward(self, user_ids, item_ids):
user_embedding_gmf = self.user_embedding_gmf(user_ids)
item_embedding_gmf = self.item_embedding_gmf(item_ids)
gmf_vector = user_embedding_gmf * item_embedding_gmf
user_embedding_mlp = self.user_embedding_mlp(user_ids)
item_embedding_mlp = self.item_embedding_mlp(item_ids)
mlp_vector = torch.cat([user_embedding_mlp, item_embedding_mlp], dim=-1)
for furniture successful self.mlp_layers:
mlp_vector = layer(mlp_vector)
concat_vector = torch.cat([gmf_vector, mlp_vector], dim=-1)
prediction = self.sigmoid(self.output_layer(concat_vector)).squeeze()
return prediction
embedding_dim = 32
mlp_layers = [64, 32, 16]
model = NCF(num_users, num_items, embedding_dim, mlp_layers).to(device)
print(model)
Training nan Model
Let’s train our NCF model:
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)
def train_epoch(model, data_loader, criterion, optimizer, device):
model.train()
total_loss = 0
for batch successful tqdm(data_loader, desc="Training"):
user_ids = batch['user_id'].to(device)
item_ids = batch['item_id'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(user_ids, item_ids)
nonaccomplishment = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
return total_loss / len(data_loader)
def evaluate(model, data_loader, criterion, device):
model.eval()
total_loss = 0
predictions = []
true_labels = []
pinch torch.no_grad():
for batch successful tqdm(data_loader, desc="Evaluating"):
user_ids = batch['user_id'].to(device)
item_ids = batch['item_id'].to(device)
labels = batch['label'].to(device)
outputs = model(user_ids, item_ids)
nonaccomplishment = criterion(outputs, labels)
total_loss += loss.item()
predictions.extend(outputs.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
from sklearn.metrics import roc_auc_score, average_precision_score
auc = roc_auc_score(true_labels, predictions)
ap = average_precision_score(true_labels, predictions)
return {
'loss': total_loss / len(data_loader),
'auc': auc,
'ap': ap
}
num_epochs = 10
history = {'train_loss': [], 'val_loss': [], 'val_auc': [], 'val_ap': []}
for epoch successful range(num_epochs):
train_loss = train_epoch(model, train_loader, criterion, optimizer, device)
eval_metrics = evaluate(model, test_loader, criterion, device)
history['train_loss'].append(train_loss)
history['val_loss'].append(eval_metrics['loss'])
history['val_auc'].append(eval_metrics['auc'])
history['val_ap'].append(eval_metrics['ap'])
print(f"Epoch {epoch+1}/{num_epochs} - "
f"Train Loss: {train_loss:.4f}, "
f"Val Loss: {eval_metrics['loss']:.4f}, "
f"AUC: {eval_metrics['auc']:.4f}, "
f"AP: {eval_metrics['ap']:.4f}")
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history['train_loss'], label='Train Loss')
plt.plot(history['val_loss'], label='Validation Loss')
plt.title('Loss During Training')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(history['val_auc'], label='AUC')
plt.plot(history['val_ap'], label='Average Precision')
plt.title('Evaluation Metrics')
plt.xlabel('Epoch')
plt.ylabel('Score')
plt.legend()
plt.tight_layout()
plt.show()
torch.save(model.state_dict(), 'ncf_model.pth')
print("Model saved successfully!")
Generating Recommendations
Now let’s create a usability to make recommendations for users:
def generate_recommendations(model, user_id, n=10):
model.eval()
user_ids = torch.tensor([user_id] * num_items, dtype=torch.long).to(device)
item_ids = torch.tensor(range(1, num_items + 1), dtype=torch.long).to(device)
pinch torch.no_grad():
predictions = model(user_ids, item_ids).cpu().numpy()
items_df = pd.DataFrame({
'item_id': range(1, num_items + 1),
'score': predictions
})
user_rated_items = set(ratings_df[ratings_df['user_id'] == user_id]['item_id'].values)
items_df = items_df[~items_df['item_id'].isin(user_rated_items)]
top_n_items = items_df.sort_values('score', ascending=False).head(n)
recommendations = pd.merge(top_n_items, movies_df[['item_id', 'title']], on='item_id')
return recommendations[['item_id', 'title', 'score']]
test_users = [1, 42, 100]
for user_id successful test_users:
print(f"nTop 10 recommendations for personification {user_id}:")
recommendations = generate_recommendations(model, user_id, n=10)
print(recommendations)
print(f"nMovies that personification {user_id} has rated highly (4-5 stars):")
user_liked = ratings_df[(ratings_df['user_id'] == user_id) & (ratings_df['rating'] >= 4)]
user_liked = pd.merge(user_liked, movies_df[['item_id', 'title']], on='item_id')
user_liked[['item_id', 'title', 'rating']]
Evaluating nan Model Further
Let’s measure our exemplary further by computing immoderate further metrics:
def evaluate_model_with_metrics(model, test_loader, device):
model.eval()
predictions = []
true_labels = []
pinch torch.no_grad():
for batch successful tqdm(test_loader, desc="Evaluating"):
user_ids = batch['user_id'].to(device)
item_ids = batch['item_id'].to(device)
labels = batch['label'].to(device)
outputs = model(user_ids, item_ids)
predictions.extend(outputs.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
from sklearn.metrics import roc_auc_score, average_precision_score, precision_recall_curve, accuracy_score
binary_preds = [1 if p >= 0.5 other 0 for p successful predictions]
auc = roc_auc_score(true_labels, predictions)
ap = average_precision_score(true_labels, predictions)
accuracy = accuracy_score(true_labels, binary_preds)
precision, recall, thresholds = precision_recall_curve(true_labels, predictions)
plt.figure(figsize=(10, 6))
plt.plot(recall, precision, label=f'AP={ap:.3f}')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.legend()
plt.grid(True)
plt.show()
return {
'auc': auc,
'ap': ap,
'accuracy': accuracy
}
metrics = evaluate_model_with_metrics(model, test_loader, device)
print(f"AUC: {metrics['auc']:.4f}")
print(f"Average Precision: {metrics['ap']:.4f}")
print(f"Accuracy: {metrics['accuracy']:.4f}")
Cold Start Analysis
Let’s analyse really our exemplary performs for caller users aliases users pinch fewer ratings (cold commencement problem):
user_rating_counts = ratings_df.groupby('user_id').size().reset_index(name='count')
user_rating_counts['group'] = pd.cut(user_rating_counts['count'],
bins=[0, 10, 50, 100, float('inf')],
labels=['1-10', '11-50', '51-100', '100+'])
print("Number of users successful each standing wave group:")
print(user_rating_counts['group'].value_counts())
def evaluate_by_user_group(model, ratings_df, user_groups, device):
results = {}
for group_name, user_ids successful user_groups.items():
group_ratings = ratings_df[ratings_df['user_id'].isin(user_ids)]
group_dataset = NCFDataset(group_ratings)
group_loader = DataLoader(group_dataset, batch_size=256, shuffle=False)
if len(group_loader) == 0:
continue
model.eval()
predictions = []
true_labels = []
pinch torch.no_grad():
for batch successful group_loader:
user_ids = batch['user_id'].to(device)
item_ids = batch['item_id'].to(device)
labels = batch['label'].to(device)
outputs = model(user_ids, item_ids)
predictions.extend(outputs.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
from sklearn.metrics import roc_auc_score
try:
auc = roc_auc_score(true_labels, predictions)
results[group_name] = auc
except:
results[group_name] = None
return results
user_groups = {}
for group successful user_rating_counts['group'].unique():
users_in_group = user_rating_counts[user_rating_counts['group'] == group]['user_id'].values
user_groups[group] = users_in_group
group_performance = evaluate_by_user_group(model, test_df, user_groups, device)
plt.figure(figsize=(10, 6))
groups = []
aucs = []
for group, auc successful group_performance.items():
if auc is not None:
groups.append(group)
aucs.append(auc)
plt.bar(groups, aucs)
plt.xlabel('Number of Ratings per User')
plt.ylabel('AUC Score')
plt.title('Model Performance by User Rating Frequency (Cold Start Analysis)')
plt.ylim(0.5, 1.0)
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()
print("AUC scores by personification standing frequency:")
for group, auc successful group_performance.items():
if auc is not None:
print(f"{group}: {auc:.4f}")
Business Insights and Extensions
def analyze_predictions(model, data_loader, device):
model.eval()
predictions = []
true_labels = []
pinch torch.no_grad():
for batch successful data_loader:
user_ids = batch['user_id'].to(device)
item_ids = batch['item_id'].to(device)
labels = batch['label'].to(device)
outputs = model(user_ids, item_ids)
predictions.extend(outputs.cpu().numpy())
true_labels.extend(labels.cpu().numpy())
results_df = pd.DataFrame({
'true_label': true_labels,
'predicted_score': predictions
})
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
sns.histplot(results_df['predicted_score'], bins=30, kde=True)
plt.title('Distribution of Predicted Scores')
plt.xlabel('Predicted Score')
plt.ylabel('Count')
plt.subplot(1, 2, 2)
sns.boxplot(x='true_label', y='predicted_score', data=results_df)
plt.title('Predicted Scores by True Label')
plt.xlabel('True Label (0=Disliked, 1=Liked)')
plt.ylabel('Predicted Score')
plt.tight_layout()
plt.show()
avg_scores = results_df.groupby('true_label')['predicted_score'].mean()
print("Average prediction scores:")
print(f"Items personification disliked (0): {avg_scores[0]:.4f}")
print(f"Items personification liked (1): {avg_scores[1]:.4f}")
analyze_predictions(model, test_loader, device)
This tutorial demonstrates implementing Neural Collaborative Filtering, a heavy learning proposal strategy combining matrix factorization pinch neural networks. Using nan MovieLens dataset and PyTorch, we built a exemplary that generates personalized contented recommendations. The implementation addresses cardinal challenges, including nan acold commencement problem and provides capacity metrics for illustration AUC and precision-recall curves. This instauration tin beryllium extended pinch hybrid approaches, attraction mechanisms, aliases deployable web applications for various business proposal scenarios.
Here is nan Colab Notebook. Also, don’t hide to travel america on Twitter and subordinate our Telegram Channel and LinkedIn Group. Don’t Forget to subordinate our 85k+ ML SubReddit.

Asjad is an intern advisor astatine Marktechpost. He is persuing B.Tech successful mechanical engineering astatine nan Indian Institute of Technology, Kharagpur. Asjad is simply a Machine learning and heavy learning enthusiast who is ever researching nan applications of instrumentality learning successful healthcare.