Почему мой алгоритм машинного обучения застревает?

Итак, я упираюсь в стену своим проектом 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) + "%");
    }
}

comment
как вы предварительно обрабатывали изображения? Я имею в виду, как вы скармливали его в сеть? Расскажите нам больше о ваших данных.   -  person moe assal    schedule 01.06.2020
comment
Хорошо, я добавлю этот блок кода в редактирование.   -  person JMC0352    schedule 01.06.2020
comment
Я добавил код. Я в основном читаю все изображение и преобразовываю его в массив значений оттенков серого. Затем подайте этот массив в качестве входных данных.   -  person JMC0352    schedule 01.06.2020
comment
значения оттенков серого находятся в диапазоне 0-255, верно?   -  person moe assal    schedule 01.06.2020
comment
попробуйте преобразовать значения оттенков серого из интервала 0–255 в интервал 0–1. Просто разделите каждый пиксель на 255. И внимательно посмотрите, как инициализируются веса нейронной сети, если вы собираетесь использовать тангенс или сигмоид. поскольку это проблема классификации, я рекомендую вам использовать функцию активации softmax в вашем выходном слое.   -  person moe assal    schedule 01.06.2020
comment
Я слышал о softmax раньше, но он не был частью этой библиотеки. Является ли softmax другой функцией активации, похожей на сигмовидную, только с немного другой формулой?   -  person JMC0352    schedule 01.06.2020
comment
Тот факт, что LeakyRELU работает лучше, чем сигмоид или тан, объясняется тем, что значения слишком велики. большими в том смысле, что tanh и сигмоид неправильно обращаются с ними, а компьютер округляет их до целых чисел.   -  person moe assal    schedule 01.06.2020
comment
Функция активации softmax даст процент на каждом узле (1-0). в котором все проценты в сумме составляют 1. Это позволит вам получить больше смысла, чем сигмоид   -  person moe assal    schedule 01.06.2020
comment
Я посмотрю на softmax и посмотрю, смогу ли я найти формулу в Интернете. Если да, то я могу добавить его в эту библиотеку.   -  person JMC0352    schedule 01.06.2020
comment
расскажите мне о результатах, как только вы обработаете изображения :)   -  person moe assal    schedule 01.06.2020
comment
Сделаю. Я работаю над этим прямо сейчас. Я также рассмотрю возможность добавления softmax в библиотеку.   -  person JMC0352    schedule 01.06.2020
comment
Итак, просто изменив входные данные на значения от 0 до 1 вместо 0 и 255, я уже увидел значительные улучшения. Используя tanh по всем направлениям, я достиг 88%, что намного лучше. Однако там он выровнялся. Поэтому я уменьшил скорость обучения и запустил ее снова. Теперь он учится намного медленнее, но я дам вам знать, каковы результаты. Я изучил softmax и понял, как его кодировать, но, насколько я могу судить, он просто говорит вам, какой результат наиболее вероятен из массива выходов. Однако это не похоже на то, что используется для обратного распространения.   -  person JMC0352    schedule 02.06.2020
comment
причина, по которой вы получаете только 88%, заключается в том, что нейронная сеть (сама по себе) не подходит для распознавания изображений. для этого используются сверточные нейронные сети. Чтобы понять проблему интуитивно, вы можете представить необработанные нейронные сети как осмысливающие все пиксели вместе, где как конв. сети. иметь смысл относительно близких пикселей.   -  person moe assal    schedule 02.06.2020


Ответы (1)


добавлен ответ для будущих посетителей

  • Попробуйте преобразовать значения оттенков серого из интервала 0–255 в интервал 0–1. Просто разделите каждый пиксель на 255. Тот факт, что LeakyRELU работает лучше, чем сигмоид или тан, объясняется тем, что значения слишком велики. большими в том смысле, что tanh и сигмоид неправильно обращаются с ними, а компьютер округляет их до целых чисел.

  • Внимательно посмотрите, как инициализируются веса нейронной сети, если вы собираетесь использовать тангенс или сигмоид.

  • Поскольку это проблема классификации, я рекомендую вам использовать функцию активации softmax в вашем выходном слое.

после предварительной обработки данных точность @JMC0352 составила всего 88 %.

причина, по которой вы получаете только 88%, заключается в том, что нейронная сеть (сама по себе) не подходит для распознавания изображений. для этого используются сверточные нейронные сети. Чтобы понять проблему интуитивно, вы можете представить необработанные нейронные сети как осмысливающие все пиксели вместе, где как конв. сети. иметь смысл относительно близких пикселей.

person moe assal    schedule 02.06.2020
comment
Спасибо за помощь. Следующим моим шагом будет исследование сверточных сетей. - person JMC0352; 02.06.2020