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