diff --git "a/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb" "b/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb"
new file mode 100644
index 0000000..487ff88
--- /dev/null
+++ "b/Week16_\353\263\265\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb"
@@ -0,0 +1,6341 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# 9.5 콘텐츠 기반 필터링 실습 - TMDB 5000 영화 데이터 세트"
+ ],
+ "metadata": {
+ "id": "rJPqTIZaGqKl"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 데이터 로딩 및 가공"
+ ],
+ "metadata": {
+ "id": "VgjJpGhAIzxS"
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 205
+ },
+ "id": "NOAHBjttGlVu",
+ "outputId": "40ffa544-8df5-4e93-d667-4f54bb8c3613"
+ },
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 20)\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " budget genres \\\n",
+ "0 237000000 [{\"id\": 28, \"name\": \"Action\"}, {\"id\": 12, \"nam... \n",
+ "\n",
+ " homepage id \\\n",
+ "0 http://www.avatarmovie.com/ 19995 \n",
+ "\n",
+ " keywords original_language \\\n",
+ "0 [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\":... en \n",
+ "\n",
+ " original_title overview \\\n",
+ "0 Avatar In the 22nd century, a paraplegic Marine is di... \n",
+ "\n",
+ " popularity production_companies \\\n",
+ "0 150.437577 [{\"name\": \"Ingenious Film Partners\", \"id\": 289... \n",
+ "\n",
+ " production_countries release_date revenue \\\n",
+ "0 [{\"iso_3166_1\": \"US\", \"name\": \"United States o... 2009-12-10 2787965087 \n",
+ "\n",
+ " runtime spoken_languages status \\\n",
+ "0 162.0 [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso... Released \n",
+ "\n",
+ " tagline title vote_average vote_count \n",
+ "0 Enter the World of Pandora. Avatar 7.2 11800 "
+ ],
+ "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, \"nam... | \n",
+ " http://www.avatarmovie.com/ | \n",
+ " 19995 | \n",
+ " [{\"id\": 1463, \"name\": \"culture clash\"}, {\"id\":... | \n",
+ " en | \n",
+ " Avatar | \n",
+ " In the 22nd century, a paraplegic Marine is di... | \n",
+ " 150.437577 | \n",
+ " [{\"name\": \"Ingenious Film Partners\", \"id\": 289... | \n",
+ " [{\"iso_3166_1\": \"US\", \"name\": \"United States o... | \n",
+ " 2009-12-10 | \n",
+ " 2787965087 | \n",
+ " 162.0 | \n",
+ " [{\"iso_639_1\": \"en\", \"name\": \"English\"}, {\"iso... | \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": 1
+ }
+ ],
+ "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)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "주요 칼럼 추출"
+ ],
+ "metadata": {
+ "id": "qNsmjGPCIRJC"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity',\n",
+ " 'keywords', 'overview']]"
+ ],
+ "metadata": {
+ "id": "4TNKxIviIDUp"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "칼럼 형태 확인"
+ ],
+ "metadata": {
+ "id": "M5pid7eXIaHa"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "pd.set_option('max_colwidth', 100)\n",
+ "movies_df[['genres', 'keywords']][:1]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 115
+ },
+ "id": "bJO6hkmsISoc",
+ "outputId": "5a65afd5-0bab-469a-cb53-ce9173ab257c"
+ },
+ "execution_count": null,
+ "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": 3
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "genres 칼럼의 문자열을 분해하여 개별 장르르 파이썬 리스트 객체로 추출"
+ ],
+ "metadata": {
+ "id": "Up6KU7XiIeh-"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from ast import literal_eval\n",
+ "\n",
+ "movies_df['genres'] = movies_df['genres'].apply(literal_eval)\n",
+ "movies_df['keywords'] = movies_df['keywords'].apply(literal_eval)"
+ ],
+ "metadata": {
+ "id": "Ul69Zi84Ikaw"
+ },
+ "execution_count": null,
+ "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": 98
+ },
+ "id": "qPXQXjaGIo1f",
+ "outputId": "9b2e3f0d-0679-49b8-d265-2ea0df41b8f3"
+ },
+ "execution_count": null,
+ "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": 5
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 장르 콘텐츠 유사도 측정"
+ ],
+ "metadata": {
+ "id": "wF-EwUNhI2DP"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "CountVectorizer를 이용해 피처 벡터 행렬로 만들기"
+ ],
+ "metadata": {
+ "id": "p0JoUt53I8fo"
+ }
+ },
+ {
+ "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=0.0, 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": "qN-9lN7WI4U3",
+ "outputId": "6874ceca-e0f0-4bde-f96a-baf600826cc3"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(4803, 276)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "cosine_similarities() 적용"
+ ],
+ "metadata": {
+ "id": "nWCIyAimJRiC"
+ }
+ },
+ {
+ "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": "JcMPpFv2JUCv",
+ "outputId": "d8bb07d8-8654-4fb1-cf67-4e3bc4f137d2"
+ },
+ "execution_count": null,
+ "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": "markdown",
+ "source": [
+ "genre_sim 객체의 기준 행별로 비교 대상이 되는 행의 유사도 값이 높은 순으로 정렬된 행렬의 위치 추출"
+ ],
+ "metadata": {
+ "id": "58I4OZflJhZo"
+ }
+ },
+ {
+ "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": "IsN4DsWjJnAk",
+ "outputId": "ebd6cff7-98e0-4743-bd85-8b0069877fb3"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "[[ 0 3494 813 ... 3038 3037 2401]]\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "자신인 0번 레코드를 제외하고 3494 레코드가 가장 유사도가 높음"
+ ],
+ "metadata": {
+ "id": "Brb9N06UJwQq"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 장르 콘텐츠 필터링을 이용한 영화 추천"
+ ],
+ "metadata": {
+ "id": "cp_tE6UeJ0r4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def find_sim_movie(df, sorted_ind, title_name, top_n=10):\n",
+ " # 인자로 입력된 movies_df DataFrame에서 'title' 칼럼이 입력된 title_name 값인 DataFrame 추출\n",
+ " title_movie = df[df['title'] == title_name]\n",
+ "\n",
+ " # title_named을 가진 DataFrame의 index 객체를 ndarray로 반환하고\n",
+ " # sorted_ind 인자로 입력된 genre_sim_sorted_ind 객체에서 유사도 순으로 top_n개의 index 추출\n",
+ " title_index = title_movie.index.values\n",
+ " similar_indexes = sorted_ind[title_index, :(top_n)]\n",
+ "\n",
+ " # 추출된 top_n index 출력. top_n index는 2차원 데이터임\n",
+ " # dataframe에서 index로 사용하기 위해서 1차원 array로 변경\n",
+ " print(similar_indexes)\n",
+ " similar_indexes = similar_indexes.reshape(-1)\n",
+ "\n",
+ " return df.iloc[similar_indexes]"
+ ],
+ "metadata": {
+ "id": "HybFKVhtJ4i6"
+ },
+ "execution_count": null,
+ "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": "l3DRcCWcKgzZ",
+ "outputId": "4b134712-3869-40a0-ed76-66cdb0c1f391"
+ },
+ "execution_count": null,
+ "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": 11
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "vote_average의 왜곡 데이터 확인"
+ ],
+ "metadata": {
+ "id": "eecYKsOmK2qL"
+ }
+ },
+ {
+ "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": "SA5I9cGqK_q4",
+ "outputId": "1dd059d8-4391-43fa-a5ca-506b6922e64e"
+ },
+ "execution_count": null,
+ "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": 12
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "가중 평점 방식을 사용하여 새로운 평점 부여"
+ ],
+ "metadata": {
+ "id": "g35siK8FLHxf"
+ }
+ },
+ {
+ "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": "rIRUpKuKLMHk",
+ "outputId": "379e1b7c-4262-44b2-f03f-aa8a548b1237"
+ },
+ "execution_count": null,
+ "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_df.apply(weighted_vote_average, axis=1)\n",
+ "\n",
+ "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": "J23c26ovLSjI",
+ "outputId": "0ce025c3-12a8-4c71-83d4-4c29723987fb"
+ },
+ "execution_count": null,
+ "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": 15
+ }
+ ]
+ },
+ {
+ "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",
+ " # top_n의 2배에 해당하는 장르 유사성이 높은 인덱스 추출\n",
+ " similar_indexes = sorted_ind[title_index, :(top_n*2)]\n",
+ " similar_indexes = similar_indexes.reshape(-1)\n",
+ "\n",
+ " # 기준 영화 인덱스는 제외\n",
+ " similar_indexes = similar_indexes[similar_indexes != title_index]\n",
+ "\n",
+ " # top_n의 2배에 해당하는 후보군에서 weighted_vote가 높은 순으로 top_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": "PZ5KUbfBLqWB",
+ "outputId": "b864cb8d-015e-4cce-ed74-c1ae7ee4ca36"
+ },
+ "execution_count": null,
+ "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": 17
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# 9.6 아이템 기반 최근접 이웃 협업 필터링 실습"
+ ],
+ "metadata": {
+ "id": "VBZ3sts-GuMP"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 데이터 가공 및 변환"
+ ],
+ "metadata": {
+ "id": "B3oR9vBXMizI"
+ }
+ },
+ {
+ "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": "auYB1hAPGxxf",
+ "outputId": "1ec71c22-c867-42cf-a566-c6fcb5dbf96c"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "(9742, 3)\n",
+ "(100836, 4)\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "협업 필터링: 사용자와 아이템 간의 평점에 기반해 추천하는 시스템"
+ ],
+ "metadata": {
+ "id": "xV7SZjLKMz4Q"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "pivot_table() 함수를 이용하여 데이터 세트 형태 변경"
+ ],
+ "metadata": {
+ "id": "3YnioTKGM5fP"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings = ratings[['userId', 'movieId', 'rating']]\n",
+ "ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')\n",
+ "ratings_matrix.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 224
+ },
+ "id": "NBRupx2rNApG",
+ "outputId": "0379a684-a9da-4231-b868-ce2aaee7fd7b"
+ },
+ "execution_count": null,
+ "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": 19
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# title 칼럼을 얻기 위해 movies와 조인\n",
+ "rating_movies = pd.merge(ratings, movies, on='movieId')\n",
+ "\n",
+ "# columns='title'로 title 칼럼으로 pivot 수행\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": "JUexDpmCNEpa",
+ "outputId": "e6ebc80a-c4a0-4e4e-c326-f7c27be7dede"
+ },
+ "execution_count": null,
+ "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": 21
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 영화 간 유사도 산출"
+ ],
+ "metadata": {
+ "id": "ob78gQnWNQ4J"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "rating_matrix를 transpose하여 cosine similarity 적용"
+ ],
+ "metadata": {
+ "id": "R0yHsTuPNYSB"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_matrix_T = ratings_matrix.transpose()\n",
+ "ratings_matrix_T.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "p2JkARJpNTV2",
+ "outputId": "8da94d38-afd0-4122-ac85-7ab6378eda62"
+ },
+ "execution_count": null,
+ "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": 22
+ }
+ ]
+ },
+ {
+ "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",
+ "# cosine_similarity()로 반환된 넘파이 행렬을 영화명을 매핑해 DataFrame으로 변환\n",
+ "item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns, columns=ratings_matrix.columns)\n",
+ "print(item_sim_df.shape)\n",
+ "item_sim_df.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 485
+ },
+ "id": "qDuJIhUzNgIb",
+ "outputId": "8344b3dd-95eb-42eb-bc1e-74c4e3b9b5a8"
+ },
+ "execution_count": null,
+ "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": 23
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "유사도가 높은 상위 6개 추출"
+ ],
+ "metadata": {
+ "id": "9el7MUIWNzrz"
+ }
+ },
+ {
+ "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": "4j2kg35jN15v",
+ "outputId": "91deac2a-9ac6-4776-b558-8b65849312dd"
+ },
+ "execution_count": null,
+ "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": 24
+ }
+ ]
+ },
+ {
+ "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": "f-wWGSfON7BC",
+ "outputId": "d8a77f20-9f06-4d68-81b9-8296ca50f241"
+ },
+ "execution_count": null,
+ "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": 25
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천"
+ ],
+ "metadata": {
+ "id": "jzuqLSeCN_cw"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "아이템 기반 협업 필터링에서 개인화된 예측 평점\n",
+ "\n",
+ "![image.png](data:image/png;base64,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)"
+ ],
+ "metadata": {
+ "id": "nFjJ-w7DOEas"
+ }
+ },
+ {
+ "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": "I_ka7qPqOCyY"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_pred = predict_rating(ratings_matrix.values, item_sim_df.values)\n",
+ "ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index=ratings_matrix.index, columns=ratings_matrix.columns)\n",
+ "ratings_pred_matrix.head(3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 345
+ },
+ "id": "nuLPgs3IOdGT",
+ "outputId": "48eb3501-8c74-4761-ae8f-36e3e809b19b"
+ },
+ "execution_count": null,
+ "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": 30
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from sklearn.metrics import mean_squared_error\n",
+ "\n",
+ "# 사용자가 평점을 부여한 영화에 대해서만 예측 성능 평가 MSE를 구함\n",
+ "def get_mse(pred, actual):\n",
+ " # 평점이 있는 실제 영화만 추출\n",
+ " pred = pred[actual.nonzero()].flatten()\n",
+ " actual = actual[actual.nonzero()].flatten()\n",
+ " return mean_squared_error(pred, actual)\n",
+ "\n",
+ "print('아이템 기반 모든 최근접 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values))"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "AldMMHQEOxDA",
+ "outputId": "6a28df5d-ccb1-4f81-b895-ab405c63a8a0"
+ },
+ "execution_count": null,
+ "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",
+ " # 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화\n",
+ " pred = np.zeros(ratings_arr.shape)\n",
+ "\n",
+ " # 사용자-아이템 평점 행렬의 열 크기만큼 루프 수행\n",
+ " for col in range(ratings_arr.shape[1]):\n",
+ " # 유사도 행렬에서 유사도가 큰 순으로 n개 데이터 행렬의 인덱스 반환\n",
+ " top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]\n",
+ " # 개인화된 예측 평점 계산\n",
+ " for row in range(ratings_arr.shape[0]):\n",
+ " pred[row, col] = item_sim_arr[col, :][top_n_items].dot(ratings_arr[row, :][top_n_items].T)\n",
+ " pred[row, col] /= np.sum(np.abs(item_sim_arr[col, :][top_n_items]))\n",
+ "\n",
+ " return pred"
+ ],
+ "metadata": {
+ "id": "ZYvF3IgHPFSZ"
+ },
+ "execution_count": null,
+ "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",
+ "# 계싼된 예측 평점 데이터는 DataFrame으로 재생성\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": "l4T0Xp4YPcwf",
+ "outputId": "be5e59bb-59bb-4bc9-906b-90ac5757dc42"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "아이템 기반 최근접 TOP-20 이웃 MSE: 3.6949827608772314\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "평점 높은 순으로 나열"
+ ],
+ "metadata": {
+ "id": "g7nZtGuiPmAb"
+ }
+ },
+ {
+ "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": "YJEMepG9Pnva",
+ "outputId": "3cf6288f-4afa-4d4f-927a-910ea94a0ed3"
+ },
+ "execution_count": null,
+ "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": 35
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "평점이 부여된 영화를 제외하고 반환하는 함수 생성"
+ ],
+ "metadata": {
+ "id": "wGKrLvJ9PtqM"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_unseen_movies(ratings_matrix, userId):\n",
+ " # userId로 입력받은 사용자의 모든 영화 정보를 추출해 Series로 반환\n",
+ " # 반환된 user_rating은 영화명(title)을 인덱스로 가지는 Series 객체\n",
+ " user_rating = ratings_matrix.loc[userId, :]\n",
+ "\n",
+ " # user_rating이 0보다 크면 기존에 관람한 영화임. 대상 인덱스를 추출해 list 객체로 만듦\n",
+ " already_seen = user_rating[user_rating>0].index.tolist()\n",
+ "\n",
+ " # 모든 영화명을 list 객체로 만듦\n",
+ " movies_list = ratings_matrix.columns.tolist()\n",
+ "\n",
+ " # list comprehension으로 already_seen에 해당하는 영화는 movies_list에서 제외함\n",
+ " unseen_list = [movie for movie in movies_list if movie not in already_seen]\n",
+ "\n",
+ " return unseen_list"
+ ],
+ "metadata": {
+ "id": "h_sm_ETKPzob"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):\n",
+ " # 예측 평점 DataFrame에서 사용자id 인덱스와 unseen_list로 들어온 영화명 칼럼을 추출해 가장 예측 평점이 높은 순으로 정렬\n",
+ " recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]\n",
+ " return recomm_movies\n",
+ "\n",
+ "# 사용자가 관람하지 않는 영화명 추출\n",
+ "unseen_list = get_unseen_movies(ratings_matrix, 9)\n",
+ "\n",
+ "# 아이템 기반의 최근접 이웃 협업 필터링으로 영화 추천\n",
+ "recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)\n",
+ "\n",
+ "# 평점 데이터를 DataFrame으로 생성\n",
+ "recomm_movies = pd.DataFrame(data=recomm_movies.values, index=recomm_movies.index, columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 411
+ },
+ "id": "v2jVdlWHQTqt",
+ "outputId": "525597f9-9d11-4ce3-83d0-eca85bd5dd04"
+ },
+ "execution_count": null,
+ "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": 37
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# 9.7 행렬 분해를 이용한 잠재 요인 협업 필터링 실습"
+ ],
+ "metadata": {
+ "id": "jtXsL2NfGyBE"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "SGD 기반 행렬 분해 적용"
+ ],
+ "metadata": {
+ "id": "7omzYA9EQ1xD"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def get_rmse(R, P, Q, non_zeros):\n",
+ " error = 0\n",
+ " # 두 개의 분해된 행렬 P와 Q.T의 내적으로 예측 R 행렬 생성\n",
+ " full_pred_matrix = np.dot(P, Q.T)\n",
+ "\n",
+ " # 실제 R 행렬에서 널이 아닌 값의 위치 인덱스 추출해 실제 R 행렬과 예측 행렬의 RMSE 추출\n",
+ " x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]\n",
+ " y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]\n",
+ " R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]\n",
+ " full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]\n",
+ " mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)\n",
+ " rmse = np.sqrt(mse)\n",
+ "\n",
+ " return rmse"
+ ],
+ "metadata": {
+ "id": "olUfWfeTSRkB"
+ },
+ "execution_count": null,
+ "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",
+ " # P와 Q 매트릭스의 크기를 지정하고 정규 분포를 가진 랜덤한 값으로 입력\n",
+ " np.random.seed(1)\n",
+ " P = np.random.normal(scale=1./K, size=(num_users, K))\n",
+ " Q = np.random.normal(scale=1./K, size=(num_items, K))\n",
+ "\n",
+ " prev_rmse = 10000\n",
+ " break_count = 0\n",
+ "\n",
+ " # R > 0 인 행 위치, 열 위치, 값을 non_zeros 리스트 객체에 저장\n",
+ " non_zeros = [(i, j, R[i, j]) for i in range(num_users) for j in range(num_items) if R[i, j] > 0]\n",
+ "\n",
+ " # SGD 기법으로 P와 Q 매트릭스를 계속 업데이트\n",
+ " for step in range(steps):\n",
+ " for i, j, r in non_zeros:\n",
+ " # 실제 값과 예측 값의 차이인 오류 값 구함\n",
+ " eij = r - np.dot(P[i, :], Q[j, :].T)\n",
+ " # Regularization을 반영한 SGD 업데이트 공식 적용\n",
+ " P[i, :] = P[i, :] + learning_rate*(eij * Q[j, :] - r_lambda*P[i, :])\n",
+ " Q[j, :] = Q[j, :] + learning_rate*(eij * P[i, :] - r_lambda*Q[j, :])\n",
+ "\n",
+ " rmse = get_rmse(R, P, Q, non_zeros)\n",
+ " if (step % 10) == 0:\n",
+ " print(\"### iteration step : \", step, \"rmse : \", rmse)\n",
+ "\n",
+ " return P, Q"
+ ],
+ "metadata": {
+ "id": "GEJgAICnG1Fl"
+ },
+ "execution_count": null,
+ "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",
+ "# title 칼럼을 얻기 위해 moveis와 조인 수행\n",
+ "rating_movies = pd.merge(ratings, movies, on='movieId')\n",
+ "\n",
+ "# columns='title'로 title 칼럼으로 pivot 수행\n",
+ "ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')"
+ ],
+ "metadata": {
+ "id": "PvoTA5cjSVD2"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "P, Q = matrix_factorization(ratings_matrix.values, K=50, steps=200, learning_rate=0.01,\n",
+ " r_lambda=0.01)\n",
+ "pred_matrix = np.dot(P, Q.T)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "BW8JsPrKSuT6",
+ "outputId": "6e98e37b-1cd7-457a-e947-cd88b39c8b9e"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "### iteration step : 0 rmse : 2.9023619751336867\n",
+ "### iteration step : 10 rmse : 0.7335768591017927\n",
+ "### iteration step : 20 rmse : 0.5115539026853442\n",
+ "### iteration step : 30 rmse : 0.37261628282537446\n",
+ "### iteration step : 40 rmse : 0.2960818299181014\n",
+ "### iteration step : 50 rmse : 0.2520353192341642\n",
+ "### iteration step : 60 rmse : 0.22487503275269854\n",
+ "### iteration step : 70 rmse : 0.2068545530233154\n",
+ "### iteration step : 80 rmse : 0.19413418783028685\n",
+ "### iteration step : 90 rmse : 0.18470082002720406\n",
+ "### iteration step : 100 rmse : 0.17742927527209104\n",
+ "### iteration step : 110 rmse : 0.1716522696470749\n",
+ "### iteration step : 120 rmse : 0.16695181946871726\n",
+ "### iteration step : 130 rmse : 0.16305292191997542\n",
+ "### iteration step : 140 rmse : 0.15976691929679646\n",
+ "### iteration step : 150 rmse : 0.1569598699945732\n",
+ "### iteration step : 160 rmse : 0.15453398186715425\n",
+ "### iteration step : 170 rmse : 0.15241618551077643\n",
+ "### iteration step : 180 rmse : 0.1505508073962831\n",
+ "### iteration step : 190 rmse : 0.1488947091323209\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings_pred_matrix = pd.DataFrame(data=pred_matrix, 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": "57iTHcvGS7p0",
+ "outputId": "aaf0c9c1-f4d5-469f-8917-f5ea56a01472"
+ },
+ "execution_count": null,
+ "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": 42
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "잠재 요인 협업 필터링으로 개인화된 영화 추천"
+ ],
+ "metadata": {
+ "id": "PsxWWfrtTKW-"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# 사용자가 관람하지 않은 영화명 추출\n",
+ "unseen_list = get_unseen_movies(ratings_matrix, 9)\n",
+ "\n",
+ "# 잠재 요인 협업 필터링으로 영화 추천\n",
+ "recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)\n",
+ "\n",
+ "# 평점 데이터를 DataFrame으로 생성\n",
+ "recomm_movies = pd.DataFrame(data=recomm_movies.values, index=recomm_movies.index, columns=['pred_score'])\n",
+ "recomm_movies"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 394
+ },
+ "id": "wOdFR5ZgTJtA",
+ "outputId": "d4b47e20-3355-459a-8331-468e41855627"
+ },
+ "execution_count": null,
+ "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": 43
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb" "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb"
new file mode 100644
index 0000000..944f534
--- /dev/null
+++ "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.ipynb"
@@ -0,0 +1,1155 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Chapter 9. 추천 시스템"
+ ],
+ "metadata": {
+ "id": "c9iIx4Pl3-kJ"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 04. 잠재 요인 협업 필터링"
+ ],
+ "metadata": {
+ "id": "aTSEyfoy4JDp"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### 확률적 경사 하강법을 이용한 행렬 분해"
+ ],
+ "metadata": {
+ "id": "UeNQmVXU4Td4"
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "id": "Ot_FAfJo33Kc"
+ },
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "# 원본 행렬 R 생성, 분해 행렬 P와 Q 초기화, 잠재 요인 차원 K는 3으로 결정\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",
+ "# P와 Q 행렬의 크기를 지정하고 정규 분포를 가진 임의의 값으로 입력\n",
+ "np.random.seed(1)\n",
+ "P = np.random.normal(scale=1./K, size=(num_users, K))\n",
+ "Q = np.random.normal(scale=1./K, size=(num_items, K))"
+ ]
+ },
+ {
+ "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",
+ " # 두 개의 분해된 행렬 P와 Q.T의 내적으로 예측 R 행렬 생성\n",
+ " full_pred_matrix = np.dot(P, Q.T)\n",
+ "\n",
+ " # 실제 R 행렬에서 널이 아닌 값의 위치 인덱스 추출해 실제 R 행렬과 예측 행렬의 RMSE 추출\n",
+ " x_non_zero_ind = [non_zero[0] for non_zero in non_zeros]\n",
+ " y_non_zero_ind = [non_zero[1] for non_zero in non_zeros]\n",
+ " R_non_zeros = R[x_non_zero_ind, y_non_zero_ind]\n",
+ " full_pred_matrix_non_zeros = full_pred_matrix[x_non_zero_ind, y_non_zero_ind]\n",
+ " mse = mean_squared_error(R_non_zeros, full_pred_matrix_non_zeros)\n",
+ " rmse = np.sqrt(mse)\n",
+ "\n",
+ " return rmse"
+ ],
+ "metadata": {
+ "id": "Zjb63dcX493C"
+ },
+ "execution_count": 4,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# R > 0인 행 위치, 열 위치, 값을 non_zeros 리스트에 저장\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\n",
+ "learning_rate = 0.01\n",
+ "r_lambda = 0.01\n",
+ "\n",
+ "# SGD 기법으로 P와 Q 매트릭스 계속 업데이트\n",
+ "for step in range(steps):\n",
+ " for i, j, r in non_zeros:\n",
+ " # 실제 값과 예측 값의 차이인 오류 값 구함\n",
+ " eij = r - np.dot(P[i, :], Q[j, :].T)\n",
+ " # Regularization을 반영한 SGD 업데이트 공식 적용\n",
+ " P[i, :] = P[i, :] + learning_rate*(eij * Q[j, :] - r_lambda*P[i, :])\n",
+ " Q[j, :] = Q[j, :] + learning_rate*(eij * P[i, :] - r_lambda*Q[j, :])\n",
+ "\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": "YL95MRpw5law",
+ "outputId": "beb72939-d51f-44ae-ac91-656b90652c98"
+ },
+ "execution_count": 5,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "### iteration step : 0 rmse : 3.261355059488935\n",
+ "### iteration step : 0 rmse : 3.26040057174686\n",
+ "### iteration step : 0 rmse : 3.253984404542389\n",
+ "### iteration step : 0 rmse : 3.2521583839863624\n",
+ "### iteration step : 0 rmse : 3.252335303789125\n",
+ "### iteration step : 0 rmse : 3.251072196430487\n",
+ "### iteration step : 0 rmse : 3.2492449982564864\n",
+ "### iteration step : 0 rmse : 3.247416477570409\n",
+ "### iteration step : 0 rmse : 3.241926055455223\n",
+ "### iteration step : 0 rmse : 3.2400454107613084\n",
+ "### iteration step : 0 rmse : 3.240166740749792\n",
+ "### iteration step : 0 rmse : 3.2388050277987723\n",
+ "### iteration step : 50 rmse : 0.5003190892212748\n",
+ "### iteration step : 50 rmse : 0.5001616291326989\n",
+ "### iteration step : 50 rmse : 0.49899601202578087\n",
+ "### iteration step : 50 rmse : 0.4988483450145831\n",
+ "### iteration step : 50 rmse : 0.49895189256631756\n",
+ "### iteration step : 50 rmse : 0.49833236830090993\n",
+ "### iteration step : 50 rmse : 0.4984148489378701\n",
+ "### iteration step : 50 rmse : 0.49792599580240876\n",
+ "### iteration step : 50 rmse : 0.4900605568692785\n",
+ "### iteration step : 50 rmse : 0.4890370238665435\n",
+ "### iteration step : 50 rmse : 0.48869176023997846\n",
+ "### iteration step : 50 rmse : 0.4876723101369648\n",
+ "### iteration step : 100 rmse : 0.15911521988578564\n",
+ "### iteration step : 100 rmse : 0.1588091617801093\n",
+ "### iteration step : 100 rmse : 0.1587409221708901\n",
+ "### iteration step : 100 rmse : 0.1582856952842508\n",
+ "### iteration step : 100 rmse : 0.1583080948216876\n",
+ "### iteration step : 100 rmse : 0.15828832993767403\n",
+ "### iteration step : 100 rmse : 0.15787486893092847\n",
+ "### iteration step : 100 rmse : 0.15792073606567072\n",
+ "### iteration step : 100 rmse : 0.15725245215457084\n",
+ "### iteration step : 100 rmse : 0.15710664164665206\n",
+ "### iteration step : 100 rmse : 0.15690252144190003\n",
+ "### iteration step : 100 rmse : 0.1564340384819247\n",
+ "### iteration step : 150 rmse : 0.07546004875264435\n",
+ "### iteration step : 150 rmse : 0.07544589133447106\n",
+ "### iteration step : 150 rmse : 0.07543234329653023\n",
+ "### iteration step : 150 rmse : 0.07514800672233914\n",
+ "### iteration step : 150 rmse : 0.07518867696418177\n",
+ "### iteration step : 150 rmse : 0.0752288950993841\n",
+ "### iteration step : 150 rmse : 0.07489318864469259\n",
+ "### iteration step : 150 rmse : 0.07493400425933257\n",
+ "### iteration step : 150 rmse : 0.07462695506527872\n",
+ "### iteration step : 150 rmse : 0.07464332131959663\n",
+ "### iteration step : 150 rmse : 0.0746444164156341\n",
+ "### iteration step : 150 rmse : 0.07455141311978046\n",
+ "### iteration step : 200 rmse : 0.04361016579439073\n",
+ "### iteration step : 200 rmse : 0.04370913068953006\n",
+ "### iteration step : 200 rmse : 0.04369072102767977\n",
+ "### iteration step : 200 rmse : 0.043475549832271414\n",
+ "### iteration step : 200 rmse : 0.0435313092537358\n",
+ "### iteration step : 200 rmse : 0.04359240037575283\n",
+ "### iteration step : 200 rmse : 0.04329647906053838\n",
+ "### iteration step : 200 rmse : 0.04332057192123618\n",
+ "### iteration step : 200 rmse : 0.04310448294502512\n",
+ "### iteration step : 200 rmse : 0.04313550286658552\n",
+ "### iteration step : 200 rmse : 0.04313786864806258\n",
+ "### iteration step : 200 rmse : 0.04325226798579314\n",
+ "### iteration step : 250 rmse : 0.029395183185609734\n",
+ "### iteration step : 250 rmse : 0.02954402948437167\n",
+ "### iteration step : 250 rmse : 0.02950187436758184\n",
+ "### iteration step : 250 rmse : 0.029329609713572593\n",
+ "### iteration step : 250 rmse : 0.02940211807327667\n",
+ "### iteration step : 250 rmse : 0.02946720568417511\n",
+ "### iteration step : 250 rmse : 0.029189294191791375\n",
+ "### iteration step : 250 rmse : 0.029198757426747605\n",
+ "### iteration step : 250 rmse : 0.028995742260002243\n",
+ "### iteration step : 250 rmse : 0.02904415445054541\n",
+ "### iteration step : 250 rmse : 0.029049587101179365\n",
+ "### iteration step : 250 rmse : 0.029248328780878973\n",
+ "### iteration step : 300 rmse : 0.022678715233749362\n",
+ "### iteration step : 300 rmse : 0.022844873864300484\n",
+ "### iteration step : 300 rmse : 0.022773566650325074\n",
+ "### iteration step : 300 rmse : 0.02263234507322516\n",
+ "### iteration step : 300 rmse : 0.02272006255153119\n",
+ "### iteration step : 300 rmse : 0.022778917442558434\n",
+ "### iteration step : 300 rmse : 0.022516243062381223\n",
+ "### iteration step : 300 rmse : 0.022515508246519694\n",
+ "### iteration step : 300 rmse : 0.02229491665298542\n",
+ "### iteration step : 300 rmse : 0.022367287171783136\n",
+ "### iteration step : 300 rmse : 0.022392303480653113\n",
+ "### iteration step : 300 rmse : 0.022621116143829466\n",
+ "### iteration step : 350 rmse : 0.019516973680183715\n",
+ "### iteration step : 350 rmse : 0.019681605297160464\n",
+ "### iteration step : 350 rmse : 0.019585635379668415\n",
+ "### iteration step : 350 rmse : 0.01946716545524988\n",
+ "### iteration step : 350 rmse : 0.01956568678979253\n",
+ "### iteration step : 350 rmse : 0.019614020075870497\n",
+ "### iteration step : 350 rmse : 0.019368393329296258\n",
+ "### iteration step : 350 rmse : 0.019361014872334943\n",
+ "### iteration step : 350 rmse : 0.019116038405167533\n",
+ "### iteration step : 350 rmse : 0.01920981547997513\n",
+ "### iteration step : 350 rmse : 0.019255623979392192\n",
+ "### iteration step : 350 rmse : 0.019493636196525135\n",
+ "### iteration step : 400 rmse : 0.01803666559195465\n",
+ "### iteration step : 400 rmse : 0.01819133106334419\n",
+ "### iteration step : 400 rmse : 0.018078504374883574\n",
+ "### iteration step : 400 rmse : 0.01797554592952707\n",
+ "### iteration step : 400 rmse : 0.018080509676855847\n",
+ "### iteration step : 400 rmse : 0.018118882879536648\n",
+ "### iteration step : 400 rmse : 0.017889686482489363\n",
+ "### iteration step : 400 rmse : 0.017878066671070433\n",
+ "### iteration step : 400 rmse : 0.01761224433968553\n",
+ "### iteration step : 400 rmse : 0.01772096734904666\n",
+ "### iteration step : 400 rmse : 0.01778179645659777\n",
+ "### iteration step : 400 rmse : 0.018022719092132704\n",
+ "### iteration step : 450 rmse : 0.017334045429542092\n",
+ "### iteration step : 450 rmse : 0.01747683493759156\n",
+ "### iteration step : 450 rmse : 0.01735361907510825\n",
+ "### iteration step : 450 rmse : 0.017260553985290646\n",
+ "### iteration step : 450 rmse : 0.01736909385010645\n",
+ "### iteration step : 450 rmse : 0.017399933857257726\n",
+ "### iteration step : 450 rmse : 0.01718431757863743\n",
+ "### iteration step : 450 rmse : 0.01716990649625117\n",
+ "### iteration step : 450 rmse : 0.01688861579579296\n",
+ "### iteration step : 450 rmse : 0.017006638154083088\n",
+ "### iteration step : 450 rmse : 0.01707679250866153\n",
+ "### iteration step : 450 rmse : 0.01731968595344266\n",
+ "### iteration step : 500 rmse : 0.016991609248052833\n",
+ "### iteration step : 500 rmse : 0.01712340891578616\n",
+ "### iteration step : 500 rmse : 0.01699398405641037\n",
+ "### iteration step : 500 rmse : 0.01690707049203008\n",
+ "### iteration step : 500 rmse : 0.01701760577221745\n",
+ "### iteration step : 500 rmse : 0.017043277556700362\n",
+ "### iteration step : 500 rmse : 0.01683803145900356\n",
+ "### iteration step : 500 rmse : 0.016821674312725313\n",
+ "### iteration step : 500 rmse : 0.016529281264429145\n",
+ "### iteration step : 500 rmse : 0.0166528887951985\n",
+ "### iteration step : 500 rmse : 0.016728541275490984\n",
+ "### iteration step : 500 rmse : 0.016973657887570753\n",
+ "### iteration step : 550 rmse : 0.016818969716266233\n",
+ "### iteration step : 550 rmse : 0.016941445597444732\n",
+ "### iteration step : 550 rmse : 0.0168082592988841\n",
+ "### iteration step : 550 rmse : 0.016725234339747562\n",
+ "### iteration step : 550 rmse : 0.01683693849143515\n",
+ "### iteration step : 550 rmse : 0.016859187050621206\n",
+ "### iteration step : 550 rmse : 0.016661644526141564\n",
+ "### iteration step : 550 rmse : 0.01664385102006508\n",
+ "### iteration step : 550 rmse : 0.016343446075494233\n",
+ "### iteration step : 550 rmse : 0.01647044082182643\n",
+ "### iteration step : 550 rmse : 0.01654932331426952\n",
+ "### iteration step : 550 rmse : 0.016796804595895633\n",
+ "### iteration step : 600 rmse : 0.016727439717439115\n",
+ "### iteration step : 600 rmse : 0.016842259158977232\n",
+ "### iteration step : 600 rmse : 0.016706687924467476\n",
+ "### iteration step : 600 rmse : 0.016626255644609397\n",
+ "### iteration step : 600 rmse : 0.016738696939262717\n",
+ "### iteration step : 600 rmse : 0.016758682415985614\n",
+ "### iteration step : 600 rmse : 0.0165668572000528\n",
+ "### iteration step : 600 rmse : 0.016547954461110684\n",
+ "### iteration step : 600 rmse : 0.016241668760761063\n",
+ "### iteration step : 600 rmse : 0.016370800056137867\n",
+ "### iteration step : 600 rmse : 0.016451627209257007\n",
+ "### iteration step : 600 rmse : 0.01670132290188466\n",
+ "### iteration step : 650 rmse : 0.016674291334806343\n",
+ "### iteration step : 650 rmse : 0.016782895588885082\n",
+ "### iteration step : 650 rmse : 0.016645698091647773\n",
+ "### iteration step : 650 rmse : 0.01656714079916223\n",
+ "### iteration step : 650 rmse : 0.016680091021598568\n",
+ "### iteration step : 650 rmse : 0.016698554271430792\n",
+ "### iteration step : 650 rmse : 0.016511017732427972\n",
+ "### iteration step : 650 rmse : 0.016491228766905293\n",
+ "### iteration step : 650 rmse : 0.01618054419796173\n",
+ "### iteration step : 650 rmse : 0.01631111150707529\n",
+ "### iteration step : 650 rmse : 0.01639316772050061\n",
+ "### iteration step : 650 rmse : 0.01664473691247669\n",
+ "### iteration step : 700 rmse : 0.0166383624426085\n",
+ "### iteration step : 700 rmse : 0.016741936743323586\n",
+ "### iteration step : 700 rmse : 0.016603524189001625\n",
+ "### iteration step : 700 rmse : 0.016526454393300468\n",
+ "### iteration step : 700 rmse : 0.016639792083379498\n",
+ "### iteration step : 700 rmse : 0.016657201345297346\n",
+ "### iteration step : 700 rmse : 0.016472928381641428\n",
+ "### iteration step : 700 rmse : 0.01645241257047358\n",
+ "### iteration step : 700 rmse : 0.016138379086448083\n",
+ "### iteration step : 700 rmse : 0.016269993747904915\n",
+ "### iteration step : 700 rmse : 0.01635288508504558\n",
+ "### iteration step : 700 rmse : 0.016605910068210026\n",
+ "### iteration step : 750 rmse : 0.01660906046895522\n",
+ "### iteration step : 750 rmse : 0.016708562969098305\n",
+ "### iteration step : 750 rmse : 0.016569153528341783\n",
+ "### iteration step : 750 rmse : 0.016493367054249922\n",
+ "### iteration step : 750 rmse : 0.016607027966870924\n",
+ "### iteration step : 750 rmse : 0.01662368102752549\n",
+ "### iteration step : 750 rmse : 0.016441927271724666\n",
+ "### iteration step : 750 rmse : 0.0164208024653437\n",
+ "### iteration step : 750 rmse : 0.016104179990850755\n",
+ "### iteration step : 750 rmse : 0.016236628551952913\n",
+ "### iteration step : 750 rmse : 0.016320141009292095\n",
+ "### iteration step : 750 rmse : 0.016574200475705\n",
+ "### iteration step : 800 rmse : 0.016581161561119846\n",
+ "### iteration step : 800 rmse : 0.016677363428436936\n",
+ "### iteration step : 800 rmse : 0.016537069269613652\n",
+ "### iteration step : 800 rmse : 0.0164624613777787\n",
+ "### iteration step : 800 rmse : 0.016576412350487568\n",
+ "### iteration step : 800 rmse : 0.01659250180024954\n",
+ "### iteration step : 800 rmse : 0.01641271740942833\n",
+ "### iteration step : 800 rmse : 0.016391072859801518\n",
+ "### iteration step : 800 rmse : 0.01607242307736876\n",
+ "### iteration step : 800 rmse : 0.016205589842521878\n",
+ "### iteration step : 800 rmse : 0.016289609430091494\n",
+ "### iteration step : 800 rmse : 0.01654431582921597\n",
+ "### iteration step : 850 rmse : 0.01655222898431553\n",
+ "### iteration step : 850 rmse : 0.01664575121547569\n",
+ "### iteration step : 850 rmse : 0.016504627328190514\n",
+ "### iteration step : 850 rmse : 0.016431145801748863\n",
+ "### iteration step : 850 rmse : 0.016545370571042432\n",
+ "### iteration step : 850 rmse : 0.016561024020105147\n",
+ "### iteration step : 850 rmse : 0.016382795627019747\n",
+ "### iteration step : 850 rmse : 0.016360700076085824\n",
+ "### iteration step : 850 rmse : 0.016040446344395578\n",
+ "### iteration step : 850 rmse : 0.016174269580681345\n",
+ "### iteration step : 850 rmse : 0.016258737354641353\n",
+ "### iteration step : 850 rmse : 0.01651375177473524\n",
+ "### iteration step : 900 rmse : 0.016521280433777957\n",
+ "### iteration step : 900 rmse : 0.016612624200841405\n",
+ "### iteration step : 900 rmse : 0.016470695682261876\n",
+ "### iteration step : 900 rmse : 0.016398314989165292\n",
+ "### iteration step : 900 rmse : 0.016512806333073466\n",
+ "### iteration step : 900 rmse : 0.01652811087350182\n",
+ "### iteration step : 900 rmse : 0.016351122754892394\n",
+ "### iteration step : 900 rmse : 0.016328629783842166\n",
+ "### iteration step : 900 rmse : 0.016007096878603234\n",
+ "### iteration step : 900 rmse : 0.016141544071514122\n",
+ "### iteration step : 900 rmse : 0.016226430843994055\n",
+ "### iteration step : 900 rmse : 0.01648146573819501\n",
+ "### iteration step : 950 rmse : 0.016488081335748316\n",
+ "### iteration step : 950 rmse : 0.016577652134974717\n",
+ "### iteration step : 950 rmse : 0.01643492933498176\n",
+ "### iteration step : 950 rmse : 0.0163636366204062\n",
+ "### iteration step : 950 rmse : 0.01647839195954869\n",
+ "### iteration step : 950 rmse : 0.01649340903060659\n",
+ "### iteration step : 950 rmse : 0.016317416842511007\n",
+ "### iteration step : 950 rmse : 0.016294568571753248\n",
+ "### iteration step : 950 rmse : 0.015972009545965248\n",
+ "### iteration step : 950 rmse : 0.0161070634587959\n",
+ "### iteration step : 950 rmse : 0.016192355609214733\n",
+ "### iteration step : 950 rmse : 0.016447171683479155\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": "akqJmyk96eFu",
+ "outputId": "b7b10fa4-7629-47f3-91f0-8d1025b1a9ff"
+ },
+ "execution_count": 6,
+ "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": [
+ "## 08. 파이썬 추천 시스템 패키지 - Surprise"
+ ],
+ "metadata": {
+ "id": "7q4HsMK26mju"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Surprise 패키지 소개"
+ ],
+ "metadata": {
+ "id": "pO3iI_vJ6qDE"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "pip install scikit-surprise"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "vRz5zocD6hyT",
+ "outputId": "5c8b9f50-9b52-46c0-a11a-cc2ef3fdf373"
+ },
+ "execution_count": 7,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Collecting scikit-surprise\n",
+ " Downloading scikit_surprise-1.1.4.tar.gz (154 kB)\n",
+ "\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/154.4 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m154.4/154.4 kB\u001b[0m \u001b[31m5.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+ "\u001b[?25h Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
+ " Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
+ " Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+ "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise) (1.4.2)\n",
+ "Requirement already satisfied: numpy>=1.19.5 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise) (1.26.4)\n",
+ "Requirement already satisfied: scipy>=1.6.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise) (1.13.1)\n",
+ "Building wheels for collected packages: scikit-surprise\n",
+ " Building wheel for scikit-surprise (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+ " Created wheel for scikit-surprise: filename=scikit_surprise-1.1.4-cp310-cp310-linux_x86_64.whl size=2357293 sha256=ce25d7540adccadb35affe9c1c47b77d3b31999468f4bfd36d13a413042da125\n",
+ " Stored in directory: /root/.cache/pip/wheels/4b/3f/df/6acbf0a40397d9bf3ff97f582cc22fb9ce66adde75bc71fd54\n",
+ "Successfully built scikit-surprise\n",
+ "Installing collected packages: scikit-surprise\n",
+ "Successfully installed scikit-surprise-1.1.4\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Surprise를 이용한 추천 시스템 구축"
+ ],
+ "metadata": {
+ "id": "h8FEfOK16xM9"
+ }
+ },
+ {
+ "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": "cPYE0Jxz6u7s"
+ },
+ "execution_count": 8,
+ "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": "P-MTyHHk7A9w",
+ "outputId": "32018132-60a7-4d57-83de-829a2f8b87b9"
+ },
+ "execution_count": 9,
+ "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": "code",
+ "source": [
+ "algo = SVD()\n",
+ "algo.fit(trainset)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "NHPDcyh27UgZ",
+ "outputId": "bb843f91-70e8-4e91-f468-bb5fc26270f0"
+ },
+ "execution_count": 10,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 10
+ }
+ ]
+ },
+ {
+ "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": "e1dIMLMU7YGT",
+ "outputId": "1c3b233a-e036-436c-bc11-d30639cdaa0b"
+ },
+ "execution_count": 11,
+ "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.5420490217434244, details={'was_impossible': False}),\n",
+ " Prediction(uid='882', iid='291', r_ui=4.0, est=4.064088825693579, details={'was_impossible': False}),\n",
+ " Prediction(uid='535', iid='507', r_ui=5.0, est=3.906510091281124, details={'was_impossible': False}),\n",
+ " Prediction(uid='697', iid='244', r_ui=5.0, est=3.567075899332699, details={'was_impossible': False}),\n",
+ " Prediction(uid='751', iid='385', r_ui=4.0, est=3.257356138581865, details={'was_impossible': False})]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 11
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "[(pred.uid, pred.iid, pred.est) for pred in predictions[:3]]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "flsX6hIy7ed8",
+ "outputId": "8c7940ce-8eaa-4fd2-f048-1a975bb2ffe5"
+ },
+ "execution_count": 13,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "[('120', '282', 3.5420490217434244),\n",
+ " ('882', '291', 4.064088825693579),\n",
+ " ('535', '507', 3.906510091281124)]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 13
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# 사용자 아이디, 아이템 아이디는 문자열로 입력해야 함\n",
+ "uid = str(196)\n",
+ "iid = str(302)\n",
+ "pred = algo.predict(uid, iid)\n",
+ "print(pred)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Lj0Y2C167m15",
+ "outputId": "a7c2c095-5d21-46db-a19b-56bd4dc62217"
+ },
+ "execution_count": 14,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "user: 196 item: 302 r_ui = None est = 3.99 {'was_impossible': False}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "accuracy.rmse(predictions)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "g9TmvX2c7w1-",
+ "outputId": "057c6530-206a-4431-d1f6-c0a4df49bc3b"
+ },
+ "execution_count": 15,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.9509\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.9508991861025147"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 15
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### Surprise 주요 모듈 소개"
+ ],
+ "metadata": {
+ "id": "qRO_Axqm74eL"
+ }
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**OS 파일 데이터를 Surprise 데이터 세트로 로딩**"
+ ],
+ "metadata": {
+ "id": "icEAiYOC78iM"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "ratings = pd.read_csv(\"/content/ratings.csv\")\n",
+ "# ratings_noh.csv 파일로 언로드 시 인덱스와 헤더를 모두 제거한 새로운 파일 생성\n",
+ "ratings.to_csv(\"/content/ratings_noh.csv\", index=False, header=False)"
+ ],
+ "metadata": {
+ "id": "xcqkqRwT70U3"
+ },
+ "execution_count": 17,
+ "outputs": []
+ },
+ {
+ "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": "5i124qZUBMmH"
+ },
+ "execution_count": 20,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "trainset, testset = train_test_split(data, test_size=.25, random_state=0)\n",
+ "\n",
+ "# 수행 시마다 동일한 결과를 도출하기 위해 random_state 설정\n",
+ "algo = SVD(n_factors=50, random_state=0)\n",
+ "\n",
+ "# 학습 데이터 세트로 학습하고 나서 테스트 데이터 세트로 평점 예측 후 RMSE 평가\n",
+ "algo.fit(trainset)\n",
+ "predictions = algo.test(testset)\n",
+ "accuracy.rmse(predictions)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "DBKT0RhUBbDA",
+ "outputId": "8fa6d9cc-82c7-4673-d4b9-59e320678831"
+ },
+ "execution_count": 22,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 22
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "**판다스 DataFrame에서 Surprise 데이터 세트로 로딩**"
+ ],
+ "metadata": {
+ "id": "NLICD93RCsWJ"
+ }
+ },
+ {
+ "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",
+ "# ratings DataFrame에서 컬럼은 사용자 아이디, 아이템 아이디, 평점 순서를 지켜야 함\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": "XOglAbsdCoQf",
+ "outputId": "d4fb8081-8011-406f-b404-e61288c24b29"
+ },
+ "execution_count": 23,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "RMSE: 0.8682\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "0.8681952927143516"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 23
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 교차 검증과 하이퍼 파라미터 튜닝"
+ ],
+ "metadata": {
+ "id": "FrO9ghSqDCuO"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise.model_selection import cross_validate\n",
+ "\n",
+ "# 판다스 DataFrame에서 Surprise 데이터 세트로 데이터 로딩\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": "QSa48celC_HC",
+ "outputId": "59542bb2-3507-4203-9dd2-5a718f17d7bc"
+ },
+ "execution_count": 24,
+ "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.8707 0.8845 0.8686 0.8645 0.8736 0.8724 0.0067 \n",
+ "MAE (testset) 0.6698 0.6809 0.6662 0.6643 0.6727 0.6708 0.0058 \n",
+ "Fit time 2.67 1.80 3.39 1.79 1.81 2.29 0.64 \n",
+ "Test time 0.14 1.49 0.13 0.33 0.26 0.47 0.52 \n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "{'test_rmse': array([0.87067389, 0.88447785, 0.8686039 , 0.86453146, 0.87360213]),\n",
+ " 'test_mae': array([0.66975423, 0.68087162, 0.66615616, 0.66429301, 0.67269149]),\n",
+ " 'fit_time': (2.6746022701263428,\n",
+ " 1.801090955734253,\n",
+ " 3.3930437564849854,\n",
+ " 1.790968656539917,\n",
+ " 1.8149125576019287),\n",
+ " 'test_time': (0.13999199867248535,\n",
+ " 1.4949455261230469,\n",
+ " 0.12560439109802246,\n",
+ " 0.3288872241973877,\n",
+ " 0.261688232421875)}"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 24
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from surprise.model_selection import GridSearchCV\n",
+ "\n",
+ "# 최적화할 파라미터를 딕셔너리 형태로 지정\n",
+ "param_grid = {'n_epochs': [20, 40, 60], 'n_factors':[50, 100, 200]}\n",
+ "\n",
+ "# CV를 3개 폴드 세트로 지정, 성능 평가는 rmse, mse로 수행하도록 GridSearchCV 구성\n",
+ "gs = GridSearchCV(SVD, param_grid, measures=['rmse', 'mae'], cv=3)\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# 최고 RMSE Evaluation 점수와 그때의 하이퍼 파라미터\n",
+ "print(gs.best_score['rmse'])\n",
+ "print(gs.best_params['rmse'])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "QDbBdbA3DVd7",
+ "outputId": "d76d7298-27ba-4bda-9bad-0c8dc7a3685d"
+ },
+ "execution_count": 25,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "0.8766257301024302\n",
+ "{'n_epochs': 20, 'n_factors': 50}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## Surprise를 이용한 개인화 영화 추천 시스템 구축"
+ ],
+ "metadata": {
+ "id": "2PtAUsEHD7CH"
+ }
+ },
+ {
+ "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": 279
+ },
+ "id": "5krW-WAlD5Y6",
+ "outputId": "64ea0732-bcec-4fa8-bdb2-53e0c7db2f62"
+ },
+ "execution_count": 26,
+ "outputs": [
+ {
+ "output_type": "error",
+ "ename": "AttributeError",
+ "evalue": "'DatasetAutoFolds' object has no attribute 'n_users'",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \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[\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[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[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[0;32m----> 4\u001b[0;31m \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[0m",
+ "\u001b[0;32m/usr/local/lib/python3.10/dist-packages/surprise/prediction_algorithms/matrix_factorization.pyx\u001b[0m in \u001b[0;36msurprise.prediction_algorithms.matrix_factorization.SVD.fit\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;32m/usr/local/lib/python3.10/dist-packages/surprise/prediction_algorithms/matrix_factorization.pyx\u001b[0m in \u001b[0;36msurprise.prediction_algorithms.matrix_factorization.SVD.sgd\u001b[0;34m()\u001b[0m\n",
+ "\u001b[0;31mAttributeError\u001b[0m: 'DatasetAutoFolds' object has no attribute 'n_users'"
+ ]
+ }
+ ]
+ },
+ {
+ "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",
+ "# 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": "Cs_JF7TGEQ8-"
+ },
+ "execution_count": 27,
+ "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": "uWVYI3AVEkPV",
+ "outputId": "cc206d1d-4cf8-4750-a94c-3843f702ab93"
+ },
+ "execution_count": 28,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 28
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# 영화에 대한 상세 속성 정보 DataFrame 로딩\n",
+ "movies = pd.read_csv('/content/movies.csv')\n",
+ "\n",
+ "# userId=9의 movieId 데이터를 추출해 movieId=42 데이터가 있는지 확인\n",
+ "movieIds = ratings[ratings['userId']==9]['movieId']\n",
+ "if movieIds[movieIds==42].count() == 0:\n",
+ " print('사용자 아이디 9는 영화 아이디 42의 평점 없음')\n",
+ "print(movies[movies['movieId']==42])"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "EYl8IWHGEniU",
+ "outputId": "935c7d4f-89cf-411d-a969-6a30fb81c621"
+ },
+ "execution_count": 29,
+ "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": "qXf3pkLjFA15",
+ "outputId": "fbfa1d9f-8192-4486-b3a1-902d3f7c6354"
+ },
+ "execution_count": 30,
+ "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",
+ " # 입력값으로 들어온 userId에 해당하는 사용자가 평점을 매긴 모든 영화를 리스트로 생성\n",
+ " seen_movies = ratings[ratings['userId'] == userId]['movieId'].tolist()\n",
+ "\n",
+ " # 모든 영화의 movieId를 리스트로 생성\n",
+ " total_movies = movies['movieId'].tolist()\n",
+ "\n",
+ " # 모든 영화의 movieId 중 이미 평점을 매긴 영화의 movieId를 제외한 후 리스트로 생성\n",
+ " unseen_movies = [movie for movie in total_movies if movie not in seen_movies]\n",
+ " print('평점 매긴 영화 수:', len(seen_movies), '추천 대상 영화 수:', len(unseen_movies), '전체 영화 수:', len(total_movies))\n",
+ "\n",
+ " return unseen_movies\n",
+ "\n",
+ "unseen_movies = get_unseen_surprise(ratings, movies, 9)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "9YJJBSczFEEi",
+ "outputId": "5ece7579-b8dd-4848-8695-cedaf1f51bc1"
+ },
+ "execution_count": 31,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9696 전체 영화 수: 9742\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "def recomm_movie_by_surprise(algo, userId, unseen_movies, top_n=10):\n",
+ " # 알고리즘 객체의 predict() 메서드를 평점이 없는 영화에 반복 수행한 후 결과를 list 객체로 저장\n",
+ " predictions = [algo.predict(str(userId), str(movieId)) for movieId in unseen_movies]\n",
+ "\n",
+ " # predictions list 객체는 surprise의 Predictions 객체를 원소로 가지고 있음\n",
+ " # [Prediction(uid='9', iid='1', est=3.69), Prediction(uid='9', iid='2', est=2.98),,,,]\n",
+ "\n",
+ " # 이를 est 값으로 정렬하기 위해서 아래의 sortkey_est 함수를 정의\n",
+ " # sortkey_est 함수는 list 객체의 sort() 함수의 키 값으로 사용되어 정렬 수행\n",
+ " def sortkey_est(pred):\n",
+ " return pred.est\n",
+ "\n",
+ " # sortkey_est() 반환값의 내림 차순으로 정렬 수행하고 top_n개의 최상위 값 추출\n",
+ " predictions.sort(key=sortkey_est, reverse=True)\n",
+ " top_predictions = predictions[:top_n]\n",
+ "\n",
+ " # top_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",
+ "\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": "m8EiNaujFW_i",
+ "outputId": "190dfaf2-dca1-4bae-d40a-e4b78d3c2590"
+ },
+ "execution_count": 32,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "평점 매긴 영화 수: 46 추천 대상 영화 수: 9696 전체 영화 수: 9742\n",
+ "##### Top-10 추천 영화 리스트 #####\n",
+ "Usual Suspects, The (1995) : 4.306302135700814\n",
+ "Star Wars: Episode IV - A New Hope (1977) : 4.281663842987387\n",
+ "Pulp Fiction (1994) : 4.278152632122759\n",
+ "Silence of the Lambs, The (1991) : 4.226073566460876\n",
+ "Godfather, The (1972) : 4.1918097904381995\n",
+ "Streetcar Named Desire, A (1951) : 4.154746591122657\n",
+ "Star Wars: Episode V - The Empire Strikes Back (1980) : 4.122016128534504\n",
+ "Star Wars: Episode VI - Return of the Jedi (1983) : 4.108009609093436\n",
+ "Goodfellas (1990) : 4.083464936588478\n",
+ "Glory (1989) : 4.07887165526957\n"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git "a/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.pdf" "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.pdf"
new file mode 100644
index 0000000..5a476b7
Binary files /dev/null and "b/Week16_\354\230\210\354\212\265\352\263\274\354\240\234_\352\271\200\353\202\250\354\232\260.pdf" differ
|