Глубокая оптимизация сверточных нейронных сетей: Анализ методов улучшения модели на примере CIFAR-10
Введение
Сверточные нейронные сети (CNN) стали основой для обработки изображений и компьютерного зрения. Однако их обучение требует тщательной настройки архитектуры и гиперпараметров, что может быть сложной задачей, особенно при работе с большими наборами данных. В этой статье мы подробно рассмотрим несколько методов оптимизации, используемых для повышения производительности CNN на примере набора данных CIFAR-10, и покажем, как различные техники влияют на потери и точность модели. Мы протестируем аугментацию данных, различные архитектурные решения, такие как Batch Normalization и Dropout, и адаптивные подходы к обучению.
Набор данных CIFAR-10 и предобработка
CIFAR-10 — это один из самых популярных наборов данных для обучения моделей компьютерного зрения. Он содержит 60,000 изображений размером 32×32 пикселя, разделенных на 10 классов, что делает его отличным кандидатом для тестирования эффективности различных методов оптимизации.
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# Определение преобразований для набора данных CIFAR-10
transform_cifar = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# Загрузка набора данных
cifar10_train = datasets.CIFAR10(root='data', train=True, transform=transform_cifar, download=True)
cifar10_loader_train = DataLoader(cifar10_train, batch_size=32, shuffle=True)
Для эксперимента мы применили несколько различных преобразований, включая аугментацию с предобработкой на основе наборов данных Fashion-MNIST и SVHN.
# Загрузка каждого набора данных с предобработкой
cifar10_loader = DataLoader(cifar10_train, batch_size=32, shuffle=True)
fmnist_loader = DataLoader(fmnist_train, batch_size=32, shuffle=True)
svhn_loader = DataLoader(svhn_train, batch_size=32, shuffle=True)
# Добавляем трансформацию для повторения канала в Fashion-MNIST
transform_fmnist_rgb = transforms.Compose([
transform_fmnist, # Текущие трансформации для FMNIST
Lambda(lambda x: x.repeat(3, 1, 1)) # Повторяем канал 3 раза
])
# Загрузка данных Fashion-MNIST с трансформацией повторения каналов
fmnist_cifar_transform = DataLoader(
datasets.FashionMNIST(root='data', train=True, transform=transform_fmnist_rgb, download=True),
batch_size=32, shuffle=True
)
svhn_loader = DataLoader(svhn_train, batch_size=32, shuffle=True)
# Загрузка CIFAR-10 с альтернативной предобработкой
cifar10_fmnist_transform = DataLoader(datasets.CIFAR10(root='data', train=True, transform=transform_fmnist, download=True), batch_size=32, shuffle=True)
cifar10_svhn_transform = DataLoader(datasets.CIFAR10(root='data', train=True, transform=transform_svhn, download=True), batch_size=32, shuffle=True)
# Загрузка Fashion-MNIST с альтернативной предобработкой
#fmnist_cifar_transform = DataLoader(datasets.FashionMNIST(root='data', train=True, transform=transform_cifar, download=True), batch_size=32, shuffle=True)
fmnist_svhn_transform = DataLoader(datasets.FashionMNIST(root='data', train=True, transform=transform_svhn, download=True), batch_size=32, shuffle=True)
# Загрузка SVHN с альтернативной предобработкой
svhn_cifar_transform = DataLoader(datasets.SVHN(root='data', split='train', transform=transform_cifar, download=True), batch_size=32, shuffle=True)
svhn_fmnist_transform = DataLoader(datasets.SVHN(root='data', split='train', transform=transform_fmnist, download=True), batch_size=32, shuffle=True)
Эксперимент 1: Влияние предобработки данных
Различные методы предобработки могут влиять на то, какие особенности изображений извлекает модель. Мы протестировали несколько вариантов, включая стандартную предобработку CIFAR-10, а также трансформации, адаптированные из других наборов данных:
Оригинальная предобработка CIFAR-10
Предобработка, основанная на Fashion-MNIST
Предобработка на основе SVHN
Обучение CIFAR-10 с собственной предобработкой: | ||
Эпоха 1 | Потери: 1.6969 | Точность: 37.90% |
… | … | … |
Эпоха 5 | Потери: 1.1355 | Точность: 59.95% |
Обучение CIFAR-10 с предобработкой Fashion-MNIST | ||
Эпоха 1 | Потери: 1.4984 | Точность: 46.54% |
… | … | … |
Эпоха 5 | Потери: 0.9440 | Точность: 66.95% |
Обучение CIFAR-10 с предобработкой SVHN | ||
Эпоха 1 | Потери: 1.6444 | Точность: 39.83% |
… | … | … |
Эпоха 5 | Потери: 1.0892 | Точность: 61.49% |
Результаты: Предобработка, основанная на Fashion-MNIST, продемонстрировала лучшие результаты, обеспечив потерю в 0.9440 и точность 66.95%. Это позволяет предположить, что некоторые преобразования могут быть лучше адаптированы для задач классификации изображений, несмотря на различия в данных.
Результаты для различных видов предобработки данных
Эксперимент 2: Оптимизация архитектуры — выбор фильтров и пуллинга
Следующим шагом было изучение того, как количество фильтров и метод пуллинга влияет на результаты. Мы тестировали три варианта с разным количеством фильтров (16, 32 и 64), а также несколько типов пуллинга.
Пример конфигурации фильтров:
import torch.nn as nn
class CNNVariant1(nn.Module):
def __init__(self):
super(CNNVariant1, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(16 * 16 * 16, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = x.view(-1, 16 * 16 * 16)
x = self.fc1(x)
return x
Результаты: Среднее количество фильтров (32 фильтра) и Max Pooling 2×2 оказались оптимальными, обеспечив 57.33% точности и потерю в 1.2018. Использование большого количества фильтров привело к переобучению, а использование Average Pooling показало менее хорошие результаты.
Результаты по различным количествам фильтров
Эксперимент 3: Batch Normalization и Dropout
Batch Normalization и Dropout — это техники, которые помогают улучшить стабильность и обобщающую способность модели. Мы тестировали Batch Normalization после каждого сверточного слоя и только в начале и в конце, а также различные значения Dropout.
Пример использования Batch Normalization и Dropout:
class CNNBatchNorm(nn.Module):
def __init__(self):
super(CNNBatchNorm, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.dropout = nn.Dropout(0.2)
self.fc1 = nn.Linear(32 * 16 * 16, 10)
def forward(self, x):
x = self.dropout(self.bn1(torch.relu(self.conv1(x))))
x = x.view(-1, 32 * 16 * 16)
x = self.fc1(x)
return x
Результаты: Batch Normalization после каждого слоя обеспечил лучшую стабильность обучения и точность 60.25%. Умеренный Dropout (0.2) оказался наиболее эффективным и достиг 63.79% точности, тогда как сильный Dropout (0.5) привел к ухудшению результатов.
Результаты по количеству уровней Dropout
Эксперимент 4: Адаптивные стратегии темпа обучения и динамический размер пакета
Использование адаптивного темпа обучения и динамического изменения размера пакета — это современные подходы, которые помогают улучшить обучение модели.
Cyclic Learning Rate:
from torch.optim.lr_scheduler import CyclicLR
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
scheduler = CyclicLR(optimizer, base_lr=0.001, max_lr=0.01, step_size_up=5)
Результаты: Оптимизатор Adam показал лучшие результаты по сравнению с Cyclic Learning Rate, с потерей 1.2331 и точностью 57.42%. Dynamic Batch Size и Gradient Accumulation продемонстрировали схожие результаты, но Gradient Accumulation позволил несколько улучшить точность при ограниченных вычислительных ресурсах.
Результаты для адаптивных стратегий оптимизации архитектуры
ROC-AUC для модели с адаптивным темпом (Adam)
Эксперимент 5: Progressive Layer Growth и Pruning
Мы протестировали Progressive Layer Growth и Random Filter Pruning для регулировки сложности модели.
Пример Progressive Layer Growth:
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.fc1 = nn.Linear(16 * 16 * 16, 10)
# После нескольких эпох добавляем слои
class ExtendedCNN(SimpleCNN):
def __init__(self):
super(ExtendedCNN, self).__init__()
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc2 = nn.Linear(32 * 8 * 8, 10)
Результаты: Progressive Layer Growth улучшил точность модели до 60.87%, а Random Filter Pruning обеспечил точность в 64.80%, что доказывает его эффективность для предотвращения переобучения.
Визуализация результатов Progressive Layer Growth
Заключение
Результаты экспериментов подтверждают, что оптимизация архитектуры и гиперпараметров CNN требует комплексного подхода, включающего различные методы.
Наилучшие результаты были достигнуты при использовании предобработки на основе Fashion-MNIST, Batch Normalization после каждого слоя, Dropout с вероятностью 0.2 и оптимизатора Adam. Эти методы в совокупности обеспечили улучшение точности, минимизацию потерь и повышение стабильности модели.
Адаптивные стратегии темпа обучения и размера пакета также показали хорошие результаты, особенно Gradient Accumulation для ограниченных вычислительных ресурсов.
Progressive Layer Growth и Pruning оказались полезными для регулировки сложности модели, помогая избежать переобучения и улучшить обобщающую способность.
Эти методы могут быть полезны при обучении сверточных нейронных сетей для более сложных задач и наборов данных. Надеемся, что эта статья поможет вам глубже понять процесс оптимизации CNN и применять его на практике.
Спасибо за внимание!
P.S. Пишите в комментариях, какие методы вы используете для улучшения моделей, и делитесь своими результатами!