LightFM 추천 알고리즘으로 도서 추천 시스템 구축하기!
안녕하세요, 이동희입니다! 이번 글에서는 LightFM이라는 추천 알고리즘과 NLP를 이용하여 도서 추천 시스템을 개발한 과정에 대한 이야기를 다뤄보려고 합니다. 이전 글에서 추천 시스템의 이론과 분류에 대한 내용을 간단하게 다뤘었는데, 링크로 접속하시면 해당 내용을 확인하실 수 있습니다.
이번 글에서 소개해드릴 도서 추천 시스템 개발 과정은 아래와 같은 순서로 글로 정리했습니다.
[INDEX]
데이터셋 탐색 및 정제
추천 모델 개발
최종 상품 추천 및 결과 분석
마무리
도서 추천 시스템 모델링을 위해 사용한 데이터셋은 Book-crossing 데이터셋입니다. 해당 데이터셋은 독일 프라이부르크 대학교 컴퓨터 사이언스 Cai-Nicolas Ziegler 교수가 Book-crossing 사이트를 크롤링하여 데이터를 쌓은 후에 배포된 데이터셋입니다.
Book-crossing 데이터셋은 기본적으로 '고객 정보 데이터', '책 정보 데이터', '평점 정보 데이터'를 포함하고 있습니다.
고객 정보 데이터를 통해 고객 ID(User-ID), 거주지(Location), 나이(Age) 정보를 알 수 있으며, 해당 데이터는 약 27만 건으로 구성되어 있습니다. 특별히 결측 데이터(nan) 처리가 필요한 Age 칼럼을 확인할 수 있었습니다.
책 정보 데이터를 통해 국제 표준 도서 번호(ISBN), 책 제목(Book-Title), 저자명(Book-Author), 출판 연도(Year-Of-Publication), 출판사명(Publisher), 책 이미지 파일 경로 (Image-URL-S, Image-URL-M, Image-URL-L) 정보를 알 수 있으며, 해당 데이터는 약 27만 건으로 구성되어 있습니다.
평점 정보 데이터를 통해 고객 ID(User-ID), 국제 표준 도서 번호(ISBN), 고객이 책에 부여한 평점(Book-Rating) 정보를 알 수 있으며, 해당 데이터는 약 110만 건으로 구성되어 있습니다. 또한, 평점(Book-Rating) 데이터는 0~10점으로 분포되어 있음을 알 수 있었습니다.
간단한 데이터셋 탐색을 통해 '평점 정보 데이터'는 '고객 기반 협업 필터링(User-based CF)' 방식으로, '상품 정보 데이터'는 '콘텐츠 기반 필터링(Contents based Filtering)' 방식으로 적용하고, 이들을 결합하여 '하이브리드(Hybrid)' 추천 시스템 구축이 가능하다는 것을 알았습니다. 추가로 하단에서 설명할 LightFM이라는 추천 모델은 '고객 정보 데이터' 역시도 학습에 사용할 수 있는데, 이를 통해 신규 고객에 대한 상품 추천도 수행 가능하다는 것을 알았습니다.
하지만 이번 글에서 설명할 책 추천 시스템은 위 사실을 온전히 인지하지 못한 채 진행하여 '평점 정보 데이터', '상품 정보 데이터', '고객 정보 데이터'를 모두 사용하여 학습을 수행하지만, 신규 고객이 아닌 사전에 평점을 부여한 기록이 있는 고객에게만 상품을 추천하는 방식(warm start)의 추천 시스템을 구축하였습니다. 대신 다음 포스팅에서 신규 고객 및 신규 상품에 대하여 추천이 가능한 온전한 LightFM '하이브리드' 추천 방식 시스템을 구축한 내용을 작성할 예정입니다.
데이터셋 정제 과정에는 오기입 된 데이터 수정, 결측 데이터 처리, NLP 등을 수행했는데, 특별히 주요한 과정이었던 NLP(자연어 처리)와 이에 속하는 Stemming(어간 추출) 적용 과정에 대해서만 다뤄보겠습니다.
먼저 NLP란 문자 그대로 해석하면 자연어 처리를 뜻하는데, 인간의 언어를 컴퓨터가 이해하여 사용할 수 있도록 연구하는 인공지능의 한 분야입니다. NLP 기술은 기본적으로 '토큰화(Tokenizer)', '불용어 처리(Stop-word)', '어간 추출(Stemming)', '형태소 분석(POS)'으로 분류할 수 있습니다.
토큰화(Tokenizer): 코퍼스를 토큰 단위(의미 있는 단위)로 나누는 작업
불용어 처리(Stop-word): 불필요한 단어(조사, 접미사, 이외 불필요하다고 판단되는 명사, 형용사 등) 제거
- 기본적으로 NLTK(자연어 툴킷)는 영어의 조사, 접미사 등을 불용어로 패키지 내에서 미리 정의하고 있음
- 한국어 불용어는 NLTK에 미리 정의되어 있지 않기에 직접 불용어를 정의하여 사용해야 함
어간 추출(Stemming): 변형된 단어에서 어미를 제거하고, 어간(단어의 원래 형태)을 추출하는 것
형태소 분석(POS): 형태소(뜻을 가진 가장 작은 단위)로 문장 분석
Stemming은 단어의 어미를 제거하고 어간(단어의 원래 형태)만 추출하는 것입니다. 예를 들어 'working, works, worked' 3개의 단어에 Stemming을 적용하면 'work'라는 어간을 추출하는 것입니다.
NLP Stemming을 최종 선택한 이유는 다른 NLP 기술에 비해 처리 속도가 월등히 빠르진 않았지만 큰 차이는 없었고, 무엇보다 정확한 예측이 중요했기 때문에 평가 지표(AUC) 값이 가장 높았던 Stemming을 채택하였습니다.
또한, NLTK 패키지는 Stemming을 수행할 수 있는 다양한 Stemmer를 제공하는데, Stemming을 처리하기 위한 데이터가 영어 문자열이었기에 이를 처리할 때 좋은 성능을 발휘하는 'PorterStemmer'를 채택하였습니다. PorterStemmer를 가지고 아래와 같이 책 정보 데이터의 'itemName' 칼럼 데이터를 Stemming 처리하여 'new_itemName' 칼럼 데이터를 생성할 때, 'Classical', 'Classics', 'Classic' 낱말이 모두 'classic' 어간으로 변환되었음을 알 수 있습니다.
추후 모델 학습 시에 Stemming 처리한 데이터를 CounterVectorizer를 통해 특징 벡터를 생성하는 데, 'Classical', 'Classics', 'Classic'을 동일한 어간인 'classic'으로 변환했기 때문에 특징 벡터의 동일한 인덱스로 취급됩니다. 이를 통해 학습 Loss를 줄일 수 있습니다.
추천 모델을 학습을 위해서는 고객 정보 데이터 및 상품의 정보 데이터는 컴퓨터가 이해할 수 있는 특징 벡터로 변환해야 합니다.
1) '거주지(Location)'
- 문자열 데이터에 NLP Stemming을 적용하여 어간 추출
- 이후에 CounterVectorizer(min_df=2, ngram_range=(1,1))를 적용하면, 전체 '거주지(Location)' 문자열 데이터에 등장하는 어간을 한 개씩 분류했을 때, 최소 2번 이상 등장한 어간의 횟수를 요소로 하는 벡터를 생성합니다.
2) '나이(Age)'
- LabelBinarizer를 사용하여 원핫인코딩 형태(자신:1, 나머지:0)의 벡터를 생성합니다.
최종 고객 정보 데이터 특징 벡터는 한 개의 고객 ID에 대한 '거주지(Location)' 벡터와 '나이(Age)' 벡터를 합친 형태로 표현합니다.
1) '책 제목(itemName)'
- 문자열 데이터에 NLP Stemming을 적용하여 어간 추출
- 이후에 CounterVectorizer(min_df=1, ngram_range=(1,3))을 적용하면, 전체 '책 제목(itemName)' 문자열 데이터에 등장하는 어간을 각각 한 개씩, 두 개씩, 세 개씩 분류했을 때, 최소 1번 이상 등장한 각 경우의 횟수를 요소로 하는 벡터를 생성합니다.
2) '저자명(bookAuthor)'
- 문자열 데이터에 NLP Stemming을 적용하여 어간 추출
- 이후에 CounterVectorizer(min_df=2, ngram_range=(1,1))을 적용하면, 전체 '저자명(bookAuthor)' 문자열 데이터에 등장하는 어간을 한 개씩 분류했을 때, 최소 2번 이상 등장한 어간의 횟수를 요소로 하는 벡터를 생성합니다.
3) '출판사명(publisher)'
- 문자열 데이터에 NLP Stemming을 적용하여 어간 추출
- 이후에 CounterVectorizer(min_df=2, ngram_range=(1,1))을 적용하면, 전체 '출판사명(publisher)' 문자열 데이터에 등장하는 어간을 한 개씩 분류했을 때, 최소 2번 이상 등장한 어간의 횟수를 요소로 하는 벡터를 생성합니다.
4) '출판 연도(year)'
- LabelBinarizer를 사용하여 원핫인코딩 형태(자신:1, 나머지:0)의 벡터를 생성합니다.
최종 책 정보 데이터 특징 벡터는 한 개의 책 ID에 대한 '책 제목(itemName)' 벡터, '저자명(bookAuthor)' 벡터, '출판사명(publisher)' 벡터와 '출판 연도(year)' 벡터를 합친 형태로 표현합니다
책 추천 모델로 LightFM이라는 파이썬으로 구현 가능한 추천 알고리즘을 사용했습니다. LightFM은 BPR(Bayesian Personlised Ranking), WARP(Weighted Approximate-Rank Pairwise)와 같은 랭킹 손실 함수를 기반으로 데이터를 학습하며, 고객이 상품에 행동(구매 여부, 평점 등)을 취할 확률을 예측하여 높은 확률을 가진 상품을 추천할 수 있는 것으로 이해했습니다.
아래와 같이 LightFM 모델 객체를 생성하여 학습 데이터, 상품 정보 데이터, 고객 정보 데이터를 가지고 학습을 수행하였습니다.
또한, 학습에 WARP라는 랭킹 손실 함수를 사용했습니다. WARP 랭킹 손실 함수는 쉬운 예시로 고객이 실제로 구매한 상품에 대한 구매 확률보다 실제로 구매하지 않은 상품에 대한 구매 확률이 더 큰 경우(에러 발생)에만 모델 업데이트를 수행해가며 최종 실제로 구매한 상품의 구매 확률을 가장 크게 만드는 것입니다.
LightFM 모델의 평가 지표로는 AUC(Area Under the Curve)를 사용했습니다. AUC는 ROC(Receiver Operating Charateristic) 곡선 아래의 면적을 뜻하며, binary classification 또는 multi-class classification 문제에서 True 라벨과 False 라벨의 분류 성능을 측정하는 지표입니다. 아래 그래프 이미지는 FPR(False Positive Rate)과 TPR(True Positive Rate)을 각각 x축, y축으로 하는 ROC 곡선을 그린 그래프입니다. ROC 곡선 아래 면적('A')이 AUC socre이며, AUC score가 1에 가까울수록 좋은 분류 성능을 가진 모델이라 할 수 있습니다. AUC와 관련된 자세한 내용은 링크에서 확인할 수 있습니다.
특별히, LightFM은 AUC를 평가 함수 패키지로 제공합니다. 아래와 같이 테스트 데이터, 고객 정보 데이터, 책 정보 데이터를 auc_score() 함수의 인자로 저장하고, 테스트 데이터에 존재하는 고객 ID에 대한 평균 AUC score를 계산하여 최종 50 epoch에서 81.48% 값을 얻을 수 있었습니다.
LightFM의 predict() 함수의 인자로 추천 대상 고객 ID를 저장하면, 각 책에 대한 추천 점수를 반환합니다. 해당 추천 점수가 의미하는 바가 평점을 예측한 값이 아닌 따로 정의된 척도가 없이 단지 추천 순위를 정하기 위한 값임을 LightFM 개발자들을 통해 알게 되었습니다.
개인적인 의견이지만, model.predict()를 통해 반환된 추천 점수가 의미하는 바는 각 책에 대하여 평점을 높게 매길 확률과 관련된 잠재된 값(lantent value)을 예측한 것이라 생각합니다. 그 이유는 학습 시에 사용했던 WARP 랭킹 손실 함수가 고객이 실제로 행동(클릭, 구매, 평점 부여 등)을 취한 상품에 대한 행동(클릭, 구매, 평점 부여 등) 확률보다 큰 행동 확률을 가진 실제로 구매하지 않은 상품 간의 행동 확률 차이에 기반하여 모델 업데이트를 수행하기 때문입니다. 결국 추천 점수(평점을 높게 매길 확률)가 큰 책을 추천할 수 있을 것입니다.
마지막으로, 추천 점수가 높은 책부터 차례대로 추천 대상 고객에게 추천합니다. 아래는 특정 추천 대상 고객이 기존에 8점 이상의 평점을 부여한 책 제목 리스트와 LightFM 추천 모델이 해당 고객에게 추천한 책 리스트입니다. 결과를 확인해보면, 기존에 8점 이상의 평점을 부여한 책 제목의 일부 낱말 또는 어간과 동일한 책을 추천했음을 확인할 수 있습니다. 이는 책 제목에 동일한 낱말 또는 어간이 있으면 비슷한 내용의 책일 것이라 가정하여 모델 학습을 수행한 효과라는 판단할 수 있습니다.
또한, 책 제목 이외에 추천에 영향을 준 다른 특징이 있는지 분석하기 위해 추천 대상 고객이 사전에 8점 이상의 평점을 부여한 책 리스트와 추천 리스트에서 동일한 낱말 또는 어간을 가진 책으로 각각 'Postcards From Life's Little...'와 'The BODACIOUS BOOK OF SUCCULENCE :...'를 선택하였습니다.
먼저, 아래와 같이 두 권의 책 정보를 확인해봤습니다. 그 결과 '저자명', '출판사명'은 동일한 낱말이나 어간이 없었고, '출판 연도'도 달랐기 때문에 최종 추천에 미치는 영향은 적었으리라 판단했습니다.
다음으로, 최종 추천한 책 'The BODACIOUS BOOK OF SUCCULENCE :...'에 8점 이상의 평점을 부여한 다른 고객들의 정보를 확인해봤는데, 대다수 고객의 '거주지'가 추천 대상 고객의 '거주지'와 동일한 'usa'인 것을 알 수 있었습니다. 고객 정보 데이터의 '거주지' 데이터 역시도 NLP Stemming으로 처리했었는데, 해당 데이터도 책 추천에 영향을 주었다고 판단할 수 있습니다.
위와 같은 절차대로 추천받은 책에 대하여 검증을 수행하면, 추천에 영향을 준 특징을 파악할 수 있을 것입니다.
지금까지 도서 추천 시스템을 구축하기 위해 진행했던 데이터셋 탐색 및 정제 과정, 추천 모델 개발 과정 및 평가 지표 분석, 추천 대상 고객에 대한 상품 추천 및 결과 분석에 관한 내용을 글에 담아보았습니다. 결론적으로, 최종 81.48%의 AUC score 성능을 가진 LightFM 추천 모델을 개발했고, 기존에 평점을 부여한 이력이 있는 고객에 대한 도서 추천을 수행할 수 있었습니다.
비록 신규 고객 및 신규 상품에 대한 '하이브리드' 추천 시스템을 구현하는 방법이 존재하지만, 당시 착오로 인해 진행하지 못했었고, 다음 글에서는 신규 고객 및 신규 상품에 대하여 추천이 가능한 '하이브리드' 추천 시스템을 구축한 내용을 작성할 예정입니다.
도입부에서 말씀드렸지만, 추천 시스템은 고객이 원하는 콘텐츠를 매번 검색하는 수고를 덜어주고, 고객 스스로 생각하지 못한 콘텐츠를 접할 수 있는 기회를 제공하는 등 많은 장점을 가지고 있습니다. 이러한 장점들이 다양한 산업 분야에서 고객 한 명에 대한 개인화 추천 시스템으로 적용되는 추세입니다. 추천 시스템은 쇼핑몰 업체에서 고객의 상품 평점, 구매한 상품 특징을 토대로 새로운 상품을 추천하는 것, YouTube와 Netflix 같은 스트리밍 서비스 업체에서 동영상이나 영화를 추천하는 것, 은행, 보험사 같은 금융 업계에서 고객 맞춤형 대출, 보험 등의 상품을 추천하는 것에 적용되고 있습니다. 마지막으로, 인스타그램, 페이스북 등 SNS의 뉴스피드 역시도 추천 시스템이 적용된 사례입니다. 하지만 어두운 이면에는 SNS 중독, 가짜 뉴스 추천, 부적절한 동영상 추천 등 사회적 문제를 일으키는 단점도 드러나고 있는 현실입니다.
제가 생각하는 좋은 추천 시스템은 단지 고객이 원하는 상품을 빠르고 정확하게 추천해주는 것을 뛰어넘어야 한다고 생각합니다. 앞서 말씀드린 사회적 문제를 일으킬 수 있는 요소(SNS 중독, 가짜 뉴스 추천, 부적절한 동영상 추천)까지 차단하고 필터링할 수 있는 추천 시스템이 좋은 시스템이라고 생각합니다. 이러한 추천 알고리즘 개발을 위해 노력을 기울여 누구에게나 건전하고 편리한 추천 시스템을 제공하는 것이 앞으로의 방향이 되어야 할 것 같습니다.
1. https://towardsdatascience.com/if-you-cant-measure-it-you-can-t-improve-it-5c059014faad
2. https://towardsdatascience.com/my-journey-to-building-book-recommendation-system-5ec959c41847