Compare commits
No commits in common. "main" and "lab_6" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,6 +17,3 @@ ipython_config.py
|
|||||||
|
|
||||||
# others
|
# others
|
||||||
/Lections
|
/Lections
|
||||||
|
|
||||||
# data
|
|
||||||
/static/pmu
|
|
@ -231,7 +231,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
@ -2923,7 +2923,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
@ -2429,7 +2429,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
@ -2323,7 +2323,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
@ -1218,7 +1218,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
@ -1184,7 +1184,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.12.8"
|
"version": "3.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
622
lab_8/lab8.ipynb
622
lab_8/lab8.ipynb
@ -1,622 +0,0 @@
|
|||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"## Обработка текстов на естественном языке\n",
|
|
||||||
"\n",
|
|
||||||
"### Определение задачи анализа текстов\n",
|
|
||||||
"\n",
|
|
||||||
"**Задача:** Необходимо проанализировать набор пояснительных записок к курсовой работе, представленных в формате Word-документов, и определить степень схожести их содержания. Это позволит выявить возможные случаи плагиата или схожести в описании проектов.\n",
|
|
||||||
"\n",
|
|
||||||
"**Цель:** Разработать модель, которая сможет автоматически анализировать текстовые данные, выделять ключевые особенности и сравнивать документы на предмет схожести содержания.\n",
|
|
||||||
"\n",
|
|
||||||
"**Методы:** Для решения задачи будут использованы методы обработки естественного языка (NLP), включая предобработку текста, нормализацию, фильтрацию, векторизацию (например, мешок слов, TF-IDF), а также методы машинного обучения для кластеризации документов.\n",
|
|
||||||
"\n",
|
|
||||||
"---"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Выгрузка данных из файлов"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import os\n",
|
|
||||||
"\n",
|
|
||||||
"import pandas as pd\n",
|
|
||||||
"from docx import Document\n",
|
|
||||||
"from docx.document import Document as DocumentType\n",
|
|
||||||
"from pandas import DataFrame\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def read_docx(file_path: str) -> str:\n",
|
|
||||||
" try:\n",
|
|
||||||
" doc: DocumentType = Document(file_path)\n",
|
|
||||||
" full_text: list[str] = [paragraph.text for paragraph in doc.paragraphs]\n",
|
|
||||||
" return \"\\n\".join(full_text)\n",
|
|
||||||
" except Exception as e:\n",
|
|
||||||
" print(f\"Error reading {file_path}: {e}\")\n",
|
|
||||||
" return \"\"\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def load_docs(dataset_path: str) -> DataFrame:\n",
|
|
||||||
" df = pd.DataFrame(columns=[\"doc\", \"text\"])\n",
|
|
||||||
" for file_name in os.listdir(dataset_path):\n",
|
|
||||||
" if file_name.startswith(\"~$\") or not file_name.lower().endswith(\".docx\"):\n",
|
|
||||||
" continue\n",
|
|
||||||
" file_path: str = os.path.join(dataset_path, file_name)\n",
|
|
||||||
" text: str = read_docx(file_path)\n",
|
|
||||||
" df.loc[len(df.index)] = [file_name, text]\n",
|
|
||||||
" return df\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Загрузка данных\n",
|
|
||||||
"project_root: str = os.path.abspath(os.path.join(os.path.dirname(\"lab8.ipynb\"), \"..\"))\n",
|
|
||||||
"dataset_path: str = os.path.join(project_root, \"static\", \"pmu\")\n",
|
|
||||||
"\n",
|
|
||||||
"df: DataFrame = load_docs(dataset_path)\n",
|
|
||||||
"df.info()\n",
|
|
||||||
"df.sort_values(by=[\"doc\"], inplace=True)\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк данных\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Предобработка текста\n",
|
|
||||||
"\n",
|
|
||||||
"Для выполнения предобработки текста необходимо выполнять следующие шаги:\n",
|
|
||||||
"- **Изменение регистра:** приведение текста к нижнему регистру.\n",
|
|
||||||
"- **Применение регулярного выражения:** удаление всех символов, кроме букв и пробелов.\n",
|
|
||||||
"- **Удаление диакритических знаков:** нормализация текста с использованием unicodedata.\n",
|
|
||||||
"- **Преобразование чисел в строку:** использование библиотеки num2words.\n",
|
|
||||||
"- **Токенизация:** разбиение текста на отдельные слова.\n",
|
|
||||||
"- **Удаление стоп-слов:** удаление слов, которые не несут смысловой нагрузки."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import re\n",
|
|
||||||
"import unicodedata\n",
|
|
||||||
"from typing import Set\n",
|
|
||||||
"\n",
|
|
||||||
"import spacy\n",
|
|
||||||
"from num2words import num2words\n",
|
|
||||||
"from spacy.tokens.doc import Doc\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Загрузка модели для русского языка\n",
|
|
||||||
"# Чтобы скачать модель\n",
|
|
||||||
"# python -m spacy download ru_core_news_lg\n",
|
|
||||||
"nlp = spacy.load(\"ru_core_news_lg\")\n",
|
|
||||||
"\n",
|
|
||||||
"# Список стоп-слов\n",
|
|
||||||
"stop_words: Set[str] = nlp.Defaults.stop_words\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Метод для предобработки текста\n",
|
|
||||||
"def preprocess_text(text: str) -> str:\n",
|
|
||||||
" # Приведение текста к нижнему регистру\n",
|
|
||||||
" text = text.lower()\n",
|
|
||||||
" \n",
|
|
||||||
" # Замена переносов на пробел\n",
|
|
||||||
" text = text.replace(\"\\n\", \" \")\n",
|
|
||||||
" \n",
|
|
||||||
" # Удаление всех символов, кроме букв и пробелов\n",
|
|
||||||
" text = re.sub(r\"[^a-zA-Zа-яА-Я ]\", \"\", text)\n",
|
|
||||||
" \n",
|
|
||||||
" # Удаление диакритических знаков\n",
|
|
||||||
" text = unicodedata.normalize(\"NFKD\", text)\n",
|
|
||||||
" text = \"\".join([char for char in text if not unicodedata.combining(char)])\n",
|
|
||||||
" \n",
|
|
||||||
" # Преобразование чисел в слова\n",
|
|
||||||
" words: list[str] = text.split()\n",
|
|
||||||
" words = [num2words(word, lang=\"ru\") if word.isdigit() else word for word in words]\n",
|
|
||||||
" text = \" \".join(words)\n",
|
|
||||||
" \n",
|
|
||||||
" # Токенизация с использованием spaCy\n",
|
|
||||||
" doc: Doc = nlp(text)\n",
|
|
||||||
" \n",
|
|
||||||
" # Удаление стоп-слов и пунктуации\n",
|
|
||||||
" tokens: list[str] = [token.text for token in doc if not token.is_stop and not token.is_punct]\n",
|
|
||||||
" \n",
|
|
||||||
" return \" \".join(tokens)\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Применение метода предобработки к каждому тексту в DataFrame\n",
|
|
||||||
"df[\"preprocessed_text\"] = df[\"text\"].apply(preprocess_text)\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк DataFrame с предобработанными текстами\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Нормализация текста (Лемматизация)\n",
|
|
||||||
"\n",
|
|
||||||
"**Нормализация текста** — это процесс приведения текста к единому стандартному виду (форме). В данном случае используется метод лемматизации.\n",
|
|
||||||
"\n",
|
|
||||||
"**Лемматизация** — это процесс приведения слова к его начальной форме (лемме)."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"# Функция для лемматизации текста\n",
|
|
||||||
"def lemmatize_text(text: str) -> str:\n",
|
|
||||||
" doc: Doc = nlp(text)\n",
|
|
||||||
" lemmas: list[str] = [token.lemma_ for token in doc]\n",
|
|
||||||
" return \" \".join(lemmas)\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Применение лемматизации к предобработанным текстам\n",
|
|
||||||
"df[\"lemmatized_text\"] = df[\"preprocessed_text\"].apply(lemmatize_text)\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк DataFrame с лемматизированными текстами\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Фильтрация текста\n",
|
|
||||||
"\n",
|
|
||||||
"**Фильтрация текста** – сокращение пространства признаков за счет удаления слов по определённым условиям. В данном случае удаляются слова с длинной меньше 4 символов и больше 25."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"# Функция для фильтрации текста\n",
|
|
||||||
"def filter_text(text: str, min_length: int = 3, max_length: int = 25) -> str:\n",
|
|
||||||
" words: list[str] = text.split()\n",
|
|
||||||
" # Удаление слов, длина которых меньше min_length или больше max_length\n",
|
|
||||||
" filtered_words: list[str] = [\n",
|
|
||||||
" word for word in words \n",
|
|
||||||
" if min_length <= len(word) <= max_length\n",
|
|
||||||
" ]\n",
|
|
||||||
" return \" \".join(filtered_words)\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Применение фильтрации к лемматизированным текстам\n",
|
|
||||||
"df[\"filtered_text\"] = df[\"lemmatized_text\"].apply(filter_text)\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк DataFrame с отфильтрованными текстами\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Морфологический анализ (POS tagging)\n",
|
|
||||||
"\n",
|
|
||||||
"**Морфологический анализ** – процесс выделения частей речи.\n",
|
|
||||||
"\n",
|
|
||||||
"На данном этапе каждому слову текста ставится в соответствие множество морфологических признаков, которые зависят от роли слова в предложении:\n",
|
|
||||||
"- Часть речи;\n",
|
|
||||||
"- Падеж;\n",
|
|
||||||
"- Род;\n",
|
|
||||||
"- Число;\n",
|
|
||||||
"- Одушевленность;\n",
|
|
||||||
"- и другие."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"# Функция для морфологического анализа\n",
|
|
||||||
"def morphological_analysis(text: str) -> list[dict]:\n",
|
|
||||||
" doc: Doc = nlp(text)\n",
|
|
||||||
" # Сбор информации о каждом токене: текст, лемма, часть речи, морфологические признаки\n",
|
|
||||||
" tokens_info: list[dict] = [\n",
|
|
||||||
" {\n",
|
|
||||||
" \"text\": token.text,\n",
|
|
||||||
" \"lemma\": token.lemma_,\n",
|
|
||||||
" \"pos\": token.pos_,\n",
|
|
||||||
" \"morph\": token.morph,\n",
|
|
||||||
" }\n",
|
|
||||||
" for token in doc\n",
|
|
||||||
" ]\n",
|
|
||||||
" return tokens_info\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Применение морфологического анализа к отфильтрованным текстам\n",
|
|
||||||
"df[\"morphological_info\"] = df[\"filtered_text\"].apply(morphological_analysis)\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк DataFrame с морфологическим анализом\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Формирование N-грамм\n",
|
|
||||||
"\n",
|
|
||||||
"**N-грамма** – это последовательность из N элементов (слов, букв или других символов) в тексте.\n",
|
|
||||||
"\n",
|
|
||||||
"**Виды N-грамм:**\n",
|
|
||||||
"- **Униграммы (Unigrams, N=1)** – последовательности из одного элемента (слова).\n",
|
|
||||||
"- **Биграммы (Bigrams, N=2)** – последовательности из двух элементов.\n",
|
|
||||||
"- **Триграммы (Trigrams, N=3)** – последовательности из трех элементов.\n",
|
|
||||||
"- **N-граммы (для N > 3)** – последовательности из N элементов."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import nltk\n",
|
|
||||||
"from nltk.util import ngrams\n",
|
|
||||||
"from nltk.tokenize import word_tokenize\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Загрузка ресурсов для токенизации\n",
|
|
||||||
"nltk.download(\"punkt\")\n",
|
|
||||||
"nltk.download(\"punkt_tab\")\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def generate_ngrams(text: str, n: int = 2) -> list[tuple]:\n",
|
|
||||||
" # Токенизация текста\n",
|
|
||||||
" tokens: list[str] = word_tokenize(text, language=\"russian\")\n",
|
|
||||||
" # Формирование N-грамм\n",
|
|
||||||
" n_grams: list[tuple] = list(ngrams(tokens, n))\n",
|
|
||||||
" return n_grams\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Пример для биграмм (N=2)\n",
|
|
||||||
"df[\"bigrams\"] = df[\"filtered_text\"].apply(lambda x: generate_ngrams(x, n=2))\n",
|
|
||||||
"\n",
|
|
||||||
"# Пример для триграмм (N=3)\n",
|
|
||||||
"df[\"trigrams\"] = df[\"filtered_text\"].apply(lambda x: generate_ngrams(x, n=3))\n",
|
|
||||||
"\n",
|
|
||||||
"# Вывод первых строк DataFrame с N-граммами\n",
|
|
||||||
"display(df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Векторизация с использованием мешка слов (BoW)\n",
|
|
||||||
"\n",
|
|
||||||
"**Векторизация текста** – это процесс преобразования текстовых данных в числовые векторы, которые могут быть использованы для анализа и обработки в моделях машинного обучения.\n",
|
|
||||||
"\n",
|
|
||||||
"**Метод мешка слов (Bag of Words, BoW)** – это простой и популярный способ векторизации текста. Основная идея заключается в том, что текст представляется как \"мешок\" (набор) слов без учета их порядка и грамматики. Каждое слово в тексте рассматривается как отдельная характеристика, а текст представляется в виде вектора, где каждый элемент соответствует частоте (или наличию) определенного слова."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from typing import Any, Union\n",
|
|
||||||
"\n",
|
|
||||||
"from numpy import ndarray\n",
|
|
||||||
"from scipy.sparse._matrix import spmatrix\n",
|
|
||||||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Создаем объект CountVectorizer\n",
|
|
||||||
"bow_vectorizer = CountVectorizer()\n",
|
|
||||||
"\n",
|
|
||||||
"# Применяем векторизацию к отфильтрованным текстам\n",
|
|
||||||
"bow_matrix: Union[ndarray[Any, Any], spmatrix] = bow_vectorizer.fit_transform(df[\"filtered_text\"])\n",
|
|
||||||
"\n",
|
|
||||||
"# Преобразуем результат в DataFrame для удобства\n",
|
|
||||||
"bow_df = pd.DataFrame(bow_matrix.toarray(), columns=bow_vectorizer.get_feature_names_out()) # type: ignore\n",
|
|
||||||
"\n",
|
|
||||||
"# Выводим первые строки результата\n",
|
|
||||||
"display(bow_df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Векторизация с использованием частотного портрета (TF-IDF)\n",
|
|
||||||
"\n",
|
|
||||||
"**TF-IDF** – это более продвинутый метод векторизации, который учитывает не только частоту слова в тексте, но и его важность в корпусе. Он состоит из двух компонентов:\n",
|
|
||||||
"- **TF (Term Frequency)** – частота слова в тексте.\n",
|
|
||||||
"- **IDF (Inverse Document Frequency)** – обратная частота документа, которая уменьшает вес слов, часто встречающихся в корпусе (например, стоп-слов)."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Создаем объект TfidfVectorizer\n",
|
|
||||||
"tfidf_vectorizer = TfidfVectorizer(max_df=0.95)\n",
|
|
||||||
"\n",
|
|
||||||
"# Применяем векторизацию к отфильтрованным текстам\n",
|
|
||||||
"tfidf_matrix: spmatrix = tfidf_vectorizer.fit_transform(df[\"filtered_text\"])\n",
|
|
||||||
"\n",
|
|
||||||
"# Преобразуем результат в DataFrame для удобства\n",
|
|
||||||
"tfidf_df = pd.DataFrame(tfidf_matrix.toarray(), columns=tfidf_vectorizer.get_feature_names_out()) # type: ignore\n",
|
|
||||||
"\n",
|
|
||||||
"# Выводим первые строки результата\n",
|
|
||||||
"display(tfidf_df.head())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"### Четкая неиерархическая кластеризация (K-Means)\n",
|
|
||||||
"\n",
|
|
||||||
"**K-Means** — это метод неиерархической кластеризации, который разделяет данные на \n",
|
|
||||||
"k кластеров (групп) на основе их близости к центроидам (центрам кластеров). Основная идея заключается в том, чтобы минимизировать суммарное квадратичное отклонение точек внутри кластера от его центра.\n",
|
|
||||||
"\n",
|
|
||||||
"---"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"#### Метод локтя\n",
|
|
||||||
"\n",
|
|
||||||
"**Метод локтя** – это способ определения оптимального числа кластеров k в алгоритме K-Means. Он основывается на анализе инерции (суммы квадратов расстояний внутри кластеров).\n",
|
|
||||||
"\n",
|
|
||||||
"**Принцип работы алгоритма:**\n",
|
|
||||||
"1. Вычисление инерции: Для различных значений k вычисляется инерция W(k).\n",
|
|
||||||
"2. Построение графика: Строится график зависимости инерции от числа кластеров k.\n",
|
|
||||||
"3. Поиск \"локтя\": На графике ищется точка, где снижение инерции начинает замедляться. Эта точка указывает на оптимальное количество кластеров.\n",
|
|
||||||
"\n",
|
|
||||||
"Метод помогает избежать переобучения и обеспечивает баланс между сложностью модели и качеством кластеризации."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from sklearn.cluster import KMeans\n",
|
|
||||||
"import matplotlib.pyplot as plt\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Вычисляем SSE для разного количества кластеров\n",
|
|
||||||
"sse = []\n",
|
|
||||||
"max_clusters = 50 # Максимальное количество кластеров для проверки\n",
|
|
||||||
"for k in range(1, max_clusters + 1):\n",
|
|
||||||
" kmeans = KMeans(n_clusters=k, random_state=42)\n",
|
|
||||||
" kmeans.fit(tfidf_matrix)\n",
|
|
||||||
" sse.append(kmeans.inertia_) # inertia_ содержит SSE\n",
|
|
||||||
"\n",
|
|
||||||
"# Визуализация метода локтя\n",
|
|
||||||
"plt.figure(figsize=(10, 6))\n",
|
|
||||||
"plt.plot(range(1, max_clusters + 1), sse, marker=\"o\")\n",
|
|
||||||
"plt.xlabel(\"Количество кластеров\")\n",
|
|
||||||
"plt.ylabel(\"SSE (Сумма квадратов ошибок)\")\n",
|
|
||||||
"plt.title(\"Метод локтя для подбора количества кластеров\")\n",
|
|
||||||
"plt.xticks(range(1, max_clusters + 1))\n",
|
|
||||||
"plt.grid()\n",
|
|
||||||
"plt.show()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"#### Коэффициент силуэта\n",
|
|
||||||
"\n",
|
|
||||||
"**Коэффициент силуэта** – это метрика для оценки качества кластеризации, варьирующаяся от -1 до 1:\n",
|
|
||||||
"- 1: Объект хорошо кластеризован, далеко от других кластеров.\n",
|
|
||||||
"- 0: Объект на границе между кластерами.\n",
|
|
||||||
"- -1: Объект, вероятно, неправильно классифицирован.\n",
|
|
||||||
"\n",
|
|
||||||
"Он рассчитывается по формуле: `s(i) = b(i) - a(i) / max(a(i), b(i))`, где `a(i)` – среднее расстояние до объектов в том же кластере, а `b(i)` – минимальное среднее расстояние до объектов в ближайшем кластере. Высокие значения указывают на хорошую кластеризацию, низкие – на проблемы с классификацией."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from sklearn.metrics import silhouette_score\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Вычисляем коэффициент силуэта для разного количества кластеров\n",
|
|
||||||
"silhouette_scores = []\n",
|
|
||||||
"for k in range(2, max_clusters + 1): # Минимум 2 кластера для силуэта\n",
|
|
||||||
" kmeans = KMeans(n_clusters=k, random_state=42)\n",
|
|
||||||
" kmeans.fit(tfidf_matrix)\n",
|
|
||||||
" score = silhouette_score(tfidf_matrix, kmeans.labels_, random_state=42)\n",
|
|
||||||
" silhouette_scores.append(score)\n",
|
|
||||||
"\n",
|
|
||||||
"# Визуализация коэффициента силуэта\n",
|
|
||||||
"plt.figure(figsize=(10, 6))\n",
|
|
||||||
"plt.plot(range(2, max_clusters + 1), silhouette_scores, marker=\"o\")\n",
|
|
||||||
"plt.xlabel(\"Количество кластеров\")\n",
|
|
||||||
"plt.ylabel(\"Коэффициент силуэта\")\n",
|
|
||||||
"plt.title(\"Коэффициент силуэта для подбора количества кластеров\")\n",
|
|
||||||
"plt.xticks(range(2, max_clusters + 1))\n",
|
|
||||||
"plt.grid()\n",
|
|
||||||
"plt.show()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"#### Кластеризация с использованием K-Means"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from sklearn.cluster import KMeans\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Задаем количество кластеров\n",
|
|
||||||
"n_clusters = 23\n",
|
|
||||||
"kmeans = KMeans(n_clusters=n_clusters, random_state=42)\n",
|
|
||||||
"\n",
|
|
||||||
"# Обучаем модель на TF-IDF векторах\n",
|
|
||||||
"kmeans.fit(tfidf_matrix)\n",
|
|
||||||
"\n",
|
|
||||||
"# Добавляем метки кластеров в DataFrame\n",
|
|
||||||
"df[\"cluster\"] = kmeans.labels_\n",
|
|
||||||
"\n",
|
|
||||||
"# Выводим документы с их кластерами\n",
|
|
||||||
"display(df[[\"doc\", \"cluster\"]])"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"#### Визуализация кластеров текстов с использованием PCA"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from sklearn.decomposition import PCA\n",
|
|
||||||
"import matplotlib.pyplot as plt\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Задаем количество кластеров\n",
|
|
||||||
"n_clusters = 23\n",
|
|
||||||
"\n",
|
|
||||||
"# Создаем модель K-Means\n",
|
|
||||||
"kmeans = KMeans(n_clusters=n_clusters, random_state=42)\n",
|
|
||||||
"\n",
|
|
||||||
"# Обучаем модель и предсказываем кластеры\n",
|
|
||||||
"clusters = kmeans.fit_predict(tfidf_matrix)\n",
|
|
||||||
"\n",
|
|
||||||
"# Уменьшаем размерность данных до 2 компонент с помощью PCA\n",
|
|
||||||
"pca = PCA(n_components=2)\n",
|
|
||||||
"X_pca = pca.fit_transform(tfidf_matrix.toarray())\n",
|
|
||||||
"\n",
|
|
||||||
"# Визуализируем кластеры\n",
|
|
||||||
"plt.figure(figsize=(12, 10))\n",
|
|
||||||
"plt.scatter(X_pca[:, 0], X_pca[:, 1], c=clusters, cmap=\"viridis\", s=50, alpha=0.6)\n",
|
|
||||||
"\n",
|
|
||||||
"# Добавляем подписи для центроидов кластеров\n",
|
|
||||||
"centers = pca.transform(kmeans.cluster_centers_)\n",
|
|
||||||
"plt.scatter(centers[:, 0], centers[:, 1], c=\"red\", s=200, alpha=0.75, marker=\"X\")\n",
|
|
||||||
"\n",
|
|
||||||
"# Добавляем подписи для каждого документа\n",
|
|
||||||
"for i, doc in enumerate(df[\"doc\"]):\n",
|
|
||||||
" plt.text(X_pca[i, 0], X_pca[i, 1], doc, fontsize=8, alpha=0.8)\n",
|
|
||||||
"\n",
|
|
||||||
"# Добавляем заголовок и подписи осей\n",
|
|
||||||
"plt.title(\"Визуализация кластеров текстов с использованием PCA\")\n",
|
|
||||||
"plt.xlabel(\"Компонента PCA 1\")\n",
|
|
||||||
"plt.ylabel(\"Компонента PCA 2\")\n",
|
|
||||||
"plt.colorbar(label=\"Кластер\")\n",
|
|
||||||
"plt.show()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"#### Интерактивное отображение результата кластеризации"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import plotly.express as px\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Создаем DataFrame для визуализации\n",
|
|
||||||
"df_pca = pd.DataFrame(X_pca, columns=[\"PCA 1\", \"PCA 2\"])\n",
|
|
||||||
"df_pca[\"Cluster\"] = clusters\n",
|
|
||||||
"df_pca[\"Text\"] = df[\"doc\"]\n",
|
|
||||||
"\n",
|
|
||||||
"# Создаем интерактивный scatter plot\n",
|
|
||||||
"fig = px.scatter(\n",
|
|
||||||
" df_pca, x=\"PCA 1\", y=\"PCA 2\", color=\"Cluster\", hover_data=[\"Text\"],\n",
|
|
||||||
" title=\"Визуализация кластеров текстов с использованием PCA\",\n",
|
|
||||||
" labels={\"PCA 1\": \"Компонента PCA 1\", \"PCA 2\": \"Компонента PCA 2\"}\n",
|
|
||||||
")\n",
|
|
||||||
"\n",
|
|
||||||
"# Добавляем центроиды кластеров\n",
|
|
||||||
"centers = pca.transform(kmeans.cluster_centers_)\n",
|
|
||||||
"centers_df = pd.DataFrame(centers, columns=[\"PCA 1\", \"PCA 2\"])\n",
|
|
||||||
"centers_df[\"Cluster\"] = range(n_clusters)\n",
|
|
||||||
"fig.add_scatter(\n",
|
|
||||||
" x=centers_df[\"PCA 1\"], y=centers_df[\"PCA 2\"], mode=\"markers\",\n",
|
|
||||||
" marker=dict(color=\"red\", size=10, symbol=\"x\"), name=\"Centroids\"\n",
|
|
||||||
")\n",
|
|
||||||
"\n",
|
|
||||||
"# Показываем график\n",
|
|
||||||
"# Уберём вывод отображение графика, \n",
|
|
||||||
"# чтобы Jupiter-notebook корректно отображался в репозитории\n",
|
|
||||||
"fig.show()"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "aimenv",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 2
|
|
||||||
}
|
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user