/
svd.jl
50 lines (41 loc) · 2.35 KB
/
svd.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
export SVD
"""
SVD(
data::DataAccessor,
n_factors::Integer
)
Recommendation based on SVD of a user-item matrix ``R \\in \\mathbb{R}^{|\\mathcal{U}| \\times |\\mathcal{I}|}``, which was originally studied by [Sarwar et al.](http://files.grouplens.org/papers/webKDD00.pdf) Rank ``k`` is configured by `n_factors`. Sparse matrix is not supported for `data.R`.
In a context of recommendation, ``U_k \\in \\mathbb{R}^{|\\mathcal{U}| \\times k}``, ``V \\in \\mathbb{R}^{|\\mathcal{I}| \\times k}`` and ``\\Sigma \\in \\mathbb{R}^{k \\times k}`` are respectively seen as ``k`` user/item feature vectors and corresponding weights. The idea of low-rank approximation that discards lower singular values intuitively works as *compression* or *denoising* of the original matrix; that is, each element in a rank-``k`` matrix ``A_k`` holds the best *compressed* (or *denoised*) value of the original element in ``A``. Thus, ``R_k = \\mathrm{SVD}_k(R)``, the best rank-``k`` approximation of ``R``, captures as much as possible of underlying users' preferences. Once ``R`` is decomposed into ``U, \\Sigma`` and ``V``, a ``(u, i)`` element of ``R_k`` calculated by ``\\sum^k_{j=1} \\sigma_j u_{u, j} v_{i, j}`` could be a prediction for the user-item pair.
"""
struct SVD <: Recommender
data::DataAccessor
n_factors::Integer
U::AbstractMatrix
S::AbstractVector
Vt::AbstractMatrix
R::AbstractMatrix
function SVD(data::DataAccessor, n_factors::Integer)
n_users, n_items = size(data.R)
U = matrix(n_users, n_factors)
S = vector(n_factors)
Vt = matrix(n_factors, n_items)
new(data, n_factors, U, S, Vt, matrix(n_users, n_items))
end
end
SVD(data::DataAccessor) = SVD(data, 20)
isdefined(recommender::SVD) = isfilled(recommender.R)
function fit!(recommender::SVD)
res = svd(recommender.data.R)
recommender.U[:] = res.U[:, 1:recommender.n_factors]
recommender.S[:] = res.S[1:recommender.n_factors]
recommender.Vt[:] = res.Vt[1:recommender.n_factors, :]
recommender.R[:] = recommender.U * Diagonal(recommender.S) * recommender.Vt
end
function predict(recommender::SVD, user::Integer, item::Integer)
validate(recommender)
recommender.R[user, item]
end
function predict(recommender::SVD, indices::AbstractVector{T}) where {T<:CartesianIndex{2}}
validate(recommender)
recommender.R[indices]
end