차원 축소 기법 중 하나인 SVD(Singular value Decomposition, 특이값 분해)을 추천 시스템에 어떻게 적용하는지 예제를 통해서 알아 보도록 하겠습니다.
차원 축소 알고리듬은 대표적으로 3가지가 있습니다.
1. PCA(Principal component analysis, 주성분 분석)
2. SVD(Singular Value Decomposition, 특이값 분해)
3. NMF(Non-negative Matrix Factorization,비음수 행렬 인수분해)
이 중에 SVD에 대해 최대한 수학적인 내용들을 제외하고 추천 시스템에 SVD를 적용하기 위한 예제 위주로 설명을 드리겠습니다. 이론적, 수학적으로 설명된 자료는 많으나 실제로 코드를 이용해서 어떻게 구현하는지 나와 있는 자료가 없어서 이 자료를 만들어 봤습니다. 이론적인 자료는 이 글 가장 아래 정리된 링크를 참고 하세요.1
간단하게 SVD란 아래 그림과 같이 A라는 m X n 의 행렬을 아래 그림과 같이 U, \(\Sigma\), \(V^t\)라는 3개의 행렬로 분해하는 것입니다.
(출처 : http://nlp.ite.tul.cz/clustering/methods.html)
이번 글에서는 영화 평점이라는 행렬(A)을 SVD를 이용하여 사용자 행렬(U), 특성 행렬(\(\Sigma\)), 영화 행렬(\(V^t\))로 만들어서 추천 데이터 생성에 필요한 데이터를 최소화 하고, 비어있는 고객의 평점을 예측하여 추천 시스템에 적용하는 방법을 설명 합니다.
그럼 이제부터 R 코드를 이용하여 SVD에 대해서 알아 보겠습니다.
SVD를 테스트할 영화 평점 데이터 생성
movieRating <- matrix(c(
1,1,1,0,0,
3,3,3,0,0,
4,4,4,0,0,
5,5,5,0,0,
0,0,0,4,4,
0,0,0,5,5,
0,0,0,2,2), byrow = T, nrow=7)
이용자 명과 영화 명을 입력
rownames(movieRating) <- c('u1','u2','u3','u4','u5','u6','u7')
colnames(movieRating) <- c('스타워즈','아바타','혹성탈출','사랑과영혼','타이타닉')
영화 평점 Matrix 확인
movieRating
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## u1 1 1 1 0 0
## u2 3 3 3 0 0
## u3 4 4 4 0 0
## u4 5 5 5 0 0
## u5 0 0 0 4 4
## u6 0 0 0 5 5
## u7 0 0 0 2 2
SVD 생성
movieRating_svd <- svd(movieRating)
SVD 조회
SVD를 수행한 결과는 아래와 같이 3개의 matrix를 만들어준다.
R = U x D x V
R은 평점
U는 사용자
D는 concept(diagonal matrix, 대각 행렬)
V는 영화
movieRating_svd
## $d
## [1] 12.369317 9.486833 0.000000 0.000000 0.000000
##
## $u
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.140028 0.0000000 0.4174829 -0.5601120 -0.4200840
## [2,] -0.420084 0.0000000 0.1538365 -0.2063933 0.8452050
## [3,] -0.560112 0.0000000 0.2051153 0.7248090 -0.2063933
## [4,] -0.700140 0.0000000 -0.3398907 -0.3439888 -0.2579916
## [5,] 0.000000 -0.5962848 0.6444444 0.0000000 0.0000000
## [6,] 0.000000 -0.7453560 -0.4444444 0.0000000 0.0000000
## [7,] 0.000000 -0.2981424 -0.1777778 0.0000000 0.0000000
##
## $v
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.5773503 0.0000000 0.0000000 0.0000000 0.8164966
## [2,] -0.5773503 0.0000000 0.0000000 -0.7071068 -0.4082483
## [3,] -0.5773503 0.0000000 0.0000000 0.7071068 -0.4082483
## [4,] 0.0000000 -0.7071068 -0.7071068 0.0000000 0.0000000
## [5,] 0.0000000 -0.7071068 0.7071068 0.0000000 0.0000000
대각행렬(Diagonal Matrix 확인)
대각선에 있는 0이 아닌 값이 특이값 입니다. 이 값은 각각의 컨셉(concept)을 의미합니다. 어떤 특징(feature)을 의미한다고 할 수도 있겠습니다. 숫자의 크기는 특징을 의미하는 강도(strength)가 얼마나 강한지를 의미합니다. 아래의 matrix에서 12라는 숫자는 전체 특이값 중 가장 해당 특징에 대한 설명력이 높다고 할 수 있습니다. 12를 의미하는 첫 번재 값은 SF라는 장르를 의미하고, 9라는 두번째 값은 로맨스라는 장르를 의미한다고 가정할 수 있습니다. 이 것은 어디까지나 가정입니다. 어디에도 이 것이 장르를 의미한다고 이야기 하지 않습니다. 다만 변수의 특징과 특이값 결과를 토대로 예상할 때 장르라로 추정할 수 있다는 것입니다. 이렇게 명확하게 cencept을 파악한다면 일하기 편하겠지요.
한 행으로 표시된 대각 행렬을 아래와 같이 matrix로 볼 수 있습니다.
(D <- diag(movieRating_svd$d))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 12.36932 0.000000 0 0 0
## [2,] 0.00000 9.486833 0 0 0
## [3,] 0.00000 0.000000 0 0 0
## [4,] 0.00000 0.000000 0 0 0
## [5,] 0.00000 0.000000 0 0 0
차트를 이용한 특이값 에너지 확인2
plot(movieRating_svd$d^2/sum(movieRating_svd$d^2), type="l", xlab="Singualar vector",
ylab = "variance explained")
특이값은 경험상 90%에 해당하는 에너지를 차지할 만큼 충분한 특이 값들을 보유하는 것이 좋은 방법입니다. 즉, 보유한 특이 값들의 제곱의 합이 모든 특이 값의 제곱의 합에 최소 90%가 되어야 합니다.3 스텐포드 대학교의 추천 시스템 강의에서는 80%~90%의 에너지를 유지하라고 가이드 합니다.4
아래 데이터 기준으로 2개의 특이값을 선택하면 된다. 아래와 같은 차트를 이용하면 누적으로 몇 번째 특이값이 80%~90%에 속하는지 알 수 있습니다.
plot(cumsum(movieRating_svd$d^2/sum(movieRating_svd$d^2)), type="l",
xlab = "Singular vector", ylab = "Cumulative percent of variance explained")
위 차트 결과를 기반으로 2개의 특이값을 선택하여 새로운 변수에 입력합니다. U, D, V 모두 2개의 열에 있는 데이터만 사용합니다.
(U <- movieRating_svd$u[1:7,1:2])
## [,1] [,2]
## [1,] -0.140028 0.0000000
## [2,] -0.420084 0.0000000
## [3,] -0.560112 0.0000000
## [4,] -0.700140 0.0000000
## [5,] 0.000000 -0.5962848
## [6,] 0.000000 -0.7453560
## [7,] 0.000000 -0.2981424
(D <- diag(movieRating_svd$d[1:2]))
## [,1] [,2]
## [1,] 12.36932 0.000000
## [2,] 0.00000 9.486833
(V <- movieRating_svd$v[1:5,1:2])
## [,1] [,2]
## [1,] -0.5773503 0.0000000
## [2,] -0.5773503 0.0000000
## [3,] -0.5773503 0.0000000
## [4,] 0.0000000 -0.7071068
## [5,] 0.0000000 -0.7071068
기존의 7x5 matrix를 7x2, 2x2, 2,5의 matrix로 만들었습니다.
그럼 차원 축소한 값으로 원래 값을 만들면 어떻게 해야 할까요?
아래와 같이 3개의 matrix를 다시 곱하면 원래의 평가 데이터를 구할수 있습니다.
(movieRating)
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## u1 1 1 1 0 0
## u2 3 3 3 0 0
## u3 4 4 4 0 0
## u4 5 5 5 0 0
## u5 0 0 0 4 4
## u6 0 0 0 5 5
## u7 0 0 0 2 2
(U%*%D%*%t(V))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 1 1 0 0
## [2,] 3 3 3 0 0
## [3,] 4 4 4 0 0
## [4,] 5 5 5 0 0
## [5,] 0 0 0 4 4
## [6,] 0 0 0 5 5
## [7,] 0 0 0 2 2
그럼 위와 같이 동일한 결과를 얻을 수 있습니다.
추천 시스템에는 어떻게 적용할 수 있을까요? 추천 시스템 알고리즘 중 한 가지인 Collaborative Filtering의 핵심인 사용자 간 유사도와 아이템 간의 유사도를 구한다면 그 다음은 쉽게 진행할 수 있습니다.
그럼 먼저 사용자 x 사용자 유사도와, 아이템 x 아이템 유사도를 계산해 보겠습니다.
cor 함수를 이용해 유사도를 구하기 위해서 lsa 라이브러리를 호출합니다.
if (!require(lsa)) install.packages("lsa")
## Loading required package: lsa
## Warning: package 'lsa' was built under R version 3.4.2
## Loading required package: SnowballC
## Warning: package 'SnowballC' was built under R version 3.4.1
영화를 평가한 사용자 간의 유사도 구하기
먼저 아래와 같이 영화 평점 데이터를 이용해서 유사도를 계산해 보겠습니다.
(movieRating)
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## u1 1 1 1 0 0
## u2 3 3 3 0 0
## u3 4 4 4 0 0
## u4 5 5 5 0 0
## u5 0 0 0 4 4
## u6 0 0 0 5 5
## u7 0 0 0 2 2
유사도를 구할 때는 열에 있는 차원 기준으로 구합니다. 즉, 사용자x영화 matrix의 경우 행의 위치에 있던 사용자 차원을 열로 옮기기 위해서 transpose(전치)를 합니다. R에서는 t()함수를 사용합니다.
cor(t(movieRating))
## u1 u2 u3 u4 u5 u6 u7
## u1 1 1 1 1 -1 -1 -1
## u2 1 1 1 1 -1 -1 -1
## u3 1 1 1 1 -1 -1 -1
## u4 1 1 1 1 -1 -1 -1
## u5 -1 -1 -1 -1 1 1 1
## u6 -1 -1 -1 -1 1 1 1
## u7 -1 -1 -1 -1 1 1 1
위의 결과에 따르면 u1~u4까지가 유사하고, u5~u7이 유사하다는 결과을 얻을 수 있습니다. u1~u4는 SF 장르를 좋아하고, u5~u7은 로맨스를 좋아하는 사용자이까요.
U
## [,1] [,2]
## [1,] -0.140028 0.0000000
## [2,] -0.420084 0.0000000
## [3,] -0.560112 0.0000000
## [4,] -0.700140 0.0000000
## [5,] 0.000000 -0.5962848
## [6,] 0.000000 -0.7453560
## [7,] 0.000000 -0.2981424
D
## [,1] [,2]
## [1,] 12.36932 0.000000
## [2,] 0.00000 9.486833
V
## [,1] [,2]
## [1,] -0.5773503 0.0000000
## [2,] -0.5773503 0.0000000
## [3,] -0.5773503 0.0000000
## [4,] 0.0000000 -0.7071068
## [5,] 0.0000000 -0.7071068
그럼 우리가 svd의 실행 결과로 얻은 위와 같은 데이터를 사용하면 유사도의 결과가 어떻게 나올까요?
cor(t(U%*%D%*%t(V)))
## [,1] [,2] [,3] [,4] [,5] [,6] [,7]
## [1,] 1 1 1 1 -1 -1 -1
## [2,] 1 1 1 1 -1 -1 -1
## [3,] 1 1 1 1 -1 -1 -1
## [4,] 1 1 1 1 -1 -1 -1
## [5,] -1 -1 -1 -1 1 1 1
## [6,] -1 -1 -1 -1 1 1 1
## [7,] -1 -1 -1 -1 1 1 1
위와 같이 영화 평가 원본 데이터를 이용한 결과와 동일한 결과를 얻을 수 있습니다. 그럼 이번에는 아이템간의 유사도를 계산해 보겠습니다.
# 원본 데이터를 이용한 아이템 간 유사도
cor(movieRating)
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## 스타워즈 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## 아바타 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## 혹성탈출 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## 사랑과영혼 -0.7487835 -0.7487835 -0.7487835 1.0000000 1.0000000
## 타이타닉 -0.7487835 -0.7487835 -0.7487835 1.0000000 1.0000000
# SVD를 수행한 결과 데이터를 이용한 아이템 간 유사도
cor(U%*%D%*%t(V))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## [2,] 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## [3,] 1.0000000 1.0000000 1.0000000 -0.7487835 -0.7487835
## [4,] -0.7487835 -0.7487835 -0.7487835 1.0000000 1.0000000
## [5,] -0.7487835 -0.7487835 -0.7487835 1.0000000 1.0000000
위와 같이 아이템 간 유사도의 결과도 동일하게 나왔습니다.
하지만, 기껏 차원 축소한 변수를 다시 원래 차원으로 만들어서 유사도를 구하면 무슨 의미가 있을까요?
그렇다면 열을 2개로 축소한 변수를 이용해 위와 같은 결과를 도출해 보겠습니다. 먼저 사용자 간 유사도를 계산해 보겠습니다. 사용자간 유사도를 계산하는 방법은 U matrix와 D matrix만 내적(곱하기)하면 됩니다.
# UxD
(U%*%D)
## [,1] [,2]
## [1,] -1.732051 0.000000
## [2,] -5.196152 0.000000
## [3,] -6.928203 0.000000
## [4,] -8.660254 0.000000
## [5,] 0.000000 -5.656854
## [6,] 0.000000 -7.071068
## [7,] 0.000000 -2.828427
# UxD 유사도
(cor(t(U%*%D)))
## [,1] [,2] [,3] [,4] [,5] [,6] [,7]
## [1,] 1 1 1 1 -1 -1 -1
## [2,] 1 1 1 1 -1 -1 -1
## [3,] 1 1 1 1 -1 -1 -1
## [4,] 1 1 1 1 -1 -1 -1
## [5,] -1 -1 -1 -1 1 1 1
## [6,] -1 -1 -1 -1 1 1 1
## [7,] -1 -1 -1 -1 1 1 1
아이템간 유사도도 아래와 같이 계산하면 됩니다.
# DXVt
(D%*%t(V))
## [,1] [,2] [,3] [,4] [,5]
## [1,] -7.141428 -7.141428 -7.141428 0.000000 0.000000
## [2,] 0.000000 0.000000 0.000000 -6.708204 -6.708204
# DXVt 유사도
(cor(D%*%t(V)))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 1 1 -1 -1
## [2,] 1 1 1 -1 -1
## [3,] 1 1 1 -1 -1
## [4,] -1 -1 -1 1 1
## [5,] -1 -1 -1 1 1
영화 간의 음수값이 원본 데이터인 경우 -0.7487835 였던 것이 -1이 되었지만 유사한 3개의 열을 줄인 결과 치고는 정화한 결과가 나왔습니다. 위와 같은 방법을 이용하면 기존의 7x5 matrix를 7x2 matrix를 이용해서 동일한 결과를 얻을 수 있습니다. 여기에서는 sample이기 때문에 5개의 차원이었지만 10만개, 100만개가 넘어가는 차원을 위와 같은 방식으로 줄일 수 있다면 속도나 성능 측면에서 더 많은 장점이 있을 것입니다.
위의 예제처럼 고객들이 SF만 또는 로멘스 영화만 보는 경우는 없을 것입니다. 그럼 아래와 같이 로맨스 영화만 좋아하던 u5와 u7 사용자가 SF 영화를 봤을 때 어떤 변화가 생기는지 확인해 보겠습니다.
movieRating2 <- matrix(c(
1,1,1,0,0,
3,3,3,0,0,
4,4,4,0,0,
5,5,5,0,0,
0,2,0,4,4,
0,0,0,5,5,
0,1,0,2,2), byrow = T, nrow=7)
이용자 명과 영화 명을 입력
rownames(movieRating2) <- c('u1','u2','u3','u4','u5','u6','u7')
colnames(movieRating2) <- c('스타워즈','아바타','혹성탈출','사랑과영혼','타이타닉')
movieRating2
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## u1 1 1 1 0 0
## u2 3 3 3 0 0
## u3 4 4 4 0 0
## u4 5 5 5 0 0
## u5 0 2 0 4 4
## u6 0 0 0 5 5
## u7 0 1 0 2 2
위와 같이 혹성탈출이라는 영화의 평점이 2개 추가되었습니다. 그럼 SVD 결과는 어떻게 달라질까요? 기존에 만들었던 SVD 결과와 비교해서 보겠습니다.
print("기존의 SVD 결과")
## [1] "기존의 SVD 결과"
(movieRating_svd)
## $d
## [1] 12.369317 9.486833 0.000000 0.000000 0.000000
##
## $u
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.140028 0.0000000 0.4174829 -0.5601120 -0.4200840
## [2,] -0.420084 0.0000000 0.1538365 -0.2063933 0.8452050
## [3,] -0.560112 0.0000000 0.2051153 0.7248090 -0.2063933
## [4,] -0.700140 0.0000000 -0.3398907 -0.3439888 -0.2579916
## [5,] 0.000000 -0.5962848 0.6444444 0.0000000 0.0000000
## [6,] 0.000000 -0.7453560 -0.4444444 0.0000000 0.0000000
## [7,] 0.000000 -0.2981424 -0.1777778 0.0000000 0.0000000
##
## $v
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.5773503 0.0000000 0.0000000 0.0000000 0.8164966
## [2,] -0.5773503 0.0000000 0.0000000 -0.7071068 -0.4082483
## [3,] -0.5773503 0.0000000 0.0000000 0.7071068 -0.4082483
## [4,] 0.0000000 -0.7071068 -0.7071068 0.0000000 0.0000000
## [5,] 0.0000000 -0.7071068 0.7071068 0.0000000 0.0000000
print("새로운 평점을 추가한 SVD 결과")
## [1] "새로운 평점을 추가한 SVD 결과"
(movieRating_svd2 <- svd(movieRating2))
## $d
## [1] 1.248101e+01 9.508614e+00 1.345560e+00 3.046427e-16 0.000000e+00
##
## $u
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.13759913 -0.02361145 -0.01080847 5.601120e-01 -0.3757346
## [2,] -0.41279738 -0.07083435 -0.03242542 2.063933e-01 0.7559744
## [3,] -0.55039650 -0.09444581 -0.04323389 -7.248090e-01 -0.1846038
## [4,] -0.68799563 -0.11805726 -0.05404236 3.439888e-01 -0.2307547
## [5,] -0.15277509 0.59110096 0.65365084 2.584979e-16 0.2000000
## [6,] -0.07221651 0.73131186 -0.67820922 0.000000e+00 0.0000000
## [7,] -0.07638754 0.29555048 0.32682542 1.292489e-16 -0.4000000
##
## $v
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.56225841 -0.12664138 -0.4096675 -7.071068e-01 0.000000e+00
## [2,] -0.59285990 0.02877058 0.8047915 1.110223e-16 -4.877240e-17
## [3,] -0.56225841 -0.12664138 -0.4096675 7.071068e-01 4.877240e-17
## [4,] -0.09013354 0.69537622 -0.0912571 5.551115e-17 -7.071068e-01
## [5,] -0.09013354 0.69537622 -0.0912571 0.000000e+00 7.071068e-01
그냥 봤을 때 큰 차이를 모르겠습니다. 큰 차이는 아래와 같이 대각행렬인 특이값 matrix에서 나타납니다. 기존에는 3x3 위치에 있는 값이 거의 0에 가까운 값어었지만, 새로운 matrix에서는 1.345560e+00이라는 약 1.3의 값이 새로 나왔습니다. 즉, 데이터를 설명하는 새로운 concept가 나타난 것입니다. 1x1의 값이 SF를 의미하고 2x2가 로맨스를 의미한다면 3x3은 로맨스도 보면서 sf도 보는 컨셉이라고 할 수 있습니다. 하지만 값이 1.3으로 작기 때문에 무시해도 무방할 것 같습니다. 위에서 이야기 한 것 처럼 concept의 에너지는 80~90%이면 충분하다고 했으니까요.
print("기존 SVD 결과")
## [1] "기존 SVD 결과"
(diag(movieRating_svd$d))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 12.36932 0.000000 0 0 0
## [2,] 0.00000 9.486833 0 0 0
## [3,] 0.00000 0.000000 0 0 0
## [4,] 0.00000 0.000000 0 0 0
## [5,] 0.00000 0.000000 0 0 0
print("신 SVD 결과")
## [1] "신 SVD 결과"
(diag(movieRating_svd2$d))
## [,1] [,2] [,3] [,4] [,5]
## [1,] 12.48101 0.000000 0.00000 0.000000e+00 0
## [2,] 0.00000 9.508614 0.00000 0.000000e+00 0
## [3,] 0.00000 0.000000 1.34556 0.000000e+00 0
## [4,] 0.00000 0.000000 0.00000 3.046427e-16 0
## [5,] 0.00000 0.000000 0.00000 0.000000e+00 0
이번에는 결측치(missing value)가 있을 경우 해결하는 방법을 보겠습니다. 새로운 사용자인 u8이 들어왔다고 하겠습니다. 이 사용자는 다음과 같이 영화에 평점을 주었습니다. [스타워즈 : 4, 아바타 : ?, 혹성탈출 : 3, 사랑과영혼 : ?, 타이타닉 : 2] 아바타와 사랑과 영혼의 값은 비어 있습니다. 비어 있는 값은 u8 사용자가 평가한 전체 영화 평점의 평균인 3점으로 입력합니다. (4+3+2)/3 = 3 [4, 3, 3, 3, 2]와 같은 값을 얻을 것입니다.
movieRating3 <- movieRating
movieRating3 <- rbind(movieRating3, c(4,3,3,3,2))
rownames(movieRating3) <- c('u1','u2','u3','u4','u5','u6','u7','u8')
movieRating3
## 스타워즈 아바타 혹성탈출 사랑과영혼 타이타닉
## u1 1 1 1 0 0
## u2 3 3 3 0 0
## u3 4 4 4 0 0
## u4 5 5 5 0 0
## u5 0 0 0 4 4
## u6 0 0 0 5 5
## u7 0 0 0 2 2
## u8 4 3 3 3 2
movieRating_svd3 <- svd(movieRating3)
#U
movieRating_svd3$u
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.12192329 0.0396281 -0.05632200 5.165148e-02 -2.367445e-01
## [2,] -0.36576988 0.1188843 -0.16896600 8.865099e-01 1.559372e-01
## [3,] -0.48769317 0.1585124 -0.22528800 -3.644568e-01 1.184665e-01
## [4,] -0.60961647 0.1981405 -0.28160999 -2.506708e-01 -1.409866e-01
## [5,] -0.09218325 -0.5568628 -0.19225399 8.371288e-02 -6.274194e-01
## [6,] -0.11522907 -0.6960785 -0.24031748 -8.371288e-02 6.274194e-01
## [7,] -0.04609163 -0.2784314 -0.09612699 4.185644e-02 -3.137097e-01
## [8,] -0.46687291 -0.2185514 0.85689262 -4.232725e-16 -3.608225e-16
#D
diag(movieRating_svd3$d)
## [,1] [,2] [,3] [,4] [,5]
## [1,] 13.83085 0.000000 0.0000000 0.000000e+00 0.00000e+00
## [2,] 0.00000 9.891885 0.0000000 0.000000e+00 0.00000e+00
## [3,] 0.00000 0.000000 0.9264298 0.000000e+00 0.00000e+00
## [4,] 0.00000 0.000000 0.0000000 1.219947e-15 0.00000e+00
## [5,] 0.00000 0.000000 0.0000000 0.000000e+00 2.51339e-16
#V
t(movieRating_svd3$v)
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.58460477 -0.5508489 -0.5508489 -0.17624952 -0.14249361
## [2,] 0.11593618 0.1380302 0.1380302 -0.69959986 -0.67750585
## [3,] 0.59923432 -0.3257064 -0.3257064 0.44020661 -0.48473407
## [4,] -0.53438761 0.2513102 0.2830774 0.53438761 -0.53438761
## [5,] -0.01200686 0.7129318 -0.7009249 0.01200686 -0.01200686
# 모든 대각 행렬 사용 시
round(movieRating_svd3$u%*%diag(movieRating_svd3$d)%*%t(movieRating_svd3$v),digits = 2)
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 1 1 0 0
## [2,] 3 3 3 0 0
## [3,] 4 4 4 0 0
## [4,] 5 5 5 0 0
## [5,] 0 0 0 4 4
## [6,] 0 0 0 5 5
## [7,] 0 0 0 2 2
## [8,] 4 3 3 3 2
# 2개의 대각 행렬 사용 시
round(movieRating_svd3$u[,1:2]%*%diag(movieRating_svd3$d[1:2])%*%t(movieRating_svd3$v[,1:2]),digits = 2)
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1.03 0.98 0.98 0.02 -0.03
## [2,] 3.09 2.95 2.95 0.07 -0.08
## [3,] 4.13 3.93 3.93 0.09 -0.10
## [4,] 5.16 4.92 4.92 0.11 -0.13
## [5,] 0.11 -0.06 -0.06 4.08 3.91
## [6,] 0.13 -0.07 -0.07 5.10 4.89
## [7,] 0.05 -0.03 -0.03 2.04 1.96
## [8,] 3.52 3.26 3.26 2.65 2.38
U8 이용자의 평점이 [4, 3, 3, 3, 2] 에서 [3.52, 3.26,3.26, 2.65, 2.38]로 변경 되었습니다. 그럼 U8 이용자가 아직 안 보았던 아바타와 타이타닉 점수가 3.26점과 2.65점이 됐기 때문에 U8 이용자에게는 아바타를 추천해줘야 합니다.
결론은 아래와 같습니다. SVD는 차원 축소 방법 중 하나 입니다. SVD를 사용하면 사용자의 특징과 아이템의 특징 그리고 이 두 가지를 대표하는 대각 행렬을 추출할 수 있습니다. 대각 행렬을 축소하여 전체 처리해야 하는 데이터의 양을 줄일 수 있습니다. 또한 아직 평가하지 않은 데이터에 대해서 평균 값을 이용해 결측치를 채운 후 SVD를 이용해 예상 점수를 추측할 수 있습니다. 추천 시스템에서는 이 추측한 점수 중 높은 아이템에 대해서 추천을 할 수 있습니다.
이로써 추천 시스템을 구현하기 위해 SVD가 어떻게 사용하는지 예제를 통해서 살펴 보았습니다. 이 자료는 SVD를 이용해서 추천 시스템을 구현하는 컨셉에 대해서 설명하기 위한 자료입니다. 실무에서 사용하기 위해서는 대용량 데이터를 처리하기 위한 다양한 방법을 적용하고 Apache Mahout 또는 Apache Spark에 포함된 SVD 기능을 이용하여야 합니다.
reference
https://www.slideshare.net/madvirus/pca-svd
http://funmv2013.blogspot.kr/2014/04/collaborative-filtering.html
'머신러닝(Machine Learning) > 추천 시스템(Recommendation System)' 카테고리의 다른 글
추천 시스템 관점에서 대체재와 보완재에 대한 일반적인 생각 (0) | 2017.12.28 |
---|---|
추천시스템 개발을 위한 SVD(특이 값 분해, Singular-value decomposition) 이해 (0) | 2017.11.28 |
Association Analysis / Association Rule / Apriori 알고리즘 - 3 of 3 (3) | 2017.10.03 |
Association Analysis / Association Rule / Apriori 알고리즘 - 2 of 3 (2) | 2017.10.01 |
추천 시스템 개발을 위한 웹로그 수집 방법 1 of 2 (0) | 2017.09.26 |