File size: 7,586 Bytes
64b5d29 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# src/analysis/similarity.py
import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import logging
from pathlib import Path
# Yerel modüller
from src.data_management import storage
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Benzerlik matrisini kaydetmek için dosya adı
SIMILARITY_FILENAME = "concept_similarities"
EMBEDDINGS_FILENAME = "concept_embeddings" # Vektörleri de kaydedebiliriz
def calculate_concept_embeddings(model_name: str = 'all-MiniLM-L6-v2', force_recalculate: bool = False) -> dict[str, np.ndarray] | None:
"""
Her konsept için ortalama embedding vektörünü hesaplar.
Mention'ların context_snippet'lerini kullanır.
Hesaplanmış embedding'leri yüklemeye çalışır, yoksa hesaplar.
Args:
model_name (str): Kullanılacak Sentence Transformer modeli.
force_recalculate (bool): Daha önce hesaplanmış olsa bile yeniden hesaplamaya zorla.
Returns:
dict[str, np.ndarray] | None: Concept ID -> Ortalama Embedding Vektörü sözlüğü veya hata durumunda None.
"""
embeddings_filepath = storage.DATA_PATH / f"{EMBEDDINGS_FILENAME}.pkl" # Pickle ile saklayalım
if not force_recalculate and embeddings_filepath.exists():
try:
embeddings = pd.read_pickle(embeddings_filepath)
logging.info(f"Önceden hesaplanmış embedding'ler '{embeddings_filepath}' dosyasından yüklendi.")
# Dosyadan yüklenen bir sözlük olmalı
if isinstance(embeddings, dict):
return embeddings
else:
logging.warning("Yüklenen embedding dosyası beklenen formatta (dict) değil. Yeniden hesaplanacak.")
except Exception as e:
logging.error(f"Embedding'ler yüklenirken hata: {e}. Yeniden hesaplanacak.")
logging.info("Konsept embedding'leri hesaplanıyor...")
mentions_df = storage.load_dataframe('mentions', storage.MENTION_COLUMNS)
if mentions_df is None or mentions_df.empty:
logging.warning("Hesaplama için mention verisi bulunamadı.")
return None
# Geçerli context snippet'i olan mention'ları al
mentions_df.dropna(subset=['context_snippet', 'concept_id'], inplace=True)
if mentions_df.empty:
logging.warning("Geçerli context snippet bulunamadı.")
return None
# Modeli yükle (ilk seferde internetten indirilebilir)
try:
model = SentenceTransformer(model_name)
logging.info(f"Sentence Transformer modeli '{model_name}' yüklendi.")
except Exception as e:
logging.exception(f"Sentence Transformer modeli '{model_name}' yüklenirken hata: {e}")
return None
# Konseptlere göre grupla
grouped_mentions = mentions_df.groupby('concept_id')['context_snippet'].apply(list)
concept_embeddings = {}
logging.info(f"{len(grouped_mentions)} konsept için embedding hesaplanacak...")
# Her konsept için embedding'leri hesapla ve ortalamasını al
for concept_id, snippets in grouped_mentions.items():
if not snippets: continue # Boş snippet listesi varsa atla
try:
# Tüm snippet'ların embedding'lerini tek seferde hesapla (daha verimli)
embeddings = model.encode(snippets, show_progress_bar=False) # İlerleme çubuğunu kapat
# Ortalama embedding'i hesapla
avg_embedding = np.mean(embeddings, axis=0)
concept_embeddings[concept_id] = avg_embedding
except Exception as e:
logging.error(f"Concept ID {concept_id} için embedding hesaplanırken hata: {e}")
continue # Bu konsepti atla
# Hesaplanan embedding'leri kaydet
try:
storage.DATA_PATH.mkdir(parents=True, exist_ok=True)
pd.to_pickle(concept_embeddings, embeddings_filepath)
logging.info(f"Hesaplanan embedding'ler '{embeddings_filepath}' dosyasına kaydedildi.")
except Exception as e:
logging.error(f"Embedding'ler kaydedilirken hata: {e}")
logging.info(f"{len(concept_embeddings)} konsept için ortalama embedding hesaplandı.")
return concept_embeddings
def calculate_similarity_matrix(concept_embeddings: dict, force_recalculate: bool = False) -> pd.DataFrame | None:
"""
Verilen embedding vektörleri arasındaki kosinüs benzerliğini hesaplar.
Hesaplanmış benzerlikleri yüklemeye çalışır, yoksa hesaplar.
Args:
concept_embeddings (dict[str, np.ndarray]): Concept ID -> Embedding Vektörü sözlüğü.
force_recalculate (bool): Daha önce hesaplanmış olsa bile yeniden hesaplamaya zorla.
Returns:
pd.DataFrame | None: 'concept_id_1', 'concept_id_2', 'similarity' sütunlarını
içeren DataFrame veya hata durumunda None.
"""
similarity_filepath = storage.DATA_PATH / f"{SIMILARITY_FILENAME}.parquet"
if not force_recalculate and similarity_filepath.exists():
try:
similarity_df = storage.load_dataframe(SIMILARITY_FILENAME, ['concept_id_1', 'concept_id_2', 'similarity'])
logging.info(f"Önceden hesaplanmış benzerlik matrisi '{similarity_filepath}' dosyasından yüklendi.")
if similarity_df is not None and not similarity_df.empty:
return similarity_df
else:
logging.warning("Yüklenen benzerlik dosyası boş veya hatalı. Yeniden hesaplanacak.")
except Exception as e:
logging.error(f"Benzerlik matrisi yüklenirken hata: {e}. Yeniden hesaplanacak.")
if not concept_embeddings:
logging.error("Benzerlik hesaplamak için embedding verisi bulunamadı.")
return None
logging.info("Konseptler arası benzerlik matrisi hesaplanıyor...")
# Sözlükten sıralı liste ve matris oluştur
concept_ids = list(concept_embeddings.keys())
embedding_matrix = np.array(list(concept_embeddings.values()))
# Boyut kontrolü
if embedding_matrix.ndim != 2 or embedding_matrix.shape[0] != len(concept_ids):
logging.error(f"Embedding matrisinin boyutları ({embedding_matrix.shape}) beklenenden farklı.")
return None
# Kosinüs benzerliğini hesapla
try:
similarity_matrix = cosine_similarity(embedding_matrix)
except Exception as e:
logging.exception(f"Kosinüs benzerliği hesaplanırken hata: {e}")
return None
# Matrisi DataFrame'e dönüştür (uzun format)
similarity_data = []
num_concepts = len(concept_ids)
for i in range(num_concepts):
for j in range(i + 1, num_concepts): # Sadece üçgenin üstünü al (j > i) ve kendini (i=j) atla
similarity_data.append({
'concept_id_1': concept_ids[i],
'concept_id_2': concept_ids[j],
'similarity': similarity_matrix[i, j]
})
similarity_df = pd.DataFrame(similarity_data)
if similarity_df.empty:
logging.warning("Hesaplama sonucu benzerlik verisi üretilemedi.")
# Boş DataFrame kaydetmeyelim, None döndürelim
return None
# Hesaplanan benzerlikleri kaydet
storage.save_dataframe(similarity_df, SIMILARITY_FILENAME)
logging.info(f"Benzerlik matrisi hesaplandı ve kaydedildi. {len(similarity_df)} çift.")
return similarity_df |