Введение

Сверточные нейронные сети (CNN) стали основой для обработки изображений и компьютерного зрения. Однако их обучение требует тщательной настройки архитектуры и гиперпараметров, что может быть сложной задачей, особенно при работе с большими наборами данных. В этой статье мы подробно рассмотрим несколько методов оптимизации, используемых для повышения производительности CNN на примере набора данных CIFAR-10, и покажем, как различные техники влияют на потери и точность модели. Мы протестируем аугментацию данных, различные архитектурные решения, такие как Batch Normalization и Dropout, и адаптивные подходы к обучению.

Набор данных CIFAR-10 и предобработка

CIFAR-10 - это один из самых популярных наборов данных для обучения моделей компьютерного зрения. Он содержит 60,000 изображений размером 32x32 пикселя, разделенных на 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 2x2 оказались оптимальными, обеспечив 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
Результаты по количеству уровней 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)
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
Визуализация результатов Progressive Layer Growth

Заключение

Результаты экспериментов подтверждают, что оптимизация архитектуры и гиперпараметров CNN требует комплексного подхода, включающего различные методы.

  • Наилучшие результаты были достигнуты при использовании предобработки на основе Fashion-MNIST, Batch Normalization после каждого слоя, Dropout с вероятностью 0.2 и оптимизатора Adam. Эти методы в совокупности обеспечили улучшение точности, минимизацию потерь и повышение стабильности модели.

  • Адаптивные стратегии темпа обучения и размера пакета также показали хорошие результаты, особенно Gradient Accumulation для ограниченных вычислительных ресурсов.

  • Progressive Layer Growth и Pruning оказались полезными для регулировки сложности модели, помогая избежать переобучения и улучшить обобщающую способность.

Эти методы могут быть полезны при обучении сверточных нейронных сетей для более сложных задач и наборов данных. Надеемся, что эта статья поможет вам глубже понять процесс оптимизации CNN и применять его на практике.

Спасибо за внимание!


P.S. Пишите в комментариях, какие методы вы используете для улучшения моделей, и делитесь своими результатами!

Комментарии (0)