2024-programowanie-w-python.../zajecia5/sklearn cz. 2.ipynb
2024-12-08 13:26:08 +01:00

487 lines
14 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Klasyfikacja w Pythonie"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 1** Które z poniższych problemów jest problemem regresji, a które klasyfikacji?\n",
" 1. Sprawdzenie, czy wiadomość jest spamem.\n",
" 1. Przewidzenie oceny (od 1 do 5 gwiazdek) na podstawie komentarza.\n",
" 1. OCR cyfr: rozpoznanie cyfry z obrazka.\n",
" \n",
" Jeżeli problem jest klasyfikacyjny, to jakie mamy klasy?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Miary dla klasyfikacji"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Istnieje wieje miar (metryk), na podstawie których możemy ocenić jakość modelu. Podobnie jak w przypadku regresji liniowej potrzebne są dwie listy: lista poprawnych klas i lista predykcji z modelu. Najpopularniejszą z metryk jest trafność, którą definiuje się w następujący sposób:\n",
" $$ACC = \\frac{k}{N}$$ \n",
" \n",
" gdzie: \n",
" * $k$ to liczba poprawnie zaklasyfikowanych przypadków,\n",
" * $N$ liczebność zbioru testującego."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zadanie** Napisz funkcję, która jako parametry przyjmnie dwie listy (lista poprawnych klas i wyjście z klasyfikatora) i zwróci trafność."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def accuracy_measure(true, predicted):\n",
" pass\n",
"\n",
"true_label = [1, 1, 1, 0, 0]\n",
"predicted = [0, 1, 0, 1, 0]\n",
"print(\"ACC:\", accuracy_measure(true_label, predicted))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Klasyfikator $k$ najbliższych sąsiadów *(ang. k-nearest neighbors, KNN)*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Klasyfikator [KNN](https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm), który został wprowadzony na ostatnim wykładzie, jest bardzo intuicyjny. Pomysł, który stoi za tym klasyfikatorem jest bardzo prosty: Jeżeli mamy nowy obiekt do zaklasyfikowania, to szukamy wśród danych trenujących $k$ najbardziej podobnych do niego przykładów i na ich podstawie decydujemy (np. biorąc większość) do jakie klasy powinien należeć dany obiekt."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"** Przykład 1** Mamy za zadanie przydzielenie obiektów do dwóch klas: trójkątów lub kwadratów. Rozpatrywany obiekt jest zaznaczony zielonym kółkiem. Przyjmując $k=3$, mamy wśród sąsiadów 2 trójkąty i 1 kwadrat. Stąd obiekt powinienm zostać zaklasyfikowany jako trójkąt. Jak zmienia się sytuacja, gdy przyjmiemy $k=5$?\n",
"\n",
"![Przykład 1](./KnnClassification.svg.png)\n",
"\n",
"( Grafika pochodzi z https://pl.wikipedia.org/wiki/K_najbli%C5%BCszych_s%C4%85siad%C3%B3w )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Herbal Iris"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Herbal Iris* jest klasycznym zbiorem danych w uczeniu maszynowym, który powstał w 1936 roku. Zawiera on informacje na 150 egzemplarzy roślin, które należą do jednej z 3 odmian."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 2** Wczytaj do zmiennej ``data`` zbiór *Herbal Iris*, który znajduje się w pliku ``iris.data``. Jest to plik csv.\n",
"\n",
"Kolumny są następujące:\n",
"\n",
"1. sepal length in cm\n",
"2. sepal width in cm\n",
"3. petal length in cm\n",
"4. petal width in cm\n",
"5. class: \n",
" * Iris Setosa\n",
" * Iris Versicolour\n",
" * Iris Virginica"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 3** Odpowiedz na poniższe pytania:\n",
" 1. Które atrybuty są wejściowe, a w której kolumnie znajduje się klasa wyjściowa?\n",
" 1. Ile jest różnych klas? Wypisz je ekran.\n",
" 1. Jaka jest średnia wartość w kolumnie ``sepal_length``? Jak zachowuje się średnia, jeżeli policzymy ją dla każdej z klas osobno?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wytrenujmy klasyfikator *KNN*, ale najpierw przygotujmy dane. Fukcja ``train_test_split`` dzieli zadany zbiór danych na dwie części. My wykorzystamy ją do podziału na zbiór treningowy (66%) i testowy (33%), służy do tego parametr ``test_size``."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.model_selection import train_test_split\n",
"\n",
"X = data.loc[:, 'sepal_length':'petal_width']\n",
"Y = data['class']\n",
"\n",
"(train_X, test_X, train_Y, test_Y) = train_test_split(X, Y, test_size=0.33, random_state=42)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Trenowanie klasyfikatora wygląda bardzo podobnie do treningi modelu regresji liniowej:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.neighbors import KNeighborsClassifier\n",
"\n",
"model = KNeighborsClassifier(n_neighbors=3)\n",
"model.fit(train_X, train_Y)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mając wytrenowany model możemy wykorzystać go do predykcji na zbiorze testowym."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"predicted = model.predict(test_X)\n",
"\n",
"for i in range(10):\n",
" print(\"Zaklasyfikowane: {}, Orginalne: {}\".format(predicted[i], test_Y.reset_index()['class'][i]))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Możemy obliczyć *accuracy*:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.metrics import accuracy_score\n",
"\n",
"print(accuracy_score(test_Y, predicted))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 4** Wytrenuj nowy model ``model_2`` zmieniając liczbę sąsiadów na 20. Czy zmieniły się wyniki?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 5** Wytrenuj model z $k=1$. Przeprowadź walidację na zbiorze trenującym zamiast na zbiorze testowym? Jakie wyniki otrzymałeś? Czy jest to wyjątek? Dlaczego tak się dzieje?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Walidacja krzyżowa"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zbiór *herbal Iris* jest bardzo małym zbiorem. Wydzielenie z niego zbioru testowego jest obciążone dużą wariancją wyników, tj. w zależności od sposoby wyboru zbioru testowego wyniki mogą się bardzo różnic. Żeby temu zaradzić, stosuje się algorytm [walidacji krzyżowej](https://en.wikipedia.org/wiki/Cross-validation_(statistics). Algorytm wygląda następująco:\n",
" 1. Podziel zbiór danych na $n$ części (losowo).\n",
" 1. Dla każdego i od 1 do $n$ wykonaj:\n",
" 1. Weź $i$-tą część jako zbiór testowy, pozostałe dane jako zbiór trenujący.\n",
" 1. Wytrenuj model na zbiorze trenującym.\n",
" 1. Uruchom model na danych testowych i zapisz wyniki.\n",
" 1. Ostateczne wyniki to średnia z $n$ wyników częściowych. \n",
" \n",
" W Pythonie służy do tego funkcja ``cross_val_score``, która przyjmuje jako parametry (kolejno) model, zbiór X, zbiór Y. Możemy ustawić parametr ``cv``, który określa na ile części mamy podzielić zbiór danych oraz parametr ``scoring`` określający miarę.\n",
" \n",
" W poniższym przykładzie dzielimy zbiór danych na 10 części (10-krotna walidacja krzyżowa) i jako miarę ustawiany celność (ang. accuracy)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.model_selection import cross_val_score\n",
"\n",
"knn = KNeighborsClassifier(n_neighbors=k)\n",
"scores = cross_val_score(knn, X, Y, cv=10, scoring='accuracy')\n",
"print(\"Wynik walidacji krzyżowej:\", scores.mean())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**zad. 6** Klasyfikator $k$ najbliższych sąsiadów posiada jeden parametr: $k$, który określa liczbę sąsiadów podczas klasyfikacji. Jak widzieliśmy, wybór $k$ może mieć duże znaczenie dla jakości klasyfikatora. Wykonaj:\n",
" 1. Stwórz listę ``neighbors`` wszystkich liczb nieparzystych od 1 do 50.\n",
" 1. Dla każdego elementu ``i`` z listy ``neighbors`` zbuduj klasyfikator *KNN* o liczbie sąsiadów równej ``i``. Nastepnie przeprowadz walidację krzyżową (parametry takie same jak powyżej) i zapisz wyniki do tablicy ``cv_scores``.\n",
" 1. Znajdź ``k``, dla którego klasyfikator osiąga najwyższy wynik."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wykres przedstawiający precent błedów w zależnosci od liczby sąsiadów."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"# changing to misclassification error\n",
"MSE = [1 - x for x in cv_scores]\n",
"\n",
"# plot misclassification error vs k\n",
"plt.plot(neighbors, MSE)\n",
"plt.xlabel('Liczba sąsiadów')\n",
"plt.ylabel('Procent błędów')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## TF IDF Vectorizer\n",
"\n",
"Czasami, żeby wytrenować model nie da się zastosować bezpośrednio danego typu danych, ponieważ najczęściej wejściem do algorytmu ML jest wektor, macierz lub tensor.\n",
"Dane tekstowe musimy również przekształcić do wektorów. Przydatny w tym przypadku jest TF IDF Vectorizer.\n",
"Oto przyład z dokumentacji jak można z niego skorzystać (https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"corpus = [\n",
" 'This is the first document.',\n",
" 'This document is the second document.',\n",
" 'And this is the third one.',\n",
" 'Is this the first document?',\n",
"]\n",
"vectorizer = TfidfVectorizer()\n",
"X = vectorizer.fit_transform(corpus)\n",
"vectorizer.get_feature_names_out()\n",
"print(X.shape)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vectorizer.get_feature_names_out()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X.todense()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Na podstawie tych danych możemy wytrenowąc model regresji logistycznej. Jest to model regresji liniowej z dodatkową nałożoną funkcją logistyczną:\n",
" ( https://en.wikipedia.org/wiki/Logistic_function )\n",
" \n",
" \n",
"![Przykład 1](./logistic.png)\n",
"\n",
"\n",
"Dzięki wyjściu modelu zawsze pomiędzy 0, a 1 można traktować wynik jako prawdopodobieństwo\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.linear_model import LogisticRegression"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y = [0,0,1,1]\n",
"model = LogisticRegression()\n",
"model.fit(X, y)\n",
"model.predict(X)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.predict_proba(X)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sieci neuronowe\n",
"\n",
"Warto zauważyć, że sieci neuronowe w najprostszym wariancie to tak naprawdę złożenie wielu funkcji regresji logistycznej ze sobą, gdzie wejściem jednego modelu regresji logistycznej jest wyjście poprzedniej. W przypadku danych tekstowych zazwyczaj jest wybierana wtedy inna reprezentacja danych niż TF IDF, ponieważ TF IDF nie uwzględnia kolejności słów"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Standard Scaler\n",
"\n",
"**Zadanie 7**\n",
"\n",
"\n",
"Sprawdź dokumentację https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html\n",
"\n",
"KNN jest wrażliwy na liniowe skalowanie danych (w przeciwieństwie do modeli bazujących na regresji, gdyż współczynniki liniowe rekompensują skalowanie liniowe).\n",
"\n",
"Wytrenuj dowolny model KNN na cechach pozyskanych ze StandardScaler. Pamiętaj, żeby wyskalować zarówno dane ze zbioru test jak i train.\n",
"\n",
"\n",
"\n",
"Zauważ, że scaler ma podobne API (fit, transform) jak TF IDF Vectorier"
]
},
{
"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.11.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}