Нейронные сети на языке Python: полный обзор методов и инструментов

Раздел: Продвинутый Python -> Машинное обучение и ИИ

Основной подход: построение нейронной сети с помощью Keras

Библиотека Keras, входящая в состав TensorFlow, предоставляет высокоуровневый интерфейс для создания и обучения нейронных сетей. Этот вариант считается наиболее эффективным для быстрого прототипирования и промышленного использования благодаря интеграции с аппаратным ускорением, обширной экосистеме и простоте синтаксиса.

Как создать и обучить полносвязную нейронную сеть для классификации изображений на Keras?

import tensorflow as tf
from tensorflow import keras

# Загрузка данных
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255.0
x_test = x_test.reshape(10000, 784).astype('float32') / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)

# Определение модели
model = keras.Sequential([
    keras.layers.Dense(128, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Обучение
history = model.fit(x_train, y_train,
                    batch_size=128,
                    epochs=10,
                    validation_data=(x_test, y_test))

нейронная сеть на python (нейронные сети на python)

Пояснение шагов: данные MNIST нормализуются и преобразуются в плоский вектор. Модель состоит из двух скрытых слоёв с функцией активации ReLU, слоя Dropout для борьбы с переобучением и выходного softmax-слоя. Компиляция задаёт оптимизатор Adam, категориальную кросс-энтропию и метрику точности. Обучение выполняется с валидационной выборкой.

Типичные ошибки и их решение:

  • Неправильная форма входных данных - проверьте input_shape и reshape.
  • Размер батча слишком мал (медленная сходимость) или велик (нехватка памяти) - подбирается экспериментально.
  • Высокий loss на валидации при низком train loss - переобучение, добавьте Dropout или регуляризацию L2.
  • Игнорирование нормализации данных - приводит к плохой сходимости.

Вариант 1: Нейронная сеть на PyTorch с динамическим графом

PyTorch предоставляет низкоуровневый контроль над вычислениями, что удобно для исследовательских задач и кастомных архитектур.

Как реализовать тот же классификатор на PyTorch с использованием классов nn.Module?

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

# Подготовка данных
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

(x_train, y_train), (x_test, y_test) = ...  # загрузка MNIST
x_train = torch.tensor(x_train.reshape(60000, 784).astype('float32') / 255.0)
y_train = torch.tensor(keras.utils.to_categorical(y_train, 10))

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 128)
        self.relu1 = nn.ReLU()
        self.drop1 = nn.Dropout(0.2)
        self.fc2 = nn.Linear(128, 64)
        self.relu2 = nn.ReLU()
        self.fc3 = nn.Linear(64, 10)
        self.softmax = nn.Softmax(dim=1)

    def forward(self, x):
        x = self.relu1(self.fc1(x))
        x = self.drop1(x)
        x = self.relu2(self.fc2(x))
        return self.softmax(self.fc3(x))

model = Net().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

train_dataset = TensorDataset(x_train, y_train)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)

for epoch in range(10):
    for data, target in train_loader:
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()

PyTorch требует явного определения forward pass и ручного управления циклами обучения, что даёт гибкость в настройке, но увеличивает объём кода.

Проблемы:

  • Забыть перенести тензоры на device (CPU/GPU) - вызывает медленную работу.
  • Неправильная размерность для CrossEntropyLoss - ожидает raw logits, не softmax внутри функции.
  • Отсутствие zero_grad() - накопление градиентов.

Вариант 2: Реализация нейронной сети с нуля на NumPy (без библиотек)

Этот вариант демонстрирует математические основы прямого распространения и обратного распространения ошибки. Используется для обучения и понимания внутренних механизмов.

Как написать двухслойный перцептрон на чистом NumPy?

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)

# Генерация данных (логическое XOR)
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])

# Инициализация весов
np.random.seed(0)
w1 = np.random.randn(2, 4)
b1 = np.zeros((1, 4))
w2 = np.random.randn(4, 1)
b2 = np.zeros((1, 1))

lr = 0.5
for epoch in range(10000):
    # Прямой проход
    z1 = np.dot(X, w1) + b1
    a1 = sigmoid(z1)
    z2 = np.dot(a1, w2) + b2
    a2 = sigmoid(z2)

    # Ошибка
    loss = np.mean((a2 - y) ** 2)

    # Обратное распространение
    dz2 = (a2 - y) * sigmoid_derivative(a2)
    dw2 = np.dot(a1.T, dz2)
    db2 = np.sum(dz2, axis=0, keepdims=True)
    dz1 = np.dot(dz2, w2.T) * sigmoid_derivative(a1)
    dw1 = np.dot(X.T, dz1)
    db1 = np.sum(dz1, axis=0, keepdims=True)

    # Обновление весов
    w2 -= lr * dw2
    b2 -= lr * db2
    w1 -= lr * dw1
    b1 -= lr * db1

print("Результат после обучения:")
x = np.array([[0,1]])
z1 = np.dot(x, w1) + b1
a1 = sigmoid(z1)
z2 = np.dot(a1, w2) + b2
print(sigmoid(z2))

Цель такого подхода – изучение процесса градиентного спуска и обратного распространения. Не рекомендуется для реальных проектов из-за низкой производительности и отсутствия оптимизаций.

Ошибки новичков:

  • Неправильная размерность матриц при умножении.
  • Отсутствие сигмоидной производной при обратном проходе.
  • Слишком малая/большая скорость обучения.

Вариант 3: Использование MLP из scikit-learn для быстрых экспериментов

Scikit-learn предлагает класс MLPClassifier – простую нейронную сеть с одним скрытым слоем. Подходит для задач, где не требуется глубокое обучение и важна скорость внедрения.

Как обучить многослойный перцептрон на датасете Iris?

from sklearn.neural_network import MLPClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

clf = MLPClassifier(hidden_layer_sizes=(10,), activation='relu',
                    max_iter=1000, random_state=42)
clf.fit(X_train_scaled, y_train)
print("Accuracy:", clf.score(X_test_scaled, y_test))

Этот вариант подходит для небольших наборов данных и быстрых проверок гипотез. Ограничение – отсутствие поддержки свёрточных и рекуррентных слоёв.

Частые трудности:

  • Отсутствие масштабирования признаков – MLP чувствителен к масштабу.
  • Слишком большое количество скрытых узлов – переобучение на маленьких данных.
  • Недостаточное количество итераций (max_iter) – модель не сходится.

Дополнительные возможности и углубленные примеры

Пример 1. Функциональный API Keras для множественных входов и выходов

Создание модели, которая принимает два изображения (например, для сиамской сети) и выдаёт два предсказания. Позволяет строить сложные DAG-архитектуры.

Пример
import tensorflow as tf
from tensorflow import keras

input_a = keras.Input(shape=(224, 224, 3))
input_b = keras.Input(shape=(224, 224, 3))

shared_conv = keras.Sequential([
    keras.layers.Conv2D(32, (3,3), activation='relu'),
    keras.layers.MaxPooling2D(2,2),
    keras.layers.Flatten()
])

encoded_a = shared_conv(input_a)
encoded_b = shared_conv(input_b)

merged = keras.layers.Concatenate()([encoded_a, encoded_b])
output = keras.layers.Dense(1, activation='sigmoid')(merged)

model = keras.Model(inputs=[input_a, input_b], outputs=output)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
Model: "functional_1"
________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
================================================================================
input_1 (InputLayer)            [(None, 224, 224, 3) 0           []                               
input_2 (InputLayer)            [(None, 224, 224, 3) 0           []                               
sequential (Sequential)         (None, 32* *? )      8960        ['input_1[0][0]', 'input_2[0][0]']
concatenate (Concatenate)       (None, ?)            0           ['sequential[1][0]',              
                                                                 'sequential[1][1]']             
dense (Dense)                   (None, 1)            ?           ['concatenate[1][0]']           
================================================================================

Пример 2. Использование коллбэков для оптимизации обучения

Коллбэки ранней остановки и снижения скорости обучения при плато снижают необходимость ручной настройки.

Пример
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

early_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=1e-6)

history = model.fit(x_train, y_train,
                    batch_size=64,
                    epochs=50,
                    validation_split=0.2,
                    callbacks=[early_stop, reduce_lr])
Epoch 1/50
...
Epoch 5/50: val_loss did not improve - early stopping, restoring best weights.

Пример 3. Сохранение и загрузка модели с весами

Возможность сохранять полностью модель (архитектура + веса + оптимизатор) или только веса для последующего инференса или дообучения.

Пример
# Сохранение всей модели
model.save('my_model.keras')
# Загрузка
loaded_model = tf.keras.models.load_model('my_model.keras')

# Сохранение только весов
model.save_weights('model_weights.h5')
# Загрузка в новую модель с той же архитектурой
new_model = create_model()  # функция, возвращающая модель с той же структурой
new_model.load_weights('model_weights.h5')

Пример 4. Кастомная функция потерь и метрики в Keras

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

Пример
def weighted_binary_crossentropy(y_true, y_pred, weight=0.9):
    bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
    weight_vec = y_true * weight + (1. - y_true) * (1. - weight)
    return tf.reduce_mean(bce * weight_vec)

model.compile(optimizer='adam',
              loss=weighted_binary_crossentropy,
              metrics=[tf.keras.metrics.AUC()])

Пример 5. Сверточная нейронная сеть (CNN) для классификации CIFAR-10

Демонстрирует построение свёрточных слоёв, пулинга и пакетной нормализации.

Пример
import tensorflow as tf
from tensorflow.keras import layers, datasets

(x_train, y_train), (x_test, y_test) = datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
y_train = tf.keras.utils.to_categorical(y_train, 10)

model = tf.keras.Sequential([
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(32,32,3)),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(2,2),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train, batch_size=64, epochs=20,
                    validation_split=0.2)
Epoch 1/20
782/782 [==============================] - 15s 19ms/step - loss: 1.3521 - acc: 0.5156 - val_loss: 1.1726 - val_acc: 0.5900
...
Epoch 20/20
782/782 [==============================] - 14s 18ms/step - loss: 0.3087 - acc: 0.8882 - val_loss: 0.5689 - val_acc: 0.8160

Пример 6. Использование GPU в PyTorch: проверка и перенос модели

Для ускорения обучения требуется явно указать использование CUDA.

Пример
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('Using device:', device)

model = Net().to(device)
data, target = data.to(device), target.to(device)

with torch.cuda.device(0):
    # дополнительная настройка, если несколько GPU
    model = nn.DataParallel(model)
Using device: cuda

Пример 7. Пакетная нормализация и Dropout для устойчивости сети

Добавление этих слоёв уменьшает внутренний ковариатный сдвиг и переобучение.

Пример
model = keras.Sequential([
    layers.Dense(256, input_shape=(100,)),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    layers.Dropout(0.3),
    layers.Dense(128),
    layers.BatchNormalization(),
    layers.Activation('relu'),
    layers.Dropout(0.3),
    layers.Dense(10, activation='softmax')
])

Пример 8. Работа с несбалансированными классами: взвешивание классов

При обучении можно задать вес для каждого класса в функции потерь.

Пример
class_weights = {0: 1.0, 1: 20.0}  # класс 1 в 20 раз важнее
model.fit(x_train, y_train, class_weight=class_weights, epochs=10)

Пример 9. Трансферное обучение: дообучение предобученной модели (VGG16)

Использование модели, обученной на ImageNet, для новой задачи с малым количеством данных.

Пример
base_model = tf.keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(224,224,3))
base_model.trainable = False  # заморозка базовых слоёв

model = tf.keras.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])

Пример 10. Сохранение и загрузка PyTorch модели (только веса)

Пример
torch.save(model.state_dict(), 'model_weights.pth')
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()  # переключение в режим оценки

Нейронные сети на Python - comments

En
нейронная сеть на python (python)