Autor notebooka: Jakub Nowacki.
Zadaniem klasyfikacji w uczeniu maszynowym jest przewidzenie dyskretnych klas na podstawie podanych cech. Do zilustrowania działania klasyfikacji wykorzystamy klasyczny zbiór parametrów irysów. Poniżej wczytujemy dane z dostępnych bibliotek.
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import linear_model, neighbors, svm, tree, datasets
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import roc_curve, roc_auc_score, classification_report
%matplotlib inline
plt.rcParams['figure.figsize'] = (10, 8)
iris_ds = datasets.load_iris()
print(iris_ds.DESCR)
Użyjemy funkcji train_test_split
do podziału zbioru na treningowy i testowy.
In [2]:
import pandas as pd
iris = pd.DataFrame(iris_ds.data, columns=iris_ds.feature_names).assign(target=iris_ds.target)
iris.columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'target']
In [3]:
iris_train, iris_test = train_test_split(iris, test_size=0.2)
In [4]:
iris_train.head()
Out[4]:
In [5]:
iris_test.head()
Out[5]:
In [6]:
iris.size, iris_train.size, iris_test.size
Out[6]:
In [7]:
iris_ds.target_names
Out[7]:
Jednym z najprostszych modeli klasyfikacji jest regresja logistyczna.
Model ten wykonuje klasyfikację z użyciem funkcji sigmoidalnej do przewidzenia jednej z dwóch klas. W przypadku wielu klas wykorzystuje się wiele funkcji sigmoidalnych.
In [8]:
features = ['petal_width', 'petal_length']
# ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'target']
logreg = linear_model.LogisticRegression(C=1e5, multi_class='multinomial', solver='lbfgs')
logreg.fit(iris_train[features], iris_train['target'])
print(classification_report(iris_test['target'], logreg.predict(iris_test[features])))
In [9]:
linear_model.LogisticRegression?
In [10]:
def plot_decision_area(df, features, model, target='target'):
if len(features) > 2:
raise ValueError('Too many features, works only with 2')
h = .02 # step size in the mesh
# Plot the decision boundary. For that, we will assign a color to each
# point in the mesh [x_min, x_max]x[y_min, y_max].
x_min, y_min = tuple(df[features].min() - .5)
x_max, y_max = tuple(df[features].max() + .5)
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
# Put the result into a color plot
Z = Z.reshape(xx.shape)
plt.pcolormesh(xx, yy, Z, cmap=plt.cm.Paired)
# Plot also the training points
plt.scatter(df[features[0]], df[features[1]], c=df[target], edgecolors='k', cmap=plt.cm.Paired)
plt.xlabel(features[0])
plt.ylabel(features[1])
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.xticks(())
plt.yticks(())
plot_decision_area(iris, features, logreg)
multi_class
i solver
; zobacz dokumentacje, żeby poznać dostępne opcję; co się zmieniło?
In [11]:
features = ['sepal_length', 'sepal_width']
svc = svm.LinearSVC()
svc.fit(iris_train[features], iris_train['target'])
print(classification_report(iris_test['target'], svc.predict(iris_test[features])))
In [12]:
plot_decision_area(iris, features, svc)
multi_class
; zobacz dokumentacje żeby poznać dostępne opcję; co się zmieniło?loss
; co się zmieniło?
In [13]:
features = ['sepal_length', 'sepal_width']
# svc = svm.LinearSVC(multi_class='ovr')
svc = svm.SVC(kernel='rbf', C=1)
svc.fit(iris_train[features], iris_train['target'])
#print(classification_report(iris_test['target'], svc.predict(iris_test[features])))
plot_decision_area(iris, features, svc)
K-Nearest Neighbors to bardzo prosty algorytm klasyfikujący. Nie ma on funkcji klasyfikacyjnej jako takie, ale zapisuje zbiór uczący i podejmuje decyzję o klasie nowego elementu na podstawie k wybranych najbliższych elementów; zobacz poniższą ilustrację.
Dla k=3 nowy element (trójkąt) będzie sklasyfikowany jako czerwony, ale już dla k=5, będzie on sklasyfikowany jako niebieski.
Poniżej przedstawiamy działanie algorytmu na przykładowym zbiorze danych.
In [17]:
features = ['sepal_length', 'sepal_width']
def my_function(*args):
print(args)
knn = neighbors.KNeighborsClassifier(n_neighbors=3, weights=my_function)
knn.fit(iris_train[features], iris_train['target'])
print(classification_report(iris_test['target'], knn.predict(iris_test[features])))
plot_decision_area(iris, features, knn)
In [ ]:
In [ ]:
In [15]:
In [18]:
plot_decision_area(iris, features, knn)
weights
; zobacz dokumentację; co się zmieniło?n_neighbors
; co się zmieniło?Kolejnym typem klasifikatorów są drzewa decyzyjne. Takie klasyfikatory składają się z drzewa, który nauczony jest podejmować decyzje w zależności od wartości parametrów.
Zastosujmy drzewo decyzyjne do naszego zbioru danych.
In [19]:
features = ['sepal_length', 'sepal_width']
dtc = tree.DecisionTreeClassifier(criterion='entropy', splitter='best') # random
dtc.fit(iris_train[features], iris_train['target'])
print(classification_report(iris_test['target'], dtc.predict(iris_test[features])))
In [20]:
dtc = tree.DecisionTreeClassifier
In [21]:
plot_decision_area(iris, features, dtc)
In [ ]:
import graphviz
dot_data = tree.export_graphviz(dtc, out_file=None,
feature_names=features,
class_names=iris_ds.target_names,
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data, )
graph
criterion
; zobacz dokumentację; co się zmieniło?splitter
; co się zmieniło?Wiele powyższych modeli ma wiele parametrów, które mogą wpłynąć na jakość klasyfikacji. Dotychczas skupialiśmy się na zmianach algorytmów lub cech. Spróbujmy teraz znaleźć najlepszy model. Wykorzystamy do tego celu funkcję GridSearchCV
.
Najpierw definiujemy przestrzeń parametrów do przeszukania.
In [39]:
param_grid = [
{'C': range(1, 1000, 1), 'kernel': ['linear']},
{'C': [1, 10, 100, 1000], 'gamma': [0.001, 0.0001], 'kernel': ['rbf']},
]
Następnie uczymy model podobnie jak poprzednio ale z użyciem GridSearchCV
.
In [40]:
features = ['sepal_length', 'sepal_width']
svc = GridSearchCV(svm.SVC(probability=True), param_grid, return_train_score=True)
svc.fit(iris_train[features], iris_train['target'])
print(classification_report(iris_test['target'], svc.predict(iris_test[features])))
In [41]:
plot_decision_area(iris, features, svc)
In [43]:
svc.best_estimator_
Out[43]:
In [44]:
svc.best_params_
Out[44]:
In [45]:
svc.cv_results_
Out[45]:
In [48]:
y_true = iris_test['target']
y_pred = svc.predict(iris_test[features])
print(classification_report(y_true, y_pred))
Funkcja ta prezentuje prezentuje precyzję i dokładność (precision and recall) w wersji wieloklasowej. Możemy też policzyć te parametry osobno jako średnie:
In [49]:
from sklearn.metrics import precision_score, recall_score, f1_score
avg = 'macro'
print('Precision: {:.4f}'.format(precision_score(y_true, y_pred, average=avg)))
print('Recall: {:.4f}'.format(recall_score(y_true, y_pred, average=avg)))
print('F1: {:.4f}'.format(f1_score(y_true, y_pred, average=avg)))
Lub dla każdej klasy jak w raporcie:
In [50]:
from sklearn.metrics import precision_recall_fscore_support
precision, recall, f1, support = precision_recall_fscore_support(y_true, y_pred)
precision, recall, f1, support
Out[50]:
Parametry powyższe liczone są na macierzy pomyłek (confusion matrix), w tym przypadku w wersji wieloklasowej. Możemy otrzymać numeryczną wersję tej macierzy używając funkcji confusion_matrix
:
In [51]:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_true, y_pred)
cm
Out[51]:
W formie graficznej:
In [52]:
import itertools
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
plot_confusion_matrix(cm, iris_ds.target_names)
Są też inne rodzaje metryk o których można poczytać w dokumentacji. Przykładowo, jest też prosta [miara Jaccarda], która opiera się na podobieństwie zbiorów:
In [ ]:
from sklearn.metrics import jaccard_similarity_score
jaccard_similarity_score(y_true, y_pred)
Inną popularną metryką jest krzywa ROC (Receiver operating characteristic). Krzywa ta mówi o jakości klasyfikacji jako poziom rozdzielenia dwóch klas od siebie.
Jako wartość metryki stosuje się powierzchnię pod krzywą (Area Under the Curve, AUC).
Wprawdzie metrykę stosuje się dla klasyfikacji binarnej, można ją policzyć dla każdej klasy i uśrednić, jak pokazano poniżej; wieloklasowy przykład na podstawie dokumentacji.
In [53]:
import numpy as np
import matplotlib.pyplot as plt
from itertools import cycle
from sklearn import svm, datasets
from sklearn.metrics import roc_curve, auc
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
from scipy import interp
# Import some data to play with
iris = datasets.load_iris()
X = iris.data
y = iris.target
# Binarize the output
y = label_binarize(y, classes=[0, 1, 2])
n_classes = y.shape[1]
# Add noisy features to make the problem harder
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
X = np.c_[X, random_state.randn(n_samples, 200 * n_features)]
# shuffle and split training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5,
random_state=0)
# Learn to predict each class against the other
classifier = OneVsRestClassifier(svm.SVC(kernel='linear', probability=True,
random_state=random_state))
y_score = classifier.fit(X_train, y_train).decision_function(X_test)
# Compute ROC curve and ROC area for each class
fpr = dict()
tpr = dict()
roc_auc = dict()
for i in range(n_classes):
fpr[i], tpr[i], _ = roc_curve(y_test[:, i], y_score[:, i])
roc_auc[i] = auc(fpr[i], tpr[i])
# Compute micro-average ROC curve and ROC area
fpr["micro"], tpr["micro"], _ = roc_curve(y_test.ravel(), y_score.ravel())
roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])
# Compute macro-average ROC curve and ROC area
# First aggregate all false positive rates
all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)]))
# Then interpolate all ROC curves at this points
mean_tpr = np.zeros_like(all_fpr)
for i in range(n_classes):
mean_tpr += interp(all_fpr, fpr[i], tpr[i])
# Finally average it and compute AUC
mean_tpr /= n_classes
fpr["macro"] = all_fpr
tpr["macro"] = mean_tpr
roc_auc["macro"] = auc(fpr["macro"], tpr["macro"])
# Plot all ROC curves
plt.figure()
plt.plot(fpr["micro"], tpr["micro"],
label='micro-average ROC curve (area = {0:0.2f})'
''.format(roc_auc["micro"]),
color='deeppink', linestyle=':', linewidth=4)
plt.plot(fpr["macro"], tpr["macro"],
label='macro-average ROC curve (area = {0:0.2f})'
''.format(roc_auc["macro"]),
color='navy', linestyle=':', linewidth=4)
colors = cycle(['aqua', 'darkorange', 'cornflowerblue'])
for i, color in zip(range(n_classes), colors):
plt.plot(fpr[i], tpr[i], color=color, lw=4,
label='ROC curve of class {0} (area = {1:0.2f})'
''.format(i, roc_auc[i]))
plt.plot([0, 1], [0, 1], 'k--', lw=4)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Some extension of Receiver operating characteristic to multi-class')
plt.legend(loc="lower right")
plt.show()
In [54]:
import graphviz
import pandas as pd
from sklearn import linear_model, neighbors, svm, tree, datasets
from sklearn.model_selection import train_test_split
iris_ds = datasets.load_iris()
iris = pd.DataFrame(iris_ds.data, columns=iris_ds.feature_names).assign(target=iris_ds.target)
iris.columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'target']
iris_train, iris_test = train_test_split(iris, test_size=0.2)
features = ['sepal_length', 'sepal_width']
dtc = tree.DecisionTreeClassifier()
dtc.fit(iris_train[features], iris_train['target'])
dot_data = tree.export_graphviz(dtc,
out_file=None,
feature_names=features,
class_names=iris_ds.target_names,
filled=True,
rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data, )
graph
Out[54]:
In [ ]: