Desenvolver um sistema de recomendação de notícias para o portal G1 predizendo qual será a próxima notícia que o usuário vai ler.
Importante:
- Como lidar com o cold-start? Usuários ou itens com poucas informações devem ser tratados de forma diferenciada em relação a perfis mais completos?
- O conceito de recência é essencial para garantir que as recomendações de notícias sejam relevantes e oportunas.
Utilizamos um modelo híbrido que combina:
- Content-Based Filtering com embeddings de notícias.
- Clusterização K-Means para agrupar notícias similares.
- Modelo de Similaridade do Tipo Item-Item usando similaridade de cosseno.
- Recomendação Híbrida para Cold-Start de Usuários (notícias populares + recentes + exploratórias).
Vamos dar uma olhada nos dois cenários princinpais, seus desafios e possíveis soluções:
Cenário 1: Cold-start para Itens (Notícias Novas)
Se uma notícia nova for adicionada ao sistema sem histórico de leitura dos usuários, ela ainda pode ser recomendada com base na similaridade semântica do conteúdo, mas essa abordagem trás os seguintes problemas:
- Uma notícia nova pode não ser recomendada inicialmente pois não há interação de usuários para reforçar sua relevância.
- Se a categoria da notícia estiver errada ou o título for muito genérico o embedding pode ser menos representativo.
Entre as possíveis soluções para este cenário, temos:
- Explorar a popularidade inicial: notícias novas podem ser recomendadas mais frequentemente nos primeiros minutos/horas com base na categoria ou trending topics.
- Aproveitar metadados adicionais: usar dados como autor, palavras-chave ou até sumarizações para enriquecer os embeddings.
- Incorporar um modelo híbrido: misturar similaridade de conteúdo com um fator de exploração baseada em novidades.
Cenário 2: Cold-start para Usuários Novos
Atualmente, um usuário precisa ter um histórico de leitura para receber recomendações precisas. Se um usuário não tiver interações anteriores, ele não poderá receber recomendações personalizadas:
- Sem histórico, não há como prever a próxima leitura com base na similaridade com conteúdos anteriores.
Possíveis soluções:
- Popularidade inicial: para novos usuários, recomendar notícias mais populares do momento até que eles interajam com alguma.
- Questionário de preferências: pedir ao usuário que selecione categorias de interesse no primeiro acesso.
- Basear-se em perfis similares: se um usuário novo se comporta como outros usuários (ex.: leitura inicial de política), ele pode receber recomendações com base nesses perfis.
Foi por isso que criamos uma estratégia híbrida: para combinar múltiplas abordagens de recomendação, garantindo que novos usuários recebam sugestões relevantes sem depender de um único critério.
- Utilizamos o all-MiniLM-L6-v2, um modelo de embeddings da SentenceTransformers que equilibra precisão e eficiência computacional.
- Cada notícia é transformada em um vetor de 384 dimensões, permitindo cálculos rápidos de similaridade.
Cálculo da Similaridade:
Ainda com relação ao modelo escolhido, ele apresenta um bom equilibrio entre precisão e eficiência computacional.
O all-MiniLM-L6-v2 é uma versão otimizada do BERT, reduzindo a complexidade e mantendo boa qualidade dos embeddings. Ele possui:
- Latencia Baixa: 6 camadas de autoatenção (mais rápido que o BERT-base)
- Dimensão Reduzida (384 dimensões): menor custo computacional
- Boa performance sem necessidade de treinamento supervisionado
Existem modelos maiores como o BERT-base e o RoBERTa, que oferecem um leve ganho na similaridade semântica, mas com um custo maior de tempo de inferência. Como tomamos a decisão de manter um tempo de resposta baixo na API, escolhemos o MiniLM.
Se o tempo de resposta e o custo não fossem um problema, teríamos escolhido o ColBERT + Multi-Armed Bandids (RL).
O ColBERT melhora drasticamente a busca de similaridade com um modelo mais refinado do que MiniLM. O Multi-Armed Bandits permitiria um sistema de autoaprendizagem que testa recomendações e se ajusta dinamicamente. E além disso ainda poderíamos combinar Clusterização + RL + embeddings de um modelo potente, resultando em um sistema de recomendação mais robusto.
- Aplicamos K-Means nos embeddings para agrupar notícias semelhantes.
- O número de clusters foi determinado com o método do cotovelo.
- Cada notícia pertence a um cluster, permitindo recomendações diversificadas dentro de grupos similares.
Quando um usuário não tem histórico de leitura, usamos a seguinte estratégia:
- Notícias Populares: Top-N notícias com mais acessos.
- Notícias Recentes: Mais novas no período de 3 dias.
- Notícias Explorátorias: Sorteio de clusters para diversidade.
O score final de cada notícia é definido por:
Onde:
O pipeline do projeto foi dividido em quatro etapas principais: processamento, treinamento dos embeddings, clusterização e implementação da API.
ATENÇÃO: Faça o download dos dados dos itens e dos treinos no link abaixo e coloque na pasta data/itens e data/treino.
Link para Download: https://drive.google.com/file/d/13rvnyK5PJADJQgYe-VbdXb7PpLPj7lPr/view
Arquivo: scripts/processa_dados.py
Objetivo: Limpar e organizar os dados brutos das notícias.
Principais etapas:
- Remoção de dados inválidos e tratamento de nulos.
- Extração de informações estruturadas (ex: título, categoria).
- Geração do dataset
itens_processados.csv
utilizado nos modelos.
Execução:
python scripts/processa_dados.py
Arquivo: scripts/treina_modelo.py
Objetivo: Gerar embeddings das notícias utilizando o modelo all-MiniLM-L6-v2.
Principais etapas:
- Carregamento do dataset
itens_processados.csv
. - Aplicação do modelo para transformar as notícias em vetores.
- Salvamento dos embeddings em
embeddings_noticias.pkl
.
Execução:
python scripts/treina_modelo.py
Arquivo: scripts/clusteriza_noticias.py
Objetivo: Aplicar K-Means nos embeddings para agrupar notícias em clusters.
Principais etapas:
- Carregamento dos embeddings
embeddings_noticias.pkl
. - Aplicação do algoritmo K-Means e determinação do melhor número de clusters usando o método do cotovelo.
- Salvamento do modelo de clusters em
embeddings_noticias_clusterizados.pkl
.
Execução:
python scripts/clusteriza_noticias.py
Arquivo: tests/test_api.py
Objetivo: Validar o funcionamento dos endpoints da API.
Principais testes implementados:
- Verificação da resposta do endpoint
/recomendacao/similar/{noticia_id}
. - Testes do endpoint
/recomendacao/coldstart
para novos usuários. - Testes do endpoint
/recomendacao/novidades
para avaliar notícias recentes.
Execução dos testes:
python tests/test_api.py
A API foi construída usando FastAPI e empacotada com Docker para facilitação do deploy.
- GET /recomendacao/similar/{noticia_id} → Retorna notícias similares.
- GET /recomendacao/coldstart → Recomendação para novos usuários.
- GET /recomendacao/novidades → Recomendação de notícias recentes.
Execução no Servidor:
uvicorn scripts.api_recomendacao:app --host 0.0.0.0 --port 8000
Para produção, utilizamos Gunicorn:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker api_recomendacao:app
O sistema foi empacotado em um container Docker:
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
RUN mkdir -p /app/processed
COPY ./requirements.txt ./requirements.txt
COPY ./scripts/api_recomendacao.py ./api_recomendacao.py
COPY ./gunicorn_config.py ./gunicorn_config.py
COPY ./processed /app/processed/
RUN apt-get update && apt-get install -y git
RUN pip install --no-cache-dir --timeout=120 --retries=10 -r requirements.txt
EXPOSE 8000
CMD ["gunicorn", "-c", "gunicorn_config.py", "api_recomendacao:app"]
Execução do Container:
docker build -t recomendacao-globo .
docker run -p 8000:8000 recomendacao-globo
- Latência esperada: Menos de 200ms por requisição.
- Validação manual: Testes qualitativos foram feitos verificando se as recomendações retornadas fazem sentido com base no contexto e categoria das notícias.
- Conseguimos criar um sistema de recomendação eficiente e escalável.
- Para o futuro poderia ser incluídas melhorias com aprendizado por reforço para otimizar as recomendações dinâmicas.