Datenanalyse

Neuronales Netz: Erkennung von handgeschriebenen Ziffern

Als Anwendung eines neuronalen Netzes ist im Folgenden die Erkennung von hand­geschriebenen Ziffern beschrieben. Das Programm ist in der Programmier­sprache Python als jupyter-Notebook implementiert.

Handgeschriebene Ziffern

Eine Standardanwendung für ein neuronales Netz ist die Erkennung von hand­geschriebenen Ziffern. Die Datei mnist_train.csv enthält die Daten von 60.000 gescannten hand­geschriebenen Ziffern. Mit diesen Daten wird das neuronale Netz zunächst trainiert. Anschließend wird das neuronale Netz mit weiteren 10.000 hand­geschriebenen Ziffern, die sich in der Datei mnist_test.csv befinden, getestet.

Jede der beiden CSV-Dateien enhält eine Tabelle. Jede Zeile der Tabelle besteht aus 785 Zahlenwerten, dabei gibt der erste Wert die Ziffer an, die dargestellt ist, und die restlichen 784 Zahlenwerte stellen Graustufen eines 28×28-Pixel-Bildes dar, das die handgeschriebene Ziffer zeigt. Als Beispiel ist in Bild 1 eine handgeschriebene Ziffer 6 zu sehen.

 

Bild 1: Handgeschriebene Ziffer 6 als 28×28-Pixel-Bild 

Bild 1: Handgeschriebene Ziffer 6 als 28×28-Pixel-Bild

 

Die Daten entstammen der öffentlich zugänglichen MNIST-Daten­sammlung (http://yann.lecun.com/exdb/mnist/).

Eingabedaten aufbereiten

Die Trainings­daten, die sich in der CSV-Datei mnist_train.csv befinden, werden zunächst mit folgender Funktion eingelesen.

def readInput(filename):
    datafile=open(filename, 'r')
    datalist=datafile.readlines()
    datafile.close()
    return datalist

Das Array datalist besteht aus 60.000 Strings, die jeweils Zeilen von durch Kommas getrennten Werten darstellen. Mit einer Funktion prepareInput werden die Daten für die weitere Verwendung aufbereitet.

Jede der durch Kommas unterteilten Zeilen wird im ersten Schritt in ein Array umgewandelt. Die Array-Einträge sind aber noch Strings, daher werden diese als nächstes in Zahlen umgewandelt. Dann wird die erste Zahl abgetrennt, diese entspricht dem Wert der dar­gestellten Ziffer, und in der Variablen d gespeichert. Die restlichen Zahlen werden um 128 vermindert, sodass sie in den Bereich {-128., ..., +127} fallen. Das Ergebnis ist der spätere Eingabe­vektor x, auf den in der Eingabe­schicht ja die Funktion σ angewandt wird. So wird erreicht, dass der Wertebereich von σ ausgeschöpft wird.

Der Zielvektor y wird folgender­maßen gebildet. Zunächst wird Vektor mit zehn gleichen Werten von 0.1 erzeugt. In diesem Vektor wird an Index­position d der Wert in 0.9 geändert. Das neuronale Netz soll später so trainiert werden, dass bei Eingabe einer hand­geschriebenen Ziffer d das Neuron an Index­position d der Ausgabe­schicht einen hohen Wert liefert und alle anderen Neuronen der Ausgabe­schicht einen niedrigen Wert. Die Ausgabewerte des neuronalen Netzes liegen stets zwischen 0 und 1, da sie als Fiúnktionswerte der Funktion σ zustande kommen.

Eingabe­vektor und zugehöriger Zielvektor werden miteinander verkettet, und alle so aufbereiteten Zeilen werden zum Schluss zu einer zwei­dimensionalen Liste r zusammengefügt.

 

def prepareInput(datalist):
    r=[]
    for z in datalist:
        v=z.split(',')
        x=[int(s) for s in v]
        d=x[0]
        x=[t-128 for t in x[1:]]
        y=[0.1]*10
        y[d]=0.9
        r+=[y+x]
    return r

In entsprechender Weise werden auch die Testdaten aus der Datei mnist_test.csv eingelesen und aufbereitet.

Programm

Es folgt das fertige Programm. Es beginnt mit den Definitionen der Funktionen readInput und prepareInput. Dann kommt die Definition der Funktion sigma. Die Definitionen der eigentlichen Funktionen des neuronalen Netzes, nämlich propagate, backpropagate, train und test schließen sich an.

Im Haupt­programm wird zunächst das neuronale Netz definiert, indem pro Schicht die Anzahl der Neuronen angegeben wird. Hier etwa wird ein neuronales Netz mit drei Schichten definiert, wobei die Eingabe­schicht 28 · 28 = 784 Neuronen umfasst, die innere Schicht 100 Neuronen und die Ausgabe­schicht 10 Neuronen. Alternativ, hier im Programmtext auskommentiert, wird ein neuronales Netz mit vier Schichten definiert.

Überraschender­weise hat die Initialisierung der Gewichtungsmatrizen erheblichen Einfluss auf die Erkennungsrate des neuronalen Netzes. Hier ist eine Initialisierung gewählt, die normalverteilte Zufallswerte enthält.

Für die im Verlauf der Berechnung erforder­lichen Vektoren werden zunächst Platzhalter erzeugt, sodass später per Indizierung darauf zugegriffen werden kann (zum Beispiel muss e[0] vorhanden sein, wenn ihm ein Wert zugewiesen wird).

Es folgt dann das Programm­stück zum Trainieren und Testen des neuronalen Netzes. Die 60.000 Trainings­datensätze werden mehrfach in sogenannten Epochen in das Netz eingegeben, um es zu trainieren. Im Anschluss daran wird mit den 10.000 Testdatensätzen jeweils die erzielte Erkennungsrate bestimmt.

 

import numpy as np

# Daten aus Datei einlesen
def readInput(filename):
    datafile=open(filename, 'r')
    datalist=datafile.readlines()
    datafile.close()
    return datalist

# Daten für die weitere Verwendung aufbereiten
def prepareInput(datalist):
    r=[]
    for z in datalist:
        v=z.split(',')
        x=[int(s) for s in v]
        d=x[0]
        x=[t-128 for t in x[1:]]
        y=[0.1]*10
        y[d]=0.9
        r+=[y+x]
    return r

# Aktivierungsfunktion
def sigma(x):
    return 1.0/(1+np.exp(-x))

# Eingabevektor x durch das neuronale Netz schicken
def propagate(x):
    e[0]=np.array(x).reshape(1,m[0])  # 1 x m[0]-Matrix
    s[0]=sigma(e[0])
    for r in range(1, n):
        e[r]=np.matmul(s[r-1], w[r-1])
        s[r]=sigma(e[r])

# Zielvektor y durch das neuronale Netz zurückverbreiten
# und dabei die Gewichtungen anpassen
def backpropagate(y):
    y=np.array(y).reshape(1, m[n-1])  # 1 x m[n-1]-Matrix
    r=n-1
    f[r]=s[r] - y
    h[r]=f[r]*s[r]*(1-s[r])
    # Fehler zurückpropagieren
    for r in range(n-2, 0, -1):
        f[r]=np.matmul(h[r+1], w[r].T)
        h[r]=f[r]*s[r]*(1-s[r])
    # Gewichte anpassen
    for r in range(n-1, 0, -1):
        w[r-1] -= np.matmul(s[r-1].T, alpha*h[r])

# das neuronale Netz mit den Trainingsdaten trainieren
def train():
    for z in traindata:
        x=z[10:]
        propagate(x)
        y=z[:10]
        backpropagate(y)

# das neuronale Netz mit den Testdaten testen
# und die Erkennungsrate bestimmen
def test():
    cnt=0
    for z in testdata:
        x=z[10:]
        propagate(x)
        y=z[:10]
        p=np.argmax(y)
        q=np.argmax(s[n-1][0])  # [0] weil 1 x m_n-1-Matrix
        if p==q:
            cnt+=1
    return 100.0*cnt/len(testdata)  # Prozent richtig erkannt
       
# Neuronales Netz definieren
# Anzahl der Neuronen pro Schicht
m=[28*28,200,10]
#m=[28*28,300,80,10]
n=len(m)    # Anzahl der Schichten
# Liste mit Gewichtungsmatrizen:
w=[np.random.normal(0.0, pow(m[r-1], -0.5), (m[r-1], m[r])) for r in range(1,n)]
e=[0]*n  # Platzhalter für Eingabevektoren
s=[0]*n  # Platzhalter für Ausgabevektoren
f=[0]*n  # Platzhalter für Fehlervektoren
h=[0]*n  # Platzhalter für Fehlervektoren

# Trainingsdaten einlesen
traindata=prepareInput(readInput("mnist_train.csv"))
# Testdaten einlesen
testdata=prepareInput(readInput("mnist_test.csv"))

# Mehrere Epochen mit jeweils 60.000 Trainingsdatensätzen und
# anschließendem Test mit 10.000 Testdatensätzen durchlaufen,
# nach und nach die Lernrate verringern
epochs=6
for i in range(epochs):
    alpha=0.1*(epochs-i)  # Lernrate nach und nach verringern
    train()
    print("Training zu Ende")
    p=test()
    print('Erkennung: '+str(p)+ " %")

Der Programmtext steht in Form eines jupyter-Notebooks unter MultilayerNeuralNetwork-HandwrittenNumbers.ipynb zum Herunterladen zur Verfügung. Zusätzlich erforderlich sind die Dateien mnist_train.csv und mnist_test.csv.

Probieren Sie das Programm mit unter­schiedlichen neuronalen Netzen mit unter­schiedlicher Anzahl von Schichten und Anzahl von Neuronen in den inneren Schichten aus. Die Anzahl der Neuronen in der Eingabe­schicht liegt fest, sie beträgt 28 · 28 = 784 entsprechend der Anzahl der Pixel der Eingabebilder. Die Anzahl der Neuronen der Ausgabe­schicht liegt ebenfalls fest, sie beträgt 10 entsprechend der Anzahl der zu erkennenden unter­schiedlichen Ziffern 0, ..., 9.

Leistungsfähigkeit

Das hier angegebene neuronale Netz erzielt nach der sechsten Trainingsepoche eine Erkennungsrate von über 97 %. Tatsächlich wäre eine Erkennungsrate von 100 % auch gar nicht unbedingt sinnvoll, da manche Ziffer so unsauber geschrieben ist, dass eine Zuordnung zu einem "richtigen" Wert rein willkürlich wäre.

 

Weiter mit:   [up]

 


H.W. Lang   mail@hwlang.de   Impressum   Datenschutz
Created: 04.01.2021   Updated: 08.02.2023
Diese Webseiten sind während meiner Lehrtätigkeit an der Hochschule Flensburg entstanden