Bernd Thomas
Beck et al.
Published in
7 min readOct 16, 2019

--

3.3 Das zweite Teilproblem: Gerade-/Ungerade-Klassifikation einstelliger Zahlen

pixabay.com

Zur Freude eines mathematisch denkenden Menschen haben wir mit dem NN für das erste Teilproblem die Klassifikation von allgemeinen geraden / ungeraden Zahlen auf das Problem mit einstelligen Zahlen reduziert — und von denen gibt es bekanntlich nur zehn: 0, 1, 2, … 9 . D.h. wir fragen uns jetzt, ob es ein NN gibt, das lernt die einstelligen Zahlen nach gerade/ungerade zu klassifizieren. Die Antwort ist “ja”, wie man im Folgenden sieht!

Anm.: Es ist aber zu beachten, dass — anders, als bei einer mathematischen “Reduktionsstrategie” — mit den NN-Lösungen für die zwei einfacheren Teilprobleme nicht auch bereits eine Lösung für das Gesamtproblem der Gerade-/Ungerade-Klassifikation gegeben ist. Das erkennt man u.a. daran, dass die Trainingsdaten für das Gesamtproblem als Zahl : Gerade/Ungerade-Klasse vorgelegt werden, wogegen das erste Teilproblem die Daten als Zahl : Ziffer der Einer-Stelle und das zweite als Ziffer : Gerade/Ungerade-Klasse benötigt. (In Teil 4 werden wir versuchen, die beiden Teil-Netze zu einem Gesamt-NN für die Gerade-/Ungerade-Klassifikation zusammen zu bringen.)

Der “Trick” bei dieser zweiten Lern-Aufgabe ist, die Trainingsdaten (einstellige Zahlen bzw. Ziffern) in eine one-hot Kodierung zu überführen. D.h. die Zahlen 0, 1, … 9 werden als Einheitsvektoren eines 10-dimensionalen Zahlenraums repräsentiert — also z.B. die Zahl 3 als [0,0,0,1,0,0,0,0,0,0]. Die zugehörige Klassifikation ist "ungerade", also y = 1. Mit den so transformierten Trainingsdaten trainieren wir folgendes NN-Model (ebenfalls wieder ein einfaches Perzeptron):

Algorithmus: Ein einfaches NN mit Input-Layer und fully connected 1-Neuron Output Layer

  • Input Layer: 10 Zeichen, x_0, ..., x_9 (one-hot Vektor)
  • Gewichte: w = (w_0,...,w_9, no bias, Startwerte w_k = 0.5 (z.B.)
  • Net input: z = w*x (dot-Produkt)
  • Activation: relu d.h. a = phi(z) = z, wenn z>0 sonst phi(z) = 0
  • Output Layer: 1 Unit
  • Output Funktion: y = a, bzw. für die Vorhersage y = round(a) (kaufm. Runden)
  • Loss: mean square error (mse) err = sum_i((y_i - z_i\**2)/N
  • Gradient des mse für w: Die Ableitung der Activation Funktion relu ist hier deriv_phi(z) = 1.0 wenn z>0 sonst 0.0 (Sprungfunktion)
  • Schrittweiten-Faktor (Learning Rate): eta = 1.0 (nicht adaptiv)
  • delta_w = eta * grad

Generieren der Daten: Zunächst werden genau wie in 3.1 die Data-Sets und die Vorgabe-Werte generiert, und die Daten dann in Training und Test-set aufgeteilt. Die Trainings- und Testdaten sind jetzt einstellige Zahlen, bzw. deren one-hot Codierung. Die zugehörigen Vorgabe-Werte (targets) sind die Klassen gerade (0) und ungerade(1) .

import numpy as np
import random as rd
import matplotlib.pyplot as plt
nmin,nmax = 0,10 # nmax = Anzahl der Ziffern
n_cases = 200
N = n_cases
n_pos = 1 # Anzahl Stellen, beliebig wählbar
# Training Data Set: Skalare
D = np.random.randint(nmin,nmax,size=(n_cases))
y = D[:] % 2
print('Die ersten 10 aus dem Data-Set:')
print([(D[i],y[i]) for i in range(10)])
Die ersten 10 aus dem Data-Set:
[(5, 1), (9, 1), (8, 0), (2, 0), (7, 1), (5, 1), (9, 1), (0, 0), (7, 1), (4, 0)]

Umwandlung in Input-Daten per one-hot Kodierung:

# One-hot Codierung für Data in Z
# z = i --> e_i = [0,0,0,..1,0,..0] , die 1 an Position i
H = np.zeros((D.shape[0],10)) # Training-Set H Vectors
for i in range(n_cases): # n_cases Input Vektoren
H[i,D[i]] = 1.0
print('Die ersten 10 Zahlen als Vektoren')
for i in range(10):
print([i, D[i],[H[i,j] for j in range(10)]])
Die ersten 10 Zahlen als Vektoren
[0, 5, [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]]
[1, 9, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]]
[2, 8, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]]
[3, 2, [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
[4, 7, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]]
[5, 5, [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]]
[6, 9, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]]
[7, 0, [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
[8, 7, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]]
[9, 4, [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]]

Modell-Struktur und Training:

Es ist klar, das es nur 10 verschiedene Trainingsvektoren gibt (10 Einheitsvektoren des 10-dim Raums). D.h. in einem Trainingset der Größe über 10 (z.B. 200) wiederholen sich Trainingsvektoren zwangläufig. Stattdessen könnte man auch nur die 10 verschiedenen Vektoren in einem Trainingsset der Größe 10 verwenden. Wir lassen aber hier das Konzept des Trainingssets allgemein.

''' Training des 10->1 NN Modells (Lineares Perceptron) '''def net_input(X,w):        # Net Input - array version
return X.dot(w) # Input gewichtet mit w
def net_input_vector(x,w): # Net Input - one vector version
return x.dot(w)
def phi_id(z): # Aktivierungsfunktion: Linear
return z
def phi_logistic(z): # Aktivierungsfunktion: Logistic
return 1.0/(1.0 + np.exp(-z))
def phi(z): # ** Aktivierungsfunktion: ReLU
return z if z>0.0 else 0.0
def step_func(z): # Ableitung der ReLU
return 1 if z>0.0 else 0.0
def err_scalar(y_true,y_calc): # Fehler (Loss) - scalar version
return 0.5*(y_true-y_calc)**2 # Halber Mean Square Error
def err(y_true,y_calc): # Loss - vector version
return 0.5*((y_true-y_calc)**2).mean()
# Training Loop w_start = 0.5* np.ones(nmax) # Initialisierung Gewichte (10)
w = w_start
n_start = 0
n_span = n_cases # Batch size
n_batches = 1 # Anzahl Batches
epochs = 50 # Anzahl Iterationen per Batch
out = 10 # Zwischenausgabe Frequenz
X = H[n_start:n_start+n_span] # Auswahl von H als Batch X
y_true = y[n_start:n_start+n_span] # Vektor der Klassifizierungen
# passend zum Batch X
plot_mse = list()
plot_w = list()
Phi = np.vectorize(phi) # Vectorize if phi is ReLU
Step_func = np.vectorize(step_func) # Same for derivative of ReLU
print('Anzahl Training Batches: ',1)for ep in range(epochs): # Loop über epochs (per Batch)

if ep%out == 0:
print(ep)
eta = 1.0
mse_cum = 0.0
grad = np.zeros(10)

Z = net_input(X,w) # Net input über Batch
y_pred = Phi(Z) # Vector Version von phi
mse = err(y_true,y_pred) # MSE über alle Fälle im Batch

if ep%out == 0:
print('Loss:',mse)

plot_mse.append(mse) # Liste für mse Plot

# Berechnung des Gradienten und Korrektur der Gewichte

delphi = (y_true - y_pred)*Step_func(Z) # Multiplikation
# komponentenweise

for k in range(10): # Gradient Komponente w_k
grad[k] = delphi.dot(X[:,k])/n_span # Mittel über Batch


w += eta * grad # Korrektur w

if ep%out == 0: # Print output
print([('%6.3f' % w[k]) for k in range(10)])
plot_w.append(list(w))

w_fit = w # Training Ergebnis
print('\nModell-Parameter (Gewichte) nach Training-Epoche:',ep,'\n',[('%6.3f' % w[k]) for k in range(10)])Anzahl Training Batches: 1
0
Loss: 0.125
[' 0.450', ' 0.540', ' 0.443', ' 0.545', ' 0.463', ' 0.552', ' 0.440', ' 0.562', ' 0.450', ' 0.545']
10
Loss: 0.015230921159507936
[' 0.157', ' 0.800', ' 0.130', ' 0.823', ' 0.212', ' 0.852', ' 0.123', ' 0.885', ' 0.157', ' 0.823']
20
Loss: 0.002086403226122504
[' 0.055', ' 0.913', ' 0.038', ' 0.931', ' 0.097', ' 0.951', ' 0.034', ' 0.970', ' 0.055', ' 0.931']
30
Loss: 0.00031625126407273344
[' 0.019', ' 0.962', ' 0.011', ' 0.973', ' 0.045', ' 0.984', ' 0.010', ' 0.992', ' 0.019', ' 0.973']
40
Loss: 5.1930165821425824e-05
[' 0.007', ' 0.984', ' 0.003', ' 0.990', ' 0.020', ' 0.995', ' 0.003', ' 0.998', ' 0.007', ' 0.990']
Modell-Parameter (Gewichte) nach Training-Epoche: 49
[' 0.003', ' 0.992', ' 0.001', ' 0.996', ' 0.010', ' 0.998', ' 0.001', ' 0.999', ' 0.003', ' 0.996']

Die Ausgabe zeigt die Entwicklung des mse und der Gewichte w_k in Schritten von 10 Epochen. Am Ende des Trainings zeigen die Gewichte bereits ein Muster: Kleine Werte für Inputs, die gerade Ziffern kodieren, große Werte für die, die ungerade Ziffern kodieren.

Die Plots zeigen das Training als typische Lernkurven für den Fehler mse (loss) und die Gewichte:

  • Oben: Entwicklung des Mean Square Errors über die Epochen
  • Unten: Entwicklung der Gewichte (unterschiedliche Farben)
Training Konvergenzüber 50 Epochen. Links: Gewichte. Rechts: Loss (mse)

Die Entwicklung der Gewichte im Veraufe der Traingsepochen zeigt sehr schön die Aufspaltung in Gewichte, die gegen Null konvergieren und solche, die gegen 1 gehen. Auf die prinzipielle Bedeutung gehen wir am Ende dieses Teils noch einmal ein.

Vorhersage: Die korrekte Vorhersage ist hier trivial, da als Test-Input nur Zahlen (one-hot Vektoren) getestet werden können, die schon im Training vorlagen.

Das Modell ist also wieder ein Beispiel für “Auswendig-Lernen”. Anders als in 3.1 gibt es aber bei dieser Lern-Aufgabe auch keine anderen Inputs als die schon im Traing verwendeten Daten. Des Modell erwirbt also vollständiges Wissen über den Lern-Gegenstand.

# Vorhersage mit Test-Zahlen bei trainiertem w_fit
# Es reicht systematisch die 10 Zahlen 0,1,...9 zu testen
# Output y_pred wird per round() klassifiziert
# (Sprungfunktion mit Schranke 0.5)

D_test = np.arange(10) # Test Zahlen (Digits)
X_test = np.zeros((D_test.shape[0],10)) # Test Set X
for i in range(10): # one-hot Coding Test Zahlen
X_test[i,D_test[i]] = 1.0

print('Prediction Test für')
for d in D_test:
test_input = X_test[d]
a = phi(net_input(test_input,w_fit)) # w_fit aus Training s.o.
y_pred = round(a)
y_text = ' gerade!' if y_pred == 0 else ' ungerade!'
print(d,test_input,' : %5.3f ' % y_pred, y_text)
Prediction Test für
0 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] : 0.000 gerade!
1 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] : 1.000 ungerade!
2 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] : 0.000 gerade!
3 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] : 1.000 ungerade!
4 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] : 0.000 gerade!
5 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] : 1.000 ungerade!
6 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] : 0.000 gerade!
7 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.] : 1.000 ungerade!
8 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] : 0.000 gerade!
9 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] : 1.000 ungerade!

Das Modell lernt diese kleine Aufgabe offensichtlich schnell und korrekt!

Was haben wir gelernt? Weiter lesen!

Weiter lesen: 3.4 Erkenntnisse

Zurück auf Anfang

bernhard.thomas@becketal.com
www.becketal.com

--

--

Bernd Thomas
Beck et al.

Dr. Bernhard Thomas — Mathematics, Theor. Biology, Computational Sciences, AI and advanced Technologies for the Enterprise. Beck et al. Consultant