Нейронные сети на языке 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() # переключение в режим оценки