Le chapitre 5 de PRML présente les réseaux de neurones récemment populaires. J'ai essayé de mettre en œuvre un réseau de neurones sur le net de nombreuses sortes de choses.J'ai donc voulu utiliser quelque chose que je n'étais pas familier autant que possible.J'ai donc décidé de mettre en œuvre un réseau à densité mixte presque exclusivement avec Numpy. Cependant, étant donné que la quantité de code est devenue assez importante, je vais la diviser en deux parties, et dans cet article, je vais éventuellement implémenter un réseau de neurones normal et faire le réseau à densité mixte la prochaine fois.
Cela signifie que l'unité d'entrée est 3D $ {\ bf x} = (x_1, x_2, x_3) $, l'unité cachée est 4D $ {\ bf z} = (z_1, z_2, z_3, z_4) $, et l'unité de sortie est 2. Ceci est une représentation schématique d'un réseau de neurones à deux couches de dimensions $ {\ bf z} = (z_1, z_2)
La propagation vers l'avant est l'étape de calcul de la sortie du réseau à partir de l'entrée. Calculez l'unité cachée $ {\ bf z} $ à partir de l'entrée $ {\ bf x} $, puis la sortie $ {\ bf y} $ de l'unité cachée $ {\ bf z} $.
L'une des premières unités masquées, $ 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}
Est calculé. Où $ a_ {z_1} $ est l'activité de la première unité cachée, $ w_ {1j} ^ {(1)} $ est le poids de la jième unité d'entrée à la première unité cachée, $ b_1 $ est Le biais de la première unité cachée, $ f ^ {(1)} $, est la fonction d'activation de la première couche.
Il est possible de formuler les mêmes formules pour $ z_2, z_3, z_4 $, mais cela devient compliqué d'avoir trois formules, donc j'utilise souvent une matrice.
\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}
Rendez cela plus concis
\begin{align}
{\bf a}_z &= W^{(1)}{\bf x} + {\bf b}^{(1)}\\
{\bf z} &= f^{(1)}({\bf a}_z)
\end{align}
Également exprimé comme. Ceci achève la propagation vers l'avant de la première couche.
De la même manière, la deuxième couche
\begin{align}
{\bf a}_y &= W^{(2)}{\bf y} + {\bf b}^{(2)}\\
{\bf y} &= f^{(2)}({\bf a}_y)
\end{align}
Peut être exprimé comme.
En passant, pour résumer ces derniers,
{\bf y} = f^{(2)}(W^{(2)}f^{(1)}(W^{(1)}{\bf x} + {\bf b}^{(1)}) + {\bf b}^{(2)})
Ce sera. De cette façon, vous pouvez calculer de l'entrée à la sortie.
Le réseau neuronal a de nombreux paramètres (26 dans cet exemple). La rétropropagation est un moyen efficace de calculer ces gradients.
Entrée et sa paire cible
{\partial E\over\partial y_i}
est. En utilisant cela, l'erreur de $ {\ bf a} \ _y $ et la différenciation partielle de la fonction de coût en $ {\ 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}
Il est obtenu comme. Si la fonction d'activation de la couche de sortie $ f ^ {(2)} $ est une fonction de concaténation canonique telle qu'un mappage constant, une fonction sigmoïde, une fonction softmax, l'erreur de ** $ {\ bf a} _y $ est simple. La différence entre la sortie et la cible **. Si l'erreur de $ {\ bf a} _y $ est trouvée, le gradient du poids dans la deuxième couche peut être trouvé. Parce que
\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}
Que,
\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}
Et peut être calculé. L'erreur de sortie a été propagée pour obtenir l'erreur d'activité $ {\ bf a} _ {y} $, et l'erreur a été utilisée pour calculer le gradient du paramètre. De plus, si l'erreur de $ {\ bf a} \ _ y $ est obtenue, non seulement le gradient du poids mais aussi l'erreur de l'entrée $ {\ bf z} $ de la deuxième couche peuvent être calculés.
\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}
De cette façon, l'erreur peut être propagée de la sortie $ {\ bf y} $ de la deuxième couche à l'entrée $ {\ bf z} $. L'erreur de l'entrée $ {\ bf z} $ de la deuxième couche est aussi l'erreur de la sortie de la première couche, donc en répétant ce qui précède, l'erreur dans l'activité de la première couche et le gradient du poids peuvent également être obtenus. Tu peux l'avoir.
\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}
De cette manière, vous pouvez propager l'erreur dans la couche de sortie vers l'entrée et calculer le gradient requis pour mettre à jour les paramètres de poids dans le processus.
Propagation dans le sens inverse de l'erreur dans l'unité de sortie
Unité de sortie | Activité de deuxième couche |
---|---|
Calculez le gradient dans le paramètre à partir de l'erreur dans l'activité.
\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}
Ceci est également propagé à la première couche.
Unité cachée | Activité de première couche |
---|---|
Et le gradient du paramètre est obtenu à partir de l'erreur dans l'activité.
\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}
Cette fois, j'ai utilisé autre chose que numpy pour utiliser la distribution normale de coupe pour l'initialisation du poids. J'utilise habituellement Tensorflow lors de la construction d'un réseau neuronal, mais Tensorflow utilise une distribution normale de coupe pour initialiser les poids, j'ai donc décidé de suivre cela également.
import numpy as np
from scipy.stats import truncnorm
Une classe qui représente une couche d'un réseau neuronal. Les pondérations sont initialisées lorsqu'une instance est créée, l'entrée est entrée, la propagation vers l'avant est effectuée, puis la propagation arrière des erreurs est effectuée. Sur la base de cette classe, nous allons construire une couche à utiliser pour modéliser réellement le réseau neuronal.
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 | La description |
---|---|
__init__ | Initialisez les paramètres en entrant les ordres d'entrée et de sortie de cette couche |
__call__ | Calculer la sortie de cette couche en se propageant vers l'avant à partir de l'entrée de cette couche |
back_propagation | Calculez l'erreur d'entrée de cette couche en mettant à jour les paramètres, y compris l'erreur de sortie et le coefficient d'apprentissage de cette couche. |
Couche avec fonction d'activation constante $ f (a) = a $. Nous construirons des couches en définissant de nouvelles méthodes de calcul de la propagation directe et de la différenciation des fonctions d'activation.
class LinearLayer(Layer):
def forward_propagation(self, X):
return X.dot(self.w) + self.b
def activation_derivative(self):
return 1
Couche où la fonction d'activation est la fonction sigmoïde logistique $ f (x) = {1 \ over1 + \ exp (-x)} $. La différenciation de la fonction sigmoïde logistique est $ 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)
De cette façon, vous pouvez également créer une couche qui utilise la fonction tangente bicurve $ \ tanh (x) $ et la fonction linéaire normalisée $ \ max (x, 0) $ comme fonction d'activation.
Il s'agit d'une fonction d'erreur souvent utilisée lors de la résolution de ** problèmes de régression **.
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
Il s'agit d'une fonction d'erreur utilisée lorsque vous souhaitez classer ** 2 classes **. L'entropie croisée est calculée après avoir effectué une transformation non linéaire avec la fonction sigmoïde logistique.
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
De cette manière, Softmax Cross Entropy utilisé pour la classification multi-classes est également implémenté.
Comme la fonction de coût a une fonction d'activation, la couche finale doit utiliser LinearLayer. En utilisant l'approximation par la différence de largeur finie, il est possible de confirmer si la propagation de retour d'erreur est correctement implémentée.
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 | La description |
---|---|
__init__ | Définition de la structure du réseau et de la fonction de coût |
__call__ | Calcul de la propagation vers l'avant |
fit | Apprendre le réseau |
cost | Calculer la valeur de la fonction de coût |
_gradient_check | Confirmation du gradient de rétropropagation |
Le code entier est ici. Importez uniquement ce dont vous avez besoin à partir de ce module et créez du code pour résoudre les problèmes de régression et de classification.
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 "==================================="
Mettez le neural_network.py ci-dessus et ce fichier dans le même répertoire.
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()
Mettez ceci dans le même répertoire que neural_network.py ci-dessus.
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()
Si vous exécutez le code qui entraîne le réseau neuronal qui effectue la classification à deux classes, la sortie sera la suivante.
Sortie de borne
===================================
checking gradient
finite difference 0.349788735199
back propagation 0.349788735237
===================================
L'implémentation semble correcte car le gradient calculé à partir de la différence de largeur finie et la valeur d'erreur calculée par la rétropropagation d'erreur sont proches.
Un réseau neuronal qui classe deux classes à l'aide de points bleus et rouges comme données d'apprentissage est entraîné, et le plan bidimensionnel est codé par couleur en fonction de la sortie. La vidéo montre le processus d'apprentissage du réseau neuronal. (Cependant, le code de classification à deux classes ci-dessus n'affiche que l'image fixe à la suite de l'apprentissage.)
C'est le résultat lorsqu'un réseau neuronal est utilisé pour la régression. Le point bleu est utilisé comme données d'apprentissage pour entraîner le réseau neuronal, et le changement dans la sortie du réseau neuronal est illustré. (Cependant, le code du problème de régression ci-dessus n'affiche également que l'image fixe à la suite de l'apprentissage.)
Cette fois, j'ai implémenté un réseau neuronal et je l'ai formé. La prochaine fois, nous utiliserons ce code pour implémenter un réseau à densité mixte. Lors de l'utilisation d'un réseau neuronal normal pour des problèmes de régression, la fonction de coût est modélisée sur une fonction gaussienne à pic unique, de sorte qu'elle ne peut pas gérer les situations à pics multiples. Les réseaux à densité mixte résolvent ce problème en utilisant des gauss mixtes comme fonction de coût.
Recommended Posts