Soudain, j'ai commencé à étudier "Deep Learning from scratch - la théorie et l'implémentation du deep learning appris avec Python". C'est un mémo du voyage.
L'environnement d'exécution est macOS Mojave + Anaconda 2019.10, et la version Python est 3.7.4. Pour plus de détails, reportez-vous au Chapitre 1 de ce mémo.
(Vers d'autres chapitres de ce mémo: Chapitre 1 / Chapitre 2 / Chapitre 3 / Chapitre 4 / Chapitre 5 / [Chapitre 6](https: / /qiita.com/segavvy/items/ca4ac4c9ee1a126bff41) / Chapitre 7 / Chapitre 8 / Résumé)
Ce chapitre décrit l'apprentissage des réseaux de neurones.
Habituellement, une personne tire de la régularité, pense à un algorithme, l'écrit dans un programme et laisse un ordinateur l'exécuter. L'apprentissage automatique, les réseaux de neurones et l'apprentissage profond permettent à l'ordinateur de réfléchir à cet algorithme.
Dans ce livre, pour les données que vous souhaitez traiter, celles qui nécessitent l'extraction (vectorisation, etc.) de la quantité de fonctionnalités qu'une personne pense à l'avance sont du "machine learning", et le "machine learning" est laissé à l'extraction de la quantité de fonctionnalités. Celui qui permet aux données brutes d'être transmises telles quelles est défini comme "réseau neuronal (apprentissage en profondeur)". Cette définition peut sembler un peu approximative, mais je ne suis pas très intéressé par l'utilisation correcte des mots, alors je vais continuer sans m'en soucier.
Il explique les données d'entraînement, les données de test, le surentraînement, etc., mais il n'y avait pas d'obstacle particulier.
Ceci est une explication de la somme des erreurs de carrés et d'entropie croisée qui sont souvent utilisées comme fonctions de perte, et une explication de l'apprentissage par mini-lots qui utilise une partie des données d'apprentissage. Il n'y avait pas non plus de pierre d'achoppement particulière ici. Il semble que l'utilisation de toutes les données d'entraînement est acceptable, mais cela prend du temps et est inefficace. Je pense que c'est comme une soi-disant enquête par sondage.
Il est également expliqué que la raison pour laquelle la précision de reconnaissance ne peut pas être utilisée à la place de la fonction de perte est que la précision de reconnaissance ne répond pas aux changements infimes du résultat et change de manière discontinue, de sorte qu'elle ne peut pas être bien apprise. Cela ne vous viendra peut-être pas au début, mais je pense que vous vous fâcherez après l'explication de la prochaine différenciation.
C'est un commentaire sur la différenciation. L'explication de l'erreur d'arrondi au moment du montage est pratique. Lorsque vous entendez les mots «différentiel» et «partiellement différencié», cela semble difficile, mais comment le résultat change-t-il si vous modifiez un peu la valeur? C'est pourquoi je peux avancer sans avoir à revoir les mathématiques du secondaire.
À propos, le symbole $ \ partial $ qui apparaît par différenciation est lu comme Wikipedia Del, Dee, Partial Dee, Round Dee, etc. C'est vrai.
Même ainsi, Python peut facilement passer une fonction comme argument. Quand j'étais programmeur, j'étais principalement C / C ++, mais je détestais la notation des pointeurs de fonction parce que c'était vraiment déroutant: suer:
Le gradient est le différentiel partiel de toutes les variables en tant que vecteur. Cela en soi n'est pas difficile.
Il est agréable de voir que la valeur est arrondie et affichée lors de la sortie d'une fraction avec un tableau NumPy.
python
>>> import numpy as np
>>> a = np.array([1.00000000123, 2.99999999987])
>>> a
array([1., 3.])
Cependant, cela peut être un problème s'il est enroulé sans autorisation, et lorsque j'ai recherché de quel type de spécifications il s'agissait, il y avait une fonction pour définir la méthode d'affichage. numpy.set_printoptions
, comment afficher des fractions et de nombreux éléments Vous pouvez modifier la méthode d'abréviation du cas. Par exemple, si vous spécifiez un grand nombre de chiffres après la virgule décimale avec precision
, il sera affiché sans être correctement arrondi.
python
>>> np.set_printoptions(precision=12)
>>> a
array([1.00000000123, 2.99999999987])
C'est pratique!
Le mot «méthode de descente de gradient» apparaît dans le texte, qui a été traduit par «méthode de descente la plus raide» dans le matériel didactique lorsque j'ai étudié auparavant.
De plus, le symbole $ \ eta $ qui indique le taux d'apprentissage apparaît, mais cela se lit comme eta en lettres grecques (je me suis souvenu comment le lire quand j'ai étudié auparavant, mais j'ai complètement oublié et googlé : transpiration :).
J'utilise numerical_gradient (f, x)
pour trouver le gradient, mais la fonction que je passe à ce f
est
python
def f(W):
return net.loss(x, t)
Est-ce? Cette fonction utilise-t-elle l'argument «W»? J'étais un peu confus, mais parce que j'essaie d'utiliser la forme de la fonction de numerical_gradient (f, x)
implémentée dans "4.4 Gradient" tel quel, l'argument W
est un mannequin. Bien sûr, la classe simpleNet
a son propre poids W
, vous n'avez donc pas besoin de passer le poids W
à la fonction de perte simpleNet.loss
. Il est difficile de comprendre s'il existe un mannequin, j'ai donc décidé de l'implémenter sans argument.
Ici aussi, nous devons modifier numerical_gradient
pour qu'il puisse être utilisé dans un tableau multidimensionnel.
À partir de maintenant, nous allons mettre en œuvre la méthode probabiliste de descente de gradient (SGD) en utilisant ce que nous avons appris jusqu'à présent.
Le premier est «functions.py», qui est une collection de fonctions nécessaires.
functions.py
# coding: utf-8
import numpy as np
def sigmoid(x):
"""Fonction Sigmaid
Puisqu'il déborde dans la mise en œuvre du livre, il est corrigé en se référant au site suivant.
http://www.kamishima.net/mlmpyja/lr/sigmoid.html
Args:
x (numpy.ndarray):contribution
Returns:
numpy.ndarray:production
"""
#Corriger x sur une plage qui ne déborde pas
sigmoid_range = 34.538776394910684
x2 = np.maximum(np.minimum(x, sigmoid_range), -sigmoid_range)
#Fonction Sigmaid
return 1 / (1 + np.exp(-x2))
def softmax(x):
"""Fonction Softmax
Args:
x (numpy.ndarray):contribution
Returns:
numpy.ndarray:production
"""
#Pour le traitement par lots, x est(Nombre de lots, 10)Il devient un tableau bidimensionnel de.
#Dans ce cas, il est nécessaire de bien calculer pour chaque image en utilisant la diffusion.
#Ici, np afin qu'il puisse être partagé en 1 et 2 dimensions..max()Et np.sum()Est axe=-Calculé par 1
#Keepdims pour qu'il puisse être diffusé tel quel=Vrai pour maintenir la dimension.
c = np.max(x, axis=-1, keepdims=True)
exp_a = np.exp(x - c) #Mesures de débordement
sum_exp_a = np.sum(exp_a, axis=-1, keepdims=True)
y = exp_a / sum_exp_a
return y
def numerical_gradient(f, x):
"""Calcul du gradient
Args:
f (function):Fonction de perte
x (numpy.ndarray):Un tableau de paramètres de poids pour lesquels vous souhaitez vérifier le dégradé
Returns:
numpy.ndarray:Pente
"""
h = 1e-4
grad = np.zeros_like(x)
# np.Énumérer les éléments d'un tableau multidimensionnel avec nditer
it = np.nditer(x, flags=['multi_index'])
while not it.finished:
idx = it.multi_index # it.multi_index est le numéro de l'élément dans la liste
tmp_val = x[idx] #Enregistrer la valeur d'origine
# f(x + h)Calculs de
x[idx] = tmp_val + h
fxh1 = f()
# f(x - h)Calculs de
x[idx] = tmp_val - h
fxh2 = f()
#Calculer le gradient
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val #Valeur de retour
it.iternext()
return grad
def cross_entropy_error(y, t):
"""Calcul de l'erreur d'entropie croisée
Args:
y (numpy.ndarray):Sortie de réseau neuronal
t (numpy.ndarray):Étiquette de réponse correcte
Returns:
float:Erreur d'entropie croisée
"""
#Façonner la forme s'il n'y a qu'une seule donnée
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
#Calculer l'erreur et normaliser par le nombre de lots
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
def sigmoid_grad(x):
"""Fonctions apprises au chapitre 5. Requis lors de l'utilisation de la méthode de propagation de retour d'erreur.
"""
return (1.0 - sigmoid(x)) * sigmoid(x)
softmax
est [Mémo qu'un amateur a trébuché dans le Deep Learning fait de toutes pièces: Chapitre 3](https://qiita.com/segavvy/items/6d79d0c3b4367869f4ea#35-%E5%87%BA%E5%8A% 9B% E5% B1% A4% E3% 81% AE% E8% A8% AD% E8% A8% 88) J'ai essayé de le rendre encore plus rafraîchissant. Je me réfère au plan d'amélioration du code de fonction softmax # 45 dans le numéro du référentiel GitHub de ce livre. ..
numerical_gradient
a éliminé l'argument de fonction passé dans l'argument f
, comme mentionné ci-dessus. Il fait également une boucle sur numpy.nditer
pour accueillir des tableaux multidimensionnels. Dans le code du livre, ʻop_flags = ['readwrite'] ʻest spécifié lors de l'utilisation de numpy.nditer
, mais l'index pour accéder à x
est juste récupéré par multi_index
. , J'ai omis ʻop_flags ("op_flags = ['readonly']
) car je ne mets pas à jour les objets énumérés par l'itérateur. Voir Itérer sur les tableaux # Modifier les valeurs des tableaux pour plus de détails.
La dernière fonction sigmoid_grad
sera apprise au chapitre 5, mais il est nécessaire de raccourcir le temps de traitement (décrit plus loin), donc elle est implémentée comme dans le livre.
Vient ensuite two_layer_net.py
, qui implémente un réseau neuronal à deux couches.
two_layer_net.py
# coding: utf-8
from functions import sigmoid, softmax, numerical_gradient, \
cross_entropy_error, sigmoid_grad
import numpy as np
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,
weight_init_std=0.01):
"""Réseau neuronal à deux couches
Args:
input_size (int):Nombre de neurones dans la couche d'entrée
hidden_size (int):Nombre de neurones de couche cachée
output_size (int):Nombre de neurones dans la couche de sortie
weight_init_std (float, optional):Paramètre de réglage de la valeur initiale du poids. La valeur par défaut est 0.01。
"""
#Initialisation du poids
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
def predict(self, x):
"""Inférence par réseau neuronal
Args:
x (numpy.ndarray):Entrée dans le réseau neuronal
Returns:
numpy.ndarray:Sortie de réseau neuronal
"""
#Récupération des paramètres
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
#Calcul du réseau neuronal (avant)
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
def loss(self, x, t):
"""Calcul de la valeur de la fonction de perte
Args:
x (numpy.ndarray):Entrée dans le réseau neuronal
t (numpy.ndarray):Étiquette de réponse correcte
Returns:
float:Valeur de la fonction de perte
"""
#inférence
y = self.predict(x)
#Calcul de l'erreur d'entropie croisée
loss = cross_entropy_error(y, t)
return loss
def accuracy(self, x, t):
"""Calcul de la précision de la reconnaissance
Args:
x (numpy.ndarray):Entrée dans le réseau neuronal
t (numpy.ndarray):Étiquette de réponse correcte
Returns:
float:Précision de reconnaissance
"""
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / x.shape[0]
return accuracy
def numerical_gradient(self, x, t):
"""Calcul du gradient pour les paramètres de poids
Args:
x (numpy.ndarray):Entrée dans le réseau neuronal
t (numpy.ndarray):Étiquette de réponse correcte
Returns:
dictionary:Un dictionnaire qui stocke les dégradés
"""
grads = {}
grads['W1'] = \
numerical_gradient(lambda: self.loss(x, t), self.params['W1'])
grads['b1'] = \
numerical_gradient(lambda: self.loss(x, t), self.params['b1'])
grads['W2'] = \
numerical_gradient(lambda: self.loss(x, t), self.params['W2'])
grads['b2'] = \
numerical_gradient(lambda: self.loss(x, t), self.params['b2'])
return grads
def gradient(self, x, t):
"""Fonctions apprises au chapitre 5. Implémentation de la méthode de propagation des erreurs
"""
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
dz1 = np.dot(dy, W2.T)
da1 = sigmoid_grad(a1) * dz1
grads['W1'] = np.dot(x.T, da1)
grads['b1'] = np.sum(da1, axis=0)
return grads
C'est presque le même que le code du livre. Le dernier gradient
est ce que vous apprendrez au chapitre 5, mais comme il est nécessaire de raccourcir le temps de traitement (décrit plus loin), il est implémenté comme dans le livre.
Enfin, la mise en œuvre du mini-batch learning.
mnist.py
# coding: utf-8
import numpy as np
import matplotlib.pylab as plt
import os
import sys
from two_layer_net import TwoLayerNet
sys.path.append(os.pardir) #Ajouter le répertoire parent au chemin
from dataset.mnist import load_mnist
#Lire les données d'entraînement MNIST et les données de test
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
#Paramètres des hyper paramètres
iters_num = 10000 #Nombre de mises à jour
batch_size = 100 #Taille du lot
learning_rate = 0.1 #Taux d'apprentissage
#Enregistrer la liste des résultats
train_loss_list = [] #Modifications de la valeur de la fonction de perte
train_acc_list = [] #Précision de reconnaissance des données d'entraînement
test_acc_list = [] #Précision de reconnaissance des données de test
train_size = x_train.shape[0] #Taille des données d'entraînement
iter_per_epoch = max(train_size / batch_size, 1) #Nombre d'itérations par époque
#Génération de travail neuronal à deux couches
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
#Commencer à apprendre
for i in range(iters_num):
#Mini génération de lots
batch_mask = np.random.choice(train_size, batch_size, replace=False)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#Calcul du gradient
# grad = network.numerical_gradient(x_batch, t_batch)Comme il est lent, utilisez la méthode de propagation des erreurs.
grad = network.gradient(x_batch, t_batch)
#Mise à jour des paramètres de poids
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
#Calcul de la valeur de la fonction de perte
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
#Calcul de la précision de la reconnaissance pour chaque époque
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
#Affichage de la progression
print(f"[Nombre de mises à jour]{i: >4} [Valeur de la fonction de perte]{loss:.4f} "
f"[Reconnaissance de l'exactitude des données d'entraînement]{train_acc:.4f} [Précision de reconnaissance des données de test]{test_acc:.4f}")
#Tracez la transition de la valeur de la fonction de perte
x = np.arange(len(train_loss_list))
plt.plot(x, train_loss_list, label='loss')
plt.xlabel("iteration")
plt.ylabel("loss")
plt.xlim(left=0)
plt.ylim(bottom=0)
plt.show()
#Dessinez la transition de la précision de reconnaissance des données d'entraînement et des données de test
x2 = np.arange(len(train_acc_list))
plt.plot(x2, train_acc_list, label='train acc')
plt.plot(x2, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.xlim(left=0)
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
Dans le code du livre, [numpy.random.choice
](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.choice" utilisé pour la génération de mini-lots Il n'y a pas de spécification de replace = False
dans l'argument de .html), mais j'ai essayé de le spécifier car il semble que le même élément puisse être extrait plus d'une fois.
À l'origine, le gradient est calculé par différenciation numérique en utilisant TwoLayerNet.numerical_gradient
, mais la vitesse de traitement est lente et dans l'environnement à portée de main ~ ~ Il semble que 10000 mises à jour ne seront pas terminées même si cela prend un jour ~ ~ Il ne peut être mis à jour qu'environ 600 fois en une demi-journée et il semble qu'il faudra environ 8 jours pour le mettre à jour 10 000 fois. Par conséquent, en suivant les conseils du livre, j'ai utilisé TwoLayerNet.gradient
, qui implémente la méthode de propagation d'erreur apprise au chapitre 5.
Enfin, la transition de la valeur de la fonction de perte et la transition de la précision de reconnaissance des données d'apprentissage et des données de test sont affichées dans un graphique.
Voici les résultats de l'exécution.
[Nombre de mises à jour] 0 [Valeur de la fonction de perte]2.2882 [Reconnaissance de l'exactitude des données d'entraînement]0.1044 [Précision de reconnaissance des données de test]0.1028
[Nombre de mises à jour] 600 [Valeur de la fonction de perte]0.8353 [Reconnaissance de l'exactitude des données d'entraînement]0.7753 [Précision de reconnaissance des données de test]0.7818
[Nombre de mises à jour]1200 [Valeur de la fonction de perte]0.4573 [Reconnaissance de l'exactitude des données d'entraînement]0.8744 [Précision de reconnaissance des données de test]0.8778
[Nombre de mises à jour]1800 [Valeur de la fonction de perte]0.4273 [Reconnaissance de l'exactitude des données d'entraînement]0.8972 [Précision de reconnaissance des données de test]0.9010
[Nombre de mises à jour]2400 [Valeur de la fonction de perte]0.3654 [Reconnaissance de l'exactitude des données d'entraînement]0.9076 [Précision de reconnaissance des données de test]0.9098
[Nombre de mises à jour]3000 [Valeur de la fonction de perte]0.2816 [Reconnaissance de l'exactitude des données d'entraînement]0.9142 [Précision de reconnaissance des données de test]0.9146
[Nombre de mises à jour]3600 [Valeur de la fonction de perte]0.3238 [Reconnaissance de l'exactitude des données d'entraînement]0.9195 [Précision de reconnaissance des données de test]0.9218
[Nombre de mises à jour]4200 [Valeur de la fonction de perte]0.2017 [Reconnaissance de l'exactitude des données d'entraînement]0.9231 [Précision de reconnaissance des données de test]0.9253
[Nombre de mises à jour]4800 [Valeur de la fonction de perte]0.1910 [Reconnaissance de l'exactitude des données d'entraînement]0.9266 [Précision de reconnaissance des données de test]0.9289
[Nombre de mises à jour]5400 [Valeur de la fonction de perte]0.1528 [Reconnaissance de l'exactitude des données d'entraînement]0.9306 [Précision de reconnaissance des données de test]0.9320
[Nombre de mises à jour]6000 [Valeur de la fonction de perte]0.1827 [Reconnaissance de l'exactitude des données d'entraînement]0.9338 [Précision de reconnaissance des données de test]0.9347
[Nombre de mises à jour]6600 [Valeur de la fonction de perte]0.1208 [Reconnaissance de l'exactitude des données d'entraînement]0.9362 [Précision de reconnaissance des données de test]0.9375
[Nombre de mises à jour]7200 [Valeur de la fonction de perte]0.1665 [Reconnaissance de l'exactitude des données d'entraînement]0.9391 [Précision de reconnaissance des données de test]0.9377
[Nombre de mises à jour]7800 [Valeur de la fonction de perte]0.1787 [Reconnaissance de l'exactitude des données d'entraînement]0.9409 [Précision de reconnaissance des données de test]0.9413
[Nombre de mises à jour]8400 [Valeur de la fonction de perte]0.1564 [Reconnaissance de l'exactitude des données d'entraînement]0.9431 [Précision de reconnaissance des données de test]0.9429
[Nombre de mises à jour]9000 [Valeur de la fonction de perte]0.2361 [Reconnaissance de l'exactitude des données d'entraînement]0.9449 [Précision de reconnaissance des données de test]0.9437
[Nombre de mises à jour]9600 [Valeur de la fonction de perte]0.2183 [Reconnaissance de l'exactitude des données d'entraînement]0.9456 [Précision de reconnaissance des données de test]0.9448
En regardant les résultats, la précision de la reconnaissance était déjà d'environ 94,5%, ce qui dépassait la précision de reconnaissance des paramètres appris préparés au chapitre 3.
Il peut être bon de lire le chapitre 4 comme un livre, mais il a été assez difficile de procéder pendant sa mise en œuvre. (Je voulais une explication de la partie où la fonction softmax et la fonction de différenciation numérique correspondent à un tableau multidimensionnel ...)
C'est tout pour ce chapitre. Si vous avez des erreurs, je vous serais reconnaissant de bien vouloir les signaler. (Vers d'autres chapitres de ce mémo: Chapitre 1 / Chapitre 2 / Chapitre 3 / Chapitre 4 / Chapitre 5 / [Chapitre 6](https: / /qiita.com/segavvy/items/ca4ac4c9ee1a126bff41) / Chapitre 7 / Chapitre 8 / Résumé)