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\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\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\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.ipynb" new file mode 100644 index 0000000..c001544 --- /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\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.ipynb" @@ -0,0 +1,2845 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "61b2e2a5", + "metadata": {}, + "source": [ + "# 9.5 컨텐츠 기반 필터링 " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f79674de", + "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", + "
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
\n", + "
" + ], + "text/plain": [ + " budget genres \\\n", + "0 237000000 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"nam... \n", + "\n", + " homepage id \\\n", + "0 http://www.avatarmovie.com/ 19995 \n", + "\n", + " keywords original_language \\\n", + "0 [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\":... en \n", + "\n", + " original_title overview \\\n", + "0 Avatar In the 22nd century, a paraplegic Marine is di... \n", + "\n", + " popularity production_companies \\\n", + "0 150.437577 [{\"name\": \"Ingenious Film Partners\", \"id\": 289... \n", + "\n", + " production_countries release_date revenue \\\n", + "0 [{\"iso_3166_1\": \"US\", \"name\": \"United States o... 2009-12-10 2787965087 \n", + "\n", + " runtime spoken_languages status \\\n", + "0 162.0 [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso... Released \n", + "\n", + " tagline title vote_average vote_count \n", + "0 Enter the World of Pandora. Avatar 7.2 11800 " + ] + }, + "execution_count": 2, + "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('./tmdb-5000-movie-dataset/tmdb_5000_movies.csv')\n", + "print(movies.shape)\n", + "movies.head(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "18829069", + "metadata": {}, + "outputs": [], + "source": [ + "movies_df = movies[['id','title', 'genres', 'vote_average', 'vote_count',\n", + " 'popularity', 'keywords', 'overview']]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9cf73681", + "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 \\\n", + "0 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... \n", + "\n", + " keywords \n", + "0 [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.set_option('max_colwidth', 100)\n", + "movies_df[['genres','keywords']][:1]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "daef6793", + "metadata": {}, + "outputs": [], + "source": [ + "from ast import literal_eval\n", + "\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": 6, + "id": "14b61fe2", + "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 \\\n", + "0 [Action, Adventure, Fantasy, Science Fiction] \n", + "\n", + " keywords \n", + "0 [culture clash, future, space war, space colony, society, space travel, futuristic, romance, spa... " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "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": "code", + "execution_count": 9, + "id": "e166b77d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4803, 276)\n" + ] + } + ], + "source": [ + "from sklearn.feature_extraction.text import CountVectorizer\n", + "\n", + "movies_df['genres_literal'] = movies_df['genres'].apply(lambda x : (' ').join(x))\n", + "count_vect = CountVectorizer(min_df=1, ngram_range=(1,2))\n", + "genre_mat = count_vect.fit_transform(movies_df['genres_literal'])\n", + "print(genre_mat.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "74fbe12b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4803, 4803)\n", + "[[1. 0.59628479 0.4472136 ... 0. 0. 0. ]\n", + " [0.59628479 1. 0.4 ... 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[:2])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a8d0c496", + "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": "code", + "execution_count": 12, + "id": "e2275202", + "metadata": {}, + "outputs": [], + "source": [ + "def find_sim_movie(df, sorted_ind, title_name, top_n=10):\n", + " \n", + " title_movie = df[df['title'] == title_name]\n", + " \n", + " title_index = title_movie.index.values\n", + " similar_indexes = sorted_ind[title_index, :(top_n)]\n", + " \n", + " print(similar_indexes)\n", + " similar_indexes = similar_indexes.reshape(-1)\n", + " \n", + " return df.iloc[similar_indexes]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "491505bc", + "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": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather',10)\n", + "similar_movies[['title', 'vote_average']]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "03719f69", + "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": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies_df[['title','vote_average','vote_count']].sort_values('vote_average', ascending=False)[:10]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "003351c7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C: 6.092 m: 370.2\n" + ] + } + ], + "source": [ + "C = movies_df['vote_average'].mean()\n", + "m = movies_df['vote_count'].quantile(0.6)\n", + "print('C:',round(C,3), 'm:',round(m,3))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "02194e4a", + "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", + " \n", + " return ( (v/(v+m)) * R ) + ( (m/(m+v)) * C ) \n", + "\n", + "movies_df['weighted_vote'] = movies_df.apply(weighted_vote_average, axis=1) " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f9f47aa3", + "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": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies_df[['title','vote_average','weighted_vote','vote_count']].sort_values('weighted_vote',\n", + " ascending=False)[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "0a2a530e", + "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": 19, + "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", + " \n", + " similar_indexes = sorted_ind[title_index, :(top_n*2)]\n", + " similar_indexes = similar_indexes.reshape(-1)\n", + " similar_indexes = similar_indexes[similar_indexes != title_index]\n", + " \n", + " return df.iloc[similar_indexes].sort_values('weighted_vote', ascending=False)[:top_n]\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": "610c2a92", + "metadata": {}, + "source": [ + "# 9.6 아이템 기반 인접 이웃 협업 필터링" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "33313146", + "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('./ml-latest-small/movies.csv')\n", + "ratings = pd.read_csv('./ml-latest-small/ratings.csv')\n", + "print(movies.shape)\n", + "print(ratings.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0dcc1974", + "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", + "
movieIdtitlegenres
01Toy Story (1995)Adventure|Animation|Children|Comedy|Fantasy
12Jumanji (1995)Adventure|Children|Fantasy
23Grumpier Old Men (1995)Comedy|Romance
34Waiting to Exhale (1995)Comedy|Drama|Romance
45Father of the Bride Part II (1995)Comedy
\n", + "
" + ], + "text/plain": [ + " movieId title \\\n", + "0 1 Toy Story (1995) \n", + "1 2 Jumanji (1995) \n", + "2 3 Grumpier Old Men (1995) \n", + "3 4 Waiting to Exhale (1995) \n", + "4 5 Father of the Bride Part II (1995) \n", + "\n", + " genres \n", + "0 Adventure|Animation|Children|Comedy|Fantasy \n", + "1 Adventure|Children|Fantasy \n", + "2 Comedy|Romance \n", + "3 Comedy|Drama|Romance \n", + "4 Comedy " + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movies.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f1085fff", + "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", + "
userIdmovieIdratingtimestamp
0114.0964982703
1134.0964981247
2164.0964982224
31475.0964983815
41505.0964982931
\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\n", + "3 1 47 5.0 964983815\n", + "4 1 50 5.0 964982931" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "632db7a8", + "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", + "
movieId12345678910...193565193567193571193573193579193581193583193585193587193609
userId
14.0NaN4.0NaNNaN4.0NaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
3NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

3 rows × 9724 columns

\n", + "
" + ], + "text/plain": [ + "movieId 1 2 3 4 5 6 7 8 \\\n", + "userId \n", + "1 4.0 NaN 4.0 NaN NaN 4.0 NaN NaN \n", + "2 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "3 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "\n", + "movieId 9 10 ... 193565 193567 193571 193573 193579 193581 \\\n", + "userId ... \n", + "1 NaN NaN ... NaN 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", + "movieId 193583 193585 193587 193609 \n", + "userId \n", + "1 NaN NaN NaN NaN \n", + "2 NaN NaN NaN NaN \n", + "3 NaN NaN NaN NaN \n", + "\n", + "[3 rows x 9724 columns]" + ] + }, + "execution_count": 23, + "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": "code", + "execution_count": 24, + "id": "fe8c3786", + "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", + "
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)...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.0...0.00.00.00.00.00.00.00.04.00.0
20.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
30.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) 'Hellboy': The Seeds of Creation (2004) \\\n", + "userId \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "\n", + "title 'Round Midnight (1986) 'Salem's Lot (2004) \\\n", + "userId \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "\n", + "title 'Til There Was You (1997) 'Tis the Season for Love (2015) \\\n", + "userId \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "\n", + "title 'burbs, The (1989) 'night Mother (1986) (500) Days of Summer (2009) \\\n", + "userId \n", + "1 0.0 0.0 0.0 \n", + "2 0.0 0.0 0.0 \n", + "3 0.0 0.0 0.0 \n", + "\n", + "title *batteries not included (1987) ... Zulu (2013) [REC] (2007) \\\n", + "userId ... \n", + "1 0.0 ... 0.0 0.0 \n", + "2 0.0 ... 0.0 0.0 \n", + "3 0.0 ... 0.0 0.0 \n", + "\n", + "title [REC]² (2009) [REC]³ 3 Génesis (2012) \\\n", + "userId \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "\n", + "title anohana: The Flower We Saw That Day - The Movie (2013) \\\n", + "userId \n", + "1 0.0 \n", + "2 0.0 \n", + "3 0.0 \n", + "\n", + "title eXistenZ (1999) xXx (2002) xXx: State of the Union (2005) \\\n", + "userId \n", + "1 0.0 0.0 0.0 \n", + "2 0.0 0.0 0.0 \n", + "3 0.0 0.0 0.0 \n", + "\n", + "title ¡Three Amigos! (1986) À nous la liberté (Freedom for Us) (1931) \n", + "userId \n", + "1 4.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rating_movies = pd.merge(ratings, movies, on='movieId')\n", + "\n", + "ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')\n", + "\n", + "ratings_matrix = ratings_matrix.fillna(0)\n", + "ratings_matrix.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "d323871a", + "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", + "
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)...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.0...0.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.0...0.00.0000000.0000000.0000000.00.00.0000000.0000000.00.0
'Round Midnight (1986)0.00.7071071.0000000.00.00.00.1767770.00.0000000.0...0.00.0000000.0000000.0000000.00.00.0000000.0000000.00.0
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) \\\n", + "title \n", + "'71 (2014) 1.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title 'Hellboy': The Seeds of Creation (2004) \\\n", + "title \n", + "'71 (2014) 0.000000 \n", + "'Hellboy': The Seeds of Creation (2004) 1.000000 \n", + "'Round Midnight (1986) 0.707107 \n", + "\n", + "title 'Round Midnight (1986) \\\n", + "title \n", + "'71 (2014) 0.000000 \n", + "'Hellboy': The Seeds of Creation (2004) 0.707107 \n", + "'Round Midnight (1986) 1.000000 \n", + "\n", + "title 'Salem's Lot (2004) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title 'Til There Was You (1997) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title 'Tis the Season for Love (2015) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title 'burbs, The (1989) \\\n", + "title \n", + "'71 (2014) 0.000000 \n", + "'Hellboy': The Seeds of Creation (2004) 0.000000 \n", + "'Round Midnight (1986) 0.176777 \n", + "\n", + "title 'night Mother (1986) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title (500) Days of Summer (2009) \\\n", + "title \n", + "'71 (2014) 0.141653 \n", + "'Hellboy': The Seeds of Creation (2004) 0.000000 \n", + "'Round Midnight (1986) 0.000000 \n", + "\n", + "title *batteries not included (1987) ... \\\n", + "title ... \n", + "'71 (2014) 0.0 ... \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 ... \n", + "'Round Midnight (1986) 0.0 ... \n", + "\n", + "title Zulu (2013) [REC] (2007) \\\n", + "title \n", + "'71 (2014) 0.0 0.342055 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 0.000000 \n", + "'Round Midnight (1986) 0.0 0.000000 \n", + "\n", + "title [REC]² (2009) \\\n", + "title \n", + "'71 (2014) 0.543305 \n", + "'Hellboy': The Seeds of Creation (2004) 0.000000 \n", + "'Round Midnight (1986) 0.000000 \n", + "\n", + "title [REC]³ 3 Génesis (2012) \\\n", + "title \n", + "'71 (2014) 0.707107 \n", + "'Hellboy': The Seeds of Creation (2004) 0.000000 \n", + "'Round Midnight (1986) 0.000000 \n", + "\n", + "title anohana: The Flower We Saw That Day - The Movie (2013) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title eXistenZ (1999) xXx (2002) \\\n", + "title \n", + "'71 (2014) 0.0 0.139431 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 0.000000 \n", + "'Round Midnight (1986) 0.0 0.000000 \n", + "\n", + "title xXx: State of the Union (2005) \\\n", + "title \n", + "'71 (2014) 0.327327 \n", + "'Hellboy': The Seeds of Creation (2004) 0.000000 \n", + "'Round Midnight (1986) 0.000000 \n", + "\n", + "title ¡Three Amigos! (1986) \\\n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "title À nous la liberté (Freedom for Us) (1931) \n", + "title \n", + "'71 (2014) 0.0 \n", + "'Hellboy': The Seeds of Creation (2004) 0.0 \n", + "'Round Midnight (1986) 0.0 \n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sklearn.metrics.pairwise import cosine_similarity\n", + "\n", + "item_sim = cosine_similarity(ratings_matrix.T, ratings_matrix.T)\n", + "\n", + "# cosine_similarity() 로 반환된 넘파이 행렬을 영화명을 매핑하여 DataFrame으로 변환\n", + "item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns,\n", + " columns=ratings_matrix.columns)\n", + "print(item_sim_df.shape)\n", + "item_sim_df.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "9274290c", + "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": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_sim_df[\"Godfather, The (1972)\"].sort_values(ascending=False)[:6]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "1a53d38e", + "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": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_sim_df[\"Inception (2010)\"].sort_values(ascending=False)[1:6]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "251dc0dd", + "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": 30, + "id": "ca945fc3", + "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", + "
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)...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.178197...0.1136080.1817380.1339620.1285740.0061790.2120700.1929210.1360240.2929550.720347
20.0182600.0427440.0188610.0000000.0000000.0359950.0134130.0023140.0322130.014863...0.0156400.0208550.0201190.0157450.0499830.0148760.0216160.0245280.0175630.000000
30.0118840.0302790.0644370.0037620.0037490.0027220.0146250.0020850.0056660.006272...0.0069230.0116650.0118000.0122250.0000000.0081940.0070170.0092290.0104200.084501
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) 'Hellboy': The Seeds of Creation (2004) \\\n", + "userId \n", + "1 0.070345 0.577855 \n", + "2 0.018260 0.042744 \n", + "3 0.011884 0.030279 \n", + "\n", + "title 'Round Midnight (1986) 'Salem's Lot (2004) \\\n", + "userId \n", + "1 0.321696 0.227055 \n", + "2 0.018861 0.000000 \n", + "3 0.064437 0.003762 \n", + "\n", + "title 'Til There Was You (1997) 'Tis the Season for Love (2015) \\\n", + "userId \n", + "1 0.206958 0.194615 \n", + "2 0.000000 0.035995 \n", + "3 0.003749 0.002722 \n", + "\n", + "title 'burbs, The (1989) 'night Mother (1986) (500) Days of Summer (2009) \\\n", + "userId \n", + "1 0.249883 0.102542 0.157084 \n", + "2 0.013413 0.002314 0.032213 \n", + "3 0.014625 0.002085 0.005666 \n", + "\n", + "title *batteries not included (1987) ... Zulu (2013) [REC] (2007) \\\n", + "userId ... \n", + "1 0.178197 ... 0.113608 0.181738 \n", + "2 0.014863 ... 0.015640 0.020855 \n", + "3 0.006272 ... 0.006923 0.011665 \n", + "\n", + "title [REC]² (2009) [REC]³ 3 Génesis (2012) \\\n", + "userId \n", + "1 0.133962 0.128574 \n", + "2 0.020119 0.015745 \n", + "3 0.011800 0.012225 \n", + "\n", + "title anohana: The Flower We Saw That Day - The Movie (2013) \\\n", + "userId \n", + "1 0.006179 \n", + "2 0.049983 \n", + "3 0.000000 \n", + "\n", + "title eXistenZ (1999) xXx (2002) xXx: State of the Union (2005) \\\n", + "userId \n", + "1 0.212070 0.192921 0.136024 \n", + "2 0.014876 0.021616 0.024528 \n", + "3 0.008194 0.007017 0.009229 \n", + "\n", + "title ¡Three Amigos! (1986) À nous la liberté (Freedom for Us) (1931) \n", + "userId \n", + "1 0.292955 0.720347 \n", + "2 0.017563 0.000000 \n", + "3 0.010420 0.084501 \n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 30, + "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,\n", + " columns = ratings_matrix.columns)\n", + "ratings_pred_matrix.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "9f35bda5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "아이템 기반 모든 인접 이웃 MSE: 9.895354759094706\n" + ] + } + ], + "source": [ + "from sklearn.metrics import mean_squared_error\n", + "\n", + "def get_mse(pred, actual):\n", + " pred = pred[actual.nonzero()].flatten()\n", + " actual = actual[actual.nonzero()].flatten()\n", + " return mean_squared_error(pred, actual)\n", + "\n", + "print('아이템 기반 모든 인접 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "88de2486", + "metadata": {}, + "outputs": [], + "source": [ + "def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):\n", + " pred = np.zeros(ratings_arr.shape)\n", + "\n", + " for col in range(ratings_arr.shape[1]):\n", + " top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]\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": "code", + "execution_count": 33, + "id": "2bbf4025", + "metadata": {}, + "outputs": [ + { + "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", + "\n", + "ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,\n", + " columns = ratings_matrix.columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "f228a3f4", + "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": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_rating_id = ratings_matrix.loc[9, :]\n", + "user_rating_id[ user_rating_id > 0].sort_values(ascending=False)[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "201daa04", + "metadata": {}, + "outputs": [], + "source": [ + "def get_unseen_movies(ratings_matrix, userId):\n", + " user_rating = ratings_matrix.loc[userId,:]\n", + " \n", + " already_seen = user_rating[ user_rating > 0].index.tolist()\n", + " \n", + " movies_list = ratings_matrix.columns.tolist()\n", + " \n", + " unseen_list = [ movie for movie in movies_list if movie not in already_seen]\n", + " \n", + " return unseen_list" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "16698972", + "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. Harry Potter and the Philosopher's Stone) (2001) 0.765159\n", + "Gladiator (2000) 0.740956\n", + "Matrix, The (1999) 0.732693\n", + "Pirates of the Caribbean: The Curse of the Black Pearl (2003) 0.689591\n", + "Lord of the Rings: The Return of the King, The (2003) 0.676711" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):\n", + " recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]\n", + " return recomm_movies\n", + " \n", + "unseen_list = get_unseen_movies(ratings_matrix, 9)\n", + "\n", + "recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)\n", + "\n", + "recomm_movies = pd.DataFrame(data=recomm_movies.values,index=recomm_movies.index,columns=['pred_score'])\n", + "recomm_movies" + ] + }, + { + "cell_type": "markdown", + "id": "c4840ae8", + "metadata": {}, + "source": [ + "# 9.7 행렬 분해 기반의 잠재 요인 협업 필터링" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "16ee3a1b", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from sklearn.metrics import mean_squared_error\n", + "\n", + "def get_rmse(R, P, Q, non_zeros):\n", + " error = 0\n", + " full_pred_matrix = np.dot(P, Q.T)\n", + " \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", + " \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": 38, + "id": "68a5e854", + "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", + " 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", + " 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", + " for step in range(steps):\n", + " for i, j, r in non_zeros:\n", + " eij = r - np.dot(P[i, :], Q[j, :].T)\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", + " \n", + " rmse = get_rmse(R, P, Q, non_zeros)\n", + " if (step % 10) == 0 :\n", + " print(\"### iteration step : \", step,\" rmse : \", rmse)\n", + " \n", + " return P, Q" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "5a271f8a", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "movies = pd.read_csv('./ml-latest-small/movies.csv')\n", + "ratings = pd.read_csv('./ml-latest-small/ratings.csv')\n", + "ratings = ratings[['userId', 'movieId', 'rating']]\n", + "ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')\n", + "\n", + "rating_movies = pd.merge(ratings, movies, on='movieId')\n", + "\n", + "ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "c3189a61", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "### iteration step : 0 rmse : 2.9023619751336867\n", + "### iteration step : 10 rmse : 0.7335768591017927\n", + "### iteration step : 20 rmse : 0.5115539026853442\n", + "### iteration step : 30 rmse : 0.37261628282537446\n", + "### iteration step : 40 rmse : 0.2960818299181014\n", + "### iteration step : 50 rmse : 0.2520353192341642\n", + "### iteration step : 60 rmse : 0.22487503275269854\n", + "### iteration step : 70 rmse : 0.2068545530233154\n", + "### iteration step : 80 rmse : 0.19413418783028685\n", + "### iteration step : 90 rmse : 0.18470082002720403\n", + "### iteration step : 100 rmse : 0.177429275272091\n", + "### iteration step : 110 rmse : 0.1716522696470749\n", + "### iteration step : 120 rmse : 0.1669518194687172\n", + "### iteration step : 130 rmse : 0.1630529219199754\n", + "### iteration step : 140 rmse : 0.1597669192967964\n", + "### iteration step : 150 rmse : 0.15695986999457318\n", + "### iteration step : 160 rmse : 0.15453398186715428\n", + "### iteration step : 170 rmse : 0.15241618551077643\n", + "### iteration step : 180 rmse : 0.15055080739628304\n", + "### iteration step : 190 rmse : 0.1488947091323209\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": "code", + "execution_count": 41, + "id": "48ddaf62", + "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", + "
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)...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
13.0550844.0920183.5641304.5021673.9812151.2716943.6032742.3332665.0917493.972454...1.4026084.2083823.7059572.7205142.7873313.4750763.2534582.1610874.0104950.859474
23.1701193.6579923.3087074.1665214.3118901.2754694.2379721.9003663.3928593.647421...0.9738113.5282643.3615322.6725352.4044564.2327892.9116021.6345764.1357350.725684
32.3070731.6588531.4435382.2088592.2294860.7807601.9970430.9249082.9707002.551446...0.5203541.7094942.2815961.7828331.6351731.3232762.8875801.0426182.2938900.396941
\n", + "

3 rows × 9719 columns

\n", + "
" + ], + "text/plain": [ + "title '71 (2014) 'Hellboy': The Seeds of Creation (2004) \\\n", + "userId \n", + "1 3.055084 4.092018 \n", + "2 3.170119 3.657992 \n", + "3 2.307073 1.658853 \n", + "\n", + "title 'Round Midnight (1986) 'Salem's Lot (2004) \\\n", + "userId \n", + "1 3.564130 4.502167 \n", + "2 3.308707 4.166521 \n", + "3 1.443538 2.208859 \n", + "\n", + "title 'Til There Was You (1997) 'Tis the Season for Love (2015) \\\n", + "userId \n", + "1 3.981215 1.271694 \n", + "2 4.311890 1.275469 \n", + "3 2.229486 0.780760 \n", + "\n", + "title 'burbs, The (1989) 'night Mother (1986) (500) Days of Summer (2009) \\\n", + "userId \n", + "1 3.603274 2.333266 5.091749 \n", + "2 4.237972 1.900366 3.392859 \n", + "3 1.997043 0.924908 2.970700 \n", + "\n", + "title *batteries not included (1987) ... Zulu (2013) [REC] (2007) \\\n", + "userId ... \n", + "1 3.972454 ... 1.402608 4.208382 \n", + "2 3.647421 ... 0.973811 3.528264 \n", + "3 2.551446 ... 0.520354 1.709494 \n", + "\n", + "title [REC]² (2009) [REC]³ 3 Génesis (2012) \\\n", + "userId \n", + "1 3.705957 2.720514 \n", + "2 3.361532 2.672535 \n", + "3 2.281596 1.782833 \n", + "\n", + "title anohana: The Flower We Saw That Day - The Movie (2013) \\\n", + "userId \n", + "1 2.787331 \n", + "2 2.404456 \n", + "3 1.635173 \n", + "\n", + "title eXistenZ (1999) xXx (2002) xXx: State of the Union (2005) \\\n", + "userId \n", + "1 3.475076 3.253458 2.161087 \n", + "2 4.232789 2.911602 1.634576 \n", + "3 1.323276 2.887580 1.042618 \n", + "\n", + "title ¡Three Amigos! (1986) À nous la liberté (Freedom for Us) (1931) \n", + "userId \n", + "1 4.010495 0.859474 \n", + "2 4.135735 0.725684 \n", + "3 2.293890 0.396941 \n", + "\n", + "[3 rows x 9719 columns]" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ratings_pred_matrix = pd.DataFrame(data=pred_matrix, index= ratings_matrix.index,\n", + " columns = ratings_matrix.columns)\n", + "\n", + "ratings_pred_matrix.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "34f99199", + "metadata": {}, + "outputs": [], + "source": [ + "def get_unseen_movies(ratings_matrix, userId):\n", + " user_rating = ratings_matrix.loc[userId,:]\n", + " \n", + " already_seen = user_rating[ user_rating > 0].index.tolist()\n", + " \n", + " movies_list = ratings_matrix.columns.tolist()\n", + " \n", + " unseen_list = [ movie for movie in movies_list if movie not in already_seen]\n", + " \n", + " return unseen_list" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "f65e91e3", + "metadata": {}, + "outputs": [], + "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" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "8a579c39", + "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
Rear Window (1954)5.704612
South Park: Bigger, Longer and Uncut (1999)5.451100
Rounders (1998)5.298393
Blade Runner (1982)5.244951
Roger & Me (1989)5.191962
Gattaca (1997)5.183179
Ben-Hur (1959)5.130463
Rosencrantz and Guildenstern Are Dead (1990)5.087375
Big Lebowski, The (1998)5.038690
Star Wars: Episode V - The Empire Strikes Back (1980)4.989601
\n", + "
" + ], + "text/plain": [ + " pred_score\n", + "title \n", + "Rear Window (1954) 5.704612\n", + "South Park: Bigger, Longer and Uncut (1999) 5.451100\n", + "Rounders (1998) 5.298393\n", + "Blade Runner (1982) 5.244951\n", + "Roger & Me (1989) 5.191962\n", + "Gattaca (1997) 5.183179\n", + "Ben-Hur (1959) 5.130463\n", + "Rosencrantz and Guildenstern Are Dead (1990) 5.087375\n", + "Big Lebowski, The (1998) 5.038690\n", + "Star Wars: Episode V - The Empire Strikes Back (1980) 4.989601" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 사용자가 관람하지 않는 영화명 추출 \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" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git "a/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.ipynb" "b/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.ipynb" new file mode 100644 index 0000000..6e5d016 --- /dev/null +++ "b/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.ipynb" @@ -0,0 +1,725 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7970f5a7", + "metadata": {}, + "source": [ + "## 경사 하강범" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e355f24c", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "R = np.array([[4, np.NaN, np.NaN, 2, np.NaN ],\n", + " [np.NaN, 5, np.NaN, 3, 1 ],\n", + " [np.NaN, np.NaN, 3, 4, 4 ],\n", + " [5, 2, 1, 2, np.NaN ]])\n", + "num_users, num_items = R.shape\n", + "K=3\n", + "\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))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "52265178", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.metrics import mean_squared_error\n", + "\n", + "def get_rmse(R, P, Q, non_zeros):\n", + " error = 0\n", + " full_pred_matrix = np.dot(P, Q.T)\n", + " \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": 3, + "id": "cbc9b963", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "### iteration step : 0 rmse : 3.2388050277987723\n", + "### iteration step : 50 rmse : 0.48767231013696477\n", + "### iteration step : 100 rmse : 0.15643403848192472\n", + "### iteration step : 150 rmse : 0.07455141311978056\n", + "### iteration step : 200 rmse : 0.04325226798579326\n", + "### iteration step : 250 rmse : 0.0292483287808792\n", + "### iteration step : 300 rmse : 0.022621116143829462\n", + "### iteration step : 350 rmse : 0.019493636196525253\n", + "### iteration step : 400 rmse : 0.018022719092132912\n", + "### iteration step : 450 rmse : 0.017319685953442663\n", + "### iteration step : 500 rmse : 0.01697365788757087\n", + "### iteration step : 550 rmse : 0.01679680459589556\n", + "### iteration step : 600 rmse : 0.016701322901884613\n", + "### iteration step : 650 rmse : 0.016644736912476723\n", + "### iteration step : 700 rmse : 0.01660591006820994\n", + "### iteration step : 750 rmse : 0.016574200475704952\n", + "### iteration step : 800 rmse : 0.016544315829216106\n", + "### iteration step : 850 rmse : 0.016513751774735172\n", + "### iteration step : 900 rmse : 0.01648146573819512\n", + "### iteration step : 950 rmse : 0.016447171683479145\n" + ] + } + ], + "source": [ + "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", + "steps=1000\n", + "learning_rate=0.01\n", + "r_lambda=0.01\n", + "\n", + "for step in range(steps):\n", + " for i, j, r in non_zeros:\n", + " eij = r - np.dot(P[i, :], Q[j, :].T)\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", + "\n", + " rmse = get_rmse(R, P, Q, non_zeros)\n", + " if (step % 50) == 0 :\n", + " print(\"### iteration step : \", step,\" rmse : \", rmse)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7670a1f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "예측 행렬:\n", + " [[3.991 0.897 1.306 2.002 1.663]\n", + " [6.696 4.978 0.979 2.981 1.003]\n", + " [6.677 0.391 2.987 3.977 3.986]\n", + " [4.968 2.005 1.006 2.017 1.14 ]]\n" + ] + } + ], + "source": [ + "pred_matrix = np.dot(P, Q.T)\n", + "print('예측 행렬:\\n', np.round(pred_matrix, 3))" + ] + }, + { + "cell_type": "markdown", + "id": "9cf0b01f", + "metadata": {}, + "source": [ + "# Surprise" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8c0c9fc1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.1.4\n" + ] + } + ], + "source": [ + "import surprise \n", + "\n", + "print(surprise.__version__)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9671e6ed", + "metadata": {}, + "outputs": [], + "source": [ + "from surprise import SVD\n", + "from surprise import Dataset \n", + "from surprise import accuracy \n", + "from surprise.model_selection import train_test_split" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3de5ba6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset ml-100k could not be found. Do you want to download it? [Y/n] Trying to download dataset from https://files.grouplens.org/datasets/movielens/ml-100k.zip...\n", + "Done! Dataset ml-100k has been saved to /Users/zinwaiyan/.surprise_data/ml-100k\n" + ] + } + ], + "source": [ + "data = Dataset.load_builtin('ml-100k')\n", + "trainset, testset = train_test_split(data, test_size=.25, random_state=0) " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4447a1de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "algo = SVD(random_state=0)\n", + "algo.fit(trainset) " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "eb47eab3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "prediction type : size: 25000\n", + "prediction 결과의 최초 5개 추출\n" + ] + }, + { + "data": { + "text/plain": [ + "[Prediction(uid='120', iid='282', r_ui=4.0, est=3.5114147666251547, details={'was_impossible': False}),\n", + " Prediction(uid='882', iid='291', r_ui=4.0, est=3.573872419581491, details={'was_impossible': False}),\n", + " Prediction(uid='535', iid='507', r_ui=5.0, est=4.033583485472447, details={'was_impossible': False}),\n", + " Prediction(uid='697', iid='244', r_ui=5.0, est=3.846363949593691, details={'was_impossible': False}),\n", + " Prediction(uid='751', iid='385', r_ui=4.0, est=3.1807542478219157, details={'was_impossible': False})]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "predictions = algo.test( testset )\n", + "print('prediction type :',type(predictions), ' size:',len(predictions))\n", + "print('prediction 결과의 최초 5개 추출')\n", + "predictions[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "84742364", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('120', '282', 3.5114147666251547),\n", + " ('882', '291', 3.573872419581491),\n", + " ('535', '507', 4.033583485472447)]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[ (pred.uid, pred.iid, pred.est) for pred in predictions[:3] ]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3c294b29", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user: 196 item: 302 r_ui = None est = 4.49 {'was_impossible': False}\n" + ] + } + ], + "source": [ + "uid = str(196)\n", + "iid = str(302)\n", + "pred = algo.predict(uid, iid)\n", + "print(pred)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "435da783", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE: 0.9467\n" + ] + }, + { + "data": { + "text/plain": [ + "0.9466860806937948" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "accuracy.rmse(predictions)" + ] + }, + { + "cell_type": "markdown", + "id": "a2365d5a", + "metadata": {}, + "source": [ + "## Suprise Main Module" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e68ffa92", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "ratings = pd.read_csv('./ml-latest-small/ratings.csv')\n", + "ratings.to_csv('./ml-latest-small/ratings_noh.csv', index=False, header=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4627f2d1", + "metadata": {}, + "outputs": [], + "source": [ + "from surprise import Reader\n", + "\n", + "reader = Reader(line_format='user item rating timestamp', sep=',', rating_scale=(0.5, 5))\n", + "data=Dataset.load_from_file('./ml-latest-small/ratings_noh.csv',reader=reader)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5bb15f69", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE: 0.8682\n" + ] + }, + { + "data": { + "text/plain": [ + "0.8681952927143516" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainset, testset = train_test_split(data, test_size=.25, random_state=0)\n", + "\n", + "algo = SVD(n_factors=50, random_state=0)\n", + "algo.fit(trainset) \n", + "predictions = algo.test( testset )\n", + "accuracy.rmse(predictions)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "a0015de6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE: 0.8682\n" + ] + }, + { + "data": { + "text/plain": [ + "0.8681952927143516" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "from surprise import Reader, Dataset\n", + "\n", + "ratings = pd.read_csv('./ml-latest-small/ratings.csv') \n", + "reader = Reader(rating_scale=(0.5, 5.0))\n", + "\n", + "data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\n", + "trainset, testset = train_test_split(data, test_size=.25, random_state=0)\n", + "\n", + "algo = SVD(n_factors=50, random_state=0)\n", + "algo.fit(trainset) \n", + "predictions = algo.test( testset )\n", + "accuracy.rmse(predictions)" + ] + }, + { + "cell_type": "markdown", + "id": "408da8bc", + "metadata": {}, + "source": [ + "## Cross Validation & Hyper Parameter Tuning" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "66cb589c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Evaluating RMSE, MAE of algorithm SVD on 5 split(s).\n", + "\n", + " Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std \n", + "RMSE (testset) 0.8763 0.8752 0.8778 0.8695 0.8700 0.8738 0.0034 \n", + "MAE (testset) 0.6720 0.6718 0.6751 0.6683 0.6697 0.6714 0.0023 \n", + "Fit time 0.42 0.41 0.42 0.41 0.52 0.44 0.04 \n", + "Test time 0.04 0.04 0.04 0.04 0.04 0.04 0.00 \n" + ] + }, + { + "data": { + "text/plain": [ + "{'test_rmse': array([0.87633319, 0.87518155, 0.8778228 , 0.86951382, 0.87004357]),\n", + " 'test_mae': array([0.67200223, 0.67184133, 0.67506669, 0.66828439, 0.66972094]),\n", + " 'fit_time': (0.41989779472351074,\n", + " 0.4130728244781494,\n", + " 0.4176449775695801,\n", + " 0.4129977226257324,\n", + " 0.5153641700744629),\n", + " 'test_time': (0.037026166915893555,\n", + " 0.03743886947631836,\n", + " 0.036936044692993164,\n", + " 0.03721189498901367,\n", + " 0.03929281234741211)}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from surprise.model_selection import cross_validate\n", + "\n", + "ratings = pd.read_csv('./ml-latest-small/ratings.csv') # reading data in pandas df\n", + "reader = Reader(rating_scale=(0.5, 5.0))\n", + "data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\n", + "algo = SVD(random_state=0)\n", + "cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c0edacd6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.877672058943516\n", + "{'n_epochs': 20, 'n_factors': 50}\n" + ] + } + ], + "source": [ + "from surprise.model_selection import GridSearchCV\n", + "\n", + "param_grid = {'n_epochs': [20, 40, 60], 'n_factors': [50, 100, 200] }\n", + "gs = GridSearchCV(SVD, param_grid, measures=['rmse', 'mae'], cv=3)\n", + "gs.fit(data)\n", + "print(gs.best_score['rmse'])\n", + "print(gs.best_params['rmse'])" + ] + }, + { + "cell_type": "markdown", + "id": "376191d6", + "metadata": {}, + "source": [ + "## 개인화 영화 추천 시스템" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "5a82e617", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'DatasetAutoFolds' object has no attribute 'n_users'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[18], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m data \u001b[38;5;241m=\u001b[39m Dataset\u001b[38;5;241m.\u001b[39mload_from_df(ratings[[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124muserId\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmovieId\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrating\u001b[39m\u001b[38;5;124m'\u001b[39m]], reader)\n\u001b[1;32m 2\u001b[0m algo \u001b[38;5;241m=\u001b[39m SVD(n_factors\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m50\u001b[39m, random_state\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m algo\u001b[38;5;241m.\u001b[39mfit(data)\n", + "File \u001b[0;32m/opt/anaconda3/lib/python3.12/site-packages/surprise/prediction_algorithms/matrix_factorization.pyx:155\u001b[0m, in \u001b[0;36msurprise.prediction_algorithms.matrix_factorization.SVD.fit\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32m/opt/anaconda3/lib/python3.12/site-packages/surprise/prediction_algorithms/matrix_factorization.pyx:196\u001b[0m, in \u001b[0;36msurprise.prediction_algorithms.matrix_factorization.SVD.sgd\u001b[0;34m()\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'DatasetAutoFolds' object has no attribute 'n_users'" + ] + } + ], + "source": [ + "data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\n", + "algo = SVD(n_factors=50, random_state=0)\n", + "algo.fit(data)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "487779e0", + "metadata": {}, + "outputs": [], + "source": [ + "from surprise.dataset import DatasetAutoFolds\n", + "\n", + "reader = Reader(line_format='user item rating timestamp', sep=',', rating_scale=(0.5, 5))\n", + "data_folds = DatasetAutoFolds(ratings_file='./ml-latest-small/ratings_noh.csv', reader=reader)\n", + "trainset = data_folds.build_full_trainset()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0a4d8878", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "algo = SVD(n_epochs=20, n_factors=50, random_state=0)\n", + "algo.fit(trainset)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f5634997", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "사용자 아이디 9는 영화 아이디 42의 평점 없음\n", + " movieId title genres\n", + "38 42 Dead Presidents (1995) Action|Crime|Drama\n" + ] + } + ], + "source": [ + "movies = pd.read_csv('./ml-latest-small/movies.csv')\n", + "\n", + "movieIds = ratings[ratings['userId']==9]['movieId']\n", + "if movieIds[movieIds==42].count() == 0:\n", + " print('사용자 아이디 9는 영화 아이디 42의 평점 없음')\n", + "\n", + "print(movies[movies['movieId']==42])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "dbd052d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user: 9 item: 42 r_ui = None est = 3.13 {'was_impossible': False}\n" + ] + } + ], + "source": [ + "uid = str(9)\n", + "iid = str(42)\n", + "pred = algo.predict(uid, iid, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "fea66d8d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "평점 매긴 영화수: 46 추천대상 영화수: 9696 전체 영화수: 9742\n" + ] + } + ], + "source": [ + "def get_unseen_surprise(ratings, movies, userId):\n", + " seen_movies = ratings[ratings['userId']== userId]['movieId'].tolist()\n", + " total_movies = movies['movieId'].tolist()\n", + " unseen_movies= [movie for movie in total_movies if movie not in seen_movies]\n", + " print('평점 매긴 영화수:',len(seen_movies), '추천대상 영화수:',len(unseen_movies), \\\n", + " '전체 영화수:',len(total_movies))\n", + " return unseen_movies\n", + "\n", + "unseen_movies = get_unseen_surprise(ratings, movies, 9)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "428d80ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "평점 매긴 영화수: 46 추천대상 영화수: 9696 전체 영화수: 9742\n", + "##### Top-10 추천 영화 리스트 #####\n", + "Usual Suspects, The (1995) : 4.306302135700814\n", + "Star Wars: Episode IV - A New Hope (1977) : 4.281663842987387\n", + "Pulp Fiction (1994) : 4.278152632122758\n", + "Silence of the Lambs, The (1991) : 4.226073566460876\n", + "Godfather, The (1972) : 4.1918097904381995\n", + "Streetcar Named Desire, A (1951) : 4.154746591122658\n", + "Star Wars: Episode V - The Empire Strikes Back (1980) : 4.122016128534504\n", + "Star Wars: Episode VI - Return of the Jedi (1983) : 4.108009609093436\n", + "Goodfellas (1990) : 4.083464936588478\n", + "Glory (1989) : 4.07887165526957\n" + ] + } + ], + "source": [ + "def recomm_movie_by_surprise(algo, userId, unseen_movies, top_n=10):\n", + " predictions = [algo.predict(str(userId), str(movieId)) for movieId in unseen_movies]\n", + " def sortkey_est(pred):\n", + " return pred.est\n", + " \n", + " predictions.sort(key=sortkey_est, reverse=True)\n", + " top_predictions= predictions[:top_n]\n", + " \n", + " top_movie_ids = [ int(pred.iid) for pred in top_predictions]\n", + " top_movie_rating = [ pred.est for pred in top_predictions]\n", + " top_movie_titles = movies[movies.movieId.isin(top_movie_ids)]['title']\n", + " top_movie_preds = [ (id, title, rating) for id, title, rating in zip(top_movie_ids, top_movie_titles, top_movie_rating)]\n", + " \n", + " return top_movie_preds\n", + "\n", + "unseen_movies = get_unseen_surprise(ratings, movies, 9)\n", + "top_movie_preds = recomm_movie_by_surprise(algo, 9, unseen_movies, top_n=10)\n", + "print('##### Top-10 추천 영화 리스트 #####')\n", + "\n", + "for top_movie in top_movie_preds:\n", + " print(top_movie[1], \":\", top_movie[2])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git "a/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.pdf" "b/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.pdf" new file mode 100644 index 0000000..405bebb Binary files /dev/null and "b/week16_\341\204\213\341\205\250\341\204\211\341\205\263\341\206\270\341\204\200\341\205\252\341\204\214\341\205\246_\341\204\214\341\205\265\341\206\253\341\204\213\341\205\260\341\204\213\341\205\265\341\204\213\341\205\243\341\206\253.pdf" differ