klydekushy commited on
Commit
dcd34fc
·
verified ·
1 Parent(s): 307b487

Update src/Algorithms/vector_search.py

Browse files
Files changed (1) hide show
  1. src/Algorithms/vector_search.py +75 -52
src/Algorithms/vector_search.py CHANGED
@@ -1,72 +1,95 @@
1
  """
2
- MODULE VECTOR SEARCH - HYBRID ENGINE
3
- ====================================
4
- Indexe le graphe pour permettre la recherche sémantique et par type.
5
  """
6
- import pandas as pd
7
- from sklearn.feature_extraction.text import TfidfVectorizer
8
- from sklearn.metrics.pairwise import cosine_similarity
9
  import numpy as np
 
 
 
10
 
11
- class GraphVectorEngine:
12
- def __init__(self):
13
- self.vectorizer = TfidfVectorizer(stop_words='english')
14
- self.vectors = None
15
- self.node_ids = []
16
- self.node_data = []
 
17
  self.is_ready = False
18
 
19
- def index_graph(self, G):
20
- """Transforme chaque nœud du graphe en document textuel searchable"""
21
- self.node_ids = []
22
- self.node_data = []
 
 
23
  corpus = []
 
 
24
 
25
- for node_id, data in G.nodes(data=True):
26
- # On crée une "soupe" de texte avec toutes les valeurs du nœud
27
- # Ex: "CLI-2026-001 Jean Dupont Dakar Responsable Commercial"
28
- text_content = f"{node_id} "
29
- text_content += " ".join([str(v) for k, v in data.items()
30
- if k not in ['color', 'size', 'shape', 'x', 'y', 'title']])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- self.node_ids.append(node_id)
33
- self.node_data.append(data)
34
- corpus.append(text_content.lower())
 
 
 
35
 
36
- if corpus:
37
- self.vectors = self.vectorizer.fit_transform(corpus)
38
- self.is_ready = True
39
- return f"✅ Indexation vectorielle terminée : {len(self.node_ids)} entités."
40
- return "⚠️ Graphe vide, indexation impossible."
 
 
 
 
 
41
 
42
- def search_semantic(self, query, top_k=5, threshold=0.1):
43
- """Recherche vectorielle (similitude de texte)"""
44
  if not self.is_ready:
45
  return []
46
 
47
- query_vec = self.vectorizer.transform([query.lower()])
48
- cosine_sim = cosine_similarity(query_vec, self.vectors).flatten()
49
 
50
- # Récupérer les indices des meilleurs scores
51
- related_docs_indices = cosine_sim.argsort()[:-top_k-1:-1]
52
 
53
  results = []
54
- for idx in related_docs_indices:
55
- score = cosine_sim[idx]
56
- if score > threshold:
57
  results.append({
58
- "id": self.node_ids[idx],
59
- "score": round(score, 2),
60
- "type": self.node_data[idx].get('group', 'Inconnu'),
61
- "label": self.node_data[idx].get('label', self.node_ids[idx])
62
  })
63
- return results
64
-
65
- def get_all_by_type(self, entity_type):
66
- """Retourne tous les nœuds d'un type précis (ex: 'Garant')"""
67
- results = []
68
- for i, data in enumerate(self.node_data):
69
- # Vérification souple (ex: 'Garant' matche 'Garant_KYC')
70
- if entity_type.lower() in str(data.get('group', '')).lower():
71
- results.append(self.node_ids[i])
72
  return results
 
1
  """
2
+ MODULE: VECTOR SEARCH ENGINE (FAISS + SENTENCE TRANSFORMERS)
3
+ ============================================================
4
+ Responsabilité : Transformer le texte en vecteurs et trouver les points d'entrée sémantiques.
5
  """
6
+ import faiss
 
 
7
  import numpy as np
8
+ from sentence_transformers import SentenceTransformer
9
+ import pickle
10
+ import os
11
 
12
+ class SemanticIndex:
13
+ def __init__(self, model_name='all-MiniLM-L6-v2'):
14
+ # Modèle léger et rapide, parfait pour CPU
15
+ self.model = SentenceTransformer(model_name)
16
+ self.index = None
17
+ self.uris = [] # Stocke les IDs correspondants aux vecteurs
18
+ self.metadatas = []
19
  self.is_ready = False
20
 
21
+ def build_index(self, rdf_graph):
22
+ """
23
+ Parcourt le graphe RDF pour vectoriser chaque entité.
24
+ On crée une 'soupe' de texte : (Type + Label + Propriétés Clés)
25
+ """
26
+ print("⏳ [VECTOR] Embedding generation started...")
27
  corpus = []
28
+ self.uris = []
29
+ self.metadatas = []
30
 
31
+ # On itère sur tous les sujets du graphe RDF qui ont un label
32
+ # Note: On suppose que rdf_manager a déjà peuplé le graphe
33
+ # Ici on simplifie : on s'attend à recevoir une liste de dicts ou on itère le graph
34
+ pass
35
+
36
+ def build_from_networkx(self, G):
37
+ """
38
+ Construit l'index depuis le graphe NetworkX (plus simple car déjà structuré)
39
+ avant la conversion RDF.
40
+ """
41
+ corpus = []
42
+ self.uris = []
43
+
44
+ for node, data in G.nodes(data=True):
45
+ # Construction de la "Signature Sémantique" du nœud
46
+ # Ex: "Garant Jean Dupont Ingénieur Informatique Dakar"
47
+ text_parts = [
48
+ str(data.get('group', '')),
49
+ str(data.get('label', '')),
50
+ str(data.get('Profession', '')),
51
+ str(data.get('Ville', '')),
52
+ str(data.get('Secteur_Activite', '')),
53
+ str(data.get('Commentaires_Notes', ''))
54
+ ]
55
+ # Nettoyage
56
+ text = " ".join([t for t in text_parts if t and t != 'nan']).lower()
57
 
58
+ corpus.append(text)
59
+ self.uris.append(node)
60
+ self.metadatas.append(f"{data.get('group')} - {data.get('label')}")
61
+
62
+ if not corpus:
63
+ return "⚠️ Graphe vide."
64
 
65
+ # Vectorisation (Batch)
66
+ embeddings = self.model.encode(corpus, show_progress_bar=True)
67
+
68
+ # Création Index FAISS
69
+ dimension = embeddings.shape[1]
70
+ self.index = faiss.IndexFlatL2(dimension)
71
+ self.index.add(np.array(embeddings).astype('float32'))
72
+
73
+ self.is_ready = True
74
+ return f"✅ Index FAISS construit : {len(self.uris)} entités vectorisées."
75
 
76
+ def search(self, query, top_k=5):
77
+ """Retourne les URIs les plus proches de la requête"""
78
  if not self.is_ready:
79
  return []
80
 
81
+ # Vectoriser la question
82
+ query_vec = self.model.encode([query.lower()]).astype('float32')
83
 
84
+ # Recherche FAISS
85
+ distances, indices = self.index.search(query_vec, top_k)
86
 
87
  results = []
88
+ for i, idx in enumerate(indices[0]):
89
+ if idx < len(self.uris):
 
90
  results.append({
91
+ "uri": self.uris[idx],
92
+ "meta": self.metadatas[idx],
93
+ "score": float(1 / (1 + distances[0][i])) # Conversion distance -> score sim
 
94
  })
 
 
 
 
 
 
 
 
 
95
  return results