이번 글에서는 프로그래머스 인공지능 데브코스의 16주차 강의에 대한 정리입니다.
이 강의에서는 추천 시스템에 대하여 다루게 됩니다.
1. Recommendation System 이란?
추천 엔진의 정의
- 사용자: 서비스를 사용하는 사람
- 아이템: 서비스에서 판매하는 물품 (다른 사용자가 물품이 될 수도 있음 - 링크드인 등)
- 일반적으로 서비스가 성장하면, 사용자나 아이템의 수도 같이 성장하게 됨
- 특히, 사용자의 성장도가 훨씬 커짐
- 하지만 아이템의 수가 커지면서 아이템의 선택에 대한 이슈가 생김
- 모든 사용자가 능동적으로 검색하지 않고, 사람들이 추천에 대한 니즈가 생김
- Twitter의 알파 제인의 정의: 사용자가 관심 있어 할 만한 아이템을 제공해주는 자동화 된 시스템 (관심, 자동화)
- Yahoo의 디팍 아그라왈의 정의: 비즈니스 장기적 목표를 개선하기 위해 사용자에게 알맞은 아이템을 자동으로 보여주는 시스템 (장기적 목표, 매출액, 자동화)
추천 엔진이 필요한 이유
- 조금의 노력으로 사용자가 관심 있어 할 만한 아이템을 찾아주는 방법
- 아이템의 수가 굉장히 큰 경우 더 의미가 있음
- 사람의 노다가로 해결할 수 없어 자동화가 필요함
- 개인화 (Personalization)로 연결 될 수 있음
- 또한 가끔씩 전혀 관심 없을 듯한 아이템도 추천 가능 (Serendipity)
- 회사 관점에서는 추천 엔진을 기반으로 다양한 기능을 추가할 수 있음
- 마케팅 시, 추천 엔진 사용 (이메일 마케팅)
- 관련 상품 추천으로 쉽게 확장 할 수 있음
- 아이템의 수가 많아서 원하는 것을 찾기 어려운 경우 (검색의 수고를 덜어줌)
- 추천을 통해 신상품 등의 마케팅이 가능해짐 (추천을 통해 신상품 노출이 가능)
- 인기 아이템 뿐 아니라, 롱 테일의 다양한 아이템을 노출 할 수 있음 (개인화가 알고리즘화 될 수 있음)
추천은 결국 매칭 문제
- 사용자에게는 맞는 아이템을 매칭해주기 (아이템은 서비스에 따라 달라지고, 다른 사용자가 될 수도 있음)
- 어떤 아이템을 추천할 것인가?
- 지금 뜨는 아이템 추천 (개인화 되어 있지 않은 추천)
- 사용자가 마지막에 클릭했던 아이템 추천
- 사용자가 구매했던 아이템을 구매한 다른 사용자가 구매한 아이템 추천 (협업 필터링)
- 추천 UI도 굉장히 중요 (보통 추천 유닛이 존재하고, 이를 어떤 순서로 노출시킬지?)
- Cold Start 문제: 사용자의 데이터가 없는 상태에서 새로운 추천을 어떻게 해야 할까?
- 따라서 사용자와 아이템 등에 대한 부가 정보들이 필요해짐
- 아이템 부가 정보: 먼저 분류 체계를 만들어야 함, 태그 형태로 부가 정보를 유지하는 것도 좋음
- 계층 구조의 분류 체계 (대분류, 소분류, 태그 및 키워드 등)
- 사용자 프로파일 정보: 개인정보 (성별, 연령), 아이템 정보 (관심 및 서브 카테고리, 태그, 클릭, 구매 아이템)
- 무엇을 기준으로 추천 할 것인가? 클릭? 매출? 소비? 평점?
추천 엔진 예제
- 아마존 관련 상품 (Related Product) 추천 - 사용자: 멤버, 아이템: 상품
- 과거 구매 이력, 인기 있는 상품들, 주기적으로 구매 할 수 있는 상품들 등이 추천되고 있음
- 넷플릭스 영화 및 드라마 추천 - 사용자: 멤버, 아이템: 영화, 드라마
- 격자 형태의 추천 유닛 (최근 뜨고 있는 것 기반, 과거 시청 이력 기반, 인기 Top 10 기반 추천)
- 구글 자동 검색어 완성 - 사용자: 검색자, 아이템: 검색어
- 타이핑을 전체 완성하지 않아도 됨, 알지 못했던 새로운 키워드로 접근 가능하다는 등의 효과
- 링크드인 혹은 페이스북 친구 추천 - 사용자: 멤버, 아이템: 멤버
- Industry, 지역, 학교 등과 관련 있는 사람들 추천
- 스포티파이 혹은 판도라 노래, 플레이리스트 추천 - 사용자: 멤버, 아이템: 노래, 플레이리스트
- 헬스케어 도메인의 위험 점수 계산 - 사용자: 의사, 간호사, 아이템: 환자
- 어느 환자가 더 위험한지 예측하여 치료시 우선순위를 주기 위함
- 환자 별로 발병 확률과 발병시 임팩트를 계산하여 곱하는 형태 (발병 확률을 모델링)
- 유데미 강좌 추천 - 사용자: 멤버, 아이템: 강좌
앞서 살펴본 추천 엔진들의 공통점
- 격자 형태의 UI 사용 (넷플릭스가 선구자)
- 다양한 종류의 추천 유닛들이 존재함
- 일부 유닛은 개인화 (사람에 따라 다른 아이템을 추천해줌)
- 일부 유닛은 인기도 등의 비개인화 정보 기반 (모든 사람에게 동일한 아이템 추천)
- 추천 유닛의 랭킹이 중요해짐
- 이 부분도 모델링 하여 개인화 하는 추세
- 클릭을 최적화 하고, 이 데이터 수집을 위한 실험을 수행 (데이터 수집을 위한 온라인 테스트)
추천 엔진의 종류
- 컨텐츠 기반 (아이템 기반)
- 개인화 된 추천은 아니고, 비슷한 아이템을 기반으로 추천이 이뤄짐
- 책이라면, 타이틀, 저자, 책 요약, 장르 등의 정보를 사용
- 많은 경우, NLP 테크닉을 사용하여 텍스트 정보를 벡터 정보로 변환 (단어 카운트, TF-IDF, 임베딩)
- 구현이 상대적으로 간단 (보통 아이템의 수가 사용자의 수 보다 작음)
- 개인화 된 추천은 아니고, 비슷한 아이템을 기반으로 추천이 이뤄짐
- 협업 필터링 (Collaborative Filtering): 평점 기준
- 기본적으로 다른 사용자들의 정보를 이용하여 내 취향을 예측하는 방식
- 사용자 기반, 아이템 기반 두 종류가 존재
- 결국은 행렬 계산으로 이뤄짐 (Sparse 행렬 형태)
- 유사도 계산 (코사인 유사도 등)
- 사용자 기반 (User): 나와 비슷한 평점 패턴을 보이는 사람을 찾아서 그 사람들의 평이 좋았던 것을 추천
- 나와 비슷한 사용자를 어떻게 찾을지가 중요 (사용자 프로파일 정보 구축, 프로파일간 유사도 계산 - KNN)
- 아이템 기반 (Item): 평점의 패턴이 비슷한 아이템들을 찾아서 그것을 추천하는 방식
- 2001년에 아마존에서 논문으로 발표. 아이템들 간 유사도를 비교하는 것으로 시작
- 사용자 기반 협업 필터링과 비교하여 더 안정적이며, 좋은 성능을 보임
- 아이템의 수가 보통 작기 때문에 사용자에 비해 평점의 수가 평균적으로 많고, 계산량이 적음
- 즉, 사용자 기반 추천에 비해, 데이터에 대한 고객들의 평점 등에 대한 데이터가 많아서 성능이 좋음
- 기본적으로 다른 사용자들의 정보를 이용하여 내 취향을 예측하는 방식
- 사용자 행동 기반
- 아이템 클릭, 구매, 소비 등의 정보를 기반으로 하는 추천
- 사용자와 아이템에 대한 부가 정보가 반드시 필요함
- 여기에 속하는 추천은 구현이 간단하긴 하지만, 아주 유용함
- 모델링을 통해 사용자와 아이템 페어에 대한 클릭 확률 등의 점수 계산이 가능
- 의사 결정 트리나 딥러닝 등이 사용 가능 (유데미에서 채택한 방법)
- Batch 기반 추천, 실시간 추천인지 방식 등도 결정해야 함
- 아이템 클릭, 구매, 소비 등의 정보를 기반으로 하는 추천
- 위 알고리즘들을 하이브리드 형태로 사용
유사도 측정 방법
- 두 개의 비교 대상을 N차원 좌표로 표현. 사용자와 사용자 혹은 아이템과 아이템
- 보통 코사인 유사도나 피어슨 상관계수 유사도를 사용하게 됨
- 두 벡터의 방향성이 비슷할수록 1에 가까운 값이 계산되는 코사인 유사도 (동일할 경우 1이 됨)
- 두 벡터가 반대 방향을 향하는 경우에는 -1이 계산
- 피어슨 유사도는 코사인 유사도의 개선 버전으로 각 벡터를 중앙 (중심)으로 재조정
협업 필터링에 대한 문제
- Cold start 문제
- 사용자: 아직 평점을 준 아이템이 없는 경우
- 아이템: 아직 평점을 준 사용자가 없는 경우
- 보통 컨텐츠 기반 혹은 사용자 행동 기반 추천과 병행하여 이 문제를 해결해나감
- 리뷰 정보의 부족 (Sparsity) - 리뷰를 했다는 자체도 사실은 관심으로 볼 수 있음
- 업데이트 시점 - 사용자나 아이템이 추가 될때마다 다시 계산해야 함
- 확장성 이슈 - 사용자와 아이템의 수가 늘어나면서 행렬 계산에 시간이 오래 걸림 (Spark 같은 것이 필요해지는 이유)
- 협업 필터링의 많은 문제들이 추천의 일반적인 문제이기도 함
협업 필터링 구현 방법
- 메모리 기반
- 앞서 설명한 방식 (사용자 기반, 아이템 기반)
- 사용자간 혹은 아이템간 유사도를 미리 계산
- 추천 요청이 오면, 유사한 사용자 혹은 아이템을 K개 뽑아서 이를 바탕으로 아이템 추천
- 구현과 이해가 상대적으로 쉽지만, 스케일하지 않음 (평점 데이터의 부족)
- 모델 기반
- 넷플릭스 프라이즈 컨테스트 때 고안된 추천 방식 (아이템 행렬에서 비어 있는 평점을 SGD를 사용해서 채움)
- 이는 보통 SVD (Singular Vector Decompostion)을 사용해서 구현 (딥러닝의 오토인코더를 사용하기도 함)
- 평점을 포함한 다른 사용자 행동을 예측하는 방식으로 진화하고 있음
- 주로 암시적 정보 (클릭, 구매, 소비)를 기반으로 행동을 예측하게 됨
- 아이템 노출 - 아이템 클릭 - 아이템 구매 - 아이템 소비 (인프라 단에서 해당 데이터들 수집이 필요함)
- 사용자 행동 기반 간단한 추천 유닛 구성
- 사용자가 관심을 보인 특정 카테고리의 새로운 아이템, 인기 아이템 등
- 사용자 행동을 예측하는 추천 (클릭 혹은 구매)
- 지도 학습 문제로 접근 가능. 무엇을 학습하고, 예측하는 모델인지 먼저 생각해야 함
- 어떤 기준으로 추천을 할까? = 머신러닝의 레이블 정보
- 명시적 힌트: 리뷰 점수 (Rating), 좋아요 (Like)
- 암시적 힌트: 클릭, 구매, 소비 등
- 지도 학습 문제로 접근 가능. 무엇을 학습하고, 예측하는 모델인지 먼저 생각해야 함
2. Recommendation System 구현 1
넷플릭스 프라이즈 개요
- 2006년부터 3년간 운영된 넷플릭스의 기념비적인 추천 엔진 경진대회
- 넷플릭스 추천 시스템 품질을 10% 개선하는 팀에서 $1M 수여 약속 (RMSE가 평가 기준으로 사용)
- 프라이버시 이슈도 제기 되었긴 했지만, 넷플릭스 브랜드 인지도도 올라감
- 이를 기폭제로 캐글과 같은 머신러닝 경진대회 플랫폼이 등장
- 이 대회를 통해서 협업 필터링이 한 단계 발전하게 되었음
- SVD를 활용한 SVD++는 이후 굉장히 많은 분야에서 활용됨
- 앙상블 방식의 모델들이 가장 좋은 성능을 보이게 됨 (하지만 실행시간이 너무 길어서, 실제로는 사용 불가)
- 앙상블과 랜덤포레스트: 다수의 분류기를 사용해서 예측하는 방식
- 성능이 좋긴 하지만, 훈련과 예측 시간이 오래 걸린다는 단점이 있음
- 다양한 알고리즘들이 논문으로 학회에서 발표됨 (SVD++ 포함)
추천 엔진의 발전 역사
- 2001년 아마존이 아이템 기반 협업 필터링 논문 발표
- 2006년 ~ 2009년 넷플릭스 프라이즈
- SVD를 이용한 사용자의 아이템 평점 예측 알고리즘 탄생
- 앙상블 알고리즘의 보편화
- 딥러닝의 일종이라고 할 수 있는 RBM (Restricted Boltzman Machine)이 단일 모델로 최고 성능을 보임
- 딥러닝이 추천의 분야에서 사용 가능성을 보이게 됨
- 2010년 딥러닝이 컨텐츠 기반 음악 추천에 사용되기 시작
- 2016년 딥러닝을 기반으로 한 추천이 활기를 띠기 시작
- 오토인코더 기반으로 복잡한 행렬 계산을 단순화 하는 방식이 하나
- 아이템 관련 사용자 정보를 시간 순으로 인코드 하는 RNN을 사용하는 방식이 다른 방식
- 아마존에서 DSSNTE 라는 알고리즘을 오픈소스화 했다가 나중에 SageMaker 라는 제품으로 통합
유데미 추천 살펴보기
- 문제 정의: 학생들에게 관심 있을만한 강의를 먼저 보여주는 것
- 추천 UI - 격자 기반 UI, 다양한 추천 유닛들이 존재 (유닛 선택과 랭킹이 필요함)
- 온라인 강의 메타 데이터 - 분류 체계, 태그, 클릭 키워드 분석 등
- 다양한 행동 기반 추천 - 클릭, 구매, 소비 등
인기도 기반 추천 유닛 개발
- 인기도 기반 추천은 Cold start 이슈가 존재하지 않음
- 그렇다면, 인기도의 기준은? 평점? 매출? 최대 판매?
- 사용자 정보에 따라 확장 가능 (서울 지역 인기 아이템 추천 등)
- 단, 개인화는 되어 있지 않음 (어느 정도는 가능함)
- 아이템의 분류 체계 정보 존재 여부에 따라 쉽게 확장 가능 (특정 카테고리에서의 인기 아이템 추천)
- 인기도를 다른 기준으로 바꿔서 다양한 추천 유닛 생성 가능 (Top course, Newest course 등)
- Cold start 이슈가 없는 추천 유닛 (현재 사용자들이 구매한 아이템, 사용자들이 보고 있는 아이템 등)
유사도 측정 (코사인 및 피어슨 유사도)
- 벡터들 사이에 유사도를 판단
- 코사인 유사도: N차원 공간에 있는 두 개의 벡터 간의 각도 (원점에서)를 보고서 유사도를 판단하는 기준
- 평점처럼 방향 뿐 아니라, 벡터 크기의 정규화도 중요하면, 피어슨 유사도를 사용하게 됨 (코사인 유사도의 개선판)
- 먼저 벡터 A와 B의 값들을 보정
- 각 벡터 내 셀들의 평균값들을 구한 뒤, 평균값을 각 셀에서 빼줌
- 예를 들면, A = {3, 4, 5} 라면, 평균값은 4이고, 보정 후에는 {-1, 0, 1}이 됨
- 그 이후 계산은 코사인 유사도와 동일함. 이를 중앙 코사인 유사도 혹은 보정된 코사인 유사도라고 부름
- 이를 통해 모든 벡터가 원점을 중심으로 이동하고, 벡터 간 비교가 더 쉬워짐 (정규화 효과)
TF-IDF 소개와 실습
- 텍스트를 행렬 (벡터)로 표현하는 방법
- 텍스트 문서를 행렬로 표현하는 방법은 여러 가지가 존재함
- 기본적으로 일단 단어를 행렬의 차원으로 표현해야 함
- Bag of Words 방식은 문서들에 나타나는 단어 수가 N개이면, N차원으로 문서를 표현
- 딥러닝의 워드임베딩 사용시, 차원 수도 축소되고, 공간 상에서 비슷한 단어끼리 가깝게 위치
- One Hot Encoding + Bag of Words (카운트) - 단어의 수를 카운트해서 표현함
- 가장 먼저는 Stopwords를 제거함 (the, is, in, we, can, see 등)
- 그 뒤 단어의 수를 계산함 (sky, blue, sun, bright, shining 5개)
- 단어 별로 차원을 배정 (sky = 1, blue = 2, sun = 3, bright = 4, shining = 5)
- One Hot Encoding + Bag of Words (TF-IDF) - 단어의 값을 TF-IDF 알고리즘으로 계산된 값으로 표현
- CountVectorizer 소개
- 앞서 Bag of Words 카운팅 방식을 구현한 모듈
- 벡터로 표현이 되면, 문서들 간의 유사도 측정이 가능함
- TF-IDF 소개
- 앞서 카운트 방식은 자주 나오는 단어가 높은 가중치를 가지게 됨
- 하지만 TF-IDF는 한 문서에서 중요한 단어를 카운트가 아닌 문서군 전체를 보고 판단함
- 어떤 단어가 한 문서에서 자주 나오면 중요하지만, 이 단어가 다른 문서들에서 자주 나오지 않으면 더 중요할 것
- TF-IDF = TF(t, d) * IDF(t)
- TF(t, d): 단어 t가 문서 d에서 몇 번 나왔는가?
- DF(t): 단어 t가 전체 문서군에서 몇 번 나왔는가?
- IDF(t): 앞서 DF(t)의 역비율
- 단어 t가 전체 문서들 중에서 몇 개의 문서에서 나왔는지? 이 비율을 역으로 계산한 것이 IDF
- In(N/DF): N은 총 문서 수를 나타내고, DF는 단어가 나온 문서를 뜻함
1
2
3
4
5
6
7
8
9
10
11
12
### CountVectorizer 코드화
from sklearn.feature_extraction.text import CountVectorizer
text = [
'The sky is blue', # sky, blue
'The sun is bright', # sun, bright
'The sun in the sky is bright', # sun, sky, bright
'We can see the shining run, the bright sun' # shining, sun, bright
]
countvectorizer = CountVectorizer(analyzer='word', stop_words='english')
count_wm = countvectorizer.fit_transform(text)
sky | blue | sun | bright | shining | |
doc1 | 1 | 1 | 0 | 0 | 0 |
doc2 | 0 | 0 | 1 | 1 | 0 |
doc3 | 1 | 0 | 1 | 1 | 0 |
doc4 | 0 | 0 | 2 | 1 | 1 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
### TFidfVectorizer 코드화
from sklearn.feature_extraction.text import TfidfVectorizer
text = [
'The sky is blue',
'The sun is bright',
'The sun in the sky is bright',
'We can see the shining run, the bright sun'
]
tfidfvectorizer = TfidfVectorizer(analyzer='word', stop_words='english', norm='l2')
tfidf_wm = tfidfvectorizer.fit_transform(text)
### Cosine 유사도 계산
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarities = cosine_similarity(tfidf_wm)
sky | blue | sun | bright | shining | |
doc1 | 1, 1*log(4/2) = 0.6191 | 1, 1*log(4/1) = 0.7852 | 0 | 0 | 0 |
doc2 | 0 | 0 | 1, 1*log(4/3) = 0.7071 | 1, 1*log(4/3) = 0.7071 | 0 |
doc3 | 1, 1*log(4/2) = 0.6578 | 0 | 1, 1*log(4/3) = 0.5325 | 1, 1*log(4/3) = 0.5325 | 0 |
doc4 | 0 | 0 | 2, 2*log(4/3) = 0.7325 | 1, 1*log(4/3) = 0.3662 | 1, 1*log(4/1) = 0.5738 |
TF-IDF 문제점
- 정확히 동일한 단어가 나와야 유사도 계산이 이뤄짐 (동의어 처리가 안됨)
- 단어의 수가 늘어나고, 아이템의 수가 늘어나면 계산이 오래 걸림
- 결국 워드 임베딩을 사용하는 것이 더 좋음 (아니면, LSA 같은 차원 축소 방식을 사용해야 함)