개인 정리용으로 정확하지 않은 부분이 있을 수 있습니다.
추천 시스템에서 사용하는 방식은 크게 내용 기반 필터링 (Contents Based Filtering)과 협업 필터링 (Collaborative Filtering) 두 가지가 있다.
내용 기반 필터링은 말 그대로 각 컨텐츠 (제품 또는 상품)의 특징을 분석해서 추천하는 방법이다. 예를 들어, 영화 추천 시스템에서 유저가 선호하는 (영화에 좋아요를 누른다거나 별점을 높게 준) 영화의 장르, 배우, 감독 등의 정보를 분석해서 유사한 영화를 추천할 수 있다. 음악 추천 서비스에서도 마찬가지로, 유저가 이전에 들은 노래와 유사한 곡의 장르, 아티스트, 앨범 등을 추천해줄 수 있다.
이처럼 내용 기반 필터링에서는 아이템에 대한 메타 데이터 (feature)와 과거 사용자 선호 데이터 (user profile)를 기반으로 한다.
내용 기반 필터링의 장점
다른 사용자에 대한 데이터가 필요하지 않아 로컬 DB를 이용하는 프로그램에서도 사용할 수 있다.
새로 추가된 아이템이나, 유명하지 않은 아이템도 추천이 가능하다. 즉, 사용자의 취향이 독특하더라도 추천이 가능해진다.
추천 결과에 대한 해석이 가능하다. 사용자가 선호하는 아이템의 설명을 이용해 유사성을 계산하여 추천하기 때문에 그 유사성의 근거를 파악할 수 있다.
내용 기반 필터링의 단점
아이템의 특성만을 기반하기 때문에 추천 다양성이 떨어진다. 즉, 선호하는 특성을 가진 아이템에 대해서만 반복 추천하는 문제가 생기게 된다. (overspecialization)
아이템의 Feature가 부족하거나, 정확하지 않으면 추천 정확도가 떨어진다.
협업 필터링은 나와 비슷한 성향을 가진 사람들이 좋아하는 것은 나도 좋아할 가능성이 높다는 사실을 기반으로 한다. 즉, 사용자의 과거 행동 (구매, 평점 패턴)을 분석하여, 비슷한 패턴을 갖는 다른 사용자가 선택한 아이템을 추천하는 방식이다.
협업 필터링도 다양한 종류가 있는데 메모리 기반 협업 필터링, 모델 기반 협업 필터링, 하이브리드 (메모리 기반 + 모델 기반) 협업 필터링 으로 나뉜다. 여기서는 전통적인 방식인 메모리 기반 협업 필터링에 대해 다룬다. 사실 모델 기반은 메모리 기반의 단점을 극복하기 위해 머신 러닝을 사용하는데 어려워서 쳐다도 안보았다..
메모리 기반 협업 필터링은 메모리에 저장된 사용자간/아이템 간의 유사도를 기반으로 동작한다. 메모리 기반 협업 필터링은 다시 사용자 기반 협업 필터링 (User-Based CF), 아이템 기반 협업 필터링 (Item-based CF)으로 구분된다.
나와 비슷한 성향을 가진 사람들이 사용한 아이템을 추천해 주는 방식이다. 예를 들어 A, B, C 사용자가 영화 '인셉션'을 좋아한다고 가정해보고, A, B 사용자가 영화 '라라랜드'를 좋아한다면, C 사용자에게 '라라랜드'를 추천해줄 수 있다.
SNS 친구 추천 서비스에서도 같은 방식을 적용하며, 나와 친구를 비슷한 성향으로 인식하여 친구의 친구들을 추천한다.
아이템 기반에서는 사용자 기반에서 대상이 아이템으로 바뀐 것 말고는 차이가 없다. 즉, 다른 사용자들이 아이템들을 어떻게 평가했는지를 기반으로 유사한 아이템을 추천하는 방식이다.
둘의 차이는 사용자 기반은 나와 비슷한 유저가 선호한 아이템을 추천해주고, 아이템 기반은 내가 선택한 아이템을 좋아한 유저들이 선호한 다른 아이템을 추천해준다.
아래는 이해를 돕기 위한 그림으로 (a) 사용자 기반 (b) 아이템 기반을 나타낸다.
(출처 : The collaborative filtering algorithms: (a) user-based; (b) item-based.)
사용자 기반과 아이템 기반 둘 중 어느 것을 사용하면 될까?
아마존과 넷플릭스를 비롯한 서비스에서는 대부분 아이템 기반 협업 필터링을 사용하는 것으로 알려져 있다. 대부분의 서비스에서 사용자 수가 아이템에 비해 많기 때문에 아이템 기반에서는 사용자 평가 데이터가 누적될수록 추천 정확도가 높아질 가능성이 높다.
또한 대규모의 사용자 서비스에서 사용자 기반은 사용자마다 유사도 행렬을 계산해야 하므로 계산 비용이 매우 증가하게 된다. 이외에도 전반적으로 아이템 기반이 사용자 기반보다 더 나은 추천 성능을 보인다고 한다.
현재 내가 하고 있는 프로젝트에서는 사용자가 레시피 페이지에 방문한 날짜, 페이지에 체류한 시간 (timeSpent), 즐겨찾기한 레시피 페이지 (favorite), 레시피 좋아요 (like)의 데이터로 협업 필터링을 통해 다른 레시피 페이지를 추천하고자 한다. 협업 필터링을 선택한 이유는 레시피에 대한 데이터를 DB에 갖고 있지 않고, 웹 사이트를 파싱하여 가져오는 구조이기 때문이다. 협업 필터링은 사용자의 행동에만 의존할 수 있기 때문에 아이템의 내용이나 특성을 몰라도 된다.
그리고 아이템 기반 협업 필터링으로 진행하기로 결정했다. 현재 하고 있는 프로젝트 같은 경우 사용자는 적고, 사용자가 방문한 레시피 페이지는 매우 다양하다. 즉, user는 적고, item이 많은 상황이다. 여기서 만약 사용자 기반 협업 필터링을 사용한다고 가정해보면, 유사도 측정을 위해 아래와 같은 user(행)-item(열) 행렬을 사용하게 된다. 그리고 사용자 간의 유사도를 계산하기 위해 각 사용자가 방문한 레시피 페이지 (아이템)들을 비교해보면 행렬의 희소성 문제로 추천이 정확하지 않을 수 있다.
Item1 | Item2 | Item3 | Item4 | Item5 | Item6 | Item7 | Item8 | Item9 | Item10 | |
---|---|---|---|---|---|---|---|---|---|---|
User1 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | 0 | 0 | 0 |
User2 | 0 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
User3 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 |
반면에, 아이템 기반 협업 필터링을 사용한다면, 모든 아이템 간의 유사도를 계산하는 것이 가능하기 때문에 희소성 문제가 해결될 수 있다. 또한 대부분의 사용자들이 비슷한 레시피 페이지를 방문할 확률이 높으므로 더 정확한 추천 결과를 제공하겠다는 생각을 했다.
아이템 기반 협업 필터링에서는 아이템 정보 데이터를 벡터 형태로 표현해 아이템간 유사도를 계산해야 한다. 그리고 아이템 간의 유사도를 통해 특정 아이템과 비슷한 아이템을 추천할 수 있다. 벡터의 유사도를 구하는 방법에는 여러가지가 있는데 몇 가지를 알아보면 다음과 같다.
- 유클리드 거리 : 두 벡터 사이의 직선 거리를 계산하여 유사도 측정
- 코사인 유사도 : 두 벡터 사이의 각도를 계산하여 유사도 측정
- 피어슨 유사도 : 두 벡터 사이의 상관 관계 (경향성)을 계산하여 유사도 측정
- 이외 사용자가 봤다/안봤다, 샀다/안샀다 등의 이진 값을 가진 경우 자카드 유사도를 사용한다.
유사도 계산 방법을 선택할 때는 추천 시스템 목적과 데이터에 따라 달라진다. 나는 이중 하나인 코사인 유사도 방식를 써보기로 했다. 아이템 기반 협업 필터링에서 주로 사용되는 것도 코사인 유사도 방식이라 한다.
코사인 유사도는 두 벡터 사이의 각도의 코사인값을 계산해서 두 벡터의 방향이 얼마나 유사한지를 나타낸다. 중요한 것은 코사인 유사도는 벡터의 길이 (크기)에 상관 없이 벡터 간의 방향성에만 초점을 둔다. 즉, 이 말은 사용자가 아이템들에 대해 평가한 양 (벡터의 크기)에 대해서는 고려하지 않고, 아이템들을 평가한 패턴 (벡터의 방향)에 대해 초점을 둔다는 얘기이다. 만약 벡터의 크기도 중요시하는 추천시스템에서는 유클리드 거리 방식을 사용하면 된다.
코사인 유사도 공식은 아래와 같다.
분자는 벡터의 내적을, 분모는 두 벡터 크기(norm)의 곱을 나타낸다. 코사인 유사도를 이해하기 위해서는 벡터의 내적을 이해해야 한다.
벡터 내적이 나온 이유는 두 벡터 사이의 사잇각을 구하기 위해 직각삼각형을 만들어야하기 때문이다. 위 그림에서 코사인 법칙으로 사잇각을 알아내려면 |a|의 길이를 알아내야한다. |a|는 a벡터를 b벡터 위로 정사형시킨 길이이다. 정사형은 어느 쪽을 하든 상관없다. 결국 벡터 내적을 구해서 |a| 또는 |b|를 나누면 직각삼각형에서 코사인 법칙으로 사잇각을 알아낼 수 있다.
이제 코사인 각을 가지고 유사도를 측정할 수 있다. 아래 코사인 그래프에서 각도가 0이면 코사인은 1의 값을 갖고, 180도일 때 -1의 값을 갖는다. 즉, 두 벡터 간의 코사인각이 0에 가까울수록 두 벡터가 유사하게되고 유사도는 1에 가까워진다. 반대로 코사인 각이 180에 가까울수록 두 벡터는 정반대의 방향을 가리키므로 유사도가 -1에 가까워진다.
프로젝트 구현 목표는 특정 레시피 id와 가장 유사한 레시피 id 5개를 item간 유사도를 계산하여 추천하는 것이다. 유사도 계산할 때는 데이터 전처리와 가중치 계산이 필요하므로 데이터 분석에 특화된 python을 사용하였다. 그러기 위해서 node.js 서버와 python과의 통신이 필요하므로 Redis 메시지 브로커를 사용하여 데이터 전송을 처리했다. 전체 흐름은 아래와 같다.
- node.js 서버로 /recommendations/:id?recipeId=?로 get요청이 들어오면, 사용자들이 페이지에 체류한 시간, 즐겨찾기, 좋아요를 누른 데이터들과 추천 대상인 레시피 id를 Redis 채널 (recommendation_request)에 publish 한다.
- python 스크립트에 Redis 리스너 (recommendation_request 구독)에서 받은 데이터로 pandas 데이터 프레임을 생성하고 데이터 전처리와 함께 레시피 id 간의 코사인 유사도를 계산하고 가장 유사한 레시피 id 5개를 선택해서 Redis 채널 (recommendation_response) publish한다.
- node.js 서버에서 Redis 리스너 (recommendation_response 구독)에서 데이터를 받고, 5개의 유사한 레시피 id를 json으로 클라이언트에 응답한다.
글이 길어져 구현 부분은 다음 글에서 작성해야겠다..
[Node.js/Python] IBCF 레시피 추천 시스템 구현 (2) - 구현
https://ko.wikipedia.org/wiki/%EC%B6%94%EC%B2%9C_%EC%8B%9C%EC%8A%A4%ED%85%9C
https://www.codingworldnews.com/news/articleView.html?idxno=2477
https://www.mathsisfun.com/algebra/trig-sin-cos-tan-graphs.html