Fatoração de Matrizes

A Fatoração de matrizes (Matrix Factorization) é uma técnica matemática amplamente utilizada em sistemas de recomendação, especialmente no filtro colaborativo (Collaborative Filtering), para prever as preferências dos usuários com base em suas interações com itens. Essa abordagem é crucial para plataformas de streaming, e-commerce e redes sociais, pois permite recomendações personalizadas.
A ideia principal é "quebrar" uma matriz de interações \(R\), com dimensões \([m,n]\), em duas matrizes menores que representam características latentes de usuários e itens:
$$\huge R≈U*V$$
- \(m\) é o número de usuários.
- \(n\) é o número de itens.
- A matriz \(U (de \ dimensões \ [m,k])\) Representa os usuários em um espaço de características latentes.
- A matriz \(V (de \ dimensões \ [k,n])\) Representa os itens em um espaço de características latentes.
O valor \(k\) define o número de fatores latentes, que capturam padrões implícitos nas interações entre usuários e itens.
Exemplo
Imagine um cenário futurista, em uma festa iluminada por luzes de néon suaves e um som ambiente agradável ao fundo. Robbie, um robô curioso e apaixonado por música, se encontra no centro de uma roda de conversa bastante peculiar. Ao seu redor, quatro figuras icônicas discutem sobre seus gostos musicais: Ripley, Darth Vader, Spock e Hermione. Robbie ajusta seus sensores auditivos e se inclina ligeiramente para frente, ansioso para aprender sobre os gostos musicais de cada personagem.
Robbie cria uma matriz de interações com dimensões \([m, n]\) com os valores de classificação dada pelos personagens.
Vamos carregar os dados de exemplo das interações dos usuários com as músicas (daqui em diante vamos chamar os personagens de usuários).
obs: Todo código utilizado nesse artigo está disponível no github .
import pandas as pd
import numpy as np
import examples as ex
matrix_interactions = ex.get_matrix_interaction()
matrix_interactions
Resultado da matrix_interactions
Usuário | Another One Bites the Dust | Back in Black | Bohemian Rhapsody | Brandenburg Concerto No. 3 | Clair de Lune | Comfortably Numb | Don't Stop Believin' | Firework | Hedwig’s Theme | O Fortuna | Survivor | The Imperial March |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Darth Vader | 4 | 4 | 2 | 0 | 1 | 2 | 2 | 0 | 0 | 4 | 0 | 5 |
Hermione | 1 | 0 | 5 | 0 | 5 | 2 | 2 | 5 | 5 | 2 | 2 | 1 |
Ripley | 5 | 5 | 2 | 1 | 2 | 3 | 4 | 4 | 0 | 5 | 5 | 4 |
Spock | 2 | 0 | 5 | 5 | 5 | 4 | 3 | 2 | 3 | 4 | 2 | 0 |
Cada célula contém uma nota de 0 a 5, onde 0 indica que o usuário nunca ouviu a música. O objetivo de Robbie é prever quais músicas poderiam ser recomendadas a cada usuário, baseado-se na semelhança de gostos entre eles. Para prever novas preferências, ele decide usar Fatoração de matrizes
(Matrix Factorization).
Fatoração de Matriz na Prática
A fatoração de matrizes é o processo de "quebrar" uma matriz original \(R\) em duas matrizes menores, \(U\) e \(V\), onde ambas possuem \(k\) dimensões latentes. Essa "quebra" transforma a matriz de interações entre usuários e itens (músicas no nosso contexto) em um espaço de características ocultas, permitindo capturar padrões ocultos nos dados e prever interações ausentes de forma eficiente.
Para iniciar a implementação, precisamos inicializar as matrizes \(U\) e \(V\) com valores aleatórios seguindo uma distribuição normal. Para evitar que esses valores iniciais sejam muito grandes, definimos o parâmetro scale
como \(1.0/k\). Isso garante que os valores tenham um desvio padrão inversamente proporcional ao número de dimensões latentes, estabilizando o processo de otimização e melhorando a qualidade das previsões do modelo.
ratings = matrix_interactions.values
k = 5 # numer of latent dimensions
num_users, num_items = ratings.shape
# Initialize user and item latent feature matrices
user_matrix = np.random.normal(scale=1. / k, size=(num_users, k)) # U matrix
item_matrix = np.random.normal(scale=1. / k, size=(num_items, k)) # V matrix
Maximização do Erro e Métodos de Otimização
Com as matrizes \(U\) e \(𝑉\) inicializadas, o próximo passo é otimizar os fatores latentes ajustando as previsões às interações reais. O objetivo é minimizar o erro entre as pontuações conhecidas e as previsões do modelo nas lacunas da matriz original.
Para esse processo, dois algoritmos são amplamente utilizados em fatoração de matrizes:
- Stochastic Gradient Descent (SGD) – um método iterativo que ajusta os fatores latentes reduzindo gradativamente o erro através da descida do gradiente. Este será o foco deste artigo.
- Alternating Least Squares (ALS) – uma abordagem que resolve as matrizes \(U\) e \(V\) alternadamente usando mínimos quadrados. Esse método será explorado em outro artigo.
Nos próximos passos, veremos como implementar o SGD para otimizar os fatores latentes e melhorar a precisão das previsões.
Implementação do algoritmo SGD
O SGD realiza três etapas principais:
- Escolher um par \((i,j)\) onde \(R_{ij}\) não seja nulo.
- Calcular o erro da previsão para essa interação:
- \(e_{ij} = R_{ij}−\hat R_{ij} = R_{ij}−U_iV_j^T\)
- Atualizar os valores das matrizes \(U\) e \(V\) usando a regra de atualização do gradiente descendente:
- \(U_i \leftarrow U_i + \alpha * (2 * e_{ij} * V_j - \lambda U_i)\)
- \(V_j \leftarrow V_j + \alpha * (2 * e_{ij} * U_i - \lambda V_j) \)
- \(\alpha\) é a taxa de aprendizado, que controla o tamanho do ajuste em cada iteração.
- \(\lambda\) é o termo de regularização, que evita overfitting penalizando valores muito altos nos fatores latentes
Esse processo se repete para diversas interações ao longo das épocas de treinamento, reduzindo progressivamente o erro.
alpha = 0.01 # learning rate
lambda_ = 0.02 # regularization term
"""
- Returns the predicted rating of user i for item j.
- It does this through the dot product between the user feature vector (users[i, :]) and the item feature vector (items[j, :]).
- The resulting vector indicates the affinity between the user and the item.
"""
def predict(users, items, i, j):
return np.dot(users[i, :], items[j, :].T)
"""
- This function performs training using Stochastic Gradient Descent (SGD).
- ratings.nonzero() returns the indices (i, j) of the non-zero positions in the ratings array, i.e. where there is a known rating.
- ratings[i, j]: actual value of the rating given by user i to item j.
- predict(users, items, i, j): rating prediction calculated by the model.
- eij: diferença entre o valor real e o previsto.
- The term 2 * eij * items[j, :] corrects the direction of the user vector based on the prediction error.
- The term - lambda_ * users[i, :] is the regularization to avoid overfitting.
- After the updates, the function returns the adjusted users and items vectors, which contain the final user and item embeddings.
"""
def sgd(ratings, users, items):
for i, j in zip(*ratings.nonzero()):
eij = ratings[i,j] - predict(users, items, i, j)
users[i, :] += alpha * (2 * eij * items[j, :] - lambda_ * users[i, :])
items[j, :] += alpha * (2 * eij * users[i, :] - lambda_ * items[j, :])
return users, items
Agora que temos nossa função de otimização pronta. Vamos implementar a maximização do erro, que é feito pela soma do erro quadrático média (MSE).
$$\large \min_{U,V} \sum_{(i,j) \ \in \ \Omega } (R_{ij} - (U * V)_{ij})^2 + \lambda ( \| U \|^2 + \| V \|^2) $$
MSE (Mean Squared Error)
Vamos iniciar nossa implementação da função de otimização pelo cálculo do MSE.
$$\large \min_{U,V} \sum_{(i,j) \ \in \ \Omega } (R_{ij} - (U * V)_{ij})^2 = MSE = \dfrac{1}{n} \sum_{i=0}^{n} (y_{i} - \hat y_{i})²$$
- \( (i,j) \in \Omega\) é o conjunto de índices onde \(R_{ij}\) é conhecido e pertencem ao conjunto
\(\Omega = \{\ (i,j) \ | \ R_{ij} \ é \ conhecido \ \}\). É o número total de interações conhecidas. - \((U * V)_{ij}\) são os valores estimados de \(R_{ij}\) pelo modelo.
- \(R_{ij}\) matriz original das classificações.
O MSE é crucial no SGD por vários motivos:
- Fornece uma medida clara do erro
- Ele quantifica a diferença média ao quadrado entre os valores reais e os valores estimados, permitindo avaliar a qualidade das previsões do modelo.
- É suave e diferenciável
- O MSE tem derivadas contínuas, o que facilita sua otimização por métodos baseados em gradiente, como o SGD.
- Penaliza grandes erros
- Como o erro é elevado ao quadrado, erros maiores têm um impacto desproporcional maior no valor final do MSE. Isso incentiva o modelo a reduzir grandes discrepâncias, melhorando a precisão das previsões.
- Facilita a interpretação do desempenho do modelo
- Valores altos de MSE indicam que o modelo ainda precisa ser otimizado, enquanto valores baixos sugerem que ele está se ajustando bem aos dados.
"""
- This variable will accumulate the squared error of each pair (i, j) in the ratings matrix.
- ratings.nonzero() returns the indices (i, j) where the ratings are known (nonzero).
- ratings[i, j]: actual rating given by user i to item j.
- (ratings[i, j] - y_pred) ** 2: squared error between actual and predicted value.
"""
def compute_loss(ratings, users, items):
error = 0
for i, j in zip(*ratings.nonzero()):
y_pred = predict(users, items, i, j)
error += (ratings[i, j] - y_pred) ** 2
mse = np.mean(error)
return mse
Vamos implementar agora a segunda parte da equação de otimização.
$$\huge \lambda ( \| U \|^2 + \| V \|^2) $$
- \(\lambda\) é a Taxa de regularização.
- \(( \| U \|^2 + \| V \|^2)\) são usados como regularização para evitar overfitting, limitando o tamanho dos parâmetros e garantindo que eles não cresçam excessivamente.
"""
- This variable will accumulate the squared error of each pair (i, j) in the ratings matrix.
- ratings.nonzero() returns the indices (i, j) where the ratings are known (nonzero).
- ratings[i, j]: actual rating given by user i to item j.
- (ratings[i, j] - y_pred) ** 2: squared error between actual and predicted value.
"""
def compute_loss(ratings, users, items):
error = 0
for i, j in zip(*ratings.nonzero()):
y_pred = predict(users, items, i, j)
error += (ratings[i, j] - y_pred) ** 2
mse = np.mean(error)
# Regularization term to avoid overfitting
# Penalizes large magnitudes in user and item vectors
regularization = lambda_ * (np.linalg.norm(users)**2 + np.linalg.norm(items)**2)
return mse + regularization
Por que usar regularização?
- Evita overfitting
- Valores altos nas matrizes
user_matrix
eitem_matrix
podem levar a um modelo que se ajusta perfeitamente aos dados de treinamento, mas tem desempenho ruim em dados não vistos. - A regularização mantém os recursos aprendidos compactos e evita magnitudes excessivas de parâmetros.
- Valores altos nas matrizes
- Melhora a estabilidade numérica
- Sem regularização, as matrizes de fatores podem crescer demais, causando instabilidade e erros no sistema.
- Adicionar o termo de penalidade controla a escala de
user_matrix
eitem_matrix
, estabilizando o treinamento.
- Incentiva a generalização
- Ele garante que fatores latentes capturem padrões significativos em vez de ruído.
Pronto. Agora que temos a função de otimização para fatoração de matrizes e maximização do erro, podemos iniciar o treinamento.
epochs = 1000
for epoch in range(epochs):
user_matrix, item_matrix = sgd(ratings, user_matrix, item_matrix)
if epoch % 100 == 0:
error = compute_loss(ratings, user_matrix, item_matrix)
print(f'Epoch {epoch + 1}/{epochs} - Error: {error:.4f}')
"""
Train output:
Epoch 1/1000 - Error: 596.5902
Epoch 101/1000 - Error: 1.6179
Epoch 201/1000 - Error: 1.6440
Epoch 301/1000 - Error: 1.6707
Epoch 401/1000 - Error: 1.6952
Epoch 501/1000 - Error: 1.7164
Epoch 601/1000 - Error: 1.7343
Epoch 701/1000 - Error: 1.7491
Epoch 801/1000 - Error: 1.7612
Epoch 901/1000 - Error: 1.7710
"""
No início do treinamento, geralmente iniciamos as matrizes de forma aleatória. Então, o modelo está praticamente “chutando” valores nas previsões. Por isso o erro começa lá em cima (no exemplo, por volta de 596)
Conforme o algoritmo SGD ajusta os parâmetros, ele vai “descobrindo” aos poucos a melhor forma de alinhar esses vetores de usuário e de item para se aproximar das interações reais. Isso explica por que, após algumas iterações, o erro já cai para perto de 1,6. O modelo rapidamente pega os padrões mais “óbvios” dos dados.
Depois dessa fase inicial de grande queda, o erro tende a estabilizar e até oscilar um pouco. É como se o modelo tivesse aprendido a maior parte das preferências gerais dos usuários, mas ainda precisa refinar detalhes mais sutis (ou lidar com dados que não seguem muito a tendência geral). À medida que o número de épocas cresce (200, 300, 400...), o modelo vai fazendo pequenos ajustes finos, o que faz o erro não cair tão drasticamente, mas manter-se em torno de 1,6 a 1,7.
Por que o erro não chega a zero? Em aplicações reais, sempre existe algum nível de ruído: usuários podem dar notas de forma inconsistente, certos itens podem ter poucas avaliações, e assim por diante. Além disso, aplicamos “regularização” para evitar que o modelo simplesmente decore cada pontinho da matriz (o chamado overfitting). Essa regularização “segura” um pouco o ajuste e impede que o modelo se adapte demais a ruídos ou dados incompletos.
Recomendando novas músicas
Agora que definimos todas as partes do processo, vamos juntar o código.
import numpy as np
class MatrixFactorization:
def __init__(self, n_factors, learning_rate, regularization):
"""
Matrix Factorization using Stochastic Gradient Descent (SGD) to factorize a matrix R into two matrices U and V.
:param n_factors: Number of latent factors
:param learning_rate: Learning rate
:param regularization: Regularization parameter
"""
self._k = n_factors
self._alpha = learning_rate
self._lambda = regularization
self._original_matrix = None
self._user_matrix = None
self._item_matrix = None
self._num_users = None
self._num_items = None
def fit(self, matrix, epochs=100) -> np.ndarray:
"""
Fit the matrix factorization model to the data
:param matrix: the matrix to factorize
:param epochs: the number of epochs to train the model
:return: the predicted ratings
"""
self._initialize(matrix)
for i in range(epochs):
self._sgd()
loss = self._compute_loss()
print(f'Epoch {i + 1}/{epochs} - Training Loss: {loss:.4f}')
return self.predict()
def predict(self) -> np.ndarray:
"""
Predict the ratings for every user and item.
U matrix * V matrix^T
:return: the predicted ratings
"""
return self._user_matrix.dot(self._item_matrix.T)
def _predict(self, i, j) -> np.ndarray:
"""
Predict the rating of user i for item j
U[i, :] * V[j, :].T
:param i: Index of the user
:param j: Index of the item
:return: the predicted rating
"""
return self._user_matrix[i, :].dot(self._item_matrix[j, :].T)
def _sgd(self):
"""
Perform Stochastic Gradient Descent to learn the latent factors U and V by minimizing the loss function using the original matrix R.
:return: None
"""
for i, j in zip(*self._original_matrix.nonzero()):
eij = self._original_matrix[i, j] - self._predict(i, j)
self._user_matrix[i] += self._alpha * (2 * eij * self._item_matrix[j] - self._lambda * self._user_matrix[i])
self._item_matrix[j] += self._alpha * (2 * eij * self._user_matrix[i] - self._lambda * self._item_matrix[j])
def _compute_loss(self) -> float:
"""
Compute the loss (MSE) of the prediction
:return: the loss of the prediction
"""
error = 0
for i, j in zip(*self._original_matrix.nonzero()):
error = self._original_matrix[i, j] - self._predict(i, j)
error += error ** 2
mse = np.mean(error)
regularization = self._lambda * (np.linalg.norm(self._user_matrix) + np.linalg.norm(self._item_matrix))
return mse + regularization
def _initialize(self, matrix):
self._original_matrix = matrix.copy()
self._num_users, self._num_items = self._original_matrix.shape
self._user_matrix = np.random.normal(scale=1. / self._k, size=(self._num_users, self._k)) # U matrix
self._item_matrix = np.random.normal(scale=1. / self._k, size=(self._num_items, self._k)) # V matrix
Treinamento
Agora que juntamos tudo, podemos fazer o treinamento do modelo. Mas antes, é importante normalizar os valores de ratings
para manter o intervalo \([0, 5]\). Isso minimiza o impacto de valores extremos e permite ao modelo aprender padrões e relações de maneira mais eficiente e consistente.
from sklearn.preprocessing import MinMaxScaler
ratings = matrix_interactions.values
scaler = MinMaxScaler(feature_range=(0, 5))
normalized_ratings = scaler.fit_transform(ratings)
model = MatrixFactorization(n_factors=75, learning_rate=0.001, regularization=0.02)
matrix_factorization_result = model.fit(ratings, epochs=600)
Resultado da matrix_factorization_result
User | Another One Bites the Dust | Back in Black | Bohemian Rhapsody | Brandenburg Concerto No. 3 | Clair de Lune | Comfortably Numb | Don't Stop Believin' | Firework | Hedwig’s Theme | O Fortuna | Survivor | The Imperial March |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Darth Vader | 4.169970 | 4.057246 | 3.667471 | 0.710625 | 1.712582 | 2.772770 | 3.595744 | 3.018872 | 2.391332 | 4.190256 | 4.012947 | 4.421050 |
Hermione | 2.686220 | 3.310009 | 5.080972 | 4.519992 | 5.051042 | 3.952128 | 3.291865 | 4.718939 | 4.862734 | 4.351025 | 2.163611 | 0.979503 |
Ripley | 4.838367 | 4.918483 | 4.106493 | 1.043555 | 2.023779 | 3.012154 | 3.992257 | 4.059402 | 3.200697 | 4.823499 | 4.927110 | 4.451684 |
Spock | 1.994154 | 2.523687 | 4.894493 | 4.974091 | 4.904404 | 3.989944 | 2.998294 | 2.253896 | 3.132301 | 3.994360 | 1.850755 | 0.795050 |
Comparação da nota real vs previsão do model:
Usuário | Música | Nota Real | Previsão | Análise |
---|---|---|---|---|
Darth Vader | Back in Black | 4 | 4.057246 | O modelo previu um valor muito próximo da nota real. |
Hermione | Bohemian Rhapsody | 5 | 5.080972 | A predição é praticamente idêntica à nota dada por Hermione. |
Ripley | Don’t Stop Believin’ | 4 | 3.992257 | O modelo previu corretamente uma interação forte. |
Spock | Bohemian Rhapsody | 5 | 4.894493 | O modelo previu um valor muito próximo da interação real. |
Darth Vade | The Imperial March | 5 | 4.421050 | O modelo quase acertou a nota, mas foi um pouco conservador. |
Previsões das lacunas
Durante o treinamento do modelo com Matrix Factorization, algumas células na matriz de interações tinham valores iguais a 0. Isso significa que esses usuários não avaliaram essas músicas ou não tiveram interações registradas com elas. Então, como o modelo conseguiu prever essas notas ausentes?
O modelo preenche essas lacunas usando a similaridade entre os fatores latentes dos usuários e das músicas. Por exemplo, mesmo que Darth Vader nunca tenha escutado "Brandenburg Concerto No. 3" (nota 0 na matriz de interações), o modelo conseguiu prever uma nota 0,71. Isso foi possível porque a matriz fatorada aprendeu que Darth Vader tende a preferir músicas épicas e de ficção científica, mas não tem grande afinidade com música clássica.
Vamos ver outros exemplos de predições feitas pelo modelo:
- Darth Vader e "Brandenburg Concerto No. 3": Predição: 0,71 Isso indica que o modelo entendeu que Darth Vader tem pouco interesse por essa música. A nota foi baixa porque os fatores latentes de Darth Vader não se alinham bem com os fatores latentes de músicas clássicas.
- Hermione e "The Imperial March": Predição: 0,97 Hermione não mostrou muito interesse por essa música (nota original 1), e o modelo previu um valor próximo. Isso aconteceu porque os fatores latentes de Hermione indicam preferência por músicas emocionais e clássicas, mas "The Imperial March" não está entre suas favoritas.
- Ripley e "Clair de Lune": Predição: 2,02 Mesmo sem uma avaliação anterior, o modelo previu uma nota moderada. Isso ocorre porque Ripley mostrou algum interesse por músicas suaves e emocionais, mas "Clair de Lune" não está totalmente alinhada com suas preferências principais.
- Spock e "Firework": Predição: 2,25 Embora Spock nunca tenha interagido com essa música, o modelo previu um interesse limitado. Isso é consistente, pois os fatores latentes de Spock indicam preferência por músicas clássicas, enquanto "Firework" é uma música pop.
De forma geral, o modelo faz essas predições multiplicando os fatores latentes dos usuários e itens, ajustados pelo Stochastic Gradient Descent (SGD). A multiplicação entre as matrizes \(U\) e \(V\) captura a relação entre as preferências gerais de cada usuário e as características das músicas.
À medida que o SGD minimiza o erro nas predições conhecidas, ele ajusta os fatores latentes para refletir corretamente os gostos individuais. Isso permite ao modelo generalizar e prever adequadamente as lacunas na matriz original, fornecendo sugestões de músicas personalizadas para cada personagem.
Isso permite que Robbie sugira novas músicas para cada usuário, personalizando suas recomendações.
Similaridade entre os usuários
from sklearn.metrics.pairwise import cosine_similarity
user_similarity = cosine_similarity(matrix_factorization_result)
df_user_similarity = pd.DataFrame(user_similarity, index=ex.users, columns=ex.users)
df_user_similarity
Ripley | Darth Vader | Spock | Hermione | |
---|---|---|---|---|
Ripley | 1.0000 | 0.836699 | 0.996773 | 0.795352 |
Darth Vader | 0.836699 | 1.0000 | 0.856497 | 0.976032 |
Spock | 0.996773 | 0.856497 | 1.0000 | 0.805946 |
Hermione | 0.795352 | 0.976032 | 0.805946 | 1.0000 |
- Ripley e Spock (0.996773):
- Alta similaridade, pois ambos têm preferências fortes por músicas como “Bohemian Rhapsody” e “Back in Black”.
- Darth Vader e Hermione (0.976032):
- Alta similaridade, o que faz sentido, já que ambos gostam de músicas épicas e clássicas, como “The Imperial March” e “Hedwig’s Theme”.
- Darth Vader e Spock (0.856497):
- Similaridade moderada, pois embora tenham algumas preferências em comum, Spock tem um foco maior em músicas clássicas.
Recomendar novas músicas para Hermione
Após treinar o modelo, Robbie está pronto para fazer recomendações! Ele olhou para as músicas que Hermione ainda não escutou e analisou os gostos de outros personagens semelhantes a ela.
def recommend_by_user(user, df_predicted, df_interactions, top_k=3):
# Calculate user similarity matrix
user_similarity = cosine_similarity(df_predicted.values)
user_similarity_df = pd.DataFrame(user_similarity, index=ex.users, columns=ex.users)
# Get the top similar users
similarities = user_similarity_df.loc[user].sort_values(ascending=False)[1:top_k+1]
print('Sser similarity score:')
print(similarities)
# Initialize recommendations
recommendations = pd.Series(0, index=df_predicted.columns, dtype=np.float64)
# Aggregate recommendations based on similar users
for user_similar, similarity in similarities.items():
recommendations = recommendations.add(
df_predicted.loc[user_similar].reindex(recommendations.index, fill_value=0) * similarity,
fill_value=0
)
# Filter out already known items
already_known = df_interactions.loc[user] > 0
recommendations = recommendations[~already_known.reindex(recommendations.index, fill_value=False)]
return recommendations.sort_values(ascending=False)
recommend_by_user('Hermione', df_matrix_interactions, matrix_interactions)
"""
Output:
Sser similarity score:
Darth Vader 0.976032
Spock 0.805946
Ripley 0.795352
"""
Recomendações:
Músicas | Score |
---|---|
Back in Black | 9.905884 |
Brandenburg Concerto No. 3 | 5.532434 |
Robbie percebeu que os personagens mais semelhantes a Hermione, baseando-se nos gostos musicais, são:
- Darth Vader (similaridade alta: 0.97)
- Spock (similaridade moderada: 0.81)
- Ripley (similaridade moderada: 0.79)
Darth Vader tem os gostos mais parecidos com os de Hermione, então suas preferências tiveram mais peso nas recomendações.
Por que "Black in Black" foi recomendada?
- Hermione nunca escutou "Black in Black" antes (classificação 0 na matriz de interações).
- Robbie viu que Darth Vader e Ripley gostaram muito dessa música:
- Como Darth Vader tem uma similaridade muito alta com Hermione, suas preferências influenciaram bastante a recomendação.
- Robbie concluiu que Hermione provavelmente também adoraria essa música por causa da forte influência de Darth Vader e Ripley.
Por que "Brandenburg Concerto No. 3" foi recomendado?
- Hermione também não tinha escutado essa música (classificação 0 na matriz de interações).
- Robbie percebeu que Spock dostou muito dessa música.
- Além disso, Hermione já demonstrou interesse por músicas clássicas (como "Clair de Lune")
- Com a influência de Spock e suas preferências anteriores, Robbie concluiu que essa música clássica seria uma boa recomendação.
O modelo funciona como um amigo (Robbie) que conhece bem os seus gostos e os compara com os de outras pessoas. Ele percebeu que Hermione:
- Compartilha gostos com Darth Vader, o que levou à recomendação de uma música de rock como "Black in Black".
- Tem uma afinidade com músicas clássicas, com Spock, o que resultou na recomendação de "Brandenburg Concerto No. 3."
Robbie agora sabe exatamente como surpreender Hermione com novas músicas! 🎶
Conclusão
A Fatoração de Matrizes se destaca como uma ferramenta essencial para criar recomendações personalizadas em larga escala, ajudando plataformas como serviços de streaming e e-commerce a entenderem melhor seus usuários. Ao decompor a matriz de interações, conseguimos identificar padrões ocultos de preferências, mesmo quando há dados ausentes.
Com o Stochastic Gradient Descent (SGD), ajustamos as matrizes de forma iterativa, reduzindo o erro entre previsões e dados reais. A regularização garantiu um modelo equilibrado, sem overfitting, enquanto a normalização dos dados trouxe mais estabilidade ao aprendizado.
No final, vimos como o modelo vai além das interações diretas, prevendo preferências mesmo quando os dados são limitados. A história de Robbie e seus amigos icônicos nos mostrou que essas técnicas podem ser aplicadas a diversos cenários, proporcionando recomendações únicas e personalizadas. 🎶
Referências
- Koren, Yehuda; Bell, Robert; Volinsky, Chris. "Matrix Factorization Techniques for Recommender Systems". IEEE Computer Society, 2009. Link para o artigo
- Ricci, Francesco; Rokach, Lior; Shapira, Bracha. "Recommender Systems Handbook". Springer, 2022. Link para o livro
- Hug, Nicolas. "Surprise: A Python Scikit for Building and Analyzing Recommender Systems". Documentação oficial
- Said, Alan; Bellogín, Alejandro. "Comparative Recommender System Evaluation". Springer, 2014. Link para o artigo