5.7 Zwei mal Drei ist Sechs — Ein keras/tf Modell lernt Vielfache von 6 und mehr
Vielfache von 6 lassen sich nicht so einfach charakterisieren wie gerade/ungerade Zahlen oder Vielfache aus den bisherigen Aufgaben, nämlich durch eine geschlossene Teilbarkeitregel für die Ziffernfolge einer Zahl. Stattdessen könnte man die Zerlegung in Primfaktoren, hier 2 und 3, verwenden, für die wir ja schon trainierbare Neuronale Netze gefunden haben.
Der “Konstrukteur” (Mensch oder Maschine) eines Deep Learning NN müsste nur die beiden Modelle so zusammen bringen, dass ein Gesamtmodell entsteht, das mit einem auf Vielfache von 6 klassifiziertes Trainingset lernt. Aufgrund des Tensor-Flow Konzeptes von tensorflow
und dem API von keras
lässt sich das - prinzipiell - machen.
Wir verlassen dabei die sequentiellen Modelle zugunsten einer verzweigten Modellarchitektur, quasi ein einfaches Netz von Deep NNs.
Zunächst die Struktur des verzweigten Modells als Schema:
- Das Modell für die Gerade/Ungerade Klassifikation aus 4.2 ist hier in der API Form definiert, wobei die in 4.2 als Preprocessing durchgeführten one-hot-Codierung und Reshape (entspricht dem Flatten) durch das vortrainierte Layer-Paar (aus 5.5) ersetzt werden.
- Verzweigungen: Der Output von #2 geht als Input in #3 und in #11. Die Outputs von #14 und #15 gehen als Input in #16 (Add Layer).
Vorbereitung: Imports, Data Sets und Klassifikation
Von den keras.layers
importieren wir hier zusätzlich den Layer Add
aus der Gruppe der Merge Layer, sonst bleibt alles wie gehabt. Die Dataset Vorgaben werden nach Teilbarkeit durch 6 klassifiziert.
# The usual imports plus Add Layer
import numpy as np
import random as rd
import matplotlib.pyplot as plt
from keras.layers import Dense, Input, Lambda, Flatten, Add
from keras.models import Model
from keras.optimizers import sgd, adam
from keras import backend as K
n_cases = 600
nmin,nmax = 0,10000
X = np.random.randint(nmin,nmax,size=(n_cases))
# Generieren der Target-Klassifikationen für Vielfache von 6
y = np.array(list(x%6 for x in X))
y = np.where(y>0,1,y) # Restklassen > 0 werden als y=1 klassifiziert
n_train = 450
X_train = X[:n_train]
y_train = y[:n_train]
n_test = n_cases - n_train
X_test = X[n_train:]
y_test = y[n_train:]
Das keras
Modell
Die kernel_initializer
und Custom-Aktivierungen sind aus 5.5 übernommen (siehe dort für die Code-Abschnitte).
# Keras model mm6 for classification of multiples of 6
# as a merged structure (Add) of the
# Even/odd and multiple of 3 models
inp = Input(shape=(1,),name='Input_integer')
# -------- Shared Layer Section:
# 1st integer-to-digits4 transformation
x = Dense(4,name = 'Dense_Divide',activation=custom_int,
use_bias=False,kernel_initializer=div_init,
trainable=False)(inp)
feat_inp = Dense(4,name = 'Dense_Digits',activation=custom_round,
use_bias=False,kernel_initializer=digit_init,
trainable=False)(x)
# feat_inp used as featured Input for MM3 branch
# and Even/Odd branch
# -------- MM3 Layer Section
# First level checksum
hand_over = Dense(1,name='Dense_sum',activation='linear',
use_bias=False,kernel_initializer=sum_init,
trainable=False)(feat_inp)
# Second level checksum
x = Dense(4,name = 'Dense_Divide_1',activation=custom_int,
use_bias=False,kernel_initializer=div_init,
trainable=False)(hand_over)
x = Dense(4,name = 'Dense_Digits_1',activation=custom_round,
use_bias=False,kernel_initializer=digit_init,
trainable=False)(x)
hand_over = Dense(1,name='Dense_sum_1',activation='linear',
use_bias=False,kernel_initializer=sum_init,
trainable=False)(x)
# 3rd level checksum
x = Dense(4,name = 'Dense_Divide_2',activation=custom_int,
use_bias=False,kernel_initializer=div_init,
trainable=False)(hand_over)
x = Dense(4,name = 'Dense_Digits_2',activation=custom_round,
use_bias=False,kernel_initializer=digit_init,
trainable=False)(x)
out_cs = Dense(1,name='Dense_sum_2',activation='linear',
use_bias=False,kernel_initializer=sum_init,
trainable=False)(x)
# Lambda for cast to integer, one-hot coding and Flatten
out_cs = Lambda(lambda x: K.one_hot(K.cast(x,'uint8'),10),
name='Lambda_One_Hot_3')(out_cs)
out_cs = Flatten()(out_cs)
# Add dense layer which takes input as one 10-dim unit vector
# which represents the one-hot coded output of the previous model steps
out_3 = Dense(1,name='Dense_out_3', activation='linear',
use_bias=False,trainable=True)(out_cs)
# -------- Odd/Even Layer Section
# Sharing output feat_inp from first int-to-digits with MM 3
# Lambda for cast to integer, one-hot coding and Flatten
x = Lambda(lambda x: K.one_hot(K.cast(x,'uint8'),10),
name='Lambda_One_Hot_2')(feat_inp)
x = Flatten()(x)
# Even/Odd model layers - see 4.2: Layers D1 and D2 can be omitted
# x = Dense(1,activation='linear',use_bias=False,name='D1')(x)
# x = Dense(10, activation='linear',use_bias=False,name='D2')(x)
out_2 = Dense(1,activation='linear',use_bias=False,
name='Dense_out_2')(x)
# -------- Merging the two structures
# out_3 and out_2 got into Add
out_23 = Add()([out_3,out_2])
# Final Dense layer to output, using sigmoid activation
out_6 = Dense(1,activation='sigmoid',name='Dense_out')(out_23)
# use_bias not really essential for trainingmm6 = Model(inputs=[inp],outputs=[out_6])
mm6.summary()
# -------- Model Compilation
# Optimizer and lr used only if training is intended
lrate= 0.01 # learning rate
opt = sgd(lrate) # alternative: sgd with learning rate
opt = adam(lrate)
mm6.compile(optimizer=opt,loss='mean_squared_error',
metrics=['accuracy'])
Trainierbarkeit
Das Moell mm6
ist so definiert, dass Trainierbarkeit für die Layer "unterhalb" der Lambda/Flatten-Schnittstelle zugelassen ist. Der Output Layer Dense_out
kann mit oder ohne Bias trainiert werden. Wie sich zeigt, geht beides gut.
# Train mm6ep = 100 # Set number of epochs
bs = 10 # Set bacth size
hist1 = mm6.fit(X_train,y_train,epochs=ep,batch_size=bs,verbose=0)plt.plot(hist1.history['acc'],'k-')
plt.show()
plt.plot(hist1.history['loss'],'g--')
plt.show()
Wie die Plots zeigen, ist das Modell sehr gut trainierbar.
Evaluation
Die Evaluation mit dem Testdaten-Teil zeigt die exakten Ergebnisse (Accuracy = 1.0) für starkes Lernen.
# Prediction for test data and test targets
pred = mm6.predict(X_test)
print(n_test,X_test[:10],y_test[:10])
y_pred = np.round(pred)
print(y_test.shape,y_pred.shape)
print(pred[:10],y_pred[:10])
acc = 1.0 - np.sum(np.abs(y_test - y_pred[:,0]))/n_test
print('Accuracy Testset:',acc)150 [9060 231 3994 9253 7715 9605 9329 9677 6282 750]
[0 1 1 1 1 1 1 1 0 0]
(150,) (150, 1)
[[0.01086223]
[0.99708664]
[0.99670726]
[0.9999999 ]
[1. ]
[0.99999994]
[0.9999998 ]
[0.9999999 ]
[0.00626734]
[0.00959241]]
[[0.]
[1.]
[1.]
[1.]
[1.]
[1.]
[1.]
[1.]
[0.]
[0.]]
Accuracy Testset: 1.0
Erkenntnisse
Tensorflow/Keras ermöglicht es über sogenannte Merge Layer, NN-Strukturen trainierbar zusammenzufügen. Das legte nahe, komplexere Aufgaben durch ein Deep NN zu trainieren, das aus “bekannten Teilkomponenten” für Unteraufgaben aufgebaut ist. “Zwei mal Drei ist Sechs” ist ein einfaches Beispiel, das dieses Prinzip illustriert, und man überlegt sich leicht, wie man dieses ausweiten kann auf andere Vielfache-Bestimmung. (Und wo nicht, z.B. für 4 oder 12.)
Es ist wichtig zu verstehen, dass der Add Layer den Output von zwei oder mehreren Teil-NNs addiert — hier also die Klassifikationen aus mm2 und mm3 — und nicht etwa Daten verrechnet! Frage: auf was könnte man ein NN trainieren, wenn man den Add Layer durch den Multiply Layer (ebenfalls ein Merge Layer) ersetzen würde?
Nicht überraschen sollte es, dass mm6
auch ohne Weiteres auf Erkennen von Vielfachen von 2, 3, 5, 9, 15 etc. trainiert werden kann, allein durch Vorgabe der entsprechenden Trainings-Klassifikationen. Wie bei den anderen Modellen wird damit noch einmal deutlich, dass in mm6
die "Sechs" nicht implizit vorweg genommen ist.
Das zeigt zum einen, dass selbst einfache Deep NNs das Potenzial haben, komplexere Aufgaben quasi baukastenartig abzubilden. Dass wir hier nur “Kinderkram”, also elementarste mathematischen Aufgaben, trainiert haben, tut dem Prinzip keinen Abbruch.
Allerdings sind die NNs sehr stark “konstruiert” durch den Entwickler — ob die Aufgaben auch in einem großen und tiefen generischen NN erlernbar sind, ist mir nicht klar. Das führt auf die Frage, ob und wie ein “KI-System” diese NNs konstruieren könnte: Ein ML-System, dass darauf trainiert werden kann (d.h. das lernt), ein NN zu generieren, das es z.B. auf Vielfache von 6 zu trainieren kann.
Im folgenden Teil 6 wollen wir untersuchen, in wie weit die Universal Approximation Eigenschaft von Deep NNs hier von praktischer Bedeutung sein kann.
Weiter lesen: 6 Machine Learning und die „Universelle Approximations-Eigenschaft“ Neuronaler Netze
bernhard.thomas@becketal.com
www.becketal.com