Created
October 6, 2017 22:32
-
-
Save dmvieira/c518f33070297048a4127921f3b933d0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pandas as pd | |
import scipy.sparse as sparse | |
import numpy as np | |
from scipy.sparse.linalg import spsolve | |
header = ['user_id', 'item_id', 'rating', 'timestamp'] | |
df = pd.read_csv('ml-100k/u.data', sep='\t', names=header) | |
n_users = df.user_id.unique().shape[0] | |
n_items = df.item_id.unique().shape[0] | |
del df['timestamp'] | |
df.head() | |
import random | |
def make_train(ratings, pct_test = 0.2, seed=0): | |
''' | |
Essa função pega a matriz user-item e "mascara" uma porcentagem original de ratings para validar previsões depois. | |
''' | |
test_set = ratings.copy() | |
test_set[test_set != 0] = 1 | |
training_set = ratings.copy() | |
nonzero_inds = training_set.nonzero() | |
nonzero_pairs = list(zip(nonzero_inds[0], nonzero_inds[1])) | |
random.seed(seed) # Define um seed pra poder reproduzir | |
num_samples = int(np.ceil(pct_test*len(nonzero_pairs))) | |
samples = random.sample(nonzero_pairs, num_samples) # Pega amostras aleatórias | |
user_inds = [index[0] for index in samples] # Pega linha aleatória (usuário) | |
item_inds = [index[1] for index in samples] # Pega coluna aleatória (item) | |
training_set[user_inds, item_inds] = 0 # Remove todos os ratings sorteados | |
training_set.eliminate_zeros() # Remove zeros pra economizar espaço | |
return training_set, test_set, list(set(user_inds)) | |
train, test, altered = make_train(sparse.csr_matrix(df.values), pct_test = 0.2) | |
def implicit_weighted_ALS(training_set, lambda_val = 0.1, alpha = 40, iterations = 10, rank_size = 20, seed = 0): | |
''' | |
Hu, Koren, and Volinsky 2008 | |
parameters: | |
training_set - Matrix de ratings m x n, aonde m é o número de usuários e n é o número de itens. | |
Bom usar matrix esparsa com CSR pra economizar espaço. | |
lambda_val - Fator de regularização. Aumentando ele diminui a variância, mas aumenta o bias (falta de significado). | |
Default é 0.1. | |
alpha - Parâmetro associado com a matrix de confiança. Diminuindo ele, diminui a variabilidade entre os ratings. | |
O paper sugere 40. | |
iterations - Número de vezes que vamos iterar sobre a matrix de usuários e de itens de forma alternada. | |
O paper sugere 10, mas as vezes precisa de mais. | |
rank_size - Número de features que vamos tirar do vetor user/item. O paper recomenda variar de 20-200. | |
Aumentando as features melhora a acurácia, mas pode diminuir o bias. | |
seed - Seed para matriz aleatória inicial | |
retorna: | |
Vetores de feature de usuários e itens | |
''' | |
conf = (alpha*training_set) # matriz de confiança. | |
num_user = conf.shape[0] | |
num_item = conf.shape[1] | |
# inicializar vetores X/Y de feature aleatoriamente com seed | |
rstate = np.random.RandomState(seed) | |
X = sparse.csr_matrix(rstate.normal(size = (num_user, rank_size))) | |
Y = sparse.csr_matrix(rstate.normal(size = (num_item, rank_size))) | |
X_eye = sparse.eye(num_user) # Matrix com 1 na diagonal para usuarios | |
Y_eye = sparse.eye(num_item) # Matrix com 1 na diagonal para itens | |
lambda_eye = lambda_val * sparse.eye(rank_size) # aplicando termo de regularização lambda*I. | |
# Começa iterações | |
for iter_step in range(iterations): # Solucionar X e Y | |
# Computa matrizes acessórias yTy e xTx no início | |
yTy = Y.T.dot(Y) | |
xTx = X.T.dot(X) | |
# Solucionar X baseado num Y fixo | |
for u in range(num_user): | |
conf_samp = conf[u,:].toarray() # Convertendo matriz pra um array denso | |
pref = conf_samp.copy() | |
pref[pref != 0] = 1 # Cria vetor binário de preferência (deu rating ou não) | |
CuI = sparse.diags(conf_samp, [0]) | |
yTCuIY = Y.T.dot(CuI).dot(Y) | |
yTCupu = Y.T.dot(CuI + Y_eye).dot(pref.T) | |
X[u] = spsolve(yTy + yTCuIY + lambda_eye, yTCupu) | |
# Resolve para Xu = ((yTy + yT(Cu-I)Y + lambda*I)^-1)yTCuPu, equação 4 do paper | |
# Solucionar Y baseado num X fixo | |
for i in range(num_item): | |
conf_samp = conf[:,i].T.toarray() # transpondo pra ficar no formato de linhas e convertendo pra um array denso | |
pref = conf_samp.copy() | |
pref[pref != 0] = 1 # Cria vetor binário de preferência (tem rating ou não) | |
CiI = sparse.diags(conf_samp, [0]) | |
xTCiIX = X.T.dot(CiI).dot(X) | |
xTCiPi = X.T.dot(CiI + X_eye).dot(pref.T) | |
Y[i] = spsolve(xTx + xTCiIX + lambda_eye, xTCiPi) | |
# Resolve para Yi = ((xTx + xT(Cu-I)X) + lambda*I)^-1)xTCiPi, equação 5 do paper | |
return X, Y.T # Transpõe Y para voltar a ter as dimensões de item e retorna 2 vetores de features (usuários e itens). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment