Bernd Thomas
Beck et al.
Published in
24 min readMar 9, 2020

--

6.6 Boolesche Funktionen und Quanten-Gates — Neuronale Netze lernen Logik

pixabay.com

Logische Verknüpfungen ermöglichen einen anderen prinzipiellen Zugang zur “Universalität” von Neuronalen Netzen. NAND (Not And), zum Beispiel, ist eine logische Verknüpfung, die als Basis aller möglichen logischen Verknüpfungen verwendet werden kann (https://en.wikipedia.org/wiki/NAND_logic). D.h. man kann alle anderen elementaren Verknüpfungen wie NOT, AND, OR und alle komplexeren Verknüpfungen daraus aufbauen. Und da man jede berechenbare Funktion aus logischen Bausteinen aufbauen kann, würde uns ein Neuronales Netz zur “Berechnung” der NAND-Verknüpung prinzipiell ermöglichen, alle berechenbaren Funktionen durch Neuronale Netze zu realisieren — und zwar konstruktiv statt, wie in den vorausgehenden Abschnitten von Teil 6, nur approximativ oder als Interpolation.

Bevor wir uns damit näher befassen noch eine Vorbemerkung zum Sprachgebrauch:

  • In der Mathematik und Informatik spricht man von logischen oder Booleschen Funktionen. NAND ist eine Boolesche Funktion, die zwei logische Werte zu einem logischen Funktionswert verrechnet. Insgesamt gibt es 16 verschiedene zweistellige Boolsche Funktionen, die wir weiter unten in einer Tabelle darstellen.
  • In der Informationstechnik werden Boolesche Funktionen auch als Gatter (gate) bezeichnet, die zwei Eingabe-Bits zu einem Ausgabe-Bit verarbeiten. Gatter und Gatter-Konstrukte heißen dort auch Schaltung.
  • Die beiden logischen Werte werden in Programmiersprachen überlicherweise als True und False bezeichnet und im mathematischen und technischen Kontext durch 0 (False) und 1 (True) repräsentiert.

Ein NAND Gatter ist also insofern universell, als man alle logischen Schaltungen daraus aufbauen und daher auch alle berechenbaren Ausdrücke berechnen kann (z.B. über Halbleiter-Schaltkreise im Computer). NAND ist nicht die einzige Boolesche Funktion mit dieser Eigenschaft und außerdem gibt es Zwei-Gatter Basen, etwa (NOT, OR). Aber das nur am Rande.

Berechnen ist eine Sache — Berechnen Lernen eine andere, wie wir in den vorausgehenden Abschnitten gesehen haben. Wir wollen im Folgenden für eine Reihe von Aufgaben versuchen, möglichst einfache Neuronale Netze zu finden, die die logische Funktionen lernen können.

6.6.1 Ein einfaches Perzeptron für die NAND Funktion

Beginnen wir mit einer logischen Basisfunktion, NAND, und sehen dann, was man daraus aufbauen kann.

Die NAND-Funktion lässt sich mit einem einfachen Perzeptron mit zwei Inputs und einem Output berechnen. Wir realisieren dieses mit einem keras/Tensorflow Modell wie folgt:

# The import list for subsequent modelsimport numpy as np
import random as rd
import matplotlib.pyplot as plt
from keras.models import Sequential, Model
from keras.layers import Dense, Input, Concatenate, Activation
from keras.optimizers import sgd # stochastic gradient descent
from keras.initializers import Zeros, Ones, Constant, RandomUniform
import keras.backend as K
# Data for Training and Test
X = [[0,0],[1,0],[0,1],[1,1]]
y_nand = [1,1,1,0] # NAND function

Die Trainingsdaten beschränken sich auf die 4 Kombinationen von Eingabewerten und der zugehörigen Ausgabe je Input-Paar. Wir werden sehen, dass wir damit auskommen. Alternativ könnte man eine größere Menge an Trainingsdaten per Zufall aus diesen 4 Kombinationen auswählen, was aber keinen Unterschied macht. Das gleiche gilt für die Daten zur Überprüfung der Berechnungen (Testdaten).

# Prepare NAND model

X = np.array(X)
y = np.array(y_nand)

print(X.shape,y.shape) # Just to check dimensions
print(X,y)

def custom_sigmoid(x): # Option for activation
a = 5
b = 0.5
return 1/(1+K.exp(-a*(x-b)))

# act = 'linear' # Set activation function. Options.
act = custom_sigmoid

inp = Input(shape=(2,))

# --- NAND Box ----
out = Dense(1,activation=act,use_bias=True)(inp)
# -----------------

m_nand = Model(inp,out)

m_nand.summary()
m_nand.get_weights()

sgd1 = sgd(lr= 0.01) # sgd with learning rate
m_nand.compile(optimizer=sgd1,loss='mean_squared_error',metrics=['accuracy'])

Das Modell m_nand hat zwei Gewichte für den Input und den Bias-Wert, also 3 trainierbare Parameter, die initial per Default-Verfahren zufällig gesetzt werden. Als Aktivierungsfunktion können wir die Identität (linear) wählen oder eine selbst definierte sigmoide Funktion, hier mit custom_sigmoid bezeichnet, die wir weiter unten noch erklären. Das Modell-Summary:

_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_25 (InputLayer) (None, 2) 0
_________________________________________________________________
dense_58 (Dense) (None, 1) 3
=================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0

Das Training geht über 200 Epochen:

# Test Data equal training data

y_pred = m_nand.predict([X])
rnd_pred = np.round(y_pred)

print('Input true predict rounded')
for i in range(4):
print(X[i],y_nand[i],' ',y_pred[i],rnd_pred[i])
------------------------------Input true predict rounded
[0 0] 1 [0.98523927] [1.]
[1 0] 1 [0.8148756] [1.]
[0 1] 1 [0.8161825] [1.]
[1 1] 0 [0.22649397] [0.]

Das Perzeptron lernt NAND perfekt. Das war der einfache Teil.

Wenn man die Gewichte bei mehreren Trainingsläufen im trainierten Zustand ausgibt, wird man feststellen, dass sie variieren. D.h.das Perzeptron lernt NAND perfekt, aber mit unterschiedlichen “inneren” Zuständen. Das kann man leicht nachvollziehen, wenn man die drei Gewichte “theoretisch” bestimmt. So liefert z.B. -1/2, -1/2, bias=1.0 das richtige Ergebnis, aber auch -1, -1, 2 und viele andere Kombibationen. (Mathematisch ist das leicht erklärbar, wir wollen hier aber darauf verzichten.) Für spätere Verwendung speichern wir die Gewichte des trainierten NAND-Perzeptron in W_nand. Sie liegen (zufällig) nahe an dem ersten Set der “theoretischen” Werte.

W_nand = m_nand.get_weights()
print(W_nand[0][0],W_nand[0][1],W_nand[1])

# 0: kernel weights, 1: bias
[-0.5437819] [-0.54204446] [1.3401834]

Eine Anmerkung zur Aktivierungsfunktion: Das gleiche Ergebnis erhalten wir auch mit act='linear', allerdings sind die predict-Werte etwas gleichmäßiger über das Intervall [0,1] verteilt. Wenn diese bei komplexeren NAND-Gatter-Schaltungen (z.B. für XOR) als Input weitergeleitet werden, schaukeln sich die "Unschärfen" auf und wir verlieren Lernfähigkeit.

Die hier verwendete custom_sigmoid Funktion ist eine Modifikation der Starndard-Aktivierungsfunktion 'sigmoid'. Abb 1. zeigt beide im Bereich -4 bis +4.

Abb. 1: Sigmoid Aktivierungsfunktion: Standard (blau), Parameter 5.0, 0.5 (rot)

Der Parameter 5.0 (statt 1.0) macht den Anstieg steiler, der Wert 0.5 “verschiebt” die Kurve so, dass der steile Anstieg bei x=0.5 liegt. Sie kommt damit einer Sprungfunktion nahe, ist aber stetig differenzierbar und damit für gradienten-basiertes Backpropagating geeignet. (Mehr dazu in Abschnitt 6.7.) Gelegentlich, in einfachen Modellen, reicht auch ‘linear’ oder ‘relu’ aus; wir beschränken uns aber hier auf die Custom Aktivierung.

6.6.2 Universalität des NAND-Gatters — Konstruktion von NNs für andere Boolesche Funktionen

Andere logische Funktionen lassen sich aus NAND-Gattern zusammen setzen. Wir wollen sehen, ob sich das auf lernfähige NN übertragen lässt. Wir beschränken uns erst mal auf einfache Funktionen wie NOT, AND, OR — später noch XOR.

Hier (https://de.wikipedia.org/wiki/NAND-Gatter) findet man eine Übersicht, wie andere Funktionen aus NAND-Gattern konstruiert werden können. Etwa NOT(x), entspricht NAND(x,x).

Ein NN, das NOT unter Verwendung des NAND-Perzeptrons lernen soll, muss dazu zunächst aus einem Input x die zwei Inputs x0,x1 für das NAND (s.o. "NAND-Box") machen, wobei aber x0=x1=x sein muß. Auch dieser Schritt soll in den Lernprozess einbezogen werden! Ein ein zweischichtiges keras-Modell kann damit so aussehen:

# Model for NOT, using NAND: NOT(x) = NAND(x,x)

X = [[0],[1]] # Single-bit Input for NOT
y_not = [1,0] # NOT

...

act1, act2 = custom_sigmoid, custom_sigmoid
act1, act2 = 'linear', custom_sigmoid


inp = Input(shape=(1,))
# --- NOT box
x = Dense(2,activation=act1,use_bias=False)(inp) # x -> (x0,x1)
# .... --- NAND box ---
out = Dense(1,activation=act2,use_bias=True)(x)
# .... -----------------
m_not = Model(inp,out)
m_not.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_9 (InputLayer) (None, 1) 0
_________________________________________________________________
dense_29 (Dense) (None, 2) 2
_________________________________________________________________
dense_30 (Dense) (None, 1) 3
=================================================================
Total params: 5
Trainable params: 5
Non-trainable params: 0

Training und Test sind wir oben. Wir haben hierbei das Training von 200 auf 1000 Epochen erweiter. Das NN lernt zwar meist auch nach 200 Epochen das NOT zufrieden stellend, die predict-Werte nähern sich aber mit mehr Epochen besser an die Zielwerte 1.0 bzw. 0.0 an, d.h. die "Trennschärfe" verbessert sich. Experimentieren mit 'linear' vs custom_sigmoid zeigt leichte Vorteile für 'linear' bei der Input-Dopplung bzgl. der Trennschärfe; das ist aber generell nicht erhärtet.

Input true predict    rounded
[0] 1 [0.93018454] [1.]
[1] 0 [0.04279922] [0.]
Trainierte Gewichte
[[-1.4039009 0.43837917]]
[[ 0.54628617]
[-0.8496588 ]] [1.0179056]

Die theoretischen Gewichte für die erste Schicht wären naheliegend 1.0, 1.0 (ohne Bias), was einfach zu einer Verdoppelung des Inputs führen würde. Als Gesamtstruktur "handelt" das NN aber seine Gewichte nach eigenem Gutdünken aus, so scheint's. Am Ende zählt, was hinten rauskommt. Anders ausgedrückt, haben wir für das Lernen eine hinreichende Redundanz im NN, so dass eine Mannigfaltigkeit von Gewichte-Sets zum Ziel führt, je nachdem, mit welchen Initial-Gewichten das Training startet.

Transfer Learning bei zusammen gesetzten Booleschen Funktionen auf Basis eines trainierten NAND-Perzeptron ist naheliegend. Statt das ganze NOT-Modell in das Training einzubeziehen, kann man die “NAND-Box” oben im trainierten Zustand, d.h. mit den zuvor trainierten Gewichten W_nand, verwenden und vom Traiing ausnehmen. Beim NOT-Beispiel muß dabei trivialerweise nur noch der Schritt der Input-Verdoppelung "gelernt" werden. In keras geht das z.B. so:

inp = Input(shape=(1,))
# --- NOT box
x = Dense(2,activation=act1,use_bias=False)(inp) # x -> (x0,x1)
# .... --- Trained NAND box ---
out = Dense(1,activation=act2,kernel_initializer=Constant(W_nand[0]),
bias_initializer=Constant(W_nand[1]),trainable=False)(x)
# .... ----------------

m_not = Model(inp,out)
m_not.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_37 (InputLayer) (None, 1) 0
_________________________________________________________________
dense_80 (Dense) (None, 2) 2
_________________________________________________________________
dense_81 (Dense) (None, 1) 3
=================================================================
Total params: 5
Trainable params: 2
Non-trainable params: 3

Das Ergebnis nach Training über 1000 Epochen zeigt perfektes Lernen. Wie zu erwarten war, sind nur die Eingangs-Gewichte trainiert worden, die anderen bleiben wie nach dem NAND-Training zu Beginn unverändert.

Input true predict    rounded
[0] 1 [0.98523927] [1.]
[1] 0 [0.06675901] [0.]
[[0.6343519 1.8868363]]
[[-0.5437819 ]
[-0.54204446]] [1.3401834]

Wir geben noch die Beispiel für AND und OR.

AND(x0,x1) = NAND(NAND(x0,x1),NAND(x0,x1)), d.h. das äußere NAND ist ein NOT (s.o.) von NAND.

OR(x0,x1) = NAND(NAND(x0,x0),NAND(x1,x1)), d.h. NAND von NOT(x0) und NOT(x1)

Das AND-Modell soll zunächst ein NAND der beiden Inputs bilden (NAND-Box) und darauf das NOT wie oben anwenden (NOT-Box):

# Model for AND, using NAND: AND(x,y) = NAND(NAND(x,y),NAND(x,y))

X = [[0,0],[1,0],[0,1],[1,1]]
y_and = [0,0,0,1]
...#act1, act2 = custom_sigmoid, custom_sigmoid # Activation option
act1, act2 = 'linear', custom_sigmoid
act0 = custom_sigmoid

inp = Input(shape=(2,))
# --- AND box ---
# --- --- NAND box ---
x = Dense(1,activation=act0,use_bias=True)(inp)
# --- --- NOT box ---
x = Dense(2,activation=act1,use_bias=True)(x)
out = Dense(1,activation=act2,use_bias=True)(x)
# --------------------
m_and = Model(inp,out)
m_and.summary()
....----------------------------------------Layer (type) Output Shape Param #
=================================================================
dense_61 (Dense) (None, 2) 2
_________________________________________________________________
dense_62 (Dense) (None, 1) 3
=================================================================
Total params: 5
Trainable params: 5
Non-trainable params: 0

Nach Training ergeben die Test-Inputs

Input predict    rounded
[0 0] [0.02222845] [0.]
[1 0] [0.09018484] [0.]
[0 1] [0.04300707] [0.]
[1 1] [0.9086443] [1.]

Spätestens bei diesem Modell fällt auf, dass das NN zwar trainierbar ist, das richtige Ergebnis (AND-Funktion) in wiederholten Trainingsläufen aber nicht immer erreicht wird. Das einzige, was sich von Lauf zu Lauf dabei ändert, sind die Start-Gewichte. Das “Richtige Lernen” hängt von den Startwerten ab, was mathematisch gesehen nicht überraschend ist. Für das “Lernen Können” eines NN reicht es prinzipiell aber aus, dass es überhaupt Startwerte gibt, die das Training zu den gewünschten Ergebnissen führt. Zur Not kann man — z.B. experimentell — nach passenden “Lernvoraussetzungen” suchen. Wir kommen weiter unten noch einmal systematischer darauf zurück.

Das OR-Modell soll zunächst beiden Inputs getrennt umkehren (NOT) und dann beide Ergebnisse einem NAND-Gatter zuführen.

Abb. 2: OR-Schaltung aus NAND-Gates: NAND(NAND(A,A),NAND(B,B)

Da wir für die Modelle für NAND und NOT schon trainierte haben, könnten wir das OR-Gatter vollständig per “Transfer Learning” konstruieren (s. NOT, oben) und nur noch testweise auswerten. Wir wollen aber wieder das komplette NN trainieren, um zu sehen, ob auch komplexere Schaltungen von NN “gelernt” werden können.

# Model for OR, using NAND: OR(x,y) = NAND(NAND(x0,x0),NAND(x1,x1))
# Using 2 Input streams for keras

X0 = [0,0,1,1]
X1 = [0,1,0,1]
y_or = [0,1,1,1]
...act1, act2, act3 = 'linear', custom_sigmoid, custom_sigmoid

inp0 = Input(shape=(1,))
inp1 = Input(shape=(1,))

# --- OR box ----
# --- --- NOT box 1
x0 = Dense(2,activation=act1,use_bias=True)(inp0)
z0 = Dense(1,activation=act2,use_bias=True)(x0)
# --- --- NOT box 2
x1 = Dense(2,activation=act1,use_bias=True)(inp1)
z1 = Dense(1,activation=act2,use_bias=True)(x1)

z = concatenate([z0,z1])

# --- --- NAND Box ----
out = Dense(1,activation=act3,use_bias=True)(z)
# ---------------------

m_or = Model(inputs=[inp0,inp1], outputs=[out])

Das Modell hat insgesamt 17 trainierbare Gewichte. Man erkennt die Teil-Gates als NN-Strukturen von oben wieder. Das concatenatebringt die Ergebnisse aus den beiden Input-Strömen zusammen in das finale NAND und hat keine Parameter.

Zur Abwechslung erzeugen wir hier einmal Trainingsdaten als Zufallssequenz von 0 und 1 für die Input-Streams X0 und X1. Das erlaubt es, die Fehler-Robustheit des Modells zu testen, indem wir Fehler in die Trainingsdaten einschleusen.

# Random training data streams for OR Input

n_cases = 100

X0 = np.random.choice([0,1],size=(n_cases),p=[0.5,0.5])
X1 = np.random.choice([0,1],size=(n_cases),p=[0.5,0.5])
y_or = np.zeros(n_cases)
np.logical_or(X0,X1,out=y_or)
y_or = y_or.astype(int)

Die Ergebnisse mit den 4 Testdaten zeigen einen perfekten Lernzustand. Selbst die m_or.predict-Werte liegen hier sehr nahe an 1 bzw 0.

Input true predict    rounded
0 0 0 [0.05398915] [0.]
0 1 1 [0.999941] [1.]
1 0 1 [0.9666313] [1.]
1 1 1 [0.97089064] [1.]

Versuche mit fehlerhaften Trainingsdaten (1% — 5% Fehler) führten nicht zum erwarrteten Lernerfolg, d.h. die Robustheit des Modells konnte so nicht nachgewiesen werden.

6.6.3 Gibt es ein generisches Perzeptron-Modell für alle Booleschen Funktionen?

Die NN-Konstrukte in 6.6.2 für die einfachen Booleschen Funktionen NOT, AND und OR sind hinsichtlich Schichten und Neuronen recht aufwändig, die Zahl der trainierbaren Parameter hoch (17 bei OR). Der Aufbau aus NAND-Gattern als Basis-Funktion ist zwar prinzipiell möglich, aber nur als Aufbau aus trainierten Teilmodellen (Transfer Learning) praktikabel. Komplexere Boolesche Funktionen lassen sich kaum mehr vollständig trainieren.

Stattdessen verlassen wir uns im Folgenden ganz auf die Fähigkeit generischer Neuronaler Netze, d.h. solche, die nicht einen bestimmten Aufbau nachbilden wollen.

Das einfachste Modell ist das Perzeptron, wie es in 6.6.1 für die NAND-Funktion engesetzt wurde. Die erste Frage lautet: Kann man damit alle einfachen, zweistelligen Funktionen trainieren? Die Tabelle zeigt eine Übersicht aller zweistelligen Booleschen Funktionen:

Abb. 3: Die 16 Booleschen Funktionen mit zwei Argumenten

Zunächst werden alle 16 Funktionen als Trainingsdaten generiert:

# Generate all 16 possible functions of two boolean variablesX = [[0,0],[1,0],[0,1],[1,1]]   # Input all 2-bit possibilities for training and test# Generate y for all functions
Y = []
for i in range(16):
y = [int(i) for i in str(bin(i))[2:].zfill(4)]
Y.append(y)
#print(y)

Wir haben schon in 6.6.2 fest gestellt, dass das Training einer Booleschen Funktion (wie das aus NAND-Gates aufgebaute AND-Modell) nicht immer gelingt. Der Trainingserfolg ist abhängig von den initialen Werten für die Gewichte. Daher wiederholen wir für jede der 16 Funktionen das Training des einfachen Perzeptrons gleich 10 mal, und werten aus, wie oft das Training erfolgreich ist. Die Trainings gehen jeweils über 1000 Epochen mit unterschiedlichen, zufällig erzeugten Anfangswerten für die 3 Gewichte.

# Repeated runs per Boole function and statistics

# Custom Activation function ...
# All 2-bit Inputs ...
# Generate y for all functions ...

X = np.array(X)

n_runs = 10 # Number of trainings per function
acc_lst = [] # Accurate results list

for y_func in Y:

y = np.array(y_func)

count = 0
for k in range(n_runs):
# Model definition - simple perceptron
inp = Input(shape=(2,))
out = Dense(1,activation=custom_sigmoid,use_bias=True)(inp)

delete later #x = Dense(2,activation='linear',use_bias=True)(inp)
dto #out = Dense(1,activation=custom_sigmoid,use_bias=True)(x)

m2 = Model(inp,out)
sgd1 = sgd(lr= 0.01)
m2.compile(optimizer=sgd1,loss='mean_squared_error',metrics=['accuracy'])

# Train model ...

# Test Data same as training data
y_pred = m2.predict([X])
rnd_pred = np.round(y_pred)
y_calc = [int(rnd_pred[_,0]) for _ in range(4)]
y_true = y.tolist()
count = count+1 if y_calc == y_true else count

acc_lst.append(count/n_runs)

print(acc_lst)
--------------------------------------------Ergebnis:
[1.0, 0.4, 0.6, 0.8, 0.7, 0.7, 0.0, 0.9, 1.0, 0.0, 0.9, 0.9, 1.0, 0.9, 0.7, 1.0]

Das pyplot-Histogramm (Code hier weg gelassen) zeigt die Häufigkeiten erfolgreicher Trainings je Funktion:

Abb. 4: Histogramm erfolgreicher Trainings

Offensichtlich kann für alle bis auf zwei Funktionen das Perzeptron erfolgreich trainiert werden! Die zwei Ausnahmen sind das XOR (#6, 0110) und das NOT XOR, was der Äquivalenz (EQUIV,#9, 1001)entspricht.

Kein Erfolg bei 10 Versuchen beweist natürlich nicht, dass das Perzeptron als einfachstes NN XOR und EQUIV nicht lernen kann. Man kann aber mathematisch sehr einfach beweisen, dass das tatsächlich nicht geht. Wir können das anhand einer Grafik deutliche machen.

Höhenlinien des trainierten Perzeptrons

Wenn wir mittels predict eines trainiereten Perzeptrons y-Werte nicht nur für die Trainingspaare (x1,x0) berechnen sondern über das ganze Quadrat, das durch die Paare bestimmt wird, dann erhalten wir eine Fläche im 3-dim Raum, die an den Ecken die logischen Funktionswerte (in Näherung) einnimmt. Für die Funktion #13, 1101 (die Implikation: "wenn x0, dann x1") liefert predict Werte nahe 1 für die Ecken (0,0), (0,1), (1,1) und nahe 0 für (1,0).

Mit der contour-Funktion aus der matplotlib.cm können wir Höhenlinien dieser Fläche darstellen:

# Evaluation of trained NNs over [0,1]x[0,1] as Contour Maps

import matplotlib.cm as cm # contour maps
import matplotlib.pyplot as plt
i_func = 13 # IMPL x0 -> x1
delta = 0.025
steps = 41

a = np.arange(0.0, 1.0+delta, delta)
b = np.arange(0.0, 1.0+delta, delta)
A,B = np.meshgrid(a,b)
C = [[u,v] for u in a for v in b]

C = np.array(C)

Z = m1.predict(C)
Z = np.reshape(Z,(steps,steps))

fig, ax = plt.subplots()
CS = ax.contour(A, B, Z)
ax.clabel(CS, inline=1, fontsize=10)
ax.set_title('model '+ str(i_func))
plt.show()

Für eines der auf IMPL trainierten Perzeptrons sieht das Höhenlinien-Bild so aus

Abb. 5: Höhenlinien eines für IMPL trainierten einfachen Perzeptrons

Man sieht, dass die drei Ecken “oberhalb” der 0.9 Höhenlinie (gelb) liegen, die Ecke (1,0) "unterhalb" von 0.1. Wie bei einer Klassifikationsaufgabe werden hier die Paare, die (gerundet) 1 liefern, klar getrennt von dem Paar, das (gerundet) 0 liefert.

Erfolgreiches Training einer Funktion bedeutet immer, dass die Punkte, die 1 ergeben, durch Höhenlinien trennbar sind von denen, die 0 liefern. Für das Perzeptron verlaufen die Höhenlinien als parallele Geraden monoton ansteigend. D.h egal, wie sie liegen, können sie nicht Paare von diagonal gegenüber liegenden Eckepunkten trennen, also etwa für XOR die Punkte (1,0), (0,1)von (0,0), (1,1). Das gleiche gilt für EQUIV. Für alle anderen Booleschen Funktionen sind die Ecken entsprechend trennbar.

Wir versuchen daher andere einfache Möglichkeiten, XOR in einem NN zu trainieren.

6.6.4 Neuronale Netze zum Lernen von XOR

Wir untersuchen folgende Ansätze:

  • Eine andere Aktivierungsfunktion
  • Aufbau aus NAND-Gattern
  • Ein zweischichtiges Perzeptron
  • Eine Erweiterung des einfachen Perzeptrons auf 4 Input-Neuronen

Das Perzeptron mit sin² als Aktivierungsfunktion

Betrachtet man die Input-Paare als Summanden einer Gerade/Ungerade-Aufgabe wie in Teil 1, so liefern die diagoal gegenüber liegenden Paare für XOR und EQUIV als Summe eine gerade Zahl, die anderen eine ungerade. In Teil 1 hatten wir gerade und ungerade Zahlen mit einer sin^2 Funktion getrennt. Wir setzen den gleichen Trick hier ein, mit sin(x*pi/2)**2 als Aktivierungsfunktion:

# Apply sin**2 as activation function for a simple perceptron modeldef sin2(x):
return K.sin(x*np.pi/2)
act = sin2# Model definition
inp = Input(shape=(2,))
out = Dense(1,activation=act,use_bias=True)(inp)
m2 = Model(inp,out)# Training ...
# Test ...
# Prediction for [0,1]x[0,1] and countour map ...

In diesem Fall klappt das Training von XOR auf Anhieb. Das erklärt das Höhelinienbild:

Abb. 6: Höhenlinien des XOR trainierten Perzeptrons mit sin² Aktivierung

Man sieht, die “XOR-true-Ecken” (0,1),(1,0) beide oberhalb der 0.9-Höhenlinien (gelb) liegen, die beiden anderen unterhalb 0.15. Das Training von XOR gelingt hier, weil die gewählte Aktivierungsfunktion über dem Quadrat nicht monoton ist, sondern entlang der Diagonalen von (0,0),(1,1) eine Senke hat.

Wir können diese Lösung allerdings nicht akzeptieren, weil wir, ähnlich wie in Teil 1, die Separierung für XOR — und analog EQUIV — bereits mit der besonderen Aktivierungsunktion vorgegebn haben. Lernen ist hier also ge-faked! Das war’s also noch nicht.

XOR Modell konstruiert aus NAND-Gates

Das Schaltdiagramm für XOR aufgebaut aus NANDs sieht so aus:

Abb. 7: XOR-Schaltung aufgebaut au NAND-Gattern

Wie in 6.6.2 oben können wir diese Struktur als NN in keras nachbilden:

# XOR Model from NAND Gates

X1 = [0,1,0,1] # A in diagram
X0 = [0,0,1,1] # B in diagram
y_xor = [0,1,1,0]

# ....

X0 = np.array(X0)
X1 = np.array(X1)
y = np.array(y)
act3 = custom_sigmoid
act1, act2 = act3
# some other combinations of act-functions work as well

inp0 = Input(shape=(1,))
inp1 = Input(shape=(1,))

# --- XOR Box ----

inp = concatenate([inp0,inp1])
# --- --- NAND Box 0 ----
z = Dense(1,activation=act3,use_bias=True)(inp)

x0 = concatenate([inp0,z])
x1 = concatenate([inp1,z])

# --- --- NAND Box 1.0 ----
z0 = Dense(1,activation=act3,use_bias=True)(x0)
# --- --- NAND Box 1.1 ----
z1 = Dense(1,activation=act3,use_bias=True)(x1)

x = concatenate([z0,z1])

# --- NAND Box 2 ----
out = Dense(1,activation=act3,use_bias=True)(x)

m = Model(inputs=[inp0,inp1], outputs=[out])
m.summary()

# ... Compile and fit

Wir wollen sehen, ob sich das Modell komplett trainieren lässt, also ohne Transfer Learning durch vortrainierte NAND-Gatter. Entsprechend hat das NN trainierbare Parameter nur in den 4 Dense-Schichten, also insgesamt 12. Die Aktivierungsfunktionen sind hier alle mit custom_sigmoid definiert. Das Training mit model.fit() erfolgt wie oben.

Ergebnis: Das Modell lernt das XOR. Nicht bei jedem Trainingsversuch, aber abhängig von den zufällig gewählten Start-Gewichten bei etwa 3 von 4 Versuchen. Ein typisches Ergebnis:

Input true predict    rounded
(0, 0) 0 [0.10258119] [0.]
(0, 1) 1 [0.86286163] [1.]
(1, 0) 1 [0.8648356 ] [1.]
(1, 1) 0 [0.13730808] [0.]

Ein zweischichtiges Perzeptron

Im Vergleich zu dem einfachen, “flachen” Perzeptron, mit dem XOR (und EQUIV) nicht gelernt werden können, hat das NAND-Modell bereits 12 anpassbare Paramter. Das ist schon eine Menge für die nur 4 Input-Output-Kombinationen von XOR.

Es ist einfach zu beweisen (s. [1] Ziegenbalg: Algorithmen), dass man XOR mit einer einfachen Erweiterung des Perzeptrons berechnen kann, durch Hinzunahme einer hidden Schicht mit 2 Neuronen. Aber kann dieses zweischichtige Perzeptron XOR auch lernen?

Das keras-Modell ist eine einfache Erweiterung des Ausgangsmodells:

# XOR and EQUIV training with 2-layer perceptron

X = [[0,0],[1,0],[0,1],[1,1]]
y_eqv = [1,0,0,1] # NOT XOR (EQUIV)
y_xor = [0,1,1,0] # XOR


# ...

n_nodes = 2 # 2-neuron hidden layer
act1, act2 = custom_sigmoid, custom_sigmoid
act1, act2 = 'relu', 'relu' # also works with relu

# Model definition
inp = Input(shape=(2,))
# --- Hidden layer
x = Dense(n_nodes,activation=act1,use_bias=True)(inp)
# --- Output layer
out = Dense(1,activation=act2,use_bias=True)(x)

m2 = Model(inp,out)
sgd1 = sgd(lr= 0.01)
m2.compile(optimizer=sgd1,loss='mean_squared_error',metrics=['accuracy'])

m2.summary()
m2.get_weights()

Das Modell hat 6 trainierbare Gewichte in der ersten Dense-Schicht (4 Kernel, 2 Bias) und 3 in der Output-Schicht, also ingesamt 9. Alternativ zu custom_sigmoid versuchen wir es auch mit der relu-Aktivierung.

Ergebnis: Die ersten Trainingsläufe waren — zufällig — nicht erfolgreich. Erst beim 5. Lauf für XOR und dem 8. bei EQUIV konvergierte das Training zu Gewichte-Sets, mit denen das Modell XOR bzw. EQUIV richtig berechnen konnte. Da die Startgewichte unkontrolliert zufällig gewählt wurden, zeigt dies, dass das Trainung zum richtigen Ergebnis für einige Startwerte klappt, für einige aber nicht.

Die Contour Map für das auf EQUIV trainierte zweischichtige NN zeigt die Separierung der Ecken, für die y=1 sein muß ((0,0), (1,1)).

Abb. 8: Höhenlinien eines für EQUIV trainierten zweischichtigen Perzeptrons

Die vierte Möglichkeit ist ein Modell, mit dem gleich alle 16 Booleschen Funktionen trainiert werden können.

6.6.5 Ein NN für alle 16 Booleschen Funktionen

Anders als bei der spezifischen Konstruktion von Neuronalen Netzen logischer Funktionen aus z.B. NAND-Komponenten, können wir uns wieder fragen, ob man ein und dasselbe NN für alle 16 zweistelligen Booleschen Funktionen trainieren kann. Was bedeutet das?

D.h., eine NN-Struktur reicht zum Training jeder der 16 Funktionen aus, wenn man zu den Trainingsdaten jeder dieser 16 logischen Funktionen passende Start-Gewichte-Sets findet, mit denen ein fit-Verfahren gegen die Vorgabefunktion konvergiert. Das Trainingsergebnis hängt also ab von den Trainingsdaten - trivialerweise - und den gewählten Anfangsgewichten. Umgekehrt, nicht alle Anfangsgewichte führen beim Training einer Funktion zum Ergebnis.

In den Versuchen, z.B. mit dem einfachen Perzeptron oben, lassen wir Anfangswerte zufällig durch das Defaultverfahren von keras auswählen. (Default Initializer ist Glorot-Uniform. Werte werden gleichverteilt innerhalb bestimmter Grenzen ausgewählt, die durch den Gewicht-Tensor bestimmt werden.) Bei den 10fach Wiederholungen zeigte das Häufigkeitsdiagramm, dass, mit Ausnahme von XOR und EQUIV, manchmal die Startgewichte zum Erfolg führten, manchmal nicht. Allein für XOR und EQUIV war das Training immer erfolglos.

Mit dem Modell mit zwei Neuronen im Hidden Layer konnten wir zeigen, dass es ein NN gibt, mit dem sich XOR und EQUIV trainieren lassen. Man könnte also erwarten, dass mit diesem NN alle einfachen Booleschen Funktionen trainierbar sind.

Das ist auch tatsächlich der Fall, wie eine Wiederholung des 10fach-Versuche mit dem zweischichtigen Perzeptron zeigt.

Weiterhin kann man bei einzelnen erfolgreichen Trainingsversuchen die Gewichte extrahieren (speichern)

  • am Ende des Trainings, um ein trainiertes NN zu bekommen, z.B. für Transfer Learning,
  • vor dem Training, um ein trainierbares NN zu bekommen

Etwas mehr Komplexität, und nach Testerfahrung “leichtere” Trainierbarkeit, kann man durch Erhöhung der Neuronenzahl im Hidden Layer erzielen, z.B. durch n_nodes=4.

Mit dem Modell mit 4 Neuronen im Hidden Layer haben wir in der ersten Dense-Schicht 12 (8 Kernel, 4 Bias) Parameter und 4 + 1 in der Output-Schicht, also ingesamt 17.

XOR und EQUIV lassen sich hiermit erwartungsgemäß perfekt trainieren. Interessant ist hier wieder die Contour Map für ein XOR-trainiertes Modell. Die Abbildung zeigt zwei Beispiele mit völlig unterschiedlicher Topologie (als 3-dim Fläche) aber gleicher Trenneigenschaft für XOR. Im Gegensatz zu den Contour Maps bisher, haben wir hier nicht-lineare Höhenlinienverläufe!

Abb. 9a: Höhenlinien für ein XOR-trainiertes NN mit n_nodes = 4
Abb. 9b: Höhenlinien für ein weiteres XOR-trainiertes NN mit n_nodes = 4

Der 10-fach-Wiederholung Trainingsversuch mit allen 16 Funktonen zeigt eine deutlich bessere Trainierbarkeit komplexeren Modells als beim einfachen Perzeptron oben, im Sinne einer (vermutlich) geringeren “Sensitivität” gegenüber der Wahl von Startgewichten.

Abb. 10: Histogramm erfolgreicher Trainings

Die Suche nach einem gemeinsamen Start-Gewichte Set

Der Lernerfolg für die elementaren Booleschen Fuktionen hängt offensichtlich von den Ausgangsbedingungen ab, d.h. von der Vorgabe der Startgewichte. Das Diagramm für das 4-Neuronen Modell zeigt zwar durchgängig hohe Training Success Häufigkeiten an, allerdings beginnt jeder Lauf mit anderen Startgewichten.

Daraus ergibt sich eine interesssante Frage. Können wir für das Modell eine Ausgangsbedinung finden, aus der heraus alle Booleschen Funktionen gelernt werden können?

Das Skript für diesen Versuch ist einfach zu erstellen, daher verzichten wir hier auf die Wiedergabe. Algorithmisch geschieht darin Folgendes:

  • Im Kern wird die Modellstruktur spezifiziert, wie oben, compiliert, trainiert und getestet.
  • Das Training erfolgt im Durchlauf mit allen 16 Booleschen Funktionen, ebenso die Überprüfung der predictions des trainierten Modells
  • Die Start-Gewichte werden bei der ersten Funktion erzeugt (systematisch oder zufällig per Default-Verfahren) und für alle weiteren wieder als Start-Gewichte gesetzt.
  • Sobald eine Funktion nicht erfolgreich trainiert wurde, bricht der Durchlauf ab und startet neu mit einem anderen Gewichte-Set.
  • Sofern alle Funktionen erfolgreich trainiert wurden, brechen wir den Versuch ab; wir haben ein Startgewichte-Set gefunden, das die Forderung erfüllt.

Mit diesem Start-Gewichte Set könnte das Modell jederzeit neu für alle Funktionen trainiert werden. Die trainierten Gewichte sind natürlich wieder funktions-spezifisch und können verwendet werden, um die jeweilige Funktion zu berechnen.

Mit dem 4-Neuronen-Modell finden wir schon mit den ersten Versuchen ein gemeinsames Start-Gewichte Set:

# n_nodes = 4 - Commonn initial weight tensor
[array([[-0.19096589, -0.4954269 , 0.03290558, 0.58038425],
[-0.18266845, -0.7887025 , 0.66017103, 0.23436832]],
dtype=float32), array([0., 0., 0., 0.], dtype=float32), array([[-0.00408578],
[ 0.8408011 ],
[-0.8000294 ],
[ 0.94862294]], dtype=float32), array([0.], dtype=float32)]

Wie sieht es dagegen mit dem 2-Neuronen Modell aus? Auch hier finden wir - nach mehreren Versuchen (hier beim 9. Versuch) - ein Start-Gewichte Set, von dem aus alle Booleschen Funtionen erlernt werden können.

# n_nodes = 2 - Common initial weight tensor
[array([[-0.07312226, 0.5754278 ],
[ 0.9883114 , -0.0028733 ]], dtype=float32), array([0., 0.], dtype=float32), array([[0.69965327],
[0.13531089]], dtype=float32), array([0.], dtype=float32)]

Ohne Beweis entsteht der Eindruck, dass beim "kleinen" Modell der Raum der möglichen Gewichte-Sets zwar deutlich kleiner ist als bei großen (6 Dimensionen vs. 17), passende Start-Bedingungen aber schwerer zu finden sind.

Die gemeinsamen Start-Gewichte Sets sind genau so wenig eindeutig wie die trainierten Sets, egal für welche Funktion.

Darstellung der trainierten Gewichte in Relation zu den Start-Gewichten Die Gewichte-Tensoren als Punkte im - immerhin- 9-dim Raum (bei n_nodes = 2) darzustellen und die Trainingskurven dazu, ist natürlich nicht möglich. Wir können aber jeweils Paare von Gewichten als Punkte einer Ebene darstellen. Die folgenden Grafiken zeigen Lage der Kernel-Gewichte von Startwerten (rot) und den 16 trainierten Gewichtstensoren (grün, blau). Die Koordinaten der Punkte sind jeweils die Gewichte der zwei eingehenden "Signale" in einen Knoten. Die ersten beiden zeigen also die Eingangsgewichte in Neuron 0 bzw. 1 des Hidden Layers und die dritte Grafik zeigt die Eingangsgewichte des Output-Neurons. Die Bias Parameter sind nicht dargestellt; bei der Inititalisierung sind diese Null.

Abb. 11a: Initiale Kernel-Parameter (rot) und trainierte Werte (grün, blau) für den Hidden Layer (n_nodes = 2)
Abb. 11b: Initiale Kernel-Parameter (rot) und trainierte Werte (grün) für den Output Layer

Es ist bemerkenswert, dass hier 16 verschiedene Funktionen auf 6 Paramterwerte (3x2 Kernel-Gewichte; die Biaswerte sind 0) “komprimiert” werden können. Jede Funktion kann “entwickelt” werden, indem man die 6 Werte vorgibt und die feste NN-Struktur mit diesen trainiert.

6.6.6 Quanten-Gates

Ausgehend vom einfachen 2-schichtigen NN (mit 2 bzw. 4 Neuronen im Hidden Layer) zum Training der grundlegenden Logik-Gattern können wir auch komplexere logische Funktionen trainieren.

Die einfachen Booleschen Funktionen haben 2 Argumente (bits) als Input und einen Wert als Output. Komplexere Funktionen können mehrere Inputs und auch mehrere Outputs haben.

Beispiel: 2x2 Mustererkennung

Mit 4 Inputvariablen kann man z.B. einfache Schwarz-Weiß-Muster bereits mit dem einfachen 2-schichtigen Perzeptron mit 4 Neuronen im Hidden Layer gut trainieren. 4 Inputvariablen können insgeamt 2⁴=16 Kombinationen annehmen. Eine zu trainierende Funktion ordnet einem oder einigen dieser Kombinationen der Ausgabewert 1, den anderen den Wert 0 zu. Beispiel: die “Funktion”, die die Diagonalfelder in einem 2x2-Quadrat markiert, ordnet den Inputs 1001 und 0110 den Werte 1, allen anderen den Wert 0 zu. Das obige NN mit 4 Neuronen in der ersten Schicht lässt sich perfekt darauf trainieren. (Das keras-Modell ist bereits oben dargestellt, Training und Test ebenso, lediglich die Input-Daten sind dabei 4-stellig von 0000 bis 1111.)

Als Beispiele für logische Funktionen (Gates) mit n Inputs und Outputs schauen wir uns einige Gatter an, die in der Quanten-Algorithmik verwendet werden.

Quantenbits (Qubits) unterscheiden sich in Defintion und Verwendung zwar sehr von normalen Bits (die wir immer als logische Variablen interpretieren können), ihre “Startwerte” (Inputs) und Ergebniswerte (Outputs, aus sogenannten Messprozessen) kann man aber wieder als klassiches Bits (Wahrheitswerte) verstehen. Lediglich “zwischen” Initialisierung und Messung nehmen Qubits aufgrund von Transformationen Zustände ein, die nicht als logische Werte interpretierbar sind. Dennoch ist der Effekt einiger Transformationen so, dass sie wie logische Funktionen wirken. Der Input besteht aus einem oder mehreren Bits, der Output (per Messung) ebenfalls aus mehreren Bits.

Wir wollen sehen, ob und wie sich welche Transformationen (Quanten-Gates) trainieren lassen. Darunter u.a. die Gates

  • CNOT — das controlled NOT Gate: CNOT(a;b): wenn a==1 dann b -> NOT(b), sonst b unverändert
  • X — das switch values Gate: X(a): a -> NOT(a)
  • SWAP — das Vertausche Gate: SWAP(a,b): a,b -> b,a
  • TOFOLI — das von 2 Qubits controlled NOT Gate: TF(a,b;c): wenn a==1 und b==1 dann c -> NOT(c), sonst c unverändert
  • FREDKIN (CSWAP)- das controlled SWAP Gate: FR(a;b,c): wenn a==1 dann SWAP(b,c), sonst unverändert

(NOT bezeichnet das einfache, Boolesche NOT.)

Wir setzen die import-Anweisungen wie in 6.6.1 voraus. Analog zu den Trainings der einfachen Booleschen Funktionen definieren wir im Script die möglichen Inputs (X_2 für 2-Bit-Inputs, X_3 für 3-Bit Inputs etc.) und die je nach Gate-Typ zugeordneten Outputs (2- oder 3-Bit-Kombinationen): y_CN für das CNOT Gate, y_X für das X Gate usw.

Die Struktur des NN ist nun anders zu definieren (in keras) als bei den einfachen 2-schichtigen Perzeptrons (s.u.). Auf jeden Fall muß Input-Size Gate-spezifisch angepasst werden und der Output Layer muß ebenfalls die entsprechende Output-Dimension haben.

Für den Output-Layer setzen wir daher die Zahl der Neuronen gleich der Output-Dimension, den Hidden Layer halten wir mal flexibel und versuchen es mit unterschiedlichen Anzahlen von Neuronen (z.B. 2,3,4 oder 6). Die folgenden Code-Zeilen definieren die verschiedenen Qubit-Funktionen, wobei im Kommentar jeweils die Wirkung der Funktion noch einmal beschrieben ist.

X_2 = [[0,0],[1,0],[0,1],[1,1]]

# CNOT: CNOT(a,b) - a==0 > leave b as is, a==1 > switch b
y_CN = [[0,0],[1,1],[0,1],[1,0]]

# X: X(a) - a==0 > a=1, b unchanged, a==1 > a=0, b unchanged
y_X = [[0,1],[1,0],[0,0],[1,1]]

# SWAP: SWAP(a,b) - a,b = b,a
y_SW = [[0,0],[0,1],[1,0],[1,1]]

# Tofoli(a,b;c) - a==0 or b==0 > c unchanged, else switch c
X_3 = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]
y_Tf = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,1],[1,1,0]]


# Fredkin(a;b,c) - a==0 > b,c unchanged, a==1 > b,c = c,b
X_3 = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]
y_Fr = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,1,0],[1,0,1],[1,1,1]]

Das NN-Modell kann in keras so formuliert werden:

# Model for n-qubit gates

act1,act2 = custom_sigmoid, custom_sigmoid

in_shape = 2
out_shape = 2
n_hidden = 4

inp = Input(shape=(in_shape,))

x = Dense(n_hidden,activation=act1,use_bias=True)(inp)
out = Dense(out_shape,activation=act2,use_bias=True)(x)

m1 = Model(inp,out)

m1.summary()

sgd1 = sgd(lr= 0.01) # sgd with learning rate
m1.compile(optimizer=sgd1,loss='mean_squared_error',metrics=['accuracy'])

Mit in_shape=2 und out_shape=2 könnte das Modell z.B. für CNOT trainiert werden. Ebenso für SWAP und X, obwohl bei X das zweite Qubit keine Rolle spielt. Die Gewichte-Initialisierung erfolgt zufällig, per Default-Verfahren. Bias-Werte werden verwendet. Der Hidden Layer hat hier 4 Neuronen. Damit ergibt die Modell-Summary:

_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_9 (InputLayer) (None, 2) 0
_________________________________________________________________
dense_17 (Dense) (None, 4) 12
_________________________________________________________________
dense_18 (Dense) (None, 2) 10
=================================================================
Total params: 22
Trainable params: 22
Non-trainable params: 0

Für Modellvarianten bzgl. Input-/Output-Shape und unterschiedlicher Neuronenzahl im Hidden Layer ergeben sich entsprechend Modell-Summaries als

  • input-shape * Neuronen * 3, für den Hidden Layer, plus
  • Neuronen Hidden Layer * output-shape + output-shape, für den Output Layer

Also etwa im Falle des TOFOLI Gates mit 4 Neuronen in der ersten Schicht: 3*4*3 + (4+1)*3 = 16+15 = 31 Gewichte-Parameter.

Training mit m1.fit und Überprüfung mit m1.predict erfolgt mit den entsprechenden Code-Snippets wie zuvor.

Ergebnisse

Die Quanten-Gates lassen sich alle mit dieser NN-Modellstruktur perfekt trainieren, zumindest für geeignete Start-Gewichte Sets. Das war zu erwarten. Je nach (Zufalls-)Wahl der initialen Gewichte konvergiert das Training gegen die richtigen Werte oder nicht. Generell scheint es, dass erfolgreiches Training bei größerer Anzahl Neuronen in der ersten Schicht “leichter” fällt.

Die Versuche erfolgten mit unterschiedlicher Ausstattung des Hidden Layers: mit 2, 3, 4, bzw.6 Neuronen. Die folgende Tabelle gibt eine Übersicht über die Trainingsergebnisse.

Abb. 12: Trainierbarkeit qubit-Gates

Legende: “OK” bedeutet, dass das Training “leicht” war, d.h. ohne lange Wiederholungen erfolgreich.”ok”: es waren mehrere Wiederholungen nötig, ca. 5–10. “nok”: Im Laufe der Wiederholungen ergab sich kein erfolgreiches Training. “ — “: Mit dieser Neuronenzahl nicht getestet.

Anmerkungen:

  • Ohne trainierbaren Bias-Parameter waren bis auf wenige Ausnahmen (CNOT mit 4) alle Tests erfolglos.
  • In den Fällen von “nok” kann nicht geschlossen werden, dass es grundsätzlich nicht möglich ist, das Gate mit dieser Struktur zu trainieren. Lediglich, in wiederholten Läufen gab es kein zufälliges Start-Gewichte-Set, das zur richtigen Konvergenz führte.

Universalität der Booleschen und Quanten Gates

Boolesche Basis-Gatter sind solche, aus denen sich Schaltungen jeder Art aufbauen lassen, so dass jede berechenbare Funktion damit berechnet werden kann. Dazu gehört z.B. das NAND-Gatter.

Auch für die Quanten-Gatter Strukturen lässt sich Entsprechendes nachweisen. Das TOFOLI Gate ist ein Beispiel.

Da wir alle diese Basis-Gatter (Boolesche oder Quanten-Gates) durch NN-Modelle realisieren können (sogar durch recht einfache), folgt daraus auch eine Universalitätseigenschft der Neuronalen Netze. Die Gatter können sogar trainiert werden! Komplexere Funktionen können damit aus vortrainierten, einfachen Gattern aufgebaut werden. Das sagt allerdings noch nichts über die praktische Trainierbarkeit von NN-Modellen für komplexere Funktionen als Gesamt-Trainingsziel aus.

Man kann versuchen, komplexere Funktionen aus elementaren Gates oder Basis-Gates als “strukturiertes” NN aufzbauen und zu trainieren (wie in 6.6.1 und 6.6.2) oder , alternativ, ein generisches zwei- oder mehrschichtiges NN zu trainieren. Wir werden das gelegentlich in einem Bonus-Track untersuchen.

Weiter lesen: 6.7 Neuronale Netzt zum Lernen von Treppenfunktionen

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