Вопрос или проблема
Я не много знаю о том, как работают 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, и предложим возможные решения.
Анализ первоначальной проблемы
-
Неинициализированные или неправильно инициализированные веса:
Из кода видно, что веса инициализируются случайными значениями. Если инициализация не будет должным образом ограничена, это может привести к нестабильности, которая, в свою очередь, может вызвать увеличение значения до бесконечности. Например, если использовать слишком большие значения в начале обучения, это может всему научить сеть выдаватьinf
илиNaN
.Решение: Используйте более строгие методы инициализации весов (например, инициализацию Хе или инициализацию Глорот), чтобы гарантировать, что начальные значения будут в разумном диапазоне.
double he_limit = sqrt(2.0 / (input_units)); cnn->weights[i][j] = ((double)rand() / RAND_MAX) * 2.0 * he_limit - he_limit;
-
Проблемы с нормализацией входных данных:
Процесс нормализации входного изображения может не срабатывать должным образом. Убедитесь, что всё изображение действительно нормализуется, а значения пикселей находятся в диапазоне от 0 до 1.Решение: Добавьте проверки значений после нормализации:
if (image->pixels[i][j] < 0 || image->pixels[i][j] > 1) { printf("Ошибка нормализации: значение пикселя вне диапазона [0, 1].\n"); }
-
Выходные данные функций активации (например, ReLU):
Если данные, проходящие через функцию активации, выходят за пределы ожидаемых значений, например, слишком большие значения, они могут вызвать проблемы с обучением.Решение: Убедитесь, что функции активации, такие как ReLU, корректно обрабатывают входные значения и не вызывают неожиданных выходных значений.
-
Проблемы с новой инициализацией весов в процессе обучения:
В коде обратного распространения весов можно заметить, что веса обновляются с использованием градиентов, которые могут быть слишком велики, если значений на выходе не контролируют.Решение: Используйте методы регуляризации, такие как L2-регуляризация, чтобы контролировать величины обновляемых весов и избегать излишней коррекции.
cnn->fc_weights[i][j] -= learning_rate * (gradient + lambda * cnn->fc_weights[i][j]);
Заключение
Проблемы с получением значений inf
и NaN
в CNN на C для распознавания символов могут быть вызваны несколькими факторами, включая неинициализированные веса, проблемы с нормализацией данных и неправильные градиенты во время обратного распространения. Правильные предварительные шаги, оптимальная инициализация и управление значениями на выходе функций активации могут значительно повысить эффективность работы вашей модели и помочь в устранении подобных проблем.
Эта статья может помочь вам лучше понять проблемы, с которыми может столкнуться ваша свёрточная нейронная сеть, и предложить полезные рекомендации для их устранения. Мы надеемся, что предложенные решения будут полезны для успешного завершения вашего проекта по распознаванию символов.