들어가며¶
실습 목표
- 추천 시스템의 개념과 목적 이해
- Implicit library를 활용하여 Matrix Factorization 기반의 추천 모델을 만들어본다.
- 음악 감상 기록을 활용하여 비슷한 아티스트를 찾고 아티스트를 추천해 본다.
- 추천 시스템에서 자주 사용되는 데이터 구조인 CSR Matrix을 익힌다
- 유저의 행위 데이터 중 Explicit data와 Implicit data의 차이점을 익힌다.
- 새로운 데이터셋으로 직접 추천 모델을 만들어 본다.
목차
- 추천 시스템이란 게 뭔가요?
- 데이터 탐색하기와 전처리
- 사용자의 명시적/암묵적 평가
- Matrix Factorization(MF)
- CSR(Compressed Sparse Row) Matrix
- MF 모델 학습하기
- 비슷한 아티스트 찾기 + 유저에게 추천하기
- 프로젝트 - MovieLens 영화 추천 실습
추천 시스템이란?¶
추천의 원리를 알아보자.
협업 필터링 vs 콘텐츠 기반 필터링¶
협업 필터링은 다수의 사용자의 아이템 구매 이력 정보만으로 사용자간 유사성 및 아이템 간 유사성을 파악하지만,
콘텐츠 기반 필터링은 아이템의 고유의 정보를 바탕으로 아이템 간 유사성을 파악합니다.
다시 말해, 협업 필터링에서는 아이템과 사용자 간의 행동 또는 관계에만 주목할 뿐 아이템 자체의 고유한 속성에 주목하지 않습니다.
콘텐츠 기반 필터링에서는 아이템 자체의 속성에만 주목하고 사용자와 아이템 간의 관련성 에는 주목하지 않습니다.
추천시스템의 핵심 근간은 협업 필터링이다. 이를 바로 사용할 수 없는 제약 조건 3가지¶
시스템이 충분한 정보를 모으지 못한 사용자나 아이템에 대한 추론을 할 수 없는 상태인 콜드 스타트(Cold Start) 상황
계산량이 너무 많아 추천의 효율이 떨어지는 상황
롱테일의 꼬리 부분, 즉 사용자의 관심이 저조한 항목의 정보가 부족하여 추천에서 배제되는 상황
이렇게 추천 시스템은 아이템이 매우 많고
, 유저의 취향이 다양할 때
유저가 소비할 만한 아이템을 예측하는 모델이다.
$ mkdir -p ~/aiffel/recommendata_iu/data/lastfm-dataset-360K
$ ln -s ~/data/lastfm-dataset-360K/* ~/aiffel/recommendata_iu/data/lastfm-dataset-360K
데이터 탐색하기와 전처리¶
데이터 준비¶
항목을 열어보면, user-mboxsha1 \t musicbrainz-artist-id \t artist-name \t plays
이렇게 4가지가 보일 것이다. 이들은 각각, User ID, Artist MBID, Artist Name, Play횟수
로 보인다.
Pandas를 이용해 데이터를 열어보자. 컬럼명을 적절히 지정해주자.
import pandas as pd
import os
fname = os.getenv('HOME') + '/aiffel/recommendata_iu/data/lastfm-dataset-360K/usersha1-artmbid-artname-plays.tsv'
col_names = ['user_id', 'artist_MBID', 'artist', 'play'] # 임의로 지정한 컬럼명
data = pd.read_csv(fname, sep='\t', names= col_names) # sep='\t'로 주어야 tsv를 열 수 있습니다.
data.head(10)
user_id | artist_MBID | artist | play | |
---|---|---|---|---|
0 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 3bd73256-3905-4f3a-97e2-8b341527f805 | betty blowtorch | 2137 |
1 | 00000c289a1829a808ac09c00daf10bc3c4e223b | f2fb0ff0-5679-42ec-a55c-15109ce6e320 | die Ärzte | 1099 |
2 | 00000c289a1829a808ac09c00daf10bc3c4e223b | b3ae82c2-e60b-4551-a76d-6620f1b456aa | melissa etheridge | 897 |
3 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 3d6bbeb7-f90e-4d10-b440-e153c0d10b53 | elvenking | 717 |
4 | 00000c289a1829a808ac09c00daf10bc3c4e223b | bbd2ffd7-17f4-4506-8572-c1ea58c3f9a8 | juliette & the licks | 706 |
5 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 8bfac288-ccc5-448d-9573-c33ea2aa5c30 | red hot chili peppers | 691 |
6 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 6531c8b1-76ea-4141-b270-eb1ac5b41375 | magica | 545 |
7 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 21f3573f-10cf-44b3-aeaa-26cccd8448b5 | the black dahlia murder | 507 |
8 | 00000c289a1829a808ac09c00daf10bc3c4e223b | c5db90c4-580d-4f33-b364-fbaa5a3a58b5 | the murmurs | 424 |
9 | 00000c289a1829a808ac09c00daf10bc3c4e223b | 0639533a-0402-40ba-b6e0-18b067198b73 | lunachicks | 403 |
artist_MBID 항목은 우리의 관심사가 아니다. 사용하는 컬럼만 남기고 제거하도록 하자.
# 사용하는 컬럼만 남겨줍니다.
using_cols = ['user_id', 'artist', 'play']
data = data[using_cols]
data.head(10)
user_id | artist | play | |
---|---|---|---|
0 | 00000c289a1829a808ac09c00daf10bc3c4e223b | betty blowtorch | 2137 |
1 | 00000c289a1829a808ac09c00daf10bc3c4e223b | die Ärzte | 1099 |
2 | 00000c289a1829a808ac09c00daf10bc3c4e223b | melissa etheridge | 897 |
3 | 00000c289a1829a808ac09c00daf10bc3c4e223b | elvenking | 717 |
4 | 00000c289a1829a808ac09c00daf10bc3c4e223b | juliette & the licks | 706 |
5 | 00000c289a1829a808ac09c00daf10bc3c4e223b | red hot chili peppers | 691 |
6 | 00000c289a1829a808ac09c00daf10bc3c4e223b | magica | 545 |
7 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the black dahlia murder | 507 |
8 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the murmurs | 424 |
9 | 00000c289a1829a808ac09c00daf10bc3c4e223b | lunachicks | 403 |
# 검색을 쉽게 하기 위해 아티스트 문자열을 소문자로 바꿔줍시다.
data['artist'] = data['artist'].str.lower()
data.head(10)
user_id | artist | play | |
---|---|---|---|
0 | 00000c289a1829a808ac09c00daf10bc3c4e223b | betty blowtorch | 2137 |
1 | 00000c289a1829a808ac09c00daf10bc3c4e223b | die ärzte | 1099 |
2 | 00000c289a1829a808ac09c00daf10bc3c4e223b | melissa etheridge | 897 |
3 | 00000c289a1829a808ac09c00daf10bc3c4e223b | elvenking | 717 |
4 | 00000c289a1829a808ac09c00daf10bc3c4e223b | juliette & the licks | 706 |
5 | 00000c289a1829a808ac09c00daf10bc3c4e223b | red hot chili peppers | 691 |
6 | 00000c289a1829a808ac09c00daf10bc3c4e223b | magica | 545 |
7 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the black dahlia murder | 507 |
8 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the murmurs | 424 |
9 | 00000c289a1829a808ac09c00daf10bc3c4e223b | lunachicks | 403 |
첫 번째 유저가 어떤 아티스트의 노래를 듣는지 확인해보자.
condition = (data['user_id']== data.loc[0, 'user_id'])
data.loc[condition]
user_id | artist | play | |
---|---|---|---|
0 | 00000c289a1829a808ac09c00daf10bc3c4e223b | betty blowtorch | 2137 |
1 | 00000c289a1829a808ac09c00daf10bc3c4e223b | die ärzte | 1099 |
2 | 00000c289a1829a808ac09c00daf10bc3c4e223b | melissa etheridge | 897 |
3 | 00000c289a1829a808ac09c00daf10bc3c4e223b | elvenking | 717 |
4 | 00000c289a1829a808ac09c00daf10bc3c4e223b | juliette & the licks | 706 |
5 | 00000c289a1829a808ac09c00daf10bc3c4e223b | red hot chili peppers | 691 |
6 | 00000c289a1829a808ac09c00daf10bc3c4e223b | magica | 545 |
7 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the black dahlia murder | 507 |
8 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the murmurs | 424 |
9 | 00000c289a1829a808ac09c00daf10bc3c4e223b | lunachicks | 403 |
10 | 00000c289a1829a808ac09c00daf10bc3c4e223b | walls of jericho | 393 |
11 | 00000c289a1829a808ac09c00daf10bc3c4e223b | letzte instanz | 387 |
12 | 00000c289a1829a808ac09c00daf10bc3c4e223b | goldfrapp | 361 |
13 | 00000c289a1829a808ac09c00daf10bc3c4e223b | horrorpops | 358 |
14 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the butchies | 329 |
15 | 00000c289a1829a808ac09c00daf10bc3c4e223b | jack off jill | 316 |
16 | 00000c289a1829a808ac09c00daf10bc3c4e223b | babes in toyland | 310 |
17 | 00000c289a1829a808ac09c00daf10bc3c4e223b | dropkick murphys | 302 |
18 | 00000c289a1829a808ac09c00daf10bc3c4e223b | all:my:faults | 288 |
19 | 00000c289a1829a808ac09c00daf10bc3c4e223b | le tigre | 281 |
20 | 00000c289a1829a808ac09c00daf10bc3c4e223b | schandmaul | 244 |
21 | 00000c289a1829a808ac09c00daf10bc3c4e223b | edguy | 232 |
22 | 00000c289a1829a808ac09c00daf10bc3c4e223b | maximum the hormone | 231 |
23 | 00000c289a1829a808ac09c00daf10bc3c4e223b | all ends | 229 |
24 | 00000c289a1829a808ac09c00daf10bc3c4e223b | jack johnson | 227 |
25 | 00000c289a1829a808ac09c00daf10bc3c4e223b | eluveitie | 222 |
26 | 00000c289a1829a808ac09c00daf10bc3c4e223b | rasputina | 220 |
27 | 00000c289a1829a808ac09c00daf10bc3c4e223b | london after midnight | 210 |
28 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the killers | 208 |
29 | 00000c289a1829a808ac09c00daf10bc3c4e223b | mutyumu | 205 |
30 | 00000c289a1829a808ac09c00daf10bc3c4e223b | judas priest | 198 |
31 | 00000c289a1829a808ac09c00daf10bc3c4e223b | rob zombie | 198 |
32 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the bosshoss | 189 |
33 | 00000c289a1829a808ac09c00daf10bc3c4e223b | blue öyster cult | 185 |
34 | 00000c289a1829a808ac09c00daf10bc3c4e223b | sandra nasic | 183 |
35 | 00000c289a1829a808ac09c00daf10bc3c4e223b | john mayer | 182 |
36 | 00000c289a1829a808ac09c00daf10bc3c4e223b | sleater-kinney | 175 |
37 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the who | 168 |
38 | 00000c289a1829a808ac09c00daf10bc3c4e223b | disciple | 154 |
39 | 00000c289a1829a808ac09c00daf10bc3c4e223b | tanzwut | 153 |
40 | 00000c289a1829a808ac09c00daf10bc3c4e223b | guano apes | 151 |
41 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the rolling stones | 150 |
42 | 00000c289a1829a808ac09c00daf10bc3c4e223b | little big town | 145 |
43 | 00000c289a1829a808ac09c00daf10bc3c4e223b | team dresch | 137 |
44 | 00000c289a1829a808ac09c00daf10bc3c4e223b | betty | 135 |
45 | 00000c289a1829a808ac09c00daf10bc3c4e223b | l7 | 135 |
46 | 00000c289a1829a808ac09c00daf10bc3c4e223b | bif naked | 134 |
47 | 00000c289a1829a808ac09c00daf10bc3c4e223b | girlschool | 134 |
48 | 00000c289a1829a808ac09c00daf10bc3c4e223b | the wallflowers | 131 |
모르는 아티스트가 많다. 추천 시스템에서는 적용 분야에 대한 지식이 특히 더 중요한데, 모르면 검증을 못 하기 때문이다.
데이터 탐색¶
추천모델을 만들기 전에 데이터의 기본적인 정보를 보고 가자.
- 유저 수, 아티스트 수, 인기 많은 아티스트
- 유저들이 몇 명의 아티스트를 듣고 있는지에 대한 통계
- 유저 play 횟수 중앙값에 대한 통계
(참고) pandas.DataFrame.nunique()은 특정 컬럼에 포함된 유니크한 데이터의 개수를 알아보는데 유용하다.
# 유저 수
data['user_id'].nunique()
358869
# 아티스트 수
data['artist'].nunique()
291347
# 인기 많은 아티스트
artist_count = data.groupby('artist')['user_id'].count()
artist_count.sort_values(ascending=False).head(30)
artist 217 77254 247 76245 62 66659 5 48924 277 46954 283 45233 268 44443 28 41229 352 39773 910 39479 193 37267 75 34174 418 33206 1170 33001 776 32624 279 32295 473 32072 531 31916 780 31862 1092 31799 96 31482 55 30348 694 30144 201 29795 41 29754 773 28946 243 28901 503 28732 203 28691 615 28232 Name: user_id, dtype: int64
# 유저별 몇 명의 아티스트를 듣고 있는지에 대한 통계
user_count = data.groupby('user_id')['artist'].count()
user_count.describe()
count 358869.000000 mean 48.863680 std 8.524494 min 1.000000 25% 46.000000 50% 49.000000 75% 51.000000 max 166.000000 Name: artist, dtype: float64
# 유저별 play횟수 중앙값에 대한 통계
user_median = data.groupby('user_id')['play'].median()
user_median.describe()
count 358869.000000 mean 142.187364 std 213.089688 min 1.000000 25% 32.000000 50% 83.000000 75% 180.000000 max 50142.000000 Name: play, dtype: float64
모델 검증을 위한 사용자 초기 정보 세팅¶
우리가 만든 추천 시스템의 추후 검증을 위해, 내가 무엇을 좋아하는지 위 데이터셋에 5개 이상 추가해주어야 한다.
# 본인이 좋아하시는 아티스트 데이터로 바꿔서 추가하셔도 됩니다! 단, 이름은 꼭 데이터셋에 있는 것과 동일하게 맞춰주세요.
my_favorite = ['black eyed peas' , 'maroon5' ,'jason mraz' ,'coldplay' ,'beyoncé']
# 'zimin'이라는 user_id가 위 아티스트의 노래를 30회씩 들었다고 가정하겠습니다.
my_playlist = pd.DataFrame({'user_id': ['zimin']*5, 'artist': my_favorite, 'play':[30]*5})
if not data.isin({'user_id':['zimin']})['user_id'].any(): # user_id에 'zimin'이라는 데이터가 없다면
data = data.append(my_playlist) # 위에 임의로 만든 my_favorite 데이터를 추가해 줍니다.
data.tail(10) # 잘 추가되었는지 확인해 봅시다.
user_id | artist | play | |
---|---|---|---|
0 | 358868 | 376 | 30 |
1 | 358868 | 270115 | 30 |
2 | 358868 | 3746 | 30 |
3 | 358868 | 62 | 30 |
4 | 358868 | 396 | 30 |
0 | zimin | black eyed peas | 30 |
1 | zimin | maroon5 | 30 |
2 | zimin | jason mraz | 30 |
3 | zimin | coldplay | 30 |
4 | zimin | beyoncé | 30 |
모델을 활용하기 위한 전처리(실습)¶
데이터 관리를 용이하게 하기 위해 번호를 붙여주자.(indexing 작업)
(참고) pandas.DataFrame.unique()은 특정 컬럼에 포함된 유니크한 데이터만 모아 줍니다. indexing 작업을 위해 매우 유용하다.
# 고유한 유저, 아티스트를 찾아내는 코드
user_unique = data['user_id'].unique()
artist_unique = data['artist'].unique()
# 유저, 아티스트 indexing 하는 코드 idx는 index의 약자입니다.
user_to_idx = {v:k for k,v in enumerate(user_unique)}
artist_to_idx = {v:k for k,v in enumerate(artist_unique)}
# 인덱싱이 잘 되었는지 확인해 봅니다.
print(user_to_idx['zimin']) # 358869명의 유저 중 마지막으로 추가된 유저이니 358868이 나와야 합니다.
print(artist_to_idx['black eyed peas'])
358869 291347
# indexing을 통해 데이터 컬럼 내 값을 바꾸는 코드
# dictionary 자료형의 get 함수는 https://wikidocs.net/16 을 참고하세요.
# user_to_idx.get을 통해 user_id 컬럼의 모든 값을 인덱싱한 Series를 구해 봅시다.
# 혹시 정상적으로 인덱싱되지 않은 row가 있다면 인덱스가 NaN이 될 테니 dropna()로 제거합니다.
temp_user_data = data['user_id'].map(user_to_idx.get).dropna()
if len(temp_user_data) == len(data): # 모든 row가 정상적으로 인덱싱되었다면
print('user_id column indexing OK!!')
data['user_id'] = temp_user_data # data['user_id']을 인덱싱된 Series로 교체해 줍니다.
else:
print('user_id column indexing Fail!!')
# artist_to_idx을 통해 artist 컬럼도 동일한 방식으로 인덱싱해 줍니다.
temp_artist_data = data['artist'].map(artist_to_idx.get).dropna()
if len(temp_artist_data) == len(data):
print('artist column indexing OK!!')
data['artist'] = temp_artist_data
else:
print('artist column indexing Fail!!')
data
user_id column indexing OK!! artist column indexing OK!!
user_id | artist | play | |
---|---|---|---|
0 | 0 | 0 | 2137 |
1 | 0 | 1 | 1099 |
2 | 0 | 2 | 897 |
3 | 0 | 3 | 717 |
4 | 0 | 4 | 706 |
... | ... | ... | ... |
0 | 358869 | 291347 | 30 |
1 | 358869 | 291348 | 30 |
2 | 358869 | 291349 | 30 |
3 | 358869 | 291350 | 30 |
4 | 358869 | 291351 | 30 |
17535665 rows × 3 columns
전처리가 완료되었다.
사용자의 명시적/암묵적 평가¶
이 추천 시스템은 사용자들이 얼마나 아이템을 선호하는지를 모델링하기를 원한다. 그러기 위해서는 사용자의 아이템 선호도를 말해주는 유저 행동 데이터셋
이 필요하다. 서비스를 이용하면서 자연스럽게 발생하는 implicit한 피드백도 사용자의 아이템에 대한 평가를 알 수 있는 단서가 된다.
암묵적 피드백 데이터셋의 특징은 다음과 같다.
- 부정적인 피드백이 없다.(No Negative Feedback)
- 애초에 잡음이 많다.(Inherently Noisy)
- 수치는 신뢰도를 의미한다.(The numerical value of implicit feedback indicates confidence)
- Implicit-feedback Recommender System의 평가는 적절한 방법을 고민해봐야 한다.(Evaluation of implicit-feedback recommender requires appropriate measures)
암묵적 평가 예시: 플레이 수/시간, 클릭 수, 구매 여부, 스킵 여부 등
# 1회만 play한 데이터의 비율을 보는 코드
only_one = data[data['play']<2]
one, all_data = len(only_one), len(data)
print(f'{one},{all_data}')
print(f'Ratio of only_one over all data is {one/all_data:.2%}') # f-format에 대한 설명은 https://bit.ly/2DTLqYU
147740,17535665 Ratio of only_one over all data is 0.84%
암묵적 데이터 해석을 위해 다음과 같이 규칙을 적용하자.
- 한 번이라도 들었으면 선호한다고 판단
- 많이 재생한 아티스트에 가중치를 주어 더 좋아한다고 판단
Matirx Factorization(MF)¶
데이터는 준비가 되었고, 이제 모델을 만들자.
추천 시스템 모델들 중 Matrix Factorization(MF, 행렬 분해)
모델을 사용하자.
(m,n) size의 행렬 R을 (m,k) size행렬 P와 (k,n) size의 행렬 Q로 분해한다면 R이란 그저 P와 Q의 행렬곱으로 표현가능할 것이다. MF 모델은 성능이 준수하고 Scalability가 좋아서 많이 사용되는 모델이다.
[출처: CuMF_SGD: Fast and Scalable Matrix Factorization(https://arxiv.org/pdf/1610.05838.pdf)]
모델 목표는 모든 유저와 아이템에 대해 k-dimensional의 벡터를 만드는 것이다. 벡터를 잘 만드는 기준은 유저 i의 벡터 ($U_i$)와 아이템 j의 벡터($I_j$)를 내적했을 때 유저 i가 아이템 j에 대해 평가한 수치($M_ij$)와 비슷한지이다.
CSR(Compressd Sparse Row) Matrix¶
유저 x 아이템 평가행렬을 다시 생각해보자. 유저는 36만, 아티스트는 29만이다. 이를 행렬로 표현하고 행렬의 각 원소에 정수 1byte가 들어간다면 36만x29만x1byte = 97GB가 필요하다. 이들 중 대부분은 0으로 채워진다. 이는 메모리 낭비이며, 낭비를 최소화하기 위해서는 유저가 들어본 아티스트에 대해서만 정보만을 저장하면서 전체 행렬 형태를 유추할 수 있는 데이터 구조가 필요하다.
그에 대한 대안이 CSR matrix이다. 이는 sparse한 matrix에서 0이 아닌 유효한 데이터로 채워지는 데이터 값과 좌표 정보만으로 구성하여 메모리 사용량을 최소화하면서 Sparse한 matrix와 동일한 행렬을 표현하게 하는 데이터 구조이다.
CSR matrix는 data, indices, indptr로 행렬을 압축하여 표현한다.
data는 0이 아닌 원소를 차례로 기입한 값이다.
data = [1, 2, 3, 4, 5, 6]
indices는 data 각 요소가 어느 열에 있는지를 표현한 index이다.
indices = [0, 4, 1, 3, 0, 3]
indptr은 각 행에서 0이 아닌 첫 번째 원소가
data list에서 몇 번째에 해당하는지
와 마지막에 data 벡터의 길이를 추가한 값이다! 세 번째 행처럼 0이 아닌 원소가 없는 경우는 그 다음 행의 값과 동일한 값을 넣는다.indptr = [0, 2, 4, 4, 6]
이제 data를 CSR matrix로 바꿔보자.
csr_matrix를 만드는 방법은 다양하다.
출처: https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html
# 실습 위에 설명보고 이해해서 만들어보기
from scipy.sparse import csr_matrix
num_user = data['user_id'].nunique()
num_artist = data['artist'].nunique()
csr_data = csr_matrix((data.play, (data.user_id, data.artist)), shape= (num_user, num_artist))
csr_data
<358870x291352 sparse matrix of type '<class 'numpy.int64'>' with 17535583 stored elements in Compressed Sparse Row format>
MF model 학습하기¶
MF모델을 implicit
패키지를 사용해 학습해보자.
implicit
패키지는 implicit dataset을 사용하는 다양한 모델을 매우 빠르게 학습시키는 패키지이다.- 패키지에 구현된
als(AlternatingLeastSquares)
모델을 사용하자. MF에서 쪼개진 두 feature matrix를 동시에 훈련하는 것보다 더욱 빨리 수렴하여 효과적이라고 알려져있다.
from implicit.als import AlternatingLeastSquares
import os
import numpy as np
# implicit 라이브러리에서 권장하고 있는 부분입니다. 학습 내용과는 무관합니다.
os.environ['OPENBLAS_NUM_THREADS']='1'
os.environ['KMP_DUPLICATE_LIB_OK']='True'
os.environ['MKL_NUM_THREADS']='1'
AlternatingLeastSquares 클래스의 init 파라미터를 살펴보자.
- factors : 유저와 아이템의 벡터를 몇 차원으로 할 것인지
- regularization : 과적합을 방지하기 위해 정규화 값을 얼마나 사용할 것인지
- use_gpu : GPU를 사용할 것인지
- iterations : epochs와 같은 의미입니다. 데이터를 몇 번 반복해서 학습할 것인지
1과 4를 늘릴수록 학습 데이터를 잘 학습하지만, overfitting을 조심하자.
# Implicit AlternatingLeastSquares 모델의 선언
als_model = AlternatingLeastSquares(factors=100, regularization=0.01, use_gpu=False, iterations=15, dtype=np.float32)
# als 모델은 input으로 (item X user 꼴의 matrix를 받기 때문에 Transpose해줍니다.)
csr_data_transpose = csr_data.T
csr_data_transpose
<291352x358870 sparse matrix of type '<class 'numpy.int64'>' with 17535583 stored elements in Compressed Sparse Column format>
# 모델 훈련
als_model.fit(csr_data_transpose)
0%| | 0/15 [00:00<?, ?it/s]
이제 2가지 사항을 살펴보자.
- zimin 벡터와 black eyed peas의 벡터를 어떻게 만들고 있는지
- 두 벡터를 곱하면 어떤 값이 나오는지
zimin, black_eyed_peas = user_to_idx['zimin'], artist_to_idx['black eyed peas']
zimin_vector, black_eyed_peas_vector = als_model.user_factors[zimin], als_model.item_factors[black_eyed_peas]
print('슝=3')
슝=3
zimin_vector
array([-0.00063334, 0.00192642, 0.00453873, -0.00098727, 0.00591053, -0.00526884, -0.0004744 , 0.01345678, 0.01203798, 0.01179077, -0.00383176, -0.01278549, -0.02120865, -0.00253777, 0.00729255, -0.01060826, -0.01639587, -0.00364366, 0.01616966, 0.01778094, -0.00960981, 0.00053605, -0.0242309 , -0.01237026, -0.01710715, 0.02617447, 0.00301509, -0.01147097, 0.01217436, 0.01010843, 0.00623398, 0.01295614, -0.00346757, 0.00490132, -0.01090054, 0.01172428, 0.00490648, 0.00382358, -0.01462624, -0.01302529, -0.00342637, -0.00374796, -0.00605203, -0.00209778, -0.01278877, -0.01485959, -0.00185056, -0.02337673, 0.02433301, 0.01203286, 0.00947518, 0.00880573, 0.00095233, -0.00241017, -0.01966149, -0.00555366, 0.02116205, -0.00932224, -0.00693795, -0.01239852, 0.00420673, 0.00713461, 0.00394419, 0.01633045, 0.01448909, 0.01861091, 0.0072172 , -0.00402183, 0.00264846, 0.00671749, 0.00346969, 0.00251347, -0.02013172, -0.00046677, 0.01248993, 0.0009399 , -0.00095685, 0.00694175, -0.0084632 , 0.01077261, 0.00140172, 0.01220554, -0.01377176, -0.00146397, 0.00484889, 0.00842849, 0.00141971, 0.0058502 , -0.01114579, 0.01822758, -0.01908236, -0.00896715, 0.00077255, -0.00170172, -0.00500479, 0.00684493, 0.00098241, -0.00591149, 0.01040074, 0.01011114], dtype=float32)
black_eyed_peas_vector
array([ 1.84955075e-02, 3.12648085e-03, 2.73260754e-02, 1.13091385e-02, 1.30027216e-02, 7.12492270e-03, -1.05624497e-02, 1.99825671e-02, -5.08196605e-03, -1.23252897e-02, 1.30540943e-02, -7.23074772e-05, 1.41553693e-02, 2.26158239e-02, -3.25573928e-04, 1.07180502e-03, -1.40532537e-03, 8.83621629e-03, 3.70181329e-03, 2.03704052e-02, 1.27853388e-02, 1.31191574e-02, 2.23001484e-02, -2.63541471e-03, -1.35505386e-02, 3.53613906e-02, 1.68826263e-02, 8.14550556e-03, -9.94398631e-03, 3.83757148e-03, 4.09717532e-03, 2.29453086e-03, 1.07086515e-02, 1.10192066e-02, 3.70370131e-03, 4.91675083e-03, 2.05083285e-02, -1.26654850e-02, 1.43576358e-02, 8.80754739e-03, 8.76171142e-03, -1.63480770e-02, -5.93769643e-03, 2.57273484e-03, 3.07619832e-02, 1.19472984e-02, -9.45035648e-03, 1.37791382e-02, 2.94578541e-03, 6.51798025e-03, 2.35960842e-03, 9.94806085e-03, 2.02198662e-02, -7.30344187e-03, 7.55541213e-03, 1.91564206e-02, 2.12366674e-02, 3.63464165e-03, -5.58858458e-03, 1.20594632e-02, 1.73738450e-02, 2.07411014e-02, 1.79225635e-02, 5.18441340e-03, -2.72314381e-02, 1.95040461e-03, -2.16548331e-03, 1.27882278e-02, 2.18157331e-03, -9.68825072e-03, 9.39695723e-03, 1.08454295e-03, -1.13498084e-02, 2.50969548e-02, 1.43998908e-03, 2.68599279e-02, 1.02109211e-02, 1.50131509e-02, 1.19618485e-02, 1.39194401e-02, 2.04474609e-02, 7.15282978e-03, -4.63017495e-03, -1.98030984e-03, 1.87525656e-02, 1.03000635e-02, 1.68708395e-02, 8.60676449e-03, 1.26077561e-02, 2.54569342e-03, 1.70822460e-02, 1.54243328e-03, 2.78498582e-03, -4.36118245e-03, 1.29431225e-02, 9.14802495e-03, 4.56131747e-05, 9.72313527e-03, 1.19166048e-02, 2.47687008e-02], dtype=float32)
# zimin과 black_eyed_peas를 내적하는 코드
np.dot(zimin_vector, black_eyed_peas_vector)
0.50302196
1이 나와야 하는데, 한참 낮은 수치가 나왔다. factors/iterations를 늘려보자.
이제, 모델이 queen에 대한 선호도를 어떻게 예측하는지 살펴보자.
queen = artist_to_idx['queen']
queen_vector = als_model.item_factors[queen]
np.dot(zimin_vector, queen_vector)
0.32252192
#coldplay와 비슷한 artist 찾아보기
favorite_artist = 'coldplay'
artist_id = artist_to_idx[favorite_artist]
similar_artist = als_model.similar_items(artist_id, N=15)
similar_artist
[(62, 0.9999999), (277, 0.99044037), (28, 0.98205984), (5, 0.97932976), (217, 0.9717016), (490, 0.97122943), (473, 0.9710707), (418, 0.96607286), (247, 0.96601087), (694, 0.95595264), (910, 0.95325226), (55, 0.95286906), (1018, 0.9461631), (268, 0.94124097), (782, 0.9389919)]
(아티스트id, 유사도)를 튜플로 반환하고 있다. 아티스트의 id를 다시 아티스트의 이름으로 매핑하자.
#artist_to_idx 를 뒤집어, index로부터 artist 이름을 얻는 dict를 생성합니다.
idx_to_artist = {v:k for k,v in artist_to_idx.items()}
[idx_to_artist[i[0]] for i in similar_artist]
['coldplay', 'muse', 'the killers', 'red hot chili peppers', 'radiohead', 'oasis', 'placebo', 'u2', 'the beatles', 'foo fighters', 'nirvana', 'arctic monkeys', 'the smashing pumpkins', 'pink floyd', 'the white stripes']
이를 함수로 만들고 확인하자.
def get_similar_artist(artist_name: str):
artist_id = artist_to_idx[artist_name]
similar_artist = als_model.similar_items(artist_id)
similar_artist = [idx_to_artist[i[0]] for i in similar_artist]
return similar_artist
print("슝=3")
슝=3
유저에게 아티스트 추천하기¶
recommend
메서드를 통해 좋아할 아티스트를 추천받자. filter_already_liked_items
는 유저가 이미 평가한 아이템을 제외하는 Argument이다.
user = user_to_idx['zimin']
# recommend에서는 user*item CSR Matrix를 받습니다.
artist_recommended = als_model.recommend(user, csr_data, N=20, filter_already_liked_items=True)
[idx_to_artist[i[0]] for i in artist_recommended]
['rihanna', 'britney spears', 'justin timberlake', 'maroon 5', 'lady gaga', 'nelly furtado', 'amy winehouse', 'madonna', 'michael jackson', 'kanye west', 'mika', 'lily allen', 'christina aguilera', 'timbaland', 'jack johnson', 'katy perry', 'pink', 'the killers', 'red hot chili peppers', 'muse']
왜 rihanna를 추천해줄까? ALS 클래스에 구현된 explain
메서드를 사용하면 기록을 남긴 데이터 중 이 추천에 기여한 정도를 확인할 수 있다.
rihanna = artist_to_idx['rihanna']
explain = als_model.explain(user, csr_data, itemid=rihanna)
[(idx_to_artist[i[0]], i[1]) for i in explain[1]]
[('beyoncé', 0.2256409607588373), ('black eyed peas', 0.14442788520524902), ('coldplay', 0.04388536532175796), ('jason mraz', 0.039566288926108834), ('maroon5', -0.00024309868724310384)]
filter_already_liked_items = True
를 했지만 이미 평가한 maroon5가 나왔다. 이는 implicit 0.4.2 버전에서 생긴 버그이다. 이렇게 유명한 패키지의 오류를 고쳐 Pull Request하여 contribute하면 좋은 이력이 되기도 한다.
마무리¶
몇가지 아쉬운 점이 있다.
- 유저, 아티스트에 대한 Meta 정보를 반영하기 쉽지 않다. 연령대별로 음악 취향이 다를 수도 있다.
- 유저가 언제 play 했는지에 반영하기 쉽지 않다. 10년 전에 들은 아티스트와 지금 듣는 아티스트를 비교해보자.
'Computer Science > AI Exploration' 카테고리의 다른 글
[E-15] OCR (0) | 2022.02.25 |
---|---|
[E-13] Bert (0) | 2022.02.22 |
[E-12] Transformer, Chatbot (0) | 2022.02.22 |
[E-11] Stock prediction (0) | 2022.02.22 |
[E-10] Generative Modeling (0) | 2022.02.22 |