62 KiB
Klasyfikacja w Pythonie
zad. 1 Które z poniższych problemów jest problemem regresji, a które klasyfikacji?
Sprawdzenie, czy wiadomość jest spamem.
Przewidzenie oceny (od 1 do 5 gwiazdek) na podstawie komentarza.
OCR cyfr: rozpoznanie cyfry z obrazka.
Jeżeli problem jest klasyfikacyjny, to jakie mamy klasy?
- klasyfikacja
- można traktować jako klasyfikację lub regresję. Jeżeli jako regresję to należy sprowadzić liczbę rzeczywistą do jednej z {1,2,3,4,5}
- klasyfikacja
Miary dla klasyfikacji
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: $$ACC = \frac{k}{N}$$
gdzie:
- $k$ to liczba poprawnie zaklasyfikowanych przypadków,
- $N$ liczebność zbioru testującego.
zadanie Napisz funkcję, która jako parametry przyjmnie dwie listy (lista poprawnych klas i wyjście z klasyfikatora) i zwróci trafność.
def accuracy_measure(true, predicted):
return sum([1 if t==p else 0 for t,p in zip(true, predicted)]) / len(true)
true_label = [1, 1, 1, 0, 0]
predicted = [0, 1, 0, 1, 0]
print("ACC:", accuracy_measure(true_label, predicted))
ACC: 0.4
Klasyfikator $k$ najbliższych sąsiadów _(ang. k-nearest neighbors, KNN)
Klasyfikator KNN, 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.
** 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$?
( Grafika pochodzi z https://pl.wikipedia.org/wiki/K_najbli%C5%BCszych_s%C4%85siad%C3%B3w )
Herbal Iris
_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.
zad. 2 Wczytaj do zmiennej data
zbiór _Herbal Iris, który znajduje się w pliku iris.data
. Jest to plik csv.
Kolumny są następujące:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris Setosa
- Iris Versicolour
- Iris Virginica
!head iris.data
5.1,3.5,1.4,0.2,Iris-setosa 4.9,3.0,1.4,0.2,Iris-setosa 4.7,3.2,1.3,0.2,Iris-setosa 4.6,3.1,1.5,0.2,Iris-setosa 5.0,3.6,1.4,0.2,Iris-setosa 5.4,3.9,1.7,0.4,Iris-setosa 4.6,3.4,1.4,0.3,Iris-setosa 5.0,3.4,1.5,0.2,Iris-setosa 4.4,2.9,1.4,0.2,Iris-setosa 4.9,3.1,1.5,0.1,Iris-setosa
import pandas as pd
data = pd.read_csv('iris.data', names=('sepal_length', 'sepal_width', 'petal_length', 'petal_width','class'),index_col=False)
data
sepal_length | sepal_width | petal_length | petal_width | class | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
1 | 4.9 | 3.0 | 1.4 | 0.2 | Iris-setosa |
2 | 4.7 | 3.2 | 1.3 | 0.2 | Iris-setosa |
3 | 4.6 | 3.1 | 1.5 | 0.2 | Iris-setosa |
4 | 5.0 | 3.6 | 1.4 | 0.2 | Iris-setosa |
... | ... | ... | ... | ... | ... |
145 | 6.7 | 3.0 | 5.2 | 2.3 | Iris-virginica |
146 | 6.3 | 2.5 | 5.0 | 1.9 | Iris-virginica |
147 | 6.5 | 3.0 | 5.2 | 2.0 | Iris-virginica |
148 | 6.2 | 3.4 | 5.4 | 2.3 | Iris-virginica |
149 | 5.9 | 3.0 | 5.1 | 1.8 | Iris-virginica |
150 rows × 5 columns
zad. 3 Odpowiedz na poniższe pytania:
- Które atrybuty są wejściowe, a w której kolumnie znajduje się klasa wyjściowa?
- Ile jest różnych klas? Wypisz je ekran.
- Jaka jest średnia wartość w kolumnie
sepal_length
? Jak zachowuje się średnia, jeżeli policzymy ją dla każdej z klas osobno?
- wejściowe są sepal_length, sepal_width, petal_length, petal_width. Klasa wyjściowa to class
data['class'].unique()
# 3 klasy
array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)
data['sepal_length'].mean()
5.843333333333334
data.groupby('class')['sepal_length'].mean()
class Iris-setosa 5.006 Iris-versicolor 5.936 Iris-virginica 6.588 Name: sepal_length, dtype: float64
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
.
from sklearn.model_selection import train_test_split
X = data.loc[:, 'sepal_length':'petal_width']
Y = data['class']
(train_X, test_X, train_Y, test_Y) = train_test_split(X, Y, test_size=0.33, random_state=42)
Trenowanie klasyfikatora wygląda bardzo podobnie do treningi modelu regresji liniowej:
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=3)
model.fit(train_X, train_Y)
KNeighborsClassifier(n_neighbors=3)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier(n_neighbors=3)
Mając wytrenowany model możemy wykorzystać go do predykcji na zbiorze testowym.
predicted = model.predict(test_X)
for i in range(10):
print("Zaklasyfikowane: {}, Orginalne: {}".format(predicted[i], test_Y.reset_index()['class'][i]))
Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor Zaklasyfikowane: Iris-setosa, Orginalne: Iris-setosa Zaklasyfikowane: Iris-virginica, Orginalne: Iris-virginica Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor Zaklasyfikowane: Iris-setosa, Orginalne: Iris-setosa Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor Zaklasyfikowane: Iris-virginica, Orginalne: Iris-virginica Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor Zaklasyfikowane: Iris-versicolor, Orginalne: Iris-versicolor
Możemy obliczyć _accuracy:
from sklearn.metrics import accuracy_score
print(accuracy_score(test_Y, predicted))
0.98
zad. 4 Wytrenuj nowy model model_2
zmieniając liczbę sąsiadów na 20. Czy zmieniły się wyniki?
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=10)
model.fit(train_X, train_Y)
predicted = model.predict(test_X)
print(accuracy_score(test_Y, predicted))
0.98
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?
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=1)
model.fit(train_X, train_Y)
predicted = model.predict(train_X)
print(accuracy_score(train_Y, predicted))
1.0
Walidacja krzyżowa
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. Algorytm wygląda następująco:
Podziel zbiór danych na $n$ części (losowo).
Dla każdego i od 1 do $n$ wykonaj:
Weź $i$-tą część jako zbiór testowy, pozostałe dane jako zbiór trenujący.
Wytrenuj model na zbiorze trenującym.
Uruchom model na danych testowych i zapisz wyniki.
Ostateczne wyniki to średnia z $n$ wyników częściowych.
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ć parametrcv
, który określa na ile części mamy podzielić zbiór danych oraz parametrscoring
określający miarę.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).
from sklearn.model_selection import cross_val_score
k=10
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X, Y, cv=10, scoring='accuracy')
print("Wynik walidacji krzyżowej:", scores.mean())
Wynik walidacji krzyżowej: 0.9666666666666668
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:
- Stwórz listę
neighbors
wszystkich liczb nieparzystych od 1 do 50. - Dla każdego elementu
i
z listyneighbors
zbuduj klasyfikator _KNN o liczbie sąsiadów równeji
. Nastepnie przeprowadz walidację krzyżową (parametry takie same jak powyżej) i zapisz wyniki do tablicycv_scores
. - Znajdź
k
, dla którego klasyfikator osiąga najwyższy wynik.
neighbors = list(range(1,50,2))
cv_scores = list()
max_score = -1
for neighbor_num in neighbors:
knn = KNeighborsClassifier(n_neighbors=neighbor_num)
score = cross_val_score(knn, X, Y, cv=10, scoring='accuracy').mean()
max_score = score if score > max_score else max_score
neighbor_num_best = neighbor_num if score == max_score else neighbor_num_best
cv_scores.append(score)
print(neighbor_num_best)
print(max_score)
13 0.9800000000000001
Wykres przedstawiający precent błedów w zależnosci od liczby sąsiadów.
import matplotlib.pyplot as plt
# changing to misclassification error
MSE = [1 - x for x in cv_scores]
# plot misclassification error vs k
plt.plot(neighbors, MSE)
plt.xlabel('Liczba sąsiadów')
plt.ylabel('Procent błędów')
plt.show()
TF IDF Vectorizer
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. Dane tekstowe musimy również przekształcić do wektorów. Przydatny w tym przypadku jest TF IDF Vectorizer. Oto przyład z dokumentacji jak można z niego skorzystać (https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html)
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'This is the first document.',
'This document is the second document.',
'And this is the third one.',
'Is this the first document?',
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
vectorizer.get_feature_names_out()
print(X.shape)
(4, 9)
vectorizer.get_feature_names_out()
array(['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this'], dtype=object)
X
<4x9 sparse matrix of type '<class 'numpy.float64'>' with 21 stored elements in Compressed Sparse Row format>
X.todense()
matrix([[0. , 0.46979139, 0.58028582, 0.38408524, 0. , 0. , 0.38408524, 0. , 0.38408524], [0. , 0.6876236 , 0. , 0.28108867, 0. , 0.53864762, 0.28108867, 0. , 0.28108867], [0.51184851, 0. , 0. , 0.26710379, 0.51184851, 0. , 0.26710379, 0.51184851, 0.26710379], [0. , 0.46979139, 0.58028582, 0.38408524, 0. , 0. , 0.38408524, 0. , 0.38408524]])
Na podstawie tych danych możemy wytrenowąc model regresji logistycznej. Jest to model regresji liniowej z dodatkową nałożoną funkcją logistyczną: ( https://en.wikipedia.org/wiki/Logistic_function )
Dzięki wyjściu modelu zawsze pomiędzy 0, a 1 można traktować wynik jako prawdopodobieństwo
from sklearn.linear_model import LogisticRegression
y = [0,0,1,1]
model = LogisticRegression()
model.fit(X, y)
model.predict(X)
array([0, 0, 1, 0])
model.predict_proba(X)
array([[0.51514316, 0.48485684], [0.56428483, 0.43571517], [0.40543928, 0.59456072], [0.51514316, 0.48485684]])
Sieci neuronowe
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
Standard Scaler
Zadanie 7
Sprawdź dokumentację https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html
KNN jest wrażliwy na liniowe skalowanie danych (w przeciwieństwie do modeli bazujących na regresji, gdyż współczynniki liniowe rekompensują skalowanie liniowe).
Wytrenuj dowolny model KNN na cechach pozyskanych ze StandardScaler. Pamiętaj, żeby wyskalować zarówno dane ze zbioru test jak i train.
Zauważ, że scaler ma podobne API (fit, transform) jak TF IDF Vectorier