#!/usr/bin/env python # coding: utf-8 # # **Surprise 를 이용한 추천 시스템 구축** # ## **1 SVD 행렬분해 모델링** # ### **01 surprise 내부 데이터를 활용** # - **ml-100k** 샘플 데이터를 활용하여 모델링을 실습합니다 # - **UserID, MovieID, ratting** 3개 필드만 사용하여 학습을 합니다 **(협업필터링)** # - 나머지 필드는 학습에서 제외됩니다 # ``` # The Reader class is used to parse a file containing ratings. # Such a file is assumed to specify only one rating per line, # and each line needs to respect the following structure: :: # # user ; item ; rating ; [timestamp] # ``` # In[1]: import surprise print(surprise.__version__) # In[2]: get_ipython().run_cell_magic('time', '', "# 샘플 데이터를 불러와서 SVD 샘플모델을 생성 합니다\nfrom surprise import SVD, Dataset, accuracy \nfrom surprise.model_selection import train_test_split\ndata = Dataset.load_builtin('ml-100k') \ntrainset, testset = train_test_split(data, test_size=.25, random_state=0) \n\nalgo = SVD()\nalgo.fit(trainset) \n") # In[3]: predictions = algo.test(testset) print('prediction type :{} size:{}'.format(type(predictions),len(predictions))) print('prediction 예측모델의 최초 5개 결과물 추출') predictions[:5] # In[4]: # UserID, MovieID, ratting # DataFrame 3개 필드만 사용하여 학습을 합니다 [ (pred.uid, pred.iid, pred.est) for pred in predictions[:3] ] # In[5]: # 사용자 아이디, 아이템 아이디는 를 입력하면 # 결과를 출력하는 모델을 활용 uid = str(196) iid = str(302) pred = algo.predict(uid, iid) print(pred) # In[6]: # Train/ Test 훈련모델의 검증결과 95% 성능을 출력합니다 accuracy.rmse(predictions) # ### **02 외부 CSV 데이터를 활용한 모델링** # - **ml-latest-small** 데이터셋을 활용하여 **SVD** 모델을 학습합니다 # - **numpy.array()** 데이터를 **Surprise 데이터 셋** 으로 변환 호출합니다 # In[7]: # 데이터셋 불러오기 # ratings_noh.csv : header를 제거 import pandas as pd ratings = pd.read_csv('./data/ml-latest-small/ratings.csv') ratings.head(3) # In[8]: ratings.to_csv('./data/ml-latest-small/ratings_noh.csv', index=False, header=False) ratings.values # In[9]: # Numpy Matrix 데이터셋을 Surprise 데이터셋으로 변환합니다 from surprise import Reader, Dataset reader = Reader(line_format = 'user item rating timestamp', # 필드별 레이블 sep = ',', # 구분기호 rating_scale = (0.5, 5)) # 데이터 min ~ max 범위 data = Dataset.load_from_file('./data/ml-latest-small/ratings_noh.csv', reader=reader) data # In[10]: # n_factors=50 : 학습 모델의 잠재요인 K를 50개로 설정 합니다 from surprise.model_selection import train_test_split trainset, testset = train_test_split(data, test_size=.25, random_state=0) algo = SVD(n_factors=50, random_state=0) algo.fit(trainset) # testset 으로 모델을 RMSE 평가 합니다 from surprise import accuracy predictions = algo.test(testset) accuracy.rmse(predictions) # ### **03 Pandas DataFrame을 활용한 모델링** # - **ml-latest-small** 데이터셋을 활용하여 **SVD** 모델을 학습합니다 # - **numpy.array()** 데이터를 **Surprise 데이터 셋** 으로 변환 호출합니다 # - 데이터 분석 필드는 **MovieLens** 데이터셋을 Default로 정의되어 있습니다 # In[11]: import pandas as pd from surprise import Reader, Dataset ratings = pd.read_csv('./data/ml-latest-small/ratings.csv') ratings.head(3) # In[12]: # ratings DataFrame 에서 컬럼은 사용자 아이디, 아이템 아이디, 평점 순서를 지켜야 합니다. reader = Reader(rating_scale=(0.5, 5.0)) data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader) algo = SVD(n_factors=50, random_state=0) trainset, testset = train_test_split(data, test_size=.25, random_state=0) algo.fit(trainset) predictions = algo.test( testset ) accuracy.rmse(predictions) # ## **2 교차 검증(Cross Validation)과 하이퍼 파라미터 튜닝** # ### **CSV 데이터셋을 활용** # **cv=5 :** cross-validation iterator 약어로, KFold 검증 구분 갯수를 입력합니다 # In[13]: get_ipython().run_cell_magic('time', '', "from surprise.model_selection import cross_validate \nratings = pd.read_csv('./data/ml-latest-small/ratings.csv')\nreader = Reader(rating_scale = (0.5, 5.0)) # Reader 인스턴스\ndata = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)\nalgo = SVD(random_state = 0) \ncross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True) \n") # In[14]: get_ipython().run_cell_magic('time', '', '# 최적화 검증용 파라미터, GridSearchCV (KFold CV 3개 분할)\nfrom surprise.model_selection import GridSearchCV\nparam_grid = {\'n_epochs\': [20, 40, 60], \'n_factors\': [50, 100, 200] }\ngs = GridSearchCV(SVD, param_grid, measures=[\'rmse\', \'mae\'], cv=3, n_jobs=-1)\ngs.fit(data)\n\n# 최고 RMSE Evaluation 점수와 그때의 하이퍼 파라미터\nprint("Bast Score: {}\\nParams: {}".format(\n gs.best_score[\'rmse\'], gs.best_params[\'rmse\']))\n') # ## **3 Surprise 를 이용한 개인화 영화 추천 시스템 구축** # - 기본소스는 `AttributeError: 'DatasetAutoFolds' object has no attribute 'global_mean'` 오류를 출력 # - 이는 데이터가 동일해서 발생한 문제로, **Train/ Test로 구분한 뒤** 입력해야 합니다 # [stackoverflow](https://stackoverflow.com/questions/49263964/datasetautofolds-object-has-no-attribute-global-mean-on-python-surprise) # ### **01 전체 데이터셋을 활용하여 학습 후 활용** # - **MovieLens 전체 데이터셋을** 학습한 뒤 활용합니다 # - 이번에는 **비어있는 평점 정보를 학습 모델로 채웁니다** # In[15]: # 아래 코드는 train_test_split( ) 구분 없어도 오류없이 출력합니다 # 최신버전 에서는 오류를 출력하지 않고 바로 결과를 출력 합니다 import pandas as pd from surprise import Reader, Dataset, SVD ratings = pd.read_csv('./data/ml-latest-small/ratings.csv') reader = Reader(line_format = 'user item rating timestamp', sep = ',', rating_scale = (0.5, 5)) data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader) trainset = data.build_full_trainset() algo = SVD(n_factors=50, random_state=0) algo.fit(trainset) # In[16]: # # 데이터를 모두 사용하여 학습을 진행합니다 # from surprise.dataset import DatasetAutoFolds # reader = Reader(line_format = 'user item rating timestamp', # sep = ',', # rating_scale = (0.5, 5)) # # DatasetAutoFolds 클래스로 파일을 불러옵니다 # data_folds = DatasetAutoFolds(ratings_file = './data/ml-latest-small/ratings_noh.csv', # reader = reader) # trainset = data_folds.build_full_trainset() # algo = SVD(n_epochs=20, n_factors=50, random_state=0) # algo.fit(trainset) # In[17]: # 영화에 대한 상세 속성 정보 DataFrame로딩 movies = pd.read_csv('./data/ml-latest-small/movies.csv') movies.head(3) # In[18]: # userId=9 의 movieId 데이터 추출하여 movieId=42 데이터가 있는지 확인. movieIds = ratings[ratings['userId']==9]['movieId'] if movieIds[movieIds==42].count() == 0: print('사용자 아이디 9는 영화 아이디 42의 평점 없음') # movies 데이터셋에서 42번 id 로 정보를 추출합니다 print(movies[movies['movieId']==42]) # In[19]: # 9번 사용자 42번 영화평점이 없을 때, 예측평점을 채웁니다 uid = str(9) iid = str(42) pred = algo.predict(uid, iid, verbose=True) # In[20]: # 영화정보를 보기 쉽도록 함수로 구현 def get_unseen_surprise(ratings, movies, userId): seen_movies = ratings[ratings['userId']== userId]['movieId'].tolist() # 입력 userId 평점목록 total_movies = movies['movieId'].tolist() # 모든 movieId 리스트 unseen_movies= [movie for movie in total_movies # 평점이 없는 movieId 목록 if movie not in seen_movies] print('평점 매긴 영화수: {}\n추천 대상 영화수: {:,}\n전체 영화수: {:,}'.format( len(seen_movies),len(unseen_movies),len(total_movies))) return unseen_movies # 9번 사용자의 추천목록을 검색 unseen_movies = get_unseen_surprise(ratings, movies, 9)