Implicit
라이브러리를 이용한 MF 기반 추천 모델 생성Q. 협업 필터링 vs 콘텐츠 기반 필터링
아이템 구매 이력 정보
만으로 사용자 간 유사성, 아이템 간 유사성 판단아이템 고유 정보
로 아이템 간 유사성 판단Q. 협업 필터링을 바로 사용할 수 없는 3가지 제약 조건
제약 조건
Q. 유튜브 뮤직에서의 첫화면에서 신규 접속 사용자에게 좋아하는 아티스트 5명 이상의 정보를 요구하는 이유는?
$ mkdir -p ~/aiffel/recommendata_iu/data/lastfm-dataset-360K
$ ln -s ~/data/lastfm-dataset-360K/* ~/aiffel/recommendata_iu/data/lastfm-dataset-360K
\t
read_csv
로도 충분히 파싱 가능$ more ~/aiffel/recommendata_iu/data/lastfm-dataset-360K/usersha1-artmbid-artname-plays.tsv
Q. 4가지 컬럼의 의미
user-mboxsha1 \t musicbrainz-artist-id \t artist-name \t plays
- user-mboxsha1 : 고유 유저 정보(User ID)
- musicbrainz-artist-id : 고유 아티스트 아이디(Artist MBID)
- artist-name : 아티스트 이름
- plays : 재생 횟수
col_names
로 적절하게 컬럼명 지정 필요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)
artist_MBID
는 관심사가 아니기 때문에 제거using_cols = ['user_id', 'artist', 'play']
data = data[using_cols]
data.head(10)
data['artist'] = data['artist'].str.lower()
data.head(10)
condition = (data['user_id']== data.loc[0, 'user_id'])
data.loc[condition]
- 유저 수, 아티스트 수, 인기가 많은 아티스트
- 유저들이 몇 명의 아티스트를 듣고 있는지에 대한 통계
- 유저 플레이 횟수 중앙값 통계
data['user_id'].nunique()
data['artist'].nunique()
artist_count = data.groupby('artist')['user_id'].count()
artist_count.sort_values(ascending=False).head(30)
user_count = data.groupby('user_id')['artist'].count()
user_count.describe()
user_median = data.groupby('user_id')['play'].median()
user_median.describe()
my_favorite = ['red hot chili peppers' , 'queen' ,'jason mraz' ,'coldplay' ,'beyoncé']
# 'hayan'이라는 user_id가 위 아티스트의 노래를 30회씩 들었다고 가정
my_playlist = pd.DataFrame({'user_id': ['hayan']*5, 'artist': my_favorite, 'play':[30]*5})
if not data.isin({'user_id':['hayan']})['user_id'].any():
data = pd.concat([data, my_playlist])
data.tail(10)
user_unique = data['user_id'].unique()
artist_unique = data['artist'].unique()
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['hayan'])
print(artist_to_idx['red hot chili peppers'])
user_to_idx.get
: user_id 컬럼 값을 인덱싱한 시리즈 구하기artist
컬럼 : artist_to_idx로 동일 방식 인덱싱 수행# user_to_idx.get을 통해 user_id 컬럼의 모든 값을 인덱싱한 Series 구하기
temp_user_data = data['user_id'].map(user_to_idx.get).dropna()
if len(temp_user_data) == len(data):
print('user_id column indexing OK!!')
data['user_id'] = temp_user_data
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
Q. 유저가 어떠한 아티스트의 곡을 1회만 들었다면, 이 유저에게 해당 아티스트와 관련된 사람을 추천해도 될지?
- 1회 들었으나, 그 평가가 긍정적일 수도 부정적일 수도 있기 때문에 애매한 지점이 있음
- 도메인 지식 + 직관이 활용되어야 하는 영역임
only_one = data[data['play']==1]
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%}')
k차원 벡터를 사용자 수만큼 모은 행렬
사용자의 영화 선호도 모델: 이 2개의 벡터를 내적해 얻은 0.88 ➡️ 으로 정의
유저 i의 벡터와 아이템 j의 벡터 내적
및 유저 i가 아이템 j에 대해 평가한 수치
와의 유사도벡터
& red hot chili peppers 벡터
의 곱 : 1에 가까워야 모델이 잘 학습한 것이라고 말할 수 있음!벡터
& 새로운 아티스트 벡터
의 곱으로 수치 예상이 가능해짐만약, 유저 재생 횟수를 맞혀야 할 경우
Q. 평가행렬의 용량이 많이 커지는 이유?
희소 행렬
- 많은 아티스트의 노래를 들었다고 하더라도, 아티스트가 29만 명에 달하기 때문에 평가 행렬 대부분이 0으로 채워짐
- 메모리 낭비 최소화 : 유저가 들어본 아티스트에 대한 정보만 저장 & 그러면서도 전체 행렬 형태는 유추가 가능해야 함
data = [1, 2, 3, 4, 5, 6]
indices = [0, 4, 1, 3, 0, 3]
indptr = [0, 2, 4, 4, 6]
Q. CSR Matrix를 (4,4) matrix로 변환
data = np.array([1,3,5,6,2,11,7,9,10,12])
indices = np.array([1,2,3,0,1,3,1,2,2,3])
indptr = np.array([0,3,6,8,10])
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
from implicit.als import AlternatingLeastSquares
import os
import numpy as np
# 기본 설정
os.environ['OPENBLAS_NUM_THREADS']='1'
os.environ['KMP_DUPLICATE_LIB_OK']='True'
os.environ['MKL_NUM_THREADS']='1'
factors
: 유저 및 아이템 벡터의 차원 수regularization
: 과적합 방지를 위한 정규화 값을 얼마나 사용하는지 결정use_gpu
: GPU 사용 여부iterations
: ==epoch(반복 학습 횟수)factors, iterations를 늘리면 -> 학습 데이터를 더 잘 학습하나, 과적합 우려가 커질 수 있음
# 모델 선언
als_model = AlternatingLeastSquares(factors=100, regularization=0.01, use_gpu=False, iterations=15, dtype=np.float32)
# Transpose
csr_data_transpose = csr_data.T
csr_data_transpose
# 모델 훈련
als_model.fit(csr_data_transpose)
hayan, red_hot_chili_peppers = user_to_idx['hayan'], artist_to_idx['red hot chili peppers']
hayan_vector, red_hot_chili_peppers_vector = als_model.user_factors[hayan], als_model.item_factors[red_hot_chili_peppers]
hayan_vector
red_hot_chili_peppers_vector
np.dot(hayan_vector, red_hot_chili_peppers_vector)
factors 혹은 iterations를 늘려서 1에 가까운 수치가 나온다면, 이 모델이 잘 학습되었다고 할 수 있는지?
- 검증되지 않음.
- fitting은 되었으나 이 모델이 보지 못한 데이터의 경우에는 예측 결과를 속단할 수 없기 때문
black_eyed_peas = artist_to_idx['black eyed peas']
black_eyed_peas_vector = als_model.item_factors[black eyed peas]
np.dot(hayan_vector, black_eyed_peas_vector)
radiohead = artist_to_idx['radiohead']
radiohead_vector = als_model.item_factors[radiohead]
np.dot(hayan_vector, radiohead_vector)
similar_items
favorite_artist = 'coldplay'
artist_id = artist_to_idx[favorite_artist]
similar_artist = als_model.similar_items(artist_id, N=15)
similar_artist
idx_to_artist = {v:k for k,v in artist_to_idx.items()}
[idx_to_artist[i[0]] for i in similar_artist]
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
2pac
으로 확인get_similar_artist('2pac')
Q. 마니아가 데이터에서 표현되는 특징?
- 특정 장르의 아티스트에게 선호도 집중
- 타 장르와는 선호도 낮은 경향
- 즉, 위의
get_similar_artist
가 마니아 유저일 경우 장르별 특성이 더 두드러지게 나타날 것
lady gaga
로 확인get_similar_artist('lady gaga')
recommend
filter_already_liked_items
user = user_to_idx['hayan']
# recommend의 경우 user*item CSR Matrix를 입력으로 받음
artist_recommended = als_model.recommend(user, csr_data, N=20, filter_already_liked_items=True)
artist_recommended
[idx_to_artist[i[0]] for i in artist_recommended]
muse
를 추천explain
메서드: 추천에 기여한 정도를 확인muse = artist_to_idx['muse']
explain = als_model.explain(user, csr_data, itemid=muse)
[(idx_to_artist[i[0]], i[1]) for i in explain[1]]
coldplay, red hot chili peppers 모두 밴드 그룹이기 때문에 유사한 밴드인 muse를 추천한 것으로 보임
filter_already_liked_items=True
로 설정하더라도 이미 있는 아티스트와 추천 아티스트가 겹치는 경우가 생김