// НС с выбором основных параметров: число скрытых слоев, количество нейронов на каждом слое public NeuralNetwork(List <ReadMnist.DigitImage> _patterns, int[] cntNeuronsOnHiddenLayers, int outSize, List <ReadMnist.DigitImage> _tests) { patterns = _patterns; tests = _tests; int outLayerSize = outSize; int inputSize = patterns.Count; if (isBias) { inputSize++; } input = new NNLayer(inputSize, 0, 'l'); int prevSize = inputSize; foreach (int size in cntNeuronsOnHiddenLayers) { int sizeLayer = size; if (isBias) { sizeLayer++; } hidden.Add(new NNLayer(sizeLayer, prevSize, 's')); prevSize = sizeLayer; } output = new NNLayer(outLayerSize, prevSize, 's'); cntLayers = 2 + hidden.Count; }
// НС по умолчанию с 1 скрытым слоем public NeuralNetwork(List <ReadMnist.DigitImage> _patterns, int _cntHiddenLayers, int[] _cntNeuronsOnHiddenLayers, List <ReadMnist.DigitImage> _tests) { int outLayerSize; // Размер выходного слоя int sizeLayer; // Размер текущего слоя (кол-во нейронов на нем); cntLayers = 2 + _cntHiddenLayers; patterns = _patterns; tests = _tests; outLayerSize = sizeLayer = trainSetSize = patterns[0].Pixels.Count; if (isBias) { sizeLayer++; } input = new NNLayer(sizeLayer, 0, 'l'); hidden.Add(new NNLayer(sizeLayer, sizeLayer, 's')); output = new NNLayer(10, sizeLayer, 's'); }
// По значениям предыдущего слоя получаем вход для следующего (Сумма произведений сигнал * вес) // Манипуляции с весами, сигналами и функцией активации private List <double> FromInputToOutputOnNeurons(List <double> inputInfo, NNLayer nextLayer) { List <double> outputInfo = new List <double>(nextLayer.cntNeurons); for (int i = 0; i < nextLayer.cntNeurons; ++i) { double sum = 0; int lastInputIndex = inputInfo.Count - 1; for (int j = 0; j < lastInputIndex; ++j) { double signal = inputInfo[j]; sum += signal * nextLayer.weight[i * inputInfo.Count + j]; } // Если есть нейрона смещения, то добавляем вес нейрона смещения, // иначе - вес последнего нейрона в слое sum += (isBias ? 1 : inputInfo[lastInputIndex]) * nextLayer.weight[i * inputInfo.Count + lastInputIndex]; outputInfo.Add(nextLayer.func(sum)); } return(outputInfo); }
// Изменение весов // Метод обратного распространения ошибки (один обратный проход с изменением весов и подсчетом дельт) private void ChangeWeights(List <double> idealOutput, List <double> outputInfo) { NNLayer nextLayer = output; List <double> deltaNext = new List <double>(idealOutput.Count); // Производная ф-ии активации от входного значения нейрона double fin = 0; // Находим дельту для выходного слоя // delta = (OUTideal - OUTcurrent) * fin for (int i = 0; i < idealOutput.Count; ++i) { // Производная ф-ии активации (здесь : гиперболический тангенс) fin = GetDerivativeFuncActivation('s', outputInfo[i]); deltaNext.Add((idealOutput[i] - outputInfo[i]) * fin); } // Находим дельты для скрытых слоев for (int i = hidden.Count - 1; i >= -1; --i) { NNLayer currentLayer; currentLayer = i != -1 ? hidden[i] : input; List <double> newWeights = new List <double> (nextLayer.weight.Count); int index = 0; // пересчет веса (на каждой связи нейронов текущего слоя и нейронов следующего слоя) for (int k = 0; k < nextLayer.cntNeurons; ++k) { for (int j = 0; j < currentLayer.cntNeurons; ++j) { // градиент j, k double grad = deltaNext[k] * currentLayer.outputs[j]; // дельта веса double deltaWeight = learningRate * grad; newWeights.Add(nextLayer.weight[index++] + deltaWeight); } } // --- List <double> deltaCurrent = new List <double>(deltaNext); deltaNext.Clear(); // Подстчет дельт данного слоя // delta = (fin * sum(W[i] * Delta[i])) for (int j = 0; j < currentLayer.cntNeurons; ++j) { double sum = 0; for (int k = 0; k < nextLayer.cntNeurons; ++k) { sum += nextLayer.weight[currentLayer.cntNeurons * k + j] * deltaCurrent[k]; } // Производная ф-ии активации (здесь : гиперболический тангенс) fin = GetDerivativeFuncActivation('s', currentLayer.outputs[j]); deltaNext.Add(fin * sum); } for (int wI = 0; wI < nextLayer.cntWeights; ++wI) { nextLayer.weight[wI] = newWeights[wI]; } nextLayer = currentLayer; // --- } }