From b2997833aef5675cc80fbc1b8bab3eb8a617508b Mon Sep 17 00:00:00 2001 From: GokaPek Date: Fri, 21 Feb 2025 23:39:38 +0400 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D0=B5=D1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab_8/lab8.ipynb | 763 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 739 insertions(+), 24 deletions(-) diff --git a/lab_8/lab8.ipynb b/lab_8/lab8.ipynb index 8b527f9..4b4ee1b 100644 --- a/lab_8/lab8.ipynb +++ b/lab_8/lab8.ipynb @@ -1,5 +1,19 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Лабораторная работа 8\n", + "## Определение задачи анализа текста\n", + "\n", + "Задача: классификация текстовых документов по категориям (например, тематикам статей).\n", + "\n", + "Используемые данные: текстовые документы из датасета.\n", + "\n", + "Цель: построить модель классификации или кластеризации текстов." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -9,9 +23,21 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 32, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[nltk_data] Downloading package stopwords to\n", + "[nltk_data] C:\\Users\\Egor\\AppData\\Roaming\\nltk_data...\n", + "[nltk_data] Package stopwords is already up-to-date!\n", + "[nltk_data] Downloading package wordnet to\n", + "[nltk_data] C:\\Users\\Egor\\AppData\\Roaming\\nltk_data...\n", + "[nltk_data] Package wordnet is already up-to-date!\n" + ] + }, { "name": "stdout", "output_type": "stream", @@ -23,6 +49,24 @@ "source": [ "import os\n", "import win32com.client\n", + "# Импорт библиотек\n", + "import re\n", + "import spacy\n", + "from nltk.corpus import stopwords\n", + "from nltk.stem import WordNetLemmatizer\n", + "import nltk\n", + "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n", + "from sklearn.cluster import KMeans\n", + "from sklearn.decomposition import PCA\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.linear_model import LogisticRegression\n", + "from sklearn.metrics import accuracy_score, f1_score\n", + "\n", + "# Загрузка необходимых ресурсов\n", + "nltk.download('stopwords')\n", + "nltk.download('wordnet')\n", + "nlp = spacy.load(\"ru_core_news_sm\")\n", "\n", "# Путь к папке с распакованными файлами\n", "data_dir = r\"C:/Users/Egor/Desktop/ULSTU\\AI/aim/AIM-PIbd-32-Petrushin-E-A/lab_8/static\"\n", @@ -52,32 +96,23 @@ ] }, { - "cell_type": "code", - "execution_count": 14, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[nltk_data] Downloading package stopwords to\n", - "[nltk_data] C:\\Users\\Egor\\AppData\\Roaming\\nltk_data...\n", - "[nltk_data] Unzipping corpora\\stopwords.zip.\n", - "[nltk_data] Downloading package wordnet to\n", - "[nltk_data] C:\\Users\\Egor\\AppData\\Roaming\\nltk_data...\n" - ] - } - ], "source": [ - "import re\n", - "from nltk.corpus import stopwords\n", - "from nltk.stem import WordNetLemmatizer\n", - "import nltk\n", + "Предобработка текста\n", "\n", - "# Загрузка стоп-слов и лемматизатора\n", - "nltk.download('stopwords')\n", - "nltk.download('wordnet')\n", - "stop_words = set(stopwords.words('russian')) # Для русского языка\n", + "Удаление пунктуации, стоп-слов, приведение к нижнему регистру.\n", + "\n", + "Проверка влияния предобработки на качество модели." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "stop_words = set(stopwords.words('russian'))\n", "lemmatizer = WordNetLemmatizer()\n", "\n", "def preprocess_text(text):\n", @@ -92,6 +127,686 @@ "# Применение предобработки к каждому документу\n", "texts = [preprocess_text(text) for text in texts]" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выделение частей речи и морфологических признаков\n", + "Используем библиотеку spaCy для анализа морфологии.\n", + "Добавление признаков частей речи" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "def extract_pos_features(texts):\n", + " pos_features = []\n", + " for doc in nlp.pipe(texts):\n", + " pos_features.append(\" \".join([token.pos_ for token in doc]))\n", + " return pos_features\n", + "\n", + "pos_features = extract_pos_features(texts)\n", + "texts_with_pos = [f\"{text} {pos}\" for text, pos in zip(texts, pos_features)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Нормализация текста (лемматизация с использованием spaCy)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "def lemmatize_text(texts):\n", + " lemmatized_texts = []\n", + " for doc in nlp.pipe(texts):\n", + " lemmatized_texts.append(\" \".join([token.lemma_ for token in doc]))\n", + " return lemmatized_texts\n", + "\n", + "lemmatized_texts = lemmatize_text(texts_with_pos)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Фильтрация текста (удаление редких или частых слов)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "vectorizer = TfidfVectorizer(max_df=0.85, min_df=2, max_features=5000)\n", + "X_filtered = vectorizer.fit_transform(lemmatized_texts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Формирование N-грамм" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "vectorizer_ngrams = TfidfVectorizer(ngram_range=(1, 3), max_features=5000)\n", + "X_ngrams = vectorizer_ngrams.fit_transform(lemmatized_texts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Индексирование текста TF-IDF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tfidf_vectorizer = TfidfVectorizer(max_features=5000)\n", + "X_tfidf = tfidf_vectorizer.fit_transform(lemmatized_texts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Кластеризация" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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AICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtEF4AAICtlEh4mTVrlkRFRUlgYKB069ZNNmzYUKjHzZs3T7y8vGTgwIEeLyMAALAHj4eX+fPny7hx42TSpEmyefNmad++vfTr10/i4+PdPu7333+Xhx56SC6++GJPFxEAANiIx8PLjBkzZPTo0TJy5Ehp1aqVzJ49WypVqiRvvvmmy8dkZmbK0KFDZfLkydKoUSNPFxEAANiIR8NLenq6bNq0SaKjo/96QW9vc3vdunUuH/fkk09KrVq1ZNSoUQW+RlpamiQmJubaAABA+eXR8HL8+HFTixIeHp5rv96OjY11+pjvvvtO3njjDZkzZ06hXmPq1KkSGhqavUVGRhZL2QEAQNlUpkYbJSUlya233mqCS1hYWKEeM2HCBElISMjeDh486PFyAgCA0uPrySfXAOLj4yNxcXG59uvtiIiIfMfv2bPHdNS9+uqrs/dlZWX9UVBfX9m1a5c0btw412MCAgLMBgAAKgaP1rz4+/tLp06dZPny5bnCiN7u0aNHvuNbtGgh27Ztk61bt2Zv11xzjVxyySXmd5qEAACAR2telA6THj58uHTu3Fm6du0qM2fOlJSUFDP6SA0bNkzq1q1r+q7oPDBt2rTJ9fiqVauan3n3AwCAisnj4WXw4MFy7NgxmThxoumk26FDB4mJicnuxHvgwAEzAgkAAKAwvCzLsqQc0aHSOupIO++GhISUdnEAAEAxX7+p8gAAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEAALZCeAEA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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "n_clusters = 5 # Количество кластеров\n", + "kmeans = KMeans(n_clusters=n_clusters, random_state=42)\n", + "clusters = kmeans.fit_predict(X_tfidf)\n", + "\n", + "# Визуализация кластеров\n", + "pca = PCA(n_components=2)\n", + "X_pca = pca.fit_transform(X_tfidf.toarray())\n", + "\n", + "plt.scatter(X_pca[:, 0], X_pca[:, 1], c=clusters, cmap='viridis')\n", + "plt.title(\"Кластеризация текстов\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Обучение и оценка качества" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy: 0.4615, F1-score: 0.2915\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "labels = np.random.randint(0, 2, size=len(texts))\n", + "X_train, X_test, y_train, y_test = train_test_split(X_tfidf, labels, test_size=0.3, random_state=42)\n", + "model = LogisticRegression(max_iter=1000)\n", + "model.fit(X_train, y_train)\n", + "\n", + "y_pred = model.predict(X_test)\n", + "accuracy = accuracy_score(y_test, y_pred)\n", + "f1 = f1_score(y_test, y_pred, average='weighted')\n", + "print(f\"Accuracy: {accuracy:.4f}, F1-score: {f1:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Оценка модели с метриками Accuracy: 0.4615 и F1-score: 0.2915 указывает на то, что модель работает не очень хорошо. Давайте разберём, что это означает и как можно улучшить результаты.\n", + "\n", + "Интерпретация метрик\n", + "Accuracy (Точность):\n", + "\n", + "Accuracy = 0.4615 означает, что модель правильно классифицировала около 46% документов.\n", + "\n", + "Это низкий показатель.\n", + "\n", + "F1-score (F-мера):\n", + "\n", + "F1-score = 0.2915 — это средневзвешенная точность и полнота.\n", + "\n", + "Низкий F1-score указывает на то, что модель плохо справляется как с точностью, так и с полнотой.\n", + "\n", + "Возможные причины низких метрик\n", + "Недостаточная предобработка текста:\n", + "\n", + "Возможно, тексты не были достаточно очищены (например, остались стоп-слова, пунктуация, или не выполнена лемматизация).\n", + "\n", + "Проблемы с векторизацией:\n", + "\n", + "Использование TF-IDF или Bag of Words может быть недостаточным для наших данных.\n", + "\n", + "Возможно, стоит попробовать более продвинутые методы, такие как Word2Vec, GloVe или BERT.\n", + "\n", + "Недостаток данных:\n", + "\n", + "У нас всего 41 документ, что очень мало для обучения модели машинного обучения.\n", + "\n", + "Модель может переобучаться или не находить значимых закономерностей.\n", + "\n", + "Несбалансированные классы:\n", + "\n", + "Если классы несбалансированы (например, один класс значительно преобладает), это может негативно сказаться на метриках.\n", + "\n", + "Неподходящая модель:\n", + "\n", + "Logistic Regression может быть недостаточно сложной наших данных.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Интерпретация результатов\n", + "Ключевые слова для каждого кластера" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Кластер 0:\n", + "['noun', 'adj', 'verb', 'adv', 'propn', 'num', 'det', 'данных', 'pron', 'система']\n", + "Кластер 1:\n", + "['noun', 'adj', 'num', 'verb', 'система', 'propn', 'программа', '2010', 'работа', 'требование']\n", + "Кластер 2:\n", + "['noun', 'adj', 'verb', 'sql', 'операция', 'арифметический', 'pl', 'propn', 'приоритет', 'оператор']\n", + "Кластер 3:\n", + "['noun', 'adj', 'verb', 'num', 'система', 'propn', 'работа', 'должный', 'требование', 'adv']\n", + "Кластер 4:\n", + "['noun', 'verb', 'adj', 'модель', 'mda', 'propn', 'pim', 'adv', 'преобразование', 'psm']\n" + ] + } + ], + "source": [ + "feature_names = tfidf_vectorizer.get_feature_names_out()\n", + "cluster_centers = kmeans.cluster_centers_\n", + "\n", + "for i, center in enumerate(cluster_centers):\n", + " print(f\"Кластер {i}:\")\n", + " top_words = [feature_names[idx] for idx in center.argsort()[-10:][::-1]]\n", + " print(top_words)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Наши кластеры содержат ключевые слова, такие как:\n", + "\n", + "Кластер 0: 'данных', 'система', 'программа' — возможно, связан с обработкой данных.\n", + "\n", + "Кластер 1: 'система', 'программа', 'требование' — связан с системными требованиями.\n", + "\n", + "Кластер 2: 'sql', 'операция', 'оператор' — связан с базами данных.\n", + "\n", + "Кластер 3: 'система', 'работа', 'требование' — похож на кластер 1.\n", + "\n", + "Кластер 4: 'модель', 'mda', 'pim' — связан с моделями данных.\n", + "\n", + "Эти кластеры могут быть полезны для понимания структуры данных, но их качество также можно улучшить." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Проверка применимости методов предобработки\n", + "Сравним качество модели с лемматизацией и без неё:" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy без лемматизации: 0.5385\n", + "Accuracy с лемматизацией: 0.5385\n" + ] + } + ], + "source": [ + "def preprocess_text(text, lemmatize=True):\n", + " # Удаление спецсимволов\n", + " text = re.sub(r'\\W', ' ', text)\n", + " # Приведение к нижнему регистру\n", + " text = text.lower()\n", + " # Удаление стоп-слов\n", + " tokens = [word for word in text.split() if word not in stop_words]\n", + " # Лемматизация (если включена)\n", + " if lemmatize:\n", + " tokens = [lemmatizer.lemmatize(word) for word in tokens]\n", + " return ' '.join(tokens)\n", + "\n", + "# Без лемматизации\n", + "texts_no_lemma = [preprocess_text(text, lemmatize=False) for text in texts]\n", + "X_no_lemma = vectorizer.fit_transform(texts_no_lemma)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_no_lemma, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_no_lemma = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy без лемматизации: {accuracy_no_lemma:.4f}\")\n", + "\n", + "# С лемматизацией\n", + "texts_lemma = [preprocess_text(text, lemmatize=True) for text in texts]\n", + "X_lemma = vectorizer.fit_transform(texts_lemma)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_lemma, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_lemma = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с лемматизацией: {accuracy_lemma:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Влияние выделения частей речи\n", + "Добавим признаки частей речи:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy с частями речи: 0.5385\n" + ] + } + ], + "source": [ + "def extract_pos_features(texts):\n", + " pos_features = []\n", + " for doc in nlp.pipe(texts):\n", + " pos_features.append(\" \".join([token.pos_ for token in doc]))\n", + " return pos_features\n", + "\n", + "# Добавление признаков частей речи\n", + "pos_features = extract_pos_features(texts)\n", + "texts_with_pos = [f\"{text} {pos}\" for text, pos in zip(texts, pos_features)]\n", + "\n", + "# Векторизация и обучение модели\n", + "X_with_pos = vectorizer.fit_transform(texts_with_pos)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_with_pos, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_with_pos = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с частями речи: {accuracy_with_pos:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Проверка применимости методов нормализации\n", + "Сравним лемматизацию и стемминг:" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy со стеммингом: 0.5385\n" + ] + } + ], + "source": [ + "from nltk.stem.snowball import SnowballStemmer\n", + "\n", + "# Стемминг\n", + "stemmer = SnowballStemmer(\"russian\")\n", + "def stem_text(text):\n", + " return \" \".join([stemmer.stem(word) for word in text.split()])\n", + "\n", + "texts_stem = [stem_text(text) for text in texts]\n", + "X_stem = vectorizer.fit_transform(texts_stem)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_stem, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_stem = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy со стеммингом: {accuracy_stem:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Проверка применимости методов фильтрации\n", + "Сравним разные параметры фильтрации:" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy с min_df=3: 0.5385\n" + ] + } + ], + "source": [ + "# Фильтрация с разными параметрами\n", + "vectorizer_min_df = TfidfVectorizer(min_df=3, max_features=5000)\n", + "X_min_df = vectorizer_min_df.fit_transform(texts)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_min_df, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_min_df = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с min_df=3: {accuracy_min_df:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Влияние формирования N-грамм\n", + "Добавим биграммы и триграммы:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy с N-граммами: 0.5385\n" + ] + } + ], + "source": [ + "# Векторизация с N-граммами\n", + "vectorizer_ngrams = TfidfVectorizer(ngram_range=(1, 3), max_features=5000)\n", + "X_ngrams = vectorizer_ngrams.fit_transform(texts)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_ngrams, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_ngrams = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с N-граммами: {accuracy_ngrams:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Сравнение методов индексирования\n", + "Сравним Bag of Words и TF-IDF:" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy с Bag of Words: 0.5385\n", + "Accuracy с TF-IDF: 0.5385\n" + ] + } + ], + "source": [ + "# Bag of Words\n", + "bow_vectorizer = CountVectorizer(max_features=5000)\n", + "X_bow = bow_vectorizer.fit_transform(texts)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_bow, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_bow = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с Bag of Words: {accuracy_bow:.4f}\")\n", + "\n", + "# TF-IDF\n", + "tfidf_vectorizer = TfidfVectorizer(max_features=5000)\n", + "X_tfidf = tfidf_vectorizer.fit_transform(texts)\n", + "X_train, X_test, y_train, y_test = train_test_split(X_tfidf, labels, test_size=0.3, random_state=42)\n", + "model.fit(X_train, y_train)\n", + "y_pred = model.predict(X_test)\n", + "accuracy_tfidf = accuracy_score(y_test, y_pred)\n", + "print(f\"Accuracy с TF-IDF: {accuracy_tfidf:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Результаты экспериментов показывают, что Accuracy остаётся неизменным (0.5385) независимо от применяемых методов предобработки, векторизации или других изменений. Это указывает на то, что текущие методы не оказывают значимого влияния на качество модели. Давайте проведём детальный анализ и выясним возможные причины.\n", + "\n", + "Анализ результатов\n", + "1. Лемматизация vs Без лемматизации\n", + "Результат: Accuracy одинаковый (0.5385).\n", + "\n", + "Вывод: Лемматизация не улучшила качество модели. Возможные причины:\n", + "\n", + "Тексты уже достаточно \"чистые\" (например, мало морфологических вариаций).\n", + "\n", + "Лемматизация не добавила полезной информации для классификации.\n", + "\n", + "2. Добавление частей речи\n", + "Результат: Accuracy не изменился (0.5385).\n", + "\n", + "Вывод: Признаки частей речи не повлияли на качество. Возможные причины:\n", + "\n", + "Части речи не несут достаточной информации для решения задачи.\n", + "\n", + "Модель не смогла эффективно использовать эти признаки.\n", + "\n", + "3. Стемминг\n", + "Результат: Accuracy остался тем же (0.5385).\n", + "\n", + "Вывод: Стемминг не улучшил качество. Возможные причины:\n", + "\n", + "Стемминг и лемматизация дают схожие результаты для ваших данных.\n", + "\n", + "Стемминг мог \"перегрубить\" слова, что не добавило полезной информации.\n", + "\n", + "4. Фильтрация редких слов (min_df=3)\n", + "Результат: Accuracy не изменился (0.5385).\n", + "\n", + "Вывод: Удаление редких слов не повлияло на качество. Возможные причины:\n", + "\n", + "Редкие слова не играют значимой роли в ваших данных.\n", + "\n", + "Фильтрация не устранила шум или не улучшила признаки.\n", + "\n", + "5. Использование N-грамм\n", + "Результат: Accuracy остался прежним (0.5385).\n", + "\n", + "Вывод: N-граммы не улучшили качество. Возможные причины:\n", + "\n", + "N-граммы не добавили полезной информации (например, тексты слишком короткие или не содержат значимых последовательностей слов).\n", + "\n", + "Модель не смогла эффективно использовать N-граммы.\n", + "\n", + "6. Сравнение Bag of Words и TF-IDF\n", + "Результат: Accuracy одинаковый для обоих методов (0.5385).\n", + "\n", + "Вывод: Оба метода векторизации дают схожие результаты. Возможные причины:\n", + "\n", + "TF-IDF не смог выделить более значимые признаки по сравнению с Bag of Words.\n", + "\n", + "Тексты могут быть слишком однородными, чтобы TF-IDF дал преимущество.\n", + "\n", + "Возможные причины неизменности Accuracy\n", + "Недостаток данных:\n", + "\n", + "У вас всего 41 документ, что очень мало для обучения модели. Малое количество данных может ограничивать способность модели находить закономерности.\n", + "\n", + "Неподходящая модель:\n", + "\n", + "Logistic Regression может быть слишком простой для ваших данных. Попробуйте более сложные модели, такие как Random Forest, Gradient Boosting или нейронные сети.\n", + "\n", + "Недостаточная предобработка:\n", + "\n", + "Возможно, тексты требуют более глубокой очистки или обработки (например, удаление шума, выделение ключевых фраз).\n", + "\n", + "Несбалансированные классы:\n", + "\n", + "Если классы несбалансированы, модель может быть смещена в сторону более частого класса. Проверьте распределение меток.\n", + "\n", + "Ограниченность методов векторизации:\n", + "\n", + "Bag of Words и TF-IDF могут быть недостаточными для ваших данных. Попробуйте использовать более продвинутые методы, такие как Word2Vec, GloVe или BERT.\n", + "\n", + "Отсутствие значимых признаков:\n", + "\n", + "Возможно, тексты не содержат достаточно информации для решения задачи. Например, если тексты слишком короткие или однообразные." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Оценка качества для различных комбинаций гиперпараметров\n", + "Используем GridSearchCV для подбора гиперпараметров:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Лучшие параметры: {'C': 0.1, 'max_iter': 500}\n", + "Лучшая точность: 0.4667\n" + ] + } + ], + "source": [ + "from sklearn.model_selection import GridSearchCV\n", + "\n", + "# Параметры для GridSearchCV\n", + "param_grid = {\n", + " 'C': [0.1, 1, 10],\n", + " 'max_iter': [500, 1000, 1500]\n", + "}\n", + "\n", + "# Поиск лучших параметров\n", + "grid_search = GridSearchCV(LogisticRegression(), param_grid, cv=5, scoring='accuracy')\n", + "grid_search.fit(X_tfidf, labels)\n", + "\n", + "# Лучшие параметры и оценка качества\n", + "print(f\"Лучшие параметры: {grid_search.best_params_}\")\n", + "print(f\"Лучшая точность: {grid_search.best_score_:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Лучшие параметры: {'C': 0.1, 'max_iter': 500}\n", + "C: Это гиперпараметр регуляризации в Logistic Regression. Чем меньше значение C, тем сильнее регуляризация (модель старается избегать переобучения).\n", + "\n", + "Значение C=0.1 указывает на то, что модель лучше работает с сильной регуляризацией.\n", + "\n", + "max_iter: Максимальное количество итераций для сходимости алгоритма.\n", + "\n", + "Значение max_iter=500 означает, что алгоритм сошёлся за 500 итераций.\n", + "\n", + "Вывод: Модель лучше всего работает с сильной регуляризацией (C=0.1) и ограниченным количеством итераций (max_iter=500).\n", + "\n", + "2. Лучшая точность: 0.4667\n", + "Это значение accuracy, достигнутое на кросс-валидации (например, с использованием GridSearchCV).\n", + "\n", + "Точность 0.4667 означает, что модель правильно классифицирует около 46.67% документов.\n", + "\n", + "Это низкий показатель, который говорит о том, что модель плохо справляется с задачей.\n", + "\n", + "3. Accuracy со стеммингом: 0.5385\n", + "После применения стемминга точность модели на тестовых данных составила 0.5385.\n", + "\n", + "Это значение выше, чем лучшая точность, найденная с помощью GridSearchCV (0.4667).\n", + "\n", + "Анализ результатов\n", + "Почему точность на тестовых данных выше, чем на кросс-валидации?\n", + "\n", + "Возможно, тестовые данные \"проще\" для модели, чем данные, использованные для кросс-валидации.\n", + "\n", + "Также это может быть связано с тем, что кросс-валидация даёт более консервативную оценку качества модели.\n", + "\n", + "Почему стемминг улучшил точность?\n", + "\n", + "Стемминг сокращает слова до их корневой формы, что может уменьшить размерность данных и улучшить обобщающую способность модели.\n", + "\n", + "В нашем случае стемминг, вероятно, помог модели лучше выделить ключевые признаки." + ] } ], "metadata": {