File size: 13,466 Bytes
68545bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
"""
Модуль для векторизации текстов для кластеризации.
Использует модели из ЛР2: Word2Vec, FastText, GloVe, а также TF-IDF и BM25.
"""

from __future__ import annotations

import os
from typing import List, Dict, Any, Optional, Tuple
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import normalize

try:
    from rank_bm25 import BM25Okapi
    BM25_AVAILABLE = True
except ImportError:
    BM25_AVAILABLE = False
    print("⚠️ rank-bm25 не установлен. BM25 недоступен. Установите: pip install rank-bm25")

from gensim.models import Word2Vec, FastText, Doc2Vec
from gensim.utils import simple_preprocess

from src.classical_vectorizers import ClassicalVectorizers, VectorizationConfig


def load_embedding_model(model_path: str):
    """
    Загружает обученную модель эмбеддингов из ЛР2.
    
    Args:
        model_path: Путь к модели
    
    Returns:
        Загруженная модель (Word2Vec, FastText или Doc2Vec)
    """
    if not os.path.exists(model_path):
        raise FileNotFoundError(f"Модель не найдена: {model_path}")
    
    # Пробуем загрузить как Word2Vec
    try:
        return Word2Vec.load(model_path)
    except:
        pass
    
    # Пробуем загрузить как FastText
    try:
        return FastText.load(model_path)
    except:
        pass
    
    # Пробуем загрузить как Doc2Vec
    try:
        return Doc2Vec.load(model_path)
    except:
        pass
    
    raise ValueError(f"Не удалось загрузить модель из {model_path}")


def vectorize_tfidf(texts: List[str],
                    max_features: Optional[int] = None,
                    ngram_range: Tuple[int, int] = (1, 2),
                    normalize_vectors: bool = True) -> Tuple[np.ndarray, Any]:
    """
    Векторизация текстов с помощью TF-IDF.
    
    Args:
        texts: Список текстов
        max_features: Максимальное количество признаков
        ngram_range: Диапазон n-грамм
        normalize_vectors: Нормализовать ли векторы (L2)
    
    Returns:
        Матрица векторов и векторизатор
    """
    vectorizer = TfidfVectorizer(
        max_features=max_features,
        ngram_range=ngram_range,
        lowercase=True,
        min_df=1
    )
    
    X = vectorizer.fit_transform(texts).toarray()
    
    if normalize_vectors:
        X = normalize(X, norm='l2')
    
    return X, vectorizer


def vectorize_bm25(texts: List[str],
                  tokenize: bool = True) -> Tuple[np.ndarray, Any]:
    """
    Векторизация текстов с помощью BM25.
    
    Args:
        texts: Список текстов
        tokenize: Токенизировать ли тексты
    
    Returns:
        Матрица векторов и BM25 объект
    """
    if not BM25_AVAILABLE:
        raise ImportError("rank-bm25 не установлен. Установите: pip install rank-bm25")
    
    if tokenize:
        tokenized_texts = [simple_preprocess(text, deacc=False, min_len=1) for text in texts]
    else:
        tokenized_texts = [text.split() for text in texts]
    
    bm25 = BM25Okapi(tokenized_texts)
    
    # Создаем матрицу BM25 для всех документов
    X = np.array([bm25.get_scores(doc) for doc in tokenized_texts])
    
    # Нормализуем
    X = normalize(X, norm='l2')
    
    return X, bm25


def vectorize_with_word2vec(texts: List[str],
                            model: Word2Vec,
                            aggregation: str = "mean",
                            normalize_vectors: bool = True) -> np.ndarray:
    """
    Векторизация текстов с помощью Word2Vec модели из ЛР2.
    
    Args:
        texts: Список текстов
        model: Обученная Word2Vec модель
        aggregation: Метод агрегации (mean, max, sum)
        normalize_vectors: Нормализовать ли векторы (L2)
    
    Returns:
        Матрица векторов документов
    """
    kv = model.wv
    vector_size = kv.vector_size
    vectors = []
    
    for text in texts:
        tokens = simple_preprocess(text, deacc=False, min_len=1)
        word_vectors = []
        
        for token in tokens:
            if token in kv:
                word_vectors.append(kv[token])
        
        if not word_vectors:
            vectors.append(np.zeros(vector_size))
            continue
        
        word_vectors = np.array(word_vectors)
        
        if aggregation == "mean":
            doc_vector = np.mean(word_vectors, axis=0)
        elif aggregation == "max":
            doc_vector = np.max(word_vectors, axis=0)
        elif aggregation == "sum":
            doc_vector = np.sum(word_vectors, axis=0)
        else:
            doc_vector = np.mean(word_vectors, axis=0)
        
        vectors.append(doc_vector)
    
    X = np.array(vectors)
    
    if normalize_vectors:
        X = normalize(X, norm='l2')
    
    return X


def vectorize_with_fasttext(texts: List[str],
                           model: FastText,
                           aggregation: str = "mean",
                           normalize_vectors: bool = True) -> np.ndarray:
    """
    Векторизация текстов с помощью FastText модели из ЛР2.
    
    Args:
        texts: Список текстов
        model: Обученная FastText модель
        aggregation: Метод агрегации (mean, max, sum)
        normalize_vectors: Нормализовать ли векторы (L2)
    
    Returns:
        Матрица векторов документов
    """
    kv = model.wv
    vector_size = kv.vector_size
    vectors = []
    
    for text in texts:
        tokens = simple_preprocess(text, deacc=False, min_len=1)
        word_vectors = []
        
        for token in tokens:
            # FastText может обрабатывать OOV слова
            if token in kv:
                word_vectors.append(kv[token])
            else:
                # Получаем вектор для OOV слова
                word_vectors.append(kv.get_vector(token))
        
        if not word_vectors:
            vectors.append(np.zeros(vector_size))
            continue
        
        word_vectors = np.array(word_vectors)
        
        if aggregation == "mean":
            doc_vector = np.mean(word_vectors, axis=0)
        elif aggregation == "max":
            doc_vector = np.max(word_vectors, axis=0)
        elif aggregation == "sum":
            doc_vector = np.sum(word_vectors, axis=0)
        else:
            doc_vector = np.mean(word_vectors, axis=0)
        
        vectors.append(doc_vector)
    
    X = np.array(vectors)
    
    if normalize_vectors:
        X = normalize(X, norm='l2')
    
    return X


def vectorize_with_doc2vec(texts: List[str],
                          model: Doc2Vec,
                          normalize_vectors: bool = True) -> np.ndarray:
    """
    Векторизация текстов с помощью Doc2Vec модели из ЛР2.
    
    Args:
        texts: Список текстов
        model: Обученная Doc2Vec модель
        normalize_vectors: Нормализовать ли векторы (L2)
    
    Returns:
        Матрица векторов документов
    """
    vectors = []
    
    for text in texts:
        tokens = simple_preprocess(text, deacc=False, min_len=1)
        if tokens:
            vec = model.infer_vector(tokens)
        else:
            vec = np.zeros(model.vector_size)
        vectors.append(vec)
    
    X = np.array(vectors)
    
    if normalize_vectors:
        X = normalize(X, norm='l2')
    
    return X


def vectorize_with_glove(texts: List[str],
                         model_path: str,
                         aggregation: str = "mean",
                         normalize_vectors: bool = True) -> np.ndarray:
    """
    Векторизация текстов с помощью GloVe модели из ЛР2.
    
    Примечание: GloVe обычно хранится в формате текстового файла или через gensim.
    Здесь предполагается, что модель загружена через gensim или аналогичный интерфейс.
    
    Args:
        texts: Список текстов
        model_path: Путь к модели GloVe
        aggregation: Метод агрегации (mean, max, sum)
        normalize_vectors: Нормализовать ли векторы (L2)
    
    Returns:
        Матрица векторов документов
    """
    # Пробуем загрузить как KeyedVectors (если сохранено через gensim)
    try:
        from gensim.models import KeyedVectors
        kv = KeyedVectors.load(model_path)
    except:
        # Если не получилось, пробуем загрузить как Word2Vec (совместимость)
        try:
            model = Word2Vec.load(model_path)
            kv = model.wv
        except:
            raise ValueError(f"Не удалось загрузить GloVe модель из {model_path}")
    
    vector_size = kv.vector_size
    vectors = []
    
    for text in texts:
        tokens = simple_preprocess(text, deacc=False, min_len=1)
        word_vectors = []
        
        for token in tokens:
            if token in kv:
                word_vectors.append(kv[token])
        
        if not word_vectors:
            vectors.append(np.zeros(vector_size))
            continue
        
        word_vectors = np.array(word_vectors)
        
        if aggregation == "mean":
            doc_vector = np.mean(word_vectors, axis=0)
        elif aggregation == "max":
            doc_vector = np.max(word_vectors, axis=0)
        elif aggregation == "sum":
            doc_vector = np.sum(word_vectors, axis=0)
        else:
            doc_vector = np.mean(word_vectors, axis=0)
        
        vectors.append(doc_vector)
    
    X = np.array(vectors)
    
    if normalize_vectors:
        X = normalize(X, norm='l2')
    
    return X


def vectorize_texts(texts: List[str],
                   method: str = "tfidf",
                   model_path: Optional[str] = None,
                   **kwargs) -> Tuple[np.ndarray, Any]:
    """
    Универсальная функция векторизации текстов.
    
    Args:
        texts: Список текстов
        method: Метод векторизации (tfidf, bm25, w2v, fasttext, doc2vec, glove)
        model_path: Путь к модели (для w2v, fasttext, doc2vec, glove)
        **kwargs: Дополнительные параметры
    
    Returns:
        Матрица векторов и объект векторизатора/модели
    """
    method = method.lower()
    
    if method == "tfidf":
        return vectorize_tfidf(texts, **kwargs)
    
    elif method == "bm25":
        return vectorize_bm25(texts, **kwargs)
    
    elif method == "w2v" or method == "word2vec":
        if model_path is None:
            raise ValueError("Для Word2Vec требуется model_path")
        model = load_embedding_model(model_path)
        X = vectorize_with_word2vec(texts, model, **kwargs)
        return X, model
    
    elif method == "fasttext":
        if model_path is None:
            raise ValueError("Для FastText требуется model_path")
        model = load_embedding_model(model_path)
        X = vectorize_with_fasttext(texts, model, **kwargs)
        return X, model
    
    elif method == "doc2vec" or method == "d2v":
        if model_path is None:
            raise ValueError("Для Doc2Vec требуется model_path")
        model = load_embedding_model(model_path)
        X = vectorize_with_doc2vec(texts, model, **kwargs)
        return X, model
    
    elif method == "glove":
        if model_path is None:
            raise ValueError("Для GloVe требуется model_path")
        X = vectorize_with_glove(texts, model_path, **kwargs)
        return X, None
    
    else:
        raise ValueError(f"Неизвестный метод векторизации: {method}")


if __name__ == "__main__":
    # Тестирование
    sample_texts = [
        "Это первый тестовый текст для проверки векторизации",
        "Второй текст содержит другую информацию",
        "Третий текст также используется для тестирования"
    ]
    
    # TF-IDF
    X_tfidf, vectorizer = vectorize_tfidf(sample_texts)
    print(f"TF-IDF векторы: форма {X_tfidf.shape}")
    
    # BM25 (если доступен)
    if BM25_AVAILABLE:
        X_bm25, bm25 = vectorize_bm25(sample_texts)
        print(f"BM25 векторы: форма {X_bm25.shape}")