ChronoSense / similarity.py
NextGenC's picture
Upload 27 files
64b5d29 verified
raw
history blame contribute delete
7.59 kB
# 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