Por que sistemas de recomendação existem
Fala do professor
"Imagina entrar numa sorveteria com 300 milhões de sabores. Você paralisa. A Amazon é exatamente isso. Sem alguém te apontar o que ver, você fecha o app."
Quando você abre Amazon, Netflix ou Spotify, a tela inicial não é um índice do catálogo — é uma seleção feita para você. Essa seleção não é opcional: sem ela, o produto seria impossível de usar.
Os números acima (lá em cima, no cabeçalho) vão aparecendo conforme a página carrega. O último é o mais impressionante: 35% das vendas da Amazon são atribuídas ao sistema de recomendação (McKinsey, 2013).
💡 Por que isso importa para você
Recomendador não é "feature bacana". Em plataformas de catálogo grande, é tão crítico quanto banco de dados ou login. A decisão de como construir um impacta conversão, retenção e LTV direto no negócio.
As 4 famílias de recomendador
Existem quatro abordagens clássicas. Nenhuma é "a melhor" — cada uma brilha num cenário. Clique no botão e veja qual é o foco da nossa aula.
🔥 Popularidade
"O que todo mundo está vendo."
Simples, ótimo para cold start. Mas: sem personalização e amplifica a bolha.
🏷️ Conteúdo
"Similar ao que você curtiu, por atributos."
Não depende de outros usuários, é explicável — mas te prende no que você já conhece.
👥 Colaborativa FOCO DE HOJE
"Gente como você também gostou."
Escala com uso, boa personalização. Ponto fraco: cold start e esparsidade.
🔀 Híbrido
"Combina as três acima."
Resolve pontos cegos; é o que roda em produção. Mas é mais complexo.
Pergunta para a turma
"Por que começar pela colaborativa? Porque ela escala com o uso da plataforma, não com curadoria humana. Amazon, Netflix, YouTube e Spotify usam variações dela como motor principal."
User-based vs Item-based
Dentro da filtragem colaborativa existem duas perguntas possíveis. Parecem iguais, mas levam a arquiteturas radicalmente diferentes.
👤 User-based AULA 3
- Pergunta: "quem é parecido comigo?"
- Olha: linhas (usuários)
- Estabilidade: baixa — gostos evoluem
- Pré-cômputo: caro, invalida rápido
- Escala: difícil em catálogos gigantes
- Explicação: "pessoas parecidas com você…"
🎬 Item-based HOJE
- Pergunta: "o que se parece com o que eu gosto?"
- Olha: colunas (itens)
- Estabilidade: alta — itens mudam pouco
- Pré-cômputo: viável; dura dias/semanas
- Escala: natural em catálogos grandes
- Explicação: "porque você curtiu X, veja Y"
O giro de perspectiva
"Trocar 'quem se parece comigo' por 'o que se parece com o que eu gosto' parece trivial — mas é uma decisão arquitetural. Muda custo, estabilidade, explicabilidade e cold start. Foi essa sacada que a Amazon publicou em 2003."
A Matriz R (usuário × item)
Tudo começa numa matriz: linhas = usuários, colunas = itens, cada célula = rating (ou vazio, se ainda não avaliou).
| 🎬 Matrix | 🎞️ Inception | 🎭 Titanic | 🚀 Interstellar | 🎠 Amelie | |
|---|---|---|---|---|---|
| Ana | 5 | 4 | · | 5 | · |
| Bia | · | 3 | 5 | · | 4 |
| Caio | 4 | 5 | 2 | 5 | · |
| Davi | 5 | · | 1 | · | 3 |
R — 4 usuários × 5 itens · ratings de 1 a 5 · "·" = não avaliou
Esta matriz tem duas leituras:
- 📏 Por linha → cada usuário vira um vetor em ℝ⁵ (quem usa: user-based)
- 📐 Por coluna → cada item vira um vetor em ℝ⁴ (quem usa: item-based, a nossa abordagem)
⚠️ A matriz real é vazia
Na Netflix, >99% das células são vazias. São ~1 bilhão de ratings numa matriz de ~500M × 17k. Nunca se itera tudo — sempre filtramos usuários que avaliaram ambos os itens que vamos comparar.
Similaridade entre itens — 3 métricas
Cada item é um vetor-coluna. Medir "quão parecidos são os itens i e j?" vira um problema de comparar dois vetores. Três opções:
5.1 Cosseno puro — o mais simples
Problema: todos os ratings são positivos (1 a 5) → todos os vetores caem no mesmo quadrante → parece que tudo é similar a tudo. E pior: cada usuário tem sua escala.
5.2 Adjusted Cosine ⭐ (o que você vai usar)
Resultado entre −1 e +1: +1 gostos idênticos · 0 sem relação · −1 opostos.
5.3 Jaccard — quando não tem nota
💡 Regra de bolso
Rating numérico (1–5) → Adjusted Cosine. Binário (comprou/não) → Jaccard. Cosseno puro → quase nunca.
Geometria — cada item é uma flecha
Vamos simplificar para 2 dimensões (só Ana e Caio). Clique para ver cada filme aparecer como um vetor:
Cada filme é um vetor saindo da origem. Similaridade = cosseno do ângulo entre dois vetores.
Repare com a turma
"Matrix e Inception estão quase sobrepostos — ângulo de ~8°, cosseno ≈ 0,99, super similares. Titanic e Amelie formam outro cluster (notas baixas). Repare: o algoritmo descobriu 'ação/sci-fi' vs 'drama' sem saber o que é gênero. Isso é a mágica."
Por que "adjusted"? — todo usuário tem escala diferente
Olhe esses dois usuários:
| Usuário | Escala que usa | Média | O que significa nota 3? |
|---|---|---|---|
| Caio (exigente) | Só dá notas 3–5 | ~4,0 | "Decepcionante" |
| Davi (liberal) | Usa de 1 a 5 | ~3,0 | "Razoável" |
O mesmo filme ganha 3 dos dois — mas não é a mesma coisa. Se tratarmos como iguais, perdemos o sinal real.
✨ A solução "adjusted"
Subtrair R̄u (média do usuário) de cada rating dele re-centraliza cada um em zero. Ratings acima da média pessoal viram positivos; abaixo, negativos. Agora dá para representar "gostos opostos" (cos ≈ −1) — antes era impossível, porque tudo estava no mesmo quadrante.
A frase para martelar
"A gente não quer saber se o Caio deu 4 para Matrix. A gente quer saber se ele deu mais que o normal dele."
Adjusted Cosine passo a passo — sim(Matrix, Inception)
Vamos calcular a similaridade entre Matrix e Inception usando quem avaliou os dois: Ana e Caio. Cinco passos. Clique e veja a conta aparecer:
🚨 Atenção: limite dos dados pequenos
Com só 2 usuários em comum, o resultado é ruído. Em produção, exige-se tipicamente ≥ 3 ou ≥ 5 usuários em comum (chama-se support threshold). Na "matriz S oficial" da próxima seção, sim(Matrix, Inception) = +0,92 — o valor verdadeiro quando há mais dados. O −0,55 é um exemplo didático de instabilidade, não uma contradição.
Matriz S (item × item) — a mágica da escala
Se você calcula todos os pares (i, j) de antemão, obtém uma matriz simétrica S de tamanho |I|×|I| que pode ser cacheada. Cada consulta online vira O(k) — instantâneo.
| Matrix | Incep. | Titanic | Inters. | Amelie | |
|---|---|---|---|---|---|
| Matrix | 1,00 | +0,92 | −0,78 | +0,88 | +0,12 |
| Incep. | +0,92 | 1,00 | −0,65 | +0,85 | +0,18 |
| Titanic | −0,78 | −0,65 | 1,00 | −0,72 | +0,81 |
| Inters. | +0,88 | +0,85 | −0,72 | 1,00 | +0,22 |
| Amelie | +0,12 | +0,18 | +0,81 | +0,22 | 1,00 |
S — simétrica · diagonal sempre 1 · valores ∈ [−1, +1]
3 propriedades para a sala lembrar
- Custo: offline caro
O(m²·n)uma vez; online baratoO(k)a cada requisição. - Estabilidade: gêneros não saem de moda do dia para a noite. Job batch diário/semanal basta.
- Explicabilidade de graça: "recomendamos Inception porque você viu Matrix (sim 0,92)". Auditável.
🚀 Truque de produção
Para 10 mil itens, S teria 100M de entradas. Guarda só os top-50 vizinhos de cada item — basta para ranqueamento de qualidade e cabe em Redis sem suar.
A fórmula de predição
Dado que temos S pronta, como prever qual rating o usuário u daria a um item i?
A intuição em uma frase
"O rating previsto é uma média ponderada dos ratings que você já deu a itens parecidos. Itens muito parecidos puxam mais; pouco parecidos, contribuem pouco."
Prever P(Davi, Interstellar)
Davi avaliou: Matrix = 5, Titanic = 1, Amelie = 3. Queremos o rating que ele daria a Interstellar.
Similaridades (vindas da matriz S):
- sim(Interstellar, Matrix) = +0,88 — ambos sci-fi cerebral
- sim(Interstellar, Titanic) = −0,72 — gostos opostos
- sim(Interstellar, Amelie) = +0,22 — leve sobreposição
Por que essa predição faz sentido?
"Davi amou Matrix (5) e Interstellar é muito similar a Matrix (+0,88). Isso puxa forte pra cima. Titanic é oposto (−0,72) e ele não gostou (1): a contribuição negativa é pequena — coerente. E o melhor: a recomendação é auditável — dá para explicar ao usuário."
Amazon 2003 — o paper que mudou tudo
Em janeiro de 2003, Greg Linden, Brent Smith e Jeremy York publicaram no IEEE Internet Computing: "Amazon.com Recommendations: Item-to-Item Collaborative Filtering". O algoritmo rodava em produção na Amazon desde 2001.
| # | Por que venceu |
|---|---|
| 1 | Escala — catálogos com 10⁷ itens × 10⁸ usuários eram inviáveis para user-based |
| 2 | Tempo real — consulta O(k) sobre S cacheada, latência < 10ms |
| 3 | Qualidade — em A/B interno, CTR e conversão maiores que user-based |
| 4 | Cold start parcial — após 1 única compra já recomenda |
👀 Você já viu isso mil vezes
A frase "Customers who bought this also bought" é o item-based em ação. Mercado Livre, Shopee, Steam, App Store, Google Play — todos copiaram essa UX.
Limitações — e como mitigar
| Problema | Sintoma | Mitigação |
|---|---|---|
| Cold start de item | item novo não tem ratings → não entra em S | híbrido com content-based nos primeiros dias |
| Viés de popularidade | blockbusters dominam top-N | penalização log, MMR, cotas por faixa |
| Esparsidade | poucos users em comum = sim ruidosa | support threshold ≥ 5, shrinkage sim · n/(n+β) |
| Filter bubble | "mais do mesmo" | serendipity, novelty, boost de itens recentes |
🧭 Princípio de engenharia
Nunca avalie um recomendador apenas com métricas offline. Usuário clica em thumb bonita, copy curta, novidade — coisas que NDCG não vê. Sempre valide com A/B test em produção: CTR, conversão, tempo de sessão, retenção.
ML.NET em C# — o que vai rodar no seu app
Stack do módulo é .NET. A Microsoft mantém uma biblioteca oficial — ML.NET — que implementa isso via Matrix Factorization. Sem Python, sem Docker, sem serviço externo.
Mapeamento teoria ↔ código
| Conceito da aula | Em ML.NET |
|---|---|
| Matriz R | IDataView com UserId, ItemId, Label |
| Vetores latentes | ApproximationRank (k) |
Predição r̂_ui | PredictionEngine.Predict().Score |
| Similaridade item↔item | GetItemMatrix() + cosseno entre linhas |
| Avaliação | ctx.Recommendation().Evaluate() → RMSE, R² |
Pipeline de treino (esqueleto)
using Microsoft.ML; using Microsoft.ML.Trainers; // Schema de cada linha do CSV public record Rating(float UserId, float ItemId, float Label); var ctx = new MLContext(seed: 42); // 1 · Carregar ratings.csv → IDataView var data = ctx.Data.LoadFromTextFile<Rating>( path: "ratings.csv", hasHeader: true, separatorChar: ','); // 2 · Split 80/20 para validar offline (RMSE, R²) var split = ctx.Data.TrainTestSplit(data, testFraction: 0.2); // 3 · Pipeline: IDs → chaves + Matrix Factorization var pipeline = ctx.Transforms.Conversion .MapValueToKey("UserIdEnc", "UserId") .Append(ctx.Transforms.Conversion.MapValueToKey("ItemIdEnc", "ItemId")) .Append(ctx.Recommendation().Trainers.MatrixFactorization(new MatrixFactorizationTrainer.Options { MatrixColumnIndexColumnName = "UserIdEnc", MatrixRowIndexColumnName = "ItemIdEnc", LabelColumnName = "Label", ApproximationRank = 16, // k fatores latentes NumberOfIterations = 20, LearningRate = 0.05, Lambda = 0.1 // regularização L2 })); // 4 · Treinar, avaliar, salvar var model = pipeline.Fit(split.TrainSet); var preds = model.Transform(split.TestSet); var metrics = ctx.Recommendation().Evaluate(preds, labelColumnName: "Label", scoreColumnName: "Score"); Console.WriteLine($"RMSE = {metrics.RootMeanSquaredError:F3} · R² = {metrics.RSquared:F3}"); ctx.Model.Save(model, split.TrainSet.Schema, "model.zip");
⚠️ Pegadinha de produção: thread-safety
PredictionEngine NÃO é thread-safe. Em API ASP.NET Core com múltiplas requisições simultâneas, criar um por request é lento e compartilhar um único causa race condition. Solução oficial: PredictionEnginePool do pacote Microsoft.Extensions.ML, registrado via DI com services.AddPredictionEnginePool<Rating, Prediction>().FromFile("model.zip").
🎛️ Calibração dos hiperparâmetros
k (ApproximationRank): comece em 8–16. Mais = overfit em dataset pequeno. Iterations: 15–40. LearningRate: 0,05 seguro; sobe para 0,1 se estagnar. Lambda: sobe se RMSE treino ≪ RMSE teste.
As 5 armadilhas da aula
Support threshold
Com poucos usuários em comum, similaridade é ruído. O −0,55 do exemplo vs +0,92 da matriz S mostra exatamente isso. Em produção, exige ≥ 3 ou ≥ 5.
Adjusted ≠ Pearson
Adjusted subtrai a média do usuário; Pearson subtrai a média do item. Em item-based queremos o primeiro.
Direção ≠ magnitude
Cosseno mede direção, não tamanho. Duas pessoas com mesmo gosto mas uma mais ativa aparecem como flechas de tamanhos diferentes, mesma direção. Cosseno diz "iguais"; euclidiana diria "diferentes".
PredictionEngine não é thread-safe
Em API concorrente, use PredictionEnginePool. Nunca crie um PredictionEngine por request, nem compartilhe um entre threads.
Offline ≠ online
NDCG melhor não significa CTR melhor. Usuário clica em thumb bonita, novidade, copy curta. Sempre A/B test.
O que fazer nesta sprint
Até a Semana 02, o grupo precisa entregar 4 artefatos:
| # | Artefato | Conteúdo |
|---|---|---|
| 01 | 🧭 Entendimento do projeto | Minimundo, atores, objetivo de negócio, escopo |
| 02 | 📋 RF + RNF | Requisitos por ISO/IEC 25010 + regras de negócio numeradas |
| 03 | 🗄️ Base de dados | Schema + dataset inicial de users/items/ratings |
| 04 | 🛠️ Gestão de projeto | Repo com template, issues, board, rotina de reuniões |
Mão na massa (insumo da entrega 03)
Antes da próxima aula, monte uma planilha simulando o domínio do app:
- Definir 5 usuários × 5 itens do seu domínio
- Preencher ratings plausíveis com algumas células vazias (esparsidade real)
- Calcular R̄u de cada usuário
- Calcular adjusted cosine entre 2 itens escolhidos
- Prever rating de um usuário em um item que ele não avaliou (fórmula ponderada)
- Comentar criticamente: o resultado faz sentido no domínio?
- Aplicar o template oficial no repositório do grupo
Encerramento
Na aula 3, você vai ver a versão user-based — e a gente compara: mesma matriz R, pergunta diferente, arquitetura diferente. Na aula 5, você vai pôr o Top-N direto no banco via Stored Procedure. Essa aula de hoje é o alicerce matemático de tudo.