Skip to content

Instantly share code, notes, and snippets.

@pyy0715
Created July 27, 2020 17:20
Show Gist options
  • Save pyy0715/ce448cc673285a8ee3994af491e63c86 to your computer and use it in GitHub Desktop.
Save pyy0715/ce448cc673285a8ee3994af491e63c86 to your computer and use it in GitHub Desktop.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# What is Suprise?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Surprise](https://github.com/NicolasHug/Surprise)는 추천 시스템에서 Sklearn Style로 빠르고 쉽게 돌려볼 수 있는 Package입니다.\n",
"\n",
"`Surprise`는 아래의 목적을 기반으로 설계되었습니다.\n",
"\n",
"- **데이터셋 처리하는데 어려움이 없도록 하자. movie_lens와 같은 Built-in 데이터들과 custom 데이터들 모두 사용 가능합니다.**\n",
"\n",
"- **SVD, PMF, SVD++, NMF 등 다양한 예측 알고리즘을 제공합니다.**\n",
"\n",
"- **새로운 알고리즘 아이디어를 쉽게 구현할 수 있습니다.**\n",
"\n",
"- **알고리즘 성능을 평가, 분석 및 비교할 수있는 도구를 제공합니다.**\n",
"\n",
"- **교차검증을 쉽게 수행할 수 있습니다.**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Data Loading"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Surprise에서 제공하는 알고리즘은 전부 (User, Id, Rating) 형태의 data만을 처리합니다.**\n",
"\n",
"Surprise에서 데이터를 불러오는 방법은 아래의 3가지입니다.\n",
"\n",
"1. Built-In Data(eg. Movie_Lens)\n",
"2. Read the local file\n",
"3. Read data frames using Pandas."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:49.994706Z",
"start_time": "2020-07-27T16:56:49.859772Z"
}
},
"outputs": [],
"source": [
"import os \n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Built-In Data"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.233291Z",
"start_time": "2020-07-27T16:56:49.995688Z"
},
"scrolled": true
},
"outputs": [],
"source": [
"from surprise import Dataset\n",
"data = Dataset.load_builtin('ml-100k') \n",
"\n",
"#Accepted values are ‘ml-100k’, ‘ml-1m’, and ‘jester’. Default is ‘ml-100k’.\n",
"#https://surprise.readthedocs.io/en/stable/dataset.html"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Read the local file"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Surprise 모델을 이용하여 데이터를 처리해주려면 데이터를 고유의 객체 형태로 바꿔주어야 합니다.\n",
"\n",
"이러한 역할을 하는 함수가 바로 `Surprise.Dataset`와 `Surprise.Reader`입니다.\n",
"\n",
"위의 Built-In 방법의 경우, 따로 바꿔주지 않아도 모델의 Input으로 사용이 가능합니다."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.235614Z",
"start_time": "2020-07-27T16:56:50.234183Z"
}
},
"outputs": [],
"source": [
"from surprise import Reader\n",
"from surprise import Dataset"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.387438Z",
"start_time": "2020-07-27T16:56:50.236318Z"
}
},
"outputs": [],
"source": [
"# Package를 다운받으면, suprise_data안에 기본 데이터들이 내장되어 있게됩니다.\n",
"\n",
"file_path = os.path.expanduser('~/.surprise_data/ml-100k/ml-100k/u.data')\n",
"reader = Reader(line_format='user item rating timestamp', sep='\\t')\n",
"data = Dataset.load_from_file(file_path, reader=reader)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Reader에는 `line_format`, `sep`과 같은 인자로 읽을 열들과 구분자를 지정할 수 있습니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Read Data Frames Using Pandas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Reader를 이용해서 Implicit, Explicit형태에 맞춰 rating_scale도 지정해줄 수 있습니다.\n",
"\n",
"- DataFrame을 이용할때는 user,item,rating 순서를 맞춰주어야 합니다."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.400353Z",
"start_time": "2020-07-27T16:56:50.388310Z"
}
},
"outputs": [],
"source": [
"ratings_dict = {'itemID': [1, 2, 3, 4, 5],\n",
" 'userID': ['kim', 'park', 'lee', 'choi', 'jung'],\n",
" 'rating': [3, 2, 4, 1, 5]\n",
" }\n",
"\n",
"df = pd.DataFrame(ratings_dict)\n",
"\n",
"# A reader is still needed but only the rating_scale param is requiered.\n",
"reader = Reader(rating_scale=(1, 5))\n",
"\n",
"# The columns must correspond to user id, item id and ratings (in that order).\n",
"data = Dataset.load_from_df(df[['userID', 'itemID', 'rating']], reader)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Train-Test Split"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Sklearn에서 사용하듯이 똑같이 사용할 수 있습니다. split할 데이터와 비율을 정해줍니다. \n",
"\n",
"- data는 위의 2번 방법을 사용하여 불러왔습니다."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.541922Z",
"start_time": "2020-07-27T16:56:50.401723Z"
}
},
"outputs": [],
"source": [
"file_path = os.path.expanduser('~/.surprise_data/ml-100k/ml-100k/u.data')\n",
"reader = Reader(line_format='user item rating timestamp', sep='\\t')\n",
"data = Dataset.load_from_file(file_path, reader=reader)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.629314Z",
"start_time": "2020-07-27T16:56:50.542752Z"
}
},
"outputs": [],
"source": [
"from surprise.model_selection import train_test_split\n",
"trainset, testset = train_test_split(data, test_size=.2)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.632698Z",
"start_time": "2020-07-27T16:56:50.630567Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Train, num user is 943, num item is 1646\n"
]
}
],
"source": [
"print(f'In Train, num user is {trainset.n_users}, num item is {trainset.n_items}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Surprise는 built-in 되어있는 모델들을 제공하며, 모든 모델들은 `AlgoBase`클래스에 내장되어 있습니다.\n",
"\n",
"크게는 KNN과 MF로 나뉘어져 있습니다. 좀 더 자세히는 [공식 홈페이지](https://surprise.readthedocs.io/en/stable/prediction_algorithms_package.html)를 참고하시기 바랍니다.\n",
"\n",
"위 모델들은 풀고자하는 문제에 따라 `baseline estimates`, `similarity measure`를 사용합니다.\n",
"\n",
"학습방법은 Scikit-learn과 같이 모델의 객체를 먼저 만들고, Fit시킨 후, Predict하게 됩니다.\n",
"\n",
"여기서는 KNNBasic을 사용합니다."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.637259Z",
"start_time": "2020-07-27T16:56:50.634099Z"
}
},
"outputs": [],
"source": [
"from surprise import KNNBasic\n",
"from surprise import BaselineOnly\n",
"\n",
"algo = KNNBasic()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Baselines estimates configuration"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ALS"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`method`에서는 모델의 학습방법을 정하게 됩니다. \n",
"\n",
"SGD(Stochastic Gradient Descent)나, ALS(Alternating Least Squares)등 업데이트 방법을 명시합니다.\n",
"\n",
"method에 따라 필요한 option들이 달라지게 됩니다.\n",
"\n",
"모든 method에는 bias는 0으로 초기화 되어 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.642550Z",
"start_time": "2020-07-27T16:56:50.638398Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using ALS\n"
]
}
],
"source": [
"print('Using ALS')\n",
"bsl_options = {'method': 'als', # default\n",
" 'n_epochs': 5,\n",
" 'reg_u': 12, # user regularizer term\n",
" 'reg_i': 5 # item regularizer term\n",
" }\n",
"algo = BaselineOnly(bsl_options=bsl_options)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.825825Z",
"start_time": "2020-07-27T16:56:50.643770Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Estimating biases using als...\n",
"RMSE: 0.9518\n"
]
},
{
"data": {
"text/plain": [
"0.9518333181939708"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from surprise import accuracy\n",
"\n",
"# 학습 예측 평가\n",
"algo.fit(trainset)\n",
"predictions = algo.test(testset)\n",
"\n",
"# Then compute RMSE\n",
"accuracy.rmse(predictions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### SGD"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:50.829032Z",
"start_time": "2020-07-27T16:56:50.826649Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using SGD\n"
]
}
],
"source": [
"print('Using SGD')\n",
"bsl_options = {'method': 'sgd',\n",
" 'learning_rate': .00005,\n",
" }\n",
"algo = BaselineOnly(bsl_options=bsl_options)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:51.143228Z",
"start_time": "2020-07-27T16:56:50.829967Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Estimating biases using sgd...\n",
"RMSE: 1.0898\n"
]
},
{
"data": {
"text/plain": [
"1.0898434778093375"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 학습 예측 평가\n",
"algo.fit(trainset)\n",
"predictions = algo.test(testset)\n",
"\n",
"# Then compute RMSE\n",
"accuracy.rmse(predictions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Inferece "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"눈치가 빠르신 분들은 뭔가 이상한 점을 느끼셨을 수도 있습니다.\n",
"\n",
"위 예제에서 사용된 testset의 경우 trainset에서 split하였기 때문에 실제 정답이 포함되어 있습니다.\n",
"\n",
"하지만 일반적으로 testset은 정답을 모르는 상태여야 합니다.\n",
"\n",
"또한 algo.test() 는 (user, item, rating)이 포함되어 있는 data만 받습니다.\n",
"\n",
"그렇다면 진짜 예측해야 할 **testset(user, item)** 은 어떻게 구해야 할까요?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```{python}\n",
"uid = str(196) # raw user id (as in the ratings file). They are **strings**!\n",
"iid = str(302) # raw item id (as in the ratings file). They are **strings**!\n",
"\n",
"# get a prediction for specific users and items.\n",
"pred = algo.predict(uid, iid) #, r_ui=4, verbose=True)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"실제로 user, item 만을 가지고 rating을 inference하는 경우는 predict method를 사용해야 합니다.\n",
"\n",
"또한 예측할 user와 item은 문자형으로 처리를 해주어야 합니다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Similarity measure configuration"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"cosine 유사도를 이용하여 아이템간의 유사성을 계산할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:51.145953Z",
"start_time": "2020-07-27T16:56:51.144047Z"
}
},
"outputs": [],
"source": [
"sim_options = {'name': 'cosine',\n",
" 'user_based': False # compute similarities between items\n",
" }\n",
"algo = KNNBasic(sim_options=sim_options)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:52.444788Z",
"start_time": "2020-07-27T16:56:51.146919Z"
},
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Computing the cosine similarity matrix...\n",
"Done computing similarity matrix.\n"
]
},
{
"data": {
"text/plain": [
"<surprise.prediction_algorithms.knns.KNNBasic at 0x7f41bab53250>"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"algo.fit(trainset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"알고자 하는 영화와 그에 따른 유사한 영화를 구하기 위해서\n",
"\n",
"raw_id와 영화 제목에 맞춰서 사전을 구축합니다."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:52.455761Z",
"start_time": "2020-07-27T16:56:52.445509Z"
}
},
"outputs": [],
"source": [
"import io \n",
"\n",
"def read_item_names():\n",
" \"\"\"Read the u.item file from MovieLens 100-k dataset and return two\n",
" mappings to convert raw ids into movie names and movie names into raw ids.\n",
" \"\"\"\n",
"\n",
" file_name = get_dataset_dir() + '/ml-100k/ml-100k/u.item'\n",
" rid_to_name = {}\n",
" name_to_rid = {}\n",
" with io.open(file_name, 'r', encoding='ISO-8859-1') as f:\n",
" for line in f:\n",
" line = line.split('|')\n",
" rid_to_name[line[0]] = line[1]\n",
" name_to_rid[line[1]] = line[0]\n",
"\n",
" return rid_to_name, name_to_rid"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`Toy Story`와 유사한 영화 10개를 구합니다.\n",
"\n",
"neighbors을 raw_id로 변환 후, 사전에 구축된 rid_to_name을 통해 출력합니다."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:56:52.470308Z",
"start_time": "2020-07-27T16:56:52.456833Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The 10 nearest neighbors of Toy Story are:\n",
"Sprung (1997)\n",
"Mrs. Dalloway (1997)\n",
"Roseanna's Grave (For Roseanna) (1997)\n",
"In the Realm of the Senses (Ai no corrida) (1976)\n",
"Scarlet Letter, The (1995)\n",
"Caught (1996)\n",
"Run of the Country, The (1995)\n",
"Mina Tannenbaum (1994)\n",
"Schizopolis (1996)\n",
"Afterglow (1997)\n"
]
}
],
"source": [
"from surprise import get_dataset_dir\n",
"\n",
"\n",
"# Read the mappings raw id <-> movie name\n",
"rid_to_name, name_to_rid = read_item_names()\n",
"\n",
"# Retrieve inner id of the movie Toy Story\n",
"toy_story_raw_id = name_to_rid['Toy Story (1995)']\n",
"toy_story_inner_id = algo.trainset.to_inner_iid(toy_story_raw_id)\n",
"\n",
"# Retrieve inner ids of the nearest neighbors of Toy Story.\n",
"toy_story_neighbors = algo.get_neighbors(toy_story_inner_id, k=10)\n",
"\n",
"# Convert inner ids of the neighbors into names.\n",
"toy_story_neighbors = (algo.trainset.to_raw_iid(inner_id)\n",
" for inner_id in toy_story_neighbors)\n",
"toy_story_neighbors = (rid_to_name[rid]\n",
" for rid in toy_story_neighbors)\n",
"\n",
"print('The 10 nearest neighbors of Toy Story are:')\n",
"for movie in toy_story_neighbors:\n",
" print(movie)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Cross-Validation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"교차검증을 이용하여 모델의 성능을 높일 수도 있습니다.\n",
"\n",
"`LeaveOneOut`, `ShuffleSp`등의 다양한 split방법을 제공합니다.\n",
"\n",
"사용법은 Scikit-learn과 동일합니다."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:57:00.416018Z",
"start_time": "2020-07-27T16:56:52.471801Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fold 1: 0.9469909455983395\n",
"Fold 2: 0.9438094486662179\n",
"Fold 3: 0.945920896377587\n"
]
}
],
"source": [
"from surprise import SVD\n",
"from surprise.model_selection import KFold\n",
"from surprise.model_selection import cross_validate\n",
"\n",
"\n",
"# Load the movielens-100k dataset\n",
"data = Dataset.load_builtin('ml-100k')\n",
"\n",
"# define a cross-validation iterator\n",
"kf = KFold(n_splits=3)\n",
"\n",
"algo = SVD()\n",
"i=0\n",
"\n",
"for trainset, testset in kf.split(data):\n",
"\n",
" # train and test algorithm.\n",
" algo.fit(trainset)\n",
" predictions = algo.test(testset)\n",
" i+=1\n",
" # Compute and print Root Mean Squared Error\n",
" print(f'Fold {i}: {accuracy.rmse(predictions, verbose=False)}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Grid_Search"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"다양한 parameter 조건을 Grid Search 방식으로 탐색가능합니다."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-27T16:57:27.203928Z",
"start_time": "2020-07-27T16:57:00.417061Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.9643316337695954\n",
"{'n_epochs': 10, 'lr_all': 0.005, 'reg_all': 0.4}\n",
"CPU times: user 26.7 s, sys: 76 ms, total: 26.8 s\n",
"Wall time: 26.8 s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"from surprise import SVD\n",
"from surprise import Dataset\n",
"from surprise.model_selection import GridSearchCV\n",
"\n",
"# Use movielens-100K\n",
"data = Dataset.load_builtin('ml-100k')\n",
"\n",
"param_grid = {'n_epochs': [5, 10], 'lr_all': [0.002, 0.005],\n",
" 'reg_all': [0.4, 0.6]}\n",
"gs = GridSearchCV(SVD, param_grid, measures=['rmse', 'mae'], cv=3)\n",
"\n",
"gs.fit(data)\n",
"\n",
"# best RMSE score\n",
"print(gs.best_score['rmse'])\n",
"\n",
"# combination of parameters that gave the best RMSE score\n",
"print(gs.best_params['rmse'])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ML_server",
"language": "python",
"name": "ml_server"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment