Javascript Neuronale Netze

    Bild
    Die Idee, diesen Artikel zu schreiben, entstand letzten Sommer, als ich einen Vortrag auf einer BigData-Konferenz über neuronale Netze hörte. Der Vortragende "besprengte" das Publikum mit den ungewöhnlichen Worten "Neuron", "Trainingsset", "Trainiere das Modell" ... "Ich habe nichts verstanden - es ist Zeit, zu Managern zu gehen", dachte ich. In letzter Zeit hat das Thema Neuronale Netze jedoch meine Arbeit berührt, und ich habe beschlossen, anhand eines einfachen Beispiels zu zeigen, wie dieses Tool in JavaScript verwendet wird.

    Wir werden ein neuronales Netzwerk erstellen, mit dem wir die manuelle Schreibweise einer Zahl von 0 bis 9 erkennen. Ein Arbeitsbeispiel wird mehrere Zeilen umfassen. Der Code wird selbst für diejenigen Programmierer klar sein, die sich zuvor nicht mit neuronalen Netzen befasst haben. Wie das alles funktioniert kann direkt im Browser angesehen werden.


    Wenn Sie bereits wissen, was Perceptron ist, sollten Sie das nächste Kapitel überspringen.

    Ein bisschen Theorie


    Neuronale Netze sind aus der Forschung auf dem Gebiet der künstlichen Intelligenz hervorgegangen, und zwar aus Versuchen, die Fähigkeit des biologischen Nervensystems zu reproduzieren, Fehler zu lernen und zu korrigieren und die Struktur des Gehirns auf niedriger Ebene zu simulieren. Im einfachsten Fall besteht es aus mehreren miteinander verbundenen Neuronen.

    Mathematisches Neuron


    Eine einfache Maschine, die Eingangssignale in das resultierende Ausgangssignal umwandelt.
    Mathematische Neuronenschaltung
    Die am Eingang ankommenden Signale x1, x2, x3 ... xn werden linear konvertiert, d.h. Kräfte treffen auf den Neuronenkörper ein: w1x1, w2x2, w3x3 ... wnxn, wobei wi die Gewichte der entsprechenden Signale sind. Das Neuron summiert diese Signale, wendet dann eine Funktion f (x) auf die Summe an und erzeugt das resultierende Ausgangssignal y.
    Die Sigmoid- oder Schwellenwertfunktionen werden am häufigsten als die Funktion f (x) verwendet.
    Sigmoid- und Schwellenwertfunktionen

    Die Schwellwertfunktion kann nur zwei diskrete Werte 0 oder 1 annehmen. Die Änderung des Werts der Funktion tritt beim Durchlaufen des angegebenen Schwellwerts T auf. +

    Sigmoid ist eine kontinuierliche Funktion, sie kann unendlich viele Werte im Bereich von 0 bis 1 annehmen.

    UPD: ReLU undMaxOut als moderner.

    Die Architektur des neuronalen Netzwerks kann unterschiedlich sein , wir werden eine der einfachsten Implementierungen des neuronalen Netzwerks betrachten - Perceptron

    Perceptron-Architektur


    Perceptron-Architektur

    Es gibt eine Schicht von Eingangsneuronen (wo Informationen von außen kommen), eine Schicht von Ausgangsneuronen (wo Sie das Ergebnis erhalten können) und eine Reihe von sogenannten versteckten Schichten zwischen ihnen. Neuronen können in mehreren Schichten angeordnet werden. Jede Verbindung zwischen Neuronen hat ein eigenes Gewicht Wij

    Ein- und Ausgangssignale


    Vor dem Anlegen von Signalen an die Neuronen der eingehenden Netzwerkschicht müssen diese normalisiert werden. Die Normalisierung von Eingabedaten ist ein Prozess, bei dem alle Eingabedaten den Prozess der "Ausrichtung" durchlaufen, d. H. auf das Intervall [0,1] oder [-1,1] setzen. Wenn keine Normalisierung durchgeführt wird, wirken sich die eingegebenen Daten zusätzlich auf das Neuron aus, was zu falschen Entscheidungen führt. Mit anderen Worten, wie können Werte verschiedener Ordnungen verglichen werden?

    Auf den Neuronen der Ausgabeebene haben wir auch keine saubere „1“ oder „0“, dies ist normal. Es gibt eine bestimmte Schwelle, bei der wir davon ausgehen, dass wir „1“ oder „0“ erhalten haben. Wir werden später über die Interpretation der Ergebnisse sprechen.

    "Ein Beispiel im Studio, sonst schlafe ich ein."


    Der Einfachheit halber empfehle ich, nodejs und npm zu installieren.

    Wir werden das Netzwerk mit der Brain.js- Bibliothek beschreiben . Am Ende des Artikels werde ich auch Links zu anderen Bibliotheken geben, die auf ähnliche Weise konfiguriert werden können. Ich mochte Brain.js wegen seiner Geschwindigkeit und der Fähigkeit, ein trainiertes Modell zu pflegen.

    Versuchen wir ein Beispiel aus der Dokumentation - einen XOR-Funktionsemulator:
    var brain = require('brain.js');
    var net = new brain.NeuralNetwork();
    net.train([{input: [0, 0], output: [0]},
               {input: [0, 1], output: [1]},
               {input: [1, 0], output: [1]},
               {input: [1, 1], output: [0]}]);
    var output = net.run([1, 0]);  // [0.987]
    console.log(output);
    

    Schreiben Sie alles in die Datei simple1.js , damit das Beispiel funktioniert. Setzen Sie das Gehirnmodul ein und führen Sie es aus
    npm install brain.js
    node simple1.js  # [ 0.9331839217737243 ]
    


    Wir haben 2 eingehende Neuronen und ein Ausgangsneuron, die brain.js-Bibliothek selbst konfiguriert die verborgene Ebene und installiert dort so viele Neuronen, wie es passt (in diesem Beispiel 3 Neuronen).

    Was wir an die .train- Methode übergeben haben, wird als Trainingssatz bezeichnet. Jedes Element besteht aus einem Array von Objekten mit der Eingabe- und Ausgabeeigenschaft (einem Array von Eingabe- und Ausgabeparametern). Wir haben die eingehenden Daten nicht normalisiert, da die Daten selbst bereits in die erforderliche Form gebracht wurden.

    Bitte beachten Sie: Die Ausgabe ist nicht [0.987], sondern [0.9331 ...] . Möglicherweise haben Sie eine etwas andere Bedeutung. Dies ist normal, da der Lernalgorithmus Zufallszahlen zur Auswahl von Gewichten verwendet.

    Verfahren .runEs wird verwendet, um die Antwort des neuronalen Netzwerks auf das im Argument angegebene Array eingehender Signale abzurufen.

    Weitere einfache Beispiele finden Sie in der Dokumentation des Gehirns.

    Zahlen erkennen


    Am Anfang müssen Bilder mit handschriftlichen Nummern auf dieselbe Größe reduziert werden. In unserem Beispiel verwenden wir das MNIST-Ziffernmodul , einen Satz von Tausenden von 28x28px-Binärbildern mit handgeschriebenen Ziffern von 0 bis 9:
    nmist Trainingsbeispiel

    Die ursprüngliche MNIST-Datenbank enthält 60.000 Beispiele für Schulungen und 10.000 Beispiele für Tests. Sie kann von der LeCun- Website heruntergeladen werden . Der Autor von MNIST-Ziffern hat einige dieser Beispiele für die JavaScript-Sprache zur Verfügung gestellt, die Bibliothek hat bereits die Normalisierung eingehender Signale durchgeführt. Mit diesem Modul können wir Trainings- und Testmuster automatisch erhalten.

    Ich musste die MNIST-Ziffernbibliothek klonen, da dort ein wenig Datenverwirrung herrscht. Ich habe 10.000 Beispiele aus der ursprünglichen Datenbank neu geladen, daher muss ich MNIST-Ziffern aus meinem Repository verwenden .

    Netzwerkkonfiguration


    In der Eingabeebene benötigen wir 28x28 = 784 Neuronen am Ausgang von 10 Neuronen. Die versteckte brain.js-Ebene konfiguriert sich selbst. Mit Blick auf die Zukunft werde ich klarstellen, dass es 392 Neuronen geben wird. Das Trainingsmuster wird vom mnist-Modul generiert

    Wir trainieren das Modell


    Installieren Sie Mnist
    npm install https://github.com/ApelSYN/mnist
    


    Alles ist fertig, wir schulen das Netzwerk
    const brain = require('brain.js');
    var net = new brain.NeuralNetwork();
    const fs = require('fs');
    const mnist = require('mnist');
    const set = mnist.set(1000, 0);
    const trainingSet = set.training;
    net.train(trainingSet,
        {
            errorThresh: 0.005,  // error threshold to reach
            iterations: 20000,   // maximum training iterations
            log: true,           // console.log() progress periodically
            logPeriod: 1,       // number of iterations between logging
            learningRate: 0.3    // learning rate
        }
    );
    let wstream = fs.createWriteStream('./data/mnistTrain.json');
    wstream.write(JSON.stringify(net.toJSON(),null,2));
    wstream.end();
    console.log('MNIST dataset with Brain.js train done.')
    

    Wir erstellen ein Netzwerk, erhalten 1000 Elemente des Trainingssatzes, rufen die .train- Methode auf , die das Netzwerktraining durchführt - speichern Sie alles in der Datei './data/mnistTrain.json' (vergessen Sie nicht, den Ordner "./data" zu erstellen).

    Wenn alles richtig gemacht wurde, erhalten Sie ungefähr das folgende Ergebnis:
    [root@HomeWebServer nn]# node train.js
    iterations: 0 training error: 0.060402555338691676
    iterations: 1 training error: 0.02802436102035996
    iterations: 2 training error: 0.020358600820106914
    iterations: 3 training error: 0.0159533285799183
    iterations: 4 training error: 0.012557029942873513
    iterations: 5 training error: 0.010245175822114688
    iterations: 6 training error: 0.008218147206099617
    iterations: 7 training error: 0.006798613211310184
    iterations: 8 training error: 0.005629051609641436
    iterations: 9 training error: 0.004910207736789503
    MNIST dataset with Brain.js train done.
    


    Alles ist zu erkennen


    Es bleibt noch einiges an Code zu schreiben - und das Erkennungssystem ist fertig!
    const brain = require('brain.js'),
          mnist = require('mnist');
    var net = new brain.NeuralNetwork();
    const set = mnist.set(0, 1);
    const testSet = set.test;
    net.fromJSON(require('./data/mnistTrain'));
    var output = net.run(testSet[0].input);
    console.log(testSet[0].output);
    console.log(output);
    


    Wir erhalten 1 zufälliges Testbeispiel aus einer Stichprobe von 10.000 Datensätzen, laden das zuvor trainierte Modell, übertragen den Testdatensatz auf den Netzeingang und prüfen, ob er korrekt erkannt wurde.

    Hier ist ein Beispiel für die Ausführung
    [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 ]
    [ 0.0002863506627761867,
      0.00002389940760904011,
      0.00039954062883041345,
      0.9910109896013567,
      7.562879202664903e-7,
      0.0038756598319246837,
      0.000016752919557362786,
      0.0007205981595354964,
      0.13699517762991756,
      0.0011053963693377692 ]
    

    In dem Beispiel kamen drei digitalisierte Elemente zu den ankommenden Neuronen in das Netzwerk (das erste Array ist die perfekte Antwort). Am Netzwerkausgang haben wir ein Array von Elementen, von denen eines nahe eins liegt (0.9910109896013567). Dies ist auch das dritte Bit. Achten Sie auf das vierte Bit dort 7,56 ... bis -7 Grad, dies ist eine solche Form der Notation von Gleitkommazahlen in JavaScript.

    Nun, die Anerkennung ist gut gelaufen. Herzlichen Glückwunsch, unser Netzwerk hat verdient!

    Wir haben unsere Ergebnisse mit der Softmax-Funktion "gekämmt", die ich aus einem maschinellen Lernbeispiel entnommen habe:
    function softmax(output) {
        var maximum = output.reduce(function(p,c) { return p>c ? p : c; });
        var nominators = output.map(function(e) { return Math.exp(e - maximum); });
        var denominator = nominators.reduce(function (p, c) { return p + c; });
        var softmax = nominators.map(function(e) { return e / denominator; });
        var maxIndex = 0;
        softmax.reduce(function(p,c,i){if(p


    Функцию можно поместить в начало нашего кода и последнюю строку заменить на
    console.log(softmax(output));
    


    Все друзья — теперь все работает красиво:
    [root@HomeWebServer nn]# node simpleRecognize.js
    [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
    [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
    [root@HomeWebServer nn]# node simpleRecognize.js
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]
    [root@HomeWebServer nn]# node simpleRecognize.js
    [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ]
    [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ]
    


    Иногда сеть может давать неправильный результат (мы взяли небольшую выборку и поставили не достаточно строгую погрешность).

    А как распознать цифру, которую напишете вы?


    Конечно, тут нет никакой подтасовки, но все же хочется самому проверить «на прочность» то, что получилось.

    С помощью HTML5 Canvas и все тем же brain.js-ом с сохраненной моделью мне удалось сделать реализацию распознавания в браузере, часть кода для отрисовки и дизайн интерфейса я позаимствовал в интернете. Можете попробовать вживую. В мобильном устройстве рисовать можно пальцем.

    Ссылки по теме




    UPD: Альтернативные реализации живого примера 1, 2 на JavaScript из комментариев и личной переписки.

    Jetzt auch beliebt: