In Kapitel 5 von PRML werden die kürzlich populären neuronalen Netze vorgestellt. Es gibt viele Arten von Dingen, bei denen ich versucht habe, ein neuronales Netzwerk im Netz zu implementieren. Deshalb wollte ich etwas verwenden, mit dem ich nicht so gut wie möglich vertraut war. Deshalb habe ich beschlossen, ein Netzwerk mit gemischter Dichte fast ausschließlich mit Numpy zu implementieren. Da die Codemenge jedoch ziemlich groß geworden ist, werde ich sie in zwei Teile teilen, und in diesem Artikel werde ich schließlich ein normales neuronales Netzwerk implementieren und das nächste Mal das Netzwerk mit gemischter Dichte ausführen.
Dies bedeutet, dass die Eingabeeinheit 3D $ {\ bf x} = (x_1, x_2, x_3) $ ist, die verborgene Einheit 4D $ {\ bf z} = (z_1, z_2, z_3, z_4) $ und die Ausgabeeinheit 2 ist. Dies ist eine schematische Darstellung eines zweischichtigen neuronalen Netzwerks mit den Dimensionen $ {\ bf z} = (z_1, z_2)
Die Vorwärtsausbreitung ist der Schritt der Berechnung der Ausgabe des Netzwerks aus der Eingabe. Berechnen Sie die versteckte Einheit $ {\ bf z} $ aus der Eingabe $ {\ bf x} $ und dann die Ausgabe $ {\ bf y} $ aus der versteckten Einheit $ {\ bf z} $.
Eine der versteckten Einheiten der ersten Ebene, $ z_1 $,
\begin{align}
a_{z_1} &= w_{11}^{(1)}x_1+w_{12}^{(1)}x_2+w_{13}^{(1)}x_3 + b_1^{(1)}\\
z_1 &= f^{(1)}(a_{z_1})
\end{align}
Ist berechnet. Wo $ a_ {z_1} $ die Aktivität der ersten versteckten Einheit ist, ist $ w_ {1j} ^ {(1)} $ das Gewicht von der j-ten Eingabeeinheit zur ersten versteckten Einheit, $ b_1 $ ist Die Vorspannung der ersten verborgenen Einheit, $ f ^ {(1)} $, ist die Aktivierungsfunktion der ersten Schicht.
Es ist möglich, die gleichen Formeln für $ z_2, z_3, z_4 $ zu formulieren, aber es wird kompliziert, drei Formeln zu haben, daher verwende ich oft eine Matrix.
\begin{align}
\begin{bmatrix}
a_{z_1}\\
a_{z_2}\\
a_{z_3}\\
a_{z_4}
\end{bmatrix}
&=
\begin{bmatrix}
w_{11}^{(1)} & w_{12}^{(1)} & w_{13}^{(1)}\\
w_{21}^{(1)} & w_{22}^{(1)} & w_{23}^{(1)}\\
w_{31}^{(1)} & w_{32}^{(1)} & w_{33}^{(1)}\\
w_{41}^{(1)} & w_{42}^{(1)} & w_{43}^{(1)}
\end{bmatrix}
\begin{bmatrix}
x_1\\
x_2\\
x_3
\end{bmatrix}
+
\begin{bmatrix}
b_1^{(1)}\\
b_2^{(1)}\\
b_3^{(1)}\\
b_4^{(1)}
\end{bmatrix}
\\
\begin{bmatrix}
z_1\\
z_2\\
z_3\\
z_4
\end{bmatrix}
&=
\begin{bmatrix}
f^{(1)}(a_{z_1})\\
f^{(1)}(a_{z_2})\\
f^{(1)}(a_{z_3})\\
f^{(1)}(a_{z_4})
\end{bmatrix}
\end{align}
Machen Sie dies prägnanter
\begin{align}
{\bf a}_z &= W^{(1)}{\bf x} + {\bf b}^{(1)}\\
{\bf z} &= f^{(1)}({\bf a}_z)
\end{align}
Auch ausgedrückt als. Dies vervollständigt die Vorwärtsausbreitung der ersten Schicht.
In gleicher Weise die zweite Schicht
\begin{align}
{\bf a}_y &= W^{(2)}{\bf y} + {\bf b}^{(2)}\\
{\bf y} &= f^{(2)}({\bf a}_y)
\end{align}
Kann ausgedrückt werden als.
Übrigens, um diese zusammenzufassen,
{\bf y} = f^{(2)}(W^{(2)}f^{(1)}(W^{(1)}{\bf x} + {\bf b}^{(1)}) + {\bf b}^{(2)})
Es wird sein. Auf diese Weise können Sie von Eingabe zu Ausgabe berechnen.
Das neuronale Netzwerk hat viele Parameter (26 in diesem Beispiel). Backpropagation ist eine effiziente Methode zur Berechnung dieser Gradienten.
Eingang und sein Zielpaar
{\partial E\over\partial y_i}
ist. Dabei wird der Fehler von $ {\ bf a} \ _y $ und die teilweise Differenzierung der Kostenfunktion bei $ {\ bf a} \ _y $
\begin{align}
{\partial E\over\partial a_{y_i}} &= {\partial E\over\partial y_i}{\partial y_i\over\partial a_{y_i}}\\
&= {\partial E\over\partial y_i}f'^{(2)}(a_{y_i})\\
(&= y_i - t_i)
\end{align}
Es wird erhalten als. Wenn die Aktivierungsfunktion der Ausgabeschicht $ f ^ {(2)} $ eine kanonische Verkettungsfunktion ist, wie z. B. eine konstante Zuordnung, eine Sigmoidfunktion oder eine Softmax-Funktion, ist der Fehler von ** $ {\ bf a} _y $ einfach. Der Unterschied zwischen der Ausgabe und dem Ziel **. Wenn der Fehler von $ {\ bf a} _y $ gefunden wird, kann der Gradient des Gewichts in der zweiten Schicht gefunden werden. weil
\begin{align}
{\partial a_{y_i}\over\partial w_{ij}^{(2)}} &= z_j\\
{\partial a_{y_i}\over\partial b_i^{(2)}} &= 1
\end{align}
Als,
\begin{align}
{\partial E\over\partial w_{ij}^{(2)}} &= {\partial E\over\partial a_{y_i}}{\partial a_{y_i}\over\partial w_{ij}^{(2)}}\\
&= {\partial E\over\partial a_{y_i}}z_j\\
{\partial E\over\partial b_i^{(2)}} &= {\partial E\over\partial a_{y_i}}{\partial a_{y_i}\over\partial b_i^{(2)}}\\
&= {\partial E\over\partial a_{y_i}}
\end{align}
Und kann berechnet werden. Der Ausgabefehler wurde weitergegeben, um den Fehler der Aktivität $ {\ bf a} _ {y} $ zu erhalten, und der Fehler wurde verwendet, um den Gradienten des Parameters zu berechnen. Wenn der Fehler von $ {\ bf a} \ _ y $ erhalten wird, kann außerdem nicht nur der Gradient des Gewichts, sondern auch der Fehler der Eingabe $ {\ bf z} $ der zweiten Schicht berechnet werden.
\begin{align}
{\partial E\over\partial z_j} &= \sum_{i=1}^2 {\partial E\over\partial a_{y_i}}{\partial a_{y_i}\over\partial z_j}\\
&= \sum_{i=1}^2 {\partial E\over\partial a_{y_i}}w_{ij}^{(2)}
\end{align}
Auf diese Weise kann der Fehler von der Ausgabe $ {\ bf y} $ der zweiten Schicht auf die Eingabe $ {\ bf z} $ übertragen werden. Der Fehler der Eingabe $ {\ bf z} $ der zweiten Schicht ist auch der Fehler der Ausgabe der ersten Schicht, so dass durch Wiederholen des Obigen auch der Fehler in der Aktivität der ersten Schicht und der Gradient des Gewichts erhalten werden können. Du kannst es haben.
\begin{align}
{\partial E\over\partial a_{z_i}} &= {\partial E\over\partial z_i}{\partial z_i\over\partial a_{z_i}}\\
&= {\partial E\over\partial z_i}f'^{(2)}(a_{z_i})\\
{\partial E\over\partial x_j} &= \sum_{i=1}^4 {\partial E\over\partial a_{z_i}}{\partial a_{z_i}\over\partial x_j}\\
&= \sum_{i=1}^4 {\partial E\over\partial a_{z_i}}w_{ij}^{(1)}\\
{\partial E\over\partial w_{ij}^{(1)}} &= {\partial E\over\partial a_{z_i}}{\partial a_{z_i}\over\partial w_{ij}^{(1)}}\\
&= {\partial E\over\partial a_{z_i}}x_j\\
{\partial E\over\partial b_i^{(1)}} &= {\partial E\over\partial a_{z_i}}{\partial a_{y_i}\over\partial b_i^{(1)}}\\
&= {\partial E\over\partial a_{z_i}}\\
\end{align}
Auf diese Weise können Sie den Fehler in der Ausgabeebene auf die Eingabe übertragen und den Gradienten berechnen, der zum Aktualisieren der Gewichtsparameter im Prozess erforderlich ist.
Ausbreitung in die entgegengesetzte Richtung vom Fehler in der Ausgabeeinheit
Ausgabeeinheit | Aktivität der zweiten Schicht |
---|---|
Berechnen Sie den Gradienten im Parameter aus dem Aktivitätsfehler.
\begin{align}
{\partial E\over\partial w_{ij}^{(2)}} &= {\partial E\over\partial a_{y_i}}z_j\\
{\partial E\over\partial b_i^{(2)}} &= {\partial E\over\partial a_{y_i}}
\end{align}
Dies wird auch auf die erste Schicht übertragen.
Versteckte Einheit | Aktivität der ersten Schicht |
---|---|
Und der Gradient des Parameters ergibt sich aus dem Fehler in der Aktivität.
\begin{align}
{\partial E\over\partial w_{ij}^{(1)}} &= {\partial E\over\partial a_{z_i}}x_j\\
{\partial E\over\partial b_i^{(1)}} &= {\partial E\over\partial a_{z_i}}
\end{align}
Dieses Mal habe ich etwas anderes als Numpy verwendet, um die Schnittnormalverteilung für die Gewichtsinitialisierung zu verwenden. Normalerweise verwende ich Tensorflow beim Aufbau eines neuronalen Netzwerks, aber Tensorflow verwendet eine Schnittnormalverteilung, um Gewichte zu initialisieren. Deshalb habe ich beschlossen, auch diesem zu folgen.
import numpy as np
from scipy.stats import truncnorm
Eine Klasse, die eine Schicht eines neuronalen Netzwerks darstellt. Gewichte werden initialisiert, wenn eine Instanz erstellt wird, eine Eingabe eingegeben wird, eine Vorwärtsausbreitung durchgeführt wird und dann eine Rückausbreitung von Fehlern durchgeführt wird. Basierend auf dieser Klasse werden wir eine Schicht erstellen, die zur tatsächlichen Modellierung des neuronalen Netzwerks verwendet wird.
class Layer(object):
def __init__(self, dim_input, dim_output, std=1., bias=0.):
self.w = truncnorm(a=-2 * std, b=2 * std, scale=std).rvs((dim_input, dim_output))
self.b = np.ones(dim_output) * bias
def __call__(self, X):
self.input = X
return self.forward_propagation(X)
def back_propagation(self, delta, learning_rate):
# derivative with respect to activation
delta = delta * self.activation_derivative()
w = np.copy(self.w)
self.w -= learning_rate * self.input.T.dot(delta)
self.b -= learning_rate * np.sum(delta, axis=0)
# derivative with respect to input
return delta.dot(w.T)
Layer | Erläuterung |
---|---|
__init__ | Initialisieren Sie die Parameter, indem Sie die Eingabe- und Ausgabereihenfolgen dieser Ebene eingeben |
__call__ | Berechnen Sie die Ausgabe dieser Ebene, indem Sie sich von der Eingabe dieser Ebene vorwärts ausbreiten |
back_propagation | Berechnen Sie den Eingabefehler dieser Ebene, indem Sie die Parameter aktualisieren, einschließlich des Ausgabefehlers dieser Ebene und des Lernkoeffizienten. |
Schicht mit konstanter Aktivierungsfunktion $ f (a) = a $. Wir werden Schichten aufbauen, indem wir neue Methoden zur Berechnung der Vorwärtsausbreitung und zur Differenzierung von Aktivierungsfunktionen definieren.
class LinearLayer(Layer):
def forward_propagation(self, X):
return X.dot(self.w) + self.b
def activation_derivative(self):
return 1
Schicht, in der die Aktivierungsfunktion die logistische Sigmoidfunktion $ f (x) = {1 \ over1 + \ exp (-x)} $ ist. Die Differenzierung der logistischen Sigmoidfunktion ist $ f '(x) = f (x) (1-f (x)) $.
class SigmoidLayer(Layer):
def forward_propagation(self, X):
activation = X.dot(self.w) + self.b
self.output = 1 / (1 + np.exp(-activation))
return self.output
def activation_derivative(self):
return self.output * (1 - self.output)
Auf diese Weise können Sie auch eine Ebene erstellen, die die Zweikurventangensfunktion $ \ tanh (x) $ und die normalisierte lineare Funktion $ \ max (x, 0) $ als Aktivierungsfunktion verwendet.
Dies ist eine Fehlerfunktion, die häufig bei der Lösung von ** Regressionsproblemen ** verwendet wird.
class SumSquaresError(object):
def activate(self, X):
return X
def __call__(self, X, targets):
return 0.5 * np.sum((X - targets) ** 2)
def delta(self, X, targets):
return X - targets
Dies ist eine Fehlerfunktion, die verwendet wird, wenn Sie ** 2 Klassen ** klassifizieren möchten. Die Kreuzentropie wird nach Durchführung einer nichtlinearen Transformation mit der logistischen Sigmoidfunktion berechnet.
class SigmoidCrossEntropy(object):
def activate(self, logits):
return 1 / (1 + np.exp(-logits))
def __call__(self, logits, targets):
probs = self.activate(logits)
p = np.clip(probs, 1e-10, 1 - 1e-10)
return np.sum(-targets * np.log(p) - (1 - targets) * np.log(1 - p))
def delta(self, logits, targets):
probs = self.activate(logits)
return probs - targets
Auf diese Weise wird auch die für die Klassifizierung mehrerer Klassen verwendete Softmax-Kreuzentropie implementiert.
Da die Kostenfunktion eine Aktivierungsfunktion hat, sollte die letzte Ebene LinearLayer verwenden. Durch Verwendung der Näherung durch die Differenz der endlichen Breite kann bestätigt werden, ob die Fehlerrückausbreitung korrekt implementiert ist.
class NeuralNetwork(object):
def __init__(self, layers, cost_function):
self.layers = layers
self.cost_function = cost_function
def __call__(self, X):
for layer in self.layers:
X = layer(X)
return self.cost_function.activate(X)
def fit(self, X, t, learning_rate):
for layer in self.layers:
X = layer(X)
delta = self.cost_function.delta(X, t)
for layer in reversed(self.layers):
delta = layer.back_propagation(delta, learning_rate)
def cost(self, X, t):
for layer in self.layers:
X = layer(X)
return self.cost_function(X, t)
def _gradient_check(self, X=None, t=None, eps=1e-6):
if X is None:
X = np.array([[0.5 for _ in xrange(np.size(self.layers[0].w, 0))]])
if t is None:
t = np.zeros((1, np.size(self.layers[-1].w, 1)))
t[0, 0] = 1.
e = np.zeros_like(X)
e[:, 0] += eps
x_plus_e = X + e
x_minus_e = X - e
grad = (self.cost(x_plus_e, t) - self.cost(x_minus_e, t)) / (2 * eps)
for layer in self.layers:
X = layer(X)
delta = self.cost_function.delta(X, t)
for layer in reversed(self.layers):
delta = layer.back_propagation(delta, 0)
print "==================================="
print "checking gradient"
print "finite difference", grad
print " back propagation", delta[0, 0]
print "==================================="
NueralNetwork | Erläuterung |
---|---|
__init__ | Definition von Netzwerkstruktur und Kostenfunktion |
__call__ | Vorwärtsausbreitungsberechnung |
fit | Das Netzwerk lernen |
cost | Berechnen Sie den Wert der Kostenfunktion |
_gradient_check | Bestätigung des Backpropagation-Gradienten |
Der ganze Code ist hier. Importieren Sie nur das, was Sie von diesem Modul benötigen, und erstellen Sie Code, um Regressions- und Klassifizierungsprobleme zu lösen.
neural_network.py
import numpy as np
from scipy.stats import truncnorm
class Layer(object):
def __init__(self, dim_input, dim_output, std=1., bias=0.):
self.w = truncnorm(a=-2 * std, b=2 * std, scale=std).rvs((dim_input, dim_output))
self.b = np.ones(dim_output) * bias
def __call__(self, X):
self.input = X
return self.forward_propagation(X)
def back_propagation(self, delta, learning_rate):
# derivative with respect to activation
delta = delta * self.activation_derivative()
w = np.copy(self.w)
self.w -= learning_rate * self.input.T.dot(delta)
self.b -= learning_rate * np.sum(delta, axis=0)
# derivative with respect to input
return delta.dot(w.T)
class LinearLayer(Layer):
def forward_propagation(self, X):
return X.dot(self.w) + self.b
def activation_derivative(self):
return 1
class SigmoidLayer(Layer):
def forward_propagation(self, X):
activation = X.dot(self.w) + self.b
self.output = 1 / (1 + np.exp(-activation))
return self.output
def activation_derivative(self):
return self.output * (1 - self.output)
class TanhLayer(Layer):
def forward_propagation(self, X):
activation = X.dot(self.w) + self.b
self.output = np.tanh(activation)
return self.output
def activation_derivative(self):
return 1 - self.output ** 2
class ReLULayer(Layer):
def forward_propagation(self, X):
activation = X.dot(self.w) + self.b
self.output = activation.clip(min=0)
return self.output
def activation_derivative(self):
return (self.output > 0).astype(np.float)
class SigmoidCrossEntropy(object):
def activate(self, logits):
return 1 / (1 + np.exp(-logits))
def __call__(self, logits, targets):
probs = self.activate(logits)
p = np.clip(probs, 1e-10, 1 - 1e-10)
return np.sum(-targets * np.log(p) - (1 - targets) * np.log(1 - p))
def delta(self, logits, targets):
probs = self.activate(logits)
return probs - targets
class SoftmaxCrossEntropy(object):
def activate(self, logits):
a = np.exp(logits - np.max(logits, 1, keepdims=True))
a /= np.sum(a, 1, keepdims=True)
return a
def __call__(self, logits, targets):
probs = self.activate(logits)
p = probs.clip(min=1e-10)
return - np.sum(targets * np.log(p))
def delta(self, logits, targets):
probs = self.activate(logits)
return probs - targets
class SumSquaresError(object):
def activate(self, X):
return X
def __call__(self, X, targets):
return 0.5 * np.sum((X - targets) ** 2)
def delta(self, X, targets):
return X - targets
class NeuralNetwork(object):
def __init__(self, layers, cost_function):
self.layers = layers
self.cost_function = cost_function
def __call__(self, X):
for layer in self.layers:
X = layer(X)
return self.cost_function.activate(X)
def fit(self, X, t, learning_rate):
for layer in self.layers:
X = layer(X)
delta = self.cost_function.delta(X, t)
for layer in reversed(self.layers):
delta = layer.back_propagation(delta, learning_rate)
def cost(self, X, t):
for layer in self.layers:
X = layer(X)
return self.cost_function(X, t)
def _gradient_check(self, X=None, t=None, eps=1e-6):
if X is None:
X = np.array([[0.5 for _ in xrange(np.size(self.layers[0].w, 0))]])
if t is None:
t = np.zeros((1, np.size(self.layers[-1].w, 1)))
t[0, 0] = 1.
e = np.zeros_like(X)
e[:, 0] += eps
x_plus_e = X + e
x_minus_e = X - e
grad = (self.cost(x_plus_e, t) - self.cost(x_minus_e, t)) / (2 * eps)
for layer in self.layers:
X = layer(X)
delta = self.cost_function.delta(X, t)
for layer in reversed(self.layers):
delta = layer.back_propagation(delta, 0)
print "==================================="
print "checking gradient"
print "finite difference", grad
print " back propagation", delta[0, 0]
print "==================================="
Legen Sie die obige Datei neural_network.py und diese Datei im selben Verzeichnis ab.
binary_classification.py
import pylab as plt
import numpy as np
from neural_network import TanhLayer, LinearLayer, SigmoidCrossEntropy, NeuralNetwork
def create_toy_dataset():
x = np.random.uniform(-1., 1., size=(1000, 2))
labels = (np.prod(x, axis=1) > 0).astype(np.float)
return x, labels.reshape(-1, 1)
def main():
x, labels = create_toy_dataset()
colors = ["blue", "red"]
plt.scatter(x[:, 0], x[:, 1], c=[colors[int(label)] for label in labels])
layers = [TanhLayer(2, 4), LinearLayer(4, 1)]
cost_function = SigmoidCrossEntropy()
nn = NeuralNetwork(layers, cost_function)
nn._gradient_check()
for i in xrange(100000):
if i % 10000 == 0:
print "step %6d, cost %f" % (i, nn.cost(x, labels))
nn.fit(x, labels, learning_rate=0.001)
X_test, Y_test = np.meshgrid(np.linspace(-1, 1, 100), np.linspace(-1, 1, 100))
x_test = np.array([X_test, Y_test]).transpose(1, 2, 0).reshape(-1, 2)
probs = nn(x_test)
Probs = probs.reshape(100, 100)
levels = np.linspace(0, 1, 11)
plt.contourf(X_test, Y_test, Probs, levels, alpha=0.5)
plt.colorbar()
plt.xlim(-1, 1)
plt.ylim(-1, 1)
plt.show()
if __name__ == '__main__':
main()
Legen Sie dies in das gleiche Verzeichnis wie neural_network.py oben.
regression.py
import pylab as plt
import numpy as np
from neural_network import TanhLayer, LinearLayer, SumSquaresError, NeuralNetwork
def create_toy_dataset(func, n=100):
x = np.random.uniform(size=(n, 1))
t = func(x) + np.random.uniform(-0.1, 0.1, size=(n, 1))
return x, t
def main():
def func(x):
return x + 0.3 * np.sin(2 * np.pi * x)
x, t = create_toy_dataset(func)
layers = [TanhLayer(1, 6, std=1., bias=-0.5), LinearLayer(6, 1, std=1., bias=0.5)]
cost_function = SumSquaresError()
nn = NeuralNetwork(layers, cost_function)
nn._gradient_check()
for i in xrange(100000):
if i % 10000 == 0:
print "step %6d, cost %f" % (i, nn.cost(x, t))
nn.fit(x, t, learning_rate=0.001)
plt.scatter(x, t, alpha=0.5, label="observation")
x_test = np.linspace(0, 1, 1000)[:, np.newaxis]
y = nn(x_test)
plt.plot(x_test, func(x_test), color="blue", label="$x+0.3\sin(2\pi x)$")
plt.plot(x_test, y, color="red", label="regression")
plt.legend(loc="upper left")
plt.xlabel("x")
plt.ylabel("y")
plt.show()
if __name__ == '__main__':
main()
Wenn Sie den Code ausführen, der das neuronale Netzwerk trainiert, das eine Zwei-Klassen-Klassifizierung durchführt, lautet die Ausgabe wie folgt.
Terminalausgang
===================================
checking gradient
finite difference 0.349788735199
back propagation 0.349788735237
===================================
Die Implementierung sieht in Ordnung aus, da der aus der endlichen Breitendifferenz berechnete Gradient und der durch Fehlerrückausbreitung berechnete Fehlerwert nahe beieinander liegen.
Ein neuronales Netzwerk, das zwei Klassen unter Verwendung von blauen und roten Punkten als Trainingsdaten klassifiziert, wird trainiert, und die zweidimensionale Ebene wird entsprechend der Ausgabe farbcodiert. Das Video zeigt den Lernprozess des neuronalen Netzwerks. (Der obige Zwei-Klassen-Klassifizierungscode zeigt jedoch nur das Standbild als Ergebnis des Lernens an.)
Dies ist das Ergebnis, wenn ein neuronales Netzwerk für die Regression verwendet wird. Der blaue Punkt wird als Trainingsdaten zum Trainieren des neuronalen Netzwerks verwendet, und die Änderung der Ausgabe des neuronalen Netzwerks wird dargestellt. (Der Code des obigen Regressionsproblems zeigt jedoch auch nur das Standbild als Ergebnis des Lernens an.)
Dieses Mal habe ich ein neuronales Netzwerk implementiert und es trainiert. Das nächste Mal werden wir diesen Code verwenden, um ein Netzwerk mit gemischter Dichte zu implementieren. Bei Verwendung eines normalen neuronalen Netzwerks für Regressionsprobleme wird die Kostenfunktion einer Gaußschen Funktion mit einem Peak nachempfunden, sodass sie nicht mit Situationen mit mehreren Peaks umgehen kann. Netzwerke mit gemischter Dichte lösen dieses Problem, indem sie gemischte Gauß als Kostenfunktion verwenden.
Recommended Posts