Я подумал, что лучшим способом изучения нейронных сетей будет создание одной с нуля в 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;
}
}
}