Skip to content
Snippets Groups Projects
Commit 7dae49ed authored by Soeren Sparmann's avatar Soeren Sparmann
Browse files

Bug fixed

parent b9f375e1
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags:
# Ziffernerkennung mit Hilfe einer Support Vector Machine (SVM)
%% Cell type:markdown id: tags:
## Einleitung
In diesem Notebook lernst du
+ wie man mit Hilfe eines Machine Learning Modells handschriftlich geschriebene Ziffern erkennen kann.
+ wie man einen Datensatz vorbereitet.
+ wie man die Effektivität eines Modells evaluieren kann
+ wie man einen Hyperparmater optimiert.
Das Notebook bildet exemplarisch den Prozess vom Einlesen der Daten bis zur Evaluation des Modells ab. Die Erläuterungen dienen in erster Linie dazu, den Prozess nachvollziehbar zu machen.
%% Cell type:markdown id: tags:
## Bibliotheken importieren
%% Cell type:code id: tags:
``` python
from sklearn.datasets import load_digits
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn import metrics
import matplotlib.pyplot as plt
import cv2
import numpy as np
from ipycanvas import Canvas
import ipywidgets as widgets
```
%% Cell type:markdown id: tags:
## Datensatz einlesen
Wir beginnen damit einen Datensatz aus der sklearn-Bibliothek zu laden.
%% Cell type:code id: tags:
``` python
digits = load_digits()
print(digits.keys())
```
%% Output
dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])
%% Cell type:code id: tags:
``` python
images = digits.images
targets = digits.target
n = len(images)
print(n)
```
%% Output
1797
%% Cell type:markdown id: tags:
Der Datensatz enthält 1797 Bilder. Auf den Bildern ist jeweils eine handschriftlich geschriebene Ziffer zu erkennen. In der Liste `target` wurde annotiert, welche Ziffer jeweils zu sehen ist. Dem Datensatz liegt außerdem eine Beschreibung bei (DESCR).
%% Cell type:markdown id: tags:
### Beschreibung
%% Cell type:code id: tags:
``` python
print(digits.DESCR)
```
%% Output
.. _digits_dataset:
Optical recognition of handwritten digits dataset
--------------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 5620
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
This is a copy of the test set of the UCI ML hand-written digits datasets
https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits
The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.
Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
an input matrix of 8x8 where each element is an integer in the range
0..16. This reduces dimensionality and gives invariance to small
distortions.
For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.
T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.
L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,
1994.
.. topic:: References
- C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their
Applications to Handwritten Digit Recognition, MSc Thesis, Institute of
Graduate Studies in Science and Engineering, Bogazici University.
- E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.
- Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.
Linear dimensionalityreduction using relevance weighted LDA. School of
Electrical and Electronic Engineering Nanyang Technological University.
2005.
- Claudio Gentile. A New Approximate Maximal Margin Classification
Algorithm. NIPS. 2000.
%% Cell type:markdown id: tags:
Aus der Beschreibung geht hervor, dass die Bilder jeweils eine Auflösung von 8 mal 8 Pixel haben. Die Bilder wurden bereits vorverarbeitet. Ein Bild wird durch eine 8x8 Matrix repräsentiert. Jeder Pixel entspricht einer Zahl zwischen 0 (schwarz) und 16 (weiß).
%% Cell type:code id: tags:
``` python
images[0]
```
%% Output
array([[ 0., 0., 5., 13., 9., 1., 0., 0.],
[ 0., 0., 13., 15., 10., 15., 5., 0.],
[ 0., 3., 15., 2., 0., 11., 8., 0.],
[ 0., 4., 12., 0., 0., 8., 8., 0.],
[ 0., 5., 8., 0., 0., 9., 8., 0.],
[ 0., 4., 11., 0., 1., 12., 7., 0.],
[ 0., 2., 14., 5., 10., 12., 0., 0.],
[ 0., 0., 6., 13., 10., 0., 0., 0.]])
%% Cell type:markdown id: tags:
## Daten visualisieren
Mit Hilfe der Funktion `imshow` können wir die Matrixdarstellung in ein Bild umwandeln. Wir betrachten das erste Bild des Datensatzes.
%% Cell type:code id: tags:
``` python
plt.gray()
plt.axis('off')
plt.imshow(images[0])
```
%% Output
<matplotlib.image.AxesImage at 0x1c09107ea48>
<matplotlib.image.AxesImage at 0x254e2953f48>
%% Cell type:markdown id: tags:
## Daten vorbereiten
%% Cell type:markdown id: tags:
### Daten vektorisieren
Bislang liegen die Daten in Form einer 8x8 Matrix vor. Wir müssen die Daten für das Modell zunächst in ein anderes Format bringen. Dazu ordnen wir die Daten hintereinander in einem langen Vektor (64 Elemente) an. Solch ein Vektor wird auch Featurevektor genannt.
%% Cell type:code id: tags:
``` python
images[0]
```
%% Output
array([[ 0., 0., 5., 13., 9., 1., 0., 0.],
[ 0., 0., 13., 15., 10., 15., 5., 0.],
[ 0., 3., 15., 2., 0., 11., 8., 0.],
[ 0., 4., 12., 0., 0., 8., 8., 0.],
[ 0., 5., 8., 0., 0., 9., 8., 0.],
[ 0., 4., 11., 0., 1., 12., 7., 0.],
[ 0., 2., 14., 5., 10., 12., 0., 0.],
[ 0., 0., 6., 13., 10., 0., 0., 0.]])
%% Cell type:code id: tags:
``` python
images = images.reshape(n, -1)
images.shape
```
%% Output
(1797, 64)
%% Cell type:code id: tags:
``` python
images
```
%% Output
array([[ 0., 0., 5., ..., 0., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.],
[ 0., 0., 0., ..., 16., 9., 0.],
...,
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 2., ..., 12., 0., 0.],
[ 0., 0., 10., ..., 12., 1., 0.]])
%% Cell type:markdown id: tags:
### Daten normalisieren
Wir skalieren den Vektor so, dass alle Werte zwischen 0 und 1 liegen. Dazu teilen wir durch den größten Wert, der auftreten kann (16). Dieser Schritt ist optional. Die Skalierung hat jedoch Auswirkungen auf die Wahl des Hyperparameters (siehe Abschnitt 11).
%% Cell type:code id: tags:
``` python
images /= 16.0
```
%% Cell type:markdown id: tags:
## Trainingsdaten und Testdaten
Wir teilen die Daten in Trainings und Testdaten auf. Die Trainingsdaten werden für das Trainieren des Modells benötigt. Die Testdaten dienen zur Optimierung des Hyperparameters und zur Evaluierung des Modells.
%% Cell type:code id: tags:
``` python
images_train, images_test, targets_train, targets_test = train_test_split(images, targets, test_size=0.5, random_state=42)
```
%% Cell type:markdown id: tags:
Wir können nun Bild und Annotation (target) paarweise betrachten:
%% Cell type:code id: tags:
``` python
train = list(zip(images_train, targets_train))
for i, (image, target) in enumerate(train[:10]):
plt.subplot(3,5, i+1)
plt.axis('off')
plt.imshow(image.reshape(8,8), interpolation='nearest')
plt.title("Training: %i" % target)
plt.show()
```
%% Output
%% Cell type:markdown id: tags:
## Modell initialisieren
Für die Klassifikation der Bilder verwenden wir eine Support Vector Machine (SVM). Die genaue Funktionsweise ist nicht Teil dieses Tutorials. Für weitere Erläuterungen siehe beispielsweise: https://monkeylearn.com/blog/introduction-to-support-vector-machines-svm/
Der Parameter $\gamma$ ist ein sogenannter Hyperparameter. Dieser hat Auswirkungen auf den Trainingsprozess und damit auf die Effektivität des Modells. Für den Anfang setzen wir diesen auf den Wert 1. Später ermitteln wir die optimale Belegung.
%% Cell type:code id: tags:
``` python
classify = svm.SVC(gamma=1)
```
%% Cell type:markdown id: tags:
## Modell trainieren
Die Methode `fit` passt das Modell an die Daten an. Das bedeutet, dass das Modell lernt anhand der Merkmale (Pixel) zwischen den verschiedenen Klassen (Ziffern) zu unterscheiden.
Das Modell sieht dabei lediglich Reihen von Zahlen (Featurevektoren) und die dazugehörige Annotationen. Das Modell sucht - vereinfacht ausgedrückt - nach statistischen Mustern zwischen den Zahlenreihen und den Annotationen. Ob es sich bei den Zahlenreihen um Bilder oder Wetterdaten handelt spielt für das Modell dabei keine Rolle.
%% Cell type:code id: tags:
``` python
classify.fit(images_train, targets_train)
```
%% Output
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma=1, kernel='rbf', max_iter=-1,
probability=False, random_state=None, shrinking=True, tol=0.001,
verbose=False)
%% Cell type:markdown id: tags:
## Vorhersagen treffen
%% Cell type:markdown id: tags:
Das trainierte Modell können wir nun dazu verwenden, neue Daten zu klassifizieren. Wir geben dem Modell die Bilder aus den Testdaten und lassen es mit Hilfe des Gelernten die dazugehörigen Annotationen vorhersagen. Anschließend vergleichen wir die Vorhersagen mit den tatsächlichen Annotationen, um die Effektivität des Modells zu bestimmen.
%% Cell type:code id: tags:
``` python
targets_pred = classify.predict(images_test)
```
%% Cell type:code id: tags:
``` python
predictions = list(zip(images_test, targets_pred))
for i, (image, predict) in enumerate(predictions[:10]):
plt.subplot(3,5, i+1)
plt.axis('off')
plt.imshow(image.reshape(8,8), interpolation='nearest')
plt.title("Predict: %i" % predict)
plt.show()
```
%% Output
%% Cell type:markdown id: tags:
## Evaluation
Die Effektivität des Modells ist auch ohne Optimierung des Hyperparameter $\gamma$ bereits sehr gut. Die Treffergenauigkeit (accuracy) gibt den Anteil der richtig klassifizierten Bilder an. Eine Genauigkeit von 0,96 bedeutet demnach, dass 96% aller Vorhersagen zutreffend waren.
Für die Optimierung des Hyperparameters $\gamma$ betrachten wir den F1-Score (makro). Dabei handelt es sich um ein das harmonische mittel zwischen Genauigkeit (precision) und Trefferquote (recall).
%% Cell type:code id: tags:
``` python
from sklearn import metrics
print(metrics.classification_report(targets_test, targets_pred))
```
%% Output
precision recall f1-score support
0 1.00 0.96 0.98 82
1 1.00 0.99 0.99 89
2 1.00 0.99 0.99 83
3 0.99 0.85 0.91 93
4 1.00 0.98 0.99 93
5 0.99 0.97 0.98 99
6 1.00 0.96 0.98 98
7 0.99 0.99 0.99 87
8 0.75 1.00 0.86 83
9 0.97 0.96 0.96 92
accuracy 0.96 899
macro avg 0.97 0.96 0.96 899
weighted avg 0.97 0.96 0.96 899
%% Cell type:markdown id: tags:
## Optimierung des Hyperparameter $\gamma$
%% Cell type:markdown id: tags:
Die Wahl von $\gamma$ einen großen Einfluss auf den Trainingsprozess und damit auf die Effektivität des Modells hat. Um die Belegung von $\gamma$ zu finden gehen wir wie folgt vor:
1. Wir definieren eine Funktion `score`. Diese erhält einen Parameter $\gamma$. die uns für gegebenes $\gamma$ den erzielten F1-Score zurürckgibt
2. Wir legen fest, welche Werte wir für $\gamma$ einsetzen möchten. Es ist meist sinnvoll zunächst verschiedene Größenordnungen (magnitudes) zu untersuchen.
3. Wir wählen denjenigen Wert aus, der den höchsten F1-Score erzielt.
%% Cell type:code id: tags:
``` python
def score(gamma):
c = svm.SVC(gamma=gamma)
c.fit(images_train, targets_train)
targets_pred = c.predict(images_test)
f1 = metrics.f1_score(targets_test, targets_pred, average='micro')
return f1
gammas = [2**mag for mag in range(-10,10)]
scores = [score(gamma) for gamma in gammas]
plt.xscale('log')
plt.xlabel('Gamma')
plt.ylabel('F1-Score')
plt.plot(gammas, scores, scalex='log')
best = max(zip(gammas, scores), key=lambda x: x[1])
print(best)
classify = svm.SVC(gamma=best[0]).fit(images_train, targets_train)
classify.fit(images_train, targets_train)
targets_pred = classify.predict(images_test)
print(metrics.classification_report(targets_test, targets_pred))
```
%% Output
(0.25, 0.9866518353726362)
precision recall f1-score support
0 0.99 1.00 0.99 82
1 1.00 1.00 1.00 89
2 1.00 1.00 1.00 83
3 0.99 0.97 0.98 93
4 1.00 1.00 1.00 93
5 0.99 0.98 0.98 99
6 1.00 0.98 0.99 98
7 0.97 0.99 0.98 87
8 0.97 1.00 0.98 83
9 0.97 0.96 0.96 92
accuracy 0.99 899
macro avg 0.99 0.99 0.99 899
weighted avg 0.99 0.99 0.99 899
%% Cell type:markdown id: tags:
Wir beobachten: Im Intervall $[10^{-2};10^0]$ erhalten wir eine zufriedenstellende Effektivität. Von den untersuchten Werte hat der Wert $\gamma=2^{-2}=0.25$ den höchsten F1-Score erzielt.
%% Cell type:markdown id: tags:
## Eigene Handschrift erkennen
%% Cell type:markdown id: tags:
Nun können wir das Modell an unserer eigenen Handschrift testen.
**Probiere es aus! Zeichne in das schwarze Feld eine Ziffer und bestätige deine Eingabe indem du den Button anklickst!**
Das Bild wird anschließend ausgelesen, verarbeitet und mit Hilfe unseres Modells klassifiziert. Die Vorhersage wird unterhalb des Bildes ausgegeben.
%% Cell type:code id: tags:
``` python
%matplotlib inline
class Drawing():
x_start = 0
y_start = 0
def __init__(self, width, height):
self.canvas = Canvas(size=(width, height), sync_image_data=True)
self.canvas.stroke_style = 'black'
self.canvas.fill_rect(0,0,width,height)
self.canvas.line_width = 10
self.canvas.line_width = 15
def handle_mouse_move(x, y):
self.canvas.line_to(x, y)
def handle_mouse_down(x, y):
print(x, y)
self.x_start = x
self.y_start = y
self.canvas.begin_path()
self.canvas.move_to(x, y)
def handle_mouse_up(x, y):
self.canvas.stroke_style = 'white'
self.canvas.stroke()
self.canvas.on_mouse_move(handle_mouse_move)
self.canvas.on_mouse_down(handle_mouse_down)
self.canvas.on_mouse_up(handle_mouse_up)
self.button_clear = widgets.Button(description="Clear")
self.output = widgets.Output()
def on_clear(b):
self.canvas.clear()
self.canvas.stroke_style = 'black'
self.canvas.fill_rect(0,0,width,height)
self.output.clear_output()
self.button_clear.on_click(on_clear)
self.button_submit = widgets.Button(description="Submit")
def on_submit(b):
data = self.canvas.get_image_data()
resized = cv2.resize(data, dsize=(8, 8), interpolation = cv2.INTER_AREA)
grayscale = cv2.cvtColor(resized, cv2.COLOR_RGBA2GRAY)
grayscale = grayscale / 255.0
grayscale = grayscale.reshape(1,64)
grayscale = grayscale / grayscale.max()
self.output.clear_output()
with self.output:
plt.axis('off')
fig = plt.imshow(grayscale.reshape(8,8))
print(grayscale)
plt.show(fig)
self.canvas.put_image_data(grayscale, 0, 0)
print('Prediction: {}'.format(classify.predict(grayscale)[0]))
self.button_submit.on_click(on_submit)
def show(self):
display(self.canvas, widgets.HBox([self.button_clear, self.button_submit]), self.output)
def to_array(self):
return self.canvas.get_image_data()
drawing = Drawing(200,200)
drawing.show()
```
%% Output
%% Cell type:code id: tags:
``` python
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment