{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Uczenie Maszynowe: Naiwny klasyfikator bayesowski\n", "\n", "## Zadanie 1\n", "Zadanie polega na implementacji klasyfikatora naiwnego Bayesa dla zmiennych ciągłych, gdzie za rozkłady cechy przyjmij rozkłady normalne.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "plt.style.use(\"ggplot\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Do testowania twojego rozwiązania użyj trzech generatorów danych sztucznych `generate1`, `generate2` oraz `generate3` (funkcje te przyjmują jako argument liczbę elementów do wygenerowania z każdej klasy - domyślnie $N=100$). Sposób ich wywołania jest przedstawiony poniżej:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Bayes_helpers import generate1, generate2, generate3\n", "\n", "X, y = generate1() #możesz dodać argument (10) oraz print X,y żeby zobaczyć, jak te tablice dokładnie wyglądają\n", "plt.scatter(X[:,0], X[:,1], c = y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "W implementacji będzie przydatna klasa `norm` z pakietu `scipy`, która zwraca wartości funkcji gęstości prawdopodobieństwa dla zmiennych ciągłych." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from scipy.stats import norm\n", "\n", "# (X, mean, std)\n", "norm.pdf(5, 0, 1) #gęstość prawd. rozkładu standardowego dla wartości 5\n", "#norm.logpdf(5, 0, 1) #logarytm gęstości prawd. rozkładu standardowego dla wartości 5" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Zaimplementuj klasyfikator naiwnego Bayesa dla zmiennych ciągłych. Pamiętaj o zabezpieczniu się przed problemem wynikającym z mnożenia wielu małych liczb (prawdopodobieństw)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "\n", "class GaussianNaiveBayes():\n", " def __init__(self):\n", " self.means = {} \n", " # Słownik, który docelowo powinien zawierać tablicę/wektor warunkowych średnich dla każdego atrybutu \n", " # Każda tablica/wektor powinna być typu np.array\n", " # np. 1) means[1] powinno zawierać wektor średnich wartości atrybutów dla klasy o indeksie 1\n", " # 2) means[0][1] powinno zawierać średnią 1-go atrybutu dla klasy o indeksie 0\n", " # (Możesz spróbować zaimplementować efektywniejszą implementację używając macierzy)\n", " self.stds = {} \n", " # Analogiczna struktura dla odchyleń standardowych\n", " self.class_log_prob = None \n", " # Wektor zawierający logarytmy prawdopodobieństwa dla każdej z klas \n", " # np. class_log_prob[1] zawiera logarytm prawdopodobieństwa, że klasa jest równa 1, log P(C=1)\n", " \n", " def fit(self, X, y):\n", " # TWÓJ KOD TUTAJ - proces uczenia czyli uzupełniania struktur zainicjaliowanych w init()\n", " # X jest macierzą gdzie każdy wiersz zawiera kolejną obserwację (obie typu np.array) \n", " # y jest wektorem wartości indeksu klasy (0 lub 1). Jego wartości odpowiadają kolejnym wierszom X\n", "\n", " \n", " def predict_proba(self, X):\n", " # TWÓJ KOD TUTAJ - predykcja - zwrócenie prawdopodobieństwa dla każdej klasy i każdej obserwacji\n", " # Funkcja powinna zwrócić macierz o dwóch kolumnach (dwie klasy) w której kolejne wiersze \n", " # zawierają prawdopodobieństwa P(c|x) przynależności dla klas dla kolejnych obserwacji w macierzy X\n", "\n", " \n", " def predict(self, X):\n", " # Gotowa funkcja wybierająca klasę z największym prawdopodobieństwem\n", " prob = self.predict_proba(X)\n", " return np.argmax(prob, axis=1)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Przetestuj twój klasyfikator na wygenerowanych wcześniej danych." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "gnb = GaussianNaiveBayes()\n", "gnb.fit(X,y)\n", "#Trafność na zbiorze uczącym\n", "np.mean(gnb.predict(X) == y) #argumentem mean() jest tablica zawierająca Fałsze (0) i Prawdy (1), mean() liczy udział Prawd w całości tej tablicy - czyli trafność\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Bayes_helpers import plotGaussianBayes\n", "plotGaussianBayes(X, y, gnb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Użyj funkcji do generowania danych, aby wygenerować zbiór testowy oraz sprawdź na nim trafność klasyfikacji metody." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "**Ćwiczenia**\n", " - Pamiętaj o przetestowaniu Twojego algorytmu dla wszystkich trzech generatorów danych. W których ze zbiorów założenie o warunkowej niezależności zmiennych jest spełnione? Jak brak spełnienia tego założenia wpływa na działanie klasyfikatora?\n", " - Z pliku `Bayes_helpers` zaimportuj klasę `GaussianBayes` (identyczna obsługa jak tej zaimplementowanej przez Ciebie). Klasa implementuje algorytm Bayesa bez założenia o niezależności zmiennych (ale z założeniem o normalności rozkładów). Porównaj wyniki - szczególnie dla zbiorów dla których założenie o warunkowej niezależności zmiennych nie jest spełnione.\n", " - Klasyfikatora `GaussianBayes` nie można wytrenować na zbiorach które mają mniej niż 3 przykłady dla każdej z klas. Jak myślisz dlaczego? Jak ten problem będzie się zmieniał dla zbiorów o wysokiej liczbie cech?\n", " - Nawet używając klasyfikatora `GaussianBayes`, który zakłada kompletny model zależności i prawidłowy rozkład danych (nasze dane są generowane z rozkładów normalnych) - często nie jest w stanie uzyskać 100% trafności nawet na zbiorze uczącym. Jak myślisz, dlaczego? \n", " - Czy gdyby przepisać do klasyfikatora prawdziwe wartości średnich i macierz wariancji-kowariancji cech (z generatora) - uzyskalibyśmy 100% trafność? Co możemy powiedzieć o takim klasyfikatorze? Czy jest możliwe uzyskanie klasyfikatora bardziej trafnego niż taki? " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Zadanie 2\n", "Zaimplementuj algorytm naiwnego Bayesa dla binarnych cech. Możesz założyć binarność, choć implementacja dla cech nominalnych nie byłaby dużo bardziej skomplikowana.\n", "\n", "*Wskazówka:* W zależności od Twojej implementacji funkcja `np.nan_to_num` może być przydatna do zabezpieczenia się przed sytuacją mnożenia zerowego prawdopodobienstwa (logarytm 0 na komputerze to $-\\infty$) przez zero." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from scipy.stats import norm\n", "class NaiveBayes():\n", " def __init__(self):\n", " self.prob = {}\n", " # Słownik, który docelowo powinien zawierać tablicę/wektor warunkowych prawdopodobieństw dla każdego atrybutu \n", " # Każda tablica/wektor powinna być typu np.array\n", " # np. 1) prob[2] powinno zawierać wektor prawdopodobieństw o długości równej liczbie atrybutów. Każda kolejna wartość wektora to prawdopodobieństwo, że dla klasy o indeksie 2 kolejny atrybut przyjmie wartość 1.\n", " # 2) prob[0][6] powinno zawierać prawdopodobieństwo, że szósty atrybut równa się 1 dla klasy o indeksie 0.\n", " # (Możesz spróbować zaimplementować efektywniejszą implementację używając macierzy)\n", " self.class_log_prob = None\n", " # Wektor zawierający logarytmy prawdopodobieństwa dla każdej z klas \n", " # np. class_log_prob[1] zawiera logarytm prawdopodobieństwa, że klasa jest równa 1, log P(C=1)\n", " \n", " def fit(self, X, y):\n", " # TWÓJ KOD TUTAJ\n", "\n", " \n", " def predict_proba(self, X):\n", " # TWÓJ KOD TUTAJ\n", " \n", " def predict(self,X):\n", " prob = self.predict_proba(X)\n", " return np.argmax(prob, axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Przetestuj algorytm dla podanych danych. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "nb = NaiveBayes()\n", "X = np.array([[1,1,1], [0,1,1], [0,0,1], [0,0,0]])\n", "y = np.array([1,1,0,0])\n", "nb.fit(X,y)\n", "nb.predict_proba(X)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podejrzyjmy wyestymowane wartości prawdopodobieństw:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "nb.prob" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Spójrzmy na analogiczną listę estymat dla gotowej implementacji algorytmu `FullBayes` (czyli wersji algorytmu bez założenia o niezależności)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Bayes_helpers import FullBayes\n", "fb = FullBayes()\n", "fb.fit(X,y)\n", "fb.prob" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Czy coś cię niepokoi w wyświetlonych estymacjach? Jak ten problem będzie się zmieniał w zależności od rozmiaru zbioru i rozmiaru wymiarowości?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Zadanie 2b\n", "Rozszerz Twój kod o estymowanie prawdopodobieństwa estymatą Laplace'a." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class SmoothNaiveBayes(NaiveBayes): \n", " def fit(self, X, y):\n", " # TWÓJ KOD TUTAJ\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Również przetestuj działanie metody i porównaj uzyskane estymaty z wersją algorytmu bez rozmywania." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "snb = SmoothNaiveBayes()\n", "snb.fit(X,y)\n", "snb.predict_proba(X)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Pora na test działania zaimplementowanych metod na większych zbiorach danych. W tym celu użyjemy funkcji `generate_binary`, która generuje sztuczne binarne dane. Jej pierwszym argumentem jest liczba przykładów uczących, a drugim argumentem jest liczba cech $k$. Poniższy kod nie tylko generuje dane, ale także dzieli je na cześć uczącą i testową." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Bayes_helpers import generate_binary\n", "Xb, yb = generate_binary(5500, k = 10)\n", "X_train, y_train = Xb[:5000], yb[:5000]\n", "X_test, y_test = Xb[5000:], yb[5000:]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Wytrenuj na tych większych danych zaimplementowane przez ciebie algorytmy `NaiveBayes` i `SmoothNaiveBayes`, także gotowe metody `FullBayes` i `SmoothFullBayes` z `Bayes_helpers`, które są pozbawione założenia o niezależności zmiennych. Dla każdego klasyfikatora zmierz trafność klasyfikacji i podejrzyj zawartości estymat `print(classifier.prob)`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "from Bayes_helpers import SmoothFullBayes, FullBayes\n", "#TWÓJ KOD TUTAJ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ile estymat zawiera klasyfikator \"pełny\", a ile klasyfikator naiwny? Jak będzie się to zmieniać wraz z rosnącą liczbą wymiarów?\n", "\n", "# Zadanie 2c\n", "Zbadajmy zależność pomiędzy trafnością klasyfikacji zaimplementowanych metod przy zmieniającym się rozmiarze zbioru danych. Poniższy kod jest gotowy i początkowo nie musisz w nim nic modyfikować." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "from Bayes_helpers import SmoothFullBayes, FullBayes, plotAccuracyIterationsPlot\n", "from collections import defaultdict\n", "\n", "#Generowanie danych\n", "Xb, yb = generate_binary(5500, k = 10) # <- Tu kontrolujesz liczbę cech\n", "X_train, y_train = Xb[:5000], yb[:5000]\n", "X_test, y_test = Xb[5000:], yb[5000:]\n", "\n", "N = X_train.shape[0]\n", "iterations = range(50, N, 50) # <- Kontrola ewaluowanych punktów (od 50 do N co 50)\n", "\n", "results = defaultdict(list)\n", "results_train = defaultdict(list)\n", "\n", "for i in iterations:\n", " for classifier in [NaiveBayes(), SmoothNaiveBayes(), FullBayes(), SmoothFullBayes()]:\n", " classifier.fit(X_train[:i],y_train[:i])\n", " results_train[type(classifier).__name__].append(np.mean(classifier.predict(X_train[:i]) == y_train[:i]))\n", " results[type(classifier).__name__].append(np.mean(classifier.predict(X_test) == y_test))\n", "\n", "# Tę ostatnią linijkę możesz chcieć wywoływać w osobnych komórkach, \n", "# tak aby na koniec porównać wykresy dla kliku ustawień\n", "plotAccuracyIterationsPlot(iterations, results, results_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wykonaj ekspermenty dla różnej liczby cech: 2, 10, 100, 500." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "**Ćwiczenia**\n", " - Czy rozmywanie ma pozytywne skutki dla klasyfikatora naiwnego? a dla klasyfikatora `FullBayes`?\n", " - Który klasyfikator z testowanych jest najlepszy w jakich sytuacjach?\n", " - Jaką trafność ma klasyfikator `FullBayes` na dostatecznie dużym zbiorze uczączym? W jakich sytuacjach `NaiveBayes` mógłby również osiągnąć 100% na uczącym?\n", " - Czy jest możliwe, żeby klasyfikator `FullBayes` nie będzie miał 100% trafności (nawet zakładając nieskończenie wielki zbiór danych)?\n", " - Przeanalizuj wygenerowane wcześniej wykresy określając czy mówimy o przeuczeniu czy niedouczeniu czy ...\n", " - Czy można w jednym klasyfikatorze Naiwnego Bayesa łączyć cechy ciągłe z dyskretnymi? Dlaczego?\n", " - W jaki sposób można obejść powyższy problem? Podaj co najmniej dwa sposoby.\n", " - Klasyfikator naiwny robi założenie o warunkowej niezależności cech. Co mógłbyś zrobić jeżeli spodziewasz się, że pewne pary/grupy cech są zależne i może to mieć duży wpływ na jakość predykcji? Podaj przykład takiej sytuacji." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Zadanie 3\n", "Klasyfikator naiwnego Bayesa często jest używany do klasyfikacji tekstów. Przetestuj działanie algorytmów na podanym rzeczywistym zbiorze danych: \n", "> The 20 newsgroups dataset comprises around 18000 newsgroups posts on 20 topics split in two subsets: one for training (or development) and the other one for testing (or for performance evaluation). The split between the train and test set is based upon a messages posted before and after a specific date.\n", "\n", "Podany zbiór jest wieloklasowy, więc poniższy kod wybiera z niego podzbiór postów tylko z dwóch tematów." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from sklearn.datasets import fetch_20newsgroups\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "\n", "categories = [ 'comp.graphics', 'sci.space']\n", "newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)\n", "newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)\n", "\n", "vectorizer = TfidfVectorizer(binary=True) # Przekształcenie tekstu na cechy binarne\n", "vectors = vectorizer.fit_transform(newsgroups_train.data)\n", "vectors_test = vectorizer.transform(newsgroups_test.data)\n", "vectors = vectors.toarray()\n", "vectors_test = vectors_test.toarray()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dokumenty w zbiorze można wyświetlić w następujący sposób." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "newsgroups_train.data[0:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Analogicznie możemy uzyskać dostęp do informacji o klasach:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "newsgroups_train.target[0:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...i do \"zbinaryzowanego\" tekstu:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "vectors[0:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wytrenuj klasyfikator i sprawdż jego trafność na zbiorze uczącym i testowym." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "**Ćwiczenia**\n", " - Dlaczego klasyfikator Naiwnego Bayesa dość dobrze sprawdza się do powyższego zadania i analogicznych?\n", " - Przeanalizuj wartości estymat prawdopodobieństw. Które cechy/słowa są najlepszymi wskaźnikami dla podanych klas? Jakie słowa bardzo słabo wskazują na którąkolwiek z klas?\n", " - Czy byłoby możliwe wytrenowanie równie skutecznego klasyfikatora z podobną liczbą cech? W jaki sposób można by to uzyskać?\n", " - Analizowany zbiór jest oryginalnie wieloklasowy z tego powodu możemy go wykorzystać do wielu testów wybierając różne pary klas. Pełna lista tematów: 'alt.atheism',\n", " 'comp.graphics',\n", " 'comp.os.ms-windows.misc',\n", " 'comp.sys.ibm.pc.hardware',\n", " 'comp.sys.mac.hardware',\n", " 'comp.windows.x',\n", " 'misc.forsale',\n", " 'rec.autos',\n", " 'rec.motorcycles',\n", " 'rec.sport.baseball',\n", " 'rec.sport.hockey',\n", " 'sci.crypt',\n", " 'sci.electronics',\n", " 'sci.med',\n", " 'sci.space',\n", " 'soc.religion.christian',\n", " 'talk.politics.guns',\n", " 'talk.politics.mideast',\n", " 'talk.politics.misc',\n", " 'talk.religion.misc'\n", " - Czy są pary tematów dla których ten klasyfikator działa znacząco gorzej?\n", " - Jakie są zalety stosowania klasyfikatora Bayesa dla tego problemy (i w ogólności)? Czy do tego problemu sprawdziłyby się reguły lub drzewa decyzyjne? Dlaczego?" ] } ], "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.6.7" } }, "nbformat": 4, "nbformat_minor": 0 }