Простой пример кода: ваша первая нейросеть за 15 минут (с помощью Python и библиотек)
В этой статье мы создадим простейшую искусственную нейронную сеть (ИНС) для решения задачи классификации. Мы будем использовать язык программирования Python и библиотеки, которые значительно упрощают процесс. Цель — на практическом примере понять базовый принцип работы нейросетей: подготовка данных, проектирование архитектуры, процесс обучения и оценка результатов. В качестве задачи рассмотрим классическое тестовое множество — распознавание рукописных цифр из набора данных MNIST.
Подготовка рабочей среды
Перед написанием кода необходимо установить требуемые библиотеки. Мы будем использовать три основных фреймворка: NumPy для численных операций, Matplotlib для визуализации и, самое главное, Keras в связке с TensorFlow в качестве бэкенда для построения и обучения нейронной сети. Keras предоставляет интуитивно понятный высокоуровневый API.
- TensorFlow: Машинный learning фреймворк от Google, выполняет низкоуровневые вычисления.
- Keras: Высокоуровневый API для нейронных сетей, работающий поверх TensorFlow (и других бэкендов).
- NumPy: Библиотека для работы с многомерными массивами и математическими функциями.
- Matplotlib: Библиотека для построения графиков и визуализации данных.
Установка выполняется через менеджер пакетов pip. Рекомендуется использовать виртуальное окружение Python.
pip install tensorflow numpy matplotlib
После успешной установки можно переходить к написанию кода.
Шаг 1: Импорт необходимых библиотек
Первым делом в Python-скрипте импортируем все необходимые модули. Это стандартное начало любого проекта в области машинного обучения.
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist
Шаг 2: Загрузка и первичный анализ данных
Используем встроенный в Keras набор данных MNIST. Он содержит 70 000 изображений рукописных цифр размером 28×28 пикселей, каждое из которых имеет метку от 0 до 9. Данные уже разделены на обучающую и тестовую выборки.
Загрузка данных
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
Давайте изучим структуру загруженных данных. Это критически важный этап для понимания того, с чем мы работаем.
print("Форма обучающего набора изображений:", train_images.shape)
print("Количество обучающих меток:", len(train_labels))
print("Форма тестового набора изображений:", test_images.shape)
print("Количество тестовых меток:", len(test_labels))
Вывод в консоли будет следующим:
- Обучающая выборка: 60000 изображений, каждое 28×28 пикселей.
- Тестовая выборка: 10000 изображений, каждое 28×28 пикселей.
Для наглядности отобразим несколько изображений из обучающей выборки.
Визуализация первых 9 изображений
plt.figure(figsize=(10, 10))
for i in range(9):
plt.subplot(3, 3, i + 1)
plt.imshow(train_images[i], cmap='gray')
plt.title(f"Метка: {train_labels[i]}")
plt.axis('off')
plt.show()
Шаг 3: Предобработка данных
Нейронные сети работают эффективнее, когда входные данные нормализованы (приведены к единому масштабу) и представлены в правильном формате.
- Нормализация: Значения пикселей в изображениях лежат в диапазоне от 0 до 255. Мы преобразуем их в диапазон [0, 1] путем деления на 255. Это ускоряет процесс обучения и улучшает сходимость модели.
- Преобразование формы (reshape): Каждое изображение 28×28 мы «разворачиваем» в одномерный вектор длиной 784 (28*28). Это превращает двумерную матрицу в одномерный массив, который может быть подан на вход полносвязного (Dense) слоя нейронной сети.
- Кодирование меток (One-Hot Encoding): Метки (цифры от 0 до 9) необходимо преобразовать в категориальный (one-hot) формат. Вместо числа ‘5’ метка станет вектором длины 10, где на 5-й позиции будет 1, а на остальных — 0. Это необходимо для корректной работы функции потерь при многоклассовой классификации.
Нормализация значений пикселей
train_images = train_images.astype('float32') / 255.0
test_images = test_images.astype('float32') / 255.0
Изменение формы данных: добавляем измерение для канала (для сверточных сетей это обязательно, для полносвязных — преобразуем в вектор)
Для полносвязной сети:
train_images_flat = train_images.reshape((60000, 28 28))
test_images_flat = test_images.reshape((10000, 28 28))
One-Hot Encoding меток
train_labels_cat = keras.utils.to_categorical(train_labels, 10)
test_labels_cat = keras.utils.to_categorical(test_labels, 10)
Шаг 4: Построение архитектуры нейронной сети
Создадим модель последовательной (Sequential) нейронной сети. Это линейный стек слоев. Наша первая сеть будет состоять из трех слоев:
- Входной слой (Flatten или Dense): Фактически, мы уже преобразовали данные в вектор. В модели мы можем использовать слой Flatten, который сделает это автоматически, либо сразу подать вектор на Dense-слой. Мы выберем первый вариант для наглядности.
- Скрытый полносвязный слой (Dense): Это основной вычислительный блок сети. Мы используем 128 нейронов и функцию активации ReLU (Rectified Linear Unit). ReLU заменяет все отрицательные значения на ноль, что позволяет сети обучаться быстрее и решать проблему затухающих градиентов.
- Выходной слой (Dense): Содержит 10 нейронов (по количеству классов — цифр от 0 до 9). Функция активации — softmax. Она преобразует выходные значения нейронов в вероятности принадлежности к каждому классу. Сумма всех выходных значений softmax равна 1.
Создание модели
model = keras.Sequential([
Слой, преобразующий изображение 28x28 в вектор из 784 элементов
layers.Flatten(input_shape=(28, 28)),
Полносвязный скрытый слой с 128 нейронами и активацией ReLU
layers.Dense(128, activation='relu'),
Выходной полносвязный слой с 10 нейронами (по числу классов) и активацией softmax
layers.Dense(10, activation='softmax')
])
Давайте посмотрим на сводку (summary) модели, чтобы понять ее структуру и количество обучаемых параметров.
model.summary()
Вывод покажет, что сеть имеет около 101,770 обучаемых параметров (весов и смещений).
Шаг 5: Компиляция модели
Перед началом обучения необходимо сконфигурировать процесс обучения с помощью метода compile. Здесь мы задаем три ключевых компонента:
- Оптимизатор (optimizer): Алгоритм, который на основе функции потерь обновляет веса сети. Мы используем ‘adam’ — адаптивный алгоритм градиентного спуска, который хорошо работает по умолчанию.
- Функция потерь (loss): Метрика, которую модель будет минимизировать в процессе обучения. Для многоклассовой классификации с one-hot кодированием используется ‘categorical_crossentropy’ (категориальная перекрестная энтропия).
- Метрики (metrics): Список метрик для мониторинга процесса обучения и тестирования. Мы будем отслеживать точность (‘accuracy’) — долю правильно классифицированных изображений.
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
Шаг 6: Обучение модели
Теперь мы обучаем модель на подготовленных данных с помощью метода fit. Обучение заключается в многократном предъявлении сети обучающих данных и корректировке весов для минимизации функции потерь.
- Эпоха (epoch): Один проход по всему обучающему набору данных. Мы зададим 5 эпох.
- Размер пакета (batch_size): Количество образцов, после обработки которых происходит обновление весов. Использование пакетов ускоряет обучение и требует меньше памяти. Возьмем размер пакета 64.
- Валидационная выборка (validation_data): Часть данных (в нашем случае — отдельный тестовый набор), на которой оценивается качество модели после каждой эпохи. Это позволяет отслеживать переобучение.
history = model.fit(train_images, train_labels_cat,
epochs=5,
batch_size=64,
validation_data=(test_images, test_labels_cat))
В процессе обучения в консоль будет выводиться прогресс для каждой эпохи, показывая значение потерь и точности на обучающей и валидационной выборках.
Шаг 7: Оценка качества модели
После обучения необходимо оценить итоговое качество модели на тестовых данных, которые она никогда не видела во время обучения. Для этого используется метод evaluate.
test_loss, test_acc = model.evaluate(test_images, test_labels_cat, verbose=2)
print(f'nТочность на тестовых данных: {test_acc:.4f}')
print(f'Потери на тестовых данных: {test_loss:.4f}')
Также полезно визуализировать процесс обучения, построив графики изменения точности и потерь по эпохам.
Построение графиков
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Точность на обучении')
plt.plot(history.history['val_accuracy'], label='Точность на валидации')
plt.xlabel('Эпоха')
plt.ylabel('Точность')
plt.legend()
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Потери на обучении')
plt.plot(history.history['val_loss'], label='Потери на валидации')
plt.xlabel('Эпоха')
plt.ylabel('Потери')
plt.legend()
plt.grid(True)
plt.show()
Шаг 8: Использование модели для предсказаний
Обученную модель можно использовать для предсказания меток новых, ранее не виденных данных. Метод predict возвращает массив вероятностей для каждого класса.
Получение предсказаний для первых 5 тестовых изображений
predictions = model.predict(test_images[:5])
Вывод вероятностей для первого изображения
print("Вероятности для первого изображения:", predictions[0])
Нахождение индекса класса с максимальной вероятностью (argmax)
print("Предсказанная цифра для первого изображения:", np.argmax(predictions[0]))
print("Истинная цифра для первого изображения:", test_labels[0])
Для наглядности можно отобразить тестовые изображения вместе с предсказанными и истинными метками.
plt.figure(figsize=(10, 5))
for i in range(5):
plt.subplot(1, 5, i + 1)
plt.imshow(test_images[i], cmap='gray')
pred_label = np.argmax(predictions[i])
true_label = test_labels[i]
color = 'green' if pred_label == true_label else 'red'
plt.title(f"Прогноз: {pred_label}nИстина: {true_label}", color=color)
plt.axis('off')
plt.tight_layout()
plt.show()
Полный код программы
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist
1. Загрузка данных
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
2. Предобработка данных
train_images = train_images.astype('float32') / 255.0
test_images = test_images.astype('float32') / 255.0
train_labels_cat = keras.utils.to_categorical(train_labels, 10)
test_labels_cat = keras.utils.to_categorical(test_labels, 10)
3. Построение модели
model = keras.Sequential([
layers.Flatten(input_shape=(28, 28)),
layers.Dense(128, activation='relu'),
layers.Dense(10, activation='softmax')
])
4. Компиляция модели
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
5. Обучение модели
history = model.fit(train_images, train_labels_cat,
epochs=5,
batch_size=64,
validation_data=(test_images, test_labels_cat))
6. Оценка качества
test_loss, test_acc = model.evaluate(test_images, test_labels_cat, verbose=2)
print(f'nИтоговая точность на тесте: {test_acc:.4f}')
7. Предсказание
predictions = model.predict(test_images[:5])
for i in range(5):
print(f"Изображение {i+1}: Прогноз = {np.argmax(predictions[i])}, Истина = {test_labels[i]}")
Ответы на часто задаваемые вопросы (FAQ)
Что такое нейрон в искусственной нейронной сети?
Искусственный нейрон — это математическая функция, являющаяся упрощенной моделью биологического нейрона. Он принимает один или несколько входных сигналов (данные или выходы других нейронов), умножает каждый на соответствующий вес (параметр, который настраивается в процессе обучения), суммирует результаты, добавляет смещение (bias) и пропускает полученное значение через нелинейную функцию активации (например, ReLU, sigmoid, softmax). Результат этого вычисления является выходом нейрона.
Почему мы нормализуем входные данные (делим пиксели на 255)?
Нормализация данных к единому масштабу (обычно в диапазон [0, 1] или [-1, 1]) является стандартной практикой предобработки. Это делается по нескольким причинам:
- Скорость сходимости: алгоритмы оптимизации, такие как градиентный спуск, работают значительно быстрее и стабильнее, когда все признаки имеют одинаковый масштаб.
- Численная стабильность: предотвращает проблемы с переполнением или исчезновением градиентов, которые могут возникнуть при работе с большими числами.
- Единый вклад признаков: без нормализации признак с большим диапазоном значений (например, от 0 до 10000) может необоснованно сильнее влиять на результат, чем признак с малым диапазоном (0-1).
В чем разница между функцией потерь (loss) и метрикой (metric)?
Функция потерь (Loss function) — это целевая функция, которую алгоритм оптимизации (например, Adam) напрямую пытается минимизировать в процессе обучения. Ее значение используется для вычисления градиентов и обновления весов сети. Пример: categorical_crossentropy.
Метрика (Metric) — это показатель качества модели, который используется для оценки и мониторинга, но не для непосредственного обучения. Метрика должна быть понятна человеку. Часто используемая метрика — accuracy (точность). Важно: модель может оптимизировать одну функцию потерь, а мы при этом отслеживать несколько метрик.
Что такое переобучение (overfitting) и как его заметить по графикам обучения?
Переобучение возникает, когда модель слишком хорошо запоминает обучающие данные, включая их шум и случайные fluctuations, и теряет способность обобщать знания на новые, невиданные данные. На графиках процесса обучения переобучение проявляется следующим образом:
- Метрика точности (accuracy) на обучающей выборке продолжает расти или остается на высоком уровне.
- Метрика точности на валидационной (тестовой) выборке перестает улучшаться и начинает снижаться после определенной эпохи.
- Значение функции потерь (loss) на обучающей выборке продолжает уменьшаться, а на валидационной — начинает увеличиваться.
В нашем простом примере с 5 эпохами переобучение, скорее всего, не успело проявиться. Для борьбы с переобучением используют методы регуляризации (Dropout, L1/L2), увеличение объема обучающих данных, аугментацию данных и раннюю остановку (early stopping).
Можно ли улучшить точность этой простой сети?
Да, точность можно значительно повысить. Набор MNIST является хорошо изученным, и на нем достигается точность выше 99%. Пути улучшения:
- Увеличение сложности модели: Добавление дополнительных Dense-слоев или увеличение числа нейронов в существующих слоях.
- Использование сверточных слоев (CNN): Замена полносвязной сети на сверточную нейронную сеть (Convolutional Neural Network), которая специально разработана для работы с изображениями и учитывает их пространственную структуру. Это самый эффективный способ.
- Регуляризация: Добавление слоев Dropout для предотвращения переобучения.
- Настройка гиперпараметров: Подбор количества эпох, размера пакета (batch size), типа оптимизатора и его learning rate.
- Увеличение количества эпох: Обучение в течение большего числа эпох (с обязательным контролем переобучения).
Что такое batch_size и почему он не равен 1?
Параметр batch_size определяет количество образцов, которые обрабатываются нейронной сетью перед одним обновлением ее весов. Варианты:
- Stochastic Gradient Descent (SGD, размер пакета = 1): Веса обновляются после каждого отдельного примера. Обновления очень шумные, обучение медленное.
- Batch Gradient Descent (размер пакета = размер всей выборки): Веса обновляются один раз за эпоху на основе градиента, вычисленного по всем данным. Требует много памяти, может застревать в локальных минимумах.
- Mini-batch Gradient Descent (1 < batch_size < размера выборки): Компромиссный вариант. Обновления менее шумные, чем при SGD, и более частые, чем при Batch GD. Позволяет эффективно использовать параллельные вычисления на GPU. Значение 32, 64, 128 является стандартным выбором.
Куда двигаться дальше после создания первой нейросети?
Рекомендуемый путь для углубления знаний:
- Изучить теорию: линейная алгебра, основы математического анализа (градиенты), теория вероятностей.
- Попрактиковаться на других классических наборах данных (например, CIFAR-10 для цветных изображений).
- Освоить более сложные архитектуры: сверточные нейронные сети (CNN) для изображений, рекуррентные нейронные сети (RNN, LSTM) для текстов и временных рядов.
- Изучить фреймворки глубже: освоить низкоуровневый API TensorFlow или PyTorch для большей гибкости.
- Реализовать собственный проект: выбрать задачу (классификация изображений, анализ тональности текста и т.д.) и попытаться решить ее с помощью нейронных сетей.
Комментарии