KNN 협업 필터링 실습¶
유저-영화 평점 데이터를 이용해 유저가 아직 평가하지 않은 영화를 추천을 해보겠습니다.
import pandas as pd
import numpy as np
np.random.seed(2021)
1. Data¶
1.1 Data Load¶
데이터에서 유저 고유 아이디를 나타내는 userId, 영화 고유 아이디를 나타내는 movieId, 유저가 영화를 평가한 점수 rating 컬럼을 이용합니다.
ratings = pd.read_csv("ratings_small.csv")
ratings = ratings[["userId", "movieId", "rating"]]
ratings.head()
| userId | movieId | rating | |
|---|---|---|---|
| 0 | 1 | 31 | 2.5 |
| 1 | 1 | 1029 | 3.0 |
| 2 | 1 | 1061 | 3.0 |
| 3 | 1 | 1129 | 2.0 |
| 4 | 1 | 1172 | 4.0 |
다른 두 데이터를 이용해 ratings 데이터의 movieId에 맞는 영화 제목을 얻습니다.
movies = pd.read_csv("movies_metadata.csv")
links = pd.read_csv("links_small.csv")
C:\Users\sjy99\AppData\Local\Temp\ipykernel_38316\1408741908.py:1: DtypeWarning: Columns (10) have mixed types. Specify dtype option on import or set low_memory=False.
movies = pd.read_csv("movies_metadata.csv")
1.2 Data Preprocessing¶
movies 데이터에서 "tt숫자"로 이루어진 imdb_id에서 숫자 부분과
links 데이터의 "숫자"로 이루어진 imdbId와 연결합니다.
movies = movies.fillna('')
movies = movies[movies["imdb_id"].str.startswith('tt')]
movies["imdbId"] = movies["imdb_id"].apply(lambda x: int(x[2:]))
movies = movies.merge(links, on="imdbId")
movies = movies[["title", "movieId"]]
movies = movies.set_index("movieId")
movies.head()
| title | |
|---|---|
| movieId | |
| 1 | Toy Story |
| 2 | Jumanji |
| 3 | Grumpier Old Men |
| 4 | Waiting to Exhale |
| 5 | Father of the Bride Part II |
pivot함수를 이용해 유저 아이디가 인덱스이고, 영화 아이디가 컬럼, 값이 평가 점수인 user_movie_matrix를 만듭니다.
user_movie_matrix = ratings.pivot(
index="userId",
columns="movieId",
values="rating",
)
user_movie_matrix.iloc[-5:, -5:]
| movieId | 161944 | 162376 | 162542 | 162672 | 163949 |
|---|---|---|---|---|---|
| userId | |||||
| 667 | NaN | NaN | NaN | NaN | NaN |
| 668 | NaN | NaN | NaN | NaN | NaN |
| 669 | NaN | NaN | NaN | NaN | NaN |
| 670 | NaN | NaN | NaN | NaN | NaN |
| 671 | NaN | NaN | NaN | NaN | NaN |
유저가 평가하지 않은 영화에 대해서 결측값을 0으로 대체합니다.
user_movie_matrix = user_movie_matrix.fillna(0)
user_movie_matrix.shape
(671, 9066)
2. KNN Basic¶
k가 5인 KNN Basic을 이용해 유저 "124" 가 아직 평가하지 않은 영화 "648"에 대한 점수를 예측해 보겠습니다.
k = 5
user_i = 124
movie_id = 648
2.1 유저 간의 유사도를 계산한다.¶
cosin_similarity 함수를 이용해 유저별 코사인 유사도를 계산합니다.
from sklearn.metrics.pairwise import cosine_similarity
user_similarity = cosine_similarity(user_movie_matrix)
user_similarity.shape
(671, 671)
user_similarity[:10, :10]
array([[1. , 0. , 0. , 0.07448245, 0.01681799,
0. , 0.08388416, 0. , 0.01284289, 0. ],
[0. , 1. , 0.12429498, 0.11882103, 0.10364614,
0. , 0.21298521, 0.11319045, 0.11333307, 0.04321284],
[0. , 0.12429498, 1. , 0.08163991, 0.15153112,
0.06069128, 0.15471414, 0.24978072, 0.13447489, 0.1146725 ],
[0.07448245, 0.11882103, 0.08163991, 1. , 0.13064868,
0.07964833, 0.31974534, 0.19101336, 0.03041726, 0.13718558],
[0.01681799, 0.10364614, 0.15153112, 0.13064868, 1. ,
0.06379575, 0.0958878 , 0.16571211, 0.08661604, 0.03237017],
[0. , 0. , 0.06069128, 0.07964833, 0.06379575,
1. , 0. , 0.12850206, 0.02174493, 0.04526415],
[0.08388416, 0.21298521, 0.15471414, 0.31974534, 0.0958878 ,
0. , 1. , 0.14957182, 0.05972764, 0.18649318],
[0. , 0.11319045, 0.24978072, 0.19101336, 0.16571211,
0.12850206, 0.14957182, 1. , 0.15735626, 0.16272417],
[0.01284289, 0.11333307, 0.13447489, 0.03041726, 0.08661604,
0.02174493, 0.05972764, 0.15735626, 1. , 0.12734115],
[0. , 0.04321284, 0.1146725 , 0.13718558, 0.03237017,
0.04526415, 0.18649318, 0.16272417, 0.12734115, 1. ]])
user_similarity = pd.DataFrame(
data=user_similarity,
index=user_movie_matrix.index,
columns=user_movie_matrix.index,
)
user_similarity.head(5)
| userId | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| userId | |||||||||||||||||||||
| 1 | 1.000000 | 0.000000 | 0.000000 | 0.074482 | 0.016818 | 0.000000 | 0.083884 | 0.000000 | 0.012843 | 0.000000 | ... | 0.000000 | 0.000000 | 0.014474 | 0.043719 | 0.000000 | 0.000000 | 0.000000 | 0.062917 | 0.000000 | 0.017466 |
| 2 | 0.000000 | 1.000000 | 0.124295 | 0.118821 | 0.103646 | 0.000000 | 0.212985 | 0.113190 | 0.113333 | 0.043213 | ... | 0.477306 | 0.063202 | 0.077745 | 0.164162 | 0.466281 | 0.425462 | 0.084646 | 0.024140 | 0.170595 | 0.113175 |
| 3 | 0.000000 | 0.124295 | 1.000000 | 0.081640 | 0.151531 | 0.060691 | 0.154714 | 0.249781 | 0.134475 | 0.114672 | ... | 0.161205 | 0.064198 | 0.176134 | 0.158357 | 0.177098 | 0.124562 | 0.124911 | 0.080984 | 0.136606 | 0.170193 |
| 4 | 0.074482 | 0.118821 | 0.081640 | 1.000000 | 0.130649 | 0.079648 | 0.319745 | 0.191013 | 0.030417 | 0.137186 | ... | 0.114319 | 0.047228 | 0.136579 | 0.254030 | 0.121905 | 0.088735 | 0.068483 | 0.104309 | 0.054512 | 0.211609 |
| 5 | 0.016818 | 0.103646 | 0.151531 | 0.130649 | 1.000000 | 0.063796 | 0.095888 | 0.165712 | 0.086616 | 0.032370 | ... | 0.191029 | 0.021142 | 0.146173 | 0.224245 | 0.139721 | 0.058252 | 0.042926 | 0.038358 | 0.062642 | 0.225086 |
5 rows × 671 columns
2.2 아이템 i를 평가한 유저들 중에서 유저 u와 비슷한 유저 k명을 찾는다.¶
이제 유저 "124"와 유사한 다른 유저 k명을 찾습니다.
user_i_similarity = user_similarity.loc[user_i]
user_i_similarity
userId
1 0.000000
2 0.129669
3 0.224600
4 0.147568
5 0.159521
...
667 0.065720
668 0.074023
669 0.049342
670 0.201474
671 0.330381
Name: 124, Length: 671, dtype: float64
user_i_similarity = user_i_similarity.sort_values(ascending=False)
user_i_similarity
userId
124 1.000000
458 0.455216
379 0.433607
355 0.432242
282 0.423280
...
640 0.000000
642 0.000000
341 0.000000
76 0.000000
1 0.000000
Name: 124, Length: 671, dtype: float64
유사도 상위 k명의 유사도와 id 추출합니다.
이때 가장 유사도가 높은 id는 user_i로 제외합니다.
top_k_similarity = user_i_similarity[1: k + 1]
top_k_similar_user_ids = top_k_similarity.index
C:\Users\sjy99\AppData\Local\Temp\ipykernel_38316\3664279467.py:1: FutureWarning: The behavior of `series[i:j]` with an integer-dtype index is deprecated. In a future version, this will be treated as *label-based* indexing, consistent with e.g. `series[i]` lookups. To retain the old behavior, use `series.iloc[i:j]`. To get the future behavior, use `series.loc[i:j]`. top_k_similarity = user_i_similarity[1: k + 1]
top_k_similar_user_ids
Int64Index([458, 379, 355, 282, 271], dtype='int64', name='userId')
top_k_similarity
userId 458 0.455216 379 0.433607 355 0.432242 282 0.423280 271 0.409402 Name: 124, dtype: float64
2.3 K명의 유사한 유저들이 아이템 i에 평가한 선호도를 유사도 기준으로 가중 평균한다.¶
tok_k_similar_ratings = user_movie_matrix.loc[top_k_similar_user_ids, movie_id]
movie_id
648
tok_k_similar_ratings
userId 458 4.5 379 0.0 355 3.5 282 4.0 271 0.0 Name: 648, dtype: float64
평가 점수가 있는 유저에 대한 Weight 추출합니다
top_k_weight = (tok_k_similar_ratings > 0) * top_k_similarity
top_k_weight
userId 458 0.455216 379 0.000000 355 0.432242 282 0.423280 271 0.000000 dtype: float64
유사도가 곱해진 평가 점수의 합을 유사도 합으로 나눕니다.
weighted_rating = top_k_weighted_ratings.sum()
weight = top_k_weight.sum()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) c:\Users\sjy99\Desktop\패스트\Part4. 머신러닝 알고리즘의 모든 것\2. 실습자료\Chapter 12_recommendation_system\03_KNN 협업필터링 실습.ipynb Cell 49 line 1 ----> <a href='vscode-notebook-cell:/c%3A/Users/sjy99/Desktop/%ED%8C%A8%EC%8A%A4%ED%8A%B8/Part4.%20%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%98%20%EB%AA%A8%EB%93%A0%20%EA%B2%83/2.%20%EC%8B%A4%EC%8A%B5%EC%9E%90%EB%A3%8C/Chapter%2012_recommendation_system/03_KNN%20%ED%98%91%EC%97%85%ED%95%84%ED%84%B0%EB%A7%81%20%EC%8B%A4%EC%8A%B5.ipynb#X66sZmlsZQ%3D%3D?line=0'>1</a> weighted_rating = top_k_weighted_ratings.sum() <a href='vscode-notebook-cell:/c%3A/Users/sjy99/Desktop/%ED%8C%A8%EC%8A%A4%ED%8A%B8/Part4.%20%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%98%20%EB%AA%A8%EB%93%A0%20%EA%B2%83/2.%20%EC%8B%A4%EC%8A%B5%EC%9E%90%EB%A3%8C/Chapter%2012_recommendation_system/03_KNN%20%ED%98%91%EC%97%85%ED%95%84%ED%84%B0%EB%A7%81%20%EC%8B%A4%EC%8A%B5.ipynb#X66sZmlsZQ%3D%3D?line=1'>2</a> weight = top_k_weight.sum() NameError: name 'top_k_weighted_ratings' is not defined
weight
1.31073817898241
weight가 0보다 작은 경우 유저 모두 평가하지 않은 경우입니다.
if weight > 0:
prediction_rating = weighted_rating / weight
else:
prediction_rating = 0
prediction_rating
4.008763709331574
2.4 예측 선호도가 높은 아이템을 유저에게 추천한다.¶
모든 영화에 대해서 점수를 예측하고 예측 평가 점수가 높은 영화를 유저에게 추천합니다.
2.4.1 선호도 계산¶
prediction_dict = {}
# 모든 영화 아이디에 대해 평점 예측
for movie_id in user_movie_matrix.columns:
# 이미 유저가 평가한 경우 제외
if user_movie_matrix.loc[user_i, movie_id] > 0:
continue
tok_k_similar_ratings = user_movie_matrix.loc[top_k_similar_user_ids, movie_id]
top_k_weighted_ratings = tok_k_similar_ratings * top_k_similarity
top_k_weight = (tok_k_similar_ratings > 0) * top_k_similarity
weighted_rating = top_k_weighted_ratings.sum()
weight = top_k_weight.sum()
if weight > 0:
prediction_rating = weighted_rating / weight
else:
prediction_rating = 0
# 영화 아이디별로 예측 평가 점수 저장
prediction_dict[movie_id] = prediction_rating
영화 아이디별 예측 평가 점수를 내림차순으로 정렬합니다.
prediction = pd.Series(prediction_dict).sort_values(ascending=False)
2.4.2 상위 아이템 추출¶
예측 평가 점수 상위 10개의 영화 아이디 추출합니다.
recommend = prediction[:10].index
movies.loc[recommend]
| title | |
|---|---|
| movieId | |
| 1258 | The Shining |
| 924 | 2001: A Space Odyssey |
| 3861 | The Replacements |
| 524 | Rudy |
| 3916 | Remember the Titans |
| 260 | Star Wars |
| 2692 | Run Lola Run |
| 1207 | To Kill a Mockingbird |
| 3949 | Requiem for a Dream |
| 3039 | Trading Places |
3. KNN with Means¶
k가 5인 KNN Basic을 이용해 유저 "124" 가 아직 평가하지 않은 영화 "31"에 대한 점수를 예측하는 과정입니다.
user_id = 124
k = 5
movie_i = 648
pivot함수를 이용해 영화 아이디가 인덱스이고, 유저 아이디가 컬럼, 값이 평가 점수인 movie_user_matrix를 만듭니다.
결측값은 0으로 대체합니다.
movie_user_matrix = ratings.pivot(
index="movieId",
columns="userId",
values="rating",
)
movie_user_matrix = movie_user_matrix.fillna(0)
3.1 아이템간의 유사도를 계산한다.¶
영화간의 피어슨 유사도를 계산합니다.
movie_similarity = np.corrcoef(movie_user_matrix)
movie_similarity = pd.DataFrame(
data=movie_similarity,
index=movie_user_matrix.index,
columns=movie_user_matrix.index,
)
movie_similarity.shape
(9066, 9066)
3.2 아이템 i와 비슷한 아이템을 k개 찾는다.¶
영화 "648"과 유사한 다른 영화 k개를 찾습니다.
우선 movie_i와 다른 영화 간의 유사도 추출
movie_i_similarity = movie_similarity.loc[movie_i]
다른 영화와의 유사도 내림차순 정렬합니다.
movie_i_similarity = movie_i_similarity.sort_values(ascending=False)
유사도 상위 k개의 유사도와 id 추출 합니다.
이 때 가장 유사도가 높은 id는 movie_i로 제외합니다.
top_k_similarity = movie_i_similarity[1: k + 1]
top_k_similar_movie_ids = top_k_similarity.index
top_k_similar_movie_ids
Int64Index([780, 733, 736, 786, 376], dtype='int64', name='movieId')
top_k_similarity
movieId 780 0.534337 733 0.522740 736 0.430270 786 0.401280 376 0.370700 Name: 648, dtype: float64
3.3 아이템 i의 평균 선호도를 계산한다.¶
영화별로 특징이 되는 평균 선호도를 계산합니다.
평점이 0인 경우 평가하지 않음을 반영하기 위해 결측값으로 대체합니다.
movie_user_matrix = movie_user_matrix.replace(0, np.NaN)
movie_bias = movie_user_matrix.mean(1)
movie_bias
movieId
1 3.872470
2 3.401869
3 3.161017
4 2.384615
5 3.267857
...
161944 5.000000
162376 4.500000
162542 5.000000
162672 3.000000
163949 5.000000
Length: 9066, dtype: float64
3.4 유저가 평가한 K개의 아이템의 선호도의 편차를 유사도 기준으로 가중 평균한다.¶
3.4.1 유저별 영화 평가 점수 편차 계산¶
movie_user_matrix_wo_bias = movie_user_matrix.sub(movie_bias, axis=0)
movie_user_matrix_wo_bias
| userId | 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 | ... | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| movieId | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | NaN | NaN | NaN | NaN | NaN | NaN | -0.87247 | NaN | 0.12753 | NaN | NaN | NaN | 1.12753 | NaN | -1.872470 | NaN | NaN | NaN | -0.872470 | -0.37247 | NaN | NaN | -0.87247 | NaN | NaN | 1.12753 | NaN | NaN | NaN | 0.127530 | NaN | NaN | NaN | NaN | NaN | NaN | 0.12753 | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | -0.87247 | NaN | NaN | NaN | NaN | 0.127530 | NaN | NaN | NaN | NaN | 1.127530 | 0.12753 | NaN | 0.127530 | NaN | NaN | NaN | 0.12753 | 1.127530 | NaN | NaN | NaN | NaN | NaN | -1.37247 | NaN | NaN | 0.12753 | -0.37247 | NaN | NaN | NaN | NaN | NaN | 0.12753 | 1.12753 |
| 2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -1.401869 | NaN | NaN | NaN | -0.401869 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -1.401869 | NaN | 0.598131 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.598131 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.401869 | 0.598131 | NaN | NaN | NaN | NaN | NaN | NaN | 1.598131 | NaN | NaN | -0.401869 | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN | 0.838983 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.161017 | NaN | NaN | NaN | NaN | NaN | -0.161017 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.838983 | -0.161017 | NaN | NaN | NaN | NaN | 1.838983 | NaN | NaN | NaN | -0.161017 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.161017 | NaN | NaN | NaN | NaN | NaN | NaN |
| 4 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.615385 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -1.384615 | NaN | NaN | NaN | NaN | 0.615385 | -1.384615 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1.232143 | NaN | NaN | -0.267857 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.267857 | 0.732143 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.267857 | NaN | NaN | NaN | 0.732143 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.267857 | NaN | NaN | NaN | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 161944 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 162376 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 162542 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 162672 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 163949 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
9066 rows × 671 columns
3.4.2 상위 k개의 선호도 추출¶
tok_k_similar_ratings = movie_user_matrix_wo_bias.loc[top_k_similar_movie_ids, user_id]
top_k_weighted_ratings = tok_k_similar_ratings * top_k_similarity
tok_k_similar_ratings
movieId 780 NaN 733 NaN 736 NaN 786 -0.108696 376 NaN Name: 124, dtype: float64
top_k_weighted_ratings
movieId 780 NaN 733 NaN 736 NaN 786 -0.043617 376 NaN dtype: float64
추출된 영화중 평가 점수가 있는 영화에 대한 가중치만 남깁니다.
top_k_weight = (pd.notna(tok_k_similar_ratings)) * top_k_similarity
top_k_weight
movieId 780 0.00000 733 0.00000 736 0.00000 786 0.40128 376 0.00000 dtype: float64
3.4.3 가중 평균¶
유사도가 곱해진 평가 점수의 편차 합을 유사도 합으로 나눔
weighted_rating = top_k_weighted_ratings.sum()
weight = top_k_weight.sum()
weight
0.40128029563155065
영화 평균 평점 추출
bias = movie_bias.loc[movie_i]
bias
3.5327380952380953
if weight != 0:
# 평균 평점에 가중 편차 합
prediction_rating = bias + weighted_rating / weight
# weight가 0인 경우 유사 영화 모두 평가하지 않은 경우
else:
prediction_rating = 0
prediction_rating
3.4240424430641823
3.5 예측 선호도가 높은 아이템을 유저에게 추천한다.¶
모든 영화에 대해서 점수를 예측하고 예측 평가 점수가 높은 영화를 유저에게 추천합니다.
prediction_dict = {}
# 모든 영화 아이디에 대해 평점 예측
for movie_id in movie_user_matrix.index:
# 이미 유저가 평가한 경우 제외
if movie_user_matrix.loc[movie_i, user_id] > 0:
continue
tok_k_similar_ratings = movie_user_matrix_wo_bias.loc[top_k_similar_movie_ids, user_id]
top_k_weighted_ratings = tok_k_similar_ratings * top_k_similarity
top_k_weight = (tok_k_similar_ratings != 0) * top_k_similarity
weighted_rating = top_k_weighted_ratings.sum()
weight = top_k_weight.sum()
bias = movie_bias.loc[movie_i]
if weight > 0:
prediction_rating = bias + weighted_rating / weight
else:
prediction_rating = 0
# 영화 아이디 별로 예측 평가 점수 저장
prediction_dict[movie_id] = prediction_rating
영화 아이디별 예측 평가 점수를 내림차순으로 정렬
prediction = pd.Series(prediction_dict).sort_values(ascending=False)
예측 평가 점수 상위 10개의 영화 아이디 추출
recommend = prediction[:10].index
movies.loc[recommend]
| title | |
|---|---|
| movieId | |
| 163949 | The Beatles: Eight Days a Week - The Touring Y... |
| 3774 | House Party 2 |
| 3785 | Scary Movie |
| 3784 | The Kid |
| 3783 | Croupier |
| 3780 | Rocketship X-M |
| 3777 | Nekromantik |
| 3775 | Make Mine Music |
| 3773 | House Party |
| 3787 | Shower |
'Machine Learning > Recommendation' 카테고리의 다른 글
| Matrix Factorization 실습 (0) | 2024.03.27 |
|---|---|
| TF-IDF 실습 (0) | 2024.03.27 |
| 유사도 함수 실습 (0) | 2024.03.27 |
