2. ALS Math: Deriving Matrix Factorization for Recommendation Systems

Table of Contents

2.1. Recommender Systems — The Landscape

Before diving into ALS specifically, here is the core recommendation problem:

given a sparse matrix of user-item interactions, predict what a user would think of an item they haven’t interacted with yet.

And here our the solutions examined in this project:

Popularity Baseline. The simplest possible approach — ignore the user entirely, and recommend whatever is broadly well-rated or broadly interacted with. No personalization, but a critical floor every other method needs to beat, and often the practical fallback for cold-start users/items with no history at all.

Collaborative Filtering (CF). The general family of methods that use the collective behavior of many users, rather than item metadata such as genre or cast, to make recommendations. CF splits into two branches:

  • Memory-based CF — compute similarity directly between raw rows or columns of the interaction matrix. User-based CF finds users similar to you and recommends what they liked; item-based CF finds items similar to what you’ve liked and recommends those. No optimization is involved — just a similarity computation such as cosine or Pearson correlation and a weighted average at prediction time.
  • Model-based CF — instead of comparing raw rows/columns directly, learn a compressed, latent representation of users and items, and use that representation to generate predictions. This is where ALS lives.

ALS is collaborative filtering — it’s simply doing it implicitly, through a shared latent space, rather than through direct neighbor comparison.


2.2. Explicit vs. Implicit Feedback

Before building the model, it matters what kind of signal you’re feeding it.

Explicit feedback is a directly stated preference — a 1–5 star rating, a thumbs up/down, a comment. It’s unambiguous (“this user rated this movie a 4”), but sparse — most users only rate a small fraction of what they interact with.

Implicit feedback is inferred from behavior — clicks, views, purchases, watch time. It’s dense and always available, but ambiguous: the absence of an interaction doesn’t mean the user dislikes the item — it might just mean they never saw it.

This changes the entire loss function ALS optimizes. Implicit ALS uses a confidence-weighted formulation rather than direct squared-error regression on ratings — covered in chapter 4.

This post builds the math for the explicit case.


2.3. ALS Setup: Users, Movies, and a Very Sparse Matrix

Say we have 3 users and 4 movies:

  Titanic Avengers Notebook Matrix
Alice 5 – 4 –
Bob – 5 – 4
Carol 1 4 – 5

Call this the interaction matrix $A$, with dimensions 3 users × 4 movies.

The dashes represent missing values — that user never rated that movie.

Real-world versions of this matrix are typically 95%+ empty. In our MovieLens dataset, the matrix was 98.3% sparse. Most users have only ever rated a tiny fraction of the full catalog.

The entire goal of a recommender system is to fill in the dashes — to predict what Alice would rate Avengers, even though she’s never rated it.


2.4. Matrix Factorization: Introducing Latent Factors

The central idea of ALS is to assume that \(A\) can be approximately reconstructed as the product of two much smaller matrices:

\[A \approx UM^T\]

where:

  • \(U\) (users × \(k\)) — one row per user. Each row is that user’s coordinates in a \(k\)-dimensional latent factor space.
  • \(M\) (movies × \(k\)) — one row per movie. Each row is that movie’s coordinates in the same \(k\)-dimensional space.
  • \(k\) is a hyperparameter you choose — the number of latent dimensions. It’s usually small, such as 5, 10, or 20, relative to the number of users or movies.

How the Dimensions of A, U, M, Line Up

\(U\) has shape \((3 \times k)\), while \(M^T\) has shape \((k \times 4)\).

Therefore:

\[UM^T = (3 \times k)(k \times 4) = (3 \times 4)\]

which exactly matches the shape of $A$.

Every cell of \(A\), whether originally observed or missing, can now be computed as a dot product:

\[\hat{a}_{ij} = u_i \cdot m_j\]

What Are the Latent Factors?

The $k$ latent factors are abstract — the algorithm doesn’t label them “action,” “romance,” or “comedy.”

Instead, they capture whatever underlying structure governs user taste and movie characteristics and compress that structure into a small number of dimensions.

If Alice’s vector (made up of her k latent factors) and a movie’s vector (made up of the movie’s k latent factors) point in similar directions, their dot product is large, and the model predicts that Alice will like that movie.


2.5. Fitting the Model: Learn from what is present, ignore whatever is absent

Our goal is to take the sparse A we have from training data, split it into two suitable matrices - U and M, each with k latent factors, and compute the completed version of A as the product of U and M.

2.5.1 Initializing \(U\) and \(M\)

Before any optimization can happen, \(U\) and \(M\) starting values — typically small random numbers drawn from a distribution centered near zero.

Since ALS alternates between solving for \(U\) given \(M\) and solving for \(M\) given \(U\), the arbitrary starting point is gradually refined as the process alternates.

The algorithm doesn’t need a “correct” initial guess. It simply needs a reasonable, non-degenerate starting point to break symmetry and get the alternating optimization started.


2.5.2. Initialize K

We also need to initialize the number of latent factors to a reasonable approximation that we believe can best capture the underlying patterns in the data. Usually for thousands of users and thousands of movies, k should ideally be a number in single or double digits, like K= 2,5, 10, etc. More on this in section __. As of now, let’s just set k= a small number like 2.


2.5.3 The Derivation: Solving for a Single User’s Vector using Regularised square loss

Now that we have initialized \(U\) and \(M\), the next step is an iterative process to keep inching closer and closer to the optimal values of \(U\) and \(M\) that help us take \(A\) from a sparse to a fully populated interaction matrix.

Let’s start with computing an optmial \(U\). The method is same for optmizing \(M\).

The way we compute optimal \(U\), is by determining each optimal constituent vector \(u_{i}\) one by one for all \(i\) users in \(U\). ie, in optimizing \(U\), our goal is to find the best \((k \times 1)\) dimensional \(u_{i}\) for each user.

Let’s see what information is available to us to compute \(u_{i}\).

Setup for Alice

Let’s restrict the problem to just the movies Alice rated: Titanic and Notebook.

We know that:

\[r_{Alice} \approx M_{O_{Alice}}u_{Alice}\]

where:

  • \(r_{Alice}\) is her actual ratings, a \((2 \times 1)\) dimensional vector, that we get from the original sparse \(A\):
\[r_{Alice} = \begin{bmatrix} 5 \\ 4 \end{bmatrix}\]
  • \(M_{O_{Alice}}\) is the \((2 \times k)\) matrix containing the rows of \(M\) corresponding only to Titanic and Notebook with k latent factors each

  • \(u_{Alice}\) is the \((k \times 1)\) vector we are solving for.


Shape Check for \(r_{Alice}\)

\[r_{Alice} \approx M_{O_{Alice}}u_{Alice}\] \[(2 \times 1)= (2 \times k)(k \times 1)\]

So the dimensions line up.

So can we simply invert \(M_{O_{Alice}}\) and obtain \(u_{i}\) ?

If k=2, then, \(M_{O_{Alice}}\) happens to be square here because Alice rated exactly 2 movies and \(k=2\).

However,k will not equal the number of movies a user has rated, therefore, \(M_{O_{user}}\) will not always be square. (Even if it is square it may not be invertible - addressed later with regualrisation).

Example,

  • 10 movies → \(M_{O_i}\) is \((10 \times 2)\), so it isn’t square and cannot be directly inverted.
  • 1 movie → \(M_{O_i}\) is \((1 \times 2)\), so the system is underdetermined.

We therefore need an approach that works regardless of how many ratings a user has.


The Fix: Minimize Squared Error with Regularisation

Instead of trying to solve the system exactly, we minimize the squared reconstruction error with regularization. The rating determined by the product \(M_{O_{Alice}}u_{Alice}\), will have some error wrt the actual rating Alice has given this movie, i.,e \(r_{Alice}\).

Therefore, we can thinking of the optimal \(u_{alice}\) as the argument which minimizes this object. And to prevent overfitting, let’s add a regularisaiton constraint on the magnitude of \(u_{alice}\) .

\[L(u_{Alice}) = \left\| r_{Alice} - M_{O_{Alice}}u_{Alice} \right\|^2 + \lambda\|u_{Alice}\|^2\]

Since optimal \(u_{Alice}\) is the one which minimizes this loss, we take the derivative of the Loss with respect to $u_{Alice}$ and set it to zero:

\[\frac{\partial L}{\partial u_{Alice}} = -2M_{O_{Alice}}^T \left( r_{Alice} - M_{O_{Alice}}u_{Alice} \right) + 2\lambda u_{Alice} = 0\]

Expand:

\[-M_{O_{Alice}}^Tr_{Alice} + M_{O_{Alice}}^TM_{O_{Alice}}u_{Alice} + \lambda u_{Alice} = 0\]

Collect the \(u_{Alice}\) terms on one side:

\[\left( M_{O_{Alice}}^TM_{O_{Alice}} + \lambda I \right) u_{Alice} = M_{O_{Alice}}^Tr_{Alice}\]

The regularised term is square and positive definite and therefore invertible (one my most favourite proofs in linear algebra). So isolating \(u_{Alive}\) to one side, we have:

\[\boxed{ u_{Alice} = \left( M_{O_{Alice}}^T M_{O_{Alice}} + \lambda I \right)^{-1} M_{O_{Alice}}^T r_{Alice} }\]

The Crux of this derivation - dimensions of \(M_{O_{user}}^TM_{O_{user}}\) and \(u_{i}\).

\(M_{O_{Alice}}^TM_{O_{Alice}}\) is always a \(k \times k\) matrix, regardless of whether Alice rated 2 movies or 2,000.

Multiplying by \(M_{O_{Alice}}^T\) collapses a potentially tall, non-square system into a fixed, square.

Therefore, solving for any single user’s \(k\)-dimensional \(u_{i}\) vector always involves the same fixed-size matrix inversion, independent of how much or how little data that user has.

This is exactly what allows Spark to solve every user’s row in parallel and independently.


\(u_i\) Is Always the Same Size, No Matter How Many Movies a User Rated

Look back at the three users from the worked example. Each rated a different number of movies, yet each ends up with a vector of the exact same length:

User Movies rated \(M_{O_i}\) shape \(p \times k\) Solved \(u_i\) is \(k \times 1\)
Alice 2 (Titanic, Notebook) \(2 \times 2\) [4.5455, 3.6364]
Bob 2 (Avengers, Matrix) \(2 \times 2\) [2.5755, 4.1280]
Carol 3 (Titanic, Avengers, Matrix) \(3 \times 2\) [2.3260, 3.6445]

Carol rated one more movie than Alice or Bob, so her \(M_{O_{Carol}}\) is a taller, non-square \(3 \times 2\) matrix rather than \(2 \times 2\).Yet her solved vector \(u_{Carol}\) is still just 2 numbers, exactly the same length as Alice’s and Bob’s.

This is a direct consequence of the derivation.

Look at the final closed-form solution again:

\[u_i = \left( M_{O_i}^T M_{O_i} + \lambda I \right)^{-1} M_{O_i}^T r_i\]

No matter how tall \(M_{O_i}\) is — 2 rows for Alice, 3 for Carol, or potentially hundreds for a very active user — \(M_{O_i}^T M_{O_i}\) is always \(k \times k\).

In general \((k \times p)\) \((p \times k)\) = \((k \times k)\), regardless of what \(p\) is, where \(p\) represents the number of movies that user has rated.

The \(\lambda I\) term added on top is also always \(k \times k\).

Therefore, the matrix being inverted always has the same fixed shape, and solving:

\[(k \times k)^{-1}(k \times 1)\]

always produces a \(k \times 1\) vector — **one number per latent factor.

Intuitively, this comes from the fact that all users and movies share the same number of latent factors, k. So \(u_i\) being a vector in \(U\) will be constrained to k components, which arrive from the corresponding \(m_i\). So even if we have 3 movies for a user in \(r_i\), if there are k=10 latent factors, the \(u_i\) will be of dimension 10x1.


2.6 Recovery of Missing Interactions

ALS does not zero-fill or drop missing entries — it simply excludes them from the loss function. And loss functions are evaluated at a user level (or item level). So when we solve the \(U_i\) for each \(i^th\) user, we only consider the movies that user has rated, and yet end up recovering ratings for movies the user has never watched (explained in the following section).

This follows directly from the original setup:

\[r_i \approx M_{O_i}u_i\]

The subscript $O_i$ means that we restrict \(M\) to the rows corresponding to movies for which user $i$ has an observed rating. There is therefore no equation involving a movie the user has not rated. The vector \(r_i\) simply has no entry for that movie.

The training objective sums squared error only over observed ratings:

\[L = \sum_{(i,j)\in\text{observed}} \left(a_{ij} - u_i \cdot m_j\right)^2 + \lambda \left( \|U\|^2 + \|M\|^2 \right)\]

Alice rated only Titanic and Notebook. Therefore, when solving for \(u_{Alice}\), only those two terms appear in her loss function via \(a_{Alice}\). Avengers and Matrix simply don’t show up in \(a_{Alice}\) at all — they’re not zeroed, not penalized, just absent. Yet, when A is recovered, all the missing interactions which we ignored in \(r_{Alice}\) will be recovered in \(\hat{A} \)!

If ALS ignores Missing Values, how are Missing Values even recovered?

This is the part that can feel almost like magic the first time you see it.

Once \(U\) and \(M\) are trained, we compute the full dense reconstruction:

\[\hat{A} = UM^T\]

Every cell is now predicted, including cells that never appeared in any loss term.

Alice’s vector \(u_{Alice}\) , though complete now, was shaped entirely by her Titanic and Notebook ratings only \(r_{Alice}\). Similarly, Avengers’ vector \(m_{Avengers}\) was shaped only by users Bob and Carol’s ratings, not by users who didn’t rate it.

Because both vectors live in the same shared $k$-dimensional space, their dot product:

\[u_{Alice} \cdot m_{Avengers}\]

produces a meaningful prediction even though that specific user-movie pair was never directly optimized.

You’re not filling in missing values directly. You’re learning the underlying latent structure from whatever data exists, and the missing values fall out as a byproduct of that structure.

The Takeaway

The height of \(M_{O_i}\) changes freely with how much data a user has. This is precisely what allows ALS to handle users with wildly different numbers of ratings without any special-casing.

But its width, and therefore the length of the resulting \(u_i\), is fixed at \(k\) for every user.

That’s because \(k\) is not a property of any individual user’s data. It is a global hyperparameter chosen once for the entire model, shared across every user and every movie in the dataset.


2.7 Solving for a Movie Vector

The mirror image — solving for a movie’s vector — follows identically, with the roles of $U$ and $M$ swapped.

Fix \(U\), restrict to the users who rated that movie, and derive the same update:

\[m_j = \left( U_{O_j}^TU_{O_j} + \lambda I \right)^{-1} U_{O_j}^Tr_j\]

One full ALS iteration therefore consists of:

  1. Solving every row of $U$ while fixing $M`.
  2. Solving every row of $M$ while fixing the newly updated $U$.
  3. Repeating until convergence or until the chosen number of iterations has been reached.

2.8 Why Is the Reconstructed Matrix Rank \(\leq k\)? Are the Original Ratings Retained in \(\hat{A}\)?

Recall that the reconstructed matrix is:

\[\hat{A} = UM^T\]

Suppose (A) has dimensions users × movies, where both dimensions are much larger than (k). You might therefore expect (\hat{A}) to potentially have rank as large as:

\[\min(n_{\text{users}}, n_{\text{movies}})\]

But it cannot. By construction:

\[\operatorname{rank}(\hat{A}) \leq k\]

Why Is the Rank Limited to \(k\)?

The key is the structure of the matrix multiplication.

Every column of \(\hat{A}=UM^T\) can be written as:

\[(UM^T)_{:,j} = U(M^T)_{:,j}\]

Expanding this:

\[(UM^T)_{:,j} = \sum_{l=1}^{k} u_{:,l}(M^T)_{l,j}\]

In other words, every column of \(\hat{A}\) is a linear combination of the \(k\) columns of \(U\).

Therefore, all columns of \(\hat{A}\) must lie within the column space of \(U\):

\[C(UM^T) \subseteq C(U)\]

Since \(U\) has only \(k\) columns:

\[\operatorname{rank}(\hat{A}) \leq \dim(C(U)) \leq k\]

So even though \(\hat{A}\) may contain thousands of users and thousands of movies, its entire structure is constrained to at most \(k\) independent latent dimensions.


Are the Original Ratings Preserved in \(\hat{A}\)?

No. \(\hat{A}\) is not the original rating matrix with the missing values simply filled in.

Every cell — whether originally observed or missing — is re-estimated from the learned user and movie vectors:

\[\hat{a}_{ij} = u_i \cdot m_j\]

For example, if Alice originally rated Titanic 5, the reconstructed value might be something like:

\[\hat{a}_{Alice,Titanic}=4.9\]

The original 5 is not copied back into \(\hat{A}\).

This means:

  • Observed cells are re-estimated from the learned latent factors.
  • Missing cells receive entirely new predictions from those same latent factors.

The reconstruction therefore treats both types of cells in exactly the same mathematical way.

Why Don’t the Observed Ratings Stay Exactly the Same?

The real rating matrix \(A\) is generated by noisy, individual human preferences and will generally have a much higher rank than the chosen (k).

But \(\hat{A}\) is deliberately restricted to rank \(\leq k\).

Therefore, unless the original ratings happen to have an extremely simple low-rank structure, it is impossible for \(\hat{A}\) to reproduce every observed rating exactly.

The model instead finds a low-dimensional approximation that captures the most useful underlying structure across the observed ratings.

Regularization further discourages an exact fit by penalizing excessively large values in \(U\) and \(M\).

This is a deliberate trade-off:

We sacrifice some training-data accuracy in exchange for a simpler model that is less likely to memorize noise and more likely to generalize to unseen user-movie combinations.

This is analogous to OLS: once predictions are constrained to a particular model space, the fitted values do not necessarily pass exactly through every training observation.

The Key Takeaway

The original ratings are not stored inside \(\hat{A}\) as fixed values. Instead, they are used to learn \(U\) and \(M\), and those learned latent factors are then used to reconstruct every cell:

\[\hat{A}=UM^T\]

The rank-\(k\) constraint is what makes this reconstruction a compressed representation of the observed data rather than a memorized copy of it.

Choosing k

The key hyperparameter is k, the number of latent factors. Mathematically, k controls the rank of our approximation:

\(\text{rank}(\hat A) \leq k\)

So choosing k is really asking: how low-rank do we believe the user–movie rating matrix can be while still capturing its important structure?

A small k imposes stronger compression: thousands of users and movies must be explained through just a few underlying dimensions. A larger k allows more complex patterns, but gives up some of that compression.

Importantly, nothing mathematically breaks when k is large. The regularization term \lambda I keeps the ALS systems invertible even when there are too few observations to determine all k dimensions. But invertibility does not mean that those dimensions contain meaningful information.

For example, if a user has rated only 5 movies but k=50, we are trying to estimate a 50-dimensional user vector from just 5 observations. The user’s data cannot meaningfully determine all 50 dimensions; regularization fills in the rest. The model therefore has far more latent capacity than the available signal can support.

As k approaches \min(\text{n\_users},\text{n\_movies}), the rank constraint becomes increasingly weak. Instead of forcing A into a genuinely compressed representation, we give the model enough dimensions to reproduce increasingly specific details of the observed ratings — moving from capturing shared structure toward memorizing the data.

So the question is not “how large can we make k?” but “how small can k be while still capturing the important structure in A?” We answer this empirically by evaluating different values of k on the validation set.


2.9. Clipping and Normalization

Once:

\[\hat{A} = UM^T\]

is reconstructed, individual predicted values are not mathematically constrained to fall inside the valid rating range, such as 0.5–5.0 stars.

A dot product of two real-valued vectors can land anywhere.

Two practical corrections are therefore useful:

  • Clipping — after generating predictions, cap any value above the maximum rating at the maximum, and any value below the minimum at the minimum. This keeps predictions within a sensible and interpretable range.
  • Normalization/scaling — relevant when comparing or combining scores that live on fundamentally different scales. For example, if blending ALS rating predictions with a raw interaction-count signal, the count signal must first be rescaled before a direct comparison is meaningful.

2.10. The ALS Workflow, Summarized

Pulling the previous sections into a single end-to-end picture:

  1. Start with the sparse interaction matrix \(A\) (users × movies), with most entries missing.
  2. Choose \(k\), the number of latent factors, and initialize \(U\) and \(M\) with small random values.
  3. Fix \(M\) and solve for every row of \(U\). Each user’s vector is solved independently using only that user’s rated items:

    \[u_i = \left( M_{O_i}^TM_{O_i} + \lambda I \right)^{-1} M_{O_i}^Tr_i\]
  4. Fix the newly updated \(U\) and solve for every row of \(M\). This is the mirror image, with each movie’s vector solved using only the users who rated it.
  5. Repeat steps 3–4 for a fixed number of iterations or until \(U\) and \(M\) stop changing meaningfully.
  6. Reconstruct the matrix:

    \[\hat{A} = UM^T\]

    Every cell, observed or missing, now has a predicted value recovered from the shared latent structure learned across all users and movies.

  7. Clip predictions to the valid rating range and evaluate against held-out data, such as RMSE on a validation or test split, to check that the model actually generalizes rather than simply reproducing the training data.

The entire method rests on one central design choice:

Restrict $\hat{A}$ to a rank-\(k\) approximation of \(A\), and let the alternating, closed-form per-row solves make that restriction computationally cheap and — critically — parallelizable.

Every user’s solve, and every movie’s solve, is fully independent of every other one within its respective step.


2.11. SVD: The Other Way to Factor a Matrix

ALS isn’t the only way to decompose a matrix into lower-rank pieces.

Singular Value Decomposition (SVD) is the classical linear-algebra factorization. Understanding it is useful both for its own sake and for clarifying why ALS exists.

2.11.1 The SVD Decomposition

For any matrix \(A\) of dimensions \(n \times m\), SVD guarantees an exact factorization:

\[A = U\Sigma V^T\]

where:

  • \(U\) — an \((n \times n)\) orthonormal matrix containing the left singular vectors.
  • \(\Sigma\) — an \((n \times m)\) diagonal matrix containing the singular values, ordered from largest to smallest.
  • \(V^T\) — the transpose of an \((m \times m)\) orthonormal matrix containing the right singular vectors.

This decomposition is exact and, apart from sign/rotation conventions, unique.

There is no approximation and no alternating optimization loop. SVD always exists for a complete matrix.


2.11.2 Getting a Low-Rank Approximation from SVD

To obtain a rank-\(k\) approximation comparable to ALS’s \(UM^T\), we truncate the SVD.

Keep only the top \(k\) singular values in \(\Sigma\) and the corresponding columns of \(U\) and rows of \(V^T\):

\[A_k = U_k\Sigma_kV_k^T\]

The Eckart–Young theorem guarantees that this truncated SVD is the provably best possible rank-\(k\) approximation of \(A\) in terms of minimizing squared reconstruction error.

This is a stronger guarantee than ALS’s alternating optimization, which converges to a local minimum but does not necessarily find the globally best rank-\(k\) approximation.


2.11.3 The Problem: SVD Needs a Complete Matrix

Here’s where SVD runs directly into the exact problem ALS was built to solve.

SVD operates on the matrix as a complete object. It does not have the concept of “ignore missing entries in the loss” that ALS’s objective function provides.

For a recommender system where \(A\) is 98%+ sparse, missing entries therefore need to be imputed before conventional SVD can be applied.

A common approach would be zero-filling or mean-filling.

This is precisely the move ALS was designed to avoid.

Treating a missing rating as 0 effectively teaches the model:

“This user hates everything they haven’t rated.”

That corrupts the structure we are actually trying to learn.

Mean imputation is gentler, but it still injects synthetic, non-observed values into the SVD optimization. The resulting factors can therefore be influenced by the chosen imputation strategy rather than genuine user behavior.


2.11.4 Side-by-Side Comparison

Aspect SVD ALS
Missing data Must be imputed first (zero/mean-fill), which can bias the result Excluded directly from the loss — no imputation needed
Solution quality Exact, provably optimal rank-\(k\) approximation via Eckart–Young Iterative; converges to a local minimum, not guaranteed to reach the global optimum
Computation Global — depends on the whole matrix at once and is difficult to parallelize Per-row closed-form solves, each independent and highly parallelizable
Scalability Expensive on large, sparse matrices and does not distribute naturally Built for horizontal scaling — which is why Spark’s native recommender algorithm is ALS rather than SVD
Best suited for Small or genuinely dense matrices, or applications needing mathematically exact decomposition such as PCA-style compression/analysis Real-world sparse recommender data at production scale

2.11.5 The One-Line Takeaway

SVD is the textbook-exact factorization but assumes a complete matrix; ALS is purpose-built to tolerate sparsity and scale horizontally across a cluster — which is exactly why Spark implements ALS, not SVD, as its native collaborative filtering algorithm.

The two aren’t really competing solutions to exactly the same problem. They answer two different constraints:

  • SVD optimizes for mathematical exactness given complete data.
  • ALS optimizes for tractability and scalability given the incomplete data that real recommender systems actually contain.

With the math established — how ALS represents users and items, why it tolerates sparsity by design, why and how its output is rank-constrained for generalisation, and how the per-vector update is actually solved — the next step is implementation: building, tuning, and evaluating explicit ALS in PySpark on real data. But before that’s let’s establish our benchmarks using some popular baselines.



This site uses Just the Docs, a documentation theme for Jekyll.