{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Naiwna klasyfikacja bayesowska\n",
"\n",
"Naiwna klasyfikacja bayesowska jest to klasyfikacja polegająca na przydzielaniu obiektom prawdopodobieństwa przynależności do danej klasy. Naiwny klasyfikator jest to bardzo prosta metoda klasyfikacji jednak mimo swej prostoty sprawdza się w wielu przypadkach gdy bardziej złożone metody zawodzą.Jednym z powodów dla których naiwny klasyfikator wypadak dobrze jest jego założenie o niezależności predykatów, własnie dlatego nazywany jest klasyfikatorem naiwnym, naiwnie zakłada niezależność atrybutów opisujących dany przykład, co często nie ma odzwierciedlenia w rzeczywistości. Klasyfikator nazywany jets bayesowskim dlatego że wykorzystuje twierdzenie bayesa do obliczania prawdopodobieństw.\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Twierdzenie Bayesa\n",
"\n",
"Twierdzneie bayesa jets to twierdzenie tworii prawdopodobieństwa wiążące prawdopodobieństwa warunkowe dwóch zdarzeń warunkujących się nawzajem. Prawdopodobieństwo wystąpienia zdarzenia A gdy wystąpiło zdarzenie B oblicza się wzorem:\n",
"\n",
"$P(A|B) = \\frac{P(B|A) P(A)}{P(B)}$\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wykorzystanie twierdzenia Bayesa w naiwnej klasyfikacji bayesowskiej\n",
"\n",
"1\\) Nawiązując do twierdzenia bayesa prawdopodobieństwo problem klasyfikacji danego obiektu możemy zapisać w następujący sposb:\n",
"
\n",
"$P(K|X)$ Zapis ten oznacza że mając obiekt X chcemy obliczyć prawdopodobieństo przynaleźności do klasy K\n",
"\n",
"$P(K|X) = \\frac{P(X|K) P(K)}{P(X)}$ \n",
"\n",
"$P(K)$ - jest to prawdopodobieństwo a-priori klasy K\n",
"\n",
"$P(X|K)$ - ten zapis możemy interpretować w taki sposób: jeżeli klasa K to szansa że X do niej należy \n",
"\n",
"$P(X)$ - ale jak interpretować prawdopodobieństwo od odibkeu X? okazuje się że nie trzeba tego obliczać gdyż obliczając prawpodobieństwa przynależności X do klasy K w mianowniku zawsze bęzie $P(X)$ więc możemy to pomijać\n",
"\n",
"Najlepiej będzie pokazać to na przykładzie:\n",
"
\n",
"2\\) Prawdopobobieństwa a-priori\n",
"
\n",
"Prawdopodobieństwo a priori jest to prawdopodobieństwo obliczane przed realizacją odpowiednich eksperymentów lub obserwacji.\n",
"\n",
"![klasy](https://www.statsoft.pl/textbook/graphics/xNaiveBayesIntro1.gif.pagespeed.ic.tNwdOtpcQH.webp)\n",
"\n",
"Powyższa ilustracja przedstawia 2 klasy czerwona(20 obiektów) i zielona(40 obiektów), prawdopodobieństwa a priori tym przykładzie to nic innego jak \n",
"\n",
"$\\frac{liczebność\\ klasy}{liczebność\\ wszystkich\\ elementów}$, więc prawdopodobieńswta a priori dla powyższego przykładu są równe\n",
"\n",
"\n",
"a priori klasy zielonej = $\\frac{2}{3}\\\\$\n",
"a priori klasy czerwonej = $\\frac{1}{3}\\\\$\n",
"\n",
"To jest nasze $P(K)$\n",
"
\n",
"3\\) Klasyfikacja nowego obiektu\n",
"
\n",
"![nowy obiekt](https://www.statsoft.pl/textbook/graphics/xNaiveBayesIntro4.gif.pagespeed.ic.kzJ0xzlOjU.webp)\n",
"\n",
"Klasyfikując nowy obiekt korzystamy z prawdopodobieństw a-priori, jednak same prawdopodobieństwa a-priori są niweystarczające, w tym przykładzie rozsądnym założeniem jest klasyfikowanie obiektu również na podstawie jego najbliższych sąsiadów. Zaznaczając okręgiem obszar możemy obliczyć prawdopodobieństwo że nowy obiekt będzie czerwony albo zielony, prawdopodobieństwa te obliczane są ze wzoru, prawdopodobieństwa te zostaną użyte do obliczenia prawdopodobieństwa a posteriori.\n",
"\n",
"$\\frac{liczba\\ obiektów\\ danej\\ klasy\\ w\\ sąsiedztwie\\ nowego\\ obiektu}{liczba\\ obiektów\\ danej\\ klasy}$ i wynoszą odpowiednio:\n",
"\n",
"prawdopodobieństow że obiekt będzie w klasie zielonej = $\\frac{1}{40}\\\\$\n",
"prawdopodobieństow że obiekt będzie w klasie czerwonej = $\\frac{3}{20}\\\\$\n",
"\n",
"To jets nasze $P(X|K)$\n",
"
\n",
"4\\) Prawdopodobieństwo a posteriori\n",
"
\n",
"Mając już obliczone wszystkie potrzebne prawdopodobieństwa możemy obliczyć prawdopodobieństwa a posteriori\n",
"\n",
"Prawdopodobieństwo a posteriori jest to prawdopodobieństwo pewnego zdarzenia gdy wiedza o tym zdarzeniu wzbogacona jest przez pewne obserwacje lub eksperymenty.\n",
"\n",
"W naszym przykładzie prawdopodobieństwo a posteriori obliczymy ze wzoru \n",
"\n",
"$(prawdopodobieństwo\\ a\\ priori\\ przynależności\\ do\\ danej\\ klasy) * (prawdopodobieństwo\\ że\\ nowy\\ obiekt\\ będzie\\ w\\ danej\\ klasie\\ na\\ podstawie\\ jego\\ sąsiadów)$ czyli $P(X|K) P(K)$\n",
"\n",
"A więc prawdopodobieństwa a posteriori są równe:\n",
"\n",
"Prawdopodobieństwo a posteriori ze nowy obiekt będzie w klasie zielonej = $\\frac{2}{3} * \\frac{1}{40} = \\frac{1}{60}\\\\$\n",
"Prawdopodobieństwo a posteriori ze nowy obiekt będzie w klasie czerwonej = $\\frac{1}{3} * \\frac{3}{40} = \\frac{1}{40}\\\\$\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Naiwna klasyfikacja bayesowska przy wielu cechach\n",
"
\n",
"Klasyfikator bayesowski możemy stosować na wielu cechach, wykonuje się to w następujący sposób:\n",
"\n",
"W naszym przypadku obiekt X posiada wiele cech $X = (x1, x2, x3, ... xn)$ wtedy stosując znany już wzór:\n",
"\n",
"$P(K|X) = \\frac{P(X|K) P(K)}{P(X)}$ \n",
"\n",
"$P(K)$ pozostaje niezmienne jest to $\\frac{liczba\\ elementów\\ klasy\\ K}{wszystkie\\ elementy\\ zbioru}$\n",
"\n",
"Element X posiada wiele cech więc:\n",
"\n",
"$P(X|K) = P(x1|K)*P(x2|K)*...*P(xn|K)$\n",
"\n",
"Zauażmy że w rzeczywistości $P(X|K)$ obliczymy z twierdzenia bayesa, jednak dzięki temu że nasz klasyfikator jest naiwny zakładamy niezależność cech $x1, x2,...,xn$ więc możemy uprościć obliczenia do powyższego wzoru\n",
"
\n",
"$P(xk|K) = \\frac{Liczba\\ elementów\\ klasy\\ K\\ dla\\ których\\ wartość\\ cechy\\ Ak\\ jest\\ równa\\ xk}{liczba\\ wszystkich\\ obiektów\\ klasy\\ K}$\n",
"
\n",
"Powyższy wzór ma jedna pewne problemy z zerowymi prawdopodobieństwami\n",
"
\n",
"Co w przypadku gdy dla którejś cechy $P(xk|K) = 0$\n",
"\n",
"Może się tak zdarzyć gdy cecha $Ak$ nie będzie przyjmowała wartości $xk$ danego obiektu $X$ wtedy zgodnie ze wzorem otrzymamy $\\frac{0}{liczba\\ wszystkich\\ obiektów\\ klasy\\ K}$\n",
"Gdy tak się stanie obliczone prawdopodobieństwo będzie równe 0, a przecież brak wartości xk dla cechy Ak klasy K nie musi wcale oznaczać śe dany obiekt nie może należeć do klasy K. Aby temu zaradzić stosuje się wygładzanie\n",
"
\n",
"Wygładzanie Laplace'a\n",
"
\n",
"Wygładzanie Laplace'a zwane jest również wygładzaniem + 1, jest to bardzo prosty sposób wystarczy dla każdego $P(xk|K)$ dodać do licznika 1 a do mianownika dodać liczbę klas obiektu, dodając 1 do licznika możemy to interpretować jako dodanie nowego obiektu do klasy, robiąc to dla każdej klasy \"mamy\" dodatkowe obiekty równe liczbie klas dlatego do mianownika musimy dodać liczbę klas.\n",
"\n",
"$P(xk|K) = \\frac{(Liczba\\ elementów\\ klasy\\ K\\ dla\\ których\\ wartość\\ cechy\\ Ak\\ jest\\ równa\\ xk\\\\)\\ + 1}{(liczba\\ wszystkich\\ obiektów\\ klasy\\ K\\\\) + liczba klas}$\n",
"\n",
"Łatwo można zauważyć że samo dodanie 1 do licznika nie jest wystarczające ponieważ wtedy $P(xk|K)$ mogłoby być $> 1$\n",
"
\n",
"Dzięki wygładzaniu Laplace'a $P(X|K)$ nigdy nie bęzie zerowe minimalna wartość to\n",
"\n",
"$\\frac{1}{n *(liczba\\ wszystkich\\ obiektów\\ klasy\\ K\\\\) + liczba\\ klas}$\n",
"
\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Implementacja\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"import pandas as pd\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n",
"import scipy.stats as stats\n",
"import numpy as np\n",
"import plotly\n",
"from plotly.subplots import make_subplots\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import random"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"class NaiveBayes():\n",
"\n",
" def __init__(self, classes, className, attribsNames, data):\n",
" self.classes = classes\n",
" self.className = className\n",
" self.attribsNames = attribsNames\n",
" self.data = data\n",
"\n",
" #przygotowanie prawdopodobienstw wartosci danych cech w zaleznosci od klasy\n",
" def getDictOfAttribProbs(self):\n",
" dictionaries = {}\n",
" for value in self.classes:\n",
" classFreq = {}\n",
" for i in range(len(self.attribsNames)):\n",
" classData = self.data[self.data[self.className] == value]\n",
" freq = {}\n",
" attribData = classData[self.attribsNames[i]]\n",
" for attrib in attribData:\n",
" count = freq.get(attrib, 1) + 1\n",
" freq[attrib] = count\n",
" freq = {k: v / len(classData) for k, v in freq.items()}\n",
" classFreq[self.attribsNames[i]] = freq\n",
" dictionaries[value] = classFreq\n",
" return dictionaries\n",
"\n",
" #a priori dla klas\n",
" def classProb(self, class_):\n",
" x = len(self.data[self.data[self.className] == class_][self.className])\n",
" y = len(self.data[self.className])\n",
" return x / y\n",
"\n",
" #prawdopodobienstwo dla wartosic danej cechy w zaelznosci od klasy\n",
" def getAttribProbs(self, attrib, value, data, clas, dictProbs):\n",
" return dictProbs[clas][attrib].get(value, 1.0 / len(data))\n",
"\n",
" #a posteriori dla danego obiektu\n",
" def getPosteriori(self, attribs, attribsNames, clas, dictProbs):\n",
" dic = {}\n",
" for i in range(len(attribs)):\n",
" dic[attribsNames[i]] = attribs[i]\n",
" sum = 0.0\n",
" for key in dic:\n",
" sum = sum + np.log(self.getAttribProbs(key, dic[key], X_train, clas, dictProbs))\n",
" return sum + np.log(self.classProb(clas))\n",
"\n",
" #predykcja dla danych\n",
" def predict(self, data, model):\n",
" attribNames = data.columns\n",
" predictions = []\n",
" for i in range(len(data)):\n",
" probs = {}\n",
" for name in self.classes:\n",
" probs[name] = self.getPosteriori(list(data.iloc[i]), list(attribNames), name, model)\n",
" keyMax = max(zip(probs.values(), probs.keys()))[1]\n",
" predictions.append(keyMax)\n",
" return predictions\n",
"\n",
" def fitModel(self):\n",
" probabilities = self.getDictOfAttribProbs()\n",
" return probabilities\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"features = [\n",
" 'edible', 'cap-shape', 'cap-surface', 'cap-color', 'bruises', 'odor',\n",
" 'gill-attachment', 'gill-spacing', 'gill-size', 'gill-color',\n",
" 'stalk-shape', 'stalk-root', 'stalk-surface-above-ring',\n",
" 'stalk-surface-below-ring', 'stalk-color-above-ring',\n",
" 'stalk-color-below-ring', 'veil-type', 'veil-color', 'ring-number',\n",
" 'ring-type', 'spore-print-color', 'population', 'habitat'\n",
"]\n",
"\n",
"mushrooms = pd.read_csv('mushrooms.tsv', sep='\\t', names=features)\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/html": [
"
\n", " | edible | \n", "cap-shape | \n", "cap-surface | \n", "cap-color | \n", "bruises | \n", "odor | \n", "gill-attachment | \n", "gill-spacing | \n", "gill-size | \n", "gill-color | \n", "... | \n", "stalk-surface-below-ring | \n", "stalk-color-above-ring | \n", "stalk-color-below-ring | \n", "veil-type | \n", "veil-color | \n", "ring-number | \n", "ring-type | \n", "spore-print-color | \n", "population | \n", "habitat | \n", "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", "edible | \n", "bell | \n", "smooth | \n", "white | \n", "bruises | \n", "anise | \n", "free | \n", "close | \n", "broad | \n", "brown | \n", "... | \n", "smooth | \n", "white | \n", "white | \n", "partial | \n", "white | \n", "one | \n", "pendant | \n", "brown | \n", "numerous | \n", "meadows | \n", "
1 | \n", "poisonous | \n", "convex | \n", "scaly | \n", "brown | \n", "bruises | \n", "pungent | \n", "free | \n", "close | \n", "narrow | \n", "brown | \n", "... | \n", "smooth | \n", "white | \n", "white | \n", "partial | \n", "white | \n", "one | \n", "pendant | \n", "brown | \n", "several | \n", "grasses | \n", "
2 | \n", "edible | \n", "bell | \n", "scaly | \n", "white | \n", "bruises | \n", "almond | \n", "free | \n", "close | \n", "broad | \n", "white | \n", "... | \n", "smooth | \n", "white | \n", "white | \n", "partial | \n", "white | \n", "one | \n", "pendant | \n", "brown | \n", "numerous | \n", "meadows | \n", "
3 | \n", "edible | \n", "bell | \n", "smooth | \n", "white | \n", "bruises | \n", "anise | \n", "free | \n", "close | \n", "broad | \n", "gray | \n", "... | \n", "smooth | \n", "white | \n", "white | \n", "partial | \n", "white | \n", "one | \n", "pendant | \n", "black | \n", "scattered | \n", "meadows | \n", "
4 | \n", "edible | \n", "convex | \n", "scaly | \n", "yellow | \n", "bruises | \n", "anise | \n", "free | \n", "close | \n", "broad | \n", "brown | \n", "... | \n", "smooth | \n", "white | \n", "white | \n", "partial | \n", "white | \n", "one | \n", "pendant | \n", "brown | \n", "numerous | \n", "meadows | \n", "
5 rows × 23 columns
\n", "