Когда я впервые села за компьютер с мыслью: «А не обучить ли мне нейросеть?» у меня не было понимания как это сделать и с чего начать. Зато была любовь к собакам, интерес к машинному обучению и желание разобраться, как всё работает. Так родился проект HappyPuppy - моя первая нейронка, которая распознаёт сибирского хаски и французского бульдога на фотографии. Просто загрузите фото (jpg, jpeg, png до 1MB) с вашим питомцем и модель предскажет породу.
Далее я расскажу, как на домашнем Маке появилась и выросла моя первая сверточная нейронная сеть (CNN): от идеи до работающей модели — её создание, обучение и тестирование.
Эта история будет особенно полезна новичкам в мире ИИ без опыта в программировании.
Ссылка на код на GitHub, архитектура модели и маленький ликбез по сверточным нейронным сетям будут в конце статьи.
А сейчас — история создания по шагам.
ЧАСТЬ 1. Обучение модели с нуля
ШАГ 1 – поиск данных, выбор библиотеки и создание проекта
Первый и самый важный шаг – это найти данные, на которых модель будет обучаться. В моем случае был необходим набор фотографий с разными породами собак. Причем я понимала, что руками его не собрать, это займет вечность, поэтому сразу искала готовый, и он нашёлся на сайте университета — Stanford Dogs Dataset.
Он содержит более 20 тысяч изображений 120 пород собак. Для первой модели я выбрала 10 пород, чтобы упростить задачу и ускорить обучение. Также решила использовать библиотеку машинного обучения TensorFlow за её два основных плюса:
Подходит для новичков
Датасет загружается напрямую из библиотеки одной строчкой кода
В Visual Studio Code я создала и настроила новый проект под названием HappyPuppy и сразу столкнулась с первой проблемой. TensorFlow отказалась работать с Python 3.13, так что пришлось повозиться с установкой Python 3.10 и переключателем версий. Когда всё заработало, я создала файл load_dogs_dataset.py со скриптом на Python для загрузки датасета.
Возможно, читая первый шаг, вы спросите:
«Ты написала, что у тебя не было понимания, как всё делать, и что статья подходит для новичков без опыта программирования — кто же писал все скрипты и объяснял, с чего начать?»
Все скрипты мне помогает писать моя ИИ-помощница ChatGPT 4o. И не только скрипты, а вообще всё: я - менеджер этого проекта, главный идеолог и тестировщик, но ChatGPT - это мой программист, архитектор и преподаватель в одном лице.
ИТОГО: проект создан, датасет загружен.
ШАГ 2 – подготовка данных к обучению
Чтобы подготовить данные к обучению, я создала файл filter_dogs.py и сохранила в нём скрипт, который:
Загружает весь датасет
Фильтрует породы, отобранные для обучения по специальной метке
Делит отфильтрованные породы на тренировочный и тестовый датасеты
То есть по 10 породам данные разделены на train и test - на них модель будет учиться и тестироваться!
ИТОГО: данные отфильтрованы и почти готовы к обучению.
ШАГ 3 – предобработка изображений
Чтобы модель обучалась, данные должны быть приведены к единому формату.
Поэтому следующим этапом стала предобработка изображений.
Для этого я создала файл data_pipeline.py и настроила функцию preprocess, которая:
Изменяет размер изображения до 224x224 пикселей
Приводит метки к нужному формату с помощью map_label
Формирует батчи и ускоряет обучение с помощью prefetch (батчи позволяют разбить данные на небольшие порции, а prefetch подгружает следующую порцию заранее)
В итоге у нас получились два датасета, готовых для обучения — train_ds и test_ds,которые можно напрямую подавать в модель.
ИТОГО: данные готовы для обучения.
ШАГ 4 – создание модели
Архитектура модели уместилась в 20 строках файла model.py!
Для построения модели я использовала Keras — это высокоуровневая надстройка над TensorFlow, которая делает создание нейросетей более понятным.
Модель создаётся с помощью Sequential API — это способ, при котором слои добавляются один за другим, последовательно.
Вот что делает мой скрипт на 20 строк по созданию модели:
Задаёт модель как Sequential
Добавляет три типа слоёв: входной, скрытые (Conv2D + Pooling) и выходной (Dense)
Запускает функцию активации (ReLU и Softmax)
Модель простая, зато легко обучается и быстро тестируется.
ИТОГО: модель создана и готова к обучению:
Компонент | Значение |
---|---|
Модель | Sequential |
Слои | Rescaling → Conv2D(32) → MaxPooling2D → Conv2D(64) → MaxPooling2D → Flatten() → Dense(32) → Dense(2, softmax) |
Выход | 2 нейрона с softmax |
Метки | целочисленные метки (0, 1) |
Loss | sparse categorical crossentropy — для целочисленных меток |
Optimizer | Adam — градиентный спуск с адаптацией |
ШАГ 5 – обучение модели на 10 породах
Я создала файл main.py, в котором:
Загружается датасет, подготовленный для обучения
Вызывается модель и обучается
Модель сохраняется в отдельный файл для тестирования и (если повезёт) для использования
Для обучения модели использовалось 10 итераций (в машинном обучении они называются эпохи) и максимум 5 минут времени.
Результат обучения — это матрица весов или, иными словами, параметры модели, которые позволяют ИИ «видеть», что изображено на картинке.
Но чтобы не ошибаться, веса проходят постоянную корректировку, прежде чем станут финальными.
Именно этот процесс и проходит в каждой эпохе обучения.
Что конкретно происходит?
На вход подаётся картинка
Модель смотрит на неё и пытается предсказать, что это
На основе метки сравнивает свой ответ с правильным
Вычисляет ошибку (loss)
Изменяет веса, чтобы ошибка стала меньше
Снизить ошибки помогает метод, хорошо известный в машинном обучении — градиентный спуск (он тоже зашит в процесс обучения)
ИТОГО: модель прошла обучение — и самое время её протестировать.
ШАГ 6 – тестирование модели и первое фиаско
Для тестирования я использовала два метода:
Метрики, которые выводятся прямо в Терминале
Новые фотографии собак, которые модель ещё не видела, но должна начать распознавать, если обучена правильно
Основных метрик 4:
Train Accuracy — насколько хорошо модель угадывает правильные ответы на обучающем наборе. Например, 95% означает, что модель хорошо запомнила изображения из обучения
Val Accuracy — насколько хорошо модель угадывает правильные ответы на проверочном (валидационном) наборе, который она никогда не видела на обучении. Например, 20% означает, что когда модели дают новую картинку собаки, она теряется и не знает, что это
Train Loss — сколько ошибок модель делает на обучающих данных, в числовом выражении, через функцию потерь
Val Loss — сколько ошибок модель делает на тестовых данных, тоже в числовом выражении
Если Val Loss падает — это лучший знак: модель учится понимать суть, а не просто запоминает.
Но увы, не в нашем случае. На моём проекте обучение сразу стало показывать плохие результаты, и Val Loss постоянно рос.
Честно скажу, мы пробовали модель переучивать, и моя ИИ-помощница предлагала много вариантов улучшений — мы всё пробовали.
Нашли одну критичную ошибку — были перепутаны метки, поэтому хаски была бульдогом. Но даже это исправление не помогло обучить модель правильно.
Мы проверяли распределение классов (пород) в тренировочном и тестовых наборах, применяли аугментацию — искусственное расширение датасета за счёт смены угла картинки, изменения размера. Ничего не помогало, и мы решили упростить обучение и перейти на вариант только с 2 породами — по сути, бинарная классификация.
Весь следующий день потратили на изменение проекта и переобучение — результат был тем же.
Модель не могла распознать по фото — это французский бульдог или сибирская хаски?
ИТОГО, мы пришли к выводу, что модель не учится потому что:
Слишком мало данных — 100 фото на класс не дают достаточного разнообразия, чтобы модель могла выделить устойчивые визуальные паттерны (ушки, мордочка, шерсть)
Слишком сложная задача «с нуля» — простая CNN с несколькими слоями не может обобщить признаки без большого количества примеров
Аугментация помогает, но это всё равно вариации тех же самых картинок
Модель постоянно залипает на одном классе, показывая одну и ту же уверенность для разных пород (например, 62.8% для бульдога и столько же для хаски, тоже считая её бульдогом), а значит веса не обучены
В этом месте я предлагаю сделать перерыв и посмеяться над комиксом, который мы сделали по поводу обучения нашей первой нейронки.
А затем сразу перейдём ко второй части — более успешной.
Комикс о том, как всё это выглядело на самом деле

ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации
(ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород:
французский бульдог и сибирская хаски
Заморозили веса базовой модели
(чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
(скриншот в конце статьи)
data_pipeline2.py — готовит данные для обучения
(скриншот в конце статьи)
Итого текущая модель:
Здесь будет табличка
Компонент | Значение |
---|---|
Модель | Sequential |
Слои | MobileNetV2 (предобученный) → GlobalAveragePooling2D → Dense(128) → Dropout(0.3) → Dense(2, softmax) |
Выход | 2 нейрона с softmax |
Метки | one-hot векторы ( |
Loss | categorical crossentropy — для one-hot меток |
Optimizer | Adam — градиентный спуск с адаптацией |
Компонент | Значение |
---|---|
Модель | Sequential |
Слои | MobileNetV2 (предобученный) → GlobalAveragePooling2D → Dense(128) → Dropout(0.3) → Dense(2, softmax) |
Выход | 2 нейрона с softmax |
Метки | one-hot векторы ( |
Loss | categorical crossentropy — для one-hot меток |
Optimizer | Adam — градиентный спуск с адаптацией |
ё и только двух: французского бульдога и сибирского хаски. Аугментацией её поливали щедро — и поворотами, и бликами, и даже контрастом, но всё было тщетно. На 10-й эпохе она всё ещё путала бульдога с хаски и твёрдо считала, что все собаки — бульдоги, а уверенность в этом у неё — 62.8%, как отгравировано в душе.
Владелица этой CNN-звезды, добрая и светлая Лена, в отчаянии пыталась помочь: она тормошила слои, перепроверяла метки, звала экспертов с Reddit, но моделька только обиженно фыркала softmax-ом и отказывалась обучаться.
Лия, её AI-компаньон, терпеливо говорила:
— Нам бы, Лена, ещё фоточек тыщ десять, GPU посовременнее и кофе с корицей...
А моделька мечтала:
— А может… я просто предобученной стану?.. Как Витя с VGG16?
Так проект и вошёл в историю как
"Мы не обучили нейронку — но очень старались".
Но знаете что? Иногда важен не результат, а путь.
А у этой команды путь был прекрасен.
ЧАСТЬ 2. Обучение предобученной модели
В действие ввели План Б — обучать предобученную модель, а именно:
Выбрали базовую модель — MobileNetV2 (лёгкая и отличная для начала)
Обрезали её финальные слои классификации (ведь нам нужны свои слои под конкретную задачу)
Добавили собственные слои для классификации двух пород: французский бульдог и сибирская хаски
Заморозили веса базовой модели (чтобы не переобучать её лишний раз)
Обучили новые слои на нашем датасете
Для этого я создала новую папку в проекте и переписала скрипты:
main2.py — загружает датасеты, подключает предобученную модель, добавляет наши слои, обучает модель и сохраняет её
data_pipeline2.py — готовит данные для обучения
Итого новая текущая модель:
Компонент | Значение |
---|---|
Модель | Sequential |
Слои | MobileNetV2 (предобученный) → GlobalAveragePooling2D → Dense(128) → Dropout(0.3) → Dense(2, softmax) |
Выход | 2 нейрона с softmax |
Метки | one-hot векторы ( |
Loss | categorical crossentropy — для one-hot меток |
Optimizer | Adam — градиентный спуск с адаптацией |
Когда модель обучилась мы увидели резкое улучшение метрик: на валидации accuracy = 100%, а val_loss пошёл вниз и составил всего 0.0053.
Это была наша первая маленькая победа!
Далее для предсказания породы в файле predict_sample2.py мы написали скрипт, который:
Загружает финальную обученную модель
Берёт фотографию пользователя
Обрабатывает её (приводит к единому формату)
Делает предсказание породы
Выводит результат пользователю
Финальная версия модели получилась очень лёгкой — всего 11 мегабайт. В этом небольшом размере уместились:
Предобученная MobileNetV2 (без лишних слоёв)
Наша собственная «голова» для классификации двух пород
Это позволило создать компактный, быстрый и удобный продукт, который можно легко переносить и разворачивать.
Тестирование
Когда всё было готово, я начала загружать фотографии, которые модель раньше не видела:
Обычные фото хаски или бульдогов — модель уверенно распознавала их с точностью 100%
Сложные фото: собака с человеком, два щенка на одной фотографии — модель справлялась
Посторонние объекты: например, мотоциклы — модель корректно отсеивала их как «не хаски и не бульдог»
Поскольку наша модель обучалась только на два класса, мы установили более высокий трешхолд уверенности: всё, в чём модель не уверена выше заданного порога, отправляется в категорию «другое». Хотя сама модель не знает, что именно это «другое».
Самые сложные тесты
Захотелось испытать модель на прочность. Я погуглила породы, максимально похожие на хаски и бульдога — и протестировала их.
Результат: модель распознавала такие изображения как валидные и относила их либо к хаски, либо к бульдогу (то есть нюансов уже не видела).
Итого
Модель хорошо видит основные признаки хаски и бульдогов
Глубоких нюансов не различает — так как не обучена этому
Несмотря на это, результат мы посчитали успешным, особенно для первой модели, построенной дома и без специальных знаний
Завершение проекта
Как только стало ясно, что модель рабочая, мы собрали веб-интерфейс на Flask и доработали рендеринг для мобильных устройств. Я уже делала это на предыдущем проекте, поэтому времени ушло минимум.
Последний штрих — опубликовать приложение. И мы выбрали Google Cloud, потому что:
нам дали бесплатный сервис
лёгкие настройки
и главное — задел на будущее
Пояснение для тех, кто будет тестировать модель
Данная CNN обучена для одной конкретной задачи - распознавать по фотографии французского бульдога или сибирскую хаски. Если вы загрузите фото одной из этих пород, модель определит её и сообщит, кто именно на снимке. Все остальные изображения будут классифицированы как “не бульдог и не хаски”. При этом сохраняется вероятность ошибок, особенно если загружаемые породы очень похожи на целевые классы.
Выводы и открытия
Маленькую нейронку можно построить дома. Это просто математическая модель, которая оживает от пары строчек кода в терминале. Для неё не нужны дата-центры и серверные комнаты размером с дом.
Компьютер сразу понял, что творится что-то серьёзное. Даже на 10 эпохах обучения он включил экстренное охлаждение. Теперь понятно, почему у IT-гигантов электростанции на заднем дворе.
Ошибки — это нормально. Любые модели могут ошибаться — от нашей маленькой нейронки до самых мощных трансформеров.
Маленькое "смогу” заводит целую вселенную. Сказать себе “Я построю свою нейронку” — это как расстелить коврик для йоги: дальше просто делаешь и получается, пусть и не сразу.
Респект учёным и инженерам, кто всё это придумал. Без моей ИИ-помощницы я бы, в лучшем случае, осталась на этапе выбора фотографий для датасета.
Приложения
Архитектура модели

Краткий ликбез, как «видит» сверточная нейронная сеть
Когда человек смотрит на изображение, он воспринимает его сразу, целиком. Сверточная нейронная сеть так не умеет. Она «едет» по изображению маленькими кусочками, каждый из которых пропускает через фильтры и сворачивает в признаки.
Это похоже на то, как если бы вам дали свернутый в трубочку постер: разворачивая его постепенно, вы сначала видите лишь фрагменты, а затем картину целиком.
Что такое фильтр?
Фильтр — это ядро свёртки: маленькая обучаемая матрица весов.
Именно веса определяют, насколько сильно тот или иной признак влияет на итоговое решение модели.
Вес — главный «показатель смысла» для модели, чем больше вес, тем важнее соответствующий фрагмент изображения для распознавания.
Как работает свёртка — шаг за шагом:
1. Преобразование изображения: изображение превращается в матрицу чисел (каждый пиксель — это число).
2. Фильтр «проезжает» по изображению и на каждом участке:
умножает свои веса на соответствующие пиксели
складывает всё в одно итоговое число
переезжает на следующий участок
3. Результат: формируется новая матрица — карта признаков (feature map).
Один фильтр может «замечать» горизонтальные линии
Другой — вертикальные
Третий — края, изгибы или текстуры
Что происходит дальше?
Постепенно сеть сохраняет важные признаки и отбрасывает лишнее.
И слой за слоем вырисовывается всё более целостная картина, которую в конце концов модель интерпретирует как «Французский бульдог» или «Сибирская хаски».
Когда я только начала учить мир ИИ, я думала, что вот сейчас «пощупаю» нейронную сеть — веса, слои, нейроны. Увижу там что-то типа галактических узлов с красивой подсветкой. А в итоге — всё, что есть у современного ИИ — это числа и больше ничего.
Ниже — скриншот кусочка весов моей модели. Это её мозг — то, как она «мыслит». Как представить, что из этого рождается картинка? Сложно.
