diff --git "a/Week16_\341\204\207\341\205\251\341\206\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\213\341\205\265\341\204\214\341\205\242\341\204\205\341\205\265\341\206\253.ipynb" "b/Week16_\341\204\207\341\205\251\341\206\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\213\341\205\265\341\204\214\341\205\242\341\204\205\341\205\265\341\206\253.ipynb" new file mode 100644 index 0000000..e7e373f --- /dev/null +++ "b/Week16_\341\204\207\341\205\251\341\206\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\213\341\205\265\341\204\214\341\205\242\341\204\205\341\205\265\341\206\253.ipynb" @@ -0,0 +1,4874 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "10411f36-0452-4cf9-9bd2-b1e4f12b4aee", + "metadata": {}, + "source": [ + "# 9-5. 콘텐츠 기반 필터링 실습 - TMDB 5000 영화 데이터 세트" + ] + }, + { + "cell_type": "markdown", + "id": "6d78094b-3846-4c87-93e8-95f0a304bcaa", + "metadata": {}, + "source": [ + "- 콘텐츠 기반 필터링이란? 사용자가 특정 영화를 감상하고 그 영화를 좋아했다면 그 영화와 비슷한 특성/속성, 구성 요소 등을 가진 다른 영화를 추천하는 것 >> 영화/상품/서비스 간의 유사성을 판단하는 기준이 이를 구성하는 다양한 컨텐츠(장르, 감독, 배우, 평점, 키워드, 영화 설명)를 기반으로 하는 방식\n", + "- 여기서는 영화 장르 속성을 기반으로 ㄱㄱ\n", + "- 장르 칼럼 값의 유사도를 비교하고 높은 평점의 영화 추천하기" + ] + }, + { + "cell_type": "markdown", + "id": "10cc3342-b1f8-4347-930c-3a44bf73aacb", + "metadata": {}, + "source": [ + "### 1. 데이터 로딩 및 가공" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9b6e996-4878-45cb-8506-a78f9bec18e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4803, 20)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
budgetgenreshomepageidkeywordsoriginal_languageoriginal_titleoverviewpopularityproduction_companiesproduction_countriesrelease_daterevenueruntimespoken_languagesstatustaglinetitlevote_averagevote_count
0237000000[{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"nam...http://www.avatarmovie.com/19995[{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\":...enAvatarIn the 22nd century, a paraplegic Marine is di...150.437577[{\"name\": \"Ingenious Film Partners\", \"id\": 289...[{\"iso_3166_1\": \"US\", \"name\": \"United States o...2009-12-102787965087162.0[{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso...ReleasedEnter the World of Pandora.Avatar7.211800
1300000000[{\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"...http://disney.go.com/disneypictures/pirates/285[{\"id\": 270, \"name\": \"ocean\"}, {\"id\": 726, \"na...enPirates of the Caribbean: At World's EndCaptain Barbossa, long believed to be dead, ha...139.082615[{\"name\": \"Walt Disney Pictures\", \"id\": 2}, {\"...[{\"iso_3166_1\": \"US\", \"name\": \"United States o...2007-05-19961000000169.0[{\"iso_639_1\": \"en\", \"name\": \"English\"}]ReleasedAt the end of the world, the adventure begins.Pirates of the Caribbean: At World's End6.94500
2245000000[{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"nam...http://www.sonypictures.com/movies/spectre/206647[{\"id\": 470, \"name\": \"spy\"}, {\"id\": 818, \"name...enSpectreA cryptic message from Bond’s past sends him o...107.376788[{\"name\": \"Columbia Pictures\", \"id\": 5}, {\"nam...[{\"iso_3166_1\": \"GB\", \"name\": \"United Kingdom\"...2015-10-26880674609148.0[{\"iso_639_1\": \"fr\", \"name\": \"Fran\\u00e7ais\"},...ReleasedA Plan No One EscapesSpectre6.34466
\n", + "
" + ], + "text/plain": [ + " budget ... vote_count\n", + "0 237000000 ... 11800\n", + "1 300000000 ... 4500\n", + "2 245000000 ... 4466\n", + "\n", + "[3 rows x 20 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import warnings; warnings.filterwarnings('ignore')\n", + "\n", + "movies=pd.read_csv('/Users/bluecloud/Documents/대학/유런/데이터셋/tmdb_5000/tmdb_5000_movies.csv')\n", + "print(movies.shape)\n", + "movies.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "a03381a3-f472-4a7c-a48e-08811ed0ccf8", + "metadata": {}, + "source": [ + ">> 1. 콘텐츠 기반 필터링 추천 분석에 사용할 주요 칼럼만 추출하기\n", + ">> ex) id, title, genres, vote_average, vote_count, popularity, keywords, overview\n", + ">> 2-1. genres, keywords의 딕셔너리 형태 확인해보기" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "edd74642-8a71-4133-8781-12a06dc49487", + "metadata": {}, + "outputs": [], + "source": [ + "movies_df=movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0ebd4a16-09bd-40f6-a1a2-6397492b4b59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
genreskeywords
0[{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {...[{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp...
\n", + "
" + ], + "text/plain": [ + " genres keywords\n", + "0 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp..." + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.set_option('max_colwidth',100)\n", + "movies_df[['genres','keywords']][:1]" + ] + }, + { + "cell_type": "markdown", + "id": "9b877156-d824-4c7c-a466-2e00bf6d5273", + "metadata": {}, + "source": [ + ">> 2-2. 딕셔너리 키 name으로 추출하기\n", + ">> 개별 장르, 키워드를 파이썬 객체로 추출하기 literal_eval()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d8ae9d85-4fd4-43ae-a0a4-8af74ec80b15", + "metadata": {}, + "outputs": [], + "source": [ + "from ast import literal_eval\n", + "movies_df['genres']=movies_df['genres'].apply(literal_eval)\n", + "movies_df['keywords']=movies_df['keywords'].apply(literal_eval)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0a2f7e61-812b-41ef-a987-1fac28bd2604", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
genreskeywords
0[Action, Adventure, Fantasy, Science Fiction][culture clash, future, space war, space colony, society, space travel, futuristic, romance, spa...
\n", + "
" + ], + "text/plain": [ + " genres keywords\n", + "0 [Action, Adventure, Fantasy, Science Fiction] [culture clash, future, space war, space colony, society, space travel, futuristic, romance, spa..." + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 리스트 내 여러 개의 딕셔너리의 'name'키에 해당하는 값을 찾아 리스트 객체로 변환하기\n", + "movies_df['genres']=movies_df['genres'].apply(lambda x:[y['name'] for y in x])\n", + "movies_df['keywords']=movies_df['keywords'].apply(lambda x:[y['name'] for y in x])\n", + "movies_df[['genres','keywords']][:1]" + ] + }, + { + "cell_type": "markdown", + "id": "8b38f99e-d2c9-4c4e-a936-b84062a73e25", + "metadata": {}, + "source": [ + "### 2. 장르 콘텐츠 유사도 측정" + ] + }, + { + "cell_type": "markdown", + "id": "5c2fa149-d8a2-407e-93ec-eb45ff398dfe", + "metadata": {}, + "source": [ + "- 영화 A와 B의 장르가 여러개일 때 어떻게 유사도 측정?\n", + "- (1) genres를 문자열로 변경 (2) CountVectorizer로 피처 벡터화하기 (3) 행렬 데이터 값을 코사인 유사도로 비교\n", + "- !! 리스트 객체 값으로 구성된 genres 칼럼을 apply(lamnda x:('').join(리스트 객체))를 적용해 공백 문자로 구분하는 문자열로 변환하기" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "198c7e3c-687c-42b2-9fa4-aac295badb28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4803, 276)\n" + ] + } + ], + "source": [ + "from sklearn.feature_extraction.text import CountVectorizer\n", + "\n", + "# CountVectorizer를 적용하기 위해 공백문자로 word 단위가 구분되는 문자열로 변환\n", + "movies_df['genres_literal'] = movies_df['genres'].apply(lambda x : (' ').join(x))\n", + "count_vect = CountVectorizer(min_df=0.0, ngram_range=(1,2))\n", + "genre_mat = count_vect.fit_transform(movies_df['genres_literal'])\n", + "print(genre_mat.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "d8ed5270-f147-487d-9a49-3abe61849dfc", + "metadata": {}, + "source": [ + "- 사이킷런의 cosine_similarity() : 기준 행과 비교 대상 행간의 코사인 유사도를 행렬 형태로 반환" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "47664d99-9809-4243-8dea-091e8e553abc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4803, 4803)\n", + "[[1. 0.59628479 0.4472136 ... 0. 0. 0. ]]\n" + ] + } + ], + "source": [ + "from sklearn.metrics.pairwise import cosine_similarity\n", + "\n", + "genre_sim=cosine_similarity(genre_mat,genre_mat)\n", + "print(genre_sim.shape)\n", + "print(genre_sim[:1])" + ] + }, + { + "cell_type": "markdown", + "id": "943203df-209d-43d3-8a25-86a1b7dd513d", + "metadata": {}, + "source": [ + ">> cosine_similarity() 호출로 생성된 genre_sim 객체는 movies_df의 genre_literal 칼럼을 피처 벡터화한 행렬(genre_mat) 데이터의 행(레코드)별 유사도 정보를 가짐\n", + ">> movies_df DataFrame의 행별 장르 유사도 값을 의미\n", + ">> 장르 기준으로 콘텐츠 기반 필터링 수행시 개별 record에 대해 장르 유사도가 높은 순으로 다른 레코드 추출할 때 genre_sim 객체를 이용" + ] + }, + { + "cell_type": "markdown", + "id": "b07cc655-d0b6-4f7c-946f-9248c39ae625", + "metadata": {}, + "source": [ + "- argsort()[:,::-1]는 유사도가 높은 순으로 정리가 되는데..추출되는건 유사도 값이 아닌 비교 대상의 위치 인덱스 값을 가져옴" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "bfd14519-1f90-4bb3-94ff-182d95e2e658", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 3494 813 ... 3038 3037 2401]]\n" + ] + } + ], + "source": [ + "genre_sim_sorted_ind=genre_sim.argsort()[:,::-1]\n", + "print(genre_sim_sorted_ind[:1])" + ] + }, + { + "cell_type": "markdown", + "id": "97230838-6e74-41f0-a973-6f7565014039", + "metadata": {}, + "source": [ + ">> 위의 숫자들은 n번 레코드를 의미" + ] + }, + { + "cell_type": "markdown", + "id": "7be0e130-7120-464e-8660-3cf0ca545453", + "metadata": {}, + "source": [ + "### 3. 장르 콘텐츠 필터링을 이용한 영화 추천" + ] + }, + { + "cell_type": "markdown", + "id": "9c909446-6dbc-4ba6-9b77-38a9f586a9ab", + "metadata": {}, + "source": [ + "- 장르 유사도에 따라 영화 추천하는 함수\n", + "- 인자로 기반데이터 movies_df, 장르 코사인 유사도 인덱스 genre_sim_sorted_ind, 영화 제목, 추천 영화 정보 df" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "c4b6d6f5-32d6-47d5-85d9-2d549ea28ce2", + "metadata": {}, + "outputs": [], + "source": [ + "def find_sim_movie(df,sorted_ind,title_name,top_n=10):\n", + " #인자로 입력된 movies_df에서 'title' 칼럼이 입력된 title_name값인 DataFrame 추출\n", + " title_movie=df[df['title']==title_name]\n", + " #title_named을 가진 df의 index 객체를 ndarray로 반환하고 sorted_ind 인자로 입력된 genre_sim_sorted_ind 객체에서 유사도 순으로 top_n개의 index 추출\n", + " title_index=title_movie.index.values\n", + " similar_indexes=sorted_ind[title_index,:(top_n)]\n", + " # 추출된 top_n index 출력, top_n index는 2차원 데이터\n", + " #dataframe에서 index로 사용하기 위해 1차원 array로 변경\n", + " print(similar_indexes)\n", + " similar_indexes=similar_indexes.reshape(-1)\n", + " return df.iloc[similar_indexes]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9cfa8e1a-95d8-45da-b951-a42e51f47ac1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1370 4041 3337 1847 3378 4217 2839 281 588 3866]]\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
titlevote_average
1370216.5
4041This Is England7.4
3337The Godfather8.4
1847GoodFellas8.2
3378Auto Focus6.1
4217Kids6.8
2839Rounders6.9
281American Gangster7.4
588Wall Street: Money Never Sleeps5.8
3866City of God8.1
\n", + "
" + ], + "text/plain": [ + " title vote_average\n", + "1370 21 6.5\n", + "4041 This Is England 7.4\n", + "3337 The Godfather 8.4\n", + "1847 GoodFellas 8.2\n", + "3378 Auto Focus 6.1\n", + "4217 Kids 6.8\n", + "2839 Rounders 6.9\n", + "281 American Gangster 7.4\n", + "588 Wall Street: Money Never Sleeps 5.8\n", + "3866 City of God 8.1" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 영화 '대부'와 장르별 유사한 영화 10개 추천하기\n", + "similar_movies=find_sim_movie(movies_df,genre_sim_sorted_ind,'The Godfather',10)\n", + "similar_movies[['title', 'vote_average']]" + ] + }, + { + "cell_type": "markdown", + "id": "9c839516-258e-47f0-9b87-52f7c10e25e4", + "metadata": {}, + "source": [ + "- 영화 평점 순으로 세워 살펴보기" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "a0bce6a4-300e-4b6f-904c-c277cdc9f59f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
titlevote_averagevote_count
3519Stiff Upper Lips10.01
4247Me You and Five Bucks10.02
4045Dancer, Texas Pop. 8110.01
4662Little Big Top10.01
3992Sardaarji9.52
2386One Man's Hero9.32
2970There Goes My Baby8.52
1881The Shawshank Redemption8.58205
2796The Prisoner of Zenda8.411
3337The Godfather8.45893
\n", + "
" + ], + "text/plain": [ + " title vote_average vote_count\n", + "3519 Stiff Upper Lips 10.0 1\n", + "4247 Me You and Five Bucks 10.0 2\n", + "4045 Dancer, Texas Pop. 81 10.0 1\n", + "4662 Little Big Top 10.0 1\n", + "3992 Sardaarji 9.5 2\n", + "2386 One Man's Hero 9.3 2\n", + "2970 There Goes My Baby 8.5 2\n", + "1881 The Shawshank Redemption 8.5 8205\n", + "2796 The Prisoner of Zenda 8.4 11\n", + "3337 The Godfather 8.4 5893" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies_df[['title', 'vote_average','vote_count']].sort_values('vote_average',ascending=False)[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "71aba9e7-b1fb-4656-86e4-a728cb52d19f", + "metadata": {}, + "source": [ + ">> 평가 횟수가 적은 영화들이 상위에 랭크되어있음 >> 왜곡된 평점 데이터\n", + ">> 평가 횟수에 대한 가중치가 부여된 평점 방식 사용\n", + ">> V : vote_count / R : vote_average / C : 전체 영화의 평균 평점\n", + ">> m 값이 높아지면 평점 투표 횟수에 더 많은 가중치가 부여됨" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "52f631ee-ffe0-4a51-8f99-071e9c02c70e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C: 6.092 m: 370.2\n" + ] + } + ], + "source": [ + "C=movies_df['vote_average'].mean()\n", + "# 전체 투표에서 상위 60%에 해당하는 횟수를 기준으로 투표 횟수에 대한 가중치 정하기\n", + "m=movies_df['vote_count'].quantile(0.6)\n", + "print('C:',round(C,3),'m:',round(m,3))" + ] + }, + { + "cell_type": "markdown", + "id": "649a0d16-5a22-4713-aca6-96999de5763e", + "metadata": {}, + "source": [ + "- 기존 평점을 새로운 가중 평점으로 변경하는 함수생성 >> df의 레코드를 인자로 받아 레코드별 가중 평점 반환\n", + "- 새로운 평점 정보 만들기" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "7fe8d541-a751-43c0-861a-10f2b9cc9e9b", + "metadata": {}, + "outputs": [], + "source": [ + "percentile=0.6\n", + "m=movies_df['vote_count'].quantile(percentile)\n", + "C=movies_df['vote_average'].mean()\n", + "\n", + "def weighted_vote_average(record):\n", + " v=record['vote_count']\n", + " R=record['vote_average']\n", + " return ((v/(v+m))*R)+((m/(v+m))*C)\n", + " \n", + "movies_df['weighted_vote']=movies_df.apply(weighted_vote_average,axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ba98f6bb-9236-4945-83bc-e830d6eae881", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
titlevote_averageweighted_votevote_count
1881The Shawshank Redemption8.58.3960528205
3337The Godfather8.48.2635915893
662Fight Club8.38.2164559413
3232Pulp Fiction8.38.2071028428
65The Dark Knight8.28.13693012002
1818Schindler's List8.38.1260694329
3865Whiplash8.38.1232484254
809Forrest Gump8.28.1059547927
2294Spirited Away8.38.1058673840
2731The Godfather: Part II8.38.0795863338
\n", + "
" + ], + "text/plain": [ + " title vote_average weighted_vote vote_count\n", + "1881 The Shawshank Redemption 8.5 8.396052 8205\n", + "3337 The Godfather 8.4 8.263591 5893\n", + "662 Fight Club 8.3 8.216455 9413\n", + "3232 Pulp Fiction 8.3 8.207102 8428\n", + "65 The Dark Knight 8.2 8.136930 12002\n", + "1818 Schindler's List 8.3 8.126069 4329\n", + "3865 Whiplash 8.3 8.123248 4254\n", + "809 Forrest Gump 8.2 8.105954 7927\n", + "2294 Spirited Away 8.3 8.105867 3840\n", + "2731 The Godfather: Part II 8.3 8.079586 3338" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies_df[['title','vote_average','weighted_vote','vote_count']].sort_values('weighted_vote',ascending=False)[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "5a9789a2-c9c3-4544-aa0f-afedca7e57f0", + "metadata": {}, + "source": [ + "- 장르 유사성이 높은 영화를 top_n의 2배수만큼 후보군으로 선정하고 weighted_vote 칼럼 값이 높은 순으로 top_n만큼 추출하는 함수" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "c9ec791a-0dbb-490a-a4f0-5185c4fefc7f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
titlevote_averageweighted_vote
1881The Shawshank Redemption8.58.396052
2731The Godfather: Part II8.38.079586
1847GoodFellas8.27.976937
3866City of God8.17.759693
1663Once Upon a Time in America8.27.657811
892Casino7.87.423040
281American Gangster7.47.141396
4041This Is England7.46.739664
1149American Hustle6.86.717525
1243Mean Streets7.26.626569
\n", + "
" + ], + "text/plain": [ + " title vote_average weighted_vote\n", + "1881 The Shawshank Redemption 8.5 8.396052\n", + "2731 The Godfather: Part II 8.3 8.079586\n", + "1847 GoodFellas 8.2 7.976937\n", + "3866 City of God 8.1 7.759693\n", + "1663 Once Upon a Time in America 8.2 7.657811\n", + "892 Casino 7.8 7.423040\n", + "281 American Gangster 7.4 7.141396\n", + "4041 This Is England 7.4 6.739664\n", + "1149 American Hustle 6.8 6.717525\n", + "1243 Mean Streets 7.2 6.626569" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def find_sim_movie(df,sorted_ind,title_name,top_n=10):\n", + " title_movie=df[df['title']==title_name]\n", + " title_index=title_movie.index.values\n", + " #top_n의 2배수만큼 후보군으로 선정\n", + " similar_indexes=sorted_ind[title_index,:(top_n*2)]\n", + " similar_indexes=similar_indexes.reshape(-1)\n", + " #기준 영화 인덱스 제외\n", + " similar_indexes=similar_indexes[similar_indexes!=title_index]\n", + " #top_n의 2배에 해당하는 후보군에서 weighted_vote가 높은 순으로 top_n만큼 추출\n", + " return df.iloc[similar_indexes].sort_values('weighted_vote',ascending=False)[:top_n]\n", + "similar_movies=find_sim_movie(movies_df,genre_sim_sorted_ind,'The Godfather',10)\n", + "similar_movies[['title', 'vote_average','weighted_vote']]" + ] + }, + { + "cell_type": "markdown", + "id": "77d6666e-19ac-4979-9841-77464e08814c", + "metadata": {}, + "source": [ + "# 9-6. 아이템 기반 최근접 이웃 협업 필터링 실습" + ] + }, + { + "cell_type": "markdown", + "id": "4fdafe32-3baf-4e1f-97c0-e091122b2225", + "metadata": {}, + "source": [ + "- 추천 정확도가 더 뛰어난 아이템 기반의 협업 필터링\n", + "- Grouplens 사이트에서 만든 MovieLens 데이터 세트" + ] + }, + { + "cell_type": "markdown", + "id": "d07965ae-8367-4482-9d1f-5849c27511c1", + "metadata": {}, + "source": [ + "### 1. 데이터 가공 및 변환" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "aaadf180-1983-4a69-bc96-e747134b7b3c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(9742, 3)\n", + "(100836, 4)\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "movies=pd.read_csv('/Users/bluecloud/Documents/대학/유런/데이터셋/ml-latest-small/movies.csv')\n", + "ratings=pd.read_csv('/Users/bluecloud/Documents/대학/유런/데이터셋/ml-latest-small/ratings.csv')\n", + "print(movies.shape)\n", + "print(ratings.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ce929228-fdd2-469f-a9f2-7d5053a931fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
movieIdtitlegenres
01Toy Story (1995)Adventure|Animation|Children|Comedy|Fantasy
12Jumanji (1995)Adventure|Children|Fantasy
23Grumpier Old Men (1995)Comedy|Romance
\n", + "
" + ], + "text/plain": [ + " movieId ... genres\n", + "0 1 ... Adventure|Animation|Children|Comedy|Fantasy\n", + "1 2 ... Adventure|Children|Fantasy\n", + "2 3 ... Comedy|Romance\n", + "\n", + "[3 rows x 3 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "72448116-b38d-468a-9994-139d48ae97a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
userIdmovieIdratingtimestamp
0114.0964982703
1134.0964981247
2164.0964982224
\n", + "
" + ], + "text/plain": [ + " userId movieId rating timestamp\n", + "0 1 1 4.0 964982703\n", + "1 1 3 4.0 964981247\n", + "2 1 6 4.0 964982224" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "c6eafade-02c7-4fb6-b60b-83fab9edef75", + "metadata": {}, + "source": [ + "movies.csv : 영화에 대한 title과 genres를 가지고 있음\n", + "ratings.csv : 사용자별로 영화에 대한 평점을 매김\n", + ">> 사용자 로우 - 영화 평점 칼럼 데이터로 변환 필요 : pivot_table('테이블값',index='',columns='') 함수" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "856012ba-566b-4c44-b0dd-3b1f2c28a48f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
movieId12345678910111213141516171819202122232425262728293031323436383940414243...185135185435185473185585186587187031187541187593187595187717188189188301188675188751188797188833189043189111189333189381189547189713190183190207190209190213190215190219190221191005193565193567193571193573193579193581193583193585193587193609
userId
14.0NaN4.0NaNNaN4.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
3NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN0.5NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

3 rows × 9724 columns

\n", + "
" + ], + "text/plain": [ + "movieId 1 2 3 4 ... 193583 193585 193587 193609\n", + "userId ... \n", + "1 4.0 NaN 4.0 NaN ... NaN NaN NaN NaN\n", + "2 NaN NaN NaN NaN ... NaN NaN NaN NaN\n", + "3 NaN NaN NaN NaN ... NaN NaN NaN NaN\n", + "\n", + "[3 rows x 9724 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings=ratings[['userId','movieId','rating']]\n", + "ratings_matrix=ratings.pivot_table('rating',index='userId',columns='movieId')\n", + "ratings_matrix.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "1cd0e9b1-5412-4e6c-b004-79c689ce29d5", + "metadata": {}, + "source": [ + "- movieId는 가독성이 떨어지므로 movies.csv에서 movieId>>title로 변경 & pivot_table()에서 columns을 title로\n", + "- NaN은 0으로" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3d945bd1-6e2d-462c-a4d9-b6d2fba3181f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
title'71 (2014)'Hellboy': The Seeds of Creation (2004)'Round Midnight (1986)'Salem's Lot (2004)'Til There Was You (1997)'Tis the Season for Love (2015)'burbs, The (1989)'night Mother (1986)(500) Days of Summer (2009)*batteries not included (1987)...All the Marbles (1981)...And Justice for All (1979)00 Schneider - Jagd auf Nihil Baxter (1994)1-900 (06) (1994)10 (1979)10 Cent Pistol (2015)10 Cloverfield Lane (2016)10 Items or Less (2006)10 Things I Hate About You (1999)10 Years (2011)10,000 BC (2008)100 Girls (2000)100 Streets (2016)101 Dalmatians (1996)101 Dalmatians (One Hundred and One Dalmatians) (1961)101 Dalmatians II: Patch's London Adventure (2003)101 Reykjavik (101 Reykjavík) (2000)102 Dalmatians (2000)10th & Wolf (2006)10th Kingdom, The (2000)10th Victim, The (La decima vittima) (1965)11'09\"01 - September 11 (2002)11:14 (2003)11th Hour, The (2007)12 Angry Men (1957)12 Angry Men (1997)12 Chairs (1971)12 Chairs (1976)12 Rounds (2009)12 Years a Slave (2013)...Zathura (2005)Zatoichi and the Chest of Gold (Zatôichi senryô-kubi) (Zatôichi 6) (1964)Zazie dans le métro (1960)Zebraman (2004)Zed & Two Noughts, A (1985)Zeitgeist: Addendum (2008)Zeitgeist: Moving Forward (2011)Zeitgeist: The Movie (2007)Zelary (2003)Zelig (1983)Zero Dark Thirty (2012)Zero Effect (1998)Zero Theorem, The (2013)Zero de conduite (Zero for Conduct) (Zéro de conduite: Jeunes diables au collège) (1933)Zeus and Roxanne (1997)Zipper (2015)Zodiac (2007)Zombeavers (2014)Zombie (a.k.a. Zombie 2: The Dead Are Among Us) (Zombi 2) (1979)Zombie Strippers! (2008)Zombieland (2009)Zone 39 (1997)Zone, The (La Zona) (2007)Zookeeper (2011)Zoolander (2001)Zoolander 2 (2016)Zoom (2006)Zoom (2015)Zootopia (2016)Zulu (1964)Zulu (2013)[REC] (2007)[REC]² (2009)[REC]³ 3 Génesis (2012)anohana: The Flower We Saw That Day - The Movie (2013)eXistenZ (1999)xXx (2002)xXx: State of the Union (2005)¡Three Amigos! (1986)À nous la liberté (Freedom for Us) (1931)
userId
10.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.04.00.0
20.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.03.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
30.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) ... À nous la liberté (Freedom for Us) (1931)\n", + "userId ... \n", + "1 0.0 ... 0.0\n", + "2 0.0 ... 0.0\n", + "3 0.0 ... 0.0\n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#title 칼럼을 얻기 위해 movies\n", + "ratings_movies=pd.merge(ratings,movies,on='movieId')\n", + "#columns을 title로\n", + "ratings_matrix=ratings_movies.pivot_table('rating',index='userId',columns='title')\n", + "#NaN 값을 모두 0으로 변환\n", + "ratings_matrix=ratings_matrix.fillna(0)\n", + "ratings_matrix.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "9f9adc35-6812-43c3-8c4e-2dcb6a897aab", + "metadata": {}, + "source": [ + "### 2. 영화 간 유사도 산출" + ] + }, + { + "cell_type": "markdown", + "id": "a075fc2f-ee8a-48ff-9dd9-f4d7cc93dda2", + "metadata": {}, + "source": [ + "- ratings_matrix를 이용해 영화 간 유사도 측정 >> cosine_similarity()로 유사도 산출 but 위의 사례는 사용자 간의 유사도가 산출됨\n", + " >> 행과 열을 반대로 transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "4a0bd0e8-69a5-4915-b489-28e6e12f51a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
userId12345678910111213141516171819202122232425262728293031323334353637383940...571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
title
'71 (2014)0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.04.0
'Hellboy': The Seeds of Creation (2004)0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
'Round Midnight (1986)0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
\n", + "

3 rows × 610 columns

\n", + "
" + ], + "text/plain": [ + "userId 1 2 3 4 ... 607 608 609 610\n", + "title ... \n", + "'71 (2014) 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 4.0\n", + "'Hellboy': The Seeds of Creation (2004) 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0\n", + "'Round Midnight (1986) 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0\n", + "\n", + "[3 rows x 610 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sklearn.metrics.pairwise import cosine_similarity\n", + "\n", + "ratings_matrix_T=ratings_matrix.transpose()\n", + "ratings_matrix_T.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8bdd6a61-fcf6-4df4-acc0-bafb03a17ae4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(9719, 9719)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
title'71 (2014)'Hellboy': The Seeds of Creation (2004)'Round Midnight (1986)'Salem's Lot (2004)'Til There Was You (1997)'Tis the Season for Love (2015)'burbs, The (1989)'night Mother (1986)(500) Days of Summer (2009)*batteries not included (1987)...All the Marbles (1981)...And Justice for All (1979)00 Schneider - Jagd auf Nihil Baxter (1994)1-900 (06) (1994)10 (1979)10 Cent Pistol (2015)10 Cloverfield Lane (2016)10 Items or Less (2006)10 Things I Hate About You (1999)10 Years (2011)10,000 BC (2008)100 Girls (2000)100 Streets (2016)101 Dalmatians (1996)101 Dalmatians (One Hundred and One Dalmatians) (1961)101 Dalmatians II: Patch's London Adventure (2003)101 Reykjavik (101 Reykjavík) (2000)102 Dalmatians (2000)10th & Wolf (2006)10th Kingdom, The (2000)10th Victim, The (La decima vittima) (1965)11'09\"01 - September 11 (2002)11:14 (2003)11th Hour, The (2007)12 Angry Men (1957)12 Angry Men (1997)12 Chairs (1971)12 Chairs (1976)12 Rounds (2009)12 Years a Slave (2013)...Zathura (2005)Zatoichi and the Chest of Gold (Zatôichi senryô-kubi) (Zatôichi 6) (1964)Zazie dans le métro (1960)Zebraman (2004)Zed & Two Noughts, A (1985)Zeitgeist: Addendum (2008)Zeitgeist: Moving Forward (2011)Zeitgeist: The Movie (2007)Zelary (2003)Zelig (1983)Zero Dark Thirty (2012)Zero Effect (1998)Zero Theorem, The (2013)Zero de conduite (Zero for Conduct) (Zéro de conduite: Jeunes diables au collège) (1933)Zeus and Roxanne (1997)Zipper (2015)Zodiac (2007)Zombeavers (2014)Zombie (a.k.a. Zombie 2: The Dead Are Among Us) (Zombi 2) (1979)Zombie Strippers! (2008)Zombieland (2009)Zone 39 (1997)Zone, The (La Zona) (2007)Zookeeper (2011)Zoolander (2001)Zoolander 2 (2016)Zoom (2006)Zoom (2015)Zootopia (2016)Zulu (1964)Zulu (2013)[REC] (2007)[REC]² (2009)[REC]³ 3 Génesis (2012)anohana: The Flower We Saw That Day - The Movie (2013)eXistenZ (1999)xXx (2002)xXx: State of the Union (2005)¡Three Amigos! (1986)À nous la liberté (Freedom for Us) (1931)
title
'71 (2014)1.00.0000000.0000000.00.00.00.0000000.00.1416530.00.0000000.0000000.00.00.00.00.2851690.00.00.00.00.00.00.00.0000000.00.00.00.00.00.0000000.00.00.00.0000000.00.00.00.00.0...0.00.00.00.80.00.00.00.00.00.00.00.00.655610.00.00.00.2128140.9191450.00.00.1209960.00.00.00.1492010.00.00.00.1780420.00.00.3420550.5433050.7071070.00.00.1394310.3273270.00.0
'Hellboy': The Seeds of Creation (2004)0.01.0000000.7071070.00.00.00.0000000.00.0000000.00.0000000.7155420.00.00.00.00.0000000.00.00.00.00.00.00.00.1502690.00.00.00.00.00.0000000.00.00.00.1241090.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.000000.00.00.00.1489700.0000000.00.00.0000000.00.00.00.0000000.00.00.00.0000000.00.00.0000000.0000000.0000000.00.00.0000000.0000000.00.0
'Round Midnight (1986)0.00.7071071.0000000.00.00.00.1767770.00.0000000.00.7071070.5059640.00.00.00.00.0000000.00.00.00.00.00.00.00.1062560.00.00.00.00.00.7071070.00.00.00.1974570.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.00.00.00.000000.00.00.00.1053380.0000000.00.00.0000000.00.00.00.0000000.00.00.00.0000000.00.00.0000000.0000000.0000000.00.00.0000000.0000000.00.0
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) ... À nous la liberté (Freedom for Us) (1931)\n", + "title ... \n", + "'71 (2014) 1.0 ... 0.0\n", + "'Hellboy': The Seeds of Creation (2004) 0.0 ... 0.0\n", + "'Round Midnight (1986) 0.0 ... 0.0\n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 영화 간 유사도 산출\n", + "item_sim=cosine_similarity(ratings_matrix_T,ratings_matrix_T)\n", + "\n", + "#cosine_similarity()로 반환된 넘파이 행렬을 영화명으로 매핑해 df로 변환\n", + "item_sim_df=pd.DataFrame(data=item_sim,index=ratings_matrix.columns,columns=ratings_matrix.columns)\n", + "print(item_sim_df.shape)\n", + "item_sim_df.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "5ccc90ba-8133-4548-ba7e-403f95442065", + "metadata": {}, + "source": [ + ">> 영화의 유사도 행렬" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "7b73fea0-e41d-4763-8177-24358cb6eb39", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "title\n", + "Godfather, The (1972) 1.000000\n", + "Godfather: Part II, The (1974) 0.821773\n", + "Goodfellas (1990) 0.664841\n", + "One Flew Over the Cuckoo's Nest (1975) 0.620536\n", + "Star Wars: Episode IV - A New Hope (1977) 0.595317\n", + "Fargo (1996) 0.588614\n", + "Name: Godfather, The (1972), dtype: float64" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# GodFather과 유사한 영화 6개 추출하기\n", + "item_sim_df[\"Godfather, The (1972)\"].sort_values(ascending=False)[:6]" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c8dcffae-245a-4188-a80c-2fdc79b3ef07", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "title\n", + "Dark Knight, The (2008) 0.727263\n", + "Inglourious Basterds (2009) 0.646103\n", + "Shutter Island (2010) 0.617736\n", + "Dark Knight Rises, The (2012) 0.617504\n", + "Fight Club (1999) 0.615417\n", + "Name: Inception (2010), dtype: float64" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Inception과 유사한 영화 추출하기(자기 자신 제외)\n", + "item_sim_df[\"Inception (2010)\"].sort_values(ascending=False)[1:6]" + ] + }, + { + "cell_type": "markdown", + "id": "fc485d26-2c2f-49c8-85e7-959bf90cc8cd", + "metadata": {}, + "source": [ + "### 3. 아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천" + ] + }, + { + "cell_type": "markdown", + "id": "882eb722-d198-4892-a8ff-acc968fb09ee", + "metadata": {}, + "source": [ + "- 영화 유사도 데이터 + 개인에게 최적화된 영화 추천 구현\n", + "- !아직 관람하지 않은 영화 추천\n", + "- 예측 평점 식은 교재에서 확인" + ] + }, + { + "cell_type": "markdown", + "id": "a70af988-4f68-4f73-84d6-afbd5ef0d299", + "metadata": {}, + "source": [ + "- 함수 predict_rating() : 영화 간 유사도 df인 item_sim_df와 사용자-영화 평점 df ratings_matrix 변수를 활용하여 사용자별 최적화된 평점 스코어를 예측하는 함수 >> 둘다 넘파이 행렬로 변환\n", + "- 모든 영화와의 코사인 유사도를 벡터 내적 곱(dot)하여 정규화를 위해 유사도 벡터 합으로 나눈다" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7207c974-50a8-4075-af01-c79cdbcfc024", + "metadata": {}, + "outputs": [], + "source": [ + "def predict_rating(ratings_arr,item_sim_arr):\n", + " ratings_pred=ratings_arr.dot(item_sim_arr)/np.array([np.abs(item_sim_arr).sum(axis=1)])\n", + " return ratings_pred" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b6bf2e04-68d2-4bdc-8837-c0b0e2eecff2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
title'71 (2014)'Hellboy': The Seeds of Creation (2004)'Round Midnight (1986)'Salem's Lot (2004)'Til There Was You (1997)'Tis the Season for Love (2015)'burbs, The (1989)'night Mother (1986)(500) Days of Summer (2009)*batteries not included (1987)...All the Marbles (1981)...And Justice for All (1979)00 Schneider - Jagd auf Nihil Baxter (1994)1-900 (06) (1994)10 (1979)10 Cent Pistol (2015)10 Cloverfield Lane (2016)10 Items or Less (2006)10 Things I Hate About You (1999)10 Years (2011)10,000 BC (2008)100 Girls (2000)100 Streets (2016)101 Dalmatians (1996)101 Dalmatians (One Hundred and One Dalmatians) (1961)101 Dalmatians II: Patch's London Adventure (2003)101 Reykjavik (101 Reykjavík) (2000)102 Dalmatians (2000)10th & Wolf (2006)10th Kingdom, The (2000)10th Victim, The (La decima vittima) (1965)11'09\"01 - September 11 (2002)11:14 (2003)11th Hour, The (2007)12 Angry Men (1957)12 Angry Men (1997)12 Chairs (1971)12 Chairs (1976)12 Rounds (2009)12 Years a Slave (2013)...Zathura (2005)Zatoichi and the Chest of Gold (Zatôichi senryô-kubi) (Zatôichi 6) (1964)Zazie dans le métro (1960)Zebraman (2004)Zed & Two Noughts, A (1985)Zeitgeist: Addendum (2008)Zeitgeist: Moving Forward (2011)Zeitgeist: The Movie (2007)Zelary (2003)Zelig (1983)Zero Dark Thirty (2012)Zero Effect (1998)Zero Theorem, The (2013)Zero de conduite (Zero for Conduct) (Zéro de conduite: Jeunes diables au collège) (1933)Zeus and Roxanne (1997)Zipper (2015)Zodiac (2007)Zombeavers (2014)Zombie (a.k.a. Zombie 2: The Dead Are Among Us) (Zombi 2) (1979)Zombie Strippers! (2008)Zombieland (2009)Zone 39 (1997)Zone, The (La Zona) (2007)Zookeeper (2011)Zoolander (2001)Zoolander 2 (2016)Zoom (2006)Zoom (2015)Zootopia (2016)Zulu (1964)Zulu (2013)[REC] (2007)[REC]² (2009)[REC]³ 3 Génesis (2012)anohana: The Flower We Saw That Day - The Movie (2013)eXistenZ (1999)xXx (2002)xXx: State of the Union (2005)¡Three Amigos! (1986)À nous la liberté (Freedom for Us) (1931)
userId
10.0703450.5778550.3216960.2270550.2069580.1946150.2498830.1025420.1570840.1781970.1194020.1850260.2691990.5210310.1416830.1166230.1354410.2248850.2265280.1136080.1852770.3036380.1136080.2550400.2604460.3269680.3057690.1550310.3487170.1868700.1194020.0997560.2063310.3487170.2674070.2371280.0509470.0509470.2007470.156893...0.1865540.0509470.0404430.1211840.1784820.1044880.1044880.1108080.1025420.1758590.1791620.2316060.0934670.0943570.1126900.1136080.1642310.0863600.2772150.2627090.1803200.1126900.1116530.1301310.2483120.1320090.2859130.1136080.1558610.1559270.1136080.1817380.1339620.1285740.0061790.2120700.1929210.1360240.2929550.720347
20.0182600.0427440.0188610.0000000.0000000.0359950.0134130.0023140.0322130.0148630.0000000.0052200.0937220.0000000.0142960.0163980.0436850.0190040.0200710.0156400.0283490.0434770.0156400.0196340.0168930.0082510.0109190.0137110.0000000.0203000.0000000.0027260.0226390.0000000.0322680.0311300.0406990.0406990.0249500.043495...0.0212690.0406990.0306100.0197210.0022150.0233520.0233520.0284030.0023140.0067910.0331430.0109330.0188060.0035250.0114250.0156400.0309040.0172900.0192500.0394490.0388950.0114250.0354000.0381010.0341810.0267640.0000000.0156400.0379800.0068590.0156400.0208550.0201190.0157450.0499830.0148760.0216160.0245280.0175630.000000
30.0118840.0302790.0644370.0037620.0037490.0027220.0146250.0020850.0056660.0062720.0914130.0074830.0187100.0806260.0069950.0067660.0069880.0054270.0067430.0069230.0053890.0089430.0069230.0085590.0093330.0063160.0316520.0073760.0098320.0220560.0914130.0025480.0087620.0098320.0087730.0043790.0011170.0011170.0070070.005163...0.0088100.0011170.0000000.0107580.0073620.0037260.0037260.0044790.0020850.0055460.0066010.0100540.0080800.0034440.0056360.0069230.0073470.0100480.0099120.0105710.0071560.0056360.0035970.0012400.0081070.0066640.0066150.0069230.0061860.0062250.0069230.0116650.0118000.0122250.0000000.0081940.0070170.0092290.0104200.084501
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) ... À nous la liberté (Freedom for Us) (1931)\n", + "userId ... \n", + "1 0.070345 ... 0.720347\n", + "2 0.018260 ... 0.000000\n", + "3 0.011884 ... 0.084501\n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings_pred=predict_rating(ratings_matrix.values,item_sim_df.values)\n", + "ratings_pred_matrix=pd.DataFrame(data=ratings_pred,index=ratings_matrix.index,columns=ratings_matrix.columns)\n", + "ratings_pred_matrix.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "c4746079-fc89-4433-ad88-f82cf2d35af8", + "metadata": {}, + "source": [ + ">> 예측 평점이 사용자별 영화의 실제 평점과 코사인 유사도 내적한 값이어서 0에 해당했던 실제 영화 평점이 예측에서는 값이 부여되는 경우가 많아 실제 평점에 비해 작을 수 있다." + ] + }, + { + "cell_type": "markdown", + "id": "b9f7f20c-dadd-41c1-8860-d04dc18bef2b", + "metadata": {}, + "source": [ + "- 위의 예측 결과가 원래의 실 평저모가 얼마나 차이가 나는지 확인해보기 : MSE지표\n", + "- 실제와 예측 평점의 차이는 기존에 평점이 부여된 데이터에 대해서만 오차 정도 측정" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "aac19bc0-fb3f-422d-acbe-84653bb72602", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "아이템 기반 모든 최근접 이웃 MSE : 9.895354759094706\n" + ] + } + ], + "source": [ + "from sklearn.metrics import mean_squared_error\n", + "\n", + "# 사용자가 평점을 부여한 영화에 대해 MSE 구하기\n", + "def get_mse(pred,actual):\n", + " #평점이 있는 실제 영화만 추출\n", + " pred=pred[actual.nonzero()].flatten()\n", + " actual=actual[actual.nonzero()].flatten()\n", + " return mean_squared_error(pred,actual)\n", + "print('아이템 기반 모든 최근접 이웃 MSE : ',get_mse(ratings_pred,ratings_matrix.values))" + ] + }, + { + "cell_type": "markdown", + "id": "7484b29d-7d2d-4933-bde9-f4d5165fbb03", + "metadata": {}, + "source": [ + ">> mse를 감소시키는 방향으로 개선하기" + ] + }, + { + "cell_type": "markdown", + "id": "68606a78-001d-444e-b5fc-9a65b0a7d0fc", + "metadata": {}, + "source": [ + "- 기존의 predict_rating() : 해당 영화와 다른 모든 영화 간의 유사도 벡터 적용 >> 상대적 평점 예측 떨어짐\n", + "- 새로운 predict_rating_topsim() : 특정 영화와 비슷한 유사도를 갖는 영화(N값으로 개수 정하기)에 대하여 유사도 벡터 적용하기" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "3e41d380-b31a-4205-935b-53a4de13f2a0", + "metadata": {}, + "outputs": [], + "source": [ + "def predict_rating_topsim(ratings_arr,item_sim_arr,n=20):\n", + " #사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화\n", + " pred=np.zeros(ratings_arr.shape)\n", + " #사용자-아이템 평점 행렬 열 크기만큼 루프 수행\n", + " for col in range(ratings_arr.shape[1]):\n", + " #유사도 행렬에서 유사도 큰 순 n개 데이터 행렬의 인덱스 변환\n", + " top_n_items=[np.argsort(item_sim_arr[:,col])[:-n-1:-1]]\n", + " #개인화된 예측 평점을 계산\n", + " for row in range(ratings_arr.shape[0]):\n", + " pred[row, col] = item_sim_arr[col, :][top_n_items].dot(ratings_arr[row, :][top_n_items].T) \n", + " pred[row, col] /= np.sum(np.abs(item_sim_arr[col, :][top_n_items])) \n", + " return pred" + ] + }, + { + "cell_type": "markdown", + "id": "d5c3ab63-c5c4-4213-9834-863d79a194d7", + "metadata": {}, + "source": [ + "- 위의 함수로 예측 평점을 계산하고 실제 평점과 MSE 구해보기" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5d45d88e-0768-4a48-b8f5-a85ef40d07ce", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/2c/j3wmswps2r5gclvj1skrczqh0000gn/T/ipykernel_32545/4158240769.py:10: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n", + " pred[row, col] = item_sim_arr[col, :][top_n_items].dot(ratings_arr[row, :][top_n_items].T)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "아이템 기반 인접 TOP-20 이웃 MSE: 3.6949999176225483\n" + ] + } + ], + "source": [ + "ratings_pred = predict_rating_topsim(ratings_matrix.values , item_sim_df.values, n=20)\n", + "print('아이템 기반 인접 TOP-20 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))\n", + "\n", + "# 계산된 예측 평점 데이터는 DataFrame으로 재생성\n", + "ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,columns = ratings_matrix.columns)" + ] + }, + { + "cell_type": "markdown", + "id": "bdd9e4de-e63d-4852-ac71-a0daef71ff96", + "metadata": {}, + "source": [ + "- MSE가 많이 향상됨\n", + "- 특정 사용자에 대한 영화 추천 : userId 기준으로 입력" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "36bf3405-d297-42de-8354-bd2f13e87edc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "title\n", + "Adaptation (2002) 5.0\n", + "Citizen Kane (1941) 5.0\n", + "Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) 5.0\n", + "Producers, The (1968) 5.0\n", + "Lord of the Rings: The Two Towers, The (2002) 5.0\n", + "Lord of the Rings: The Fellowship of the Ring, The (2001) 5.0\n", + "Back to the Future (1985) 5.0\n", + "Austin Powers in Goldmember (2002) 5.0\n", + "Minority Report (2002) 4.0\n", + "Witness (1985) 4.0\n", + "Name: 9, dtype: float64" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# userId=9\n", + "user_rating_id=ratings_matrix.loc[9,:]\n", + "user_rating_id[user_rating_id>0].sort_values(ascending=False)[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "6bcef080-c65a-42db-bb81-bb949432fd8b", + "metadata": {}, + "source": [ + "- userId=9한테 아이템 기반 협업 필터링을 통해 영화 추천하기 >> 평점 주지 않은 영화 리스트로 생성" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "8cbab9d3-1d00-4d6a-8122-a765f5f04bf5", + "metadata": {}, + "outputs": [], + "source": [ + "def get_unseen_movies(ratings_matrix,userId):\n", + " #userId로 입력받은 사용자의 모든 영화 정보를 추출해 Series로 반환\n", + " #반환된 user_rating으 title을 인덱스로 가짐\n", + " user_rating=ratings_matrix.loc[userId,:]\n", + " \n", + " #user_rating이 0보다 크다? >> 기존에 관람한 영화\n", + " already_seen=user_rating[user_rating>0].index.tolist()\n", + " \n", + " #모든 영화명을 list 객체로 만듦\n", + " movies_list=ratings_matrix.columns.tolist()\n", + " \n", + " #already_seen을 제외하고 movie_list에서 제외\n", + " unseen_list=[movie for movie in movies_list if movie not in already_seen]\n", + " \n", + " return unseen_list\n" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "b1c6d66b-d177-441f-bcf1-3bbf9805e9c9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pred_score
title
Shrek (2001)0.866202
Spider-Man (2002)0.857854
Last Samurai, The (2003)0.817473
Indiana Jones and the Temple of Doom (1984)0.816626
Matrix Reloaded, The (2003)0.800990
Harry Potter and the Sorcerer's Stone (a.k.a. Harry Potter and the Philosopher's Stone) (2001)0.765159
Gladiator (2000)0.740956
Matrix, The (1999)0.732693
Pirates of the Caribbean: The Curse of the Black Pearl (2003)0.689591
Lord of the Rings: The Return of the King, The (2003)0.676711
\n", + "
" + ], + "text/plain": [ + " pred_score\n", + "title \n", + "Shrek (2001) 0.866202\n", + "Spider-Man (2002) 0.857854\n", + "Last Samurai, The (2003) 0.817473\n", + "Indiana Jones and the Temple of Doom (1984) 0.816626\n", + "Matrix Reloaded, The (2003) 0.800990\n", + "Harry Potter and the Sorcerer's Stone (a.k.a. H... 0.765159\n", + "Gladiator (2000) 0.740956\n", + "Matrix, The (1999) 0.732693\n", + "Pirates of the Caribbean: The Curse of the Blac... 0.689591\n", + "Lord of the Rings: The Return of the King, The ... 0.676711" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):\n", + " # 예측 평점 DataFrame에서 사용자id index와 unseen_list로 들어온 영화명 컬럼을 추출하여\n", + " # 가장 예측 평점이 높은 순으로 정렬함. \n", + " recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]\n", + " return recomm_movies\n", + " \n", + "# 사용자가 관람하지 않는 영화명 추출 \n", + "unseen_list = get_unseen_movies(ratings_matrix, 9)\n", + "\n", + "# 아이템 기반의 인접 이웃 협업 필터링으로 영화 추천 \n", + "recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)\n", + "\n", + "# 평점 데이타를 DataFrame으로 생성. \n", + "recomm_movies = pd.DataFrame(data=recomm_movies.values,index=recomm_movies.index,columns=['pred_score'])\n", + "recomm_movies" + ] + }, + { + "cell_type": "markdown", + "id": "712dccec-86fc-40f1-9aef-0baad0724ca1", + "metadata": {}, + "source": [ + "# 9-7. 행렬 분해를 이용한 잠재 요인 협업 필터링 실습" + ] + }, + { + "cell_type": "markdown", + "id": "8b445d05-b692-4798-bedf-52166938c1eb", + "metadata": {}, + "source": [ + "- 행렬 분해 잠재 요인 협업 필터링 : 주로 SVD나 NMF 등이 적용됨 >> Null 데이터가 많아 SGD나 ALS 기반의 행렬 분해 활용됨\n", + "- P와 Q 행렬로 계산된 예측 R 행렬 값이 실제 R 행렬 값과 최소한의 오류를 가질 수 있도록 반복적으로 비용함수를 최적화함으로써 적합한 P와 Q 행렬을 유추\n", + "- 행렬 분해 로직을 새로운 함수로 정리 : R은 원본 사용자-아이템 평점 행렬, K는 잠재요인의 차원 수, steps는 SGD의 반복 횟수, learning_rate는 학습률, r_lambda는 L2규제 계수" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "14ede839-38d9-4594-955f-045c57b0850f", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.metrics import mean_squared_error\n", + " \n", + "def get_rmse(R, P, Q, non_zeros):\n", + " error = 0\n", + " # 두개의 분해된 행렬 P와 Q.T의 내적으로 예측 R 행렬 생성\n", + " full_pred_matrix = np.dot(P, Q.T)\n", + " \n", + " # 실제 R 행렬에서 널이 아닌 값의 위치 인덱스 추출하여 실제 R 행렬과 예측 행렬의 RMSE 추출\n", + " x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]\n", + " y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]\n", + " R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]\n", + " full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]\n", + " \n", + " mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)\n", + " rmse = np.sqrt(mse)\n", + " \n", + " return rmse" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "81d8b6c6-28d3-4996-86c3-c967a8c2cc2a", + "metadata": {}, + "outputs": [], + "source": [ + "def matrix_factorization(R,K,steps=200,learning_rate=0.01,r_lambda=0.01):\n", + " num_users,num_items=R.shape\n", + " #P와 Q 매트릭스의 크기를 지정하고 정규 분포를 가진 랜덤한 값으로 입력\n", + " np.random.seed(1)\n", + " P=np.random.normal(scale=1./K,size=(num_users,K))\n", + " Q=np.random.normal(scale=1./K,size=(num_items,K))\n", + " \n", + " prev_rmse=10000\n", + " break_count=0\n", + " \n", + " #R>0인 행, 열 위치, 값을 non_zero 리스트 객체에 저장\n", + " non_zeros=[(i,j,R[i,j]) for i in range(num_users) for j in range(num_items) if R[i,j]>0]\n", + " \n", + " #SGD 기법으로 PQ matrix를 업데이트\n", + " for step in range(steps):\n", + " for i,j,r in non_zeros:\n", + " #실제값과 예측값 차이인 오류값 구하기\n", + " eij=r-np.dot(P[i,:],Q[j,:].T)\n", + " #정규화를 반영한 SGD 업데이트 공식 적용\n", + " P[i,:]=P[i,:]+learning_rate*(eij*Q[j,:]-r_lambda*P[i,:])\n", + " Q[j,:]=Q[j,:]+learning_rate*(eij*P[i,:]-r_lambda*Q[j,:])\n", + " rmse=get_rmse(R, P, Q, non_zeros)\n", + " if(step%10)==0:\n", + " print(\"### iteration step :\", step, \" rmse: \", rmse)\n", + " return P,Q " + ] + }, + { + "cell_type": "markdown", + "id": "0e6479f5-78bf-4a87-be76-472c2ddc0972", + "metadata": {}, + "source": [ + "- 영화 평점 데이터를 새롭게 df로 로딩하고 사용자-아이템 평점 행렬로 만들기" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "e472a87e-92ea-4a23-85c8-b83e4cf83e06", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "movies=pd.read_csv('/Users/bluecloud/Documents/대학/유런/데이터셋/ml-latest-small/movies.csv')\n", + "ratings=pd.read_csv('/Users/bluecloud/Documents/대학/유런/데이터셋/ml-latest-small/ratings.csv')\n", + "ratings=ratings[['userId','movieId','rating']]\n", + "ratings_matrix=ratings.pivot_table('rating',index='movieId',columns='movieId')\n", + "\n", + "# title 칼럼을 얻기 위해 movies와 합병\n", + "ratings_movies=pd.merge(ratings,movies,on='movieId')\n", + "\n", + "#columns='title'로 title 칼럼으로 pivot 수행\n", + "ratings_matrix=ratings_movies.pivot_table('rating',index='movieId',columns='title')" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "38157ac0-e4af-4659-8b97-29b2d3e2ef05", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "### iteration step : 0 rmse: 3.3750897479512445\n", + "### iteration step : 10 rmse: 3.3599635148116462\n", + "### iteration step : 20 rmse: 3.3353939286973278\n", + "### iteration step : 30 rmse: 3.2871196118332597\n", + "### iteration step : 40 rmse: 3.188960175697435\n", + "### iteration step : 50 rmse: 2.999934953055246\n", + "### iteration step : 60 rmse: 2.6842216825334426\n", + "### iteration step : 70 rmse: 2.2567331943484645\n", + "### iteration step : 80 rmse: 1.7896616636795306\n", + "### iteration step : 90 rmse: 1.36311369208508\n", + "### iteration step : 100 rmse: 1.0210300220038908\n", + "### iteration step : 110 rmse: 0.7669582241020713\n", + "### iteration step : 120 rmse: 0.5844966327516745\n", + "### iteration step : 130 rmse: 0.45397853509526637\n", + "### iteration step : 140 rmse: 0.3594576144952893\n", + "### iteration step : 150 rmse: 0.28987583246960613\n", + "### iteration step : 160 rmse: 0.2379319898852414\n", + "### iteration step : 170 rmse: 0.19868316494423963\n", + "### iteration step : 180 rmse: 0.16862496601086513\n", + "### iteration step : 190 rmse: 0.14523088549757693\n" + ] + } + ], + "source": [ + "P,Q=matrix_factorization(ratings_matrix.values,K=50,steps=200,learning_rate=0.01,r_lambda=0.01)\n", + "pred_matrix=np.dot(P,Q.T)" + ] + }, + { + "cell_type": "markdown", + "id": "7b817475-5216-4b66-9eda-f2e067a206fe", + "metadata": {}, + "source": [ + "- 예측 사용자-아이템 평점 행렬을 영화 타이틀을 col로 가지는 df로 변경하기" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "bf946197-9978-4e88-b4d5-736419b18e15", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
title'71 (2014)'Hellboy': The Seeds of Creation (2004)'Round Midnight (1986)'Salem's Lot (2004)'Til There Was You (1997)'Tis the Season for Love (2015)'burbs, The (1989)'night Mother (1986)(500) Days of Summer (2009)*batteries not included (1987)...All the Marbles (1981)...And Justice for All (1979)00 Schneider - Jagd auf Nihil Baxter (1994)1-900 (06) (1994)10 (1979)10 Cent Pistol (2015)10 Cloverfield Lane (2016)10 Items or Less (2006)10 Things I Hate About You (1999)10 Years (2011)10,000 BC (2008)100 Girls (2000)100 Streets (2016)101 Dalmatians (1996)101 Dalmatians (One Hundred and One Dalmatians) (1961)101 Dalmatians II: Patch's London Adventure (2003)101 Reykjavik (101 Reykjavík) (2000)102 Dalmatians (2000)10th & Wolf (2006)10th Kingdom, The (2000)10th Victim, The (La decima vittima) (1965)11'09\"01 - September 11 (2002)11:14 (2003)11th Hour, The (2007)12 Angry Men (1957)12 Angry Men (1997)12 Chairs (1971)12 Chairs (1976)12 Rounds (2009)12 Years a Slave (2013)...Zathura (2005)Zatoichi and the Chest of Gold (Zatôichi senryô-kubi) (Zatôichi 6) (1964)Zazie dans le métro (1960)Zebraman (2004)Zed & Two Noughts, A (1985)Zeitgeist: Addendum (2008)Zeitgeist: Moving Forward (2011)Zeitgeist: The Movie (2007)Zelary (2003)Zelig (1983)Zero Dark Thirty (2012)Zero Effect (1998)Zero Theorem, The (2013)Zero de conduite (Zero for Conduct) (Zéro de conduite: Jeunes diables au collège) (1933)Zeus and Roxanne (1997)Zipper (2015)Zodiac (2007)Zombeavers (2014)Zombie (a.k.a. Zombie 2: The Dead Are Among Us) (Zombi 2) (1979)Zombie Strippers! (2008)Zombieland (2009)Zone 39 (1997)Zone, The (La Zona) (2007)Zookeeper (2011)Zoolander (2001)Zoolander 2 (2016)Zoom (2006)Zoom (2015)Zootopia (2016)Zulu (1964)Zulu (2013)[REC] (2007)[REC]² (2009)[REC]³ 3 Génesis (2012)anohana: The Flower We Saw That Day - The Movie (2013)eXistenZ (1999)xXx (2002)xXx: State of the Union (2005)¡Three Amigos! (1986)À nous la liberté (Freedom for Us) (1931)
movieId
1-0.758233-0.052356-0.756763-1.0684580.374390-0.3470950.827393-0.3488380.2476710.1655860.2827830.0382660.2903010.3245970.3077280.0861660.5634920.2908170.3125010.1010770.358749-0.640350-0.3705200.2088910.713583-0.146131-0.490882-0.055080-1.296807-0.1763260.1529260.1057980.237644-0.818375-0.343055-0.0981920.2100910.4280780.1127180.057621...-0.1872650.5216110.2974610.258622-0.501279-0.442185-0.602635-0.1437000.8509900.1651900.3914010.9369980.130533-0.293678-0.4250530.042371-0.583682-0.677361-0.4474780.039424-0.718930-0.317645-0.8543370.6097820.129936-0.232532-0.021407-0.1324750.984525-0.246026-0.083644-0.2802190.280893-0.1563480.324372-0.386604-0.1459460.1636100.461678-0.184093
20.262478-0.133071-0.110659-0.3740490.4415850.3846560.082095-0.342185-0.8322780.1666750.4950350.3140010.131488-0.179685-0.053513-0.0400350.486862-0.044437-0.1198870.1347100.2718110.8100750.1979830.4608580.7274260.293826-0.047625-0.2550790.0029160.2765960.2906720.142992-0.137102-0.0676040.3876270.162499-1.0436400.0703580.363119-0.185451...-0.308718-1.3099310.4682600.1575710.5147451.1276970.2603130.1566500.418566-0.1660400.327056-0.274339-0.2584170.078386-0.016262-0.534788-0.0726800.4694950.177813-0.1189140.2938900.335335-0.295754-0.396453-0.3266400.5131770.0436110.7026750.118769-0.608559-0.0347260.476649-0.534565-0.631697-0.0441960.994124-0.419517-0.418244-0.037347-0.222577
3-0.252080-0.4607260.082015-0.547901-0.1772380.0499360.103018-0.651234-0.2005840.6340520.4180760.3422570.267310-0.7085460.753267-0.4758580.550255-0.106564-0.5568940.564272-0.6199940.5285880.378427-0.800294-0.2356840.112360-0.1151770.168395-0.7671720.568721-0.670013-0.114777-0.2051500.103062-0.0005770.2823240.0202450.733746-0.927594-0.204226...-0.847986-0.2733790.4348990.6761190.121997-0.554818-0.143181-0.3664410.023801-1.0267070.475462-0.559935-0.007184-0.070603-0.019109-0.221016-0.3665620.2367240.8385510.053980-0.7778650.0975900.222445-0.0791510.2775230.8083880.5165870.310497-0.036965-0.7134860.1349940.321515-0.246105-0.767090-0.169077-0.434571-0.253952-0.4228820.8766400.192177
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) ... À nous la liberté (Freedom for Us) (1931)\n", + "movieId ... \n", + "1 -0.758233 ... -0.184093\n", + "2 0.262478 ... -0.222577\n", + "3 -0.252080 ... 0.192177\n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rating_pred_matrix=pd.DataFrame(data=pred_matrix,index=ratings_matrix.index,columns=ratings_matrix.columns)\n", + "rating_pred_matrix.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "93cdb929-1c7d-43af-a974-30149397e805", + "metadata": {}, + "source": [ + "- 위의 예측 사용자-아이템 평점 행렬 정보로 개인화된 영화 추천 짆ㅇ" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "2ef9d6be-c21f-4fd8-96ec-3de979f14139", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pred_score
title
Body of Evidence (1993)2.967085
Blind Date (1984)2.967085
Last Seduction, The (1994)1.837418
Sunshine State (2002)1.210886
Back to the Future Part III (1990)1.177924
Back to the Future Part II (1989)1.146718
Lord of the Rings: The Fellowship of the Ring, The (2001)1.118403
Back to the Future (1985)1.018926
Producers, The (1968)1.002248
Minority Report (2002)0.958495
\n", + "
" + ], + "text/plain": [ + " pred_score\n", + "title \n", + "Body of Evidence (1993) 2.967085\n", + "Blind Date (1984) 2.967085\n", + "Last Seduction, The (1994) 1.837418\n", + "Sunshine State (2002) 1.210886\n", + "Back to the Future Part III (1990) 1.177924\n", + "Back to the Future Part II (1989) 1.146718\n", + "Lord of the Rings: The Fellowship of the Ring, ... 1.118403\n", + "Back to the Future (1985) 1.018926\n", + "Producers, The (1968) 1.002248\n", + "Minority Report (2002) 0.958495" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#사용자가 관람하지 않은 영화명 추출\n", + "unseen_list=get_unseen_movies(ratings_matrix,9)\n", + "\n", + "#잠재 요인 협업 필터링으로 영화 추천\n", + "recomm_movies=recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)\n", + "\n", + "#평점 데이터를 df로 생성\n", + "recomm_movies=pd.DataFrame(data=recomm_movies.values,index=recomm_movies.index,columns=['pred_score'])\n", + "recomm_movies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1b25ffe-014d-45ea-b138-a436275335a1", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}