NLP_Homework_1 / src /text_to_vector.py
Kolesnikov Dmitry
feat: Попытка навайбкодить 3 и 4 лабораторные
68545bc
"""
Модуль для векторизации текстов для кластеризации.
Использует модели из ЛР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}")