From 91c04e21e9c026ef831f86f67acb2ea735244ccd Mon Sep 17 00:00:00 2001 From: Mokhov Andrey Date: Sat, 18 Feb 2023 11:01:44 +0300 Subject: [PATCH] lab02 --- README.md | 3 + labs/OATD_LR2.md | 92 ++++++++ labs/OATD_LR2_metod.ipynb | 432 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 527 insertions(+) create mode 100644 labs/OATD_LR2.md create mode 100644 labs/OATD_LR2_metod.ipynb diff --git a/README.md b/README.md index 9dd5fbf..6cf562b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ | А-01-19 | 27.02.2023 | | А-03-19 | 20.02.2023 | +* [Задание](labs/OATD_LR2.md) +* [Методические указания](labs/OATD_LR2_metod.ipynb) + ### Лабораторная работа №3 ### Лабораторная работа №4 diff --git a/labs/OATD_LR2.md b/labs/OATD_LR2.md new file mode 100644 index 0000000..2584b05 --- /dev/null +++ b/labs/OATD_LR2.md @@ -0,0 +1,92 @@ +# Лабораторная работа №2. Предварительная обработка текстовых данных + +## Цель работы + +Получить практические навыки обработки текстовых данных в среде Jupiter Notebook. +Научиться проводить предварительную обработку текстовых данных и выявлять параметры обработки, позволяющие добиться наилучшей точности классификации. + +## Задание + +1. В среде Jupyter Notebook создать новый ноутбук (Notebook) +2. Импортировать необходимые для работы библиотеки и модули +3. Загрузить обучающую и экзаменационную выборку в соответствие с вариантом +4. Вывести на экран по одному-два документа каждого класса. +5. Применить стемминг, записав обработанные выборки (тестовую и обучающую) в новые переменные +6. Провести векторизацию выборки: + a. Векторизовать обучающую и тестовую выборки простым подсчетом слов (CountVectorizer) + b. Вывести и проанализировать первые 20 наиболее частотных слов всей выборки и каждого класса по-отдельности. + c. Рассчитать сходство по коэффициенту Жаккара между тремя классами + d. Применить процедуру отсечения стоп-слов и повторить пункты b-c. + e. Провести пункты a – c для обучающей и тестовой выборки, для которой проведена процедура стемминга. + f. Векторизовать выборки с помощью TfidfTransformer (с использованием TF и TF-IDF взвешиваний). + +7. Используя конвейер (Pipeline) реализовать модель Наивного Байесовского классификатора и выявить на основе показателей качества +(значения полноты, точности, f1-меры и аккуратности), какая предварительная обработка данных обеспечит наилучшие результаты классификации. +Должны быть исследованы следующие характеристики: + * Наличие \ отсутствие стемминга + * Отсечение \ не отсечение стоп-слов + * Взвешивание: Count, TF, TF-IDF + * Количество информативных терминов (max_features) - исследовать 5 значений в диапазоне от 100 до общего количества слов в выборке. + + При проведении данного исследования предлагается зафиксировать все переменные кроме одной, а далее менять незафиксированную переменную для определения ее наилучего значения. + После того как наилучшее значение найдено, фиксировать это значение, и переходить к следующей переменной. + Например, ищем оптимальные значения для переменных A, B, C: фиксируем переменные А = 10, B = False, находим оптимальное значение переменной С. Пусть оптимальное значение С = 20,42. + Теперь фиксируем B = False, C = 20,42, ищем оптимальное значение переменной A, и т.д. + +8. По каждому пункту работы занести в отчет программный код и результат вывода. +9. По результатам классификации занести в отчет выводы о влиянии каждого из этапом предобработки данных (наличие стемминга, взвешивание терминов, стоп-слова, количество информативных терминов) +и о наиболее подходящей их комбинации. Объяснить различия (если имеются) в качестве классификации разных классов. + + + +## Варианты заданий + +| Вариант | Класс | +| :--- | :---: | +| 1 | 2, 3, 8 | +| 2 | 6, 10, 11 | +| 3 | 1, 9, 17 | +| 4 | 7, 12, 18 | +| 5 | 4, 14, 18 | +| 6 | 1, 15, 16 | +| 7 | 3, 7 , 13 | +| 8 | 5, 16, 20 | +| 9 | 6, 17, 19 | +| 10 | 3, 5, 15 | +| 11 | 7, 14, 20 | +| 12 | 2, 12, 13 | + +Названия классов + +| № класса | Название класса | +| :--- | :--- | +|1 | 'alt.atheism'| +|2 | 'comp.graphics'| +|3 | 'comp.os.ms-windows.misc'| +|4 | 'comp.sys.ibm.pc.hardware'| +|5 | 'comp.sys.mac.hardware'| +|6 | 'comp.windows.x'| +|7 | 'misc.forsale'| +|8 | 'rec.autos'| +|9 | 'rec.motorcycles'| +|10 | 'rec.sport.baseball'| +|11 | 'rec.sport.hockey'| +|12 | 'sci.crypt'| +|13 | 'sci.electronics'| +|14 | 'sci.med'| +|15 | 'sci.space'| +|16 | 'soc.religion.christian' | +|17 | 'talk.politics.guns'| +|18 | 'talk.politics.mideast'| +|19 | 'talk.politics.misc'| +|20 | 'talk.religion.misc'| + +## Контрольные вопросы + +1. Особенности задачи классификации текстовых данных. +2. Этапы предварительной обработки данных. +3. Алгоритм и особенности Наивного Байесовского метода. +4. Как влияет размер словаря терминов на точность классификации? +5. Какие способы выявления информативных терминов вам известны? +6. Как влияет способ взвешивания терминов на точность классификации? + diff --git a/labs/OATD_LR2_metod.ipynb b/labs/OATD_LR2_metod.ipynb new file mode 100644 index 0000000..8737487 --- /dev/null +++ b/labs/OATD_LR2_metod.ipynb @@ -0,0 +1,432 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# \tМетодические указания к лабораторной работе №2\t" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "В данной работе мы продолжаем работать с библиотекой scikit-learn \n", + "(http://scikit-learn.org), и хотим выяснить ее возможности при работе с текстовыми документами.\n", + "\n", + "Ниже приведены новые модули, которые будут использованы в данной работе:\n", + "\n", + "* fetch_20newsgroups - http://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_20newsgroups.html - загружает датасет «20 news groups», состоящий приблизительно из 18000 сообщений на английском языке по 20 тематикам, разбитым на обучающую и тестовую выборки. \n", + "Векторизаторы текста: \n", + "* CountVectorizer - http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html \n", + "* TfidfTransformer - http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html#sklearn.feature_extraction.text.TfidfVectorizer \n", + "* Pipeline - http://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html - конвейерный классификатор\n", + "* MultinominalNB - http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.MultinomialNB.html - Полиномиальный (Мультиномиальный) Наивный Байесовский метод – разновидность Наивного Байесовского метода, которая хорошо работает с текстами, длины которых [сильно варьируются](https://logic.pdmi.ras.ru/~sergey/teaching/mlstc12/sem01-naivebayes.pdf).\n", + "\n", + "Для проведения стемминга предлагается использовать библиотеку [NLTK](https://www.nltk.org/) и [стеммер Портера](https://www.nltk.org/_modules/nltk/stem/porter.html)\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Импорт библиотек" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.neighbors import KNeighborsClassifier\n", + "from sklearn.datasets import fetch_20newsgroups\n", + "from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer \n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Загрузка выборки\n", + "\n", + "Выборка 20 news groups представляет собой сообщения, котороые состоят из заголовка (header), основной части, подписи или сноски (footer), а также могут содержать в себе цитирование предыдущего сообщения (quotes).\n", + "Модуль `fetch_20newsgroups` позволяет выбирать интересующие тематики и удалять ненужные части сообщений. Для того чтобы выбрать сообщения по интересующим тематикам, необходимо передать список тематик в параметр categories. \n", + "\n", + "Для того чтобы удалить ненужные части сообщений, нужно передать их в параметр `remove`. Кроме того, важным параметром `fetch_20newsgroups` является `subset` - тип выборки – обучающая или тестовая.\n", + "\n", + "\n", + "Выберем сообщения по тематикам *Атеизм* и *Компьютерная графика*, а также укажем, что нас не интересуют заголовки, цитаты и подписи:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [], + "source": [ + "categories = ['alt.atheism', 'comp.graphics'] \n", + "remove = ('headers', 'footers', 'quotes')\n", + "twenty_train = fetch_20newsgroups(subset='train', shuffle=True, random_state=42, categories = categories, remove = remove )\n", + "twenty_test = fetch_20newsgroups(subset='test', shuffle=True, random_state=42, categories = categories, remove = remove )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Возвращаемый набор данных — это *scikit-learn* совокупность: одномерный контейнер с полями, которые могут интерпретироваться как признаки объекта (*object attributes*). Например, `target_names` содержит список названий запрошенных категорий, `target` - тематику сообщения, а `data` – непосредственно текст сообщения:" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Does anyone know of any good shareware animation or paint software for an SGI\n", + " machine? I've exhausted everyplace on the net I can find and still don't hava\n", + " a nice piece of software.\n", + "\n", + "Thanks alot!\n", + "\n", + "Chad\n", + "\n", + "\n" + ] + } + ], + "source": [ + "print (twenty_train.data[2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Векторизация" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Чтобы использовать машинное обучение на текстовых документах, первым делом, нужно перевести текстовое содержимое в числовой вектор признаков.\n", + "Предобработка текста, токенизация и отбрасывание стоп-слов включены в состав модуля `CountVectorizer`, который позволяет создать словарь характерных признаков и перевести документы в векторы признаков.\n", + "Создадим объект-векторизатор `vect` со следующими параметрами: \n", + "* `max_features = 10000` - количество наиболее частотных терминов, из которых будет состоять словарь. По умолчанию используются все слова.\n", + "* `stop_words = 'english'` – на данный момент модулем поддерживается отсечение английских стоп-слов. Кроме того, здесь можно указать список стоп-слов вручную. Если параметр не указывать, будут использованы все термины словаря.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [], + "source": [ + "vect = CountVectorizer(max_features = 10000, stop_words = 'english')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также, в работе может потребоваться настройка следующих параметров: \n", + "* `max_df` - `float` в диапазоне [0.0, 1.0] или `int`, по умолчанию = 1.0. При построении словаря игнорирует термины, частота которых в документе строго превышает заданный порог (стоп-слова для конкретного корпуса). Если `float`, параметр обозначает долю документов, если целое число – то абсолютное значение. \n", + "* `min_df` - `float` в диапазоне [0.0, 1.0] или `int`, по умолчанию = 1.0. При построении словаря игнорируйте термины, частота которых в документе строго ниже заданного порога. В литературе это значение также называется порогом. Если `float`, параметр обозначает долю документов, если целое число – то абсолютное значение. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "После того как объект-векторизатор создан, необходимо создать словарь характерных признаков с помощью метода fit() и перевести документы в векторы признаков c помощью метода transform(), подав на него обучающую выборку: " + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [], + "source": [ + "vect.fit(twenty_train.data)\n", + "\n", + "train_data = vect.transform(twenty_train.data)\n", + "test_data = vect.transform(twenty_test.data)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Также, можно отметить что эти два действия могут быть объединены одним методом `fit_transform()`. Однако, в этом случае нужно учесть, что для перевода тестовой выборки в вектор признаков, по-прежнему нужно использовать метод `transform()`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [], + "source": [ + "train_data = vect.fit_transform(twenty_train.data)\n", + "test_data = vect.transform(twenty_test.data)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если для тестовых данных также воспользоваться методом `fit_transform()`, это приведет к перестроению словаря признаков и неправильным результатам классификации. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Следующий блок кода позволит вывести первые 10 терминов, упорядоченных по частоте встречаемости:" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('image', 489), ('don', 417), ('graphics', 410), ('god', 409), ('people', 384), ('does', 364), ('edu', 349), ('like', 329), ('just', 327), ('know', 319)]\n" + ] + } + ], + "source": [ + "x = list(zip(vect.get_feature_names(), np.ravel(train_data.sum(axis=0))))\n", + "def SortbyTF(inputStr):\n", + " return inputStr[1]\n", + "x.sort(key=SortbyTF, reverse = True)\n", + "print (x[:10])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Если для каждого класса по-отдельности получить подобные списки наиболее частотных слов, то можно оценить пересекаемость терминов двух классов, например, с помощью [меры сходства Жаккара](https://ru.wikipedia.org/wiki/Коэффициент_Жаккара).\n", + "Ниже приведена функция, которая на вход принимает два списка слов и возвращает значение коэффициента Жаккара:" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "def jaccard_similarity(list1, list2):\n", + " intersection = len(list(set(list1).intersection(list2)))\n", + " union = (len(set(list1)) + len(set(list2))) - intersection\n", + " return float(intersection) / union\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Стемминг" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Существует целый ряд алгоритмов стемминга. В работе предлагается использовать алгоритм Портера, реализация которого приведена в библиотеке [nltk](https://www.nltk.org/)\n", + "Для проведения стемминга нужно создать объект `PorterStemmer()`. Стеммер работает таким образом: у созданного объекта `PorterStemmer` есть метод `stem`, производящий стемминга. Таким образом, необходимо каждую из частей выборки (обучающую и тестовую) разбить на отдельные документы, затем, проходя в цикле по каждому слову в документе, произвести стемминг и объединить эти слова в новый документ.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " I 'll take a wild guess and say freedom is object valuabl . I base thi on the assumpt that if everyon in the world were depriv utterli of their freedom ( so that their everi act wa contrari to their volit ) , almost all would want to complain . therefor I take it that to assert or believ that `` freedom is not veri valuabl '' , when almost everyon can see that it is , is everi bit as absurd as to assert `` it is not rain '' on a raini day . I take thi to be a candid for an object valu , and it it is a necessari condit for object moral that object valu such as thi exist .\n" + ] + } + ], + "source": [ + "from nltk.stem import *\n", + "from nltk import word_tokenize\n", + "\n", + "porter_stemmer = PorterStemmer()\n", + "stem_train = []\n", + "for text in twenty_train.data:\n", + " nltk_tokens = word_tokenize(text)\n", + " line = ''\n", + " for word in nltk_tokens:\n", + " line += ' ' + porter_stemmer.stem(word)\n", + " stem_train.append(line)\n", + "print (stem_train[0])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## TF- и TF-IDF взвешивание" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`CountVectorizer` позволяет лишь определять частоту встречаемости термина во всей выборке, но такой подход к выявлению информативных терминов не всегда дает качественный результат. На практике используют более продвинутые способы, наибольшее распространение из которых получили TF- и TF-IDF взвешивания. \n", + "Воспользуемся методом `fit()` класса `TfidfTransformer()`, который переводит матрицу частот встречаемости в TF- и TF-IDF веса.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [], + "source": [ + "tfidf = TfidfTransformer(use_idf = True).fit(train_data)\n", + "train_data_tfidf = tfidf.transform(train_data)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Отметим, что в метод `fit()` нужно передавать не исходные текстовые данные, а вектор слов и их частот, полученный с помощью метода `transform()` класса `CountVectorizer`. \n", + "Для того, чтобы получить tf-idf значения, необходимо установить параметр `use_idf = True`, в противном случае на выходе мы получим значения tf\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Классификация" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "После того как мы провели векторизацию текста, обучение модели и классификация для текстовых данных выглядит абсолютно идентично классификации объектов в первой лабораторной работе. \n", + "\n", + "Задача обучения модели заключается не только в выборе подходящих данных обучающей выборки, способных качественно охарактеризовать объекты, но и в настройке многочисленных параметров метода классификации, предварительной обработке данных и т.д.\n", + "Рассмотрим, какие возможности предлагаются в библиотеке scikit-learn для автоматизации и упрощения данной задачи.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pipeline\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Чтобы с цепочкой *vectorizer* => *transformer* => *classifier* было проще работать, в scikit-learn есть класс `Pipeline` (конвейер), который функционирует как составной (конвейерный) классификатор." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.pipeline import Pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Промежуточными шагами конвейера должны быть преобразования, то есть должны выполняться методы `fit()` и `transform()`, а последний шаг – только `fit()`.\n", + "При этом, *pipeline* позволяет устанавливать различные параметры на каждом своем шаге. Таким образом, проделанные нами действия по векторизации данных, взвешиванию с помощью TF-IDF и классификации методом К-БС с использованием pipeline будут выглядеть следующим образом: \n" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [], + "source": [ + "text_clf = Pipeline([('vect', CountVectorizer(max_features= 1000, stop_words = 'english')),\n", + " ('tfidf', TfidfTransformer(use_idf = True)),\n", + " ('clf', KNeighborsClassifier (n_neighbors=1)),]) \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Названия `vect`, `tfidf` и `clf` выбраны нами произвольно. Мы рассмотрим их использование в следующей лабораторной работе. Теперь обучим модель с помощью всего 1 команды:" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [], + "source": [ + "text_clf = text_clf.fit(twenty_train.data, twenty_train.target)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "И проведем классификацию на тестовой выборке:" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [], + "source": [ + "prediction = text_clf.predict(twenty_test.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}