#cd /content/drive/MyDrive/네카라쿠배/최종 프로젝트/code
#ls
#!pip install nbformat
# Module import, Pre-Work
from collections import Counter
import numpy as np
import pandas as pd
import scipy.sparse as spr
import pickle
# 결과값 저장하기 위한 write_json 함수 작성
# arena_util.py
# -*- coding: utf-8 -*-
import io
import os
import json
import distutils.dir_util
from collections import Counter
import numpy as np
def write_json(data, fname):
def _conv(o):
if isinstance(o, np.int64) or isinstance(o, np.int32):
return int(o)
raise TypeError
parent = os.path.dirname(fname)
distutils.dir_util.mkpath("../data/" + parent)
with io.open("../data/" + fname, "w", encoding="utf8") as f:
json_str = json.dumps(data, ensure_ascii=False, default=_conv)
f.write(json_str)
def load_json(fname):
with open(fname, encoding='utf8') as f:
json_obj = json.load(f)
return json_obj
def debug_json(r):
print(json.dumps(r, ensure_ascii=False, indent=4))
# Load Data
ls
song_meta = pd.read_json("../data/song_meta.json")
train = pd.read_json("../data/train.json")
test = pd.read_json("../data/val.json")
playlist, song, tag의 id(각각 nid, sid, tid)를 새로 생성하는 이유는, 새로 생성할 id를 matrix의 row, column index로 사용할 것이기 때문입니다.
# train + test => new train
plylst = pd.concat([train, test], ignore_index=True)
n_plylst = len(plylst)
# playlist id
plylst["nid"] = range(n_plylst)
# id <-> nid
plylst_id_nid = dict(zip(plylst["id"],plylst["nid"]))
plylst_nid_id = dict(zip(plylst["nid"],plylst["id"]))
plylst_tag = plylst['tags']
tag_counter = Counter([tg for tgs in plylst_tag for tg in tgs])
tag_dict = {x: tag_counter[x] for x in tag_counter}
tag_id_tid = dict()
tag_tid_id = dict()
for i, t in enumerate(tag_dict):
tag_id_tid[t] = i
tag_tid_id[i] = t
n_tags = len(tag_dict)
plylst_song = plylst['songs']
song_counter = Counter([sg for sgs in plylst_song for sg in sgs])
song_dict = {x: song_counter[x] for x in song_counter}
song_id_sid = dict()
song_sid_id = dict()
for i, t in enumerate(song_dict):
song_id_sid[t] = i
song_sid_id[i] = t
n_songs = len(song_dict)
# plylst의 songs와 tags를 새로운 id로 변환하여 DataFrame에 추가합니다
plylst['songs_id'] = plylst['songs'].map(lambda x: [song_id_sid.get(s) for s in x if song_id_sid.get(s) != None])
plylst['tags_id'] = plylst['tags'].map(lambda x: [tag_id_tid.get(t) for t in x if tag_id_tid.get(t) != None])
plylst_use = plylst[['nid','updt_date','songs_id','tags_id']]
plylst_use.loc[:,'num_songs'] = plylst_use['songs_id'].map(len)
plylst_use.loc[:,'num_tags'] = plylst_use['tags_id'].map(len)
plylst_use = plylst_use.set_index('nid')
plylst_train = plylst_use.copy()
plylst_train.head()
row = np.repeat(range(n_plylst), plylst_train['num_songs'])
col = [song for songs in plylst_train['songs_id'] for song in songs]
dat = np.repeat(1, plylst_train['num_songs'].sum())
train_songs_A = spr.csr_matrix((dat, (row, col)), shape=(n_plylst, n_songs))
row = np.repeat(range(n_plylst), plylst_train['num_tags'])
col = [tag for tags in plylst_train['tags_id'] for tag in tags]
dat = np.repeat(1, plylst_train['num_tags'].sum())
train_tags_A = spr.csr_matrix((dat, (row, col)), shape=(n_plylst, n_tags))
#전치행렬이 필요없는것으로 판단되어 코드 제거
#train_songs_A_T = train_songs_A.T.tocsr()
#train_tags_A_T = train_tags_A.T.tocsr()
train_songs_A
1 - (5707070/(138086*638336))
# Sparsity: 얼마나 비어있나?
# Sparsity of plylst x songs matrix
matrix_size = train_songs_A.shape[0]* train_songs_A.shape[1]
num_songs = len(train_songs_A.nonzero()[0])
sparsity = 100 * (1 - (num_songs / matrix_size))
sparsity
train_tags_A
1 - (503669/(138086*30197))
# Sparsity: 얼마나 비어있나?
# Sparsity of plylst x tags matrix
matrix_size = train_tags_A.shape[0]* train_tags_A.shape[1]
num_tags = len(train_tags_A.nonzero()[0])
sparsity = 100 * (1 - (num_tags / matrix_size))
sparsity
# 첫번째 한계점 발생 : sparsity가 대략 99.5% 이하 수준일 때까지 Collaborative Filtering이 효과가 있는데, 우리의 데이터는 99.9%를 웃돈다..
train set에서 일부를 가려 test set을 생성할 것이다.
기존의 val.json의 songs과 tags는 일부가 가려져있다.
그렇다면 이 상황에서,
import random
def make_train (matrix, percentage = .2):
'''
-----------------------------------------------------
설명
유저-아이템 행렬 (matrix)에서
1. 0 이상의 값을 가지면 1의 값을 갖도록 binary하게 테스트 데이터를 만들고
2. 훈련 데이터는 원본 행렬에서 percentage 비율만큼 0으로 바뀜
-----------------------------------------------------
반환
training_set: 훈련 데이터에서 percentage 비율만큼 0으로 바뀐 행렬
test_set: 원본 유저-아이템 행렬의 복사본
user_inds: 훈련 데이터에서 0으로 바뀐 유저의 index
'''
test_set = matrix.copy()
test_set[test_set !=0] = 1 # binary하게 만들기
training_set = matrix.copy()
nonzero_inds = training_set.nonzero()
nonzero_pairs = list(zip(nonzero_inds[0], nonzero_inds[1]))
random.seed(0)
num_samples = int(np.ceil(percentage * len(nonzero_pairs)))
samples = random.sample(nonzero_pairs, num_samples)
user_inds = [index[0] for index in samples]
item_inds = [index[1] for index in samples]
training_set[user_inds, item_inds] = 0
training_set.eliminate_zeros()
return training_set, test_set, list(set(user_inds))
# 훈련, 테스트 데이터 생성
train_songs_A, test_songs_A, product_users_altered = make_train(train_songs_A, 0.2)
train_tags_A, test_tags_A, product_users_altered = make_train(train_tags_A, 0.2)
# 훈련데이터는 일부분의 1이 0으로 가려지고, 테스트 데이터는 가려지지 않은 데이터
# 훈련데이터로 학습하고 테스트 데이터로 성능을 평가하기 위한 방법
- train_songs_A 와 test_songs_A의 비교
train_songs_A
test_songs_A
# train_songs_A 의 Sparsity
1 - (4565656/(138086*638336))
# from scipy.sparse.linalg import spsolve
'''
def implicit_weighted_ALS(training_set, lambda_val =.1, alpha = 40, n_iter = 10, rank_size = 20, seed = 0):
'''
# 협업 필터링에 기반한 ALS
# -----------------------------------------------------
# input
# 1. training_set : m x n 행렬로, m은 유저 수, n은 아이템 수를 의미. csr 행렬 (희소 행렬) 형태여야 함
# 2. lambda_val: ALS의 정규화 term. 이 값을 늘리면 bias는 늘지만 분산은 감소. default값은 0.1
# 3. alpha: 신뢰 행렬과 관련한 모수 (C_{ui} = 1 + alpha * r_{ui}). 이를 감소시키면 평점 간의 신뢰도의 다양성이 감소
# 4. n_iter: 반복 횟수
# 5. rank_size: 유저/ 아이템 특성 벡터의 잠재 특성의 개수. 논문에서는 20 ~ 200 사이를 추천하고 있음. 이를 늘리면 과적합 위험성이 있으나
# bias가 감소
# 6. seed: 난수 생성에 필요한 seed
# -----------------------------------------------------
# 반환
# 유저와 아이템에 대한 특성 벡터
'''
# 1. Confidence matrix
# C = 1+ alpha * r_{ui}
conf = (alpha*training_set) # sparse 행렬 형태를 유지하기 위해서 1을 나중에 더함
num_user = conf.shape[0]
num_item = conf.shape[1]
# X와 Y 초기화
rstate = np.random.RandomState(seed)
X = spr.csr_matrix(rstate.normal(size = (num_user, rank_size)))
Y = spr.csr_matrix(rstate.normal(size = (num_item, rank_size)))
X_eye = spr.eye(num_user)
Y_eye = spr.eye(num_item)
# 정규화 term: 𝝀I
lambda_eye = lambda_val * spr.eye (rank_size)
# 반복 시작
for i in range(n_iter):
yTy = Y.T.dot(Y)
xTx = X.T.dot(X)
# Y를 고정해놓고 X에 대해 반복
# Xu = (yTy + yT(Cu-I)Y + 𝝀I)^{-1} yTCuPu
for u in range(num_user):
conf_samp = conf[u,:].toarray() # Cu
pref = conf_samp.copy()
pref[pref!=0] = 1
# Cu-I: 위에서 conf에 1을 더하지 않았으니까 I를 빼지 않음
CuI = spr.diags(conf_samp, [0])
# yT(Cu-I)Y
yTCuIY = Y.T.dot(CuI).dot(Y)
# yTCuPu
yTCupu = Y.T.dot(CuI+Y_eye).dot(pref.T)
X[u] = spsolve(yTy + yTCuIY + lambda_eye, yTCupu)
# X를 고정해놓고 Y에 대해 반복
# Yi = (xTx + xT(Cu-I)X + 𝝀I)^{-1} xTCiPi
for i in range(num_item):
conf_samp = conf[:,i].T.toarray()
pref = conf_samp.copy()
pref[pref!=0] = 1
#Ci-I
CiI = spr.diags (conf_samp, [0])
# xT(Ci-I)X
xTCiIX = X.T.dot(CiI).dot(X)
# xTCiPi
xTCiPi = X.T.dot(CiI+ X_eye).dot(pref.T)
Y[i] = spsolve(xTx + xTCiIX + lambda_eye, xTCiPi)
return X, Y.T
'''
# 1회 반복 수행해보기
# lambda = 0.1, alpha = 30, latent dimension = 100
user_vecs, item_vecs = implicit_weighted_ALS(train_songs_A, lambda_val = 0.1, alpha = 30, n_iter = 1, rank_size = 100)
# 계산량이 상당히 많아서 그런지, 1회 반복임에도 오래걸렸다. (~분)
# 이를 해결하기 위해 implicit module을 이용한다고 한다.
# 병렬처리가
# 첫번째 플레이리스트의 벡터 확인해보기
first = user_vecs[0].dot(item_vecs).toarray() # 1x
first[0,:5]
#pip install --upgrade pip setuptools wheel
#!pip install implicit
- alpha 값이 parameter로 지정 할 수 없기 때문에, 따로 지정해줘서 sparse matrix와 곱한 결과 인자로 넣어줘야함 (train_songs_A * alpha)
- sparse matrix의 자료형이 double이어야함 (상관 없는 것 같다)
#Building the model - plylst x song
model = implicit.als.AlternatingLeastSquares(factors=200, regularization=0.1, iterations=1, random_state=13)
alpha_val = 30
data_conf = (train_songs_A * alpha_val) # double이나 int나 거의 비슷한 결과
model.fit(data_conf.T)
#Building the model - plylst x song
model2 = implicit.als.AlternatingLeastSquares(factors=30, regularization=0.1, iterations=1, random_state=13)
alpha_val = 30
data_conf = (train_tags_A * alpha_val) # double이나 int나 거의 비슷한 결과
model2.fit(data_conf.T)
### 모델 만드는 함수 build_model, 모델 적합 함수 fit_model 만들기
import implicit
def build_model(factors=200, regularization=0.1, iteration=100, random_state=13):
model = implicit.als.AlternatingLeastSquares(factors=factors, regularization=regularization,
iterations=iteration, random_state=random_state)
return model
def fit_model(model, ui_sparse_matrix, alpha = 40):
#global model
data_conf = (ui_sparse_matrix * alpha)
model.fit(data_conf.T)
alpha : 신뢰 행렬 만들 때의 파라미터, 플레이리스트에 노래의 존재 여부에 대한 스케일링 term이고 평점간의 신뢰도의 다양성과 정비례
factors : 잠재행렬의 차원의 수 (줄이고 싶은 차원의 수)
regularization : The regularization factor to use
iterations : 반복 수
random_state : 초기 값 설정시 random seed
def recommend(nid, song_model, tag_model):
rec_sid = song_model.recommend(nid,test_songs_A, N = 300)
rec_tid = tag_model.recommend(nid,test_tags_A, N = 30)
exist_sid = plylst_train.loc[nid]['songs_id']
exist_tid = plylst_train.loc[nid]['tags_id']
final_rec_songs = []
final_rec_tags = []
for sid, score in rec_sid:
if sid not in exist_sid:
final_rec_songs.append(song_sid_id[sid])
if len(final_rec_songs) == 100:
break
for tid, score in rec_tid:
if tid not in exist_tid:
final_rec_tags.append(tag_tid_id[tid])
if len(final_rec_tags) == 10:
break
return plylst_nid_id[nid], final_rec_songs, final_rec_tags
def recommend_songs(nid, song_model):
rec_sid = song_model.recommend(nid,test_songs_A, N = 300)
exist_sid = plylst_train.loc[nid]['songs_id']
final_rec_songs = []
for sid, score in rec_sid:
if sid not in exist_sid:
final_rec_songs.append(song_sid_id[sid])
if len(final_rec_songs) == 100:
break
return plylst_nid_id[nid], final_rec_songs
def recommend_tags(nid, tag_model):
rec_tid = tag_model.recommend(nid,test_tags_A, N = 30)
exist_tid = plylst_train.loc[nid]['tags_id']
final_rec_tags = []
for tid, score in rec_tid:
if tid not in exist_tid:
final_rec_tags.append(tag_tid_id[tid])
if len(final_rec_tags) == 10:
break
return plylst_nid_id[nid], final_rec_tags
def recommend_ver2(nid, song_model):
rec_sid = song_model.recommend(nid,test_songs_A, N = 300)
sim_user_idxs = song_model.similar_users(nid, N = 500)
exist_sid = plylst_train.loc[nid]['songs_id']
exist_tid = plylst_train.loc[nid]['tags_id']
final_rec_songs = []
final_rec_tags_tid = []
# 노래 추천
for sid, score in rec_sid:
if sid not in exist_sid:
final_rec_songs.append(song_sid_id[sid])
if len(final_rec_songs) == 100:
break
# 태그 추천
for sim_nid, score in sim_user_idxs[1:]: # tid (중복제외) 10개를 추천 받아보자
rec_tids = plylst_train.loc[sim_nid,'tags_id']
for each in rec_tids:
if (each not in exist_tid): # 기존 플레이리스트에 없다면,
final_rec_tags_tid.extend(rec_tids) # 추천 플레이리스트(tid로 이루어진)에 넣어라
final_rec_tags_tid = set(final_rec_tags_tid) # 중복 제거
final_rec_tags_tid = list(final_rec_tags_tid) # 중복 제거
if len(final_rec_tags_tid) > 10:
final_rec_tags_tid = final_rec_tags_tid[:10]
break
final_rec_tags = [] # tid를 실제 tag이름으로 바꿔주자
for each in final_rec_tags_tid:
final_rec_tags.append(tag_tid_id[each])
return plylst_nid_id[nid], final_rec_songs, final_rec_tags
def recommend_tags_ver2(nid, song_model):
sim_user_idxs = song_model.similar_users(nid, N = 500)
exist_tid = plylst_train.loc[nid]['tags_id']
final_rec_tags_tid = []
for sim_nid, score in sim_user_idxs[1:]: # tid (중복제외) 10개를 추천 받아보자
rec_tids = plylst_train.loc[sim_nid,'tags_id']
final_rec_tags_tid.extend(rec_tids)
final_rec_tags_tid = set(final_rec_tags_tid) # 중복 제거
final_rec_tags_tid = list(final_rec_tags_tid) # 중복 제거
if len(final_rec_tags_tid) > 10:
final_rec_tags_tid = final_rec_tags_tid[:10]
break
final_rec_tags = [] # tid를 실제 tag이름으로 바꿔주자
for each in final_rec_tags_tid:
final_rec_tags.append(tag_tid_id[each])
return plylst_nid_id[nid], final_rec_tags
song_model = build_model(factors = 2500, iteration = 5)
tag_model = build_model(factors = 30, iteration = 30)
song_model, tag_model
fit_model(song_model, test_songs_A, alpha = 100)
fit_model(tag_model, test_tags_A, alpha = 80)
song_model.user_factors.shape, song_model.item_factors.shape
np.save('../data/user_factors_1000.npy', song_model.user_factors)
np.save('../data/item_factors_1000.npy', song_model.item_factors)
sum(song_model.user_factors[0] * song_model.item_factors[67])
### 플레이리스트에 곡과 태그 추천해주기
from tqdm import tqdm
answer = []
for idx in tqdm(range(115071,138086)): # test nid start : 115071
result = recommend(idx, song_model, tag_model)
answer.append({
'id' : result[0],
'songs' : result[1],
'tags' : result[2]
})
# tag 하이퍼 파라미터만 변경해서 따로 작업했을 때
for idx in tqdm(range(115071,138086)):
tag_result = recommend_tags_ver2(idx, song_model)
answer[idx-115071]['tags'] = tag_result[1]
# simillar user 이용해서 tag 추천하는 recommend_ver2로 전체 결과 추천할 때
from tqdm import tqdm
answer = []
for idx in tqdm(range(115071,138086)): # test nid start : 115071
result = recommend_ver2(idx, song_model)
answer.append({
'id' : result[0],
'songs' : result[1],
'tags' : result[2]
})
# 노래 추천 잘 됐는지 확인
len_wrong_cnt = 0
len_unique_wrong_cnt = 0
wrong_idx = []
for i in range(len(answer)):
if len(answer[i]['songs']) != 100:
len_wrong_cnt += 1
wrong_idx.append({i : len(answer[i]['songs'])})
len(answer), len_wrong_cnt, wrong_idx
# 태그 추천 잘 됐는지 확인
len_wrong_cnt = 0
len_unique_wrong_cnt = 0
wrong_idx = []
for i in range(len(answer)):
if len(answer[i]['tags']) != 10:
len_wrong_cnt += 1
wrong_idx.append({i : len(answer[i]['tags'])})
len(answer), len_wrong_cnt, wrong_idx
plylst.iloc[115071,:]
answer[0]
import io
import os
import json
import distutils.dir_util
from collections import Counter
import numpy as np
def write_json(data, fname):
def _conv(o):
if isinstance(o, np.int64) or isinstance(o, np.int32):
return int(o)
raise TypeError
parent = os.path.dirname(fname)
distutils.dir_util.mkpath("../data/" + parent)
with io.open("../data/" + fname, "w", encoding="utf8") as f:
json_str = json.dumps(data, ensure_ascii=False, default=_conv)
f.write(json_str)
write_json(answer, "similar_user/f_2500/results.json")
ls
base_answer = load_json('/Users/jun/Documents/study/ds_study/code/results.json')
base_answer
base_answer[0]['tags']
for i in range(len(answer)):
answer[i]['tags'] = base_answer[i]['tags']
answer[0]['tags']
write_json(answer, "similar_user/best_result/results.json")
rec_tid = model2.recommend(2,train_tags_A, N = 30)
rec_tid[:10]
for tid,_ in rec_tid:
print(tag_tid_id[tid])
- 2번(nid)플레이리스트에 추천해줄만한 곡을 상위 10개만 찾아보자
rec_sid = model.recommend(2,train_songs_A, N = 300)
rec_sid[:10]
for sid, score in rec_sid[:10]:
print(song_sid_id[sid])
print(song_meta.loc[song_sid_id[sid]]['song_name'])
print(song_meta.loc[song_sid_id[sid]]['artist_name_basket'])
print(song_meta.loc[song_sid_id[each]]['song_gn_gnr_basket'])
print('------------------------')
- 그렇다면 현재 들어있는 2번(nid) 플레이리스트의 노래들을 찾아보자
plylst_nid_id[2] #원래 plylst id = 76951
exist_sid = plylst_train.loc[2]['songs_id'] #담겨있는 songs_sid
len(exist_sid), exist_sid[:5]
for each in exist_sid:
print(song_sid_id[each])
print(song_meta.loc[song_sid_id[each]]['song_name'])
print(song_meta.loc[song_sid_id[each]]['artist_name_basket'])
print(song_meta.loc[song_sid_id[each]]['song_gn_gnr_basket'])
print('------------------------')
song_meta.loc[song_sid_id[sid]]
# 115071 부터 test set에 대한 idx
plylst_nid_id[115071]
song_model.similar_users(115071,300)[-5:]
#plylst.loc[115071,:]
plylst_train.loc[72420,:]
안녕하세요 :) 혹시 어떤 환경에서 실행하셨는지 알 수 있을까요? 코랩 무료버전에서는 돌아가지 않네요 ㅠ