56 KiB
1. Постановка задачи анализа текстовой информации¶
Цель эксперимента — исследовать влияние этапов предобработки, морфологического анализа, фильтрации, нормализации и индексирования на качество классификации текстов.
В качестве входных данных используется новый набор из 10 текстов (документы Word), содержащих различное тематическое содержание. Каждому тексту сопоставлена метка класса.
Задача: построить pipeline по обработке текстов и классификации на основе содержимого с применением методов машинного обучения.
import os
import pandas as pd
import re
import nltk
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin
import spacy
import win32com.client
nltk.download('stopwords')
nlp = spacy.load('ru_core_news_sm')
2. Подготовка текста: загрузка файлов и формирование набора данных¶
Для начала загружаем все документы в формате .doc из выбранной директории с помощью COM-интерфейса Windows.
Из каждого файла извлекаем содержимое и составляем DataFrame, где каждой строке соответствует текст и его метка.
Предобработка текста — это совокупность шагов, направленных на очистку и упрощение исходного материала перед анализом. Основная задача — устранение лишней информации и приведение текста к единой форме.
Цель этапа: Сделать текстовые данные максимально удобными и стандартизированными для дальнейшего анализа.
Ключевые шаги предобработки:
- Удаление лишних символов, таких как знаки препинания, специальные символы и т.д.
- Приведение всех слов к нижнему регистру
- Очистка от лишних пробелов, табуляций, символов перевода строки
- Исключение стоп-слов — слов, не несущих смысловой нагрузки (например, "а", "но", "в", "на")
- Часто применяется токенизация — разделение текста на отдельные слова или элементы
Пример преобразования: Фраза "Это пример! текста, для анализа." преобразуется в: "это пример текста для анализа"
data_path = os.path.abspath("../static/tz_itdocs")
if not os.path.exists(data_path):
raise FileNotFoundError(f"Папка {data_path} не найдена.")
word = win32com.client.Dispatch("Word.Application")
word.Visible = False
texts = []
filenames = []
for filename in os.listdir(data_path):
if filename.endswith(".doc"):
file_path = os.path.join(data_path, filename)
try:
doc = word.Documents.Open(file_path)
text = doc.Content.Text
texts.append(text)
filenames.append(filename)
doc.Close(SaveChanges=False)
except Exception as e:
print(f"Ошибка при чтении файла {filename}: {e}")
word.Quit()
print(f"Загружено документов: {len(texts)}")
df = pd.DataFrame({'filename': filenames, 'text': texts})
3. Анализ частей речи и морфологических признаков¶
На данном этапе выполняется морфологический анализ текстов с использованием spaCy. Каждое слово анализируется с точки зрения грамматических свойств (часть речи, склонение, падеж и пр.).
Цель — проверить, как использование морфологических признаков влияет на точность модели. Сравним классификацию текста с лемматизацией и без неё.
Также можно визуализировать распределение частей речи по текстам.
import spacy
import matplotlib.pyplot as plt
nlp = spacy.load("ru_core_news_sm")
# Функция для анализа POS
def analyze_pos(texts):
pos_counts = {}
for doc in texts:
doc_nlp = nlp(doc)
for token in doc_nlp:
if token.pos_ not in pos_counts:
pos_counts[token.pos_] = 0
pos_counts[token.pos_] += 1
return pos_counts
# Анализ на всём корпусе
pos_result = analyze_pos(df['text'])
plt.bar(pos_result.keys(), pos_result.values())
plt.xticks(rotation=45)
plt.title("Распределение частей речи в корпусе")
plt.show()
4. Нормализация текста (лемматизация)¶
Лемматизация — это процесс приведения слов к их нормальной форме (например, "бежал", "бежит" → "бежать").
В этом этапе сравнивается качество модели на тексте:
- без лемматизации
- после лемматизации
Цель — выяснить, улучшает ли нормализация точность классификации.
def lemmatize_text(text):
doc = nlp(text)
return " ".join([token.lemma_ for token in doc if token.is_alpha])
df['lemmatized'] = df['text'].apply(lemmatize_text)
df[['text', 'lemmatized']].head(2)
5. Фильтрация текста¶
Фильтрация — это удаление «шума» из текста:
- стоп-слов (неинформативных слов: "в", "и", "но")
- слишком коротких или длинных слов
Цель — уменьшить объём нерелевантной информации и повысить качество признаков.
from nltk.corpus import stopwords
import nltk
nltk.download('stopwords')
stop_words = set(stopwords.words("russian"))
def filter_text(text):
return " ".join([word for word in text.split() if word.lower() not in stop_words and len(word) > 2])
df['filtered'] = df['lemmatized'].apply(filter_text)
df[['lemmatized', 'filtered']].head(2)
6. Формирование N-грамм¶
N-граммы — это последовательности из N слов. Они помогают учитывать контекст словосочетаний (например: "база данных", "система управления").
Будем сравнивать классификацию:
- только на униграммах (1-граммы)
- и биграммах (2-граммы)
from sklearn.feature_extraction.text import CountVectorizer
unigram_vectorizer = CountVectorizer(ngram_range=(1, 1))
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2))
X_uni = unigram_vectorizer.fit_transform(df['filtered'])
X_bi = bigram_vectorizer.fit_transform(df['filtered'])
print(f"Размерность униграмм: {X_uni.shape}")
print(f"Размерность биграмм: {X_bi.shape}")
Это значит, что у нас 5 документов и 3538 уникальных признаков (слов и биграмм), используемых для представления каждого документа.
7. Векторизация текста¶
Векторизация — это перевод текста в числовой формат:
CountVectorizer— мешок словTfidfVectorizer— частотный портрет
Сравним качество модели на обоих подходах.
from sklearn.feature_extraction.text import TfidfVectorizer
cv = CountVectorizer()
tfidf = TfidfVectorizer()
X_cv = cv.fit_transform(df['filtered'])
X_tfidf = tfidf.fit_transform(df['filtered'])
Отчёты показывают низкое качество модели — в частности, точность (precision) для класса 1 равна 0.00, что означает, что модель не умеет хорошо распознавать этот класс.
Заметно, что выборка очень маленькая (support по 1 объекту для каждого класса), что затрудняет обучение и оценку модели.
accuracy (доля правильных ответов) равна 0.5 — случайный результат для двух классов.
Так как модель не предсказала ни одного объекта класса 1, то precision и f1-score для этого класса не вычислены.
8. Обучение модели и оценка качества¶
На этом этапе выполняется классификация с использованием LogisticRegression.
Мы сравним точность моделей при разных способах обработки:
- только с CountVectorizer
- с TfidfVectorizer
- с лемматизацией
- с фильтрацией
- с разными N-граммами
Для оценки используется accuracy_score и classification_report.
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# Извлекаем метку класса из имени файла (до первого подчеркивания или пробела)
df['label'] = df['filename'].apply(lambda name: name.split('_')[0].split()[0])
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
y = df['label']
# TF-IDF векторизация
X_train, X_test, y_train, y_test = train_test_split(X_tfidf, y, test_size=0.3, random_state=42)
clf = LogisticRegression(max_iter=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print("Точность модели:", accuracy_score(y_test, y_pred))
print("Классификационный отчёт:\n", classification_report(y_test, y_pred))
Интерпретация результатов модели¶
После обучения логистической регрессии на новых 10 текстах мы получили следующие метрики:
Точность (accuracy):
0.33
Это означает, что модель правильно классифицировала только 33% текстов из тестовой выборки (1 из 3).Precision / Recall / F1-score по классам: Модель вообще не предсказала некоторые классы, в частности:
- Для классов
Встроенные,Технологии,Условия—precision,recall,f1-score=0.0, т.к. ни один из них не был предсказан. - Лишь один класс —
Этапы— был распознан верно.
- Для классов
Macro avg и weighted avg также показывают низкие значения (0.25–0.33), что подтверждает слабое качество модели при текущих параметрах.
Предупреждение UndefinedMetricWarning говорит о том, что некоторые метрики (например, precision и recall) не определены, потому что:
- модель не сделала ни одного предсказания для некоторых классов,
- или в выборке не оказалось ни одного истинного примера соответствующего класса.
Возможные причины низкой точности:¶
- Слишком маленький объем данных (всего 10 текстов).
- Несбалансированность классов — некоторые классы представлены по 1 разу.
- Низкое качество признаков — возможно, тексты короткие, плохо различимы, слишком шаблонные.