Skip to content

Instantly share code, notes, and snippets.

@dmvieira
Created October 6, 2017 22:32
Show Gist options
  • Save dmvieira/c518f33070297048a4127921f3b933d0 to your computer and use it in GitHub Desktop.
Save dmvieira/c518f33070297048a4127921f3b933d0 to your computer and use it in GitHub Desktop.
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