Spaces:
Sleeping
Sleeping
| """ | |
| Модуль для векторизации текстов для кластеризации. | |
| Использует модели из ЛР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}") | |