Bernd Thomas
Beck et al.
Published in
6 min readNov 15, 2019

--

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:

Abb. 1: Eine nicht-sequentiells Modell-Struktur für Vielfache von 6
  • 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 training
mm6 = 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'])
Abb. 2: Summary von mm6

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()
Abb. 3: Konvergenz mm6 — Loss
Abb. 4: Konvergenz mm6 — Accuracy

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

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