도서 추천시스템 - Surprise 활용 잠재요인 협업 필터링
Surprise 추천시스템 패키지 소개
Surprise는 파이썬 기반의 추천 시스템 구축을 위한 전용 패키지 중 하나이다.
Surprise패키지는 파이썬 기반에서 사이킷런과 유사한 API와 프레임워크를 제공하기 때문에 추천시스템에 대한 기본적인 이해와 사이킷런 사용경험이 있다면 쉽게 사용할 수 있는 패키지다.
Surprise 패키지는 아래와 같이 설치한다
pip install scikit-surprise
conda install -c conda-forge scikit-surprise
Surprise package
surprise는 user_id(사용자 아이디), item_id(아이템 아이디), rating(평점) 데이터가 로우 레벨로 된 데이터셋만 적용한다.
판다스 데이터프레임으로 로딩할 경우 반드시 데이터셋 컬럼 순서가 사용자id, 아이템id, 평점 순으로 되어있어야 한다.
Surprise를 활용한 도서 추천시스템 적용
그럼 Suprise 패키지를 잠재 요인 협업 필터링 기반의 개인화된 도서 추천에 적용해보자 데이터셋의 경우 이전 아이템 기반 협업 필터링에 사용한 데이터셋과 동일하다.
데이터 불러오기 및 새로운 데이터셋 생성
먼저 Suprise 패키지 및 필요한 라이브러리를 불러와보자
# Library import
import surprise
from surprise import SVD
from surprise import Dataset
from surprise import accuracy
from surprise.model_selection import train_test_split
from surprise.dataset import DatasetAutoFolds
import os
import pandas as pd
from surprise import Reader
먼저 아이템 기반 실습과 동일한 데이터셋을 불러와서 book-title 을 구하기 위해 조인을 진행하고 데이터를 파싱하였다.
서프라이즈 특징중 하나는 문자열로 구성되면 안 된다는 것이다.
그러므로 헤더를 제거하고 새로운 파일을 생성해야 한다.
Surprise API에서 제공하는 Reader 클래스를 사용하기 위해 Reader 객체 생성 시에 line_format 인자로 user, item, rating 컬럼으로 데이터가 구성돼 있음을 명시했고 각 컬럼의 분리문자는 콤마, 평점 단위는 1~10점으로 설정하였다.
이렇게 Reader 설정이 완료한 뒤 Dataset.load_from_file()를 통해 데이터를 파싱하며, Dataset을 로딩하였다.
ratings = pd.read_csv('data/kaggle_book/BX_Book_Ratings.csv', sep=';', encoding="cp949")
books = pd.read_csv('data/kaggle_book/BX_Books.csv', sep=';')
bookratings = pd.merge(ratings, books, on='ISBN')
bookratings.rename(columns={"ISBN":"item"}, inplace=True)
books.rename(columns={"Book-Title":"title", "ISBN":"item"}, inplace=True)
# ratings_noh.csv 파일로 언로드 시 인덱스와 헤더를 모두 제거한 새로운 파일 생성.
bookratings.to_csv('Book_Ratings_noh.csv', index=False, header=False)
col = 'user item rating'
reader = Reader(line_format=col, sep=',', rating_scale=(1, 10)) #Reader를 통해 데이터 파싱
data=Dataset.load_from_file('Book_Ratings_noh.csv', reader=reader)
- line_format(string) : 컬럼을 순서대로 나열. 입력된 문자열을 공백으로 분리해 컬럼으로 인식.
- sep(char) : 컬럼을 분리하는 분리자, 디폴트 ‘\t’. Pandas DataFrame에서 입력받을 경우에는 기재할 필요가 없다.
- rating_scale(tuple, optional) : 평점 값의 최소 ~ 최대 평점을 설정. 디폴트는 (1, 5)이지만 Book-Ratings.csv 파일의 경우는 최소 평점이 1, 최대 평점이 10이므로 (0, 10)로 설정.
개인화 도서 추천 시스템 구축
개인화된 도서 추천시스템 진행을 위한 예시로 2313이라는 사용자를 예로 들어보자
먼저 2313 유저가 전체도서중에서 평점을 매긴 도서가 몇 권인지 확인해보았다.
def get_unread_surprise(bookratings, books, userId):
#입력값으로 들어온 userId에 해당하는 사용자가 평점을 매긴 모든 도서를 리스트로 생성
read_books = bookratings[bookratings['User-ID']==userId]['item'].tolist()
# 모든 도셔의 ISBN를 리스트로 생성.
total_books = books['item'].tolist()
# 모든 도서의 ISBN 중 이미 평점을 매긴 도서의 ISBN를 제외한 후 리스트로 생성
unread_books = [book for book in total_books if book not in read_books]
print('평점 매긴 도서수:',len(read_books), '추천 대상 도서수:',len(unread_books), \
'전체 도서수 : ', len(total_books))
return unread_books
unread_books = get_unread_surprise(bookratings, books, 2313)
평점 매긴 도서수: 36 추천 대상 도서수: 271343 전체 도서수 : 271379
현재 하단 데이터셋을 보면 2313이 0345로 시작하는 ISBN 책에 대해 0으로 데이터가 표시된 것을 확인할 수 있다. 현재 데이터셋은 1점부터 10점까지의 평점방식으로 구성되어 있기 때문에 0은 책에 대한 평점을 매기지 않았다고 볼 수 있다.
그럼 구축한 suprise 패키지로 구축한 추천시스템 함수를 통해 2313유저의 평점 데이터를 기반으로 추천 도서 10개를 추출해보자
def recomm_book_by_surprise(algo, userId, unread_books, top_n=10):
# 알고리즘 객체의 predict() 메서드를 평점이 없는 영화에 반복 수행한 후 결과를 list 객체로 저장
predictions = [algo.predict(str(userId), str(item)) for item in unread_books]
# predictions list 객체는 surprise의 Prediction 객체를 원소로 가지고 있음.
# [Prediction(uid='2313', iid='1', est=3.69), Prediction(uid='2313', iid='2', est=2.98),,,,]
# 이를 est 값으로 정렬하기 위해서 아래의 sortkey_eat 함수를 정의함.
# sortkey_est 함수는 list 객체의 sort() 함수의 키 값으로 사용되어 정렬 수행.
def sortkey_est(pred):
return pred.est
# sortkey_est( ) 반환값의 내림 차순으로 정렬 수행하고 top_n개의 최상위 값 추출.
predictions.sort(key=sortkey_est, reverse=True)
top_predictions= predictions[:top_n]
# top_n으로 추출된 책의 정보 추출. [책 아이디, 추천 예상 평점, 제목 추출]
top_book_ids = [ str(pred.iid) for pred in top_predictions] #int 아니고 str
top_book_rating = [ pred.est for pred in top_predictions]
top_book_titles = bookratings[bookratings.item.isin(top_book_ids)]['Book-Title']
top_book_preds = [ (id, rating) for id, rating in zip(top_book_ids, top_book_rating)]
return top_book_preds
unread_books = get_unread_surprise(bookratings, books, 2313)
top_book_preds = recomm_book_by_surprise(algo, 2313, unread_books, top_n=10)
print('##### Top-10 추천 도서 리스트 #####')
for top_book in top_book_preds:
print(top_book[0], ":", top_book[1])
평점 매긴 도서수: 36 추천 대상 도서수: 271343 전체 도서수 : 271379
##### Top-10 추천 도서 리스트 #####
0151008116 : 10
0446532231 : 10
043935806X : 10
0802130208 : 9.76653080244683
039592720X : 9.633786282778438
0316603570 : 9.554672991920611
0345353145 : 9.400709640529469
0743418204 : 9.184100725602942
0385337116 : 9.13610550498025
0439136350 : 9.082734231643158
해당 도서의 구체적인 정보를 아래와 같이 확인할 수 있다.
list = ['0060976845', '068484267X', '0440219078', '0099771519', '0767902521', '0385510438', '0345413350', '0671776134', '0451161343', '076790592X']
bookstitle=books[books.item.isin(list)]
bookstitle
이중 가장 상위 랭크에 속한 서적은 레베카 웰스의 Little Altars Everywhere, 두번째로 프랭크 매코트의 Angela’s Ashes A Memoir로 결과가 나온 것을 알 수 있다.
Reference
- 파이썬 머신러닝 완벽가이드 - 권철민