SLIM (Sparse Linear Methods) recommender system in Rust
Project description
fastslim
Rust implementation of SLIM (Sparse Linear Methods) for item-based collaborative filtering with parallel computation.
Usage
uv add fastslim
import fastslim
from scipy import sparse
# User-item interaction matrix (users x items)
interactions = sparse.random(1000, 500, density=0.01, format='csr')
# Fit model
weights = fastslim.fit(
interactions,
lambd=0.5, # L1 regularization (sparsity)
beta=0.5, # L2 regularization
)
Development
uv sync --extra dev
API
The library exposes one function - fastslim.fit, which fits the model using coordinate descent solver.
Parameters:
interaction_matrix: scipy sparse matrix (users x items)lambd: L1 regularization coefficientbeta: L2 regularization coefficientmax_iter: maximum iterations per itemn_threads: number of threads (None = all cores)
Returns: CSR item-item weight matrix
Algorithm
This section is about math behind the model, some optimization of coordinate descend for the specific scenario with non-negative weights and zero diagonal in item-to-item matrix.
Objective (per item)
We solve each item independently with coordinate descent:
$$ L_i = \frac{1}{2} \left| a_i - \sum_j w_{ij} a_j \right|^2 + \lambda \sum_j |w_{ij}| + \beta \sum_j w_{ij}^2 \rightarrow \min_{w_{i1}\dots w_{in}} $$
Since we solve for non-negative weights, the absolute value drops and the update is projected onto the feasible set.
The coordinate-wise optimum (gradient = 0) is:
$$ w_{ik} = \frac{\langle a_i, a_k \rangle - \sum_{j \ne k} w_{ij} \langle a_j, a_k \rangle - \lambda}{|a_k|^2 + \beta} $$
Projected to non-negative weights:
$$ w_{ik} = \max\left(0, \frac{\langle a_i, a_k \rangle - \sum_{j \ne k} w_{ij} \langle a_j, a_k \rangle - \lambda}{|a_k|^2 + \beta} \right) $$
Practical speedups
These follow directly from the non-negativity constraint and the structure of $P = X^T X$:
- If $\langle a_i, a_k \rangle < \lambda$, then even with all other weights zero the update is non-positive, so $w_{ik}$ will always be 0. We skip such coordinates.
- If for item $i$ the interaction norm is too small ($P_{ii} < \lambda^2$), the entire solution is zero and we can skip the item.
Implementation
-
Gram::from_csr(...)- build the Gram matrix $P = X^T X$ in sparse form (from CSR rows).- Diagonal:
P_kk = sum(value^2)per item. - Off-diagonal: for each user row, accumulate all item pairs
(a, b)withP_ab += v_a * v_b. - Convert pair map into symmetric adjacency lists
nbrs[k] = {(j, P_kj)}.
- Diagonal:
-
solve_item(...)- for each target itemi, solve only over neighbors ofi.- Candidates are
cand = { k | P_ik != 0 }, filtered byP_ik >= lambd(valid under non-negative inputs). - Initialize residuals
r[t] = P_ikfor each local candidate indext.
- Candidates are
-
solve_item(...)- coordinate descent with residual maintenance (non-negative weights).- For each candidate
k, update:a = P_kk + betac = r[t] + P_kk * w[t]w_new = max(0, (c - lambd) / a)
- If
delta = w_new - w_oldis non-zero, update residuals:- For each neighbor
(j, P_kj)innbrs[k]that is a candidate:r[pos(j)] -= P_kj * delta - Also update self residual:
r[t] -= P_kk * delta.
- For each neighbor
- For each candidate
-
solve_item(...)- active set and early stopping of optimizer- After a short warm-up, iterate only candidates that are non-zero or still potentially active (
c > lambd). - Stop if max weight change falls below a tolerance, or after
max_iter.
- After a short warm-up, iterate only candidates that are non-zero or still potentially active (
-
solve_slim(...)- ssemble sparse weights.- Return only
(k, w_ki)wherew > 0. - No self-edges, because diagonal is always zero.
- Return only
Tests
uv sync --extra dev
uv run pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file fastslim-0.1.3.tar.gz.
File metadata
- Download URL: fastslim-0.1.3.tar.gz
- Upload date:
- Size: 13.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1735e55765937ca0808e53fa4c1a9648bffe57325f393d90a6dd4da4cd3d0924
|
|
| MD5 |
31df7613d40c1d56856753def841ef97
|
|
| BLAKE2b-256 |
eebface51896bae0db19c99e2e58d689c5e3843cb186a1994959b64c7a6cd104
|