diff --git "a/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb" "b/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb"
new file mode 100644
index 0000000..1cef1c1
--- /dev/null
+++ "b/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb"
@@ -0,0 +1,6981 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **05. 콘텐츠 기반 필터링 실습-TMDB 5000 영화 데이터 세트**\n",
+ "\n",
+ "**장르 속성을 이용한 영화 콘텐츠 기반 필터링**\n",
+ "* 특정 영화와 비슷한 특성/속성, 구성 요소 등을 가진 다른 영화 추천\n",
+ "* 영화 간의 유사성을 판단하는 기준이 영화를 구성하는 다양한 콘텐츠를 기반으로 하는 방식\n",
+ "\n",
+ "**데이터 로딩 및 가공**"
+ ],
+ "metadata": {
+ "id": "h7poBF8WG7ng"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import warnings; warnings.filterwarnings('ignore')\n",
+ "\n",
+ "movies = pd.read_csv('/content/tmdb_5000_movies.csv')\n",
+ "print(movies.shape)\n",
+ "movies.head(1)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 327
+ },
+ "id": "KILZrMfNH3oN",
+ "outputId": "d3db7976-21ff-4195-a69f-06a6687fff95"
+ },
+ "execution_count": 31,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 20)\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " budget \\\n",
+ "0 237000000 \n",
+ "\n",
+ " genres \\\n",
+ "0 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... \n",
+ "\n",
+ " homepage id \\\n",
+ "0 http://www.avatarmovie.com/ 19995 \n",
+ "\n",
+ " keywords \\\n",
+ "0 [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... \n",
+ "\n",
+ " original_language original_title \\\n",
+ "0 en Avatar \n",
+ "\n",
+ " overview \\\n",
+ "0 In the 22nd century, a paraplegic Marine is dispatched to the moon Pandora on a unique mission, ... \n",
+ "\n",
+ " popularity \\\n",
+ "0 150.437577 \n",
+ "\n",
+ " production_companies \\\n",
+ "0 [{\"name\": \"Ingenious Film Partners\", \"id\": 289}, {\"name\": \"Twentieth Century Fox Film Corporatio... \n",
+ "\n",
+ " production_countries \\\n",
+ "0 [{\"iso_3166_1\": \"US\", \"name\": \"United States of America\"}, {\"iso_3166_1\": \"GB\", \"name\": \"United ... \n",
+ "\n",
+ " release_date revenue runtime \\\n",
+ "0 2009-12-10 2787965087 162.0 \n",
+ "\n",
+ " spoken_languages \\\n",
+ "0 [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso_639_1\": \"es\", \"name\": \"Espa\\u00f1ol\"}] \n",
+ "\n",
+ " status tagline title vote_average vote_count \n",
+ "0 Released Enter the World of Pandora. Avatar 7.2 11800 "
+ ],
+ "text/html": [
+ "\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " budget | \n",
+ " genres | \n",
+ " homepage | \n",
+ " id | \n",
+ " keywords | \n",
+ " original_language | \n",
+ " original_title | \n",
+ " overview | \n",
+ " popularity | \n",
+ " production_companies | \n",
+ " production_countries | \n",
+ " release_date | \n",
+ " revenue | \n",
+ " runtime | \n",
+ " spoken_languages | \n",
+ " status | \n",
+ " tagline | \n",
+ " title | \n",
+ " vote_average | \n",
+ " vote_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 237000000 | \n",
+ " [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... | \n",
+ " http://www.avatarmovie.com/ | \n",
+ " 19995 | \n",
+ " [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... | \n",
+ " en | \n",
+ " Avatar | \n",
+ " In the 22nd century, a paraplegic Marine is dispatched to the moon Pandora on a unique mission, ... | \n",
+ " 150.437577 | \n",
+ " [{\"name\": \"Ingenious Film Partners\", \"id\": 289}, {\"name\": \"Twentieth Century Fox Film Corporatio... | \n",
+ " [{\"iso_3166_1\": \"US\", \"name\": \"United States of America\"}, {\"iso_3166_1\": \"GB\", \"name\": \"United ... | \n",
+ " 2009-12-10 | \n",
+ " 2787965087 | \n",
+ " 162.0 | \n",
+ " [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso_639_1\": \"es\", \"name\": \"Espa\\u00f1ol\"}] | \n",
+ " Released | \n",
+ " Enter the World of Pandora. | \n",
+ " Avatar | \n",
+ " 7.2 | \n",
+ " 11800 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "movies",
+ "summary": "{\n \"name\": \"movies\",\n \"rows\": 4803,\n \"fields\": [\n {\n \"column\": \"budget\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 40722391,\n \"min\": 0,\n \"max\": 380000000,\n \"num_unique_values\": 436,\n \"samples\": [\n 439000,\n 68000000,\n 700000\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1175,\n \"samples\": [\n \"[{\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 16, \\\"name\\\": \\\"Animation\\\"}]\",\n \"[{\\\"id\\\": 28, \\\"name\\\": \\\"Action\\\"}, {\\\"id\\\": 35, \\\"name\\\": \\\"Comedy\\\"}, {\\\"id\\\": 80, \\\"name\\\": \\\"Crime\\\"}, {\\\"id\\\": 18, \\\"name\\\": \\\"Drama\\\"}]\",\n \"[{\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 16, \\\"name\\\": \\\"Animation\\\"}, {\\\"id\\\": 10751, \\\"name\\\": \\\"Family\\\"}, {\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 878, \\\"name\\\": \\\"Science Fiction\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"homepage\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1691,\n \"samples\": [\n \"https://www.warnerbros.com/running-scared\",\n \"http://www.51birchstreet.com/index.php\",\n \"http://movies2.foxjapan.com/glee/\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 88694,\n \"min\": 5,\n \"max\": 459488,\n \"num_unique_values\": 4803,\n \"samples\": [\n 8427,\n 13006,\n 18041\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4222,\n \"samples\": [\n \"[{\\\"id\\\": 782, \\\"name\\\": \\\"assassin\\\"}, {\\\"id\\\": 1872, \\\"name\\\": \\\"loss of father\\\"}, {\\\"id\\\": 2908, \\\"name\\\": \\\"secret society\\\"}, {\\\"id\\\": 3045, \\\"name\\\": \\\"mission of murder\\\"}, {\\\"id\\\": 9748, \\\"name\\\": \\\"revenge\\\"}]\",\n \"[{\\\"id\\\": 2987, \\\"name\\\": \\\"gang war\\\"}, {\\\"id\\\": 4942, \\\"name\\\": \\\"victim of murder\\\"}, {\\\"id\\\": 5332, \\\"name\\\": \\\"greed\\\"}, {\\\"id\\\": 6062, \\\"name\\\": \\\"hostility\\\"}, {\\\"id\\\": 156212, \\\"name\\\": \\\"spaghetti western\\\"}]\",\n \"[{\\\"id\\\": 703, \\\"name\\\": \\\"detective\\\"}, {\\\"id\\\": 1299, \\\"name\\\": \\\"monster\\\"}, {\\\"id\\\": 6101, \\\"name\\\": \\\"engine\\\"}, {\\\"id\\\": 10988, \\\"name\\\": \\\"based on tv series\\\"}, {\\\"id\\\": 15162, \\\"name\\\": \\\"dog\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"original_language\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 37,\n \"samples\": [\n \"xx\",\n \"ta\",\n \"es\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"original_title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4801,\n \"samples\": [\n \"I Spy\",\n \"Love Letters\",\n \"Sleepover\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"overview\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4800,\n \"samples\": [\n \"When the Switchblade, the most sophisticated prototype stealth fighter created yet, is stolen from the U.S. government, one of the United States' top spies, Alex Scott, is called to action. What he doesn't expect is to get teamed up with a cocky civilian, World Class Boxing Champion Kelly Robinson, on a dangerous top secret espionage mission. Their assignment: using equal parts skill and humor, catch Arnold Gundars, one of the world's most successful arms dealers.\",\n \"When \\\"street smart\\\" rapper Christopher \\\"C-Note\\\" Hawkins (Big Boi) applies for a membership to all-white Carolina Pines Country Club, the establishment's proprietors are hardly ready to oblige him.\",\n \"As their first year of high school looms ahead, best friends Julie, Hannah, Yancy and Farrah have one last summer sleepover. Little do they know they're about to embark on the adventure of a lifetime. Desperate to shed their nerdy status, they take part in a night-long scavenger hunt that pits them against their popular archrivals. Everything under the sun goes on -- from taking Yancy's father's car to sneaking into nightclubs!\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 31.816649749537806,\n \"min\": 0.0,\n \"max\": 875.581305,\n \"num_unique_values\": 4802,\n \"samples\": [\n 13.267631,\n 0.010909,\n 5.842299\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"production_companies\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3697,\n \"samples\": [\n \"[{\\\"name\\\": \\\"Paramount Pictures\\\", \\\"id\\\": 4}, {\\\"name\\\": \\\"Cherry Alley Productions\\\", \\\"id\\\": 2232}]\",\n \"[{\\\"name\\\": \\\"Twentieth Century Fox Film Corporation\\\", \\\"id\\\": 306}, {\\\"name\\\": \\\"Dune Entertainment\\\", \\\"id\\\": 444}, {\\\"name\\\": \\\"Regency Enterprises\\\", \\\"id\\\": 508}, {\\\"name\\\": \\\"Guy Walks into a Bar Productions\\\", \\\"id\\\": 2645}, {\\\"name\\\": \\\"Deep River Productions\\\", \\\"id\\\": 2646}, {\\\"name\\\": \\\"Friendly Films (II)\\\", \\\"id\\\": 81136}]\",\n \"[{\\\"name\\\": \\\"Twentieth Century Fox Film Corporation\\\", \\\"id\\\": 306}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"production_countries\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 469,\n \"samples\": [\n \"[{\\\"iso_3166_1\\\": \\\"ES\\\", \\\"name\\\": \\\"Spain\\\"}, {\\\"iso_3166_1\\\": \\\"GB\\\", \\\"name\\\": \\\"United Kingdom\\\"}, {\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}, {\\\"iso_3166_1\\\": \\\"FR\\\", \\\"name\\\": \\\"France\\\"}]\",\n \"[{\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}, {\\\"iso_3166_1\\\": \\\"CA\\\", \\\"name\\\": \\\"Canada\\\"}, {\\\"iso_3166_1\\\": \\\"DE\\\", \\\"name\\\": \\\"Germany\\\"}]\",\n \"[{\\\"iso_3166_1\\\": \\\"DE\\\", \\\"name\\\": \\\"Germany\\\"}, {\\\"iso_3166_1\\\": \\\"ES\\\", \\\"name\\\": \\\"Spain\\\"}, {\\\"iso_3166_1\\\": \\\"GB\\\", \\\"name\\\": \\\"United Kingdom\\\"}, {\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"release_date\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 3280,\n \"samples\": [\n \"1966-10-16\",\n \"1987-07-31\",\n \"1993-09-23\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"revenue\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 162857100,\n \"min\": 0,\n \"max\": 2787965087,\n \"num_unique_values\": 3297,\n \"samples\": [\n 11833696,\n 10462500,\n 17807569\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"runtime\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 22.611934588844207,\n \"min\": 0.0,\n \"max\": 338.0,\n \"num_unique_values\": 156,\n \"samples\": [\n 74.0,\n 85.0,\n 170.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"spoken_languages\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 544,\n \"samples\": [\n \"[{\\\"iso_639_1\\\": \\\"es\\\", \\\"name\\\": \\\"Espa\\\\u00f1ol\\\"}, {\\\"iso_639_1\\\": \\\"en\\\", \\\"name\\\": \\\"English\\\"}, {\\\"iso_639_1\\\": \\\"fr\\\", \\\"name\\\": \\\"Fran\\\\u00e7ais\\\"}, {\\\"iso_639_1\\\": \\\"hu\\\", \\\"name\\\": \\\"Magyar\\\"}]\",\n \"[{\\\"iso_639_1\\\": \\\"en\\\", \\\"name\\\": \\\"English\\\"}, {\\\"iso_639_1\\\": \\\"it\\\", \\\"name\\\": \\\"Italiano\\\"}, {\\\"iso_639_1\\\": \\\"pt\\\", \\\"name\\\": \\\"Portugu\\\\u00eas\\\"}]\",\n \"[{\\\"iso_639_1\\\": \\\"de\\\", \\\"name\\\": \\\"Deutsch\\\"}, {\\\"iso_639_1\\\": \\\"it\\\", \\\"name\\\": \\\"Italiano\\\"}, {\\\"iso_639_1\\\": \\\"la\\\", \\\"name\\\": \\\"Latin\\\"}, {\\\"iso_639_1\\\": \\\"pl\\\", \\\"name\\\": \\\"Polski\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"status\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Released\",\n \"Post Production\",\n \"Rumored\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"tagline\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3944,\n \"samples\": [\n \"When you're 17, every day is war.\",\n \"An Unspeakable Horror. A Creative Genius. Captured For Eternity.\",\n \"May the schwartz be with you\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4800,\n \"samples\": [\n \"I Spy\",\n \"Who's Your Caddy?\",\n \"Sleepover\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.1946121628478925,\n \"min\": 0.0,\n \"max\": 10.0,\n \"num_unique_values\": 71,\n \"samples\": [\n 5.1,\n 7.2,\n 4.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1234,\n \"min\": 0,\n \"max\": 13752,\n \"num_unique_values\": 1609,\n \"samples\": [\n 7604,\n 3428,\n 225\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 31
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']]"
+ ],
+ "metadata": {
+ "id": "5ElyMDBcIMde"
+ },
+ "execution_count": 32,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* ‘genres’, ‘keywords’ 등과 같은 칼럼을 보면 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\":\n",
+ "\"Adventure\"}]와 같이 파이썬 리스트(list) 내부에 여러 개의 딕셔너리(diet)가 있는 형태의 문자열로\n",
+ "표기돼 있음"
+ ],
+ "metadata": {
+ "id": "b11_PCmcMqko"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "pd.set_option('max_colwidth', 100)\n",
+ "movies_df[['genres', 'keywords']][:1]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 81
+ },
+ "id": "hiMmTFFkNDCX",
+ "outputId": "23adf098-b8a5-4253-eb45-170b4087863f"
+ },
+ "execution_count": 33,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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... "
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " genres | \n",
+ " keywords | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... | \n",
+ " [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['genres', 'keywords']][:1]\",\n \"rows\": 1,\n \"fields\": [\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"[{\\\"id\\\": 28, \\\"name\\\": \\\"Action\\\"}, {\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 878, \\\"name\\\": \\\"Science Fiction\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"[{\\\"id\\\": 1463, \\\"name\\\": \\\"culture clash\\\"}, {\\\"id\\\": 2964, \\\"name\\\": \\\"future\\\"}, {\\\"id\\\": 3386, \\\"name\\\": \\\"space war\\\"}, {\\\"id\\\": 3388, \\\"name\\\": \\\"space colony\\\"}, {\\\"id\\\": 3679, \\\"name\\\": \\\"society\\\"}, {\\\"id\\\": 3801, \\\"name\\\": \\\"space travel\\\"}, {\\\"id\\\": 9685, \\\"name\\\": \\\"futuristic\\\"}, {\\\"id\\\": 9840, \\\"name\\\": \\\"romance\\\"}, {\\\"id\\\": 9882, \\\"name\\\": \\\"space\\\"}, {\\\"id\\\": 9951, \\\"name\\\": \\\"alien\\\"}, {\\\"id\\\": 10148, \\\"name\\\": \\\"tribe\\\"}, {\\\"id\\\": 10158, \\\"name\\\": \\\"alien planet\\\"}, {\\\"id\\\": 10987, \\\"name\\\": \\\"cgi\\\"}, {\\\"id\\\": 11399, \\\"name\\\": \\\"marine\\\"}, {\\\"id\\\": 13065, \\\"name\\\": \\\"soldier\\\"}, {\\\"id\\\": 14643, \\\"name\\\": \\\"battle\\\"}, {\\\"id\\\": 14720, \\\"name\\\": \\\"love affair\\\"}, {\\\"id\\\": 165431, \\\"name\\\": \\\"anti war\\\"}, {\\\"id\\\": 193554, \\\"name\\\": \\\"power relations\\\"}, {\\\"id\\\": 206690, \\\"name\\\": \\\"mind and soul\\\"}, {\\\"id\\\": 209714, \\\"name\\\": \\\"3d\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 33
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from ast import literal_eval\n",
+ "import numpy as np\n",
+ "\n",
+ "def safe_literal_eval(val):\n",
+ " if isinstance(val, str):\n",
+ " try:\n",
+ " return literal_eval(val)\n",
+ " except (ValueError, SyntaxError):\n",
+ " return []\n",
+ " elif val is np.nan:\n",
+ " return []\n",
+ " else:\n",
+ " return []\n",
+ "\n",
+ "movies_df['genres'] = movies_df['genres'].apply(safe_literal_eval)\n",
+ "movies_df['keywords'] = movies_df['keywords'].apply(safe_literal_eval)"
+ ],
+ "metadata": {
+ "id": "BbiyDYBFcnrf"
+ },
+ "execution_count": 34,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 81
+ },
+ "id": "xTnEmykJebTh",
+ "outputId": "181e222c-44fe-4760-edb2-316eb84252d8"
+ },
+ "execution_count": 35,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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... "
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " genres | \n",
+ " keywords | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " [Action, Adventure, Fantasy, Science Fiction] | \n",
+ " [culture clash, future, space war, space colony, society, space travel, futuristic, romance, spa... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['genres', 'keywords']][:1]\",\n \"rows\": 1,\n \"fields\": [\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 35
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**장르 콘텐츠 유사도 측정**\n",
+ "* 문자열로 변환된 genres 칼럼을 Count 기반으로 피처 벡터화 변환\n",
+ "* genres 문자열을 피처 벡터화 행렬로 변환한 데이터 세트로 코사인 유사도를 통해 비교합니다. 이를 위해 데이터 세트의 레크드별로 타 레코드와 장르에서 코사인 유사도 값을 가지는 객체를 생성\n",
+ "* 장르 유사도가 높은 영화 중에 평점이 높은 순으로 영화를 추천"
+ ],
+ "metadata": {
+ "id": "BKDGHUopfZf3"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "bTjG5FRJgcEZ",
+ "outputId": "8797f611-216e-41f4-c1a2-dfa6c3f0b6cc"
+ },
+ "execution_count": 36,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 276)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ ""
+ ],
+ "metadata": {
+ "id": "oLDUmAkAi2dt"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "gd4dp9hRi4rR",
+ "outputId": "13211c95-46d8-4f37-e6bf-46cde96924b1"
+ },
+ "execution_count": 37,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 4803)\n",
+ "[[1. 0.59628479 0.4472136 ... 0. 0. 0. ]\n",
+ " [0.59628479 1. 0.4 ... 0. 0. 0. ]]\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]\n",
+ "print(genre_sim_sorted_ind[:1])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "v7bPtl5ljscw",
+ "outputId": "079d023a-d209-48f2-c4c0-c6d5b41dd75b"
+ },
+ "execution_count": 38,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "[[ 0 46 3494 ... 3331 3333 2031]]\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**장르 콘텐츠 필터링을 이용한 영화추천**"
+ ],
+ "metadata": {
+ "id": "cX1SHNLcnUSM"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def find_sim_movie(df, sorted_ind, title_name, top_n = 10):\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]"
+ ],
+ "metadata": {
+ "id": "EwMpgfulnXQR"
+ },
+ "execution_count": 39,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)\n",
+ "similar_movies[['title', 'vote_average']]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 381
+ },
+ "id": "gFbJliblnw-a",
+ "outputId": "387d11dc-6bd1-47fe-ffb8-e0e9f0f4f6ac"
+ },
+ "execution_count": 40,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "[[1881 3378 3866 1370 1464 588 3887 3594 2839 892]]\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " title vote_average\n",
+ "1881 The Shawshank Redemption 8.5\n",
+ "3378 Auto Focus 6.1\n",
+ "3866 City of God 8.1\n",
+ "1370 21 6.5\n",
+ "1464 Black Water Transit 0.0\n",
+ "588 Wall Street: Money Never Sleeps 5.8\n",
+ "3887 Trainspotting 7.8\n",
+ "3594 Spring Breakers 5.0\n",
+ "2839 Rounders 6.9\n",
+ "892 Casino 7.8"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ "
\n",
+ " \n",
+ " | 3378 | \n",
+ " Auto Focus | \n",
+ " 6.1 | \n",
+ "
\n",
+ " \n",
+ " | 3866 | \n",
+ " City of God | \n",
+ " 8.1 | \n",
+ "
\n",
+ " \n",
+ " | 1370 | \n",
+ " 21 | \n",
+ " 6.5 | \n",
+ "
\n",
+ " \n",
+ " | 1464 | \n",
+ " Black Water Transit | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 588 | \n",
+ " Wall Street: Money Never Sleeps | \n",
+ " 5.8 | \n",
+ "
\n",
+ " \n",
+ " | 3887 | \n",
+ " Trainspotting | \n",
+ " 7.8 | \n",
+ "
\n",
+ " \n",
+ " | 3594 | \n",
+ " Spring Breakers | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | 2839 | \n",
+ " Rounders | \n",
+ " 6.9 | \n",
+ "
\n",
+ " \n",
+ " | 892 | \n",
+ " Casino | \n",
+ " 7.8 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"similar_movies[['title', 'vote_average']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Rounders\",\n \"Auto Focus\",\n \"Wall Street: Money Never Sleeps\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2.4636242498490803,\n \"min\": 0.0,\n \"max\": 8.5,\n \"num_unique_values\": 9,\n \"samples\": [\n 5.0,\n 6.1,\n 5.8\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 40
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df[['title', 'vote_average', 'vote_count']].sort_values('vote_average', ascending=False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "NoHMGc3Iqdpu",
+ "outputId": "854d8884-74ac-4e8d-f76d-13b37f5c1bbb"
+ },
+ "execution_count": 41,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " title vote_average vote_count\n",
+ "4662 Little Big Top 10.0 1\n",
+ "3519 Stiff Upper Lips 10.0 1\n",
+ "4045 Dancer, Texas Pop. 81 10.0 1\n",
+ "4247 Me You and Five Bucks 10.0 2\n",
+ "3992 Sardaarji 9.5 2\n",
+ "2386 One Man's Hero 9.3 2\n",
+ "1881 The Shawshank Redemption 8.5 8205\n",
+ "2970 There Goes My Baby 8.5 2\n",
+ "3337 The Godfather 8.4 5893\n",
+ "2796 The Prisoner of Zenda 8.4 11"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " vote_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 4662 | \n",
+ " Little Big Top | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 3519 | \n",
+ " Stiff Upper Lips | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 4045 | \n",
+ " Dancer, Texas Pop. 81 | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 4247 | \n",
+ " Me You and Five Bucks | \n",
+ " 10.0 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " | 3992 | \n",
+ " Sardaarji | \n",
+ " 9.5 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " | 2386 | \n",
+ " One Man's Hero | \n",
+ " 9.3 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ " 8205 | \n",
+ "
\n",
+ " \n",
+ " | 2970 | \n",
+ " There Goes My Baby | \n",
+ " 8.5 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " | 3337 | \n",
+ " The Godfather | \n",
+ " 8.4 | \n",
+ " 5893 | \n",
+ "
\n",
+ " \n",
+ " | 2796 | \n",
+ " The Prisoner of Zenda | \n",
+ " 8.4 | \n",
+ " 11 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['title', 'vote_average', 'vote_count']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"The Godfather\",\n \"Stiff Upper Lips\",\n \"One Man's Hero\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.7366591251499343,\n \"min\": 8.4,\n \"max\": 10.0,\n \"num_unique_values\": 5,\n \"samples\": [\n 9.5,\n 8.4,\n 9.3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3020,\n \"min\": 1,\n \"max\": 8205,\n \"num_unique_values\": 5,\n \"samples\": [\n 2,\n 11,\n 8205\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 41
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* 왜곡된 평점 데이터를 회피할 수 있도록 평점에 평가 횟수를 반영할 수 있는 새로운 평가 방식이 필요\n",
+ "\n",
+ "\n",
+ "```\n",
+ "가중평점(Weighted Rating) = (v/(v+m))*R +(m/(v+m))*C\n",
+ "```\n",
+ "* v: 개별 영화에 평점을 투표한 횟수: vote_count\n",
+ "* m: 평점을 부여하기 위한 최소 투표 횟수\n",
+ "* R: 개별 영화에 대한 평균 평점: vote_average\n",
+ "* C: 젼체 영화에 대한 평균 평점\n",
+ "\n"
+ ],
+ "metadata": {
+ "id": "MNXToqXeq3Hk"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Gp4RH5-IrVqD",
+ "outputId": "f300ced6-5e0d-41fb-c7d9-52dd9af737c9"
+ },
+ "execution_count": 42,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "C: 6.092 m: 370.2\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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.apply(weighted_vote_average, axis =1)"
+ ],
+ "metadata": {
+ "id": "CL3Q42bPrsJP"
+ },
+ "execution_count": 43,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']].sort_values('weighted_vote', ascending = False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "Ac2BxbuXsLU1",
+ "outputId": "c73e4b1f-7aad-4224-9ce0-6e1cfbcb717e"
+ },
+ "execution_count": 44,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " weighted_vote | \n",
+ " vote_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ " 8.396052 | \n",
+ " 8205 | \n",
+ "
\n",
+ " \n",
+ " | 3337 | \n",
+ " The Godfather | \n",
+ " 8.4 | \n",
+ " 8.263591 | \n",
+ " 5893 | \n",
+ "
\n",
+ " \n",
+ " | 662 | \n",
+ " Fight Club | \n",
+ " 8.3 | \n",
+ " 8.216455 | \n",
+ " 9413 | \n",
+ "
\n",
+ " \n",
+ " | 3232 | \n",
+ " Pulp Fiction | \n",
+ " 8.3 | \n",
+ " 8.207102 | \n",
+ " 8428 | \n",
+ "
\n",
+ " \n",
+ " | 65 | \n",
+ " The Dark Knight | \n",
+ " 8.2 | \n",
+ " 8.136930 | \n",
+ " 12002 | \n",
+ "
\n",
+ " \n",
+ " | 1818 | \n",
+ " Schindler's List | \n",
+ " 8.3 | \n",
+ " 8.126069 | \n",
+ " 4329 | \n",
+ "
\n",
+ " \n",
+ " | 3865 | \n",
+ " Whiplash | \n",
+ " 8.3 | \n",
+ " 8.123248 | \n",
+ " 4254 | \n",
+ "
\n",
+ " \n",
+ " | 809 | \n",
+ " Forrest Gump | \n",
+ " 8.2 | \n",
+ " 8.105954 | \n",
+ " 7927 | \n",
+ "
\n",
+ " \n",
+ " | 2294 | \n",
+ " Spirited Away | \n",
+ " 8.3 | \n",
+ " 8.105867 | \n",
+ " 3840 | \n",
+ "
\n",
+ " \n",
+ " | 2731 | \n",
+ " The Godfather: Part II | \n",
+ " 8.3 | \n",
+ " 8.079586 | \n",
+ " 3338 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Spirited Away\",\n \"The Godfather\",\n \"Schindler's List\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08755950357709151,\n \"min\": 8.2,\n \"max\": 8.5,\n \"num_unique_values\": 4,\n \"samples\": [\n 8.4,\n 8.2,\n 8.5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"weighted_vote\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.09696608479450805,\n \"min\": 8.07958629828635,\n \"max\": 8.39605162693645,\n \"num_unique_values\": 10,\n \"samples\": [\n 8.105867158639835,\n 8.263590802034972,\n 8.126068673669016\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2866,\n \"min\": 3338,\n \"max\": 12002,\n \"num_unique_values\": 10,\n \"samples\": [\n 3840,\n 5893,\n 4329\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 44
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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']]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "9saSifP2s7xV",
+ "outputId": "58c0b9e1-0ce1-45ea-9544-60fe28dbbc7e"
+ },
+ "execution_count": 45,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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",
+ "3887 Trainspotting 7.8 7.591009\n",
+ "883 Catch Me If You Can 7.7 7.557097\n",
+ "892 Casino 7.8 7.423040\n",
+ "4041 This Is England 7.4 6.739664\n",
+ "1149 American Hustle 6.8 6.717525"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " weighted_vote | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ " 8.396052 | \n",
+ "
\n",
+ " \n",
+ " | 2731 | \n",
+ " The Godfather: Part II | \n",
+ " 8.3 | \n",
+ " 8.079586 | \n",
+ "
\n",
+ " \n",
+ " | 1847 | \n",
+ " GoodFellas | \n",
+ " 8.2 | \n",
+ " 7.976937 | \n",
+ "
\n",
+ " \n",
+ " | 3866 | \n",
+ " City of God | \n",
+ " 8.1 | \n",
+ " 7.759693 | \n",
+ "
\n",
+ " \n",
+ " | 1663 | \n",
+ " Once Upon a Time in America | \n",
+ " 8.2 | \n",
+ " 7.657811 | \n",
+ "
\n",
+ " \n",
+ " | 3887 | \n",
+ " Trainspotting | \n",
+ " 7.8 | \n",
+ " 7.591009 | \n",
+ "
\n",
+ " \n",
+ " | 883 | \n",
+ " Catch Me If You Can | \n",
+ " 7.7 | \n",
+ " 7.557097 | \n",
+ "
\n",
+ " \n",
+ " | 892 | \n",
+ " Casino | \n",
+ " 7.8 | \n",
+ " 7.423040 | \n",
+ "
\n",
+ " \n",
+ " | 4041 | \n",
+ " This Is England | \n",
+ " 7.4 | \n",
+ " 6.739664 | \n",
+ "
\n",
+ " \n",
+ " | 1149 | \n",
+ " American Hustle | \n",
+ " 6.8 | \n",
+ " 6.717525 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"similar_movies[['title', 'vote_average', 'weighted_vote']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"This Is England\",\n \"The Godfather: Part II\",\n \"Trainspotting\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5006662228138289,\n \"min\": 6.8,\n \"max\": 8.5,\n \"num_unique_values\": 8,\n \"samples\": [\n 8.3,\n 7.7,\n 8.5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"weighted_vote\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5360110086094774,\n \"min\": 6.717525466229835,\n \"max\": 8.39605162693645,\n \"num_unique_values\": 10,\n \"samples\": [\n 6.739664363482589,\n 8.07958629828635,\n 7.591009490713154\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 45
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **06.아이템 기반 최근접 이웃 협업 필터링 실습**"
+ ],
+ "metadata": {
+ "id": "EPvqs026vyzE"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "print(movies.shape)\n",
+ "print(ratings.shape)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ZRnCRT66v76Y",
+ "outputId": "1531ef33-a569-4772-9553-722b183c5be4"
+ },
+ "execution_count": 46,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(9742, 3)\n",
+ "(100836, 4)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings = ratings[['userId', 'movieId', 'rating']]\n",
+ "#pivot_table()의 인자로 columns = 'movieId'와 같이 부여하면 movieId 칼럼의 모든 값이 새로운 칼럼 이름으로 변환\n",
+ "ratings_matrix = ratings.pivot_table('rating', index = 'userId', columns ='movieId')\n",
+ "ratings_matrix.head(3)"
+ ],
+ "metadata": {
+ "id": "2XjxD8d0w825",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 204
+ },
+ "outputId": "719af044-877b-4e88-ac1d-4b5b68932c8d"
+ },
+ "execution_count": 47,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | movieId | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 3 | \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 6 | \n",
+ " 7 | \n",
+ " 8 | \n",
+ " 9 | \n",
+ " 10 | \n",
+ " ... | \n",
+ " 193565 | \n",
+ " 193567 | \n",
+ " 193571 | \n",
+ " 193573 | \n",
+ " 193579 | \n",
+ " 193581 | \n",
+ " 193583 | \n",
+ " 193585 | \n",
+ " 193587 | \n",
+ " 193609 | \n",
+ "
\n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 9724 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 47
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* NaN 값이 많음: 사용자가 평점을 매기지 않은 영화가 칼럼으로 변환\n",
+ "* 최소평점이 0 => NaN은 모두 0으로 변환 NaN 값이 많음: 사용자가 평점을 매기지 않은 영화가 칼럼으로 변환\n",
+ "* 최소평점이 0 => NaN은 모두 0으로 변환"
+ ],
+ "metadata": {
+ "id": "TZUOwitLpKH4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "ZYUN-yaVpmhf",
+ "outputId": "d7e20cf8-9d0e-4aa1-cf44-348c943b1e73"
+ },
+ "execution_count": 48,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ "
\n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 9719 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 48
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**영화 간 유사도 산출**\n",
+ "* cosine_similarity()는 행 간의 유사도 산출 but, 위 데이터에서는 영화가 아닌 userID간의 유사도가 산출됨"
+ ],
+ "metadata": {
+ "id": "-B-F7w8Fq0lk"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_matrix_T = ratings_matrix.transpose()\n",
+ "ratings_matrix_T.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 204
+ },
+ "id": "sI7bKBD2raXy",
+ "outputId": "48072932-af63-43fc-c201-1cb1f5805ee6"
+ },
+ "execution_count": 49,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "userId 1 2 3 4 5 6 7 \\\n",
+ "title \n",
+ "'71 (2014) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
+ "'Hellboy': The Seeds of Creation (2004) 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 \n",
+ "\n",
+ "userId 8 9 10 ... 601 602 603 \\\n",
+ "title ... \n",
+ "'71 (2014) 0.0 0.0 0.0 ... 0.0 0.0 0.0 \n",
+ "'Hellboy': The Seeds of Creation (2004) 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 \n",
+ "\n",
+ "userId 604 605 606 607 608 609 610 \n",
+ "title \n",
+ "'71 (2014) 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 \n",
+ "'Round Midnight (1986) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
+ "\n",
+ "[3 rows x 610 columns]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 3 | \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 6 | \n",
+ " 7 | \n",
+ " 8 | \n",
+ " 9 | \n",
+ " 10 | \n",
+ " ... | \n",
+ " 601 | \n",
+ " 602 | \n",
+ " 603 | \n",
+ " 604 | \n",
+ " 605 | \n",
+ " 606 | \n",
+ " 607 | \n",
+ " 608 | \n",
+ " 609 | \n",
+ " 610 | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | '71 (2014) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ "
\n",
+ " \n",
+ " | 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 'Round Midnight (1986) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 610 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix_T"
+ }
+ },
+ "metadata": {},
+ "execution_count": 49
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from sklearn.metrics.pairwise import cosine_similarity\n",
+ "\n",
+ "item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)\n",
+ "\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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 485
+ },
+ "id": "LLg_HOABvdQQ",
+ "outputId": "2815ccfa-189c-45c6-c180-76510ec6f607"
+ },
+ "execution_count": 50,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(9719, 9719)\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | '71 (2014) | \n",
+ " 1.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.141653 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.342055 | \n",
+ " 0.543305 | \n",
+ " 0.707107 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.139431 | \n",
+ " 0.327327 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 0.0 | \n",
+ " 1.000000 | \n",
+ " 0.707107 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 'Round Midnight (1986) | \n",
+ " 0.0 | \n",
+ " 0.707107 | \n",
+ " 1.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.176777 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 9719 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "item_sim_df"
+ }
+ },
+ "metadata": {},
+ "execution_count": 50
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "item_sim_df[\"Godfather, The (1972)\"].sort_values(ascending=False)[:6]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 303
+ },
+ "id": "U2H_-HrYwSVw",
+ "outputId": "84922a7f-cf0b-4d6d-89cc-451c035638e4"
+ },
+ "execution_count": 51,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Godfather, The (1972) | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Godfather, The (1972) | \n",
+ " 1.000000 | \n",
+ "
\n",
+ " \n",
+ " | Godfather: Part II, The (1974) | \n",
+ " 0.821773 | \n",
+ "
\n",
+ " \n",
+ " | Goodfellas (1990) | \n",
+ " 0.664841 | \n",
+ "
\n",
+ " \n",
+ " | One Flew Over the Cuckoo's Nest (1975) | \n",
+ " 0.620536 | \n",
+ "
\n",
+ " \n",
+ " | Star Wars: Episode IV - A New Hope (1977) | \n",
+ " 0.595317 | \n",
+ "
\n",
+ " \n",
+ " | Fargo (1996) | \n",
+ " 0.588614 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 51
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "item_sim_df[\"Inception (2010)\"].sort_values(ascending=False)[1:6]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 272
+ },
+ "id": "6WdgQhjiwmjR",
+ "outputId": "ebcca204-3fbb-4242-ccdc-8e8bf25201e6"
+ },
+ "execution_count": 52,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Inception (2010) | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Dark Knight, The (2008) | \n",
+ " 0.727263 | \n",
+ "
\n",
+ " \n",
+ " | Inglourious Basterds (2009) | \n",
+ " 0.646103 | \n",
+ "
\n",
+ " \n",
+ " | Shutter Island (2010) | \n",
+ " 0.617736 | \n",
+ "
\n",
+ " \n",
+ " | Dark Knight Rises, The (2012) | \n",
+ " 0.617504 | \n",
+ "
\n",
+ " \n",
+ " | Fight Club (1999) | \n",
+ " 0.615417 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 52
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천**\n",
+ ""
+ ],
+ "metadata": {
+ "id": "7kVnf14sw3_v"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ ""
+ ],
+ "metadata": {
+ "id": "5fiwPSG48Nr7"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* N값은 아이템의 최근접 이웃 범위 계수를 의미-> 특정 아이템과 유사도가 가장 높은 Top-N개의 다른 아이템을 추출하는데 사용"
+ ],
+ "metadata": {
+ "id": "Pb7HZydc8e-i"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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"
+ ],
+ "metadata": {
+ "id": "Gm7XQcyY9I86"
+ },
+ "execution_count": 53,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#예측 평점\n",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "aaHeQJkeqzS0",
+ "outputId": "b3de8ff7-4235-48c3-eead-c51fc1a20b21"
+ },
+ "execution_count": 54,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ "
\n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 0.070345 | \n",
+ " 0.577855 | \n",
+ " 0.321696 | \n",
+ " 0.227055 | \n",
+ " 0.206958 | \n",
+ " 0.194615 | \n",
+ " 0.249883 | \n",
+ " 0.102542 | \n",
+ " 0.157084 | \n",
+ " 0.178197 | \n",
+ " ... | \n",
+ " 0.113608 | \n",
+ " 0.181738 | \n",
+ " 0.133962 | \n",
+ " 0.128574 | \n",
+ " 0.006179 | \n",
+ " 0.212070 | \n",
+ " 0.192921 | \n",
+ " 0.136024 | \n",
+ " 0.292955 | \n",
+ " 0.720347 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 0.018260 | \n",
+ " 0.042744 | \n",
+ " 0.018861 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.035995 | \n",
+ " 0.013413 | \n",
+ " 0.002314 | \n",
+ " 0.032213 | \n",
+ " 0.014863 | \n",
+ " ... | \n",
+ " 0.015640 | \n",
+ " 0.020855 | \n",
+ " 0.020119 | \n",
+ " 0.015745 | \n",
+ " 0.049983 | \n",
+ " 0.014876 | \n",
+ " 0.021616 | \n",
+ " 0.024528 | \n",
+ " 0.017563 | \n",
+ " 0.000000 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 0.011884 | \n",
+ " 0.030279 | \n",
+ " 0.064437 | \n",
+ " 0.003762 | \n",
+ " 0.003749 | \n",
+ " 0.002722 | \n",
+ " 0.014625 | \n",
+ " 0.002085 | \n",
+ " 0.005666 | \n",
+ " 0.006272 | \n",
+ " ... | \n",
+ " 0.006923 | \n",
+ " 0.011665 | \n",
+ " 0.011800 | \n",
+ " 0.012225 | \n",
+ " 0.000000 | \n",
+ " 0.008194 | \n",
+ " 0.007017 | \n",
+ " 0.009229 | \n",
+ " 0.010420 | \n",
+ " 0.084501 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 9719 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_pred_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 54
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#실제 평점과의 차이\n",
+ "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))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "svZOAsty--90",
+ "outputId": "519c8ad1-ceff-4dc8-8825-fa207d71a04d"
+ },
+ "execution_count": 55,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "아이템 기반 모든 최근접 이웃 MSE: 9.895354759094706\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ " return pred"
+ ],
+ "metadata": {
+ "id": "8oTvBsktAPNK"
+ },
+ "execution_count": 56,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "ratings_pred_matrix = pd.DataFrame(data = ratings_pred, index = ratings_matrix.index, columns= ratings_matrix.columns)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "zb1_ffRyBFeq",
+ "outputId": "68bd82c5-fa6c-40c9-d822-7f1ba1e665b7"
+ },
+ "execution_count": 57,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "아이템 기반 최근접 Top-20 이웃 MSE: 3.694409449382562\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "user_rating_id = ratings_matrix.loc[9, :]\n",
+ "user_rating_id[user_rating_id >0].sort_values(ascending=False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 429
+ },
+ "id": "R5Bqv4mJBhiD",
+ "outputId": "8db5c4d2-feae-48ee-938f-9f455b3cace6"
+ },
+ "execution_count": 58,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "title\n",
+ "Adaptation (2002) 5.0\n",
+ "Austin Powers in Goldmember (2002) 5.0\n",
+ "Back to the Future (1985) 5.0\n",
+ "Citizen Kane (1941) 5.0\n",
+ "Lord of the Rings: The Fellowship of the Ring, The (2001) 5.0\n",
+ "Lord of the Rings: The Two Towers, The (2002) 5.0\n",
+ "Producers, The (1968) 5.0\n",
+ "Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) 5.0\n",
+ "Elling (2001) 4.0\n",
+ "King of Comedy, The (1983) 4.0\n",
+ "Name: 9, dtype: float64"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 9 | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Adaptation (2002) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Austin Powers in Goldmember (2002) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Back to the Future (1985) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Citizen Kane (1941) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Lord of the Rings: The Fellowship of the Ring, The (2001) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Lord of the Rings: The Two Towers, The (2002) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Producers, The (1968) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) | \n",
+ " 5.0 | \n",
+ "
\n",
+ " \n",
+ " | Elling (2001) | \n",
+ " 4.0 | \n",
+ "
\n",
+ " \n",
+ " | King of Comedy, The (1983) | \n",
+ " 4.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 58
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_unseen_movies(ratings_matrix, userId):\n",
+ " user_rating = ratings_matrix.loc[userId, :]\n",
+ " already_seen = user_rating[user_rating >0].index.tolist()\n",
+ " movies_list = ratings_matrix.columns.tolist()\n",
+ " unseen_list = [movie for movie in movies_list if movie not in already_seen]\n",
+ " return unseen_list"
+ ],
+ "metadata": {
+ "id": "Ad--lUP_BvfG"
+ },
+ "execution_count": 59,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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,\n",
+ " columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 394
+ },
+ "id": "HynFIaJqCYX5",
+ "outputId": "cb11513f-726e-4dfd-d706-915beda9a938"
+ },
+ "execution_count": 60,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " pred_score | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Shrek (2001) | \n",
+ " 0.866202 | \n",
+ "
\n",
+ " \n",
+ " | Spider-Man (2002) | \n",
+ " 0.857854 | \n",
+ "
\n",
+ " \n",
+ " | Last Samurai, The (2003) | \n",
+ " 0.817473 | \n",
+ "
\n",
+ " \n",
+ " | Indiana Jones and the Temple of Doom (1984) | \n",
+ " 0.816626 | \n",
+ "
\n",
+ " \n",
+ " | Matrix Reloaded, The (2003) | \n",
+ " 0.800990 | \n",
+ "
\n",
+ " \n",
+ " | Harry Potter and the Sorcerer's Stone (a.k.a. Harry Potter and the Philosopher's Stone) (2001) | \n",
+ " 0.765159 | \n",
+ "
\n",
+ " \n",
+ " | Gladiator (2000) | \n",
+ " 0.740956 | \n",
+ "
\n",
+ " \n",
+ " | Matrix, The (1999) | \n",
+ " 0.732693 | \n",
+ "
\n",
+ " \n",
+ " | Pirates of the Caribbean: The Curse of the Black Pearl (2003) | \n",
+ " 0.689591 | \n",
+ "
\n",
+ " \n",
+ " | Lord of the Rings: The Return of the King, The (2003) | \n",
+ " 0.676711 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "recomm_movies",
+ "summary": "{\n \"name\": \"recomm_movies\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Pirates of the Caribbean: The Curse of the Black Pearl (2003)\",\n \"Spider-Man (2002)\",\n \"Harry Potter and the Sorcerer's Stone (a.k.a. Harry Potter and the Philosopher's Stone) (2001)\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"pred_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.06614432811511851,\n \"min\": 0.6767108283499336,\n \"max\": 0.8662018746933645,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.6895905595608812,\n 0.8578535950426878,\n 0.7651586070058114\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 60
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "#**07.행렬 분해를 이용한 잠재 요인 협업 필터링 실습**\n"
+ ],
+ "metadata": {
+ "id": "UL4MPaLtDKba"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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",
+ " mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)\n",
+ " rmse = np.sqrt(mse)"
+ ],
+ "metadata": {
+ "id": "eXr27imETiQN"
+ },
+ "execution_count": 64,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\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",
+ " 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",
+ " return P,Q"
+ ],
+ "metadata": {
+ "id": "SiZFW1s4IJY9"
+ },
+ "execution_count": 65,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "ratings = pd.read_csv('/content/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",
+ "ratings_matrix = rating_movies.pivot_table('rating', index = 'userId', columns = 'title')"
+ ],
+ "metadata": {
+ "id": "MwdnEOAzJgkw"
+ },
+ "execution_count": 66,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3emxwCD4KIV8",
+ "outputId": "1779c8f6-317e-4183-a6cb-549a076bb808"
+ },
+ "execution_count": 67,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 10 rmse: None\n",
+ "### iteration step: 20 rmse: None\n",
+ "### iteration step: 30 rmse: None\n",
+ "### iteration step: 40 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 60 rmse: None\n",
+ "### iteration step: 70 rmse: None\n",
+ "### iteration step: 80 rmse: None\n",
+ "### iteration step: 90 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 110 rmse: None\n",
+ "### iteration step: 120 rmse: None\n",
+ "### iteration step: 130 rmse: None\n",
+ "### iteration step: 140 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 160 rmse: None\n",
+ "### iteration step: 170 rmse: None\n",
+ "### iteration step: 180 rmse: None\n",
+ "### iteration step: 190 rmse: None\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_pred_matrix = pd.DataFrame(data=pred_matrix, index= ratings_matrix.index, columns = ratings_matrix.columns)\n",
+ "ratings_pred_matrix.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "tRE-gmHYKQYx",
+ "outputId": "f60d1125-3123-4d4e-801d-f87c6178fb85"
+ },
+ "execution_count": 68,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ "
\n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 3.055084 | \n",
+ " 4.092018 | \n",
+ " 3.564130 | \n",
+ " 4.502167 | \n",
+ " 3.981215 | \n",
+ " 1.271694 | \n",
+ " 3.603274 | \n",
+ " 2.333266 | \n",
+ " 5.091749 | \n",
+ " 3.972454 | \n",
+ " ... | \n",
+ " 1.402608 | \n",
+ " 4.208382 | \n",
+ " 3.705957 | \n",
+ " 2.720514 | \n",
+ " 2.787331 | \n",
+ " 3.475076 | \n",
+ " 3.253458 | \n",
+ " 2.161087 | \n",
+ " 4.010495 | \n",
+ " 0.859474 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 3.170119 | \n",
+ " 3.657992 | \n",
+ " 3.308707 | \n",
+ " 4.166521 | \n",
+ " 4.311890 | \n",
+ " 1.275469 | \n",
+ " 4.237972 | \n",
+ " 1.900366 | \n",
+ " 3.392859 | \n",
+ " 3.647421 | \n",
+ " ... | \n",
+ " 0.973811 | \n",
+ " 3.528264 | \n",
+ " 3.361532 | \n",
+ " 2.672535 | \n",
+ " 2.404456 | \n",
+ " 4.232789 | \n",
+ " 2.911602 | \n",
+ " 1.634576 | \n",
+ " 4.135735 | \n",
+ " 0.725684 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 2.307073 | \n",
+ " 1.658853 | \n",
+ " 1.443538 | \n",
+ " 2.208859 | \n",
+ " 2.229486 | \n",
+ " 0.780760 | \n",
+ " 1.997043 | \n",
+ " 0.924908 | \n",
+ " 2.970700 | \n",
+ " 2.551446 | \n",
+ " ... | \n",
+ " 0.520354 | \n",
+ " 1.709494 | \n",
+ " 2.281596 | \n",
+ " 1.782833 | \n",
+ " 1.635173 | \n",
+ " 1.323276 | \n",
+ " 2.887580 | \n",
+ " 1.042618 | \n",
+ " 2.293890 | \n",
+ " 0.396941 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
3 rows × 9719 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_pred_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 68
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# 사용자가 관람하지 않은 영화명 추출\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",
+ "# 평점 데이터를 DataFrame으로 생성.\n",
+ "recomm_movies = pd.DataFrame(data=recomm_movies.values, index=recomm_movies.index, columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 394
+ },
+ "id": "D2_UAkzhKei7",
+ "outputId": "3acbdbd3-6a27-4966-c5c5-8f561c97f53a"
+ },
+ "execution_count": 69,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " pred_score | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Rear Window (1954) | \n",
+ " 5.704612 | \n",
+ "
\n",
+ " \n",
+ " | South Park: Bigger, Longer and Uncut (1999) | \n",
+ " 5.451100 | \n",
+ "
\n",
+ " \n",
+ " | Rounders (1998) | \n",
+ " 5.298393 | \n",
+ "
\n",
+ " \n",
+ " | Blade Runner (1982) | \n",
+ " 5.244951 | \n",
+ "
\n",
+ " \n",
+ " | Roger & Me (1989) | \n",
+ " 5.191962 | \n",
+ "
\n",
+ " \n",
+ " | Gattaca (1997) | \n",
+ " 5.183179 | \n",
+ "
\n",
+ " \n",
+ " | Ben-Hur (1959) | \n",
+ " 5.130463 | \n",
+ "
\n",
+ " \n",
+ " | Rosencrantz and Guildenstern Are Dead (1990) | \n",
+ " 5.087375 | \n",
+ "
\n",
+ " \n",
+ " | Big Lebowski, The (1998) | \n",
+ " 5.038690 | \n",
+ "
\n",
+ " \n",
+ " | Star Wars: Episode V - The Empire Strikes Back (1980) | \n",
+ " 4.989601 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "recomm_movies",
+ "summary": "{\n \"name\": \"recomm_movies\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Big Lebowski, The (1998)\",\n \"South Park: Bigger, Longer and Uncut (1999)\",\n \"Gattaca (1997)\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"pred_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.21272885538651393,\n \"min\": 4.989601238872484,\n \"max\": 5.704612469838172,\n \"num_unique_values\": 10,\n \"samples\": [\n 5.0386897288205725,\n 5.451100205772531,\n 5.183178550884765\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 69
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **08.파이썬 추천 시스템 패키지-Surprise**"
+ ],
+ "metadata": {
+ "id": "8yZkAqfbiT78"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import numpy as np"
+ ],
+ "metadata": {
+ "id": "UByPgYZVVyxn"
+ },
+ "execution_count": 1,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "!pip install scikit-surprise"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "moXV4rgAiaVQ",
+ "outputId": "648c3ae2-968d-4081-9229-c38adc41567a"
+ },
+ "execution_count": 3,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Requirement already satisfied: scikit-surprise in /usr/local/lib/python3.12/dist-packages (1.1.4)\n",
+ "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.5.3)\n",
+ "Requirement already satisfied: numpy>=1.19.5 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.26.4)\n",
+ "Requirement already satisfied: scipy>=1.6.0 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.16.3)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Surprise에서 데이터 로딩은 Dataset 클래스를 이용해서만 가능.\n",
+ "* Surprise는 무비렌즈 사이트에서 제공하는 과거 버전의 데이터 세트를 가져오는 API를 제공\n",
+ "* Surprise Dataset 클래스의 load_builtin()은 무비렌즈 사이트에서 제공하는 과거 버전\n",
+ "데이터 세트인 ‘ml-10*(10만 개 평점 데이터) 또는 ‘ml-lm’(100만 개 평점 데이터) 데이터를 아카\n",
+ "이브 사이트로부터 내려받아 로컬 디렉터리에 저장한 뒤 데이터를 로딩합니다."
+ ],
+ "metadata": {
+ "id": "MHg0fV8ulD2t"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise import SVD\n",
+ "from surprise import Dataset\n",
+ "from surprise import accuracy\n",
+ "from surprise.model_selection import train_test_split"
+ ],
+ "metadata": {
+ "id": "DiqAwYlhkZNT"
+ },
+ "execution_count": 4,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "data = Dataset.load_builtin('ml-100k')\n",
+ "#수행 시마다 동일하게 데이터를 분할하기 위해 random_state 값 부여\n",
+ "trainset, testset = train_test_split(data, test_size =.25, random_state=0)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "B_Ws32sEqTS9",
+ "outputId": "38c1a86d-149b-46c1-855f-c23dbe79b240"
+ },
+ "execution_count": 5,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Dataset ml-100k could not be found. Do you want to download it? [Y/n] 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 /root/.surprise_data/ml-100k\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* 과거 버전의 데이터 세트. 분리 문자가 \\t문자\n",
+ "* 무비렌즈 사이트에서 내려받은 데이터 파일과 동이랗게 로우 레벨의 사용자-아이템 평점 데이터를 그대로 적용 과거 버전의 데이터 세트. 분리 문자가 \\t문자\n",
+ "* 무비렌즈 사이트에서 내려받은 데이터 파일과 동이랗게 로우 레벨의 사용자-아이템 평점 데이터를 그대로 적용"
+ ],
+ "metadata": {
+ "id": "wwyFrujWqo1b"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "algo = SVD(random_state=0)\n",
+ "algo.fit(trainset)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "urgvZhtwrX1u",
+ "outputId": "f62c1c61-f25c-48bd-b97c-9817636622e7"
+ },
+ "execution_count": 6,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 6
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "predictions = algo.test(testset)\n",
+ "print('prediction type:', type(predictions), 'size:', len(predictions))\n",
+ "print('prediction 결과의 최초 5개 추출')\n",
+ "predictions[:5]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "z_GMf6ChrpkA",
+ "outputId": "9d6a036e-32b6-4b23-c551-278f46d6eeda"
+ },
+ "execution_count": 7,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "prediction type: size: 25000\n",
+ "prediction 결과의 최초 5개 추출\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "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.8463639495936905, details={'was_impossible': False}),\n",
+ " Prediction(uid='751', iid='385', r_ui=4.0, est=3.1807542478219157, details={'was_impossible': False})]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 7
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "[(pred.uid, pred.iid, pred.est) for pred in predictions[:3]]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "L8vU9v2VsYxG",
+ "outputId": "f070c09f-1c1f-476c-cb25-0534fe50afd2"
+ },
+ "execution_count": 8,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "[('120', '282', 3.5114147666251547),\n",
+ " ('882', '291', 3.573872419581491),\n",
+ " ('535', '507', 4.033583485472447)]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 8
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "uid = str(196)\n",
+ "iid = str(302)\n",
+ "pred = algo.predict(uid, iid)\n",
+ "print(pred)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3LXfM9KfsenL",
+ "outputId": "fdced8ac-e6ce-4688-b758-1090593dc166"
+ },
+ "execution_count": 9,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "user: 196 item: 302 r_ui = None est = 4.49 {'was_impossible': False}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "accuracy.rmse(predictions)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ZVUmMYoos1HB",
+ "outputId": "9deb72e2-5db3-4e7b-fdcc-e7dbb61bd4f5"
+ },
+ "execution_count": 10,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.9467\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.9466860806937948"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 10
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**OS 파일 데이터를 Surprise 데이터 세트로 로딩**\n",
+ "* 주의: 로딩되는 데이터 파일에 칼럼명을 가지는 헤더 문자열이 있어서는 안됨. -> ratings.csv 파일은 헤더를 가지고 있음.->to_csv() 함수로 헤더 삭제"
+ ],
+ "metadata": {
+ "id": "pB5LFb3bs5zF"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "#헤더 제거한 새로운 파일 생성\n",
+ "ratings.to_csv('/content/ratings_noh.csv', index = False, header=False)"
+ ],
+ "metadata": {
+ "id": "9K0qLK5DudQ1"
+ },
+ "execution_count": 11,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Reader 클래스를 이용해 데이터 파일의 파싱 포맷을 정의\n",
+ "* 4개의 칼럼이 사용자 아이디, 아이템 아이디, 평점, 타임스탬프임을 로딩할 때 알려줘야함"
+ ],
+ "metadata": {
+ "id": "YJC8vh3luzzr"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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('/content/ratings_noh.csv', reader=reader)"
+ ],
+ "metadata": {
+ "id": "G4JqDZCcvC7Z"
+ },
+ "execution_count": 12,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Surprise 데이터 세트는 기본적으로 무비렌즈 데이터 형식을 따르므로 무비렌즈 데이터 형식이 아닌\n",
+ "다른 OS 파일의 경우 Reader 클래스를 먼저 설정\n",
+ "**Reader 클래스 주요 파라미터**\n",
+ "* line_format(string): 칼럼을 순서대로 나열. 문자열을 공백으로 분리\n",
+ "* sep(char): 칼럼을 분리하는 분리자. 디폴트: \\t. 판다스에서 입력받을 경우 기재 필요X\n",
+ "* rating_scale(tuple, optional): 평점 값의 최소~최대 평점 설저. 디폴트: (1,5)"
+ ],
+ "metadata": {
+ "id": "P1oYHVz9wQKj"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "HIxK1R0TxDli",
+ "outputId": "f78f192e-d398-4ab4-a891-f3d79a2b5b29"
+ },
+ "execution_count": 13,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 13
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "** 판다스 DataFrame에서 Surprise 데이터 세트로 로딩**"
+ ],
+ "metadata": {
+ "id": "2yTvebbex2Ix"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "from surprise import Reader, Dataset\n",
+ "\n",
+ "ratings = pd.read_csv('/content/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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "T4k_EmaPx56x",
+ "outputId": "f601cdc3-b237-4fa3-f252-cd5ff350c1a5"
+ },
+ "execution_count": 14,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 14
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**교차 검증과 하이퍼 파라미터 튜닝**\n",
+ "* Surprise는 교차 검증과 하이퍼 파라미터 튜닝을 위해 사이킷런과 유사한 cross_validate( )와 GridSearchCV 클래스를 제공"
+ ],
+ "metadata": {
+ "id": "iLmPV9Mt29JF"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise.model_selection import cross_validate\n",
+ "\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "reader = Reader(rating_scale = (0.5, 5.0))\n",
+ "data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\n",
+ "\n",
+ "algo = SVD(random_state = 0)\n",
+ "cross_validate(algo, data, measures=['RMSE', 'MAE'], cv = 5, verbose= True)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "R5HFRwSg3Fz7",
+ "outputId": "e606698a-1374-463e-a17f-7cb3b97841bf"
+ },
+ "execution_count": 15,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "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.8741 0.8766 0.8670 0.8722 0.8729 0.8726 0.0032 \n",
+ "MAE (testset) 0.6716 0.6727 0.6663 0.6686 0.6729 0.6704 0.0026 \n",
+ "Fit time 1.33 1.72 1.25 1.25 1.28 1.37 0.18 \n",
+ "Test time 0.33 0.10 0.21 0.10 0.22 0.19 0.09 \n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "{'test_rmse': array([0.87413994, 0.87656526, 0.86698849, 0.87217666, 0.87291197]),\n",
+ " 'test_mae': array([0.67158723, 0.67271883, 0.66627016, 0.66860324, 0.6728898 ]),\n",
+ " 'fit_time': (1.3310132026672363,\n",
+ " 1.7168080806732178,\n",
+ " 1.25482177734375,\n",
+ " 1.249457836151123,\n",
+ " 1.277155876159668),\n",
+ " 'test_time': (0.3288121223449707,\n",
+ " 0.10231542587280273,\n",
+ " 0.21318769454956055,\n",
+ " 0.0996389389038086,\n",
+ " 0.22016644477844238)}"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 15
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ "print(gs.best_score['rmse'])\n",
+ "print(gs.best_params['rmse'])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "eq5jlRby37on",
+ "outputId": "8d672828-6f69-457b-9a2d-ceeea0bf4e54"
+ },
+ "execution_count": 16,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "0.877736089495885\n",
+ "{'n_epochs': 20, 'n_factors': 50}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**Surprise를 이용한 개인화 영화 추천 시스템 구축**"
+ ],
+ "metadata": {
+ "id": "9PQ4ss695v_D"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#다음 코드는 train_test_split()으로 분리되지 않은 데이터 세트에 fit()을 호출해 오류가 발생\n",
+ "data = Dataset.load_from_df(ratings['userId', 'movieId', 'rating'], reader)\n",
+ "algo = SVD(n_factors = 50, random_state=0)\n",
+ "algo.fit(data)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 569
+ },
+ "id": "Qp0Q5TPI6Dgq",
+ "outputId": "6cbe18b5-1633-4689-fb96-dd05f1439dfe"
+ },
+ "execution_count": 17,
+ "outputs": [
+ {
+ "output_type": "error",
+ "ename": "KeyError",
+ "evalue": "('userId', 'movieId', 'rating')",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3804\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3805\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3806\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32mindex.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mindex.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;31mKeyError\u001b[0m: ('userId', 'movieId', 'rating')",
+ "\nThe above exception was the direct cause of the following exception:\n",
+ "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/tmp/ipython-input-3089583357.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m#다음 코드는 train_test_split()으로 분리되지 않은 데이터 세트에 fit()을 호출해 오류가 발생\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload_from_df\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mratings\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'userId'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'movieId'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'rating'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreader\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0malgo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mSVD\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn_factors\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m50\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0malgo\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/frame.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 4100\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnlevels\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4101\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_getitem_multilevel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 4102\u001b[0;31m \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4103\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_integer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4104\u001b[0m \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3810\u001b[0m ):\n\u001b[1;32m 3811\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mInvalidIndexError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3812\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3813\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3814\u001b[0m \u001b[0;31m# If we have a listlike key, _check_indexing_error will raise\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mKeyError\u001b[0m: ('userId', 'movieId', 'rating')"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise.dataset import DatasetAutoFolds\n",
+ "\n",
+ "reader = Reader(line_format='user item rating timestamp', sep = ',', rating_scale = (0.5,5))\n",
+ "\n",
+ "data_folds = DatasetAutoFolds(ratings_file = '/content/ratings_noh.csv', reader = reader)\n",
+ "\n",
+ "trainset = data_folds.build_full_trainset()"
+ ],
+ "metadata": {
+ "id": "TuxvxeSp6Xhr"
+ },
+ "execution_count": 18,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "algo = SVD(n_epochs=20, n_factors = 50, random_state=0)\n",
+ "algo.fit(trainset)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "D7t3MOdeuHzf",
+ "outputId": "ff85cfe8-457a-45c4-d674-e062bb21931e"
+ },
+ "execution_count": 19,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 19
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "\n",
+ "movieIds = ratings[ratings['userId'] == 9]['movieId']\n",
+ "\n",
+ "if movieIds[movieIds==42].count() ==0:\n",
+ " print('사용자 아이디 9는 영화 아이디 42의 평점 없음')\n",
+ "\n",
+ "print(movies[movies['movieId']==42])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "93GKv6Td7EmI",
+ "outputId": "6d8f4660-77d3-4a53-e51c-d33ec3781271"
+ },
+ "execution_count": 20,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "사용자 아이디 9는 영화 아이디 42의 평점 없음\n",
+ " movieId title genres\n",
+ "38 42 Dead Presidents (1995) Action|Crime|Drama\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "uid = str(9)\n",
+ "iid = str(42)\n",
+ "\n",
+ "pred = algo.predict(uid, iid, verbose=True)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "a0ps2XQ77hAG",
+ "outputId": "b7d45d82-3e82-4b55-d747-8116e3352790"
+ },
+ "execution_count": 21,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "user: 9 item: 42 r_ui = None est = 3.13 {'was_impossible': False}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_unseen_surprise(ratings, movies, userId):\n",
+ " seen_movies = ratings[ratings['userId']==userId]['movieId'].tolist()\n",
+ " total_movies = movies['movieId'].tolist()\n",
+ "\n",
+ " unseen_movies = [movie for movie in total_movies if movie not in 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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "SVL_7m017o_u",
+ "outputId": "906a4459-d4c2-43a2-89e0-6565e5d688cd"
+ },
+ "execution_count": 22,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9742 전체 영화 수: 9742\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ " def sortkey_est(pred):\n",
+ " return pred.est\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",
+ "\n",
+ "print('#### Top-10 추천 영화 리스트 ####')\n",
+ "for top_movie in top_movie_preds:\n",
+ " print(top_movie[1], \":\", top_movie[2])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Ed_wXo3I8z6A",
+ "outputId": "ec468318-ef81-4471-d974-0d609d1b1444"
+ },
+ "execution_count": 23,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9742 전체 영화 수: 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.278152632122759\n",
+ "Godfather, The (1972) : 4.226073566460876\n",
+ "Streetcar Named Desire, A (1951) : 4.205267497432363\n",
+ "Star Wars: Episode V - The Empire Strikes Back (1980) : 4.1918097904381995\n",
+ "Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) : 4.154746591122657\n",
+ "Star Wars: Episode VI - Return of the Jedi (1983) : 4.122016128534504\n",
+ "Goodfellas (1990) : 4.118002684813024\n",
+ "Lord of the Rings: The Fellowship of the Ring, The (2001) : 4.108009609093436\n"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb" "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb"
new file mode 100644
index 0000000..4964ce6
--- /dev/null
+++ "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.ipynb"
@@ -0,0 +1,7325 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**확률적 경사하강법을 이용한 행렬 분해**"
+ ],
+ "metadata": {
+ "id": "ayjUvpzHHCOE"
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "id": "czID4-9gG5W9"
+ },
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "#원본 행렬 R 생성\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))\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ " mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)\n",
+ " rmse = np.sqrt(mse)"
+ ],
+ "metadata": {
+ "id": "PJ-zo4ALIQlD"
+ },
+ "execution_count": 50,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from os import P_PID\n",
+ "#SGD 기반으로 행렬 분해 수행\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",
+ "steps = 1000 #SGD를 반복해서 업데이트할 횟수\n",
+ "learning_rate = 0.01 #SGD의 학습률\n",
+ "r_lambda = 0.01 #L2 규제 계수\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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "VNgiqI63KZCj",
+ "outputId": "2040d273-b9d7-46f5-c475-0d0a6eeb39c4"
+ },
+ "execution_count": 51,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 200 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 250 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 300 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 350 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 400 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 450 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 500 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 550 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 600 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 650 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 700 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 750 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 800 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 850 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 900 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n",
+ "### iteration step: 950 rmse: None\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "pred_matrix = np.dot(P, Q.T)\n",
+ "print('예측 행렬:\\n', np.round(pred_matrix, 3))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "iW6WHXvTMM5X",
+ "outputId": "b2bdb79f-fe91-4fac-abca-47dfadb96ab2"
+ },
+ "execution_count": 52,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "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"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **05. 콘텐츠 기반 필터링 실습-TMDB 5000 영화 데이터 세트**\n",
+ "\n",
+ "**장르 속성을 이용한 영화 콘텐츠 기반 필터링**\n",
+ "* 특정 영화와 비슷한 특성/속성, 구성 요소 등을 가진 다른 영화 추천\n",
+ "* 영화 간의 유사성을 판단하는 기준이 영화를 구성하는 다양한 콘텐츠를 기반으로 하는 방식\n",
+ "\n",
+ "**데이터 로딩 및 가공**"
+ ],
+ "metadata": {
+ "id": "h7poBF8WG7ng"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import warnings; warnings.filterwarnings('ignore')\n",
+ "\n",
+ "movies = pd.read_csv('/content/tmdb_5000_movies.csv')\n",
+ "print(movies.shape)\n",
+ "movies.head(1)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 327
+ },
+ "id": "KILZrMfNH3oN",
+ "outputId": "eb8081ba-3f9d-48e7-81e0-d36c01b1be20"
+ },
+ "execution_count": 53,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 20)\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " budget \\\n",
+ "0 237000000 \n",
+ "\n",
+ " genres \\\n",
+ "0 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... \n",
+ "\n",
+ " homepage id \\\n",
+ "0 http://www.avatarmovie.com/ 19995 \n",
+ "\n",
+ " keywords \\\n",
+ "0 [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... \n",
+ "\n",
+ " original_language original_title \\\n",
+ "0 en Avatar \n",
+ "\n",
+ " overview \\\n",
+ "0 In the 22nd century, a paraplegic Marine is dispatched to the moon Pandora on a unique mission, ... \n",
+ "\n",
+ " popularity \\\n",
+ "0 150.437577 \n",
+ "\n",
+ " production_companies \\\n",
+ "0 [{\"name\": \"Ingenious Film Partners\", \"id\": 289}, {\"name\": \"Twentieth Century Fox Film Corporatio... \n",
+ "\n",
+ " production_countries \\\n",
+ "0 [{\"iso_3166_1\": \"US\", \"name\": \"United States of America\"}, {\"iso_3166_1\": \"GB\", \"name\": \"United ... \n",
+ "\n",
+ " release_date revenue runtime \\\n",
+ "0 2009-12-10 2787965087 162.0 \n",
+ "\n",
+ " spoken_languages \\\n",
+ "0 [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso_639_1\": \"es\", \"name\": \"Espa\\u00f1ol\"}] \n",
+ "\n",
+ " status tagline title vote_average vote_count \n",
+ "0 Released Enter the World of Pandora. Avatar 7.2 11800 "
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " budget | \n",
+ " genres | \n",
+ " homepage | \n",
+ " id | \n",
+ " keywords | \n",
+ " original_language | \n",
+ " original_title | \n",
+ " overview | \n",
+ " popularity | \n",
+ " production_companies | \n",
+ " production_countries | \n",
+ " release_date | \n",
+ " revenue | \n",
+ " runtime | \n",
+ " spoken_languages | \n",
+ " status | \n",
+ " tagline | \n",
+ " title | \n",
+ " vote_average | \n",
+ " vote_count | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 237000000 | \n",
+ " [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... | \n",
+ " http://www.avatarmovie.com/ | \n",
+ " 19995 | \n",
+ " [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... | \n",
+ " en | \n",
+ " Avatar | \n",
+ " In the 22nd century, a paraplegic Marine is dispatched to the moon Pandora on a unique mission, ... | \n",
+ " 150.437577 | \n",
+ " [{\"name\": \"Ingenious Film Partners\", \"id\": 289}, {\"name\": \"Twentieth Century Fox Film Corporatio... | \n",
+ " [{\"iso_3166_1\": \"US\", \"name\": \"United States of America\"}, {\"iso_3166_1\": \"GB\", \"name\": \"United ... | \n",
+ " 2009-12-10 | \n",
+ " 2787965087 | \n",
+ " 162.0 | \n",
+ " [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso_639_1\": \"es\", \"name\": \"Espa\\u00f1ol\"}] | \n",
+ " Released | \n",
+ " Enter the World of Pandora. | \n",
+ " Avatar | \n",
+ " 7.2 | \n",
+ " 11800 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "movies",
+ "summary": "{\n \"name\": \"movies\",\n \"rows\": 4803,\n \"fields\": [\n {\n \"column\": \"budget\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 40722391,\n \"min\": 0,\n \"max\": 380000000,\n \"num_unique_values\": 436,\n \"samples\": [\n 439000,\n 68000000,\n 700000\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1175,\n \"samples\": [\n \"[{\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 16, \\\"name\\\": \\\"Animation\\\"}]\",\n \"[{\\\"id\\\": 28, \\\"name\\\": \\\"Action\\\"}, {\\\"id\\\": 35, \\\"name\\\": \\\"Comedy\\\"}, {\\\"id\\\": 80, \\\"name\\\": \\\"Crime\\\"}, {\\\"id\\\": 18, \\\"name\\\": \\\"Drama\\\"}]\",\n \"[{\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 16, \\\"name\\\": \\\"Animation\\\"}, {\\\"id\\\": 10751, \\\"name\\\": \\\"Family\\\"}, {\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 878, \\\"name\\\": \\\"Science Fiction\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"homepage\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1691,\n \"samples\": [\n \"https://www.warnerbros.com/running-scared\",\n \"http://www.51birchstreet.com/index.php\",\n \"http://movies2.foxjapan.com/glee/\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 88694,\n \"min\": 5,\n \"max\": 459488,\n \"num_unique_values\": 4803,\n \"samples\": [\n 8427,\n 13006,\n 18041\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4222,\n \"samples\": [\n \"[{\\\"id\\\": 782, \\\"name\\\": \\\"assassin\\\"}, {\\\"id\\\": 1872, \\\"name\\\": \\\"loss of father\\\"}, {\\\"id\\\": 2908, \\\"name\\\": \\\"secret society\\\"}, {\\\"id\\\": 3045, \\\"name\\\": \\\"mission of murder\\\"}, {\\\"id\\\": 9748, \\\"name\\\": \\\"revenge\\\"}]\",\n \"[{\\\"id\\\": 2987, \\\"name\\\": \\\"gang war\\\"}, {\\\"id\\\": 4942, \\\"name\\\": \\\"victim of murder\\\"}, {\\\"id\\\": 5332, \\\"name\\\": \\\"greed\\\"}, {\\\"id\\\": 6062, \\\"name\\\": \\\"hostility\\\"}, {\\\"id\\\": 156212, \\\"name\\\": \\\"spaghetti western\\\"}]\",\n \"[{\\\"id\\\": 703, \\\"name\\\": \\\"detective\\\"}, {\\\"id\\\": 1299, \\\"name\\\": \\\"monster\\\"}, {\\\"id\\\": 6101, \\\"name\\\": \\\"engine\\\"}, {\\\"id\\\": 10988, \\\"name\\\": \\\"based on tv series\\\"}, {\\\"id\\\": 15162, \\\"name\\\": \\\"dog\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"original_language\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 37,\n \"samples\": [\n \"xx\",\n \"ta\",\n \"es\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"original_title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4801,\n \"samples\": [\n \"I Spy\",\n \"Love Letters\",\n \"Sleepover\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"overview\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4800,\n \"samples\": [\n \"When the Switchblade, the most sophisticated prototype stealth fighter created yet, is stolen from the U.S. government, one of the United States' top spies, Alex Scott, is called to action. What he doesn't expect is to get teamed up with a cocky civilian, World Class Boxing Champion Kelly Robinson, on a dangerous top secret espionage mission. Their assignment: using equal parts skill and humor, catch Arnold Gundars, one of the world's most successful arms dealers.\",\n \"When \\\"street smart\\\" rapper Christopher \\\"C-Note\\\" Hawkins (Big Boi) applies for a membership to all-white Carolina Pines Country Club, the establishment's proprietors are hardly ready to oblige him.\",\n \"As their first year of high school looms ahead, best friends Julie, Hannah, Yancy and Farrah have one last summer sleepover. Little do they know they're about to embark on the adventure of a lifetime. Desperate to shed their nerdy status, they take part in a night-long scavenger hunt that pits them against their popular archrivals. Everything under the sun goes on -- from taking Yancy's father's car to sneaking into nightclubs!\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 31.816649749537806,\n \"min\": 0.0,\n \"max\": 875.581305,\n \"num_unique_values\": 4802,\n \"samples\": [\n 13.267631,\n 0.010909,\n 5.842299\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"production_companies\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3697,\n \"samples\": [\n \"[{\\\"name\\\": \\\"Paramount Pictures\\\", \\\"id\\\": 4}, {\\\"name\\\": \\\"Cherry Alley Productions\\\", \\\"id\\\": 2232}]\",\n \"[{\\\"name\\\": \\\"Twentieth Century Fox Film Corporation\\\", \\\"id\\\": 306}, {\\\"name\\\": \\\"Dune Entertainment\\\", \\\"id\\\": 444}, {\\\"name\\\": \\\"Regency Enterprises\\\", \\\"id\\\": 508}, {\\\"name\\\": \\\"Guy Walks into a Bar Productions\\\", \\\"id\\\": 2645}, {\\\"name\\\": \\\"Deep River Productions\\\", \\\"id\\\": 2646}, {\\\"name\\\": \\\"Friendly Films (II)\\\", \\\"id\\\": 81136}]\",\n \"[{\\\"name\\\": \\\"Twentieth Century Fox Film Corporation\\\", \\\"id\\\": 306}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"production_countries\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 469,\n \"samples\": [\n \"[{\\\"iso_3166_1\\\": \\\"ES\\\", \\\"name\\\": \\\"Spain\\\"}, {\\\"iso_3166_1\\\": \\\"GB\\\", \\\"name\\\": \\\"United Kingdom\\\"}, {\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}, {\\\"iso_3166_1\\\": \\\"FR\\\", \\\"name\\\": \\\"France\\\"}]\",\n \"[{\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}, {\\\"iso_3166_1\\\": \\\"CA\\\", \\\"name\\\": \\\"Canada\\\"}, {\\\"iso_3166_1\\\": \\\"DE\\\", \\\"name\\\": \\\"Germany\\\"}]\",\n \"[{\\\"iso_3166_1\\\": \\\"DE\\\", \\\"name\\\": \\\"Germany\\\"}, {\\\"iso_3166_1\\\": \\\"ES\\\", \\\"name\\\": \\\"Spain\\\"}, {\\\"iso_3166_1\\\": \\\"GB\\\", \\\"name\\\": \\\"United Kingdom\\\"}, {\\\"iso_3166_1\\\": \\\"US\\\", \\\"name\\\": \\\"United States of America\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"release_date\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 3280,\n \"samples\": [\n \"1966-10-16\",\n \"1987-07-31\",\n \"1993-09-23\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"revenue\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 162857100,\n \"min\": 0,\n \"max\": 2787965087,\n \"num_unique_values\": 3297,\n \"samples\": [\n 11833696,\n 10462500,\n 17807569\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"runtime\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 22.611934588844207,\n \"min\": 0.0,\n \"max\": 338.0,\n \"num_unique_values\": 156,\n \"samples\": [\n 74.0,\n 85.0,\n 170.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"spoken_languages\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 544,\n \"samples\": [\n \"[{\\\"iso_639_1\\\": \\\"es\\\", \\\"name\\\": \\\"Espa\\\\u00f1ol\\\"}, {\\\"iso_639_1\\\": \\\"en\\\", \\\"name\\\": \\\"English\\\"}, {\\\"iso_639_1\\\": \\\"fr\\\", \\\"name\\\": \\\"Fran\\\\u00e7ais\\\"}, {\\\"iso_639_1\\\": \\\"hu\\\", \\\"name\\\": \\\"Magyar\\\"}]\",\n \"[{\\\"iso_639_1\\\": \\\"en\\\", \\\"name\\\": \\\"English\\\"}, {\\\"iso_639_1\\\": \\\"it\\\", \\\"name\\\": \\\"Italiano\\\"}, {\\\"iso_639_1\\\": \\\"pt\\\", \\\"name\\\": \\\"Portugu\\\\u00eas\\\"}]\",\n \"[{\\\"iso_639_1\\\": \\\"de\\\", \\\"name\\\": \\\"Deutsch\\\"}, {\\\"iso_639_1\\\": \\\"it\\\", \\\"name\\\": \\\"Italiano\\\"}, {\\\"iso_639_1\\\": \\\"la\\\", \\\"name\\\": \\\"Latin\\\"}, {\\\"iso_639_1\\\": \\\"pl\\\", \\\"name\\\": \\\"Polski\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"status\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Released\",\n \"Post Production\",\n \"Rumored\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"tagline\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3944,\n \"samples\": [\n \"When you're 17, every day is war.\",\n \"An Unspeakable Horror. A Creative Genius. Captured For Eternity.\",\n \"May the schwartz be with you\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4800,\n \"samples\": [\n \"I Spy\",\n \"Who's Your Caddy?\",\n \"Sleepover\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.1946121628478925,\n \"min\": 0.0,\n \"max\": 10.0,\n \"num_unique_values\": 71,\n \"samples\": [\n 5.1,\n 7.2,\n 4.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1234,\n \"min\": 0,\n \"max\": 13752,\n \"num_unique_values\": 1609,\n \"samples\": [\n 7604,\n 3428,\n 225\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 53
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']]"
+ ],
+ "metadata": {
+ "id": "5ElyMDBcIMde"
+ },
+ "execution_count": 54,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* ‘genres’, ‘keywords’ 등과 같은 칼럼을 보면 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\":\n",
+ "\"Adventure\"}]와 같이 파이썬 리스트(list) 내부에 여러 개의 딕셔너리(diet)가 있는 형태의 문자열로\n",
+ "표기돼 있음"
+ ],
+ "metadata": {
+ "id": "b11_PCmcMqko"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "pd.set_option('max_colwidth', 100)\n",
+ "movies_df[['genres', 'keywords']][:1]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 81
+ },
+ "id": "hiMmTFFkNDCX",
+ "outputId": "da89b87b-2ab3-4811-a8ea-dbf20cbf251d"
+ },
+ "execution_count": 55,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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... "
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " genres | \n",
+ " keywords | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"name\": \"Adventure\"}, {\"id\": 14, \"name\": \"Fantasy\"}, {... | \n",
+ " [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\": 2964, \"name\": \"future\"}, {\"id\": 3386, \"name\": \"sp... | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['genres', 'keywords']][:1]\",\n \"rows\": 1,\n \"fields\": [\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"[{\\\"id\\\": 28, \\\"name\\\": \\\"Action\\\"}, {\\\"id\\\": 12, \\\"name\\\": \\\"Adventure\\\"}, {\\\"id\\\": 14, \\\"name\\\": \\\"Fantasy\\\"}, {\\\"id\\\": 878, \\\"name\\\": \\\"Science Fiction\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"[{\\\"id\\\": 1463, \\\"name\\\": \\\"culture clash\\\"}, {\\\"id\\\": 2964, \\\"name\\\": \\\"future\\\"}, {\\\"id\\\": 3386, \\\"name\\\": \\\"space war\\\"}, {\\\"id\\\": 3388, \\\"name\\\": \\\"space colony\\\"}, {\\\"id\\\": 3679, \\\"name\\\": \\\"society\\\"}, {\\\"id\\\": 3801, \\\"name\\\": \\\"space travel\\\"}, {\\\"id\\\": 9685, \\\"name\\\": \\\"futuristic\\\"}, {\\\"id\\\": 9840, \\\"name\\\": \\\"romance\\\"}, {\\\"id\\\": 9882, \\\"name\\\": \\\"space\\\"}, {\\\"id\\\": 9951, \\\"name\\\": \\\"alien\\\"}, {\\\"id\\\": 10148, \\\"name\\\": \\\"tribe\\\"}, {\\\"id\\\": 10158, \\\"name\\\": \\\"alien planet\\\"}, {\\\"id\\\": 10987, \\\"name\\\": \\\"cgi\\\"}, {\\\"id\\\": 11399, \\\"name\\\": \\\"marine\\\"}, {\\\"id\\\": 13065, \\\"name\\\": \\\"soldier\\\"}, {\\\"id\\\": 14643, \\\"name\\\": \\\"battle\\\"}, {\\\"id\\\": 14720, \\\"name\\\": \\\"love affair\\\"}, {\\\"id\\\": 165431, \\\"name\\\": \\\"anti war\\\"}, {\\\"id\\\": 193554, \\\"name\\\": \\\"power relations\\\"}, {\\\"id\\\": 206690, \\\"name\\\": \\\"mind and soul\\\"}, {\\\"id\\\": 209714, \\\"name\\\": \\\"3d\\\"}]\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 55
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from ast import literal_eval\n",
+ "import numpy as np\n",
+ "\n",
+ "def safe_literal_eval(val):\n",
+ " if isinstance(val, str):\n",
+ " try:\n",
+ " return literal_eval(val)\n",
+ " except (ValueError, SyntaxError):\n",
+ " return [] # Return empty list for malformed strings\n",
+ " elif val is np.nan:\n",
+ " return [] # Handle NaN values explicitly, which are float type\n",
+ " else:\n",
+ " return [] # Handle any other unexpected types\n",
+ "\n",
+ "movies_df['genres'] = movies_df['genres'].apply(safe_literal_eval)\n",
+ "movies_df['keywords'] = movies_df['keywords'].apply(safe_literal_eval)"
+ ],
+ "metadata": {
+ "id": "BbiyDYBFcnrf"
+ },
+ "execution_count": 56,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 81
+ },
+ "id": "xTnEmykJebTh",
+ "outputId": "76317e1c-075e-4bd0-a36b-c25f69ba9656"
+ },
+ "execution_count": 57,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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... "
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " genres | \n",
+ " keywords | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " [Action, Adventure, Fantasy, Science Fiction] | \n",
+ " [culture clash, future, space war, space colony, society, space travel, futuristic, romance, spa... | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['genres', 'keywords']][:1]\",\n \"rows\": 1,\n \"fields\": [\n {\n \"column\": \"genres\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"keywords\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 57
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**장르 콘텐츠 유사도 측정**\n",
+ "* 문자열로 변환된 genres 칼럼을 Count 기반으로 피처 벡터화 변환합니다.\n",
+ "* genres 문자열을 피처 벡터화 행렬로 변환한 데이터 세트로 코사인 유사도를 통해 비교합니다. 이를 위해 데이터 세트의 레크드별로 타 레코드와 장르에서 코사인 유사도 값을 가지는 객체를 생성합니다.\n",
+ "* 장르 유사도가 높은 영화 중에 평점이 높은 순으로 영화를 추천합니다,\n"
+ ],
+ "metadata": {
+ "id": "BKDGHUopfZf3"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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 = 1, ngram_range=(1,2)) # min_df를 0에서 1로 수정\n",
+ "genre_mat = count_vect.fit_transform(movies_df['genres_literal'])\n",
+ "print(genre_mat.shape)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "bTjG5FRJgcEZ",
+ "outputId": "be2b0e75-3b67-4908-d448-2e675d4cb1bb"
+ },
+ "execution_count": 58,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 276)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ ""
+ ],
+ "metadata": {
+ "id": "oLDUmAkAi2dt"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "gd4dp9hRi4rR",
+ "outputId": "d8f53414-31e1-43e6-e05b-6b0c098f85f8"
+ },
+ "execution_count": 59,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 4803)\n",
+ "[[1. 0.59628479 0.4472136 ... 0. 0. 0. ]\n",
+ " [0.59628479 1. 0.4 ... 0. 0. 0. ]]\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]\n",
+ "print(genre_sim_sorted_ind[:1]) #유사도가 높은 순으로 정리된 genre_sim 객체의 비교 행 위치 인덱스 값을 간편하게 얻음"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "v7bPtl5ljscw",
+ "outputId": "0eced92a-d67d-4d3a-fe39-f592e2e393b7"
+ },
+ "execution_count": 60,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "[[ 0 3494 813 ... 3038 3037 2401]]\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**장르 콘텐츠 필터링을 이용한 영화추천**"
+ ],
+ "metadata": {
+ "id": "cX1SHNLcnUSM"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def find_sim_movie(df, sorted_ind, title_name, top_n = 10):\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]"
+ ],
+ "metadata": {
+ "id": "EwMpgfulnXQR"
+ },
+ "execution_count": 61,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)\n",
+ "similar_movies[['title', 'vote_average']]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 381
+ },
+ "id": "gFbJliblnw-a",
+ "outputId": "731a4796-60ca-4025-fca8-f558a272af76"
+ },
+ "execution_count": 62,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "[[2731 1243 3636 1946 2640 4065 1847 4217 883 3866]]\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " title vote_average\n",
+ "2731 The Godfather: Part II 8.3\n",
+ "1243 Mean Streets 7.2\n",
+ "3636 Light Sleeper 5.7\n",
+ "1946 The Bad Lieutenant: Port of Call - New Orleans 6.0\n",
+ "2640 Things to Do in Denver When You're Dead 6.7\n",
+ "4065 Mi America 0.0\n",
+ "1847 GoodFellas 8.2\n",
+ "4217 Kids 6.8\n",
+ "883 Catch Me If You Can 7.7\n",
+ "3866 City of God 8.1"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2731 | \n",
+ " The Godfather: Part II | \n",
+ " 8.3 | \n",
+ " \n",
+ " \n",
+ " | 1243 | \n",
+ " Mean Streets | \n",
+ " 7.2 | \n",
+ " \n",
+ " \n",
+ " | 3636 | \n",
+ " Light Sleeper | \n",
+ " 5.7 | \n",
+ " \n",
+ " \n",
+ " | 1946 | \n",
+ " The Bad Lieutenant: Port of Call - New Orleans | \n",
+ " 6.0 | \n",
+ " \n",
+ " \n",
+ " | 2640 | \n",
+ " Things to Do in Denver When You're Dead | \n",
+ " 6.7 | \n",
+ " \n",
+ " \n",
+ " | 4065 | \n",
+ " Mi America | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 1847 | \n",
+ " GoodFellas | \n",
+ " 8.2 | \n",
+ " \n",
+ " \n",
+ " | 4217 | \n",
+ " Kids | \n",
+ " 6.8 | \n",
+ " \n",
+ " \n",
+ " | 883 | \n",
+ " Catch Me If You Can | \n",
+ " 7.7 | \n",
+ " \n",
+ " \n",
+ " | 3866 | \n",
+ " City of God | \n",
+ " 8.1 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"similar_movies[['title', 'vote_average']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Catch Me If You Can\",\n \"Mean Streets\",\n \"Mi America\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2.4467892793981623,\n \"min\": 0.0,\n \"max\": 8.3,\n \"num_unique_values\": 10,\n \"samples\": [\n 7.7,\n 7.2,\n 0.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 62
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df[['title', 'vote_average', 'vote_count']].sort_values('vote_average', ascending=False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "NoHMGc3Iqdpu",
+ "outputId": "066d1418-5b08-4844-81f2-04065170a341"
+ },
+ "execution_count": 63,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " vote_count | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 3519 | \n",
+ " Stiff Upper Lips | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ " \n",
+ " \n",
+ " | 4247 | \n",
+ " Me You and Five Bucks | \n",
+ " 10.0 | \n",
+ " 2 | \n",
+ " \n",
+ " \n",
+ " | 4045 | \n",
+ " Dancer, Texas Pop. 81 | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ " \n",
+ " \n",
+ " | 4662 | \n",
+ " Little Big Top | \n",
+ " 10.0 | \n",
+ " 1 | \n",
+ " \n",
+ " \n",
+ " | 3992 | \n",
+ " Sardaarji | \n",
+ " 9.5 | \n",
+ " 2 | \n",
+ " \n",
+ " \n",
+ " | 2386 | \n",
+ " One Man's Hero | \n",
+ " 9.3 | \n",
+ " 2 | \n",
+ " \n",
+ " \n",
+ " | 2970 | \n",
+ " There Goes My Baby | \n",
+ " 8.5 | \n",
+ " 2 | \n",
+ " \n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ " 8205 | \n",
+ " \n",
+ " \n",
+ " | 2796 | \n",
+ " The Prisoner of Zenda | \n",
+ " 8.4 | \n",
+ " 11 | \n",
+ " \n",
+ " \n",
+ " | 3337 | \n",
+ " The Godfather | \n",
+ " 8.4 | \n",
+ " 5893 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['title', 'vote_average', 'vote_count']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"The Prisoner of Zenda\",\n \"Me You and Five Bucks\",\n \"One Man's Hero\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.7366591251499343,\n \"min\": 8.4,\n \"max\": 10.0,\n \"num_unique_values\": 5,\n \"samples\": [\n 9.5,\n 8.4,\n 9.3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3020,\n \"min\": 1,\n \"max\": 8205,\n \"num_unique_values\": 5,\n \"samples\": [\n 2,\n 5893,\n 8205\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 63
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* 왜곡된 평점 데이터를 회피할 수 있도록 평점에 평가 횟수를 반영할 수 있는 새로운 평가 방식이 필요\n",
+ "\n",
+ "\n",
+ "```\n",
+ "가중평점(Weighted Rating) = (v/(v+m))*R +(m/(v+m))*C\n",
+ "```\n",
+ "* v: 개별 영화에 평점을 투표한 횟수: vote_count\n",
+ "* m: 평점을 부여하기 위한 최소 투표 횟수\n",
+ "* R: 개별 영화에 대한 평균 평점: vote_average\n",
+ "* C: 젼체 영화에 대한 평균 평점\n",
+ "\n"
+ ],
+ "metadata": {
+ "id": "MNXToqXeq3Hk"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Gp4RH5-IrVqD",
+ "outputId": "54ed8ae4-fe1c-4e14-a2aa-705c5e60f94a"
+ },
+ "execution_count": 64,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "C: 6.092 m: 370.2\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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.apply(weighted_vote_average, axis =1)"
+ ],
+ "metadata": {
+ "id": "CL3Q42bPrsJP"
+ },
+ "execution_count": 65,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']].sort_values('weighted_vote', ascending = False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "Ac2BxbuXsLU1",
+ "outputId": "c1a2355c-82cd-4249-dcc9-bfb2cfe04baf"
+ },
+ "execution_count": 66,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " weighted_vote | \n",
+ " vote_count | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1881 | \n",
+ " The Shawshank Redemption | \n",
+ " 8.5 | \n",
+ " 8.396052 | \n",
+ " 8205 | \n",
+ " \n",
+ " \n",
+ " | 3337 | \n",
+ " The Godfather | \n",
+ " 8.4 | \n",
+ " 8.263591 | \n",
+ " 5893 | \n",
+ " \n",
+ " \n",
+ " | 662 | \n",
+ " Fight Club | \n",
+ " 8.3 | \n",
+ " 8.216455 | \n",
+ " 9413 | \n",
+ " \n",
+ " \n",
+ " | 3232 | \n",
+ " Pulp Fiction | \n",
+ " 8.3 | \n",
+ " 8.207102 | \n",
+ " 8428 | \n",
+ " \n",
+ " \n",
+ " | 65 | \n",
+ " The Dark Knight | \n",
+ " 8.2 | \n",
+ " 8.136930 | \n",
+ " 12002 | \n",
+ " \n",
+ " \n",
+ " | 1818 | \n",
+ " Schindler's List | \n",
+ " 8.3 | \n",
+ " 8.126069 | \n",
+ " 4329 | \n",
+ " \n",
+ " \n",
+ " | 3865 | \n",
+ " Whiplash | \n",
+ " 8.3 | \n",
+ " 8.123248 | \n",
+ " 4254 | \n",
+ " \n",
+ " \n",
+ " | 809 | \n",
+ " Forrest Gump | \n",
+ " 8.2 | \n",
+ " 8.105954 | \n",
+ " 7927 | \n",
+ " \n",
+ " \n",
+ " | 2294 | \n",
+ " Spirited Away | \n",
+ " 8.3 | \n",
+ " 8.105867 | \n",
+ " 3840 | \n",
+ " \n",
+ " \n",
+ " | 2731 | \n",
+ " The Godfather: Part II | \n",
+ " 8.3 | \n",
+ " 8.079586 | \n",
+ " 3338 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Spirited Away\",\n \"The Godfather\",\n \"Schindler's List\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08755950357709151,\n \"min\": 8.2,\n \"max\": 8.5,\n \"num_unique_values\": 4,\n \"samples\": [\n 8.4,\n 8.2,\n 8.5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"weighted_vote\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.09696608479450805,\n \"min\": 8.07958629828635,\n \"max\": 8.39605162693645,\n \"num_unique_values\": 10,\n \"samples\": [\n 8.105867158639835,\n 8.263590802034972,\n 8.126068673669016\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2866,\n \"min\": 3338,\n \"max\": 12002,\n \"num_unique_values\": 10,\n \"samples\": [\n 3840,\n 5893,\n 4329\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 66
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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']]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "9saSifP2s7xV",
+ "outputId": "1fb82f69-2144-4452-e5df-202d58bd8da6"
+ },
+ "execution_count": 67,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " title vote_average weighted_vote\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",
+ "883 Catch Me If You Can 7.7 7.557097\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\n",
+ "2839 Rounders 6.9 6.530427"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " vote_average | \n",
+ " weighted_vote | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2731 | \n",
+ " The Godfather: Part II | \n",
+ " 8.3 | \n",
+ " 8.079586 | \n",
+ " \n",
+ " \n",
+ " | 1847 | \n",
+ " GoodFellas | \n",
+ " 8.2 | \n",
+ " 7.976937 | \n",
+ " \n",
+ " \n",
+ " | 3866 | \n",
+ " City of God | \n",
+ " 8.1 | \n",
+ " 7.759693 | \n",
+ " \n",
+ " \n",
+ " | 1663 | \n",
+ " Once Upon a Time in America | \n",
+ " 8.2 | \n",
+ " 7.657811 | \n",
+ " \n",
+ " \n",
+ " | 883 | \n",
+ " Catch Me If You Can | \n",
+ " 7.7 | \n",
+ " 7.557097 | \n",
+ " \n",
+ " \n",
+ " | 281 | \n",
+ " American Gangster | \n",
+ " 7.4 | \n",
+ " 7.141396 | \n",
+ " \n",
+ " \n",
+ " | 4041 | \n",
+ " This Is England | \n",
+ " 7.4 | \n",
+ " 6.739664 | \n",
+ " \n",
+ " \n",
+ " | 1149 | \n",
+ " American Hustle | \n",
+ " 6.8 | \n",
+ " 6.717525 | \n",
+ " \n",
+ " \n",
+ " | 1243 | \n",
+ " Mean Streets | \n",
+ " 7.2 | \n",
+ " 6.626569 | \n",
+ " \n",
+ " \n",
+ " | 2839 | \n",
+ " Rounders | \n",
+ " 6.9 | \n",
+ " 6.530427 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "summary": "{\n \"name\": \"similar_movies[['title', 'vote_average', 'weighted_vote']]\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Mean Streets\",\n \"GoodFellas\",\n \"American Gangster\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vote_average\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5613475849338901,\n \"min\": 6.8,\n \"max\": 8.3,\n \"num_unique_values\": 8,\n \"samples\": [\n 8.2,\n 6.8,\n 8.3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"weighted_vote\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5954507780784589,\n \"min\": 6.530427473190107,\n \"max\": 8.07958629828635,\n \"num_unique_values\": 10,\n \"samples\": [\n 6.626568667932654,\n 7.976937256676415,\n 7.1413961709782265\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 67
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **06.아이템 기반 최근접 이웃 협업 필터링 실습**\n",
+ "**데이터 가공 및 변환**"
+ ],
+ "metadata": {
+ "id": "EPvqs026vyzE"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "print(movies.shape)\n",
+ "print(ratings.shape)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ZRnCRT66v76Y",
+ "outputId": "ae6766d3-608c-49c5-dc23-9bf7f17d6ba1"
+ },
+ "execution_count": 68,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(9742, 3)\n",
+ "(100836, 4)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* 사용자를 로우로, 모든 영화를 칼럼으로 구성한 데이터 세트로 변경\n",
+ "* DataFrame의 pivot_table() 함수를 이용."
+ ],
+ "metadata": {
+ "id": "6xUsx9rqoXj6"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings = ratings[['userId', 'movieId', 'rating']]\n",
+ "#pivot_table()의 인자로 columns = 'movieId'와 같이 부여하면 movieId 칼럼의 모든 값이 새로운 칼럼 이름으로 변환\n",
+ "ratings_matrix = ratings.pivot_table('rating', index = 'userId', columns ='movieId')\n",
+ "ratings_matrix.head(3)"
+ ],
+ "metadata": {
+ "id": "2XjxD8d0w825",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 204
+ },
+ "outputId": "e6d906c6-65fe-447b-e2de-4019e9f9a077"
+ },
+ "execution_count": 69,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | movieId | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 3 | \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 6 | \n",
+ " 7 | \n",
+ " 8 | \n",
+ " 9 | \n",
+ " 10 | \n",
+ " ... | \n",
+ " 193565 | \n",
+ " 193567 | \n",
+ " 193571 | \n",
+ " 193573 | \n",
+ " 193579 | \n",
+ " 193581 | \n",
+ " 193583 | \n",
+ " 193585 | \n",
+ " 193587 | \n",
+ " 193609 | \n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " 4.0 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " ... | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " NaN | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 9724 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 69
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* NaN 값이 많음: 사용자가 평점을 매기지 않은 영화가 칼럼으로 변환\n",
+ "* 최소평점이 0 => NaN은 모두 0으로 변환 NaN 값이 많음: 사용자가 평점을 매기지 않은 영화가 칼럼으로 변환\n",
+ "* 최소평점이 0 => NaN은 모두 0으로 변환"
+ ],
+ "metadata": {
+ "id": "TZUOwitLpKH4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#title 칼럼을 얻기 위해 movies와 조인\n",
+ "rating_movies = pd.merge(ratings, movies, on='movieId')\n",
+ "\n",
+ "#columns = 'title'로 title 칼럼으로 피벗 수행\n",
+ "ratings_matrix = rating_movies.pivot_table('rating', index = 'userId', columns='title')\n",
+ "\n",
+ "#NaN을 모두 0으로\n",
+ "ratings_matrix = ratings_matrix.fillna(0)\n",
+ "ratings_matrix.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "ZYUN-yaVpmhf",
+ "outputId": "0a37d271-4a8a-481a-f193-7e440970e7f9"
+ },
+ "execution_count": 70,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 9719 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 70
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**영화 간 유사도 산출**\n",
+ "* cosine_similarity()는 행 간의 유사도 산출 but, 위 데이터에서는 영화가 아닌 userID간의 유사도가 산출됨"
+ ],
+ "metadata": {
+ "id": "-B-F7w8Fq0lk"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_matrix_T = ratings_matrix.transpose()\n",
+ "ratings_matrix_T.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 204
+ },
+ "id": "sI7bKBD2raXy",
+ "outputId": "822147d9-a3d3-4a63-cf3a-4a1f1c913389"
+ },
+ "execution_count": 71,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "userId 1 2 3 4 5 6 7 \\\n",
+ "title \n",
+ "'71 (2014) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
+ "'Hellboy': The Seeds of Creation (2004) 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 \n",
+ "\n",
+ "userId 8 9 10 ... 601 602 603 \\\n",
+ "title ... \n",
+ "'71 (2014) 0.0 0.0 0.0 ... 0.0 0.0 0.0 \n",
+ "'Hellboy': The Seeds of Creation (2004) 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 \n",
+ "\n",
+ "userId 604 605 606 607 608 609 610 \n",
+ "title \n",
+ "'71 (2014) 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 \n",
+ "'Round Midnight (1986) 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n",
+ "\n",
+ "[3 rows x 610 columns]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 3 | \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 6 | \n",
+ " 7 | \n",
+ " 8 | \n",
+ " 9 | \n",
+ " 10 | \n",
+ " ... | \n",
+ " 601 | \n",
+ " 602 | \n",
+ " 603 | \n",
+ " 604 | \n",
+ " 605 | \n",
+ " 606 | \n",
+ " 607 | \n",
+ " 608 | \n",
+ " 609 | \n",
+ " 610 | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | '71 (2014) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 4.0 | \n",
+ " \n",
+ " \n",
+ " | 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 'Round Midnight (1986) | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 610 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_matrix_T"
+ }
+ },
+ "metadata": {},
+ "execution_count": 71
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from sklearn.metrics.pairwise import cosine_similarity\n",
+ "\n",
+ "item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)\n",
+ "\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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 485
+ },
+ "id": "LLg_HOABvdQQ",
+ "outputId": "e8e6b703-f0a8-4ed9-937a-207d0e4bd2d1"
+ },
+ "execution_count": 72,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(9719, 9719)\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | '71 (2014) | \n",
+ " 1.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.141653 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.342055 | \n",
+ " 0.543305 | \n",
+ " 0.707107 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.139431 | \n",
+ " 0.327327 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 0.0 | \n",
+ " 1.000000 | \n",
+ " 0.707107 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " | 'Round Midnight (1986) | \n",
+ " 0.0 | \n",
+ " 0.707107 | \n",
+ " 1.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.176777 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " ... | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 9719 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "item_sim_df"
+ }
+ },
+ "metadata": {},
+ "execution_count": 72
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "item_sim_df[\"Godfather, The (1972)\"].sort_values(ascending=False)[:6]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 303
+ },
+ "id": "U2H_-HrYwSVw",
+ "outputId": "2c47443b-6130-49bd-807a-c963f98142c3"
+ },
+ "execution_count": 73,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " Godfather, The (1972) | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | Godfather, The (1972) | \n",
+ " 1.000000 | \n",
+ " \n",
+ " \n",
+ " | Godfather: Part II, The (1974) | \n",
+ " 0.821773 | \n",
+ " \n",
+ " \n",
+ " | Goodfellas (1990) | \n",
+ " 0.664841 | \n",
+ " \n",
+ " \n",
+ " | One Flew Over the Cuckoo's Nest (1975) | \n",
+ " 0.620536 | \n",
+ " \n",
+ " \n",
+ " | Star Wars: Episode IV - A New Hope (1977) | \n",
+ " 0.595317 | \n",
+ " \n",
+ " \n",
+ " | Fargo (1996) | \n",
+ " 0.588614 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "execution_count": 73
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "item_sim_df[\"Inception (2010)\"].sort_values(ascending=False)[1:6]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 272
+ },
+ "id": "6WdgQhjiwmjR",
+ "outputId": "de8bc4db-3e05-4448-e802-f4e36e952326"
+ },
+ "execution_count": 74,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " Inception (2010) | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | Dark Knight, The (2008) | \n",
+ " 0.727263 | \n",
+ " \n",
+ " \n",
+ " | Inglourious Basterds (2009) | \n",
+ " 0.646103 | \n",
+ " \n",
+ " \n",
+ " | Shutter Island (2010) | \n",
+ " 0.617736 | \n",
+ " \n",
+ " \n",
+ " | Dark Knight Rises, The (2012) | \n",
+ " 0.617504 | \n",
+ " \n",
+ " \n",
+ " | Fight Club (1999) | \n",
+ " 0.615417 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "execution_count": 74
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천**\n",
+ ""
+ ],
+ "metadata": {
+ "id": "7kVnf14sw3_v"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ ""
+ ],
+ "metadata": {
+ "id": "5fiwPSG48Nr7"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* N값은 아이템의 최근접 이웃 범위 계수를 의미-> 특정 아이템과 유사도가 가장 높은 Top-N개의 다른 아이템을 추출하는데 사용"
+ ],
+ "metadata": {
+ "id": "Pb7HZydc8e-i"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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"
+ ],
+ "metadata": {
+ "id": "Gm7XQcyY9I86"
+ },
+ "execution_count": 75,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#예측 평점\n",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "aaHeQJkeqzS0",
+ "outputId": "2fa188c1-dfb2-4c35-bd3c-22ff73304a77"
+ },
+ "execution_count": 35,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 0.070345 | \n",
+ " 0.577855 | \n",
+ " 0.321696 | \n",
+ " 0.227055 | \n",
+ " 0.206958 | \n",
+ " 0.194615 | \n",
+ " 0.249883 | \n",
+ " 0.102542 | \n",
+ " 0.157084 | \n",
+ " 0.178197 | \n",
+ " ... | \n",
+ " 0.113608 | \n",
+ " 0.181738 | \n",
+ " 0.133962 | \n",
+ " 0.128574 | \n",
+ " 0.006179 | \n",
+ " 0.212070 | \n",
+ " 0.192921 | \n",
+ " 0.136024 | \n",
+ " 0.292955 | \n",
+ " 0.720347 | \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " 0.018260 | \n",
+ " 0.042744 | \n",
+ " 0.018861 | \n",
+ " 0.000000 | \n",
+ " 0.000000 | \n",
+ " 0.035995 | \n",
+ " 0.013413 | \n",
+ " 0.002314 | \n",
+ " 0.032213 | \n",
+ " 0.014863 | \n",
+ " ... | \n",
+ " 0.015640 | \n",
+ " 0.020855 | \n",
+ " 0.020119 | \n",
+ " 0.015745 | \n",
+ " 0.049983 | \n",
+ " 0.014876 | \n",
+ " 0.021616 | \n",
+ " 0.024528 | \n",
+ " 0.017563 | \n",
+ " 0.000000 | \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " 0.011884 | \n",
+ " 0.030279 | \n",
+ " 0.064437 | \n",
+ " 0.003762 | \n",
+ " 0.003749 | \n",
+ " 0.002722 | \n",
+ " 0.014625 | \n",
+ " 0.002085 | \n",
+ " 0.005666 | \n",
+ " 0.006272 | \n",
+ " ... | \n",
+ " 0.006923 | \n",
+ " 0.011665 | \n",
+ " 0.011800 | \n",
+ " 0.012225 | \n",
+ " 0.000000 | \n",
+ " 0.008194 | \n",
+ " 0.007017 | \n",
+ " 0.009229 | \n",
+ " 0.010420 | \n",
+ " 0.084501 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 9719 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_pred_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 35
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#실제 평점과의 차이\n",
+ "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))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "svZOAsty--90",
+ "outputId": "583a9cbf-755d-48d8-a5fd-4aeea4baf766"
+ },
+ "execution_count": 36,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "아이템 기반 모든 최근접 이웃 MSE: 9.895354759094706\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ " return pred"
+ ],
+ "metadata": {
+ "id": "8oTvBsktAPNK"
+ },
+ "execution_count": 37,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "ratings_pred_matrix = pd.DataFrame(data = ratings_pred, index = ratings_matrix.index, columns= ratings_matrix.columns)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "zb1_ffRyBFeq",
+ "outputId": "41ec29db-b0f4-4653-abc9-7cff42c04294"
+ },
+ "execution_count": 38,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "아이템 기반 최근접 Top-20 이웃 MSE: 3.6949827608772314\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "user_rating_id = ratings_matrix.loc[9, :]\n",
+ "user_rating_id[user_rating_id >0].sort_values(ascending=False)[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 429
+ },
+ "id": "R5Bqv4mJBhiD",
+ "outputId": "2cc475d9-f9ba-4bbb-a486-55a022bf0d71"
+ },
+ "execution_count": 39,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " 9 | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | Adaptation (2002) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Citizen Kane (1941) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Producers, The (1968) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Lord of the Rings: The Two Towers, The (2002) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Lord of the Rings: The Fellowship of the Ring, The (2001) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Back to the Future (1985) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Austin Powers in Goldmember (2002) | \n",
+ " 5.0 | \n",
+ " \n",
+ " \n",
+ " | Minority Report (2002) | \n",
+ " 4.0 | \n",
+ " \n",
+ " \n",
+ " | Witness (1985) | \n",
+ " 4.0 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "execution_count": 39
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_unseen_movies(ratings_matrix, userId):\n",
+ " user_rating = ratings_matrix.loc[userId, :]\n",
+ " already_seen = user_rating[user_rating >0].index.tolist()\n",
+ " movies_list = ratings_matrix.columns.tolist()\n",
+ " unseen_list = [movie for movie in movies_list if movie not in already_seen]\n",
+ " return unseen_list"
+ ],
+ "metadata": {
+ "id": "Ad--lUP_BvfG"
+ },
+ "execution_count": 40,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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,\n",
+ " columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 394
+ },
+ "id": "HynFIaJqCYX5",
+ "outputId": "fa64ce0c-37ea-41ce-f0f7-1970816fc495"
+ },
+ "execution_count": 41,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " pred_score | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | Shrek (2001) | \n",
+ " 0.866202 | \n",
+ " \n",
+ " \n",
+ " | Spider-Man (2002) | \n",
+ " 0.857854 | \n",
+ " \n",
+ " \n",
+ " | Last Samurai, The (2003) | \n",
+ " 0.817473 | \n",
+ " \n",
+ " \n",
+ " | Indiana Jones and the Temple of Doom (1984) | \n",
+ " 0.816626 | \n",
+ " \n",
+ " \n",
+ " | Matrix Reloaded, The (2003) | \n",
+ " 0.800990 | \n",
+ " \n",
+ " \n",
+ " | Harry Potter and the Sorcerer's Stone (a.k.a. Harry Potter and the Philosopher's Stone) (2001) | \n",
+ " 0.765159 | \n",
+ " \n",
+ " \n",
+ " | Gladiator (2000) | \n",
+ " 0.740956 | \n",
+ " \n",
+ " \n",
+ " | Matrix, The (1999) | \n",
+ " 0.732693 | \n",
+ " \n",
+ " \n",
+ " | Pirates of the Caribbean: The Curse of the Black Pearl (2003) | \n",
+ " 0.689591 | \n",
+ " \n",
+ " \n",
+ " | Lord of the Rings: The Return of the King, The (2003) | \n",
+ " 0.676711 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "recomm_movies",
+ "summary": "{\n \"name\": \"recomm_movies\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Pirates of the Caribbean: The Curse of the Black Pearl (2003)\",\n \"Spider-Man (2002)\",\n \"Harry Potter and the Sorcerer's Stone (a.k.a. Harry Potter and the Philosopher's Stone) (2001)\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"pred_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.06614432811511851,\n \"min\": 0.6767108283499336,\n \"max\": 0.8662018746933645,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.6895905595608812,\n 0.8578535950426878,\n 0.7651586070058114\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 41
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**행렬 분해를 이용한 잠재 요인 협업 필터링 실습**\n"
+ ],
+ "metadata": {
+ "id": "UL4MPaLtDKba"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\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",
+ " 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",
+ " return P,Q"
+ ],
+ "metadata": {
+ "id": "SiZFW1s4IJY9"
+ },
+ "execution_count": 42,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "ratings = pd.read_csv('/content/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",
+ "ratings_matrix = rating_movies.pivot_table('rating', index = 'userId', columns = 'title')"
+ ],
+ "metadata": {
+ "id": "MwdnEOAzJgkw"
+ },
+ "execution_count": 43,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3emxwCD4KIV8",
+ "outputId": "877eacb7-910f-402c-f682-1571df1ec890"
+ },
+ "execution_count": 44,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "### iteration step: 0 rmse: None\n",
+ "### iteration step: 10 rmse: None\n",
+ "### iteration step: 20 rmse: None\n",
+ "### iteration step: 30 rmse: None\n",
+ "### iteration step: 40 rmse: None\n",
+ "### iteration step: 50 rmse: None\n",
+ "### iteration step: 60 rmse: None\n",
+ "### iteration step: 70 rmse: None\n",
+ "### iteration step: 80 rmse: None\n",
+ "### iteration step: 90 rmse: None\n",
+ "### iteration step: 100 rmse: None\n",
+ "### iteration step: 110 rmse: None\n",
+ "### iteration step: 120 rmse: None\n",
+ "### iteration step: 130 rmse: None\n",
+ "### iteration step: 140 rmse: None\n",
+ "### iteration step: 150 rmse: None\n",
+ "### iteration step: 160 rmse: None\n",
+ "### iteration step: 170 rmse: None\n",
+ "### iteration step: 180 rmse: None\n",
+ "### iteration step: 190 rmse: None\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_pred_matrix = pd.DataFrame(data=pred_matrix, index= ratings_matrix.index, columns = ratings_matrix.columns)\n",
+ "ratings_pred_matrix.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "tRE-gmHYKQYx",
+ "outputId": "c3ffec10-c3a4-4cb6-c7b8-871d87127040"
+ },
+ "execution_count": 45,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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]"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " '71 (2014) | \n",
+ " 'Hellboy': The Seeds of Creation (2004) | \n",
+ " 'Round Midnight (1986) | \n",
+ " 'Salem's Lot (2004) | \n",
+ " 'Til There Was You (1997) | \n",
+ " 'Tis the Season for Love (2015) | \n",
+ " 'burbs, The (1989) | \n",
+ " 'night Mother (1986) | \n",
+ " (500) Days of Summer (2009) | \n",
+ " *batteries not included (1987) | \n",
+ " ... | \n",
+ " Zulu (2013) | \n",
+ " [REC] (2007) | \n",
+ " [REC]² (2009) | \n",
+ " [REC]³ 3 Génesis (2012) | \n",
+ " anohana: The Flower We Saw That Day - The Movie (2013) | \n",
+ " eXistenZ (1999) | \n",
+ " xXx (2002) | \n",
+ " xXx: State of the Union (2005) | \n",
+ " ¡Three Amigos! (1986) | \n",
+ " À nous la liberté (Freedom for Us) (1931) | \n",
+ " \n",
+ " \n",
+ " | userId | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 3.055084 | \n",
+ " 4.092018 | \n",
+ " 3.564130 | \n",
+ " 4.502167 | \n",
+ " 3.981215 | \n",
+ " 1.271694 | \n",
+ " 3.603274 | \n",
+ " 2.333266 | \n",
+ " 5.091749 | \n",
+ " 3.972454 | \n",
+ " ... | \n",
+ " 1.402608 | \n",
+ " 4.208382 | \n",
+ " 3.705957 | \n",
+ " 2.720514 | \n",
+ " 2.787331 | \n",
+ " 3.475076 | \n",
+ " 3.253458 | \n",
+ " 2.161087 | \n",
+ " 4.010495 | \n",
+ " 0.859474 | \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " 3.170119 | \n",
+ " 3.657992 | \n",
+ " 3.308707 | \n",
+ " 4.166521 | \n",
+ " 4.311890 | \n",
+ " 1.275469 | \n",
+ " 4.237972 | \n",
+ " 1.900366 | \n",
+ " 3.392859 | \n",
+ " 3.647421 | \n",
+ " ... | \n",
+ " 0.973811 | \n",
+ " 3.528264 | \n",
+ " 3.361532 | \n",
+ " 2.672535 | \n",
+ " 2.404456 | \n",
+ " 4.232789 | \n",
+ " 2.911602 | \n",
+ " 1.634576 | \n",
+ " 4.135735 | \n",
+ " 0.725684 | \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " 2.307073 | \n",
+ " 1.658853 | \n",
+ " 1.443538 | \n",
+ " 2.208859 | \n",
+ " 2.229486 | \n",
+ " 0.780760 | \n",
+ " 1.997043 | \n",
+ " 0.924908 | \n",
+ " 2.970700 | \n",
+ " 2.551446 | \n",
+ " ... | \n",
+ " 0.520354 | \n",
+ " 1.709494 | \n",
+ " 2.281596 | \n",
+ " 1.782833 | \n",
+ " 1.635173 | \n",
+ " 1.323276 | \n",
+ " 2.887580 | \n",
+ " 1.042618 | \n",
+ " 2.293890 | \n",
+ " 0.396941 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 rows × 9719 columns \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "ratings_pred_matrix"
+ }
+ },
+ "metadata": {},
+ "execution_count": 45
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# 사용자가 관람하지 않은 영화명 추출\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",
+ "# 평점 데이터를 DataFrame으로 생성.\n",
+ "recomm_movies = pd.DataFrame(data=recomm_movies.values, index=recomm_movies.index, columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 394
+ },
+ "id": "D2_UAkzhKei7",
+ "outputId": "fc975360-2ab2-42bb-b03f-1772bf2a5612"
+ },
+ "execution_count": 46,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "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"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " pred_score | \n",
+ " \n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " | Rear Window (1954) | \n",
+ " 5.704612 | \n",
+ " \n",
+ " \n",
+ " | South Park: Bigger, Longer and Uncut (1999) | \n",
+ " 5.451100 | \n",
+ " \n",
+ " \n",
+ " | Rounders (1998) | \n",
+ " 5.298393 | \n",
+ " \n",
+ " \n",
+ " | Blade Runner (1982) | \n",
+ " 5.244951 | \n",
+ " \n",
+ " \n",
+ " | Roger & Me (1989) | \n",
+ " 5.191962 | \n",
+ " \n",
+ " \n",
+ " | Gattaca (1997) | \n",
+ " 5.183179 | \n",
+ " \n",
+ " \n",
+ " | Ben-Hur (1959) | \n",
+ " 5.130463 | \n",
+ " \n",
+ " \n",
+ " | Rosencrantz and Guildenstern Are Dead (1990) | \n",
+ " 5.087375 | \n",
+ " \n",
+ " \n",
+ " | Big Lebowski, The (1998) | \n",
+ " 5.038690 | \n",
+ " \n",
+ " \n",
+ " | Star Wars: Episode V - The Empire Strikes Back (1980) | \n",
+ " 4.989601 | \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "recomm_movies",
+ "summary": "{\n \"name\": \"recomm_movies\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"Big Lebowski, The (1998)\",\n \"South Park: Bigger, Longer and Uncut (1999)\",\n \"Gattaca (1997)\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"pred_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.21272885538651393,\n \"min\": 4.989601238872484,\n \"max\": 5.704612469838172,\n \"num_unique_values\": 10,\n \"samples\": [\n 5.0386897288205725,\n 5.451100205772531,\n 5.183178550884765\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
+ }
+ },
+ "metadata": {},
+ "execution_count": 46
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# **08.파이썬 추천 시스템 패키지-Surprise**"
+ ],
+ "metadata": {
+ "id": "8yZkAqfbiT78"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "!pip install scikit-surprise"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "moXV4rgAiaVQ",
+ "outputId": "6498b906-947f-45f2-bdc7-d47e1a81db6a"
+ },
+ "execution_count": 47,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Requirement already satisfied: scikit-surprise in /usr/local/lib/python3.12/dist-packages (1.1.4)\n",
+ "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.5.3)\n",
+ "Requirement already satisfied: numpy>=1.19.5 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.26.4)\n",
+ "Requirement already satisfied: scipy>=1.6.0 in /usr/local/lib/python3.12/dist-packages (from scikit-surprise) (1.16.3)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Surprise에서 데이터 로딩은 Dataset 클래스를 이용해서만 가능.\n",
+ "* Surprise는 무비렌즈 사이트에서 제공하는 과거 버전의 데이터 세트를 가져오는 API를 제공\n",
+ "* Surprise Dataset 클래스의 load_builtin()은 무비렌즈 사이트에서 제공하는 과거 버전\n",
+ "데이터 세트인 ‘ml-10*(10만 개 평점 데이터) 또는 ‘ml-lm’(100만 개 평점 데이터) 데이터를 아카\n",
+ "이브 사이트로부터 내려받아 로컬 디렉터리에 저장한 뒤 데이터를 로딩합니다."
+ ],
+ "metadata": {
+ "id": "MHg0fV8ulD2t"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise import SVD\n",
+ "from surprise import Dataset\n",
+ "from surprise import accuracy\n",
+ "from surprise.model_selection import train_test_split"
+ ],
+ "metadata": {
+ "id": "DiqAwYlhkZNT"
+ },
+ "execution_count": 48,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "data = Dataset.load_builtin('ml-100k')\n",
+ "#수행 시마다 동일하게 데이터를 분할하기 위해 random_state 값 부여\n",
+ "trainset, testset = train_test_split(data, test_size =.25, random_state=0)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "B_Ws32sEqTS9",
+ "outputId": "421d975d-a273-4ccd-8a5b-0e505a2db2e3"
+ },
+ "execution_count": 76,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Dataset ml-100k could not be found. Do you want to download it? [Y/n] 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 /root/.surprise_data/ml-100k\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* 과거 버전의 데이터 세트. 분리 문자가 \\t문자\n",
+ "* 무비렌즈 사이트에서 내려받은 데이터 파일과 동이랗게 로우 레벨의 사용자-아이템 평점 데이터를 그대로 적용 과거 버전의 데이터 세트. 분리 문자가 \\t문자\n",
+ "* 무비렌즈 사이트에서 내려받은 데이터 파일과 동이랗게 로우 레벨의 사용자-아이템 평점 데이터를 그대로 적용"
+ ],
+ "metadata": {
+ "id": "wwyFrujWqo1b"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "algo = SVD(random_state=0)\n",
+ "algo.fit(trainset)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "urgvZhtwrX1u",
+ "outputId": "fc59dc3e-8d58-4bf1-9dac-83e0360ff754"
+ },
+ "execution_count": 77,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 77
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "predictions = algo.test(testset)\n",
+ "print('prediction type:', type(predictions), 'size:', len(predictions))\n",
+ "print('prediction 결과의 최초 5개 추출')\n",
+ "predictions[:5]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "z_GMf6ChrpkA",
+ "outputId": "1efa8ca0-a7f4-4322-d275-0f6ca255d67d"
+ },
+ "execution_count": 78,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "prediction type: size: 25000\n",
+ "prediction 결과의 최초 5개 추출\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "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.8463639495936905, details={'was_impossible': False}),\n",
+ " Prediction(uid='751', iid='385', r_ui=4.0, est=3.1807542478219157, details={'was_impossible': False})]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 78
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "[(pred.uid, pred.iid, pred.est) for pred in predictions[:3]]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "L8vU9v2VsYxG",
+ "outputId": "9ec028ae-c219-46c9-a3cc-e542e0410cc4"
+ },
+ "execution_count": 79,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "[('120', '282', 3.5114147666251547),\n",
+ " ('882', '291', 3.573872419581491),\n",
+ " ('535', '507', 4.033583485472447)]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 79
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "uid = str(196)\n",
+ "iid = str(302)\n",
+ "pred = algo.predict(uid, iid)\n",
+ "print(pred)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3LXfM9KfsenL",
+ "outputId": "5bd249db-4e4f-4634-b2f0-c3476031a094"
+ },
+ "execution_count": 80,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "user: 196 item: 302 r_ui = None est = 4.49 {'was_impossible': False}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "accuracy.rmse(predictions)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ZVUmMYoos1HB",
+ "outputId": "064ee1b1-3fc1-419c-ed03-2ed0d35e27d1"
+ },
+ "execution_count": 81,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.9467\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.9466860806937948"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 81
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**OS 파일 데이터를 Surprise 데이터 세트로 로딩**\n",
+ "* 주의: 로딩되는 데이터 파일에 칼럼명을 가지는 헤더 문자열이 있어서는 안됨. -> ratings.csv 파일은 헤더를 가지고 있음.->to_csv() 함수로 헤더 삭제"
+ ],
+ "metadata": {
+ "id": "pB5LFb3bs5zF"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "#헤더 제거한 새로운 파일 생성\n",
+ "ratings.to_csv('/content/ratings_noh.csv', index = False, header=False)"
+ ],
+ "metadata": {
+ "id": "9K0qLK5DudQ1"
+ },
+ "execution_count": 95,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Reader 클래스를 이용해 데이터 파일의 파싱 포맷을 정의\n",
+ "* 4개의 칼럼이 사용자 아이디, 아이템 아이디, 평점, 타임스탬프임을 로딩할 때 알려줘야함"
+ ],
+ "metadata": {
+ "id": "YJC8vh3luzzr"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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('/content/ratings_noh.csv', reader=reader)"
+ ],
+ "metadata": {
+ "id": "G4JqDZCcvC7Z"
+ },
+ "execution_count": 96,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "* Surprise 데이터 세트는 기본적으로 무비렌즈 데이터 형식을 따르므로 무비렌즈 데이터 형식이 아닌\n",
+ "다른 OS 파일의 경우 Reader 클래스를 먼저 설정\n",
+ "**Reader 클래스 주요 파라미터**\n",
+ "* line_format(string): 칼럼을 순서대로 나열. 문자열을 공백으로 분리\n",
+ "* sep(char): 칼럼을 분리하는 분리자. 디폴트: \\t. 판다스에서 입력받을 경우 기재 필요X\n",
+ "* rating_scale(tuple, optional): 평점 값의 최소~최대 평점 설저. 디폴트: (1,5)"
+ ],
+ "metadata": {
+ "id": "P1oYHVz9wQKj"
+ }
+ },
+ {
+ "cell_type": "code",
+ "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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "HIxK1R0TxDli",
+ "outputId": "53b1e56f-4476-4a59-b7bf-76195f174b60"
+ },
+ "execution_count": 97,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 97
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "** 판다스 DataFrame에서 Surprise 데이터 세트로 로딩**"
+ ],
+ "metadata": {
+ "id": "2yTvebbex2Ix"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "from surprise import Reader, Dataset\n",
+ "\n",
+ "ratings = pd.read_csv('/content/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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "T4k_EmaPx56x",
+ "outputId": "98635611-b126-4a34-c525-f1006567d9ec"
+ },
+ "execution_count": 98,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 98
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**교차 검증과 하이퍼 파라미터 튜닝**\n",
+ "* Surprise는 교차 검증과 하이퍼 파라미터 튜닝을 위해 사이킷런과 유사한 cross_validate( )와 GridSearchCV 클래스를 제공"
+ ],
+ "metadata": {
+ "id": "iLmPV9Mt29JF"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise.model_selection import cross_validate\n",
+ "\n",
+ "ratings = pd.read_csv('/content/ratings.csv')\n",
+ "reader = Reader(rating_scale = (0.5, 5.0))\n",
+ "data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\n",
+ "\n",
+ "algo = SVD(random_state = 0)\n",
+ "cross_validate(algo, data, measures=['RMSE', 'MAE'], cv = 5, verbose= True)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "R5HFRwSg3Fz7",
+ "outputId": "3e34caee-f2b0-49bd-eef8-15bcc76f43e8"
+ },
+ "execution_count": 100,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "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.8739 0.8639 0.8700 0.8787 0.8775 0.8728 0.0054 \n",
+ "MAE (testset) 0.6704 0.6644 0.6710 0.6747 0.6739 0.6709 0.0036 \n",
+ "Fit time 2.36 1.69 1.51 1.63 1.86 1.81 0.30 \n",
+ "Test time 0.20 0.11 0.11 0.13 0.19 0.15 0.04 \n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "{'test_rmse': array([0.87386097, 0.86385394, 0.87001019, 0.87869206, 0.87754842]),\n",
+ " 'test_mae': array([0.67040961, 0.66442237, 0.67098542, 0.67468529, 0.67385167]),\n",
+ " 'fit_time': (2.3593761920928955,\n",
+ " 1.6873219013214111,\n",
+ " 1.5088114738464355,\n",
+ " 1.625060796737671,\n",
+ " 1.8643643856048584),\n",
+ " 'test_time': (0.19586944580078125,\n",
+ " 0.11407184600830078,\n",
+ " 0.11287569999694824,\n",
+ " 0.12824273109436035,\n",
+ " 0.19462895393371582)}"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 100
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ "print(gs.best_score['rmse'])\n",
+ "print(gs.best_params['rmse'])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "eq5jlRby37on",
+ "outputId": "9c873c46-8033-46e5-8fb9-2f28b89a66df"
+ },
+ "execution_count": 102,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "0.8779338881055662\n",
+ "{'n_epochs': 20, 'n_factors': 50}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**Surprise를 이용한 개인화 영화 추천 시스템 구축**"
+ ],
+ "metadata": {
+ "id": "9PQ4ss695v_D"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#다음 코드는 train_test_split()으로 분리되지 않은 데이터 세트에 fit()을 호출해 오류가 발생\n",
+ "data = Dataset.load_from_df(ratings['userId', 'movieId', 'rating'], reader)\n",
+ "algo = SVD(n_factors = 50, random_state=0)\n",
+ "algo.fit(data)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 569
+ },
+ "id": "Qp0Q5TPI6Dgq",
+ "outputId": "000b165e-226e-4943-a516-c84ea91985df"
+ },
+ "execution_count": 103,
+ "outputs": [
+ {
+ "output_type": "error",
+ "ename": "KeyError",
+ "evalue": "('userId', 'movieId', 'rating')",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3804\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3805\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3806\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32mindex.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mindex.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;31mKeyError\u001b[0m: ('userId', 'movieId', 'rating')",
+ "\nThe above exception was the direct cause of the following exception:\n",
+ "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/tmp/ipython-input-3089583357.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m#다음 코드는 train_test_split()으로 분리되지 않은 데이터 세트에 fit()을 호출해 오류가 발생\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDataset\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload_from_df\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mratings\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'userId'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'movieId'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'rating'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreader\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0malgo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mSVD\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn_factors\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m50\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0malgo\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/frame.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 4100\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnlevels\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4101\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_getitem_multilevel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 4102\u001b[0;31m \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4103\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_integer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4104\u001b[0m \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mindexer\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/usr/local/lib/python3.12/dist-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3810\u001b[0m ):\n\u001b[1;32m 3811\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mInvalidIndexError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3812\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3813\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3814\u001b[0m \u001b[0;31m# If we have a listlike key, _check_indexing_error will raise\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mKeyError\u001b[0m: ('userId', 'movieId', 'rating')"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "#DatasetAutoFolds 클래스\n",
+ "from surprise.dataset import DatasetAutoFolds\n",
+ "\n",
+ "reader = Reader(line_format='user item rating timestamp', sep = ',', rating_scale = (0.5,5))\n",
+ "#DatasetAutoFolds 클래스를 ratings_noh.csv 파일 기반으로 생성\n",
+ "data_folds = DatasetAutoFolds(ratings_file = '/content/ratings_noh.csv', reader = reader)\n",
+ "\n",
+ "#전체 데이터를 학습 데이터로 생성\n",
+ "trainset = data_folds.build_full_trainset()"
+ ],
+ "metadata": {
+ "id": "TuxvxeSp6Xhr"
+ },
+ "execution_count": 104,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "algo = SVD(n_epochs=20, n_factors = 50, random_state=0)\n",
+ "algo.fit(trainset)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "D7t3MOdeuHzf",
+ "outputId": "0c0f45bf-a3eb-47cf-ebdf-bcf3b819e790"
+ },
+ "execution_count": 105,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 105
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "\n",
+ "movieIds = ratings[ratings['userId'] == 9]['movieId']\n",
+ "\n",
+ "if movieIds[movieIds==42].count() ==0:\n",
+ " print('사용자 아이디 9는 영화 아이디 42의 평점 없음')\n",
+ "\n",
+ "print(movies[movies['movieId']==42])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "93GKv6Td7EmI",
+ "outputId": "f3389d01-7eba-4dd9-ab07-a2b7b9c46155"
+ },
+ "execution_count": 108,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "사용자 아이디 9는 영화 아이디 42의 평점 없음\n",
+ " movieId title genres\n",
+ "38 42 Dead Presidents (1995) Action|Crime|Drama\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "uid = str(9)\n",
+ "iid = str(42)\n",
+ "\n",
+ "pred = algo.predict(uid, iid, verbose=True)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "a0ps2XQ77hAG",
+ "outputId": "dce1d789-13bc-450e-9848-baf9d47faf04"
+ },
+ "execution_count": 109,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "user: 9 item: 42 r_ui = None est = 3.13 {'was_impossible': False}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_unseen_surprise(ratings, movies, userId):\n",
+ " seen_movies = ratings[ratings['userId']==userId]['movieId'].tolist()\n",
+ " total_movies = movies['movieId'].tolist()\n",
+ "\n",
+ " unseen_movies = [movie for movie in total_movies if movie not in 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)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "SVL_7m017o_u",
+ "outputId": "b502a190-dc8d-41ab-dffe-75ca1c2b5bcb"
+ },
+ "execution_count": 111,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9742 전체 영화 수: 9742\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "\n",
+ " def sortkey_est(pred):\n",
+ " return pred.est\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",
+ "\n",
+ "print('#### Top-10 추천 영화 리스트 ####')\n",
+ "for top_movie in top_movie_preds:\n",
+ " print(top_movie[1], \":\", top_movie[2])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Ed_wXo3I8z6A",
+ "outputId": "b119bf8f-2239-497f-db87-75aa1409bbce"
+ },
+ "execution_count": 112,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9742 전체 영화 수: 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.278152632122759\n",
+ "Godfather, The (1972) : 4.226073566460876\n",
+ "Streetcar Named Desire, A (1951) : 4.205267497432363\n",
+ "Star Wars: Episode V - The Empire Strikes Back (1980) : 4.1918097904381995\n",
+ "Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981) : 4.154746591122657\n",
+ "Star Wars: Episode VI - Return of the Jedi (1983) : 4.122016128534504\n",
+ "Goodfellas (1990) : 4.118002684813024\n",
+ "Lord of the Rings: The Fellowship of the Ring, The (2001) : 4.108009609093436\n"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.pdf" "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.pdf"
new file mode 100644
index 0000000..b9d32fd
Binary files /dev/null and "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\354\235\264\354\213\234\355\230\204.pdf" differ
| |