J'ai implémenté un réseau de neurones à 3 couches avec python et essayé d'identifier XNOR. J'ai également publié les formules, donc si vous êtes intéressé, veuillez les lire. J'ai utilisé ["Deep Learning"](http://www.amazon.co.jp/ Deep Learning-Machine Learning Professional Series-Okaya-Takayuki / dp / 4061529021) comme manuel.
Structure de cet article
Un réseau neuronal est un modèle qui imite les circuits neuronaux du cerveau humain. La reconnaissance d'image et la reconnaissance vocale sont possibles en utilisant ce modèle. Le réseau mis en œuvre cette fois a une structure à trois couches constituée d'une couche d'entrée, d'une couche intermédiaire (1 couche) et d'une couche de sortie.
Je vais expliquer avec la figure ci-dessous.
Soit le poids de l'unité $ i $ ème dans la couche $ l-1 $ à l'unité $ j $ ème dans la couche $ l $ soit $ w_ {ji} ^ {(l)} $. Aussi, soit $ u_ {i} ^ {(l-1)} $ la valeur détenue par la $ i $ ème unité dans la couche $ l-1 $. La fonction sigmoïde $ g (x) $ est utilisée comme fonction d'activation. C'est pratique car la valeur différentielle peut être obtenue en utilisant la valeur avant différenciation.
g(x) = \cfrac{1}{1 + \exp(-x)} \\
g'(x) = g(x)(1 - g(x))
L'erreur carrée $ E_n $ est utilisée comme fonction objectif et le poids est mis à jour par la formule suivante pour le minimiser. $ E_n $ est l'erreur causée par un échantillon, et la méthode de mise à jour du poids à l'aide de ce $ E_n $ est appelée méthode de descente de gradient stochastique. La méthode de descente de gradient stochastique a l'avantage qu'elle est moins susceptible d'être piégée dans la solution locale car elle change chaque fois que la fonction objectif est mise à jour. $ \ Epsilon $ est appelé le taux d'apprentissage et est un paramètre qui détermine la vitesse d'apprentissage. Le point est de savoir comment calculer le terme de différenciation partielle.
{{w}_{ji}^{(l)}}_{new} = {w_{ji}^{(l)}}_{old} - \epsilon\cfrac{\partial E_n}{\partial {w_{ji}^{(l)}}_{old}}
Maintenant, je vais vous expliquer comment trouver $ \ cfrac {\ partial E_n} {\ partial w_ {ji} ^ {(l)}} $. Étant donné que la méthode d'obtention de la couche de sortie et de la couche intermédiaire sont légèrement différentes, nous les expliquerons séparément.
**
\begin{align}
\cfrac{\partial E_n}{\partial w_{ji}^{(l)}} &= \cfrac{\partial}{\partial w_{ji}^{(l)}}\cfrac{1}{2}(t_j - g(u_{j}^{(l)}))^{2} \\
&= (t_j - g(u_{j}^{(l)}))\cdot\cfrac{\partial}{\partial w_{ji}^{(l)}}(t_j - g(u_{j}^{(l)})) \\
&= (t_j - g(u_{j}^{(l)}))\cdot\cfrac{\partial}{\partial u_{j}^{(l)}}(t_j - g(u_{j}^{(l)}))\cdot\cfrac{\partial u_{j}^{(l)}}{\partial w_{ji}^{(l)}} \\
\\
&= (g(u_{j}^{(l)}) - t_j)\cdot g'(u_{j}^{(l)})\cdot g(u_{i}^{(l-1)}) \\
\\
&= (g(u_{j}^{(l)}) - t_j)g(u_{j}^{(l)})(1 - g(u_{j}^{(l)}))g(u_{i}^{(l-1)})
\end{align}
** <Couche intermédiaire> ** Lorsque l'erreur carrée $ E_n $ est partiellement différenciée par le poids de la couche intermédiaire $ w_ {ji} ^ {(l)} $,
\begin{align}
\cfrac{\partial E_n}{\partial w_{ji}^{(l)}} &= \cfrac{\partial E_n}{\partial u_{j}^{(l)}}\cfrac{\partial u_{j}^{(l)}}{\partial w_{ji}^{(l)}} \\
\\
&= \delta_{j}^{(l)}\cfrac{\partial u_{j}^{(l)}}{\partial w_{ji}^{(l)}}
\end{align}
Le premier terme du côté droit est
\begin{align}
\delta_{j}^{(l)} &= \cfrac{\partial E_n}{\partial u_{j}^{(l)}} \\
&= \sum_{k}\cfrac{\partial E_n}{\partial u_{k}^{(l+1)}}\cfrac{\partial u_{k}^{(l+1)}}{\partial u_{j}^{(l)}} \\
\\
&= \sum_{k}\delta_{k}^{(l+1)}w_{kj}^{(l+1)}g'(u_{j}^{(l)}) \\
\\
&= \Bigl(\sum_{k}\delta_{k}^{(l+1)}w_{kj}^{(l+1)}\Bigr)g(u_{j}^{(l)})(1 - g(u_{j}^{(l)}))
\end{align}
Le deuxième terme sur le côté droit est
\begin{align}
\cfrac{\partial u_{j}^{(l)}}{\partial w_{ji}^{(l)}} &= g(u_{i}^{(l-1)})
\end{align}
De ce qui précède,
\begin{align}
\cfrac{\partial E_n}{\partial w_{ji}^{(l)}} &= \delta_{j}^{(l)}\cfrac{\partial u_{j}^{(l)}}{\partial w_{ji}^{(l)}} \\
\\
&= \Bigl(\sum_{k}\delta_{k}^{(l+1)}w_{kj}^{(l+1)}\Bigr)g(u_{j}^{(l)})(1 - g(u_{j}^{(l)}))g(u_{i}^{(l-1)})
\end{align}
Lors de la mise à jour du poids de la couche intermédiaire $ l $, $ \ delta_ {k} ^ {(l + 1)} $ de la couche suivante $ l + 1 $ est nécessaire. $ \ Delta $ dans la couche de sortie peut être obtenu comme la différence entre la valeur de sortie et l'enseignant, et en propageant ceci dans l'ordre de la couche de sortie à la couche d'entrée, le poids de la couche intermédiaire est mis à jour. C'est la raison pour laquelle il est appelé propagation de retour d'erreur.
XNOR
XNOR génère $ 1 $ lorsque les valeurs d'entrée sont identiques et $ 0 $ lorsque les valeurs d'entrée sont différentes. Il ne peut pas être identifié de manière linéaire.
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Nous avons implémenté un réseau neuronal qui identifie XNOR. Le nombre de couches intermédiaires est de un et le nombre d'unités dans la couche intermédiaire est de deux. Le taux d'apprentissage est $ \ epsilon = 0,1 $ et le coefficient d'élan est $ \ mu = 0,9 $. (Momentum est l'une des méthodes pour améliorer les performances de convergence, et le montant de correction de poids est ajouté en multipliant le montant de correction de poids précédent par un coefficient.)
neuralnetwork.py
import numpy
import math
import random
from matplotlib import pyplot
class Neural:
# constructor
def __init__(self, n_input, n_hidden, n_output):
self.hidden_weight = numpy.random.random_sample((n_hidden, n_input + 1))
self.output_weight = numpy.random.random_sample((n_output, n_hidden + 1))
self.hidden_momentum = numpy.zeros((n_hidden, n_input + 1))
self.output_momentum = numpy.zeros((n_output, n_hidden + 1))
# public method
def train(self, X, T, epsilon, mu, epoch):
self.error = numpy.zeros(epoch)
N = X.shape[0]
for epo in range(epoch):
for i in range(N):
x = X[i, :]
t = T[i, :]
self.__update_weight(x, t, epsilon, mu)
self.error[epo] = self.__calc_error(X, T)
def predict(self, X):
N = X.shape[0]
C = numpy.zeros(N).astype('int')
Y = numpy.zeros((N, X.shape[1]))
for i in range(N):
x = X[i, :]
z, y = self.__forward(x)
Y[i] = y
C[i] = y.argmax()
return (C, Y)
def error_graph(self):
pyplot.ylim(0.0, 2.0)
pyplot.plot(numpy.arange(0, self.error.shape[0]), self.error)
pyplot.show()
# private method
def __sigmoid(self, arr):
return numpy.vectorize(lambda x: 1.0 / (1.0 + math.exp(-x)))(arr)
def __forward(self, x):
# z: output in hidden layer, y: output in output layer
z = self.__sigmoid(self.hidden_weight.dot(numpy.r_[numpy.array([1]), x]))
y = self.__sigmoid(self.output_weight.dot(numpy.r_[numpy.array([1]), z]))
return (z, y)
def __update_weight(self, x, t, epsilon, mu):
z, y = self.__forward(x)
# update output_weight
output_delta = (y - t) * y * (1.0 - y)
_output_weight = self.output_weight
self.output_weight -= epsilon * output_delta.reshape((-1, 1)) * numpy.r_[numpy.array([1]), z] - mu * self.output_momentum
self.output_momentum = self.output_weight - _output_weight
# update hidden_weight
hidden_delta = (self.output_weight[:, 1:].T.dot(output_delta)) * z * (1.0 - z)
_hidden_weight = self.hidden_weight
self.hidden_weight -= epsilon * hidden_delta.reshape((-1, 1)) * numpy.r_[numpy.array([1]), x]
self.hidden_momentum = self.hidden_weight - _hidden_weight
def __calc_error(self, X, T):
N = X.shape[0]
err = 0.0
for i in range(N):
x = X[i, :]
t = T[i, :]
z, y = self.__forward(x)
err += (y - t).dot((y - t).reshape((-1, 1))) / 2.0
return err
main.py
from neuralnetwork import *
if __name__ == '__main__':
X = numpy.array([[0, 0], [0, 1], [1, 0], [1, 1]])
T = numpy.array([[1, 0], [0, 1], [0, 1], [1, 0]])
N = X.shape[0] # number of data
input_size = X.shape[1]
hidden_size = 2
output_size = 2
epsilon = 0.1
mu = 0.9
epoch = 10000
nn = Neural(input_size, hidden_size, output_size)
nn.train(X, T, epsilon, mu, epoch)
nn.error_graph()
C, Y = nn.predict(X)
for i in range(N):
x = X[i, :]
y = Y[i, :]
c = C[i]
print x
print y
print c
print ""
Il peut être identifié correctement.
0 | 0 | [ 1, 0 ] | [ 0.92598739, 0.07297757 ] |
0 | 1 | [ 0, 1 ] | [ 0.06824915, 0.93312514 ] |
1 | 0 | [ 0, 1 ] | [ 0.06828438, 0.93309010 ] |
1 | 1 | [ 1, 0 ] | [ 0.92610205, 0.07220633 ] |
J'ai fait un graphique montrant comment l'erreur diminue. L'axe horizontal est le nombre d'époques et l'axe vertical est l'erreur.
Nous avons implémenté un réseau neuronal à trois couches et avons pu identifier XNOR. Il y a encore du travail à faire, comme des astuces d'apprentissage (comment déterminer la valeur de poids initiale, comment déterminer le taux d'apprentissage, AdaGrad, momentum), mais c'est la fin.
Recommended Posts