Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

433 строки
21 KiB
Plaintext

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

{
"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": 9,
"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": 10,
"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": 11,
"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": 12,
"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": 13,
"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": 14,
"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": 15,
"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_out(), 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": 16,
"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": 17,
"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": 18,
"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": 19,
"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": 20,
"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": 21,
"metadata": {},
"outputs": [],
"source": [
"text_clf = text_clf.fit(twenty_train.data, twenty_train.target)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"И проведем классификацию на тестовой выборке:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"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 (ipykernel)",
"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.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 4
}