Depuis que j'ai récemment commencé à utiliser Theano comme troisième bibliothèque après chainer et caffe, je vais vous expliquer l'implémentation du réseau de neurones convolutifs (CNN) en utilisant Theano. Autres articles et Tutoriel officiel sont faciles à comprendre pour une utilisation basique de Theano. Veuillez donc vous y référer. Cet article sera expliqué en fonction du Tutoriel Deep Convolutional Network of Deep Learning. Le code a été modifié pour l'explication, mais le flux général est le même. Il n'y a pas d'explication du Deep Learning lui-même ou de l'utilisation de base de theano dans l'explication de l'implémentation de CNN à l'aide de Theano, veuillez donc vous référer à d'autres articles pour ce domaine. Aussi, même si cela dit l'explication de la mise en œuvre, il y a de nombreux points que je ne peux pas atteindre car la capacité de mise en œuvre de la personne elle-même n'est pas si élevée et il n'est pas bon en anglais (le tutoriel officiel est en anglais) pendant quelques jours après avoir commencé à utiliser Theano. (Ne vous attendez pas à autant car cela ressemble à un mémorandum de la personne)
LeNet Nous effectuerons une reconnaissance minime de caractères manuscrits basée sur LeNet selon le tutoriel d'apprentissage en profondeur. LeNet est un CNN de base composé de 2 couches convolutives, 2 couches de regroupement et une couche entièrement connectée. Dans cet article, les détails tels que la fonction d'activation sont différents de ceux d'origine, mais la structure de base est la même. (Veuillez lire le document original pour une explication détaillée de LeNet)
Nous implémenterons CNN sur la base du LeNet ci-dessus. Comme prémisse, on suppose que ce qui suit est importé.
import theano
import theano.tensor as T
import numpy as np
Tout d'abord, nous allons implémenter la couche de convolution. Theano fournit T.nnet.conv.conv2d comme symbole de convolution. Theano utilise numpy et theano.shared parce que vous devez écrire vous-même les poids et les biais. Les poids et les biais sont des valeurs mises à jour par apprentissage, nous les définissons donc à l'aide de theano.shared. De plus, comme il est difficile de décrire la couche chaque fois qu'une couche de convolution est ajoutée, elle est définie comme une classe. Voici un exemple d'implémentation de la classe de couche de convolution.
class Conv2d(object):
def __init__(self, input, out_c, in_c, k_size)
self._input = input #Symbole à saisir
self._out_c = out_c #Nombre de canaux de sortie
self._in_c = in_c #Nombre de canaux d'entrée
w_shp = (out_c, in_c, k_size, k_size) #Forme du poids
w_bound = np.sqrt(6. / (in_c * k_size * k_size + \
out_c * k_size * k_size)) #Contraintes de poids
#Définition du poids
self.W = theano.shared( np.asarray(
np.random.uniform( #Initialiser avec des nombres aléatoires
low=-w_bound,
high=w_bound,
size=w_shp),
dtype=self._intype.dtype), name ='W', borrow=True)
b_shp = out_c, #Forme de biais
#Définition du biais(Initialiser avec zéro)
self.b = theano.shared(np.zeros(b_shp,
dtype=self._input.dtype), name ='b', borrow=True)
#Définition des symboles de convolution
self.output = T.nnet.conv.conv2d(self._input, self.W) \
+ self.b.dimshuffle('x', 0, 'x', 'x')
#Enregistrer les paramètres mis à jour
self.params = [self.W, self.b]
dimshuffle fonctionne pour faire correspondre la dimension du terme de biais de vecteur à tensor4, qui est la sortie de T.nnet.conv.conv2d. Cela ressemble à une combinaison de remodelage et de np.transpose. Dans le cas de ('x', 0, 'x', 'x'), la forme de self.b devient (1, self.b.shape [0], 1, 1).
À l'origine, la fonction d'activation de LeNet est tanh, mais cette fois nous utiliserons relu. relu est une fonction d'activation exprimée par une formule simple appelée max (0, x). Theano n'a pas de symbole relu, vous devrez donc le définir vous-même. Le symbole T.max () de Theano est écrit d'une manière légèrement spéciale car il ne peut pas contenir de valeurs réelles (bien qu'il puisse y avoir un moyen) et l'instruction if ne peut pas être utilisée pour le symbole. Voici un exemple d'implémentation relu.
class relu(object):
def __init__(self, input):
self._input = input
self.output = T.switch(self._input < 0, 0, self._input)
La couche de pooling est placée dans Theano et le symbole est défini dans theano.tensor.signal.pool.pool_2d. La couche de regroupement est facile à écrire car contrairement au pliage, il n'est pas nécessaire de préparer des symboles pour la mise à jour tels que les poids et les biais. Voici un exemple de mise en œuvre de la couche de regroupement.
from theano.tensor.signal import pool
class Pool2d(object):
def __init__(self, input, k_size, st, pad=0, mode='max'):
self._input = input
#Définition des symboles de couche de regroupement
self.output = pool.pool_2d(self._input,
(k_size, k_size), #Taille du noyau
ignore_border=True, #Traitement des bords(Fondamentalement vrai et ok,Pour plus de détails, consultez le document officiel)
st=(st, st), #foulée
padding=(pad, pad), #Rembourrage
mode=mode) #Types de mise en commun('max', 'sum', 'average_inc_pad', 'average_exc_pad')
La couche entièrement connectée est décrite par moi-même car le symbole n'est pas préparé dans le theano, mais il peut être exprimé par le calcul du produit interne de la matrice, et le symbole du produit interne est donné par T.dot (), ce n'est donc pas particulièrement difficile. Comme pour la couche convolutive, il y a des poids et des biais, donc chacun est défini. Voici un exemple de mise en œuvre de la couche entièrement connectée.
class FullyConnect(object):
def __init__(self, input, inunit, outunit):
self._input = input
#Définition du poids
W = np.asarray(
np.random.uniform(
low=-np.sqrt(6. / (inunit + outunit)),
high=np.sqrt(6. / (inunit + outunit)),
size=(inunit, outunit)
),
dtype=theano.config.floatX)
self.W = theano.shared(value=W, name='W', borrow=True)
#Définition du biais
b = np.zeros((outunit,), dtype=theano.config.floatX) #Initialiser avec zéro
self.b = theano.shared(value=b, name='b', borrow=True)
#Définition des symboles de couche entièrement connectés
self.output = T.dot(self._input, self.W) + self.b
#Enregistrer les paramètres mis à jour
self.params = [self.W, self.b]
La fonction de perte utilise l'entropie croisée softmax pour résoudre la classification à 10 classes de mnist. Le symbole softmax est fourni par T.nnet.softmax () dans Theano, utilisez donc ceci. Voici un exemple de mise en œuvre.
class softmax(object):
def __init__(self, input, y):
self._input = input
#Définition du symbole Softmax
self.output = nnet.softmax(self._input)
#Définition du symbole de l'entropie croisée(La formule est somme, mais ici nous utilisons la moyenne.)
self.cost = -T.mean(T.log(self.output)[T.arange(y.shape[0]), y])
y représente le symbole d'étiquette de l'enseignant. [T.arange (y.shape [0]), y] signifie ajouter de y [0] à y [y.shape [0] -1] lors de l'exécution de T.mean.
LeNet
Maintenant que nous avons défini chaque couche, passons à l'implémentation LeNet. Tout d'abord, préparez les données mnist. Les données pkl sont disponibles à http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz, donc téléchargez-les dans le dossier approprié. Extraire les données de train, de validation et de test d'ici. Voici un exemple de chargement de données.
import gzip
import cPickle
def shared_dataset(data_xy):
data_x, data_y = data_xy
set_x = theano.shared(np.asarray(data_x,
dtype=theano.config.floatX).reshape(-1,1,28,28),
borrow=True)
set_y = T.cast(theano.shared(np.asarray(data_y,
dtype=theano.config.floatX), borrow=True), 'int32')
return set_x, set_y
with open('/path/to/mnist.pkl.gz', 'rb') as f:
train_set, valid_set, test_set = cPickle.load(f)
train_set_x, train_set_y = shared_dataset(train_set)
valid_set_x, valid_set_y = shared_dataset(valid_set)
test_set_x, test_set_y = shared_dataset(test_set)
Ici, pour faciliter la mise en œuvre, les données sont définies comme un symbole de theano.shared, mais l'entrée peut être un tableau de numpy. Cependant, le définir dans theano.shared rend l'implémentation un peu plus propre. Ensuite, définissez les données d'entrée et le symbole de l'étiquette de l'enseignant. Puisque les données d'entrée sont de 4 dimensions (nombre de lots, nombre de canaux, longueur, largeur), il s'agit de T.tensor4 (), et puisque l'étiquette de l'enseignant est un vecteur de valeurs entières à une dimension, c'est T.ivector ().
x = T.tensor4() #Symbole des données d'entrée
y = T.ivector() #Symbole des données de sortie
De là, cela devient la définition de chaque couche. Chaque classe a été créée ci-dessus, donc c'est un peu plus facile à écrire.
conv1 = Conv2d(x, 20, 1, 5) #Avec x comme entrée, la sortie est de 20 canaux, l'entrée est de 1 canal, la taille du noyau 5
relu1 = relu(conv1.output) #Prendre la sortie de conv1 comme entrée
pool1 = Pool2d(relu1.output, 2, 2) #Prendre la sortie de relu1 comme entrée,Taille du noyau 2, foulée 2
conv2 = Conv2d(pool1.output, 50, 20, 5) #La sortie de poo1 est prise comme entrée, la sortie est de 50 canaux, l'entrée est de 20 canaux et la taille du noyau est de 5.
relu2 = relu(conv2.output) #Prendre la sortie de conv2 comme entrée
pool2 = Pool2d(relu2.output, 2, 2) #Prendre la sortie de relu2 comme entrée,Taille du noyau 2, foulée 2
fc1_input = pool2.output.flatten(2) #Le symbole de sortie de pool2 est T.aplatir pour tenseur4()Pour faire correspondre le symbole d'entrée de la couche entièrement connectée à l'aide de
fc1 = FullyConnect(fc1_input, 50*4*4, 500) #50 unités d'entrée*4*4(Nombre de canaux*Verticale*côté), Le nombre d'unités de sortie est de 500
relu3 = relu(fc1.output)
fc2 = FullyConnect(relu3.output, 500, 10) #500 unités d'entrée, 10 unités de sortie(Pour 10 classements)
loss = softmax(fc2.output, y)
Vous avez maintenant écrit la définition de LeNet. En remplaçant le symbole de sortie de chaque couche par le symbole d'entrée de la couche suivante, tous les symboles sont connectés et le calcul du gradient peut être effectué en même temps. C'est, ・ ・ ・ T.nnet.conv.conv2d (pool.pool2d (T.nnet.conv.conv2d ())) ・ ・ ・ Cela signifie qu'un long symbole tel que défini. Par conséquent, en donnant à T.grad () le symbole final (loss.cost cette fois), il est possible de calculer facilement le gradient de toutes les couches.
Enfin, nous définissons la fonction de formation et d'évaluation par des données de validation et des données de test. Jusqu'à présent, nous n'avons défini que des symboles, nous ne pouvons donc pas saisir de valeurs réelles pour l'apprentissage. Par conséquent, nous définissons le symbole comme la fonction ano. L'apprentissage peut être fait en définissant pour mettre à jour les paramètres à ce moment. Cette fois, nous mettrons à jour les paramètres à l'aide de SGD. Ce qui suit est un exemple de mise en œuvre de la fonction ano pour l'apprentissage.
#Lister tous les paramètres à apprendre
params = conv1.params + conv2.params + fc1.params + fc2.params
#Calculez le différentiel pour chaque paramètre
grads = T.grad(loss.cost, params)
#Définition du taux d'apprentissage
learning_rate = 0.001
#Définir l'expression de mise à jour
updates = [(param_i, param_i - learning_rate * grad_i) for param_i, grad_i in zip(params, grads)]
#Apprendre le théano.définir la fonction
index = T.lscalar()
batch_size = 128
train_model = theano.function(inputs=[index], #L'entrée est l'index des données d'entraînement
outputs=loss.cost, #La sortie est une perte.cost
updates=updates, #Formule de renouvellement
givens={
x: train_set_x[index: index + batch_size], #S'entraîner à x en entrée_set_Donnez x
y: train_set_y[index: index + batch_size] #S'entraîner à entrer y_set_Donnez y
})
Premièrement, params est une liste de symboles de poids et de biais pour chaque couche (car c'est l'ajout des listes). Étant donné une liste de variables, T.grads () renvoie une liste de symboles différenciés par chaque variable, donc grades est une liste avec des symboles différenciés par chaque paramètre de loss.cost. updates est également une liste d'expressions de mise à jour pour chaque paramètre. Vient ensuite la définition de train_model. Comme mentionné ci-dessus, conv1 à loss.cost sont un symbole qui prend x et y comme entrées. Dans train_model, x reçoit la valeur de train_set_x et y reçoit la valeur de train_set_y. train_set_x et train_set_y reçoivent l'index et se réfèrent aux données pour batch_size à partir de l'index reçu. Par conséquent, en donnant uniquement index comme argument à train_model, les valeurs de train_set_x et train_set_y d'index à index + batch_size sont données à x et y. Après cela, vous pouvez apprendre en appelant à plusieurs reprises ce train_model avec une instruction for ou autre.
for i in range(0, train_set_y.get_value().shape[0], batch_size):
train_model(i)
Enfin, définissez la fonction ano pour évaluer la précision du modèle d'apprentissage. Comme le paramètre n'est pas mis à jour dans l'évaluation, la sortie est définie sur le taux d'erreur. Puisqu'il y a un symbole softmax dans loss.output, utilisez-le pour définir le symbole qui calcule le taux d'erreur et définissez la fonction ano. qui évalue à l'aide du symbole de taux d'erreur.
pred = T.argmax(loss.output, axis=1) #Renvoie la classe avec la probabilité prédite la plus élevée
error = T.mean(T.neq(pred,y)) #Comparez la classe prédite avec l'étiquette correcte
test_model = theano.function(inputs=[index],
outputs=error,
givens={
x: test_set_x[index: index + batch_size],
y: test_set_y[index: index + batch_size]
})
val_model = theano.function(inputs=[index],
outputs=error,
givens={
x: test_set_x[index: index + batch_size],
y: test_set_y[index: index + batch_size]
})
Maintenant que la fonction d'évaluation pour les données de validation et de test a été définie, elle peut être évaluée en utilisant l'instruction for de la même manière que train_model.
test_losses = [test_model(i)
for i in range(0, test_set_y.get_value().shape[0], batch_size] #Enregistrer la perte moyenne par lot dans la liste
mean_test_loss = np.mean(test_losses) #Calculez la moyenne globale
Avec ce qui précède, le code d'apprentissage et d'évaluation de CNN utilisant theano a été écrit. Veuillez vérifier le résultat d'apprentissage réel en connectant tous les codes ci-dessus (j'ai changé le code que j'ai écrit pour Quiita à certains endroits et je l'ai écrit sans débogage, donc cela peut ne pas fonctionner lol). Si vous avez des questions, du code ou des explications, veuillez nous le faire savoir dans les commentaires.
J'ai expliqué la mise en œuvre du réseau de neurones convolutifs en utilisant Theano. À première vue, le code peut sembler long et fastidieux, mais une fois que vous avez défini les classes de couches pour votre propre convenance, il est facile à écrire. L'avantage de Theano est que vous pouvez le définir vous-même (même si c'est gênant). Les implémentations autres que LeNet peuvent créer divers modèles en modifiant la partie de définition de couche. De plus, les fonctions de perte et les méthodes de mise à jour des paramètres peuvent être librement décrites en définissant des symboles. J'espère qu'il sera utile à ceux qui utiliseront Theano à partir de maintenant.
Recommended Posts