Files
AIM-PIbd-32-Smirnov-A-A/lab_8/lab8.ipynb
2025-05-24 14:33:40 +04:00

56 KiB
Raw Blame History

Лабораторная работа 8: Анализ текстов и обработка естественного языка (NLP)

Цель:

Исследовать влияние различных этапов предобработки, нормализации, фильтрации, индексирования и методов машинного обучения на качество решения задачи анализа текстов.

1. Постановка задачи анализа текстовой информации

Цель эксперимента — исследовать влияние этапов предобработки, морфологического анализа, фильтрации, нормализации и индексирования на качество классификации текстов.

В качестве входных данных используется новый набор из 10 текстов (документы Word), содержащих различное тематическое содержание. Каждому тексту сопоставлена метка класса.

Задача: построить pipeline по обработке текстов и классификации на основе содержимого с применением методов машинного обучения.

In [33]:
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')
[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\admin\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!

2. Подготовка текста: загрузка файлов и формирование набора данных

Для начала загружаем все документы в формате .doc из выбранной директории с помощью COM-интерфейса Windows. Из каждого файла извлекаем содержимое и составляем DataFrame, где каждой строке соответствует текст и его метка.

Предобработка текста — это совокупность шагов, направленных на очистку и упрощение исходного материала перед анализом. Основная задача — устранение лишней информации и приведение текста к единой форме.

Цель этапа: Сделать текстовые данные максимально удобными и стандартизированными для дальнейшего анализа.

Ключевые шаги предобработки:

  1. Удаление лишних символов, таких как знаки препинания, специальные символы и т.д.
  2. Приведение всех слов к нижнему регистру
  3. Очистка от лишних пробелов, табуляций, символов перевода строки
  4. Исключение стоп-слов — слов, не несущих смысловой нагрузки (например, "а", "но", "в", "на")
  5. Часто применяется токенизация — разделение текста на отдельные слова или элементы

Пример преобразования: Фраза "Это пример! текста, для анализа." преобразуется в: "это пример текста для анализа"

In [34]:
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})
Загружено документов: 10

3. Анализ частей речи и морфологических признаков

На данном этапе выполняется морфологический анализ текстов с использованием spaCy. Каждое слово анализируется с точки зрения грамматических свойств (часть речи, склонение, падеж и пр.).

Цель — проверить, как использование морфологических признаков влияет на точность модели. Сравним классификацию текста с лемматизацией и без неё.

Также можно визуализировать распределение частей речи по текстам.

In [35]:
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()
No description has been provided for this image

4. Нормализация текста (лемматизация)

Лемматизация — это процесс приведения слов к их нормальной форме (например, "бежал", "бежит" → "бежать").

В этом этапе сравнивается качество модели на тексте:

  • без лемматизации
  • после лемматизации

Цель — выяснить, улучшает ли нормализация точность классификации.

In [36]:
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)
Out[36]:
<style scoped=""> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
text lemmatized
0 2.2 ТЕХНИЧЕСКОЕ ЗАДАНИЕ\r2.2.1 Общие сведения\... техническое задание общий сведение полный наим...
1 Встроенные операторы SQL. \rКак было отмечено ... встроенные оператор sql как было отметить ране...

5. Фильтрация текста

Фильтрация — это удаление «шума» из текста:

  • стоп-слов (неинформативных слов: "в", "и", "но")
  • слишком коротких или длинных слов

Цель — уменьшить объём нерелевантной информации и повысить качество признаков.

In [37]:
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)
[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\admin\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!
Out[37]:
<style scoped=""> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
lemmatized filtered
0 техническое задание общий сведение полный наим... техническое задание общий сведение полный наим...
1 встроенные оператор sql как было отметить ране... встроенные оператор sql отметить ранее sql str...

6. Формирование N-грамм

N-граммы — это последовательности из N слов. Они помогают учитывать контекст словосочетаний (например: "база данных", "система управления").

Будем сравнивать классификацию:

  • только на униграммах (1-граммы)
  • и биграммах (2-граммы)
In [38]:
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}")
Размерность униграмм: (10, 3743)
Размерность биграмм: (10, 21111)

Это значит, что у нас 5 документов и 3538 уникальных признаков (слов и биграмм), используемых для представления каждого документа.

7. Векторизация текста

Векторизация — это перевод текста в числовой формат:

  • CountVectorizer — мешок слов
  • TfidfVectorizer — частотный портрет

Сравним качество модели на обоих подходах.

In [39]:
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.

In [42]:
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))
Точность модели: 0.3333333333333333
Классификационный отчёт:
               precision    recall  f1-score   support

  Встроенные       0.00      0.00      0.00         1
  Технология       0.00      0.00      0.00         0
     Условия       0.00      0.00      0.00         1
       Этапы       1.00      1.00      1.00         1

    accuracy                           0.33         3
   macro avg       0.25      0.25      0.25         3
weighted avg       0.33      0.33      0.33         3

d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Recall is ill-defined and being set to 0.0 in labels with no true samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Recall is ill-defined and being set to 0.0 in labels with no true samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
d:\study\3_course\6_sem\mii_vsc\AIM-PIbd-32-Smirnov-A-A\aimvenv\Lib\site-packages\sklearn\metrics\_classification.py:1565: UndefinedMetricWarning: Recall is ill-defined and being set to 0.0 in labels with no true samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))

Интерпретация результатов модели

После обучения логистической регрессии на новых 10 текстах мы получили следующие метрики:

  • Точность (accuracy): 0.33
    Это означает, что модель правильно классифицировала только 33% текстов из тестовой выборки (1 из 3).

  • Precision / Recall / F1-score по классам: Модель вообще не предсказала некоторые классы, в частности:

    • Для классов Встроенные, Технологии, Условияprecision, recall, f1-score = 0.0, т.к. ни один из них не был предсказан.
    • Лишь один класс — Этапы — был распознан верно.
  • Macro avg и weighted avg также показывают низкие значения (0.250.33), что подтверждает слабое качество модели при текущих параметрах.

Предупреждение UndefinedMetricWarning говорит о том, что некоторые метрики (например, precision и recall) не определены, потому что:

  • модель не сделала ни одного предсказания для некоторых классов,
  • или в выборке не оказалось ни одного истинного примера соответствующего класса.

Возможные причины низкой точности:

  • Слишком маленький объем данных (всего 10 текстов).
  • Несбалансированность классов — некоторые классы представлены по 1 разу.
  • Низкое качество признаков — возможно, тексты короткие, плохо различимы, слишком шаблонные.