Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import pandas as pd | |
| import pickle | |
| from scipy.sparse.linalg import svds | |
| class ALSRecommender: | |
| def __init__(self, factors=50, iterations=20, regularization=0.01, alpha=40): | |
| self.factors = factors | |
| self.iterations = iterations | |
| self.regularization = regularization | |
| self.alpha = alpha | |
| self.user_factors = None | |
| self.item_factors = None | |
| self.user_history = {} | |
| self.is_fitted = False | |
| def fit(self, weighted_matrix, train_df): | |
| print(f"Fitting SVD with factors={self.factors} ...") | |
| matrix = (weighted_matrix * self.alpha).tocsr().astype(float) | |
| k = min(self.factors, min(matrix.shape) - 1) | |
| U, sigma, Vt = svds(matrix, k=k) | |
| idx = np.argsort(sigma)[::-1] | |
| sigma = sigma[idx] | |
| U = U[:, idx] | |
| Vt = Vt[idx, :] | |
| sqrt_s = np.sqrt(sigma) | |
| self.user_factors = U * sqrt_s[np.newaxis, :] | |
| self.item_factors = Vt.T * sqrt_s[np.newaxis, :] | |
| self.user_history = ( | |
| train_df.groupby("user_idx")["item_idx"].apply(set).to_dict() | |
| ) | |
| self.is_fitted = True | |
| print(f"Fitted — user_factors: {self.user_factors.shape}, item_factors: {self.item_factors.shape}") | |
| return self | |
| def recommend(self, user_idx, k=10): | |
| if not self.is_fitted: | |
| raise RuntimeError("Call fit() first") | |
| scores = self.item_factors @ self.user_factors[user_idx] | |
| for i in self.user_history.get(user_idx, set()): | |
| if i < len(scores): | |
| scores[i] = -np.inf | |
| return np.argsort(scores)[::-1][:k] | |
| def recommend_batch(self, user_indices, k=10): | |
| return {u: self.recommend(u, k) for u in user_indices} | |
| def get_similar_items(self, item_idx, k=10): | |
| scores = self.item_factors @ self.item_factors[item_idx] | |
| scores[item_idx] = -np.inf | |
| top_k = np.argsort(scores)[::-1][:k] | |
| return top_k, scores[top_k] | |
| def save(self, path): | |
| with open(path, "wb") as f: | |
| pickle.dump(self, f) | |
| print(f"Saved to {path}") | |
| def load(path): | |
| with open(path, "rb") as f: | |
| return pickle.load(f) | |