Deep Learning from scratch Java Chapter 4 Apprentissage des réseaux de neurones

table des matières

4.2 Fonction de perte

Fonctions afin que vous puissiez facilement appeler diverses fonctions qui sont apparues jusqu'à présent ) Il est résumé dans la classe.

4.2.1 Erreur de somme des carrés

public static double mean_squared_error(INDArray y, INDArray t) {
    INDArray diff = y.sub(t);
    //Prend un produit intérieur avec votre propre matrice de translocation.
    return 0.5 * diff.mmul(diff.transpose()).getDouble(0);
}

public static double mean_squared_error2(INDArray y, INDArray t) {
    //Utilisez la fonction de distance quadratique du ND4J.
    return 0.5 * (double)y.squaredDistance(t);
}

INDArray t = Nd4j.create(new double[] {0, 0, 1, 0, 0, 0, 0, 0, 0, 0});
INDArray y = Nd4j.create(new double[] {0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0});
assertEquals(0.097500000000000031, mean_squared_error(y, t), 5e-6);
assertEquals(0.097500000000000031, mean_squared_error2(y, t), 5e-6);
// LossFunctions.LossFunction.Il peut également être réalisé en utilisant MSE.
assertEquals(0.097500000000000031, LossFunctions.score(t, LossFunctions.LossFunction.MSE, y, 0, 0, false), 5e-6);
y = Nd4j.create(new double[] {0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0});
assertEquals(0.59750000000000003, mean_squared_error(y, t), 5e-6);
assertEquals(0.59750000000000003, mean_squared_error2(y, t), 5e-6);
assertEquals(0.59750000000000003, LossFunctions.score(t, LossFunctions.LossFunction.MSE, y, 0, 0, false), 5e-6);

4.2.2 Erreur d'entropie croisée

public static double cross_entropy_error(INDArray y, INDArray t) {
    double delta = 1e-7;
    // Python: return -np.sum(t * np.log(y + delta))
    return -t.mul(Transforms.log(y.add(delta))).sumNumber().doubleValue();
}

INDArray t = Nd4j.create(new double[] {0, 0, 1, 0, 0, 0, 0, 0, 0, 0});
INDArray y = Nd4j.create(new double[] {0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0});
assertEquals(0.51082545709933802, cross_entropy_error(y, t), 5e-6);
//Cela peut également être réalisé à l'aide des fonctions de perte.
assertEquals(0.51082545709933802, LossFunctions.score(t, LossFunctions.LossFunction.MCXENT, y, 0, 0, false), 5e-6);
y = Nd4j.create(new double[] {0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0});
assertEquals(2.3025840929945458, cross_entropy_error(y, t), 5e-6);

4.2.3 Mini apprentissage par lots

Utilisez la classe DataSet de ND4J (https://nd4j.org/doc/org/nd4j/linalg/dataset/DataSet.html) pour extraire des échantillons de manière aléatoire.

//Chargez le jeu de données MNIST.
MNISTImages train = new MNISTImages(Constants.TrainImages, Constants.TrainLabels);
assertArrayEquals(new int[] {60000, 784}, train.normalizedImages().shape());
assertArrayEquals(new int[] {60000, 10}, train.oneHotLabels().shape());
//Extrayez au hasard 10 images.
//Stockez l'image et l'étiquette une fois dans le DataSet et retirez le nombre spécifié d'échantillons comme échantillon.
DataSet ds = new DataSet(train.normalizedImages(), train.oneHotLabels());
DataSet sample = ds.sample(10);
assertArrayEquals(new int[] {10, 784}, sample.getFeatureMatrix().shape());
assertArrayEquals(new int[] {10, 10}, sample.getLabels().shape());
//Pour confirmer que l'image de l'échantillon obtenu correspond à la valeur de l'étiquette
//Exportez l'exemple d'image sous forme de fichier PNG.
// one-Convertit une étiquette au format à chaud en la valeur d'étiquette d'origine. (Trouvez l'index de la valeur maximale de chaque ligne)
INDArray indexMax = Nd4j.getExecutioner().exec(new IAMax(sample.getLabels()), 1);
if (!Constants.SampleImagesOutput.exists())
    Constants.SampleImagesOutput.mkdirs();
for (int i = 0; i < 10; ++i) {
    //Le nom du fichier est"(Numéro de série)-(Valeur d'étiquette).png "Ce sera.
    File f = new File(Constants.SampleImagesOutput,
        String.format("%05d-%d.png ",
            i, indexMax.getInt(i)));
    MNISTImages.writePngFile(sample.getFeatures().getRow(i), train.rows, train.columns, f);
}

4.2.4 [Version compatible Batch] Implémentation de l'erreur d'entropie croisée

public static double cross_entropy_error2(INDArray y, INDArray t) {
    int batch_size = y.size(0);
    return -t.mul(Transforms.log(y.add(1e-7))).sumNumber().doubleValue() / batch_size;
}

//Pour des données uniques
INDArray t = Nd4j.create(new double[] {0, 0, 1, 0, 0, 0, 0, 0, 0, 0});
INDArray y = Nd4j.create(new double[] {0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0});
assertEquals(0.51082545709933802, cross_entropy_error2(y, t), 5e-6);
//Taille du lot=Dans le cas de 2 (2 données identiques)
t = Nd4j.create(new double[][] {
    {0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 1, 0, 0, 0, 0, 0, 0, 0}});
y = Nd4j.create(new double[][] {
    {0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0},
    {0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0}});
assertEquals(0.51082545709933802, cross_entropy_error2(y, t), 5e-6);
// todo: one-Implémentation d'une erreur d'entropie croisée lorsqu'elle n'est pas représentée comme chaude

4.3 Différenciation numérique

4.3.1 Différenciation

public static double numerical_diff_bad(DoubleUnaryOperator f, double x) {
    double h = 10e-50;
    return (f.applyAsDouble(x + h) - f.applyAsDouble(x)) / h;
}

assertEquals(0.0, (float)1e-50, 1e-52);

4.3.2 Exemple de différenciation numérique

public static double numerical_diff(DoubleUnaryOperator f, double x) {
    double h = 1e-4;
    return (f.applyAsDouble(x + h) - f.applyAsDouble(x - h)) / (h * 2);
}

public double function_1(double x) {
    return 0.01 * x * x + 0.1 * x;
}

public double function_1_diff(double x) {
    return 0.02 * x + 0.1;
}
assertEquals(0.200, numerical_diff(this::function_1, 5), 5e-6);
assertEquals(0.300, numerical_diff(this::function_1, 10), 5e-6);
assertEquals(0.200, function_1_diff(5), 5e-6);
assertEquals(0.300, function_1_diff(10), 5e-6);

4.3.3 Différenciation partielle

public double function_2(INDArray x) {
    double x0 = x.getDouble(0);
    double x1 = x.getDouble(1);
    return x0 * x0 + x1 * x1;
}

DoubleUnaryOperator function_tmp1 = x0 -> x0 * x0 + 4.0 * 4.0;
assertEquals(6.00, numerical_diff(function_tmp1, 3.0), 5e-6);
DoubleUnaryOperator function_tmp2 = x1 -> 3.0 * 3.0 + x1 * x1;
assertEquals(8.00, numerical_diff(function_tmp2, 4.0), 5e-6);

4.4 Dégradé

public double function_2(INDArray x) {
    double x0 = x.getFloat(0);
    double x1 = x.getFloat(1);
    return x0 * x0 + x1 * x1;
    //Ou
    // return x.mul(x).sumNumber().doubleValue();
    //Vous pouvez également prendre le produit interne avec la matrice transposée comme suit.
    // return x.mmul(x.transpose()).getDouble(0);
}

assertEquals("[6.00,8.00]", Util.string(Functions.numerical_gradient(this::function_2, Nd4j.create(new double[] {3.0, 4.0}))));
assertEquals("[0.00,4.00]", Util.string(Functions.numerical_gradient(this::function_2, Nd4j.create(new double[] {0.0, 2.0}))));
assertEquals("[6.00,0.00]", Util.string(Functions.numerical_gradient(this::function_2, Nd4j.create(new double[] {3.0, 0.0}))));

4.4.1 Méthode du gradient

public static INDArray gradient_descent(INDArrayFunction f, INDArray init_x, double lr, int step_num) {
    INDArray x = init_x;
    for (int i = 0; i < step_num; ++i) {
        INDArray grad = Functions.numerical_gradient(f, x);
        INDArray y = x.sub(grad.mul(lr));
//            System.out.printf("step:%d x=%s grad=%s x'=%s%n", i, x, grad, y);
        x = y;
    }
    return x;
}

// lr = 0.1
INDArray init_x = Nd4j.create(new double[] {-3.0, 4.0});
INDArray r = gradient_descent(this::function_2, init_x, 0.1, 100);
assertEquals("[-0.00,0.00]", Util.string(r));
assertEquals(-6.11110793e-10, r.getDouble(0), 5e-6);
assertEquals(8.14814391e-10, r.getDouble(1), 5e-6);
//Exemple de taux d'apprentissage trop élevé: lr = 10.0
r = gradient_descent(this::function_2, init_x, 10.0, 100);
//Ce ne sera pas le même que le résultat Python, mais dans tous les cas vous n'obtiendrez pas le résultat correct.
assertEquals("[-763,389.44,1,017,852.62]", Util.string(r));
//Exemple où le taux d'apprentissage est trop faible: lr = 1e-10
r = gradient_descent(this::function_2, init_x, 1e-10, 100);
assertEquals("[-3.00,4.00]", Util.string(r));

4.4.2 Gradient par rapport au réseau neuronal

static class simpleNet {

    /**poids*/
    public final INDArray W;

    /**
     *Poids 0.0 à 1.Initialisez avec un nombre aléatoire de l'ordre de 0.
     */
    public simpleNet() {
        try (Random r = new DefaultRandom()) {
            //Créez une matrice de nombres aléatoires basée sur une distribution gaussienne 2x3.
            W = r.nextGaussian(new int[] {2, 3});
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *Poids pour s'assurer que les résultats correspondent à ce livre
     *Permet de le donner de l'extérieur.
     */
    public simpleNet(INDArray W) {
        this.W = W.dup();   //Copiez de manière défensive.
    }

    public INDArray predict(INDArray x) {
        return x.mmul(W);
    }

    public double loss(INDArray x, INDArray t) {
        INDArray z = predict(x);
        INDArray y = Functions.softmax(z);
        double loss = Functions.cross_entropy_error(y, t);
        return loss;
    }
}

//Pour le poids, donnez la même valeur que ce livre au lieu d'un nombre aléatoire.
INDArray W = Nd4j.create(new double[][] {
    {0.47355232, 0.9977393, 0.84668094},
    {0.85557411, 0.03563661, 0.69422093},
});
simpleNet net = new simpleNet(W);
assertEquals("[[0.47,1.00,0.85],[0.86,0.04,0.69]]", Util.string(net.W));
INDArray x = Nd4j.create(new double[] {0.6, 0.9});
INDArray p = net.predict(x);
assertEquals("[1.05,0.63,1.13]", Util.string(p));
assertEquals(2, Functions.argmax(p).getInt(0));
INDArray t = Nd4j.create(new double[] {0, 0, 1});
assertEquals(0.92806853663411326, net.loss(x, t), 5e-6);
//La définition de fonction utilise une expression lambda.
INDArrayFunction f = dummy -> net.loss(x, t);
INDArray dW = Functions.numerical_gradient(f, net.W);
assertEquals("[[0.22,0.14,-0.36],[0.33,0.22,-0.54]]", Util.string(dW));

4.5 Mise en œuvre de l'algorithme d'apprentissage

4.5.1 Classe de réseau neuronal à deux couches

La classe pour le réseau neuronal à deux couches est TwoLayerNet. Les poids et les biais sont stockés dans TwoLayerParams au lieu de Map .. Le nombre aléatoire utilise l'interface ND4J Randam i. Cela prend environ 5 à 10 minutes dans mon environnement.

TwoLayerNet net = new TwoLayerNet(784, 100, 10);
assertArrayEquals(new int[] {784, 100}, net.parms.get("W1").shape());
assertArrayEquals(new int[] {1, 100}, net.parms.get("b1").shape());
assertArrayEquals(new int[] {100, 10}, net.parms.get("W2").shape());
assertArrayEquals(new int[] {1, 10}, net.parms.get("b2").shape());
try (Random r = new DefaultRandom()) {
    INDArray x = r.nextGaussian(new int[] {100, 784});
    INDArray t = r.nextGaussian(new int[] {100, 10});
    INDArray y = net.predict(x);
    assertArrayEquals(new int[] {100, 10}, y.shape());
    Params grads = net.numerical_gradient(x, t);
    assertArrayEquals(new int[] {784, 100}, grads.get("W1").shape());
    assertArrayEquals(new int[] {1, 100}, grads.get("b1").shape());
    assertArrayEquals(new int[] {100, 10}, grads.get("W2").shape());
    assertArrayEquals(new int[] {1, 10}, grads.get("b2").shape());
}

4.5.2 Mise en œuvre de l'apprentissage par mini-lots

L'utilisation des données MNIST prend beaucoup de temps. Dans mon environnement, cela prend environ 90 secondes pour chaque boucle, donc si vous bouclez 10 000 fois, cela prendra environ 10 jours.

//Chargez le jeu de données MNIST.
MNISTImages train = new MNISTImages(Constants.TrainImages, Constants.TrainLabels);
INDArray x_train = train.normalizedImages();
INDArray t_train = train.oneHotLabels();
assertArrayEquals(new int[] {60000, 784}, x_train.shape());
assertArrayEquals(new int[] {60000, 10}, t_train.shape());
List<Double> train_loss_list =  new ArrayList<>();
int iters_num = 10000;
// int train_size = images.size(0);
 int batch_size = 100;
double learning_rate = 0.1;
TwoLayerNet network = new TwoLayerNet(784, 50, 10);
// batch_Les données de taille sont extraites au hasard.
for (int i = 0; i < iters_num; ++i) {
    long start = System.currentTimeMillis();
    //Obtenez un mini lot
    DataSet ds = new DataSet(x_train, t_train);
    DataSet sample = ds.sample(batch_size);
    INDArray x_batch = sample.getFeatureMatrix();
    INDArray t_batch = sample.getLabels();
    Params grad =  network.numerical_gradient(x_batch, t_batch);
    network.parms.update((p, a) -> p.subi(a.mul(learning_rate)), grad);
    //Registre des progrès d'apprentissage
    double loss = network.loss(x_batch, t_batch);
    train_loss_list.add(loss);
    System.out.printf("iteration %d loss=%f elapse=%dms%n",
        i, loss, System.currentTimeMillis() - start);
}

4.5.3 Évaluation avec données d'essai

L'utilisation des données MNIST prend beaucoup de temps. Dans mon environnement, cela prend environ 90 secondes pour chaque boucle, donc si vous bouclez 10 000 fois, cela prendra environ 10 jours. Par conséquent, je ne l'ai pas exécuté jusqu'à la fin, mais il a fallu 4,6 heures pour s'entraîner jusqu'à ce que la précision de reconnaissance des données d'apprentissage et des données de test atteigne 90% ou plus, et le nombre de boucles était de 214. Comme vous pouvez le voir sur le graphique du livre, il monte assez rapidement jusqu'à environ 80%, il peut donc être préférable de s'arrêter lorsque la précision de reconnaissance dépasse le seuil, au lieu de boucler 10 000 fois.

//Chargez le jeu de données MNIST.
MNISTImages train = new MNISTImages(Constants.TrainImages, Constants.TrainLabels);
INDArray x_train = train.normalizedImages();
INDArray t_train = train.oneHotLabels();
MNISTImages test = new MNISTImages(Constants.TestImages, Constants.TestLabels);
INDArray x_test = test.normalizedImages();
INDArray t_test = test.oneHotLabels();
assertArrayEquals(new int[] {60000, 784}, x_train.shape());
assertArrayEquals(new int[] {60000, 10}, t_train.shape());
List<Double> train_loss_list =  new ArrayList<>();
List<Double> train_acc_list = new ArrayList<>();
List<Double> test_acc_list = new ArrayList<>();
int iters_num = 10000;
int train_size = x_train.size(0);
int batch_size = 100;
double learning_rate = 0.01;
int iter_per_epoch = Math.max(train_size / batch_size, 1);
TwoLayerNet network = new TwoLayerNet(784, 50, 10);
// batch_Les données de taille sont extraites au hasard.
for (int i = 0; i < iters_num; ++i) {
    long start = System.currentTimeMillis();
    //Obtenez un mini lot
    DataSet ds = new DataSet(x_train, t_train);
    DataSet sample = ds.sample(batch_size);
    INDArray x_batch = sample.getFeatureMatrix();
    INDArray t_batch = sample.getLabels();
    Params grad =  network.numerical_gradient(x_batch, t_batch);
    network.parms.update((p, a) -> p.subi(a.mul(learning_rate)), grad);
    //Registre des progrès d'apprentissage
    double loss = network.loss(x_batch, t_batch);
    train_loss_list.add(loss);
    //Calcul du système de reconnaissance pour chaque époque
    if (i % iter_per_epoch == 0) {
        double train_acc = network.accuracy(x_train, t_train);
        double test_acc = network.accuracy(x_test, t_test);
        train_acc_list.add(train_acc);
        test_acc_list.add(test_acc);
        System.out.printf("train acc, test acc | %s, %s%n",
            train_acc, test_acc);
    }
    System.out.printf("iteration %d loss=%f elapse=%dms%n",
        i, loss, System.currentTimeMillis() - start);
}

Recommended Posts

Deep Learning from scratch Java Chapter 4 Apprentissage des réseaux de neurones
[Deep Learning from scratch] dans Java 3. Réseau neuronal
Deep Learning Java à partir de zéro Chapitre 3 Réseau neuronal
Deep Learning Java à partir de zéro Chapitre 1 Introduction
Deep Learning Java à partir de zéro Chapitre 2 Perceptron
Deep Learning Java from scratch 6.4 Régularisation
Deep Learning Java from scratch Chapter 5 Méthode de propagation de retour d'erreur
Étudiez le Deep Learning à partir de zéro en Java.
Deep Learning Java from scratch 6.1 Mise à jour des paramètres
Deep Learning Java from scratch 6.3 Normalisation par lots
Deep Learning Java from scratch 6.2 Valeur initiale du poids
Configuration PC la plus rapide pour un apprentissage en profondeur à partir de zéro
[Apprentissage profond à partir de zéro] 2. Il n'existe pas de NumPy en Java.
[Deep Learning from scratch] en Java 1. Pour le moment, différenciation et différenciation partielle
La vie Java à partir de zéro
Backpropagation Neural Network Java Code pour l'apprentissage du modèle de porte XOR
[DL4J] Premier apprentissage en profondeur Java (reconnaissance de caractères manuscrits à l'aide d'un réseau neuronal entièrement connecté)
Apprendre Java (0)
Java scratch scratch
Premiers pas pour l'apprentissage profond en Java
Efficace Java Chapitre 2
Jour d'apprentissage Java 5
Effective Java Chapitre 6 34-35
Effective Java Chapitre 4 15-22
Java efficace Chapitre 3
java learning day 2
java learning day 1
Créer un environnement VS Code + WSL + Java + Gradle à partir de zéro
[Note] Créez un environnement Java à partir de zéro avec docker
Apprentissage rapide de Java "Introduction?" Partie 3 Parler de programmation