Документ о свертке | Асимметричная свертка ACNet | ICCV | 2019

искусственный интеллект
  • Название статьи: «ACNet: укрепление скелетов ядра для мощной CNN с помощью асимметричных блоков свертки»
  • Ссылка на бумагу:АР Вест V.org/ABS/1908.03…
  • Аббревиатура модели: ACNet

0 Мое понимание

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

Цена этого трюка — увеличение времени и параметров тренировочной фазы,Но это не увеличивает продолжительность фазы вывода и не увеличивает параметры конечной модели..

1 объяснение тезиса

Этот метод довольно прост, вы можете использовать эту картинку, чтобы показать:

Алхимический брат поможет вам понять эту картину:

  • Изображение разделено на левую и правую части, левая — это ACNet в фазе обучения, а правая — развернутая модель, которую можно понимать как фазу тестового вывода;
  • Обычная свертка 3x3 — это на самом деле свертка в первой строке на левом рисунке.Инновация ACNet заключается вСторона свертки 3x3 параллельна свертке двух прямоугольных ядер свертки 1x3 и 3x1.. Можно понять, что в любой сверточной сети исходный сверточный слой 3x3, если использовать метод ACNet, станет параллельной структурой из трех сверточных слоев.
  • Добавляется выходная структура трех сверточных слоев, которая является выходной картой объектов этого сверточных слоев AC.
  • Почему параметр модели не увеличивается на этапе тестирования? Это не лишние два сверточных слоя, так как же не увеличить параметры? Как видно из рисунка справа, эти три ядра свертки на самом деле могут быть объединены в одно ядро ​​свертки, так что фактически accet полностью эквивалентен общей модели свертки.

Личное понимание, общая модель также можно тренировать эффект ACNet, потому что параметры двух полностью эквивалентны. Но ACNet может добиться лучших результатов, поскольку усиливает горизонтальные и вертикальные функции. А это равносильно добавлению слоя ограничения в ядро ​​свертки.Каждый параметр ядра свертки уже не одинаково важен, а центр важнее. Может также избежать переоснащения из-за добавленного предела. Это некоторые догадки и мысли, которые люди получают в результате экспериментов.

Давайте посмотрим на объяснение другой статьи, и друзья, которые понимают его, могут проверить, правильно ли они его понимают:

2 тренировочный код

Я начну с написания очень простой сети классификации с использованием общих сверток:

class Net(nn.Module):    
    def __init__(self):
        super(Net, self).__init__()
          
        self.features = nn.Sequential(
            nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
          
        self.classifier = nn.Sequential(
            nn.Dropout(p = 0.5),
            nn.Linear(64 * 7 * 7, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(inplace=True),
            nn.Dropout(p = 0.5),
            nn.Linear(512, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(inplace=True),
            nn.Dropout(p = 0.5),
            nn.Linear(512, 10),
        )                

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        
        return x

Далее я преобразую эту сеть в структуру с помощью ACNet и сначала построю acblock вместо свертки:

class ACConv2d(nn.Module):
    def __init__(self,in_channels,out_channels,kernel_size=3,stride=1,padding=1,bias=True):
        super(ACConv2d,self).__init__()
        self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size,
                             stride=stride,padding=padding,bias=True)
        self.ac1 = nn.Conv2d(in_channels,out_channels,kernel_size=(1,kernel_size),
                            stride=stride,padding=(0,padding),bias=True)
        self.ac2 = nn.Conv2d(in_channels,out_channels,kernel_size=(kernel_size,1),
                            stride=stride,padding=(padding,0),bias=True)
        
    def forward(self,x):
        ac1 = self.ac1(x)
        ac2 = self.ac2(x)
        x = self.conv(x)
        return (ac1+ac2+x)/3

Затем поставьте сетьnn.Conv2dзаменитьACConv2dПросто:

class ACNet(nn.Module):    
    def __init__(self):
        super(ACNet, self).__init__()
          
        self.features = nn.Sequential(
            ACConv2d(1, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            ACConv2d(32, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            ACConv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            ACConv2d(64, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
          
        self.classifier = nn.Sequential(
            nn.Dropout(p = 0.5),
            nn.Linear(64 * 7 * 7, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(inplace=True),
            nn.Dropout(p = 0.5),
            nn.Linear(512, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(inplace=True),
            nn.Dropout(p = 0.5),
            nn.Linear(512, 10),
        )  

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

3 Эффекты и причины

С точки зрения эффекта модель оказывает определенное влияние на ImageNet. Почему такое улучшение? Объяснение дается в статье, поскольку ядра свертки 1x3 и 3x1 устойчивы к вертикальным и горизонтальным переворотам. Посмотрите на картинку ниже:

После того, как карта признаков перевернута по вертикали, свойства ядра свертки 1x3 не действуют, но свойства ядра свертки 3x3 изменились. Точно так же ядро ​​свертки 3x1 также устойчиво к горизонтальному отражению.

этоФлип Надежностьесть объяснение, а ниже есть еще одно объяснение:

Личное понимание этой части причины связано с дифференциацией градиента.Существует только один [формула] сверточный слой, и можно увидеть один градиент.После добавления сверточных слоев 1x3 и 3x1 градиент некоторых позиций становится равным 2 и 3 также более утонченный. Кроме того, в теории бесчисленное количество сверточных слоев может быть объединено, чтобы непрерывно приближаться к пределу влияния существующей сети.Метод объединения не ограничивается добавлением (этапы обучения и вывода могут быть одинаковыми), а объединенные сверточные слои не ограничивается размером 1x3 или 3x1.

Я использовал этот метод для распознавания моего набора данных MNIST, но он не сработал, ха-ха. Я надеюсь, что мой проект может быть улучшен в будущем.Этот трюк стоит попробовать.Всем может понравиться.

4 улучшения

Наконец, если вы терпеливо прочитаете это и подумаете о предыдущем содержании, вы обнаружите, что написанная мной ac-свертка не реализует слияние ядер свертки в процессе вывода. Позже я немного усовершенствовал код при вызовеmodel.eval()После этого acconv свертка будет объединена в один сверточный слой вместо 3-х параллельных сверточных слоев:

class ACConv2d(nn.Module):
    def __init__(self,in_channels,out_channels,kernel_size=3,stride=1,padding=1,bias=False):
        super(ACConv2d,self).__init__()
        self.bias = bias
        self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size,
                             stride=stride,padding=padding,bias=bias)
        self.ac1 = nn.Conv2d(in_channels,out_channels,kernel_size=(1,kernel_size),
                            stride=stride,padding=(0,padding),bias=bias)
        self.ac2 = nn.Conv2d(in_channels,out_channels,kernel_size=(kernel_size,1),
                            stride=stride,padding=(padding,0),bias=bias)
        self.fusedconv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size,
                                 stride=stride,padding=padding,bias=bias)
    def forward(self,x):
        if self.training:
            ac1 = self.ac1(x)
            ac2 = self.ac2(x)
            x = self.conv(x)
            return (ac1+ac2+x)/3
        else:
            x = self.fusedconv(x)
            return x
        
    def train(self,mode=True):
        super().train(mode=mode)
        if mode is False:
            weight = self.conv.weight.cpu().detach().numpy()
            weight[:,:,1:2,:] = weight[:,:,1:2,:] + self.ac1.weight.cpu().detach().numpy()
            weight[:,:,:,1:2] = weight[:,:,:,1:2] + self.ac2.weight.cpu().detach().numpy()
            self.fusedconv.weight = torch.nn.Parameter(torch.FloatTensor(weight/3))
            if self.bias:
            	bias = self.conv.bias.cpu().detach().numpy()+self.conv.ac1.cpu().detach().numpy()+self.conv.ac2.cpu().detach().numpy()
                self.fusedconv.bias = torch.nn.Parameter(torch.FloatTensor(bias/3))
            if torch.cuda.is_available():
                self.fusedconv = self.fusedconv.cuda()

Спасибо за прочтение, если понравилось, можно нажать "Нравится" и "Смотреть"!Справочная статья:

  1. АР Вест V.org/PDF/1908.03…
  2. zhuanlan.zhihu.com/p/131282789
  3. blog.CSDN.net/U014380165/…
  4. АР Вест V.org/ABS/1908.03…