Подробная анатомия CNN с использованием Pytorch

глубокое обучение Python PyTorch

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

инструмент

  • Библиотеки глубокого обучения с открытым исходным кодом:PyTorch
  • набор данных:MNIST

выполнить

первоначальные требования

Сначала создайте базовую сеть BASE со следующим кодом в Pytorch:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1), padding=0)
        self.conv2 = nn.Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1), padding=0)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x):
        x = F.max_pool2d(self.conv1(x), 2)
        x = F.max_pool2d(self.conv2(x), 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x)

Смотрите эту часть кодаbase.py.

Проблема A: предварительная обработка

То есть набор данных MNIST требуется прочитать по правилам и преобразовать в формат, пригодный для обработки. Код, прочитанный здесь, следует методу чтения BigDL Python Support.Излишне говорить, что его можно быстро прочитать в соответствии с форматом данных на домашней странице MNIST.Ключевой блок имеет функцию для чтения 32 бит:

def _read32(bytestream):
    dt = numpy.dtype(numpy.uint32).newbyteorder('>')    # 大端模式读取,最高字节在前(MSB first)
    return numpy.frombuffer(bytestream.read(4), dtype=dt)[0]

После прочтения это тензор (N, 1, 28, 28), каждый пиксель имеет значение 0-255, сначала его нормализуем, делим все значения на 255, получаем значение 0-1, и затем нормализовать, среднее значение и дисперсия тренировочного набора и тестового набора известны, и вы можете сделать это напрямую. Поскольку средняя дисперсия обучающего набора и тестового набора относится к нормализованным данным, нормализация вначале не выполнялась, поэтому прямой вывод и градация были очень возмутительными, и позже было обнаружено, что здесь была проблема.

Смотрите эту часть кодаpreprocessing.py.

Задача B: БАЗОВАЯ модель

Установите случайное начальное число на 0, изучите параметры первых 10 000 обучающих выборок и, наконец, посмотрите на частоту ошибок тестового набора после 20 эпох. Окончательный результат:

Test set: Average loss: 0.0014, Accuracy: 9732/10000 (97.3%)

Видно, что точность модели BASE не так высока.

Проблема C: Пакетная нормализация против BASE

Добавьте слой пакетной нормализации после сверточных слоев первых трех блоков и просто измените структуру сети следующим образом:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1), padding=0)
        self.conv2 = nn.Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1), padding=0)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)
        self.bn1 = nn.BatchNorm2d(20)
        self.bn2 = nn.BatchNorm2d(50)
        self.bn3 = nn.BatchNorm1d(500)

    def forward(self, x):
        x = self.conv1(x)
        x = F.max_pool2d(self.bn1(x), 2)
        x = self.conv2(x)
        x = F.max_pool2d(self.bn2(x), 2)
        x = x.view(-1, 4*4*50)
        x = self.fc1(x)
        x = F.relu(self.bn3(x))
        x = self.fc2(x)
        return F.log_softmax(x)

Запускаем те же параметры и получаем результат добавления BN:

Test set: Average loss: 0.0009, Accuracy: 9817/10000 (98.2%)

Видно, что эффект значительно улучшился.
См. [2], [5] для получения дополнительной информации о пакетной нормализации.

Проблема D: Выпадающий слой

в последнем слоеfc2добавить слой послеDropout(p=0.5)После этого результаты по BASE и BN такие:

BASE:Test set: Average loss: 0.0011, Accuracy: 9769/10000 (97.7%)
BN:  Test set: Average loss: 0.0014, Accuracy: 9789/10000 (97.9%)

Замечено, что отсев может в определенной степени улучшить модель BASE, но влияние на модель BN не очевидно, а уменьшено.
Причина может заключаться в том, что сама модель BN содержит эффект регуляризации, и добавление слоя Dropout кажется ненужным и может повлиять на результаты.

Вопрос E: Модель SK

SK model: Stacking two 3x3 conv. layers to replace 5x5 conv. layer


После таких изменений построенная модель СК выглядит следующим образом:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1_1 = nn.Conv2d(1, 20, kernel_size=(3, 3), stride=(1, 1), padding=0)
        self.conv1_2 = nn.Conv2d(20, 20, kernel_size=(3, 3), stride=(1, 1), padding=0)
        self.conv2 = nn.Conv2d(20, 50, kernel_size=(3, 3), stride=(1, 1), padding=0)
        self.fc1 = nn.Linear(5*5*50, 500)
        self.fc2 = nn.Linear(500, 10)
        self.bn1_1 = nn.BatchNorm2d(20)
        self.bn1_2 = nn.BatchNorm2d(20)
        self.bn2 = nn.BatchNorm2d(50)
        self.bn3 = nn.BatchNorm1d(500)
        self.drop = nn.Dropout(p=0.5)

    def forward(self, x):
        x = F.relu(self.bn1_1(self.conv1_1(x)))
        x = F.relu(self.bn1_2(self.conv1_2(x)))
        x = F.max_pool2d(x, 2)
        x = self.conv2(x)
        x = F.max_pool2d(self.bn2(x), 2)
        x = x.view(-1, 5*5*50)
        x = self.fc1(x)
        x = F.relu(self.bn3(x))
        x = self.fc2(x)
        return F.log_softmax(x)

После 20 эпох результат выглядит следующим образом:

SK: Test set: Average loss: 0.0008, Accuracy: 9848/10000 (98.5%)

Точность набора тестов несколько улучшена.
Здесь два ядра свертки 3x3 используются для замены большого ядра свертки 5x5, а количество параметров изменено с 5x5=25 на 2x3x3=18. Практика показала, что это ускоряет вычисления, а также помогают ReLU между небольшими свёрточными слоями.
Этот метод используется в VGG.

Вопрос F: Изменить количество каналов

Умножая размер карты признаков на кратное, а затем выполняя ее через программу-оболочку, получаются следующие результаты:

SK0.2:  97.7%
SK0.5:  98.2%
SK1:    98.5%
SK1.5:  98.6%
SK2:    98.5%  (max 98.7%)

Когда карты признаков равны 4, 10, 30 и 40, конечная точность в основном повышается. В определенной степени это показывает, что до достижения переобучения увеличение количества карт признаков эквивалентно извлечению большего количества признаков, а увеличение количества извлеченных признаков помогает повысить точность.
Смотрите эту часть кодаSK_s.pyиrunSK.sh.

Вопрос G: Используйте разные размеры тренировочных наборов

Также запускаем скрипт, добавляя параметры

parser.add_argument('--usedatasize', type=int, default=60000, metavar='SZ',
                    help='use how many training data to train network')

Указывает используемый размер данных, взятый спереди назадusebatchsizeданные.
Для этой части процедуры см.SK_s.pyиrunTrainingSize.sh.
Результат запуска следующий:

500:   84.2%
1000:  92.0%
2000:  94.3%
5000:  95.5%
10000: 96.6%
20000: 98.4%
60000: 99.1%

Отсюда видно, что чем больше данных, тем выше точность результатов.
Слишком мало данных не может точно отражать общее распределение данных, и их легко переобучить. Если данных в определенной степени слишком много, эффект не будет очевиден. Однако в большинстве случаев мы по-прежнему думаем, что есть слишком мало данных, и можно получить больше данных.Есть также некоторые трудности.

Вопрос H: Используйте разные тренировочные наборы

Используйте скрипт для завершения, эту часть программы см.SK_0.2.pyиdiffTrainingSets.sh.
Результаты приведены ниже:

    0-10000: 98.0%
10000-20000: 97.8%
20000-30000: 97.8%
30000-40000: 97.4%
40000-50000: 97.5%
50000-60000: 97.7%

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

Проблема I: эффекты Random Seed

использоватьrunSeed.shСценарий завершен, и все 60 000 обучающих наборов использованы.
Результат запуска следующий:

Seed      0:  98.9%
Seed      1:  99.0%
Seed     12:  99.1%
Seed    123:  99.0%
Seed   1234:  99.1%
Seed  12345:  99.0%
Seed 123456:  98.9%

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

Вопрос J: ReLU или сигмоид?

После замены всех ReLU на Sigmoid используйте для обучения все 60 000 обучающих наборов, и результаты сравнения будут следующими:

   ReLU SK_0.2:  99.0%
Sigmoid SK_0.2:  98.6%

Видно, что при обучении CNN лучше использовать блок активации ReLU, чем блок активации Sigmoid. Причиной может быть различие между двумя механизмами.Когда входное значение нейрона велико или мало, выходное значение сигмоиды будет близко к 0 или 1, что делает градиент во многих местах почти 0, а вес может вряд ли обновится. Хотя ReLU увеличивает вычислительную нагрузку, он может значительно ускорить процесс сходимости, и проблема насыщения градиента отсутствует.

Суммировать

В этой статье PyTorch используется для сравнения нескольких моделей CNN в наборе данных MNIST и влияния некоторых настроек параметров на производительность модели, что приводит к некоторым подробным оценкам многих аспектов CNN.
После его использования я чувствую, что PyTorch по-прежнему очень прост в использовании.Он относительно прост.Я не знаю о других моделях.Во всяком случае, модель сверточной нейронной сети такая.
Конкретный код проекта можно найти в [7].
Поскольку у автора нет глубоких исследований CNN (Convolutional Neural Network), интерпретация каждого результата может быть предвзятой, и читатели могут критически его прочитать.

References