95 lines
No EOL
4.1 KiB
Python
95 lines
No EOL
4.1 KiB
Python
import numpy as np
|
|
from sklearn.metrics.pairwise import cosine_similarity
|
|
from typing import List, Optional, Tuple, Dict
|
|
import logging
|
|
|
|
from .data_loader import load_embeddings, get_user_articles
|
|
|
|
def _calculate_mean_embedding(embeddings: np.ndarray, article_ids: List[int]) -> np.ndarray:
|
|
"""Calcule la moyenne des embeddings pour une liste d'articles."""
|
|
valid_ids = [aid for aid in article_ids if aid < len(embeddings)]
|
|
if not valid_ids:
|
|
raise ValueError("No valid article IDs found")
|
|
|
|
articles_embeddings = embeddings[valid_ids]
|
|
return np.mean(articles_embeddings, axis=0).reshape(1, -1)
|
|
|
|
def _compute_similarities(reference_embedding: np.ndarray, embeddings: np.ndarray) -> np.ndarray:
|
|
"""Calcule la similarité cosinus entre l'embedding de référence et tous les embeddings."""
|
|
return cosine_similarity(reference_embedding, embeddings)[0]
|
|
|
|
def _filter_candidates(similarities: np.ndarray, clicked_articles: List[int]) -> List[Tuple[int, float]]:
|
|
"""Filtre les candidats en excluant les articles déjà vus."""
|
|
candidates = []
|
|
for article_id, similarity in enumerate(similarities):
|
|
if article_id not in clicked_articles:
|
|
candidates.append((article_id, similarity))
|
|
return candidates
|
|
|
|
def _get_top_recommendations_with_scores(candidates: List[Tuple[int, float]], n_recommendations: int) -> List[Dict[str, any]]:
|
|
"""Trie les candidats et retourne le top N avec les scores."""
|
|
candidates.sort(key=lambda x: x[1], reverse=True)
|
|
recommendations = []
|
|
for article_id, score in candidates[:n_recommendations]:
|
|
recommendations.append({
|
|
'article_id': article_id,
|
|
'similarity_score': float(score)
|
|
})
|
|
return recommendations
|
|
|
|
def _validate_user_articles(clicked_articles: List[int], embeddings: np.ndarray) -> List[int]:
|
|
"""Valide et filtre les articles dans les limites des embeddings."""
|
|
valid_articles = [art for art in clicked_articles if 0 <= art < len(embeddings)]
|
|
if not valid_articles:
|
|
raise ValueError("No valid articles found for user")
|
|
return valid_articles
|
|
|
|
def get_recommendations(user_id: int, n_recommendations: int = 5) -> Optional[List[int]]:
|
|
"""
|
|
Génère des recommandations pour un utilisateur donné (version compatibilité).
|
|
Retourne seulement les IDs pour maintenir la compatibilité.
|
|
"""
|
|
recommendations_with_scores = get_recommendations_with_scores(user_id, n_recommendations)
|
|
if recommendations_with_scores:
|
|
return [rec['article_id'] for rec in recommendations_with_scores]
|
|
return None
|
|
|
|
def get_recommendations_with_scores(user_id: int, n_recommendations: int = 5) -> Optional[List[Dict[str, any]]]:
|
|
"""
|
|
Génère des recommandations avec scores pour un utilisateur donné.
|
|
|
|
Args:
|
|
user_id: ID de l'utilisateur
|
|
n_recommendations: Nombre de recommandations à retourner
|
|
|
|
Returns:
|
|
Liste des recommandations avec article_id et similarity_score ou None si erreur
|
|
"""
|
|
try:
|
|
# Charger les données
|
|
embeddings = load_embeddings()
|
|
clicked_articles = get_user_articles(user_id)
|
|
|
|
if not clicked_articles:
|
|
logging.warning(f"No articles found for user {user_id}")
|
|
return None
|
|
|
|
# Valider les articles
|
|
valid_articles = _validate_user_articles(clicked_articles, embeddings)
|
|
|
|
# Calculer l'embedding de référence (stratégie mean)
|
|
reference_embedding = _calculate_mean_embedding(embeddings, valid_articles)
|
|
|
|
# Calculer similarités
|
|
similarities = _compute_similarities(reference_embedding, embeddings)
|
|
|
|
# Filtrer et trier
|
|
candidates = _filter_candidates(similarities, clicked_articles)
|
|
recommendations = _get_top_recommendations_with_scores(candidates, n_recommendations)
|
|
|
|
logging.info(f"Generated {len(recommendations)} recommendations with scores for user {user_id}")
|
|
return recommendations
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error generating recommendations for user {user_id}: {str(e)}")
|
|
return None |