Bernd Thomas
Beck et al.
Published in
5 min readDec 3, 2019

--

6.1 Ein kubisches Polynom als Beispiel für die NN-Approximation nicht-linearer Funktionen

Dass man mit Neuronalen Netzen lineare Funktionen darstellen kann, ist klar. Es geht letztlich um eine lineare Regression, wobei die Punkte auf einer Geraden oder Ebene liegen. Wir verzichten daher hier auf Beispiele.

Interessanter sind Beispiele für Näherungen von nicht-linearen Funktionen, also solchen, die nicht durch eine Gerade, Ebene oder mehrdimensionale “Ebenen” dargestellt werden können. Ein Beispiel:

Abb. 1: Brendan Fortunes Beispiel

Dieses Beispiel stammt aus einem der ersten Blogs, auf die ich bei der Recherche nach Universal Approximation und Training von NN gestoßen bin.

In diesem Blog illustriert Brendan Fortune sehr verständlich den Versuch, für die kubische Funktion analytisch eine Approximation durch Geraden-Abschnitte herzuleiten und diese dann in ein NN mit 6 Knoten und angepassten relu-Ausdrücken darzustellen. Das gelingt - zumindest optisch - erstaunlich gut.

Die Frage ist dann, ob sich die Paramter (Gewichte) dieses Modells auf das Ergebnis hin trainieren lassen. Das gelingt tatsächlich, zumindest in der gleichen “optischen” Güte. Allerdings stellt man fest, dass das Training nicht etwa auf die vorher analytisch errechneten Gewichte führt, sondern auf einen anderen Satz an Gewichten.

Wir wollen dieses Beispiel einer trainierbaren Approximation mit keras hier nachbilden. (Im Original mit sklearn.)

Zunächst die erforderlichen Imports und die Generierung der Trainingsdaten. Um das geleiche Verfahren auch für andere Funktionen anwenden zu können, wird die kubische Funktion hier als Paramter einer Funktionsdaten-Generierungsroutine get_func_data() eingesetzt, die dann die x-Werte und zugehörigen y-Werte zwischen start und end in der Schrittweite step_size erzeugt. Die Grafik zeigt den Funktionsverlauf zwischen -2 und 2. (Ebenfalls eine Adaptation von BF's Verfahren.)

import numpy as np
import random as rd
import matplotlib.pyplot as plt
%matplotlib inline
from numpy import pi, sin
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import sgd, adam

# Generate Data
def cubic(x):
return x**3 + x**2 - x - 1
def get_func_data(func,start,end,step_size):
X = np.arange(start, end, step_size)
X.shape = (len(X),1)
y = np.array([func(X[i]) for i in range(len(X))])
y.shape = (len(y),1)
return X,y
func = cubicX,y = get_func_data(func,-2,2,0.01) #.025) # The range of x is relevant
print(len(X)) # obsolete
n_cases = X.shape[0]
print([(X[i],y[i]) for i in range(10)]) # obsolete
plt.plot(X,y,'b-')
plt.show()
Abb. 2: Plot des Funktionsgraphen zwischen -2 und 2

Das keras Modell hat entsprechend dem Originalmodell einen Hidden Layer mit 6 Neuronen und die Ausgabeschicht. Die trainierbaren Parameter sind je 6 kernel- und bias-Gewichte für den Hidden Layer, sowie 6 kernel- und ein bias-Gewichte für die Ausgabeschicht.

# Sequential model with keras: BF's example
# Using same structure and activation as in the cited blog
mf = Sequential()mf.add(Dense(6, activation='relu',input_shape=(1,)))
mf.add(Dense(1, activation='linear'))
mf.summary()sgd1 = sgd(lr= 0.001) # sgd with learning rate
mf.compile(optimizer=sgd1,loss='mean_squared_error',
metrics=['accuracy'])
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_7 (Dense) (None, 6) 12
_________________________________________________________________
dense_8 (Dense) (None, 1) 7
=================================================================
Total params: 19
Trainable params: 19
Non-trainable params: 0

Training und Plot der Konvergenz (loss).

# Training of mfne = 400          # Epochs
h_step = 20 # Loss output steps
bs = 5 # Batch size
hist1 = mf.fit(X,y,epochs=ne,batch_size=bs,verbose=0)print([hist1.history['loss'][i] for i in range(0,ne,h_step)])
mf.get_weights()
plt.plot(hist1.history['loss'],'g--')
plt.show()
Loss: [0.1560605631908402, 0.1340008360799402, 0.1146203305455856, 0.09794861453119666, 0.08439469043514691, 0.07248467828612774, 0.06280773456674069, 0.054902305599534885, 0.048457512055756526, 0.04330251654027961]Trained weigths:Hidden Layer:
[array([[ 1.7359983 , -1.519115 , 2.0113566 , -0.3391946 , -0.77214766, -0.63007206]], dtype=float32),
array([-1.1551014 , -1.6249542 , -2.5957189 , 0.429481 , 0.2673822 , 0.22130482], dtype=float32),
Output Layer:
array([[ 1.9923668],
[-2.1272972],
[ 3.304608 ],
[-0.576233 ],
[ 0.8549807],
[ 0.6584685]], dtype=float32),
array([-0.9877084], dtype=float32)]
Abb. 3: Konvergenz (loss)

Die Evaluation erfolgt hier mittels predict Methode des Modells mf. "Prediction" ist hier ein wenig irreführend, denn tatsächlich verwenden wir hier als "Testdaten" Werte, die schon im Trainings-Set verwendet wurden. Wir wollen aber gerade die Anpassung an die wahre Funktion sehen, die das trainierte Modell leistet.

# "Prediction" of function values by mfT = np.arange(-2,2,0.025)
y_pred = mf.predict(T)
# Compare with plot of true function
vfunc = np.vectorize(func)
y_true = vfunc(T)
# Plot both
plt.plot(T,y_pred,'r')
plt.plot(T,y_true,'g')
plt.show
Abb. 4: Die NN-Approximation (rot)

Das grafische Ergebnis sieht tatsächlich sehr gut aus. Für praktische Zwecke könnte man das trainierte NN im Bereich -1.0 bis 2.0 durchaus verwenden.

Wie sieht es aber tatsächlich mit “unbekannten” Daten aus. Wir erweitern dazu den Bereich der x-Werte auf -4.0 bis 4.0.

Abb. 5: NN-Ergebnis über [-2,2] hinaus

Nun ja — das war zu erwarten. Die Approximationseigenschaft beschränkt sich auf den Bereich der Daten aus dem vorgegebenen Bereich. Wir haben es hier mit einem Interpolationsverfahren zu tun, von denen die Numerische Mathematik eine Vielzahl kennt, hier aber mittels einem NN-Ansatz. Eine brauchbare Genauigkeit für die Extrapolation kann damit im allgemeinen nicht erwartet werden.

Dieses einfache Beispiel illustriert sehr schön die zwei Teile der Aufgabenstellung: (1) Ein NN, das die kubische Funktion im vorgegebenen Bereich näherungsweise berechnen kann, und, (2) dass es darauf trainiert werden kann.

Im Folgenden werden wir ein Reihe komplexerer Aufgaben mit NNs angehen. Als Erstes, den Versuch, einem NN das “Wurzelziehen” beizubringen.

Weiter lesen: 6.2 NN-Modelle lernen Wurzeln und Quadrate

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