Deep Learning Java à partir de zéro Chapitre 3 Réseau neuronal

table des matières

3.2 Fonction d'activation

3.2.3 Mise en œuvre de la fonction step

La fonction d'étape peut être implémentée comme suit. Ici, j'ai essayé de rendre possible la cartographie d'INDArray avec une fonction simple (DoubleFunction).

public static double step_function(double x) {
    if (x > 0)
        return 1.0;
    else
        return 0.0;
}

public static <T extends Number> INDArray map(INDArray x, DoubleFunction<T> func) {
    int size = x.length();
    INDArray result = Nd4j.create(size);
    for (int i = 0; i < size; ++i)
        result.put(0, i, func.apply(x.getDouble(i)));
    return result;
}

public static INDArray step_function(INDArray x) {
    return map(x, d -> d > 0.0 ? 1 : 0);
}

INDArray x = Nd4j.create(new double[] {-1.0, 1.0, 2.0});
assertEquals("[-1.00,1.00,2.00]", Util.string(x));
assertEquals("[0.00,1.00,1.00]", Util.string(step_function(x)));

3.2.4 Implémentation de la fonction sigmoïde

La fonction sigmoïde peut être implémentée comme suit. ND4J fournit une fonction sigmoïde dans la classe Transforms, vous pouvez donc également l'utiliser. ..

public static double sigmoid(double x) {
    return (double)(1.0 / (1.0 + Math.exp(-x)));
}

public static INDArray sigmoid(INDArray x) {
    //Les opérateurs ne peuvent pas être surchargés en Java
    //Décrit en appelant une méthode.
    return Transforms.exp(x.neg()).add(1.0).rdiv(1.0);
    //Alternativement, il peut être implémenté comme suit en utilisant la carte décrite ci-dessus.
    // return map(x, d -> sigmoid(d));
}


INDArray x = Nd4j.create(new double[] {-1.0, 1.0, 2.0});
assertEquals("[0.27,0.73,0.88]", Util.string(sigmoid(x)));
assertEquals("[0.27,0.73,0.88]", Util.string(Transforms.sigmoid(x)));
INDArray t = Nd4j.create(new double[] {1.0, 2.0, 3.0});
// A.rdiv(k)Est k divisé par chaque élément de A.
assertEquals("[1.00,0.50,0.33]", Util.string(t.rdiv(1.0)));
}

3.2.7 Fonction ReLU

La fonction ReLU peut être implémentée comme suit. ND4J fournit une fonction relu dans la classe Transforms, vous pouvez donc également l'utiliser.

public static INDArray relu(INDArray x) {
    return map(x, d -> Math.max(0.0, d));
}

INDArray x = Nd4j.create(new double[] {-4, -2, 0, 2, 4});
assertEquals("[0.00,0.00,0.00,2.00,4.00]", Util.string(relu(x)));
assertEquals("[0.00,0.00,0.00,2.00,4.00]", Util.string(Transforms.relu(x)));

3.3 Calcul du tableau multidimensionnel

3.3.1 Tableau multidimensionnel

//Tableau unidimensionnel
INDArray A = Nd4j.create(new double[] {1, 2, 3, 4});
assertEquals("[1.00,2.00,3.00,4.00]", Util.string(A));
//Dans ND4J, le tableau unidimensionnel est un tableau bidimensionnel 1xN.
assertArrayEquals(new int[] {1, 4}, A.shape());
//Dans ND4J, le nombre de dimensions est le rang()Obtenu par la méthode.
assertEquals(2, A.rank());
assertEquals(1, A.size(0));  //Nombre de lignes
assertEquals(4, A.size(1));  //Le nombre de colonnes
//Tableau bidimensionnel
INDArray B = Nd4j.create(new double[][] {{1, 2}, {3, 4}, {5, 6}});
assertEquals("[[1.00,2.00],[3.00,4.00],[5.00,6.00]]", Util.string(B));
assertEquals(2, B.rank());
assertArrayEquals(new int[] {3, 2}, B.shape());

3.3.2 Produit matriciel

Dans ND4J, le produit interne est calculé par INDArray.mmul (INDArray).

INDArray A = Nd4j.create(new double[][] {{1, 2}, {3, 4}});
assertArrayEquals(new int[] {2, 2}, A.shape());
INDArray B = Nd4j.create(new double[][] {{5, 6}, {7, 8}});
assertArrayEquals(new int[] {2, 2}, B.shape());
assertEquals("[[19.00,22.00],[43.00,50.00]]", Util.string(A.mmul(B)));

A = Nd4j.create(new double[][] {{1, 2, 3}, {4, 5, 6}});
assertArrayEquals(new int[] {2, 3}, A.shape());
B = Nd4j.create(new double[][] {{1, 2}, {3, 4}, {5, 6}});
assertArrayEquals(new int[] {3, 2}, B.shape());
assertEquals("[[22.00,28.00],[49.00,64.00]]", Util.string(A.mmul(B)));

INDArray C = Nd4j.create(new double[][] {{1, 2}, {3, 4}});
assertArrayEquals(new int[] {2, 2}, C.shape());
assertArrayEquals(new int[] {2, 3}, A.shape());
try {
    //Dans ND4J, s'il y a une erreur dans le nombre d'éléments de la matrice qui prend le produit interne,
    //Lève une ND4JIllegalStateException.
    A.mmul(C);
    fail();
} catch (ND4JIllegalStateException e) {
    assertEquals(
        "Cannot execute matrix multiplication: [2, 3]x[2, 2]: "
        + "Column of left array 3 != rows of right 2"
        , e.getMessage());
}

A = Nd4j.create(new double[][] {{1, 2}, {3, 4}, {5, 6}});
assertArrayEquals(new int[] {3, 2}, A.shape());
B = Nd4j.create(new double[] {7, 8});
assertArrayEquals(new int[] {1, 2}, B.shape());
//Dans ND4J, un tableau unidimensionnel est une matrice de 1 × N lignes.
//Transposer pour trouver le produit()Doit être transposé dans la méthode.
assertArrayEquals(new int[] {2, 1}, B.transpose().shape());
assertEquals("[23.00,53.00,83.00]", Util.string(A.mmul(B.transpose())));

3.3.3 Le produit de la matrice du réseau de neurones

INDArray X = Nd4j.create(new double[] {1, 2});
assertArrayEquals(new int[] {1, 2}, X.shape());
INDArray W = Nd4j.create(new double[][] {{1, 3, 5}, {2, 4, 6}});
assertEquals("[[1.00,3.00,5.00],[2.00,4.00,6.00]]", Util.string(W));
assertArrayEquals(new int[] {2, 3}, W.shape());
INDArray Y = X.mmul(W);
assertEquals("[5.00,11.00,17.00]", Util.string(Y));

3.4 Mise en œuvre d'un réseau neuronal à 3 couches

3.4.2 Mise en œuvre de la transmission du signal dans chaque couche

INDArray X = Nd4j.create(new double[] {1.0, 0.5});
INDArray W1 = Nd4j.create(new double[][] {{0.1, 0.3, 0.5}, {0.2, 0.4, 0.6}});
INDArray B1 = Nd4j.create(new double[] {0.1, 0.2, 0.3});
assertArrayEquals(new int[] {2, 3}, W1.shape());
assertArrayEquals(new int[] {1, 2}, X.shape());
assertArrayEquals(new int[] {1, 3}, B1.shape());
INDArray A1 = X.mmul(W1).add(B1);
INDArray Z1 = Transforms.sigmoid(A1);
assertEquals("[0.30,0.70,1.10]", Util.string(A1));
assertEquals("[0.57,0.67,0.75]", Util.string(Z1));

INDArray W2 = Nd4j.create(new double[][] {{0.1, 0.4}, {0.2, 0.5}, {0.3, 0.6}});
INDArray B2 = Nd4j.create(new double[] {0.1, 0.2});
assertArrayEquals(new int[] {1, 3}, Z1.shape());
assertArrayEquals(new int[] {3, 2}, W2.shape());
assertArrayEquals(new int[] {1, 2}, B2.shape());
INDArray A2 = Z1.mmul(W2).add(B2);
INDArray Z2 = Transforms.sigmoid(A2);
assertEquals("[0.52,1.21]", Util.string(A2));
assertEquals("[0.63,0.77]", Util.string(Z2));

INDArray W3 = Nd4j.create(new double[][] {{0.1, 0.3}, {0.2, 0.4}});
INDArray B3 = Nd4j.create(new double[] {0.1, 0.2});
INDArray A3 = Z2.mmul(W3).add(B3);
//ND4J a une identité dans la classe Transforms(INDArray)Une méthode est fournie.
INDArray Y = Transforms.identity(A3);
assertEquals("[0.32,0.70]", Util.string(A3));
assertEquals("[0.32,0.70]", Util.string(Y));
// Y.equals(A3)Est vrai.
assertEquals(A3, Y);

3.4.3 Résumé de la mise en œuvre

public static Map<String, INDArray> init_network() {
    Map<String, INDArray> network = new HashMap<>();
    network.put("W1", Nd4j.create(new double[][] {{0.1, 0.3, 0.5}, {0.2, 0.4, 0.6}}));
    network.put("b1", Nd4j.create(new double[] {0.1, 0.2, 0.3}));
    network.put("W2", Nd4j.create(new double[][] {{0.1, 0.4}, {0.2, 0.5}, {0.3, 0.6}}));
    network.put("b2", Nd4j.create(new double[] {0.1, 0.2}));
    network.put("W3", Nd4j.create(new double[][] {{0.1, 0.3}, {0.2, 0.4}}));
    network.put("b3", Nd4j.create(new double[] {0.1, 0.2}));
    return network;
}

public static INDArray forward(Map<String, INDArray> network, INDArray x) {
    INDArray W1 = network.get("W1");
    INDArray W2 = network.get("W2");
    INDArray W3 = network.get("W3");
    INDArray b1 = network.get("b1");
    INDArray b2 = network.get("b2");
    INDArray b3 = network.get("b3");

    INDArray a1 = x.mmul(W1).add(b1);
    INDArray z1 = Transforms.sigmoid(a1);
    INDArray a2 = z1.mmul(W2).add(b2);
    INDArray z2 = Transforms.sigmoid(a2);
    INDArray a3 = z2.mmul(W3).add(b3);
    INDArray y = Transforms.identity(a3);
    return y;
}

Map<String, INDArray> network = init_network();
INDArray x = Nd4j.create(new double[] {1.0, 0.5});
INDArray y = forward(network, x);
assertEquals("[0.32,0.70]", Util.string(y));

3.5 Conception de la couche de sortie

3.5.1 Fonction Equal et fonction softmax

INDArray a = Nd4j.create(new double[] {0.3, 2.9, 4.0});
//Exponentiel
INDArray exp_a = Transforms.exp(a);
assertEquals("[1.35,18.17,54.60]", Util.string(exp_a));
//Somme des fonctions exponentielles
Number sum_exp_a = exp_a.sumNumber();
assertEquals(74.1221542102, sum_exp_a.doubleValue(), 5e-6);
//Fonction Softmax
INDArray y = exp_a.div(sum_exp_a);
assertEquals("[0.02,0.25,0.74]", Util.string(y));

3.5.2 Précautions pour la mise en œuvre de la fonction Softmax

public static INDArray softmax_wrong(INDArray a) {
    INDArray exp_a = Transforms.exp(a);
    Number sum_exp_a = exp_a.sumNumber();
    INDArray y = exp_a.div(sum_exp_a);
    return y;
}

public static INDArray softmax_right(INDArray a) {
    Number c = a.maxNumber();
    INDArray exp_a = Transforms.exp(a.sub(c));
    Number sum_exp_a = exp_a.sumNumber();
    INDArray y = exp_a.div(sum_exp_a);
    return y;
}

INDArray a = Nd4j.create(new double[] {1010, 1000, 990});
//Non calculé correctement
assertEquals("[NaN,NaN,NaN]", Util.string(Transforms.exp(a).div(Transforms.exp(a).sumNumber())));
Number c = a.maxNumber();
assertEquals("[0.00,-10.00,-20.00]", Util.string(a.sub(c)));
assertEquals("[1.00,0.00,0.00]", Util.string(Transforms.exp(a.sub(c)).div(Transforms.exp(a.sub(c)).sumNumber())));

//erreur
assertEquals("[NaN,NaN,NaN]", Util.string(softmax_wrong(a)));
//correct
assertEquals("[1.00,0.00,0.00]", Util.string(softmax_right(a)));
//Corriger softmax pour ND4J(INDArray)Est préparé.
assertEquals("[1.00,0.00,0.00]", Util.string(Transforms.softmax(a)));

3.5.3 Caractéristiques de la fonction Softmax

INDArray a = Nd4j.create(new double[] {0.3, 2.9, 4.0});
INDArray y = Transforms.softmax(a);
assertEquals("[0.02,0.25,0.74]", Util.string(y));
//Le total est de 1.
assertEquals(1.0, y.sumNumber().doubleValue(), 5e-6);

3.6 Reconnaissance des numéros manuscrits

3.6.1 Ensemble de données MNIST

Données MNIST sont lues par la classe MNISTImages /java/deep/learning/common/MNISTImages.java). Cette classe lit le fichier après avoir téléchargé et décompressé les données dans THE MNIST DATABASE of handwritten digits.

//Chargez l'ensemble de données MNIST dans la classe MNISTImages.
MNISTImages train = new MNISTImages(Constants.TrainImages, Constants.TrainLabels);
MNISTImages test = new MNISTImages(Constants.TestImages, Constants.TestLabels);
assertEquals(60000, train.size);
assertEquals(784, train.imageSize);
assertEquals(10000, test.size);
assertEquals(784, test.imageSize);

//Les 100 premières images des données d'entraînement sont sorties au format PNG.
if (!Constants.TrainImagesOutput.exists())
    Constants.TrainImagesOutput.mkdirs();
for (int i = 0; i < 100; ++i) {
    File image = new File(Constants.TrainImagesOutput,
        String.format("%05d-%d.png ", i, train.label(i)));
    train.writePngFile(i, image);
}

L'image de sortie réelle est la suivante. Le nom du fichier est "[numéro de série 5 chiffres] - [étiquette] .png ".

00000-5.png 00000-5.png 00001-0.png 00001-0.png 00002-4.png 00002-4.png 00003-1.png 00003-1.png 00004-9.png 00004-9.png 00005-2.png 00005-2.png 00006-1.png 00006-1.png 00007-3.png 00007-3.png 00008-1.png 00008-1.png 00009-4.png 00009-4.png .....

3.6.2 Traitement d'inférence de réseau neuronal

Exemple de données de poids (sample_weight.pkl) est [SampleWeight](https://github.com/ saka1029 / Deep.Learning / blob / master / src / main / java / deep / learning / common / SampleWeight.java) Charger à l'aide de la classe. Cependant, sample_weight.pkl est des données sérialisées en Python et ne peut pas être lu directement en Java. Par conséquent, sample_weight.pkl est une fois converti en texte à l'aide du programme Python suivant. Les données textuelles sont SampleWeight.txt. La classe SampleWeight lit ce fichier texte.

sample_weight.py


import pickle
import numpy

pkl = "sample_weight.pkl"
with open(pkl, "rb") as f:
	network = pickle.load(f)
for k, v in network.items():
    print(k, end="")
    dim = v.ndim
    for d in v.shape:
        print("", d, end="")
    print()
    for e in v.flatten():
        print(e)

Le code qui lit et déduit réellement est le suivant. La précision de la reconnaissance est [Deep Learning made from scratch] Il est de 93,52%, ce qui équivaut à (https://www.oreilly.co.jp/books/9784873117584/).

static INDArray normalize(byte[][] images) {
    int imageCount = images.length;
    int imageSize = images[0].length;
    INDArray norm = Nd4j.create(imageCount, imageSize);
    for (int i = 0; i < imageCount; ++i)
        for (int j = 0; j < imageSize; ++j)
            norm.putScalar(i, j, (images[i][j] & 0xff) / 255.0);
    return norm;
}

static INDArray predict(Map<String, INDArray> network, INDArray x) {
    INDArray W1 = network.get("W1");
    INDArray W2 = network.get("W2");
    INDArray W3 = network.get("W3");
    INDArray b1 = network.get("b1");
    INDArray b2 = network.get("b2");
    INDArray b3 = network.get("b3");

    //Si vous procédez comme suit, une erreur se produira dans le traitement par lots.
    // INDArray a1 = x.mmul(W1).add(b1);
    // x.mmul(W1)En effet, b1 est unidimensionnel même si le résultat de est un tableau bidimensionnel.
    // add(INDArray)Ne diffuse pas automatiquement.
    //Il peut également être diffusé explicitement comme suit:
    // INDArray a1 = x.mmul(W1).add(b1.broadcast(x.size(0), b1.size(1)));
    INDArray a1 = x.mmul(W1).addRowVector(b1);
    INDArray z1 = Transforms.sigmoid(a1);
    INDArray a2 = z1.mmul(W2).addRowVector(b2);
    INDArray z2 = Transforms.sigmoid(a2);
    INDArray a3 = z2.mmul(W3).addRowVector(b3);
    INDArray y = Transforms.softmax(a3);

    return y;
}

//Chargez l'image de test.
MNISTImages test = new MNISTImages(Constants.TestImages, Constants.TestLabels);
//Chargez les données de poids de l'échantillon.
Map<String, INDArray> network = SampleWeight.read(Constants.SampleWeights);
//Normaliser l'image(0-255 -> 0.0-1.0)
INDArray x = test.normalizedImages();
int size = x.size(0);
int accuracy_cnt = 0;
for (int i = 0; i < size; ++i) {
    INDArray y = predict(network, x.getRow(i));
    //Le dernier argument, 1 représente la dimension.
    INDArray max = Nd4j.getExecutioner().exec(new IAMax(y), 1);
    if (max.getInt(0) == test.label(i))
        ++accuracy_cnt;
}
//        System.out.printf("Accuracy:%f%n", (double) accuracy_cnt / size);
assertEquals(10000, size);
assertEquals(9352, accuracy_cnt);

3.6.3 Traitement par lots

NDArrayIndex pour récupérer les données de taille de lot depuis INDArray Utilisez doc / org / nd4j / linalg / indexing / NDArrayIndex.html). Vous pouvez récupérer les éléments du début à la fin avec NDArrayIndex.interval (int start, int end) (Début ≤ i <fin). Vous pouvez également utiliser la classe IAMax pour faire la même chose que la fonction argmax de NumPy.

int batch_size = 100;
//Chargez l'image de test.
MNISTImages test = new MNISTImages(Constants.TestImages, Constants.TestLabels);
//Chargez les données de poids de l'échantillon.
Map<String, INDArray> network = SampleWeight.read(Constants.SampleWeights);
//Normaliser l'image(0-255 -> 0.0-1.0)
INDArray x = test.normalizedImages();
int size = x.size(0);
int accuracy_cnt = 0;
for (int i = 0; i < size; i += batch_size) {
    //Extraire l'image pour la taille du lot et prévoir()Est appelé.
    INDArray y = predict(network, x.get(NDArrayIndex.interval(i, i + batch_size)));
    //Le dernier argument, 1 représente la dimension.
    INDArray max = Nd4j.getExecutioner().exec(new IAMax(y), 1);
    for (int j = 0; j < batch_size; ++j)
        if (max.getInt(j) == test.label(i + j))
            ++accuracy_cnt;
}
//        System.out.printf("Accuracy:%f%n", (double) accuracy_cnt / size);
assertEquals(10000, size);
assertEquals(9352, accuracy_cnt);

Dans mon environnement, le traitement par lots m'a permis de passer de 2,7 secondes à 0,5 secondes. Je n'utilise pas de GPU.

Recommended Posts

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
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
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