Lección 5 — Construire le moteur

Produits similaires et dédoublonnage

5 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Produits similaires et dédoublonnage

Une fiche contre les autres

Pour recommander des produits proches d'une fiche, on prend son propre vecteur comme requête. Aucun appel au modèle : tout est déjà dans vecteurs.npy.

files/moteur.py (extrait)
def similaires(vecteurs, produits, produit_id, k=4):
    ligne = next(p["ligne"] for p in produits if p["id"] == produit_id)
    return [(produits[i], s) for i, s in top_k(vecteurs, vecteurs[ligne], k, exclure=[ligne])]

On exclut la fiche elle-même, sinon elle serait toujours première avec un score de 1.

Toutes les paires d'un coup

Pour trouver les doublons, on compare chaque fiche à chaque autre : la matrice vecteurs @ vecteurs.T contient les 60 × 60 cosinus. On garde le triangle supérieur (chaque paire une fois, sans la diagonale) au-dessus d'un seuil.

files/moteur.py (extrait)
def doublons(vecteurs, seuil=0.9):
    """Paires (i, j, score) au-dessus du seuil. Limite : O(n²), passer à un index ANN au-delà de ~100 000 fiches."""
    sim = vecteurs @ vecteurs.T
    i, j = np.where(np.triu(sim, k=1) >= seuil)
    return sorted(((int(a), int(b), float(sim[a, b])) for a, b in zip(i, j)), key=lambda x: -x[2])
flowchart LR
  V["vecteurs (60, 768)"] --> S["sim = V @ V.T (60 x 60)"]
  S --> T["triangle supérieur, sans diagonale"]
  T --> F{"score >= seuil ?"}
  F -- "oui" --> D["Paire candidate au doublon"]
  F -- "non" --> X["Ignorée"]
  D --> H["Revue humaine avant fusion"]

Choisir le seuil

Il n'existe pas de seuil universel : il dépend du modèle, du formatage des textes et de votre catalogue. La méthode : regarder la distribution des meilleures paires et chercher une cassure.

Rang de la paireScoreNature
10,978doublon (P037 / P060, tente)
20,950doublon (P019 / P059, casque)
30,936doublon (P001 / P058, chaussure de trail)
40,797produits différents de même famille
50,792produits différents de même famille

Ici, une cassure nette entre 0,936 et 0,797 : n'importe quel seuil entre 0,80 et 0,93 trouve exactement les 3 doublons.

Pourquoi exclure la fiche elle-même dans similaires() ?

Combien d'appels au modèle d'embeddings coûte la détection des doublons sur l'index existant ?

Exercice : produits complémentaires

« Similaires » propose des produits substituables (un autre sac à dos). Comment proposer des produits complémentaires (avec une tente : un sac de couchage, une frontale) en réutilisant top_k ?

Voir la solution

Une piste simple : chercher les voisins dans d'autres catégories que le produit consulté.

def complementaires(vecteurs, produits, produit_id, k=4):
    p = next(p for p in produits if p["id"] == produit_id)
    meme_categorie = [x["ligne"] for x in produits if x["categorie"] == p["categorie"]]
    return [(produits[i], s) for i, s in top_k(vecteurs, vecteurs[p["ligne"]], k, exclure=meme_categorie)]

Pour la tente (Camping), cela exclut les autres articles de camping et remonte des produits d'autres rayons utilisés dans le même contexte (sac de randonnée, chaussures de montagne). En production, les vraies données de co-achat (« achetés ensemble ») sont souvent un meilleur signal ; les embeddings servent alors au démarrage, quand ces données n'existent pas encore.

Résultat obtenu

Exécutions réelles le 2026-10-15 (gemini-embedding-2, 768 dimensions) :

$ python moteur.py similaires P044
0.792  P048  Sac à dos randonnée Crête 45
0.754  P045  Sac de voyage Weekend
0.732  P043  Valise cabine Escale 55
0.721  P047  Pèse-bagage Envol
$ python moteur.py doublons
0.978  P037 Tente Dôme 3 places  <->  P060 Tente Dôme 3 personnes
0.950  P019 Casque Silence Pro  <->  P059 Casque Silence Pro - édition noire
0.936  P001 Trailrun Gecko  <->  P058 Trail Gecko (coloris forêt)
$ python moteur.py doublons 0.8
0.978  P037 Tente Dôme 3 places  <->  P060 Tente Dôme 3 personnes
0.950  P019 Casque Silence Pro  <->  P059 Casque Silence Pro - édition noire
0.936  P001 Trailrun Gecko  <->  P058 Trail Gecko (coloris forêt)

Les 3 quasi-doublons placés volontairement dans produits.json sont trouvés, et aucun faux positif même au seuil de 0,8.

Je sais faire

  • Recommander des produits similaires à partir du vecteur d'une fiche
  • Calculer toutes les similarités d'un catalogue avec un produit de matrices
  • Choisir un seuil de doublon en regardant la distribution des scores
  • Expliquer pourquoi le dédoublonnage reste une proposition à valider
Siguiente