Будучи студентом останнього курсу комп'ютерних наук із захопленням до штучного інтелекту (AI) та машинного навчання (ML), я занурився у захоплюючий світ нейронних мереж. І що може бути кращим способом навчатися, ніж навчити машину робити неможливе: вирішувати, чи є розмита картинка величним собакою чи відстороненою кішкою? Ця подорож у бінарну класифікацію зображень — це не просто технічне завдання; це битва логіки проти капризів хутра та вусів. З нейронною мережею, оснащеною 12,288 вхідними нейронами, шаром обчислень і місією класифікації собак та котів, цей проект поєднує чарівність штучного інтелекту з хвилюванням від спостереження, як алгоритми вчаться. Спойлер: ніякі ласощі не знадобились для навчання цієї моделі, але математичні перекуси були спожиті у великій кількості.
Пояснення
Ця реалізація демонструє нейронну мережу з прямим проходом, призначену для бінарної класифікації зображень, зокрема для розрізнення зображень собак і котів. У своїй основі мережа обробляє вхідні зображення, пропускаючи їх через серію шарів, перетворюючи сирі піксельні дані на значущі патерни, і, в кінцевому підсумку, видаючи рішення про класифікацію. Архітектура починається з вхідного шару, здатного обробляти зображення розміром 64 x 64 пікселів і три кольорові канали (RGB). Це перетворюється на 12,288 вхідних нейронів (64 x 64 x 3), які служать початковою точкою для обчислень мережі.
NeuralNetwork::NeuralNetwork() : learningRate(0.0001) {
layers.push_back(Layer(IMAGE_SIZE * IMAGE_SIZE * 3, 256)); // Input → 256
layers.push_back(Layer(256, 128)); // 256 → 128
layers.push_back(Layer(128, 64)); // 128 → 64
layers.push_back(Layer(64, 32)); // 64 → 32
layers.push_back(Layer(32, 1)); // 32 → Output
}
Ці вхідні дані проходять через кілька прихованих шарів зменшуваного розміру: 256, 128, 64 і 32 нейрони. Прогресивне зменшення розміру дозволяє мережі витягувати та стискати значущі характеристики, ігноруючи неважливу інформацію. Наприкінці конвеєра один вихідний нейрон надає остаточну класифікацію. Якщо вихідне значення більше за 0.5, зображення класифікується як собака, тоді як значення 0.5 або менше вказує на кішку.
// Forward pass through a layer
std::vector Layer::forward(const std::vector& input) {
lastInput = input;
lastOutput.resize(outputSize);
for (int i = 0; i < outputSize; ++i) {
double sum = biases[i];
for (int j = 0; j < inputSize; ++j) {
sum += weights[i][j] * input[j]; // Weighted sum
}
lastOutput[i] = sigmoid(sum); // Apply activation
}
return lastOutput;
}
// Sigmoid activation function
double Layer::sigmoid(double x) {
return 1.0 / (1.0 + exp(-x)); // Maps any input to (0,1)
}
Для забезпечення того, щоб мережа отримувала вхідні дані у форматі, який сприяє ефективному навчанню, до зображень застосовується попередня обробка. Перший крок — нормалізація, що масштабує всі піксельні значення в діапазон [0, 1], ділячи кожне піксельне значення на 255. Це забезпечує послідовність масштабу вхідних даних. Потім нормалізовані дані стандартизуються шляхом центрування їх навколо середнього значення (µ) і масштабується за стандартним відхиленням (σ). Цей крок забезпечує не тільки однаковий масштаб входів, але й має постійну розподільність, що є критичним для стабільного та ефективного навчання мережі.
Після того, як вхідні зображення були попередньо оброблені, вони передаються через мережу за допомогою процесу, відомого як пряме поширення. Для кожного шару мережа обчислює зважену суму вхідних даних з попереднього шару, додає вектор зсувів і застосовує функцію активації. Математично цей процес можна описати так:
де W[l] представляє матрицю ваг для шару, a[l−1] представляє вихід активації попереднього шару, а b[l] — вектор зсувів.
Функція активації, яка застосовується до z[l], є сигмоїдною функцією, визначеною як
Ця функція стискає вихід у діапазоні [0, 1], що робить її придатною для задач бінарної класифікації. Для ефективної ініціалізації ваг мережа використовує ініціалізацію Хе. У цьому методі ваги вибираються з нормального розподілу з середнім значенням 0 і дисперсією
де n[l−1] — це кількість нейронів у попередньому шарі. Ця ініціалізація запобігає зменшенню або вибуху дисперсії активацій під час їх поширення через мережу.
void Layer::updateWeights() {
const double learningRate = 0.0001;
const double momentum = 0.9;
for (int i = 0; i < outputSize; ++i) {
for (int j = 0; j < inputSize; ++j) {
weights[i][j] -= learningRate * weightGradients[i][j]; // Update weights
weightGradients[i][j] *= momentum; // Apply momentum
}
biases[i] -= learningRate * biasGradients[i]; // Update biases
biasGradients[i] *= momentum; // Apply momentum
}
}
Для оцінки продуктивності мережі використовується бінарна крос-ентропія як функція втрат. Ця функція втрат спеціально розроблена для задач бінарної класифікації і кількісно оцінює різницю між прогнозованою ймовірністю ˆ y та справжньою міткою (y). Втрати обчислюються за формулою:
Штрафуючи за некоректні прогнози, функція втрат направляє мережу до мінімізації помилок під час навчання. Навчання мережі включає зворотне поширення (backpropagation), процес, який обчислює градієнти функції втрат щодо кожного параметра. Для вихідного шару помилка обчислюється як:
де ˆ y — прогнозований вихід, а y — справжня мітка. Потім обчислюються градієнти для ваг і зсувів:
Для прихованих шарів помилка поширюється назад за допомогою правила ланцюга:
де σ′(x) = σ(x)(1−σ(x)) — це похідна функції активації сигмоїд. Ці градієнти використовуються для оновлення параметрів під час кроку оптимізації.
Для оптимізації параметрів мережа використовує метод градієнтного спуску з моментумом для мінібатчів. Цей метод оновлює кожен параметр θ за формулою v = βv−α∇J(θ) та θ = θ+v, де β — коефіцієнт моментуму (встановлений на 0.9), α — це швидкість навчання, а ∇J(θ) — градієнт функції втрат щодо θ. Моментум допомагає прискорити збіжність, згладжуючи коливання, особливо в областях з високою кривизною. Швидкість навчання зменшується експоненціально з часом за формулою αt = α0 · 0.95t, де t — номер епохи. Це зменшення забезпечує зниження швидкості навчання по мірі просування навчання, що дозволяє мережі точно налаштувати свої параметри. Реалізація обробляє навчальні дані в мінібатчах по 32 зображення, що дає баланс між обчислювальною ефективністю та стабільністю оновлень градієнтів. Механізм завантаження даних є надійним, використовуючи OpenCV для перевірки форматів зображень та структури каталогів. Кодова база структурована за допомогою об'єктно-орієнтованого підходу, з окремими класами, які виконують різні функції.
Клас Neural Network управляє загальною архітектурою та циклом навчання, в той час як клас Layer обробляє обчислення та оновлення параметрів для окремих шарів.
// Inside train() method
for (int epoch = 0; epoch < epochs; ++epoch) {
// For each image
for (size_t i = 0; i < trainingData.size(); ++i) {
const auto& [imagePath, isDog] = trainingData[i];
// Forward pass
auto output = preprocessImage(img);
for (auto& layer : layers) {
output = layer.forward(output);
}
// Compute error
double target = isDog ? 1.0 : 0.0;
double error = output[0] - target;
// Backward pass
std::vector gradients = {error};
for (int j = layers.size() - 1; j >= 0; --j) {
gradients = layers[j].backward(gradients, learningRate);
}
// Update every batch_size iterations
if ((i + 1) % batchSize == 0) {
for (auto& layer : layers) {
layer.updateWeights();
}
}
}
learningRate *= 0.95; // Decay learning rate
}
Хоча ця реалізація орієнтована на зрозумілість і навчальні цілі, є можливість для покращень. Техніки, такі як нормалізація партій (batch normalization), можуть допомогти вирішити проблеми з внутрішніми ковариаційними зсувами, регуляризація через випадкове відключення (dropout regularization) може зменшити переобучення, а передові архітектури, такі як резидуальні з'єднання, можуть покращити продуктивність. Тим не менш, ця реалізація дає міцну основу для розуміння принципів глибокого навчання та бінарної класифікації зображень.
Висновок
Результати навчання показують поступове покращення точності протягом епох, починаючи з 56,6% на першій епосі і досягаючи 60,8% до п'ятої. Найбільший прогрес відбувається між першою та другою епохою, після чого приріст точності починає стабілізуватись — це типова тенденція при навчанні нейронних мереж. Хоча мережа показує трохи кращі результати, ніж випадковий вибір (50% для бінарної класифікації), загальна точність близько 60% свідчить про значний потенціал для покращення. Потенційні покращення можуть включати збільшення кількості епох навчання, тонке налаштування швидкості навчання, впровадження додаткових методів регуляризації, розширення або углиблення архітектури мережі або застосування аугментації даних для збагачення навчального набору.
Додаток
Код для цього проєкту доступний на GitHub за адресою:
[
GitHub - sinhaparth5/cat-dogs-classifier: Cats and Dog CNN
Cats and Dog CNN. Contribute to sinhaparth5/cat-dogs-classifier development by creating an account on GitHub.
github.com
](https://github.com/sinhaparth5/cat-dogs-classifier/tree/master?source=post_page-----b16f9598ac06--------------------------------)
Набір даних, використаний в цьому проєкті, можна знайти за посиланням:
Перекладено з: A Nueral Network’s tale of tails