Выходные значения нейронной сети прямого прохода всегда сходятся к среднему - PullRequest
0 голосов
/ 07 апреля 2020

Я подумал, что лучшим способом изучения нейронных сетей будет создание одной с нуля в JavaScript с использованием базы данных изображений MNIST, каждое изображение отображает одно рукописное число от 0 до 9. Каждое изображение имеет 784 пикселя, каждый пиксель значение может быть 1 (белое) или 0 (черное).

Теперь, прежде чем я начну публиковать какой-либо код, я хотел сначала сохранить абстрактность и обсудить мою проблему, чтобы понять, не упускаю ли я что-то очень простое. c и неловко.

NN basi c Характеристики:

  • 784 входных значений 2 скрытых слоя
  • каждый из которых содержит 50 нейронов 10 выходных нейронов, представляющих метки От 0 до 9
  • бета = 0 в каждом слое
  • скорость обучения = 0,01 начальных весов, установленных с использованием ксавье
  • функция активации = сигмовидная повсюду на данный момент

Когда я запускаю этот NN для 1000 изображений или около того, все выходы медленно снижаются примерно до 0,1, а затем в основном все зависают там навсегда. Когда я смотрю один поток изображений через NN и затем backprop, это действительно имеет смысл. Большую часть времени, фактически в 90% случаев, истинное значение для одного выхода будет равно нулю. Таким образом, для одного выхода, если я пропущу 10 изображений через 9, эти изображения будут хотеть, чтобы вывод двигался в направлении истинности = нуля, в то время как только 1 изображение будет хотеть, чтобы он достигал истины = одного. Я на самом деле вижу, как это происходит в числах ... вы можете видеть, как единичное выходное значение медленно спускается к истине = нулю несколько раз, а затем временное увеличение, когда истина = единица.

Я надеюсь, что это делает смысл .... в основном мой NN скорее уменьшит мою общую ошибку, переместив все мои выводы ближе к истине = ноль. Это почти как если бы мне нужно было настроить свою скорость обучения, чтобы она была в 10 раз больше, когда я настраиваю свои веса в сторону истины = один ... но тогда я подозреваю, что это просто отрегулирует все мои результаты на 0.5.

Я был через мой код так много раз, и я считаю, что моя математика хороша, но, конечно, я мог что-то упустить. Тем не менее ... Я подумал, что сначала я начну здесь и посмотрю, видел ли кто-нибудь еще это.

EDIT .... необработанный код, содержащий NN классы. Извините, немного грязно. В основном вы вызываете новый класс сети, а затем запускаете network.addlayer для заполнения слоев, нейронов и установки весов. Оттуда вы можете начать обучение, вызвав network.inputTrainingData, который принимает двоичный массив из 784 пикселей и значение истинности для изображения. И, наконец, вы можете подключиться к сети BackPropogate после каждого изображения.

    class network {

    layers = [];
    learning_rate = 0.01;
    inputCnt = 784;
    labels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    xValues = [];
    truth = [];

    constructor() {     

    }

    addLayer(neuronCnt, isOutputLayer) {
        //note...we're not adding an "input layer....why do that when you can just feed the inputs into the first hidden layer?

        if (isOutputLayer) {
            this.layers.push(new layer(this.labels.length, this.layers[this.layers.length - 1].neurons.length, isOutputLayer));
        } else if(this.layers.length) {
            this.layers.push(new layer(neuronCnt, this.layers[this.layers.length - 1].neurons.length, isOutputLayer));
        } else {            
            this.layers.push(new layer(neuronCnt, this.inputCnt, isOutputLayer));
        }        
    }


    get numberOfLayers() {
        return this.layers.length;
    }


    inputTrainingData(data, truth) {
        this.xValues = data;
        this.xValues.forEach(function (x, i) {
            if (x == 0) { x = 0.0001; };
        })
        this.truth = truth;
        var $this = this;
        this.layers.forEach(function (layer, index) {
            layer.input($this.xValues);
            $this.xValues = layer.output();
        });    
    }

    backPropogate() {         
        var $this = this;
        var prev_layer = 0;
        var t = this.getTruth();
        for (var i = this.layers.length-1; i >= 0; i--) {            
            this.layers[i].backPropogate(t, $this.learning_rate, prev_layer);
            prev_layer = this.layers[i];
        }
    }

    getTruth() {
        var truth = [];   
        var $this = this;
        this.labels.forEach(function (label, index) {
            if ($this.truth == label) {
                truth.push(1);
            } else {
                truth.push(0);
            }
        })
        return truth;
    }

    }

    class layer {

    neurons = [];
    inputs = [];
    outputs = [];
    final_deriv = [];
    hidden_deriv = [];
    beta = 0; //Math.random();
    isOutputLayer = false;

    constructor(neuronCnt, prevLayerNeuronCnt, _isOutputLayer) {
        this.isOutputLayer = _isOutputLayer;
        var i;
        for (i = 0; i < neuronCnt; i++) {
            var newNeuron = new neuron(neuronCnt, prevLayerNeuronCnt);
            this.neurons.push(newNeuron);
        }
    }

    input(xValues) {
        this.inputs = xValues;
        var $this = this;
        this.neurons.forEach(function (neuron, index) {
            neuron.input(xValues, $this.beta);
        });
    }

    output() {
        this.outputs = [];
        var $this = this;
        this.neurons.forEach(function (neuron, index) {
            $this.outputs.push(neuron.output($this.isOutputLayer));
        })
        return this.outputs;
    }

    backPropogate(truth, learning_rate, prev_layer) {
        var $this = this;
        this.neurons.forEach(function (neuron, index) {
            neuron.backPropogateWeights(truth[index], learning_rate, prev_layer, index);
            //neuron.backPropogateBeta(truth[index], prev_layer); 
        });

    }
    }

    class neuron {

    xValues = 0;
    b = 0;
    weights = [];
    h = 0;
    neuron_deriv = 1;
    weights_deriv = [];


    constructor(neuronCnt, prevLayerNeuronCnt) {
        var i;
        var xavier = 1 / prevLayerNeuronCnt;        
        for (i = 0; i < prevLayerNeuronCnt; i++) {  
            var rnd = (Math.random() - 0.5) * xavier;
            //rnd = ((Math.random() * 2) - 1) * xavier;
            this.weights.push(rnd);
        }   
    }

    input(xValues, beta) {
        this.xValues = xValues;
        this.b = beta;
    }

    output(isOutputLayer) {
        var func = 0;
        var $this = this;
        this.xValues.forEach(function (x, index) {
            func += x * $this.weights[index];
        });
        func += this.b;

            var sigmoid = 1 / (1 + Math.exp(-func));

            $this.h = sigmoid;
            $this.neuron_deriv = (sigmoid * (1 - sigmoid));    


        return this.h;

    }

    backPropogateWeights(truth, learning_rate, prev_layer, cnt) {

        var $this = this;  
        var prevSumError = 1;
        if (prev_layer == 0) {
            //var SqError = Math.pow((this.h - truth), 2)/2;
            var dEdH = (this.h - truth);
            this.neuron_deriv = this.neuron_deriv * dEdH;
        } else {
            prevSumError = 0;
            prev_layer.neurons.forEach(function (neuron, index) {
                prevSumError += neuron.weights_deriv[cnt];
            });
        }

        this.weights.forEach(function (weight, index) {
            $this.weights_deriv.push($this.neuron_deriv * weight * prevSumError);
        });

        for (var i = 0; i < this.weights.length; i++) {
            var slope = $this.neuron_deriv * $this.xValues[i] * prevSumError;
            var weightAdjust = learning_rate * slope;
            $this.weights[i] = $this.weights[i] - weightAdjust; 
        }   


    }





    }

1 Ответ

0 голосов
/ 15 апреля 2020

Отладка заняла очень много времени. Это были веса. Я использовал функцию Ксавье, чтобы рандомизировать все веса, но когда я открыл диапазон веса от -2,5 до 2,5, все только начало работать.

Вот мой процесс отладки, если он кому-нибудь поможет: - вручную повторил все мои вычисления кода, чтобы убедиться, что код работал правильно - уменьшил всю нейронную сеть до минимального количества слоев и нейронов. - Тренируются на одном изображении, потом на двух, потом на трех. - Первый прорыв произошел, когда я тренировался только на двух изображениях и устранил ВСЕ скрытый слой ... он действительно научился! - Отсюда я медленно добавил слой и еще несколько изображений. - Именно в этот момент я начал расширять свой весовой диапазон .... и я увидел, что он начинает учиться. - Наконец-то я взорвал свой диапазон веса, вернулся к исходным настройкам и бум! сработало!

Это все еще не учится так быстро, как следовало бы, основываясь на той же реализации Андрея Капарти в его Con vNetJS .... но, по крайней мере, я сейчас в состоянии начать настройку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...