Итак, я упираюсь в стену своим проектом C# Machine Learning. Я пытаюсь обучить алгоритм распознаванию чисел. Поскольку это всего лишь упражнение, у меня есть набор изображений из 200 чисел (по 20 от 0 до 9). Очевидно, что если бы мне нужен был должным образом обученный алгоритм, я бы использовал более надежный тренировочный набор, но это всего лишь упражнение, чтобы увидеть, смогу ли я заставить его работать в первую очередь. Я могу получить точность до 60%, но не более того. Я провел некоторое исследование функций активации и, насколько я понимаю, LeakyRelu — это функция, которую я должен использовать. Однако, если я использую функцию LeakyRelu повсеместно, она ничего не узнает, и я не знаю, как использовать LeakyRelu в качестве функции активации вывода. Использование сигмоиды или тангенса в качестве функции активации выхода имеет для меня больше смысла. Вот блок кода, создающий массив для обратного распространения:
public static float ACTIVE_VALUE = 1;
public static float INACTIVE_VALUE = -1;
// This is specifically designed for a algorithm that will detect a number between 0 - 9
public static float[] valueToArray(int value)
{
switch (value)
{
case 0:
return new float[] { ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 1:
return new float[] { INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 2:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 3:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 4:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 5:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 6:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 7:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
case 8:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE, INACTIVE_VALUE };
case 9:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, ACTIVE_VALUE };
default:
return new float[] { INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE,
INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE, INACTIVE_VALUE };
}
}
Я не знаю, как использовать что-то подобное для чтения вывода LeakyRelu. Поэтому я решил, что лучшим вариантом будет использовать LeakyRelu для входного и скрытого слоев, а затем использовать tanh или sigmoid для выходного слоя. Однако это создает проблему, потому что sigmoid просто возвращает NAN (из-за ошибки округления, насколько я понимаю), а tanh возвращает -1 или 1, но ничего между ними. Если я использую tanh повсеместно, он работает и обучается, но достигает точности только в 60%, после чего перестает развиваться. Я предполагаю, что это связано с проблемой «исчезающего градиента». Однако, если я использую LeakyRelu для ввода и скрытых слоев, а затем tanh для вывода, он остается на уровне 12-14% (что так же хорошо, как случайное угадывание числа).
Я использую нейронную сеть, полученную от пользователя github: https://github.com/kipgparker/BackPropNetwork
Он разместил в Интернете исследовательскую работу о нейронных сетях, и она стала одной из самых популярных в Google. Вот как я нашел это в первую очередь. Я разместил свой полный проект в zip-архиве на GitHub здесь: Обучение
Я не против использования библиотеки, которую я могу получить из nuget, такой как SiaNet (https://scisharp.github.io/SiaNet/api/SiaNet.Layers.AvgPooling1D.html), однако я настолько хорошо познакомился с тем, с которым сейчас работаю, что не хочу переключаться, потому что будет казаться, что я почти начинаю с нуля, потому что мне придется научиться взаимодействовать с совершенно новой библиотекой.
РЕДАКТИРОВАТЬ: дополнительный код. Это мой цикл while, который считывает изображение и обучает алгоритм:
public static void singleThread()
{
int batchSize = 10000;
int rangeLow = 0;
int rangeHi = 9;
int hits = 0;
while (true)
{
// alternates between training and testing
//Console.WriteLine("Training... ");
for (int i = 0; i < batchSize; i++)
{
// Give a training progress report every 100 iterations, this should increase performance
if (i % 100 == 0)
{
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write("Training: ");
Console.Write("(" + (((float)i / (float)batchSize) * 100) + "%)");
Console.Write(" ");
}
// randomly select an image from the list
int number = rng.Next(rangeLow, rangeHi);
int index = rng.Next(1, 20);
Bitmap loadedImage = (Bitmap)Image.FromFile("Train/" + number + "/" +
index + ".png", true);
int indexLocation = 0;
// Convert the image into a grayScale value
for (int x = 0; x < loadedImage.Width; x++)
{
for (int y = 0; y < loadedImage.Height; y++)
{
Color pixel = loadedImage.GetPixel(x, y);
int grayValue = (int)((pixel.R * 0.3) + (pixel.G * 0.59) + (pixel.B * 0.11));
//Console.WriteLine(grayValue);
networkInputs[indexLocation] = grayValue;
indexLocation++;
}
}
// The network will guess what the image is, and return the guess as a float array
float[] guess = currentNetwork.BackPropagate(networkInputs, Interface.valueToArray(number));
// This if statement checks if the guess was correct
if (Interface.guessToValue(guess) == number)
{
hits++;
}
}
currentNetwork.Performance = ((float) hits / (float) batchSize);
hits = 0;
Console.WriteLine("Score: " + (currentNetwork.Performance * 100) + "%");
}
}