{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Лабораторная работа 3.\n", "\n", "## Общая информация\n", "\n", "Дата выдачи: 08.03.2016\n", "\n", "Срок сдачи: 29.03.2016 09:00MSK\n", "\n", "### О задании\n", "Лабораторная работа №3 направлена на реализацию наивного байесвского классификатора для многоклассового случая.\n", "\n", "### Оценивание и штрафы\n", "Каждая из задач имеет определенную «стоимость» (указана в скобках около задачи). Максимально допустимая оценка за работу — 10 баллов. Обратите внимание, что только за реализацию функций без подтверждения их корректной работы оценка выставляться не будет.\n", "\n", "Сдавать задание после указанного срока сдачи нельзя. При выставлении неполного балла за задание в связи с наличием ошибок на усмотрение проверяющего предусмотрена возможность исправить задание на указанных в ответном письме условиях.\n", "\n", "Задание выполняется САМОСТОЯТЕЛЬНО. «Похожие» решения считаются плагиатом и все задействованные студенты (в том числе те, у кого списали) не могут получить за него больше 0 баллов. Если вы нашли решение какого-то из заданий в открытом источнике, необходимо прислать ссылку на этот источник (скорее всего вы будете не единственным, кто это нашел, поэтому чтобы исключить подозрение в плагиате, необходима ссылка на источник).\n", "\n", "Если вы будете решать задание на виртуальной машине, учтите, что его могут видеть все. К тому же недоступность виртуальной машины не является уважительной причиной для продления дедлайна.\n", "\n", "### Формат сдачи\n", "Для сдачи задания переименуйте получившийся файл *.ipynb в соответствии со следующим форматом: Username_(group)_Lab3.ipynb, где Username — ваша фамилия на латинице, group — название группы (например, Kozlova_IAD-11_Lab3.ipynb). Далее отправьте этот файл на используемую в Вашей группе почту курса (hse.minor.dm@gmail.com) c темой письма [ИАД-NN] - Лабораторная работа 3 - Фамилия Имя Отчество." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Теория вероятностей" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np\n", "import scipy as sp\n", "\n", "import pylab as plt\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(2 балла)** Для каждого из распределений (биномиального и Пуассона) выполните следующее задание при разных значениях параметров (p и $\\lambda$ соответственно):\n", " - сгенерируйте 500 точек\n", " - посчитайте для них следующие статистики: среднее, медиану, дисперсию, среднеквадратичное отклонение\n", " - постройте гистограммы (не забывайте подписывать оси)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Наивный байесовский классификатор\n", "\n", "На семинаре был рассмотрен пример как с помощью наивного байесовского алгоритма решать задачу бинарной классификации. В лабораторной работе мы попробуем решить аналогичную задачу для случая трех классов.\n", "\n", "### Данные\n", "\n", "Данными для задачи будет выступать подмножество одного из популярных датасетов [20 News Groups](http://qwone.com/~jason/20Newsgroups/), который является набором статей, разбитых по категориям (темам). В задаче мы будем работать с тремя из них: атеизм (в данных имеет обозначение *alt.atheism*), космос (*sci.space*) и религия (*soc.religion.christian*). \n", "\n", "Пример статьи на тему космоса:\n", " \n", " From: MUNIZB%RWTMS2.decnet@rockwell.com (\"RWTMS2::MUNIZB\") \n", " \n", " Subject: Alaska Pipeline and Space Station! \n", " \n", " X-Added: Forwarded by Space Digest \n", " \n", " Organization: [via International Space University] \n", " \n", " Original-Sender: isu@VACATION.VENARI.CS.CMU.EDU \n", " \n", " Distribution: sci \n", " \n", " Lines: 16 \n", " \n", " on Date: 01 Apr 93 18:03:12 GMT, Ralph Buttigieg writes: \n", " \n", " Why can't the government just be a tennant? Private commercial concerns could just build a space station system and charge rent to the government financed researchers wanting to use it. I believe that this was the thought behind the Industrial Space Facility. I don't remember all the details, but I think Space Services (?) wanted NASA to sign an anchor tenancy deal in order to help secure some venture capital but NASA didn't like the deal. (I'm sure I'll hear about it if I'm wrong!) \n", " \n", " Disclaimer: Opinions stated are solely my own (unless I change my mind). \n", " \n", " Ben Muniz \n", " MUNIZB%RWTMS2.decnet@consrt.rockwell.com w(818)586-3578 Space Station \n", " Freedom:Rocketdyne/Rockwell:Structural Loads and Dynamics \"Man will not fly for fifty years\": Wilbur to Orville Wright, 1901\n", "\n", "Можно заметить, что данные никак не предобработаны, пунктуация и служебная информация сохранены.\n", "\n", "### Цель задания\n", "\n", "Последовательно построить классификатор, который по некоторой статье делал бы предположение к какой из трех тем она принадлежит.\n", "\n", "### Оценивание качества классификации\n", "\n", "В этот раз для оценки качества мы будем использовать метрику **accuracy**. Она означает долю верных ответов классификатора.\n", "\n", "В виде формулы ее можно записать следующим образом:\n", "\n", "$Accuracy(X^l) = \\dfrac{1}{l}\\sum_{x_i}[y(x_i) = \\hat{y}(x_i)]$\n", "\n", "В данном случае:\n", " - $X^l$ — некоторая тестовая выборка, по которой оценивается качество\n", " - $l$ — количество объектов в выборке $X^l$\n", " - $y(x_i)$ — истинный класс объекта $x_i$\n", " - $\\hat{y}(x_i)$ — предсказанный класс объекта $x_i$\n", " - выражение $[y(x_i) = \\hat{y}(x_i)]$ равно 1, если истинный и предсказанный классы совпадают и 0 иначе.\n", " \n", "Чем больше значение этой метрики, то есть чем выше доля правильных ответов, тем лучше работает классификатор. Accuracy наилучшего классификатора равно 1, а наихудшего — 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ход работы" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В данной работе вам будет необходимо дописывать код на месте пропусков, обозаченных как\n", " \n", " ##################\n", " ### YOUR CODE HERE\n", " ##################" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для начала импортируем библиотеки, которые могут понадобиться для выполнения этого задания." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "from __future__ import division" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Загрузите обучающую и тестовы выборки. Они находятся в файлах *data.train.csv* и *data.test.csv*. Вычисления всех параметров будут производиться на обучающей выборке. Тестовая выборка нужна только для вычисления итогового качества построенного классификатора." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data_train = pd.read_csv('data.train.csv', sep='\\t')\n", "data_test = pd.read_csv('data.test.csv', sep='\\t')\n", "data_train.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В следующей ячейки написана реализация функции (*preprocess_message*), которой на вход передается текст, который обрабатывается следующим образом:\n", " - все слова в нем приводятся к нижнему регистру (*text.lower()*)\n", " - все \"нетипичные\" символы (то есть любой не латинский символ) заменяются на пробелы (*re.sub(\"[^a-zA-Z]\", \" \", text.lower())*), после чего сам текст разбивается на слова\n", " - для каждого слова считается сколько раз оно встретилось в тексте (*Counter(words)*, подробнее прочитать что такое Counter можно [здесь](https://docs.python.org/2/library/collections.html#collections.Counter))\n", " \n", "В итоге эта функция возвращается для каждого текста какие слова и сколько раз в нем содержатся." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import re\n", "from collections import Counter\n", "\n", "def preprocess_message(text):\n", " text_lower = text.lower()\n", " words = re.sub(\"[^a-zA-Z]\", \" \", text_lower).split()\n", " return Counter(words)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Примените описанную выше функцию к любому тексту из обучающей выборки" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "##################\n", "### YOUR CODE HERE\n", "##################" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Преобразуем наши данные с помощью указанной выше функции" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "train_messages = data_train.apply(lambda row: preprocess_message(row['text']), axis=1)\n", "data_train.iloc[:, 1] = train_messages\n", "test_messages = data_test.apply(lambda row: preprocess_message(row['text']), axis=1)\n", "data_test.iloc[:, 1] = test_messages\n", "\n", "data_train.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вспомним о том, что изначально наша цель — написать классификатор, который бы каждому тексту умел в соответствие ставить некоторый класс. В данной работе мы реализуем наивный байесовский классификатор, который записывается кледующим образом:\n", "\n", "$a(x) = \\arg\\max_{y} P(y~|~(x^1, \\cdots, x^n)) = \\arg\\max_{y} \\prod_j{P(x^j~|~y})P(y) $\n", "\n", "где\n", " - $P(y)$ вероятность класса $y$\n", " - $P(x^j~|~y)$ — веротность некоторого признака $x^j$ при условии класса $y$\n", " \n", "Небольшая проблема в том, что указанные вероятности нам изначально не заданы, но можно их оценить, используя имеющуюся обучающую выборку.\n", "\n", "Для начала попробуем разобраться как оценивать $P(\\dot{y})$. Будем считать, что это значение — доля объектов класса $\\dot{y}$ в обучающей выборке. \n", "\n", "**(1 балл)** Реализуйте функцию *classes_prob*, которой на вход передаются метки классов выборки, а результатом является dict, в котором для каждого класса, что встретился в выборке, записана его вероятность (то есть доля объектов этого класса среди всех).\n", "\n", "Например, если этой функции передать на вход [1, 2], то она должна вернуть следующий словарь: { 1: 0.5, 2: 0.5}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def classes_prob(targets):\n", " class_probs = {}\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " \n", " return class_probs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Протестируйте свою функцию, выполнив следующую ячейку. Если хотя бы один тест провален (хотя бы одной \"Failed: ...\"), значит ваша реализация некорректна. (Обратите внимание, что получившиеся вероятности должны быть больше 0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "assert classes_prob([1, 1, 1]) == { 1: 1}, \"Failed: 'classes_prob([1, 1, 1]) == { 1: 1}'\"\n", "assert classes_prob(['1', '1', '1']) == { '1': 1}, \"Failed: 'classes_prob([\\'1\\', \\'1\\', \\'1\\']) == { \\'1\\': 1}'\"\n", "assert classes_prob([1, 1, 2, 2]) == { 1: 0.5, 2: 0.5}, \"Failed: 'classes_prob([1, 1, 2, 2]) == { 1: 0.5, 2: 0.5}'\"\n", "assert classes_prob([1, 2, 3]) == { 1: 1 / 3, 2: 1 / 3, 3: 1 / 3}, \"Failed: 'classes_prob([1, 2, 3]) == { 1: 1 / 3, 2: 1 / 3, 3: 1 / 3}'\"\n", "assert classes_prob([1, 2, 3, 1]) == { 1: 0.5, 2: 0.25, 3: 0.25}, \"Failed: 'classes_prob([1, 2, 3, 1]) == { 1: 0.5, 2: 0.25, 3: 0.25}'\"\n", "assert classes_prob(['one', 'two']) == { 'one': 0.5, 'two': 0.5}, \"Failed: 'classes_prob([\\'one\\', \\'two\\']) == { \\'one\\': 0.5, \\'two\\': 0.5}'\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, вычислите вероятности для всех классов обучающей выборки, выполнив следующую ячейку:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class_probs = classes_prob(data_train.target)\n", "print class_probs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь пришла пора вычислить оценки вероятностей $P(x^j~|~y)$. В данном случае признак $x^j$ — это слово текста. \n", "\n", "Как будем оценивать вероятности слов для заданного класса? \n", "\n", "1. Найдем все уникальные слова.\n", "2. Для каждого слова посчитаем в скольки текстах определенной тематики оно встречалось\n", "3. Посчитаем для каждого класса: $P(x^j~|~y) = \\dfrac{count(x^j, x^j \\in y) + \\alpha}{count(y) + |V|\\alpha}$ (здесь $count(x^j, x^j \\in y)$ — количество раз сколько слово $x^j$ встретилось в классе $y$, а $count(y)$ — количество слов в обучающей выборке для класса $y$)\n", "\n", "где общее $|V|$ — количество уникальных слов" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1 балл)** Для начала реализуем функцию, которая будет делать следующее: на вход ей передается размеченная выборка (то есть для каждого текста известно к какой категории он относится), а возвращаемый результат — это dict, в котором ровно столько ключей, сколько классов в выборке. Значение при каждом ключе — это тоже словарь, в котором посчитано сколько раз слово встречалось в текстах с заданным классом. В качестве словарей, хранящих значения частот, лучше использовать структуру [defaultdict](https://docs.python.org/2/library/collections.html#collections.defaultdict).\n", "\n", "Например, представим себе игрушечную выборку, в которой предложения есть предложения \"Cat eats a bird\", \"Dog ats a bone\" имеют метку класса \"1\", в предложение \"Person eats a banana\" имеет метку класса 0. Преобразуем их аналогично тому, как мы поступили в данными в нашей задаче:\n", " - {'cat': 1, 'eats': 1, 'a': 1, 'bird': 1},\n", " - {'dog': 1, 'eats': 1, 'a': 1, 'bone': 1},\n", " - {'person': 1, 'eats': 1, 'a': 1, 'banana': 1}\n", "\n", "Тогда ваша функция должна вернуть следующее:\n", "{1: {'a': 2, 'bird': 1, 'bone': 1, 'cat': 1, 'dog': 1, 'eats': 2}, 0: {'a': 1, 'banana': 1, 'eats': 1, 'person': 1}}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from collections import defaultdict\n", "\n", "def find_counts(data):\n", " counts = {}\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return counts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Протестируйте свою функцию, выполнив следующую ячейку. Если хотя бы один тест провален (хотя бы одной \"Failed: ...\"), значит ваша реализация некорректна." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "small_data = pd.DataFrame([[1, {'cat': 1, 'eats': 1, 'a': 1, 'bird': 1}], \n", " [1, {'dog': 1, 'eats': 1, 'a': 1, 'bone': 1}],\n", " [0, {'person': 1, 'eats': 1, 'a': 1, 'banana': 1}]], columns=['target', 'text'])\n", "\n", "assert find_counts(small_data) == {0: defaultdict(int, {'a': 1, 'banana': 1, 'eats': 1, 'person': 1}),\n", " 1: defaultdict(int, {'a': 2, 'bird': 1, 'bone': 1, 'cat': 1, 'dog': 1, 'eats': 2})}, \"Failed!\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, вычислите частоты для всех слов обучающей выборки, выполнив следующую ячейку:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "counts = find_counts(data_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь, наконец-то, вычислим вероятности $P(x^j | y)$\n", "\n", "**(1 балл)** Реализуйте функцию *find_probs*, которой на вход будет передаваться dict, получающийся в резльтате функции *find_counts*, а результатом будет аналогичный словарь, где вместо количества раз, которое слово встречалось в тексте, будет стоять вероятность встретить слово в текстах данного класса. Вероятность вычисляется по формуле:\n", "\n", "$P(x^j~|~y) = \\dfrac{count(x^j, x^j \\in y) + \\alpha}{count(y) + |V|\\alpha}$ \n", "\n", "где общее $|V|$ — количество уникальных слов\n", "\n", "В качестве словарей, хранящих вероятности, лучше использовать структуру defaultdict, которая по неизвестному ранее ключу возвращала бы значение $\\frac{1}{|V|}$." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def find_probs(counts, alpha=0.1):\n", " probs = {}\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return probs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Протестируйте свою функцию, выполнив следующую ячейку. Получившиеся вероятности должны быть очень похожи на следующие (с точностью до 3-4 знака).\n", "\n", " {0: \n", " {'a': 0.22916666666666669,\n", " 'banana': 0.22916666666666669,\n", " 'bird': 0.020833333333333336,\n", " 'bone': 0.020833333333333336,\n", " 'cat': 0.020833333333333336,\n", " 'dog': 0.020833333333333336,\n", " 'eats': 0.22916666666666669,\n", " 'person': 0.22916666666666669},\n", " 1: \n", " {'a': 0.23863636363636362,\n", " 'banana': 0.011363636363636364,\n", " 'bird': 0.125,\n", " 'bone': 0.125,\n", " 'cat': 0.125,\n", " 'dog': 0.125,\n", " 'eats': 0.23863636363636362,\n", " 'person': 0.011363636363636364}}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "find_probs(find_counts(small_data))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, вычислите вероятности для всех слов обучающей выборки, выполнив следующую ячейку:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "probs = find_probs(counts, alpha=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наш классификатор почти написан. Осталась только реализовать функцию, которая будет выполнять саму классификацию. На вход этой функции будет передаваться посчитанные вероятности классов, вероятности слов для каждого класса и сообщение, которое нужно классифицировать.\n", "\n", "Сама же классификация происходит исходя из формулы: $a(x) = \\arg\\max_{y} \\prod_j{P(x^j~|~y})P(y) $\n", "\n", "**(1 балл)** Реализуйте функцию *classify*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def classify(class_probs, probs, message):\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Протестируйте свою функцию, выполнив следующую ячейку. Если хотя бы один тест провален (хотя бы одной \"Failed: ...\"), значит ваша реализация некорректна." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "assert classify({1: 2 / 3, 0: 1 / 3}, find_probs(find_counts(small_data)), {'cat': 1, 'eats': 1, 'a': 1, 'bone': 1}) == 1, \"Failed\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, выполните следующую ячейку. В ней записана функция, котроая будет выполнять классификацию, но которой на вход передается сообщение, а в качестве вероятностей классов и вероятностей слов при условии классов она использует те, что были вычислены ранее." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "make_classify = lambda message: classify(class_probs, probs, message)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1 балл)** Реализуйте функцию, которая бы вычисляла метрику *accuracy*:\n", "\n", "$Accuracy(X^l) = \\dfrac{1}{l}\\sum_{x_i}[y(x_i) = \\hat{y}(x_i)]$" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def accuracy(y_true, y_pred):\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Протестируйте свою функцию, выполнив следующую ячейку. Если хотя бы один тест провален (хотя бы одной \"Failed: ...\"), значит ваша реализация некорректна." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "assert accuracy([1, 2, 3], [3, 2, 1]) == 1 / 3, \"Failed: [1, 2, 3], [3, 2, 1]\"\n", "assert accuracy([1, 2, 3], [1, 2, 3]) == 1, \"Failed: [1, 2, 3], [1, 2, 3]\"\n", "assert accuracy([1, 2, 3], [2, 3, 1]) == 0, \"Failed: [1, 2, 3], [2, 3, 1]\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, посчитайте качество вашего классификатора, выполнив следующую ячейку:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "accuracy(data_test.target, data_test.text.apply(make_classify))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Качество получилось не очень. Давайте попробуем разобраться в чем же дело. Посмотрим внимательно на формулу, по которой делается классификация:\n", "\n", "$a(x) = \\arg\\max_{y} \\prod_j{P(x^j~|~\\dot{y}})P(y) $\n", "\n", "Если представить, что в тексте у вас достаточно много слов (посмотрите, например, на тест из начала), то перемножение большого количества вероятностей (которые лежат в интервале от 0 до 1), очень быстро приводит к тому, что итоговая величина становится равной 0 (так как компьютер не способен хранить такие очень маленькие величины). В этом случае скорее всего для всех классов эта величина становится равной 0 и алгоритм не может выбрать верный класс." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1 балл)** Есть ли в тестовой выборке примеры текстов у которых вероятности всех классов равны 0? Приведите их примеры и поясните как вы их нашли (например, приведите код). Сколько раз возникает такая ситуация для тестовой выборки? Какую долю от всей тестовой выборки это составляет?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "##################\n", "### YOUR CODE HERE\n", "##################" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как можно видеть выше, таких примеров получилось немало. Чтобы устранить этот недостаток, на практике преобразуют формулу, а именно логарифмируют:\n", "\n", "$a(x) = \\arg\\max_{y} \\log{\\prod_j{P(x^j~|~y})P(y)} = \\arg\\max_{y} (\\log{P(y)} + \\sum_{j}\\log{P(x^j~|~y)})$\n", "\n", "Это преобразование не сказывается на корректности вычисления ответа, однако позволяет избежать проблемы, описанной ранее.\n", "\n", "Давайте убедимся в этом на практике.\n", "\n", "**(0.5 балла)** Реализуйте функцию *classify_log*, которая будет аналогична функции *classify*, но вместо произведений будет вычислять сумму логарифмов." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def classify_log(class_probs, probs, message):\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Убедимся что и эта функция работает корректно:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "assert classify_log({1: 2 / 3, 0: 1 / 3}, find_probs(find_counts(small_data)), {'cat': 1, 'eats': 1, 'a': 1, 'bone': 1}) == 1, \"Failed\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После того как ваша функция начала работать корректно, выполните следующую ячейку. В ней записана функция, котроая будет выполнять классификацию, но которой на вход передается сообщение, а в качестве вероятностей классов и вероятностей слов при условии классов она использует те, что были вычислены ранее." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "make_classify_log = lambda m: classify_log(class_probs, probs, m)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь вычислите качество с помощью новой функции:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "accuracy(data_test.target, data_test.text.apply(make_classify_log))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если все было реализовано верно, то качество нашего классификатора должно было возрасти драматически." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Давайте теперь посмотрим на получившиеся вероятности. \n", "\n", "**(0.5 балла)** Для каждого класса выведете топ10 слов с наибольшими вероятностями." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "##################\n", "### YOUR CODE HERE\n", "##################" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Скорее всего у вас получился какой-нибудь мусор, например \"lines\", \"from\" и т.д. Откуда они могли взяться? Посмотрите на пример текста в датасете. Там некоторые сообщения — это переписка и наиболее вероятные слова как раз оттуда. Понятно, что они не несут никакой информации, но так как они встречаются почти в каждом сообщении, то и частоты у них выше. Давайте попробуем отсеить эти слова.\n", "\n", "Попробуем самый простой способ. Посчитаем сколько раз каждое слово встречалось в данных. После чего удалим из данных топ100 самых частотных слов и посмотрим увеличится ли итоговое качество после этого.\n", "\n", "**(0.5 балл)** Реализуйте функцию *count_freq*, которой передается список текстов, и которая для каждого слова вычисляет сколько раз оно встречалось в текстах." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def count_freqs(messages):\n", " ##################\n", " ### YOUR CODE HERE\n", " ##################\n", " return" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Убедимся что и эта функция работает корректно. Выполните следующую ячейку и сравните: значения должны быть близки к следующим:\n", "\n", " {'a': 3, 'banana': 1,\n", " 'bird': 1, 'bone': 1,\n", " 'cat': 1, 'dog': 1,\n", " 'eats': 3, 'person': 1}" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "count_freqs(small_data.text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вычислим эти значения для всех слов" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "words_counts = count_freqs(data_train.text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(0.5 балла)** Отсортируйте все слова по возрастанию частоты. Найдите топ100 самых частотных. Сохраните их в переменной *bad_words*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "##################\n", "### YOUR CODE HERE\n", "##################\n", "bad_words = " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь запустите код ниже. Улучшилось ли качество после того, как были удалены самые частотные слова? " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def filter_bad_words(text):\n", " words = {}\n", " for word, count in text.iteritems():\n", " if word not in bad_words:\n", " words[word] = count\n", " return words\n", "\n", "train_messages = data_train.apply(lambda row: filter_bad_words(row['text']), axis=1)\n", "data_train.iloc[:, 1] = train_messages\n", "test_messages = data_test.apply(lambda row: filter_bad_words(row['text']), axis=1)\n", "data_test.iloc[:, 1] = test_messages\n", "\n", "counts = find_counts(data_train)\n", "good_probs = find_probs(counts, alpha=0.1)\n", "\n", "make_classify_log = lambda m: classify_log(class_probs, good_probs, m)\n", "accuracy(data_test.target, data_test.text.apply(make_classify_log))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь снова выведите наиболее вероятные слова для каждого класса. Изменились ли они?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "##################\n", "### YOUR CODE HERE\n", "##################" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.10" } }, "nbformat": 4, "nbformat_minor": 0 }