Erkennung von Ziffern

Das Erkennen von Ziffern hat in dem Bereich Maschinelles Lernen Tradition. Hiervon ausgehend kann mit Schrifterkennung gearbeitet werden, um z. B. automatisch Briefe der Adresse nach zu sortieren.

In diesem Jupyter Notebook werden die Ziffern klassifziert, die mit scikit-learn bereits mit ausgeliefert werden.

[1]:
import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt

Zunächst wird der Datensatz mit den Ziffern geladen.

[2]:
digits = datasets.load_digits()
print(digits.DESCR)
.. _digits_dataset:

Optical recognition of handwritten digits dataset
--------------------------------------------------

**Data Set Characteristics:**

:Number of Instances: 1797
: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.

|details-start|
**References**
|details-split|

- 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.

|details-end|

Darstellung einer Ziffer

Nun wird exemplarisch die 0te Ziffer des Datensatzes dargestellt.

[3]:
digits.images[0]
[3]:
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.]])

Die Pixel werden anscheinend als Fließkommazahlen zwischen 0 und 16 dargestellt, wobei die Nachkommastellen nicht genutzt werden. Eine Ziffer wird durch eine \(8 \times 8\)-Matrix repräsentiert.

Schauen wir uns mal das allererste Bild an.

[4]:
plt.imshow(digits.images[0], cmap="gray")
plt.show()
../../_images/03-einsatzszenarien_01_maschinelles-sehen_02_Erkennung_von_Ziffern_7_0.svg

Es handelt sich scheinbar um eine 0, wobei aus der Matrix eine 0 als schwarz und eine 16 als weiß dargestellt wird. Die Zahlen repräsentieren Intensitätswerte.

Wir als Menschen erkennen, dass es sich um eine Null handelt, aber ist dies auch richtig im Datensatz hinterlegt? Beim Überwachten Lernen brauchen wir für die Automatisierung bereits erkannte Ziffern.

Eine Möglichkeit, Abbildungen von Ziffern zu analysieren, ist, einfach jeden Pixel wie ein eigenes Attribut (also eine eigene Spalte in einer Tabelle) zu behandeln. Beim Aufruf von flatten werden die Reihen der Matrix aneinandergehängt, vgl. die numpy-Dokumentation. Dabei erhält man eine eindimensionale Liste.

[5]:
flattened = digits.images[0].flatten()
flattened
[5]:
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.])

Hier sind nun einfach alle Pixel aneinander gehängt worden. So nimmt ein Bild nur eine Zeile ein.

[6]:
fig, ax = plt.subplots(figsize=(16, .2))
ax.imshow(np.expand_dims(flattened, axis=0), cmap="gray", aspect="auto")
plt.yticks([])  # verstecke Y-Achse
plt.show()
../../_images/03-einsatzszenarien_01_maschinelles-sehen_02_Erkennung_von_Ziffern_13_0.svg

Vorverarbeitung der Bilder

Die eindimensionale Repräsentation von Bildern ist notwendig, weil die bislang vorgestellten Klassifizierungs-Algorithmen nur einfache Attribute (quasi Spalten) verstehen - die räumliche Nähe der Pixel kann von ihnen nicht ausgewertet werden. Bei diesem Vorverarbeitungs-Schritt geht die Information über die räumliche Nähe im 2d-Bild offensichtlicherweise verloren.

[7]:
images = [image.flatten() for image in digits.images]

Nun liegt die Eingabe so vor, dass damit wie bislang auch verfahren werden kann. Die Kategorie ist in digits.target vermerkt. Also digits.images[22] ist das Bild und in digits.target[22] steht dann, dass es sich bei dem Bild um eine Zwei handelt.

[8]:
X_train, X_test, y_train, y_test = train_test_split(
    images, digits.target, test_size=0.33, random_state=42
)

clf = RandomForestClassifier(
    n_estimators=10,
    max_depth=7,
    max_features="sqrt",
    class_weight=None,
    random_state=10
)
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
print(f"Die erreichte Test-Genauigkeit: {accuracy:.02%}")
Die erreichte Test-Genauigkeit: 93.60%

Nun lasssen wir uns eine Konfusionsmatrix anzeigen. Damit kann man sehen, welche Ziffern mit welchen anderen Ziffern am häufigsten verwechselt worden sind.

[9]:
ConfusionMatrixDisplay.from_estimator(clf, X_test, y_test, cmap="BuPu")
plt.show()
../../_images/03-einsatzszenarien_01_maschinelles-sehen_02_Erkennung_von_Ziffern_20_0.svg

Eigentlich ist die Vorhersage relativ gut: Die tatsächliche und die vorhergesagte Ziffer stimmen ganz gut überein. Man sieht nur, dass Fünfen und Achten häufiger für Neunen gehalten worden sind (in der Mitte rechts bzw. unten).

Zur Erinnering: Mit dem Fragezeichen-Operator lässt sich die Dokumentation zu einem beliebigen Objekt, wie z. B. der Klasse RandomForestClassifier anzeigen. In Python sind auch Klassen Objekte.

[10]:
?RandomForestClassifier

Aufgabe

Verändern Sie einen beliebigen Parameter des Random Forests und führen Sie den Code erneut aus. Tragen Sie die Parameter wie unten vorgegeben in die Liste ein. Falls Sie sich fragen, was die einzelnen Parameter des Random Forests bedeuten, verwenden Sie den Fragezeichen-Operator (siehe unten). Hat sich nach der Veränderung der Wert verbessert oder verschlechtert? Wiederholen Sie den Vorgang insgesamt fünfmal.

Ihre Antwort:

Versuch 1

clf = RandomForestClassifier(
    n_estimators=10,
    max_depth=7,
    max_features="sqrt",
    class_weight=None
)
#  --> Test-Genauigkeit: 94,95 %

Versuch 2

clf = ...
#  --> Test-Genauigkeit: ... %

Versuch 3

clf = ...
#  --> Test-Genauigkeit: ... %

Versuch 4

clf = ...
#  --> Test-Genauigkeit: ... %

Versuch 5

clf = ...
#  --> Test-Genauigkeit: ... %

Ein weiterer Klassifizierer neben den Entscheidungsbäumen und dem Random Forest ist der k-Nächsten-Nachbarn-Algorithmus. Hier werden die Eingabedaten als Vektoren betrachtet und die euklidische Distanz zwischen diesen Eingabedaten berechnet. Es gibt keine Regeln, wann welcher Algorithmus besser funktioniert. Beim Maschinellen Lernen hilft nur Ausprobieren!

Ein weiterer Klassifizierer

[11]:
from sklearn.neighbors import KNeighborsClassifier
neigh = KNeighborsClassifier(n_neighbors=3)
neigh.fit(X_train, y_train)
accuracy = neigh.score(X_test, y_test)
print(f"Die erreichte Test-Genauigkeit: {accuracy:.02%}")
Die erreichte Test-Genauigkeit: 98.99%

Die Literatur zur Erkennung von Ziffern ist sehr umfangreich, ein Startpunkt ist bspw. die Webseite http://yann.lecun.com/exdb/mnist/

Creative Commons Lizenzvertrag     Dieses Werk von Marvin Kastner ist lizenziert unter einer Creative Commons Namensnennung 4.0 International Lizenz.