추천시스템 기초개념

2019-05-20

.

그림, 실습코드 등 학습자료 출처 : https://datascienceschool.net

1. 추천시스템 개요

사용자 아이디 카테고리 입력을 받아서 상품 카테고리를 출력하는 classification 문제 같지만 이렇게 하면 풀리지가 않는다. 왜냐하면 인풋이 달랑 하나이기 때문이다. 그런데 인풋이(사용자 아이디) 어마어마하게 많고 아웃풋(상품 카테고리)도 어마어마하게 많다.

또한 실제로 상품 카테고리를 아웃풋으로 하나만 출력하지 않는다. 다시말해서 추천을 할때 하나만 추천하는 것이 아니라 여러개를 추천한다. 현실적으로는 얘는 사실 카테고리 값을 예측하는 것이 아니라 실제로는 이 유저가 특정아이템을 좋아할 rating을 예측을 하는 것인데 그 예측모델이 뭐냐면 모든 아이템의 평점이 나와야 한다. 예를들어서 1억개의 아이템이 있는데 유저아이디를 하나 입력하면 예측문제를 1억개를 풀어야 한다. 1번 아이템의 평점, 2번 아이템의 평점…, 1억번째 아이템의 평점 이런식으로 풀어야 한다. 그래서 엄청나게 많은 세트의 회귀분석문제를 한꺼번에 푼다고 보면 된다.

근데 이것도 역시 회귀분석 문제라고 단정하기 어려운게 인풋 클래스가 너무 많은게 문제다. 옛날에 나왔던 뭔가 데이터가 있어야 하는데 적은 경우가 많아서 쉽지 않은 문제다. 얼핏보면 분류문제 같지만 안을 들여다보면 회귀분석 문제이고 회귀분석 문제지만 기존의 회귀분석 문제를 푸는 방법론을 조금 다르게 접근해야 한다.

우리가 최종적으로 추천을 하려면 어떤 유저가 들어가서 어떤 아이템을 집어넣으면 평점이 얼마나온다 라는 것을 예측해야 한다. 두개의 카테고리 인풋이 들어가면 한개의 리얼값이 나온다.

  • 추천시스템 모델에 반영하기 위한 위한 피벗테이블

이렇게 변환해주면 평점행렬이 나오게 된다. 그러면 어떤 유저가 들어가서 어떤 아이템을 집어넣게 되면 평점이 얼마가 나온다

빈공간은 물건을 안샀다던지 물건을 샀는데도 평점을 안매긴건지는 모르는 것이나 어쨌든 이런 빈공간이 대부분일 것이다.

그래서 우리가 prediction 모델을 만든다는 것은 결국에는 여기의 빈칸을 채우는 것이다.

0

  • rate matrix의 빈칸을 채우기 위한 추천시스템 개요

1

2. Baseline model

회귀분석모델에서 카테고리 값 두개만 입력변수다라고 했을때 쓰는 가장 기본적인 모델이 baseline model이다.

쉽게말해서 선형회귀모형을 적용한 모델이라는 것이다.

아래 rui헷을 선형회귀모델 형식으로 바꾸면 곧 아래식과 같다.

yui헷 = W0 + WuXu(유저에 대한 카테고리 변수) + WiXi(아이템에 대한 카테고리 변수)

카테고리 값은 더미변수로 인코딩이 되어야한다 그래서 유저개수에 해당하는 u1 u2 u3 .. 이런식으로 유저개수만큼의 어마어마하게 많은 더미변수로 확장이 된다. i1 i2 i3 … 로 아이템 개수만큼 어마어마하게 많은 더미변수로 확장이 된다.

베이스라인은 그러나 x행렬에 대해서 covariance matrix 만들고 XTX 행렬 만들고 걔의 인버스를 구해서 XT에다가 곱해야 한다. 그런데 얘는 인버스 행렬을 구할 수 없다. 너무 크기 때문이다.

그렇기 때문에 우리가 할 수 있는 것은 SGD 방법밖에 없다. W를 한스텝 한스텝 조금씩 최적점에 다가가는 방법밖에 없다. 그때 error function은 RSS가 된다.

이렇게 푸는 것이 베이스라인의 기본이다. 단 RSS를 할때도 우리가 가지고 있는게 답이 모든게 있는 것이 아니라 일부만 있기 때문에 갖고 있는 것만이라도 RSS를 최소화한다.

또는 이문제에 관한 한 Alternating Least Squares(ALS)라는 방법을 쓰게 되면 계산양을 줄일 수 있어서 이 방법을 쓰기도 한다.

2

  • surprise 패키지를 이용한 파이썬 코드 실습

3

import surprise

data = surprise.Dataset.load_builtin('ml-100k')
df = pd.DataFrame(data.raw_ratings, columns=["user", "item", "rate", "id"])
print(df.shape)
df.head(10)
(100000, 4)
user item rate id
0 196 242 3.0 881250949
1 186 302 3.0 891717742
2 22 377 1.0 878887116
3 244 51 2.0 880606923
4 166 346 1.0 886397596
5 298 474 4.0 884182806
6 115 265 2.0 881171488
7 253 465 5.0 891628467
8 305 451 3.0 886324817
9 6 86 3.0 883603013
from surprise import Reader
from surprise import Dataset

reader = Reader(rating_scale=(0,5))
dataframe = Dataset.load_from_df(df[["user", "item", "rate"]],reader)
# loda_from_df에 들어가는 데이터 프레임 컬럼 파라미터는 
# 순서대로 사용자 id, 아이템 id, rating이다.

# 마지막 10개만 출력
data.raw_ratings[:10]
[('196', '242', 3.0, '881250949'),
 ('186', '302', 3.0, '891717742'),
 ('22', '377', 1.0, '878887116'),
 ('244', '51', 2.0, '880606923'),
 ('166', '346', 1.0, '886397596'),
 ('298', '474', 4.0, '884182806'),
 ('115', '265', 2.0, '881171488'),
 ('253', '465', 5.0, '891628467'),
 ('305', '451', 3.0, '886324817'),
 ('6', '86', 3.0, '883603013')]
from surprise.model_selection import KFold

bsl_options = {
    'method': 'als',
    'n_epochs': 5,
    'reg_u': 12,
    'reg_i': 5
}
algo = surprise.BaselineOnly(bsl_options)

np.random.seed(0)
acc = np.zeros(3)
cv = KFold(3)
for i, (trainset, testset) in enumerate(cv.split(dataframe)):
    algo.fit(trainset)
    predictions = algo.test(testset)
    acc[i] = surprise.accuracy.rmse(predictions, verbose=True)
acc.mean()
Estimating biases using als...
RMSE: 0.9454
Estimating biases using als...
RMSE: 0.9377
Estimating biases using als...
RMSE: 0.9500





0.9443731335854068
from surprise.model_selection import cross_validate

cross_validate(algo, dataframe)
Estimating biases using als...
Estimating biases using als...
Estimating biases using als...
Estimating biases using als...
Estimating biases using als...





{'test_rmse': array([0.93852765, 0.94652511, 0.93621515, 0.94228131, 0.9443244 ]),
 'test_mae': array([0.74497763, 0.75130899, 0.73987318, 0.7458679 , 0.74674833]),
 'fit_time': (0.09366917610168457,
  0.09369611740112305,
  0.09055066108703613,
  0.09372663497924805,
  0.1216742992401123),
 'test_time': (0.1269059181213379,
  0.07813453674316406,
  0.15621280670166016,
  0.0853111743927002,
  0.08278083801269531)}

3. 추천성능 평가기준

아래 식에서 R헷은 테스트 데이터셋을 뜻한다.

RMSE는 RSS를 평균한 거라고 생각하면 된다.

RMSE나 MAE는 아무렇게나해도 일단 기본점수가 잘나온다. 기본점수가 잘 나오게 되면 좋은 모델과 나쁜모델을 고를때 고를 수 있는 편차치가 작아서 좋은 모델과 나쁜 모델을 고르는게 어렵다. 또한 기본점수가 높게 나오면 보는사람의 판단을 호도할 수도 있다. 쉽게말해서 기본점수가 잘 나오면 ‘아 내가 모델을 잘 만들었구나. 이정도면 되겠다’라고 더이상 모델을 발전시킬 여지를 안만든다는 것이다.

그런거를 보완하기 위해서 추천시스템에서 제안하는 것은 FCP라는 것이다. concordant pair와 disconcordant pair라는거를 찾아낸다. 데이터 레코드를 두개를 찾아낸다. 예를들어서 데이터가 100만개 있으면 100만 combination 2의 경우의 수가 있을것이다. 그래서 일일이 다 2개 짝지은 것을 찾아서 걔들이 concordant한지 disconcordant한지 찾아낸다. 어떻게 찾아내냐면 예를들어서 임의로 y 100번하고 y헷 100번을 찾고, y 121번하고 y헷 121번을 고른다. 얘네들이 concordant한다는 것은 100번이 아니라 121번으로가면서 y값이 증가했을때 그럴때 y헷값도 같이 증가했으면 그때 concordant한것이다. 그런데 만약에 y값이 증가했을때 y헷값은 감소했다면 그때는 disconcordant한 것이다. 그래서 내가 찾을 수 있는 모든 경우를 다 찾아서 거기에 대해서 concordant한 것과 disconcordant한 것의 비율을 계산을 한 것이 FCP다. 이게 조금 더 엄격한 기준이다.

4

4. Collaborative Filter 개요

baseline보다 많이 쓰는 모델이 collaborative filter다.

5

5. collaborative filter 방법 사용을 위한 유사도 계산법

7

5-1) 평균제곱 차이 유사도

유클리드 거리를 쓴다.

여기서 I는 테이블에 펑크난 데이터는 쓰기가 어렵겠고 공통으로 들어간 피쳐를 쓰겠다는 의도이다.

8

5-2) 코사인 유사도

어떤사람은 그 사람 특성상 모든 상품에 대해서 평점을 짜게 주는 사람이 있고, 반대로 후하게 주는 사람도 있기 때문에 평점간의 비율과 방향이 더 중요하다고 보는 것이 cosine 유사도이다.

9

5-3) 피어슨 유사도

유저간의 rating 주는 성향에 대해서 상관관계를 구하는 것이라고 보면된다.

10

5-4) 피어슨-베이스라인 유사도

correlation을 구할때 평균값을 구해야 하는데 이 평균값이 겹치는 얘들이 적거나 차원이 적어지면 부정확해지는 경향이 있는데 이 부정확한 평균값을 쓰지말고 baseline으로 평균값을 예측을 한 다음에 그 예측된 조금 더 정확한 평균값을써서 correlation을 정확하게 구해보자 라는 아이디어다.

11

6. Collaborative Filter에서 Neighborhood 모형

Neighborhood 모형은 비지도학습에서 KNN 모형과 유사한 방법이다.

KNN 모형은 regression이든 classification 모형이든 다쓸 수 있는데 내가 테스트하려는 데이터가 있으면 그 테스트하려는 데이터에서 제일 가까운 k개의 이웃을 찾아낸 다음에 k개 이웃이 가지고 있는 y값을 regression이면 평균을 하면 되고, classification은 다수결원칙으로 다수결이 가장 높은 클래스를 선택하면 된다.

이 KNN방법을 collaborative filter에 적용한것이 neighborhood 모형이다. 그러면 이 데이터들이 얼마나 가까운지 측정을 해야하기 때문에 미리 모든 pair의 데이터 간의 유사도(거리)를 계산을 해놔야한다.

그리고 어떤 특정데이터를 볼때 사용자라고 보고 하는 방법이 사용자 기반 방법이고, 그게 사용자가 아니라 상품이나 영화 이런거로 푸는 방법이 상품기반 방법이다.

사용자기반 방법은 예를들어서 A라는 사용자가 있고 A사용자는 아직 B상품을 구매해보지 않은 사용자이다. 이 A사용자가 어떤 B상품에 대해서 평점을 얼마나 줄것인가 예측하기 위해서는 B상품을 구매했던 다른 사용자들 중에서 A사용자와 가까운 사람들을 몇명 찾은다음에 그 사람들이 B 상품에 대해 평점을 얼마나 줬는지를 찾아서 평균을 내면 A사용자의 B상품에 대한 예상점수가 되는 것이다.

상품기반 방법은 어벤저스라는 영화가 있으면 이 어벤저스와 비슷한 다른영화들을 찾아내서 그 다른 영화들에 대해 매겨진 점수를 평균내는것이다.

x 데이터를 유저나 상품이냐에 따라 사용자 기반이냐 상품기반이냐 달라지는 것이다.

그러면 유사도를 계산하려면 모든 사용자들끼리의 유사도를 모두 계산해놔야 한다. 그래서 유사도행렬을 미리계산을 해놔야한다. 유저의 수 x 유저의 수의 테이블이다. 상품기반이라면 상품의 수 x 상품의 수의 테이블을 미리 계산해놔야 한다.

만약에 유저의 수는 천천히 늘어나는데 상품의 수가 빠르게 늘어나거나 유저의 수에 비해 상품가 너무 많다고 하면 사용자 기반으로 하는 것이 좋을 것이다.

반대로 유저의 수는 빠르게 늘어나거나 유저의 수가 상품의 수보다 너무 많으면 상품기반 방법으로 하는 것이 좋다.

또는 두개 방법을 모두 써봤을때 한쪽의 방법이 퍼포먼스가 월등히 좋으면 그 좋은방법으로 쓰면 된다.

그래서 예측을 할때는 사전에 구한 유사도를 테이블로 보관하고 있다가 예를들어서 A유저라고 선택을 하면 유사도 테이블에서 A유저와 가장 유사도가 높은 k명의 이웃을 찾아내서 그걸 가중평균한다.

6

7. Neighborhood 모형에서 KNN 가중치 예측방법

12

8. Collaborative Filter 에서 Latent Factor 모형

rating matrix에는 어떤 유저가 어떤 아이템에 매긴 점수가 있다. 그러면 이 유저가 어떤 아이템에 매긴 점수가 어떻게 나온거냐 판단할때 어떤 판단 근거가 있다고 가정하는 것이다. 예를들어서 영화에 그 사용자가 평가를 할때 보는 어떤 요소들이 있어서 그 요소에 대해 정량화가 되어 있고, 각 유저가 그 요소들 중에 어떤 요소를 더 중요하게 생각하고 어떤 요소는 감점요인이고 이런것들을 유저마다 값이 있을텐데 이 두개의 값의 inner product가 된게 rating이라는 값으로 나타난다고 가정하는 것이다.

13

만약에 Latent Factor 모형의 가정이 맞는 것이라면 Rating matrix는 P와 Q의 곱으로 만들어진다. Rating matrix는 현재 완벽한 테이블은 아니다 중간중간에 구멍이 뻥뻥 뚫려 있는데 이거는 언젠가는 채워질 데이터들이다. 그래서 만약에 모든 유저가 모든 영화를 다본다면 그때는 Rating matrix가 꽉채워질텐데 현재는 일부만 채워져 있는 것이고 현재 Rating matrix의 일부요소들을 잘 이용해서 아직 안채워져 있는 부분을 잘 채우면 되는 것이다.

그려면 어떻게 채울것이냐 P와 Q 메트릭스의 곱이라고 생각하고 R을 구할 것이다. 유저의 P(preference)와 영화자체의 퀄리티를 나타내는 Q를 곱한것이 R 메트릭스라는 것이다. 그래서 P와 Q를 꽉채워서 계산하게 된다. P와 Q는 기존의 R정보들로 구하게 된다.

Latent Factor는 실제로는 어떻게 구하냐면 이 P하고 Q의 모든 요소를 미지수 x로 두고 그거를 SGD나 ALS로 그냥 무식하게 찾는다. 예를들어서 factor가 하나라고 하면 미지수가 유저개수 + 아이템 개수 가 될 것이다. 예를들어서 P와 Q를 아무렇게나 집어 넣고, 원래 있던데이터 몇개와 이 미지수를 구한 값의 차이를 오차함수로 두고 SGD를 계속 돌리게 된다.

그러면 우리가 구한것과 눈에 보이는 부분과 거의 같게 되면 나머지 눈에 안보이는 아직 일어나지 않은 rating도 아마도 거의 비슷하지 않을까 가정하는 것이다.

14

그런데 우리가 영화를 평가할때 factor가 한두개가 아닌게 현실이다. 그러나 이 factor가 늘어나면 늘어날수록 SGD로는 계산양을 감당을 할수가 없다.

그래서 나온 방법이 초기값이라도 좋은 초기값을 찾아보자해서 SVD를 이용해서 좋은 초기값을 찾는다. SVD를 하는데 구멍이 송송뚫린 R에서 일부 있는 것만이라도 SVD를 하게 된다. SVD는 아래와 같이 R = U시그마VT로 표현할 수 있다. 그런데 우리가 원하는 것은 P X Q 형태의 두가지로 나누고 싶다. 그런데 SVD의 특징 중에 하나는 singular value는 항상 양수여야 한다는 것이다. 이말은 양수이고 대각행렬이기 때문에 두개로 쪼갤 수 있다는 것이다. 각각의 대각성분에 제곱근 값을 넣어주면 두개로 쪼개지는 것이다. 그래서 이렇게 두개로 쪼개는 방법으로 R = U시그마VT에서 시그마를 쪼개서 양쪽으로 나누어주면 P메트릭스와 Q메트릭스의 어떤 approximation이 될 것이다. 그래서 그 초기값을 SGD하면 좀 더 빠르게 수렴할 것이다라는 것이다.

이 방법이 대량의 데이터에 대해 정확도가 그나마 높은 편이다.

collaborative filter 방법의 단점은 유사도 행렬을 사전에 만들어놔야 하는데 이게 어마어마하게 크니까 이걸 계산하는데도 오래걸리고, 계속 이 유사도행렬을 업데이트해줘야하고, 관리하는 것도 힘들다. 왜냐하면 지속적으로 유저나 상품의 변화가 있기 때문이다. 그래서 현실적으로 하기 어렵다.

그리고 KNN방법의 단점중에 하나는 뭐 할때마다 DB를 쿼리해서 가장 좋은거 K개를 찾는 작업을 해야하기 때문에 모델링할때는 시간이 별로 안걸리는데 prediction할때 시간이 오래걸린다.

그래서 Latent factor가 데이터양에 비해서 걸리는 시간 만큼은 장점이 있다고 할 수 있다.

9. Content based recommend 방법

지금까지의 방법은 유저들이 평점을 매겨줘야 계산할 수 있는 방법들이다. 그런데 평점이 없이 추천시스템을 시작해야 하는 경우도 있다. 그러면 기본적으로 할 수 있는 것은 rule based 방법이다. 실제로 이 사람이 무엇을 좋아할지는 모르겠지만 내가 어떤 법칙을 가정해버리는 것이다. 그 중에 하나가 내 맘대로 아이템의 유사도를 정하는 것이다. 그래서 유사도가 같으면 아마도 같은 평점을 내리지 않을까 가정을 하는 것이다.

이 방법의 단점은 내가 정한 법칙이 실제로 선호도의 유사도와 같은지는 보장할 수 없다.

그러나 기존에 평점데이터가 없는 상태라면 이 방법으로 시작하는수 밖에 없다.

그래서 처음에는 Content based recommend 방법으로 추천시스템을 시작하고 데이터가 쌓이고 데이터가 적을때는 baseline 모델, 조금 더 커지면 네이버후드 방식 그리고 완전 데이터가 커지면 matrix factorization 방법이나 SVD 방법을써서 Latent factor model을 채택하는 것이 통상적이다.