150 KiB
150 KiB
Импорт библиотек¶
In [125]:
import os
import re
import numpy as np
import pandas as pd
from tqdm import tqdm
# Работа с .docx
import docx
import textract
# Обработка текста
import spacy
import nltk
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import TfidfVectorizer
# Инициализация
nltk.download('punkt')
nltk.download('stopwords')
nlp = spacy.load("ru_core_news_sm")
1. Загрузка данных¶
In [107]:
def extract_text_from_docx(path):
doc = docx.Document(path)
return "\n".join([p.text for p in doc.paragraphs])
def extract_text_from_doc(path):
return textract.process(path).decode('utf-8')
# Сбор всех документов
folder_path = '../data/pmu/'
documents = []
filenames = []
for filename in tqdm(os.listdir(folder_path)):
full_path = os.path.join(folder_path, filename)
try:
if filename.endswith('.docx'):
text = extract_text_from_docx(full_path)
elif filename.endswith('.doc'):
text = extract_text_from_doc(full_path)
else:
continue
documents.append(text)
filenames.append(filename)
except Exception as e:
print(f"Ошибка при обработке {filename}: {e}")
df = pd.DataFrame({'filename': filenames, 'text': documents})
df.head()
Out[107]:
2. Предобработка текста¶
In [124]:
def clean_text(text):
text = text.lower()
# Удаление латиницы
text = re.sub(r"[a-zA-Z]+", "", text)
# Удаление спецсимволов и чисел
text = re.sub(r"[^а-яА-ЯёЁ\s]", "", text)
return text
df['clean_text'] = df['text'].apply(clean_text)
3. Токенизация, лемматизация, удаление стоп-слов¶
In [109]:
stop_words = set(stopwords.words('russian'))
def preprocess_spacy(text):
doc = nlp(text)
return " ".join([token.lemma_ for token in doc if token.is_alpha and token.text not in stop_words])
tqdm.pandas()
df['lemmatized'] = df['clean_text'].progress_apply(preprocess_spacy)
4. Векторизация: TF-IDF¶
In [123]:
from scipy import sparse
vectorizer = TfidfVectorizer()
counts_matrix = sparse.csr_matrix(vectorizer.fit_transform(df["lemmatized"]))
words = vectorizer.get_feature_names_out()
df["vector"] = df.apply(lambda row: counts_matrix.toarray()[row.name], axis=1)
X = np.vstack(df["vector"].values) # Объединяем в единый массив
df
Out[123]:
5. Кластеризация: KMeans¶
In [126]:
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.decomposition import TruncatedSVD
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# 1. Поиск оптимального k (число кластеров)
inertia = []
silhouette_scores = []
K = range(2, len(df) // 2) # Пробуем разное кол-во кластеров
for k in K:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
inertia.append(kmeans.inertia_)
silhouette_scores.append(silhouette_score(X, kmeans.labels_))
# 2. Выбор лучшего k по метрике силуэта
best_k = K[np.argmax(silhouette_scores)]
print(f"Оптимальное количество кластеров: {best_k}")
# 3. Повторная кластеризация с оптимальным числом кластеров
kmeans = KMeans(n_clusters=best_k, random_state=42)
clusters = kmeans.fit_predict(X)
df['cluster'] = clusters
# 4. Визуализация: метод локтя и силуэт
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(K, inertia, marker='o')
plt.xlabel('Количество кластеров')
plt.ylabel('Inertia')
plt.title('Метод локтя')
plt.subplot(1, 2, 2)
plt.plot(K, silhouette_scores, marker='o')
plt.xlabel('Количество кластеров')
plt.ylabel('Silhouette Score')
plt.title('Silhouette-анализ')
plt.tight_layout()
plt.show()
# 5. Визуализация кластеров (через SVD)
svd = TruncatedSVD(n_components=2)
reduced = svd.fit_transform(X)
plt.figure(figsize=(8,6))
sns.scatterplot(x=reduced[:,0], y=reduced[:,1], hue=clusters, palette='tab10')
plt.title(f'Кластеризация с KMeans (k={best_k})')
plt.show()
Выводим список работ по кластерам¶
In [127]:
sims = df[['filename', 'cluster']]
for sim_cluster in range(best_k):
print('Cluster', sim_cluster)
sim_files = sims.loc[sims['cluster'] == sim_cluster]
print(*sim_files['filename'], sep='\n')
print()
Вычислим косинусное расстояние для схожих работ¶
In [139]:
from sklearn.metrics.pairwise import cosine_similarity
SIMILARITY_THRESHOLD = 0.8
# Для каждого кластера:
for cluster_id in sorted(df['cluster'].unique()):
cluster_indices = df[df['cluster'] == cluster_id].index
cluster_texts = df.loc[cluster_indices, 'filename'].values
cluster_vectors = X[cluster_indices]
# Вычислим попарное сходство
similarity_matrix = cosine_similarity(cluster_vectors)
# Найдём наиболее похожие пары документов
n_top_pairs = 10 # можно увеличить
counted = 0
printed_pairs = set()
for i in range(len(cluster_indices)):
for j in range(i + 1, len(cluster_indices)):
sim = similarity_matrix[i, j]
if sim > SIMILARITY_THRESHOLD: # порог похожести
doc1 = cluster_texts[i].replace('\n', ' ')
doc2 = cluster_texts[j].replace('\n', ' ')
pair_id = (min(i,j), max(i,j))
if pair_id not in printed_pairs:
if counted == 0:
print(f"\n🔷 Кластер {cluster_id} — похожие документы:\n")
print(f"🔸 Пара {counted + 1} (похожесть: {sim:.2f}):")
print(f" 📄 Документ 1: {doc1}")
print(f" 📄 Документ 2: {doc2}\n")
counted += 1
printed_pairs.add(pair_id)
if counted >= n_top_pairs:
break
if counted >= n_top_pairs:
break