해당 글은 교재 'Python을 이용한 개인화 추천시스템' 교재를 정리한 글로, 이전 글과 이어집니다! 자세한 내용이 궁금하시면 교재와 글을 참고해주세요!
https://jhklee-coder.tistory.com/76
MF (matrix factorization) 기반 추천 1 [ReSys]
추천을 위한 알고리즘은 크게 메모리 기반(memory-based)과 모델 기반(model-based)으로 나눌 수 있다. 메모리 기반 알고리즘은 추천을 위한 데이터를 모두 메모리에 갖고 있으면서 추천이 필요할 때마
jhklee-coder.tistory.com
앞서 추천 시스템의 궁극적 목표는 각 유저의 각 아이템에 대한 선호도(혹은 구매 확률)를 예측하는 것이라고 했다. MF는 유저의 취향과 아이템의 특성을 나타내는 특성값(feature)을 K개로 요약해서 추출하고, 이를 이용해 각 유저의 각 아이템의 선호도를 예측하는 방식이라면, FM은 유저와 아이템의 다양한 특성을 모델화함으로써 이러한 예측의 성능을 높이는 방법이다.
만약 유저의 취향, 아이템의 특성 외에도 예측에 도움되는 다른 변수가 있다면??
예를 들어 유저의 경우 나이, 성별, 지역과 같은 인구통계 변수가 선호도 예측에 도움을 줄 수 있다. 아이템의 경우에는 영화를 예로 들었을 때 각 영화의 특성을 나타내는 감독, 출연 배우, 장르와 같은 변수를 반영하면 선호도 예측이 더 정확해질 수 있다. 이에 더해 영화의 관람이나 제품의 구매 시점의 상황을 나타내는 시간대, 요일 등의 변수까지 추가되면 더 정확해진다. 이렇게 다양한 변수들을 종합하여 요인화(factorization)하는 방법이 바로 FM이다!
📌 FM의 표준식
FM의 기본 아이디어는 '모든 변수와 그 변수들 간의 상호작용을 고려해 평점을 예측'하는 것이다.
Ex) 영화에 대한 유저의 평가에 영향을 미치는 요인이 유저의 나이와 영화의 상영시간일 때.
→ '행렬요인화'에서 사용했던 유저ID와 영화ID + 나이, 상영시간까지 총 4 가지 변수가 있다.
→ 각 변수 간의 상호 작용으로는 유저ID x 상영시간 , 영화ID x 나이, 상영시간 x 나이가 있을 수 있다. 이러한
모든 변수가 영화의 평점에 미치는 영향을 학습하고, 이를 바탕으로 평점을 예측하는 모델이 바로 FM!
1️⃣ 표준식 세우기


입력 변수 x 의 모든 가능한 2개 씩의 조합에 대해 해당 x의 잠재요인행렬 v의 값을 내적하고, 여기에 x의 값을 곱한다. v 는 MF의 P 혹은 Q 행렬처럼 각 변수를 k개의 특성값(feature value)으로 표현하는 잠재요인행렬로 생각한다.
➡️ 즉 변수가 n개이고 특성값이 k개이므로, n x k 행렬이 된다.
➡️ x는 MF 처럼 각 유저나 아이템을 나타내는 것일 수도 있고, 장르나 시간과 같이 별도의 변수일 수도 있다. 어떤 경우이든 하나의 변수인 x(i)에 대해 하나의 편향값(w(i))과 k개의 특성값(feature)를 갖는다.
➡️ 만일 x가 각 유저와 아이템 각각을 나타내는 One-hot Encoding이고 유저와 아이템 외에 다른 변수가 사용되지 않는다면 위 식은 결국 MF의 식과 동일해진다.
결국 FM은 MF에 다른 변수를 추가할 수 있도록 좀 더 일반화한 모델!!
📌 FM의 식 변형
앞의 표준식 1️⃣ 의 경우 변수 x의 모든 가능한 두 개의 조합으로 구성되어 있다. 따라서 변수의 수가 늘어남에 따라 계산의 복잡도 또한 기하급수적으로 커진다. 식의 단순화를 위해선 아래와 같은 변형 과정을 거쳐서 표준식 2️⃣, 표준식 3️⃣를 순차적으로 구해야 한다.

이렇게 얻어진 표준식 2️⃣를 원래 식에 대입하면 최종 표준식 3️⃣이 나온다.

➡️ FM의 표준식 1은 계산의 효율성이 떨어지기 때문에 FM을 실제로 적용할 때는 변형된 식 표준식 3️⃣을 사용하는 것이 유용하다.
📌 FM의 학습
FM의 학습은 *경사하강법의 예를 통해 설명이 가능하다.
*경사하강법
경사하강법은 주어진 함수(비용 함수)의 최솟값을 찾기 위해 현재 위치에서 함수의 기울기(그래디언트)를 계산하고, 그 기울기의 반대 방향으로 이동하여 최솟값에 접근하는 최적화 알고리즘다. 이 과정은 다음과 같이 요약할 수 있습니다.
1) 초기화 : 시작 위치를 임의로 선택하거나 미리 정의된 값을 사용하여 파라미터를 초기화한다.
2) 그래디언트 계산 : 현재 위치에서 비용 함수의 그래디언트(기울기)를 계산한다. (이는 함수가 현재 위치에서 어떻게 변하는지를 나타낸다.)
3) 파라미터 업데이트 : 그래디언트의 반대 방향으로 파라미터를 업데이트한다. (이때 학습률(learning rate)이 사용되며, 이 값은 한 번의 업데이트 당 얼마나 멀리 이동할지를 제어한다.)
4) 반복: 위 단계를 반복하여 비용 함수의 최솟값을 찾을 때까지 파라미터를 업데이트한다. (또는 미리 정의된 반복 횟수(epoch)를 수행하면서 멈출 수도 있다.)


FM의 학습은 다른 기계학습 방식과 동일하다.
1️⃣ 비용함수(rmse)를 설정한다.
2️⃣ w(0), w, v를 초기화한다.
3️⃣ 주어진 w(0), w, v에 따라 비용함수를 계산한다.
4️⃣ w, v를 업데이트한다.
5️⃣ 비용함수의 최솟값이 나올 때까지(개선되지 않을 때까지) 3, 4 과정을 반복한다.
📌 FM의 데이터 변형
초기 FM 데이터는 다음과 같은 형태로 표현이 가능하다.

➡️ 유저 별 영화(아이템)에 대해 평가를 한 값을 행렬 형태로 만들었다. (-> 5명의 유저가 5개의 아이템에 대해 총 11개의 평점을 부여!)
➡️ 유저-아이템 행렬은 다시 'FM'에 맞게끔 아래 표와 같이 형태 변형이 가능하다.

cc FM에서는 유저와 아이템 Encoding 외에 다른 정보가 포함될 수 있다고 했다. '추가 변수' 열이 이들 변수를 나타낸다. FM은 MF 와 다르게 유저, 아이템 변수 외에 추가적인 변수를 사용할 수 있다는 점에서 더 일반화된 모형이라고 할 수 있다.
- x11 : 유저의 특성을 나타내는 연속값 변수 (ex. 월평균 영화를 보는 횟수)
- x12 : 영화의 특성을 나타내는 연속값 변수 (ex. 관객 수 순위)
- x13 : binary값, x14 : binary값 (ex. 성별, 영화가 애니메이션인지 아닌지 등)
1) 유저 관련 변수 = 5개 (x1 ~ x5)
2) 아이템 관련 변수 = 5개 (x6 ~ x10)
3) 추가 변수 = 5개 (x11 ~ x14)
*추가변수를 사용하지 않고, 유저와 아이템 데이터만 사용하는 경우라면?
한 유저와 한 아이템은 v행렬의 한 행, 즉 각 k개의 잠재요인값을 갖는다. 앞의 식 (1)을 보면 예측값은 아래와 같은 방식으로 계산된다.
1) 해당 유저와 해당 아이템의 잠재요인 행렬 v 중 해당 유저의 k개의 값과 해당 아이템의 k개의 값을 내적한다.
2) 유저와 아이템 편향값(w)과 전체편향(w(o))를 더한다.
=> 'MF와 같은 식' . FM은 이외에도 유저, 아이템 변수 외에 추가적인 변수를 사용할 수 있다!!
📌 Surprise 패키지 사용해보기
파이썬에는 CF 와 MF 기반 추천시스템을 구현하고 테스트 할 수 있는 패키지가 존재한다. 현재 가장 잘 알려진 것 중에 하나가 바로 Surprise 패키지.
➡️ scikit 패키지의 일부로 CF, MF 기반 알고리즘을 테스트할 수 있는 유용한 패키지!!
1️⃣ 활용 방법
ml-100K, ml-1m, jester 이렇게 3가지 데이터가 내장되어 있다. 이와 함께 다양한 종류의 알고리즘을 사용할 수 있는데, 다음과 같은 알고리즘이 있다.

이외에도 정확도 측정이나 train/test set 분리를 위한 다양한 메소드들도 내장되어 있다.
#️⃣ 실습
# surprise 라이브러리 설치
!pip install scikit-surprise
import numpy as np
import surprise as sp
data = sp.Dataset.load_builtin('ml-100k')
# train 데이터셋, test 데이터셋 분류.
train_set, test_set = sp.model_selection.train_test_split(data, test_size = 0.25)
pred = sp.KNNWithMeans()
pred.fit(train_set)
predictions = pred.test(test_set)
sp.accuracy.rmse(predictions)
# Computing the msd similarity matrix...
# Done computing similarity matrix.
# RMSE: 0.9530
# 0.9529942664009241
2️⃣ 알고리즘 비교
Surprise에서 지원하는 알고리즘 중에서 BaselineOnly, KNNWithMeans, SVD, SVDpp, 이렇게 4가지를 비교한다.
algorithms = [sp.BaselineOnly, sp.KNNWithMeans, sp.SVD, sp.SVDpp]
names = []
results = []
for option in algorithms:
algo = option()
names.append(option.__name__)
algo.fit(train_set)
predictions = algo.test(test_set)
results.append(sp.accuracy.rmse(predictions))
names = np.array(names)
results = np.array(results)
import matplotlib.pyplot as plt
idx = np.argsort(results)
plt.ylim(0.8, 1)
plt.plot(names[idx], results[idx])
results[idx]

➡️ SVDpp 가 RMSE = 0.9219 정도로 가장 정확하고, KNNWithMeans가 RMSE = 0.95299 정도로 정확도가 가장 낮다.
3️⃣ 알고리즘 옵션 지정
➡️ KNN CF 알고리즘의 정확도 계산 시 사용했던 KNNWithMeans 알고리즘..
유사도지표 및 이웃 크기를 기본값으로 사용했다면, 그 기본값이 도대체 무엇일까..?
- 이웃 크기의 기본 default 값 : 40
- 유사도지표의 기본 값 : MSD (mean squared error)
➡️ 이웃의 크기를 변경하고 싶거나 유사도지표를 다른 것을 사용해야 한다면?? 파라미터를 다르게 부여하면 되지!!
4️⃣ 다양한 조건의 비교
1) 이웃 크기 변화
➡️ 최적의 이웃 크기를 찾기 위한 이웃 크기 변화 시도.
result = []
for neighbor_size in (10, 20, 30, 40, 50, 60):
algo = sp.KNNWithMeans(k = neighbor_size, sim_options = {'name': 'pearson_baseline',
'user_based' : True})
algo.fit(train_set)
predictions = algo.test(test_set)
result.append([neighbor_size, sp.accuracy.rmse(predictions)])
result

결론. 이웃의 크기가 40인 경우 가장 좋은 결과가 도출되는구나!
2) Grid Search
➡️ 가능한 모든 조합의 비교
이웃 크기의 비교뿐 아니라 다른 조건의 비교도 파라미터의 튜닝과 같은 방식으로 가능하다.
Surprise에는 이웃 크기, 유사도지표, User-based vs Item-based 등의 조건을 조합해서 비교하는 가능한 모든 조합을 비교하는 Grid search를 지원한다.
from surprise.model_selection import GridSearchCV # GridSearch 모듈 불러오기
param_grid = {'k' : [5, 10, 15, 25],
'sim_options' : {'name' : ['pearson_baseline', 'cosine'],
'user_based' : [True, False]}
}
gs = GridSearchCV(sp.KNNWithMeans, param_grid, measures = ['rmse'], cv = 4)
gs.fit(data)
print(gs.best_score['rmse'])
print(gs.best_params['rmse'])
# 0.9264665667610966
# {'k': 25, 'sim_options': {'name': 'pearson_baseline', 'user_based': False}}
3) SVD(MF) 알고리즘의 조건 비교
➡️ KNN과는 다른 지정 옵션.
from surprise.model_selection import GridSearchCV
param_grid = {'n_epochs' : [70, 80, 90],
'lr_all' : [0.005, 0.006, 0.007],
'reg_all' : [0.05, 0.07, 0.1]}
gs = GridSearchCV(sp.KNNWithMeans, param_grid, measures = ['rmse'], cv = 4)
gs.fit(data)
print(gs.best_score['rmse'])
print(gs.best_params['rmse'])
# {'n_epochs': 70, 'lr_all': 0.005, 'reg_all': 0.05}
'기획(PM & PO) > ✏️ 개발 일지' 카테고리의 다른 글
| 'MF' 와 '딥러닝' [ReSys] (0) | 2024.04.14 |
|---|---|
| 'FM (Factorization Machines)' 기반 추천 2 [ReSys] (0) | 2024.04.01 |
| 'MF (matrix factorization)' 기반 추천 2 [ReSys] (0) | 2024.03.25 |
| 'MF (matrix factorization)' 기반 추천 1 [ReSys] (0) | 2024.03.24 |
