Проблемы с CNN на C для распознавания символов [закрыто]

Вопрос или проблема

Я не много знаю о том, как работают CNN, это проект на 2-м курсе. Я думаю, что есть проблема с тем, как инициалizируются веса, потому что когда я печатаю фильтры, в какой-то момент появляются значения inf:

Фильтр 3:
15509237381568029284873029055332193447861458985912363161430999510533873879945596980993373191426571676366200003674939087855772719557843833854929947475634127247787636790120994775858932865077113852230532698882277136447985541188313086538012728229888.00 18359763017890164425204389583297207386500610797455126179166711591615457938219216111178954045532106895141650625125688627294738209844119686988754507086207366374214643351877560986148275558795959729575429393568922065554408851103506914355184438083584.00 18587972624253969433103096503088033054398875424422022161201791087320043338159670321825588556672421756413239811372283115956682886935293872047719314900414929754815910393776733548788432952928054655714736698578068863356830166106385512472051019939840.00 11202112176897473590891196824465094735414470083597372697897160999130350820156114804831600238439218055782093611297177158338166880067627828875885701372098392296329194749158443331337943936840748618081295299118121845385822568211669840441136065282048.00 18280614470983078645263446708570136418677980146052096715976120767535791313896762753715308289415329352516558078470767654623980429174978612232541879350428703827965603178875969376893022829593794439964055096419613735383544597737137397974436879007744.00
17091669196341113743365348505420551408748886168433058616379362217677666281522517370792737181538673532806414924608733665520512606849107507525351210959464672096821507973463403465403240863529066389182299450764963396681888159310876678752698309279744.00 17399544622379269982941877291265759712082326649704098240841264227290364254910254834899353653226434041624722866027285435430564813133185369837232623713195301028395509893758908660303375884353547740263543199862709742935428137829623624133579030659072.00 17946941730026600549775527500965276447602509976075345411106328441451839695350586142338819078131304233532480889961887222496382617680637253882985704177251067173088350145872858981091264675449648066236758030170187454948545450995473094353213907271680.00 15461125685602715097053216788775194765496995221121291872250322452959184583614973627686268288599752275425206179463110643736649224358516815114911083077411151171773032322469247286898705660616836895364197455138899983224654672063721763305601324023808.00 16118647001582977681073485239125701479972724361248189795865288455355297782105241653802068533931570646548485229929958449359003965855560339939932036226554521685201992736586280571125553937423953129387124867064234055211725415067493378007354266615808.00
18950296402460567061398021440118295815963182527928485180229953027125650929638655646822290488492687352649623819653650583986677135447743189052166742635966357892201560850808973385691508611482916894206168055900473372083661720300588766691485454172160.00 14852729428707089271281054906715402161743652458199641016297815623950535203047573286330504422689923191864104257299330296787075717860331702368876679387368406978789437741706857156118325283532570215758343749311769355895988207255231399711178183671808.00 inf inf inf
inf inf inf 10328809716981526075095606780625142240392696792233437245694138198549990742276015628114903112082317299513129807948502208170275376712175901374405631701844965354038213039101401629814496842107566793147527505047158785012869666865558330541078639553549017758656521424751936469331177508870029312.00 11551071442677416175047639948275120802671022801377359867551900189155748092661388720657218957209694691274547666639645389165542948928296649404766709769651990919355077644004843384441067399293131637020233292262958321600033933670559179294490337475624351771196441643168648137374068369646419968.00
11097116249303597199803802497426418538119113202137975640966683562201035036655818321020579563040143773298564929295078130982780881767043919871874547860195429538657537839104593191872667045045815194390258944416057677926852693259574690231037918807255115841185144357345320208638055901399351296.00 10645499419072038223023634047844757988779096776849112935926112929781290941663708524542301075112383908755417985977005658450129590461024336460967204364532901923869114813921948091841795470866894117615345515497019080146698602253043173180264649107522959548793535629228016424297354697761619968.00 0.00 0.00 inf

и после этого он сообщает, что мой fc_output[0] имеет значения inf или NaN здесь мой код cnn.c: (если хотите больше кода, скажите мне)

#include "cnn.h"
#include <stdlib.h> // для rand()
#include "layers.h"
#include "utils.h"
#include "image_loader.h"

// Функция для нормализации изображения
void normalize_image(Image *image) {
    for (int i = 0; i < IMG_HEIGHT; i++) {
        for (int j = 0; j < IMG_WIDTH; j++) {
            // Нормализация пикселя, деля на 255
            image->pixels[i][j] /= 255.0;
        }
    }
}

// Функция для инициализации весов CNN случайными значениями
void initialize_cnn(CNN *cnn, Image *input_image) {
    // Нормализация входного изображения
    normalize_image(input_image);

    // Инициализация фильтров conv1 с диапазоном от -0,1 до 0,1
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 5; j++) {
            for (int k = 0; k < 5; k++) {
                cnn->conv1_filter[i][j][k] = ((double)rand() / RAND_MAX) * 0.2 - 0.1; // Отрегулированный диапазон
                if (isnan(cnn->conv1_filter[i][j][k]) || isinf(cnn->conv1_filter[i][j][k])) {
                    printf("Ошибка: недопустимое значение в conv1_filter[%d][%d][%d]\n", i, j, k);
                }
            }
        }
    }

    // Инициализация фильтров conv2 с диапазоном от -0,05 до 0,05
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 3; k++) {
                for (int l = 0; l < 2; l++) {
                    cnn->conv2_filter[i][j][k][l] = ((double)rand() / RAND_MAX) * 0.2 - 0.1; // Отрегулированный диапазон
                    if (isnan(cnn->conv2_filter[i][j][k][l]) || isinf(cnn->conv2_filter[i][j][k][l])) {
                        printf("Ошибка: недопустимое значение в conv2_filter[%d][%d][%d][%d]\n", i, j, k, l);
                    }
                }
            }
        }
    }

    // Вычисление предела для полностью связанного слоя (инициализация He)
    double he_limit = sqrt(2.0 / (4 * 5 * 5));
    printf("Предел He: %f\n", he_limit);

    // Инициализация весов полностью связанного слоя с помощью инициализации He
    for (int i = 0; i < 4 * 5 * 5; i++) {
        for (int j = 0; j < 26; j++) {
            cnn->fc_weights[i][j] = ((double)rand() / RAND_MAX) * 2 * he_limit - he_limit; // Отрегулированный предел
            if (isnan(cnn->fc_weights[i][j]) || isinf(cnn->fc_weights[i][j])) {
                printf("Ошибка: недопустимое значение в fc_weights[%d][%d]: %f\n", i, j, cnn->fc_weights[i][j]);
                return;
            }
        }
    }

    printf("Инициализация весов завершена успешно.\n");
}

// Прямое распространение в CNN
void forward(CNN *cnn, double input[28][28], double output[26]) {

    // Проверка весов полностью связанного слоя
    for (int i = 0; i < 4 * 5 * 5; i++) {
        for (int j = 0; j < 26; j++) {
            if (isnan(cnn->fc_weights[i][j]) || isinf(cnn->fc_weights[i][j])) {
                printf("Ошибка: недопустимое значение в cnn->fc_weights[%d][%d].\n", i, j);
                return;
            }
        }
    }

    // Проверка значений flat_output перед любым использованием
    for (int i = 0; i < 4 * 5 * 5; i++) {
        if (isnan(cnn->flat_output[i]) || isinf(cnn->flat_output[i])) {
            printf("Ошибка: недопустимое значение в cnn->flat_output[%d] перед распространением.\n", i);
            return;
        }
    }

    // Первый слой свертки + ReLU
    for (int f = 0; f < 2; f++) {
        convolution(input, cnn->conv1_output[f], cnn->conv1_filter[f]);
        for (int i = 0; i < 24; i++) {
            for (int j = 0; j < 24; j++) {
                cnn->conv1_output[f][i][j] = relu(cnn->conv1_output[f][i][j]);
            }
        }
    }

    // Печать результатов первой свертки
    printf("Результаты conv1_output после ReLU:\n");
    for (int f = 0; f < 2; f++) {
        printf("Фильтр %d:\n", f);
        for (int i = 0; i < 24; i++) {
            for (int j = 0; j < 24; j++) {
                printf("%.2f ", cnn->conv1_output[f][i][j]);
            }
            printf("\n");
        }
    }

    // Первый слой пулинга
    for (int f = 0; f < 2; f++) {
        max_pooling(cnn->conv1_output[f], cnn->pool1_output[f], 2, 2);
    }

    // Печать результатов первого слоя пулинга
    printf("Результаты pool1_output:\n");
    for (int f = 0; f < 2; f++) {
        printf("Фильтр %d:\n", f);
        for (int i = 0; i < 12; i++) {
            for (int j = 0; j < 12; j++) {
                printf("%.2f ", cnn->pool1_output[f][i][j]);
            }
            printf("\n");
        }
    }

    // Второй слой свертки + ReLU
    for (int f = 0; f < 4; f++) {
        for (int c = 0; c < 2; c++) {
            convolution(cnn->pool1_output[c], cnn->conv2_output[f], cnn->conv2_filter[f][c]);
            for (int i = 0; i < 10; i++) {
                for (int j = 0; j < 10; j++) {
                    cnn->conv2_output[f][i][j] = relu(cnn->conv2_output[f][i][j]);
                }
            }
        }
    }

    // Печать результатов второй свертки
    printf("Результаты conv2_output после ReLU:\n");
    for (int f = 0; f < 4; f++) {
        printf("Фильтр %d:\n", f);
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                printf("%.2f ", cnn->conv2_output[f][i][j]);
            }
            printf("\n");
        }
    }

    // Второй слой пулинга
    for (int f = 0; f < 4; f++) {
        max_pooling(cnn->conv2_output[f], cnn->pool2_output[f], 2, 2);
    }

    // Печать результатов второго слоя пулинга
    printf("Результаты pool2_output:\n");
    for (int f = 0; f < 4; f++) {
        printf("Фильтр %d:\n", f);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                printf("%.2f ", cnn->pool2_output[f][i][j]);
            }
            printf("\n");
        }
    }

    // Уплощение
    for (int f = 0; f < 4; f++) {
        flatten(cnn->pool2_output[f], cnn->flat_output + (f * 25), 5, 5);
    }

    // Полностью связанный слой
    double fc_output[26] = {0};
    for (int i = 0; i < 4 * 5 * 5; i++) {
        for (int j = 0; j < 26; j++) {
            fc_output[j] += cnn->flat_output[i] * cnn->fc_weights[i][j];
        }
    }

    // Проверка значений перед softmax
    for (int i = 0; i < 26; i++) {
        if (isnan(fc_output[i]) || isinf(fc_output[i])) {
            printf("Ошибка: недопустимое значение в fc_output[%d] перед softmax.\n", i);
            return;
        }
    }

    // Активация Softmax
    softmax(fc_output, output, 26);

    // Печать окончательных результатов в output
    printf("Окончательные результаты в output после softmax:\n");
    for (int i = 0; i < 26; i++) {
        printf("%.2f ", output[i]);
    }
    printf("\n");
}

// Функция обратного распространения
void backpropagation(CNN *cnn, double input[28][28], int label, double learning_rate) {
    // Этап 1: Прямое распространение для получения выхода
    double output[26];
    forward(cnn, input, output);

    if (label < 0 || label >= 26) 
    {
        printf("Ошибка: метка %d вне границ в backpropagation.\n", label);
        return;
    }

    for (int i = 0; i < 26; i++) 
    {
        if (isnan(output[i]) || isinf(output[i])) {
            printf("Ошибка: недопустимое значение в output[%d] перед softmax_derivative.\n", i);
            return;
        }
    }
    // Этап 2: Расчет ошибки выхода и производной
    double output_derivative[26];
    softmax_derivative(output, output_derivative, 26, label);

    // Этап 3: Производная для полностью связанного слоя
    // Используйте flat_output, уже присутствующий в структуре CNN
    // Заметьте, что flat_output уже обновляется в функции forward

    // Обновление весов полностью связанного слоя
    for (int i = 0; i < 4 * 5 * 5; i++) {
        for (int j = 0; j < 26; j++) {
            cnn->fc_weights[i][j] -= learning_rate * cnn->flat_output[i] * output_derivative[j];
        }
    }

    // Этап 4: Производные для второго слоя пулинга
    double pool2_derivative[4][5][5] = {0}; // Производные для выхода второго слоя пулинга
    for (int f = 0; f < 4; f++) {
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                pool2_derivative[f][i][j] = output_derivative[f]; // Распространение ошибки
            }
        }
    }

    // Вычислить градиенты для фильтров второго слоя
    double conv2_gradient[4][3][3][2] = {0}; // Градиент для фильтров второго слоя
    for (int f = 0; f < 4; f++) {
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                for (int c = 0; c < 2; c++) {
                    for (int m = 0; m < 3; m++) {
                        for (int n = 0; n < 3; n++) {
                            // Вычисление градиента для фильтров второго слоя
                            conv2_gradient[f][m][n][c] += pool2_derivative[f][i][j] * cnn->pool1_output[c][i + m][j + n];
                        }
                    }
                }
            }
        }
    }

    // Обновление весов для второго слоя свертки
    for (int f = 0; f < 4; f++) {
        for (int c = 0; c < 2; c++) {
            for (int m = 0; m < 3; m++) {
                for (int n = 0; n < 3; n++) {
                    cnn->conv2_filter[f][m][n][c] -= learning_rate * conv2_gradient[f][m][n][c];
                }
            }
        }
    }

    // Этап 5: Производные для первого слоя пулинга
    double pool1_derivative[2][12][12] = {0}; // Производные для выхода первого слоя пулинга
    double conv1_gradient[2][5][5] = {0}; // Градиент для фильтров первого слоя
    double relu1_derivative[2][24][24] = {0}; // Производная ReLU для первого слоя

    // Вычислить производную для слоя свертки 1
    for (int f = 0; f < 4; f++) {
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                // Распространение ошибки в слое свертки 1
                for (int c = 0; c < 2; c++) {
                    relu1_derivative[c][i][j] += pool2_derivative[f][i][j] * cnn->conv2_filter[f][c][i][j];
                }
            }
        }
    }

    // Обновление весов для первого слоя свертки
    for (int f = 0; f < 2; f++) {
        for (int m = 0; m < 5; m++) {
            for (int n = 0; n < 5; n++) {
                for (int c = 0; c < 1; c++) {
                    // Вычисление градиента для фильтров первого слоя
                    for (int i = 0; i < 24; i++) {
                        for (int j = 0; j < 24; j++) {
                            conv1_gradient[f][m][n] += relu1_derivative[f][i][j] * input[i + m][j + n];
                        }
                    }

                    // Обновление весов для слоя свертки 1
                    cnn->conv1_filter[f][m][n] -= learning_rate * conv1_gradient[f][m][n];
                }
            }
        }
    }
}

Я просто хочу, чтобы CNN распознавал символы

Ответ или решение

Проблемы с использованием сверточной нейронной сети (CNN) на языке C для распознавания символов: пошаговый разбор

Введение

Сверточные нейронные сети (CNN) являются мощным инструментом для распознавания образов, включая символы. Однако, если возникают проблемы, такие как получение бесконечных (inf) или неопределённых (NaN) значений в процессе работы сети, это может существенно повлиять на обучение модели. В данном ответе мы рассмотрим основные причины возникновения подобных проблем, основываясь на предоставленном коде CNN, и предложим возможные решения.

Анализ первоначальной проблемы

  1. Неинициализированные или неправильно инициализированные веса:
    Из кода видно, что веса инициализируются случайными значениями. Если инициализация не будет должным образом ограничена, это может привести к нестабильности, которая, в свою очередь, может вызвать увеличение значения до бесконечности. Например, если использовать слишком большие значения в начале обучения, это может всему научить сеть выдавать inf или NaN.

    Решение: Используйте более строгие методы инициализации весов (например, инициализацию Хе или инициализацию Глорот), чтобы гарантировать, что начальные значения будут в разумном диапазоне.

    double he_limit = sqrt(2.0 / (input_units));
    cnn->weights[i][j] = ((double)rand() / RAND_MAX) * 2.0 * he_limit - he_limit;
  2. Проблемы с нормализацией входных данных:
    Процесс нормализации входного изображения может не срабатывать должным образом. Убедитесь, что всё изображение действительно нормализуется, а значения пикселей находятся в диапазоне от 0 до 1.

    Решение: Добавьте проверки значений после нормализации:

    if (image->pixels[i][j] < 0 || image->pixels[i][j] > 1) {
       printf("Ошибка нормализации: значение пикселя вне диапазона [0, 1].\n");
    }
  3. Выходные данные функций активации (например, ReLU):
    Если данные, проходящие через функцию активации, выходят за пределы ожидаемых значений, например, слишком большие значения, они могут вызвать проблемы с обучением.

    Решение: Убедитесь, что функции активации, такие как ReLU, корректно обрабатывают входные значения и не вызывают неожиданных выходных значений.

  4. Проблемы с новой инициализацией весов в процессе обучения:
    В коде обратного распространения весов можно заметить, что веса обновляются с использованием градиентов, которые могут быть слишком велики, если значений на выходе не контролируют.

    Решение: Используйте методы регуляризации, такие как L2-регуляризация, чтобы контролировать величины обновляемых весов и избегать излишней коррекции.

    cnn->fc_weights[i][j] -= learning_rate * (gradient + lambda * cnn->fc_weights[i][j]);

Заключение

Проблемы с получением значений inf и NaN в CNN на C для распознавания символов могут быть вызваны несколькими факторами, включая неинициализированные веса, проблемы с нормализацией данных и неправильные градиенты во время обратного распространения. Правильные предварительные шаги, оптимальная инициализация и управление значениями на выходе функций активации могут значительно повысить эффективность работы вашей модели и помочь в устранении подобных проблем.

Эта статья может помочь вам лучше понять проблемы, с которыми может столкнуться ваша свёрточная нейронная сеть, и предложить полезные рекомендации для их устранения. Мы надеемся, что предложенные решения будут полезны для успешного завершения вашего проекта по распознаванию символов.

Оцените материал
Добавить комментарий

Капча загружается...