[OUC Deep Learning Introduction] Semana 3 Registro de aprendizado: ResNet+ResNeXt

Parte 1 ResNet

1 leitura de papel

Sabe-se que quanto mais profundo o número de camadas de rede, mais difícil é treinar. Para resolver este problema, este trabalho propõe um módulo de aprendizagem residual. Este módulo estrutural é fácil de otimizar e pode atingir alta precisão quando a profundidade é muito profundo.

O treinamento de redes profundas enfrenta principalmente dois problemas, um é o desaparecimento/explosão de gradiente e o outro é a degradação da rede, o primeiro pode ser resolvido normalizando operações, ajustando otimizadores e taxas de aprendizado, enquanto o segundo torna a rede mais profunda em camadas. O efeito do treinamento atinge a saturação quando , e essa degradação da rede não é causada por overfitting.

Portanto, a partir da transformação de identidade, este artigo propõe um módulo de aprendizado residual para resolver o problema de degradação da rede sob essa rede profunda, de modo que os módulos estruturais na rede profunda possam não apenas realizar transformações não lineares para obter melhores resultados de treinamento, mas também executar uma transformação de identidade para manter o efeito de treinamento atual bom o suficiente.

ResNet tem principalmente as seguintes características:

  1. representação residual
  2. Conexão de atalho (realizar transformação de identidade)

2 Estrutura da rede

Destaques da rede :

  • Estrutura de rede profunda (avanço 1000)
  • módulo residual
  • Descarte o Dropout e use a Normalização em Lote para acelerar o treinamento

 Problemas enfrentados pelas estruturas tradicionais de redes profundas :

  • Gradientes desaparecendo ou explodindo
  • problema de degradação

estrutura residual :

A estrutura residual à esquerda é usada para a rede mais rasa (ResNet34) e o BottleNeck à direita é usado para ResNet50/101/152. A forma da matriz de recursos de saída da ramificação principal e do atalho deve ser a mesma.

O kernel de convolução 1*1 no BottleNeck é usado para reduzir a dimensão, primeiro reduzir a dimensão e depois aumentar a dimensão, o que é benéfico para reduzir a quantidade de parâmetros.

O atalho pontilhado no diagrama esquemático da estrutura ResNet indica que a estrutura residual aqui tem aprimoramento de dimensão e um kernel de convolução 1*1 para aprimoramento de dimensão é adicionado ao atalho correspondente.

O ResNet do artigo original é diferente do ResNet oficial implementado pelo PyTorch : no ramo principal da estrutura residual pontilhada no artigo original, o tamanho do passo da convolução 1*1 é 2 e o tamanho do passo da convolução 3*3 é 1; enquanto o PyTorch oficial Na implementação, o tamanho do passo da convolução 1*1 é 1, e o tamanho do passo da convolução 3*3 é 2, o que melhora ligeiramente a taxa de precisão.

Normalização de lote (BN) : ajuste o mapa de recursos em um lote para atender à lei de distribuição com um valor médio de 0 e uma variação de 1. Tanto o valor médio quanto a variação são vetores, e as dimensões e profundidades correspondem às estatísticas no avanço propagação. γ e β são treinados ao contrário durante a propagação. ( Explicação detalhada da normalização em lote e experimento pytorch_Sunflower's Mung Bean Blog-CSDN Blog_batchnormalization pytorch )

 

 Ao usar o BN, preste atenção a :

  • training=True durante o treinamento, training=False durante a verificação, que pode ser controlado pelos métodos model.train() e model.eval() de criação de modelos em pytorch
  • Quanto maior o tamanho do lote for definido, mais próxima a média e a variância estarão da média e da variância de todo o conjunto de treinamento
  • Recomenda-se colocar a camada bn entre a camada convolucional (Conv) e a camada de ativação (como Relu), e a camada convolucional não deve usar viés

A vantagem do aprendizado por transferência : ele pode treinar rapidamente melhores resultados, mesmo que o conjunto de dados seja pequeno, pode alcançar o efeito desejado (preste atenção ao método de processamento pré-treinamento)

Métodos comuns de aprendizado de transferência : treine todos os parâmetros após carregar os pesos; treine apenas as últimas camadas de parâmetros após carregar os pesos; adicione camadas totalmente conectadas após carregar os pesos

3 Crie ResNet com base no PyTorch

Link do código: (colab) Crie ResNet com base no PyTorch

from torch.nn.modules.batchnorm import BatchNorm2d
import torch
import torch.nn as nn


# 浅层ResNet的残差结构
class BasicBlock(nn.Module):
  expansion = 1 # 主分支中卷积核个数是否发生变化
  def __init__(self,in_channel,out_channel,stride=1,downsample=None):
    super(BasicBlock,self).__init__()
    self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,
                kernel_size=3,stride=stride,padding=1,bias=False)
    self.bn1 = nn.BatchNorm2d(out_channel)
    self.relu = nn.ReLU()
    self.conv2 = nn.Conv2d(in_channels=out_channel,out_channels=out_channel,
                kernel_size=3,stride=1,padding=1,bias=False)
    self.bn2 = nn.BatchNorm2d(out_channel)
    self.downsample = downsample

  def forward(self,x):
    identity = x
    # 如果传入了下采样,就是虚线的残差块
    if self.downsample is not None:
      identity = self.downsample(x)

    out = self.conv1(x)
    out = self.bn1(out)
    out = self.relu(out)

    out = self.conv2(out)
    out = self.bn2(out)

    out += identity
    out = self.relu(out)

    return out


# 深层ResNet的残差结构
class Bottleneck(nn.Moudule):
  expansion = 4
  def __init__(self,in_channel,out_channel,stride=1,downsample=None):
    super(Bottleneck,self).__init__()
    # 降维
    self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,
                kernel_size=1,stride=1,bias=False)
    self.bn1 = nn.BatchNorm2d(out_channel)

    self.conv2 = nn.Conv2d(in_channels=out_channel,out_channels=out_channel,
                kernel_size=3,stride=stride,padding=1,bias=False)
    self.bn2 = nn.BatchNorm2d(out_channel)

    # 升维
    self.conv3 = nn.Conv2d(in_channels=out_channel,out_channels=out_channel*self.expansion,
                kernel_size=1,stride=1,bias=False)
    self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)

    self.relu = nn.ReLU(inplace=True)
    self.downsample = downsample

  def forward(self,x):
    identity = x
    # 如果传入了下采样,就是虚线的残差块
    if self.downsample is not None:
      identity = self.downsample(x)

    out = self.conv1(x)
    out = self.bn1(out)
    out = self.relu(out)

    out = self.conv2(out)
    out = self.bn2(out)
    out = self.relu(out)

    out = self.conv3(out)
    out = self.bn3(out)

    out += identity
    out = self.relu(out)

    return out



# ResNet
class ResNet(nn.Module):
  def __init__(self,block,blocks_num,num_classes=1000,include_top=True):
    super(ResNet,self).__init__()
    self.include_top = include_top
    self.in_channel = 64

    self.conv1 = nn.Conv2d(3,self.in_channel,kernel_size=7,stride=2,padding=3,bias=False)
    self.bn1 = nn.BatchNorm2d(self.in_channel)
    self.relu = nn.ReLU(inplace=True)
    self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)

    self.layer1 = self._make_layer(block,64,blocks_num[0])
    self.layer2 = self._make_layer(block,128,blocks_num[1],stride=2)
    self.layer3 = self._make_layer(block,256,blocks_num[2],stride=2)
    self.layer4 = self._make_layer(block,512,blocks_num[3],stride=2)

    # 如果包含全连接层
    if self.include_top:
      self.avgpool = nn.AdaptiveAvgPool2d((1,1))
      self.fc = nn.Linear(512*block.expansion,num_classes)

    for m in self.modules():
      if isinstance(m,nn.Conv2d):
        nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')

  def _make_layer(self,block,channel,block_num,stride=1):
    downsample = None
    if stride!=1 or self.in_channel!=channel*block.expansion:
      downsample = nn.Sequential(
          nn.Conv2d(self.in_channel,channel*block.expansion,kernel_size=1,stride=stride,bias=False),
          nn.BatchNorm2d(channel*block.expansion)
      )
      layers = []
      layers.append(block(self.in_channel,channel,downsample=downsample,stride=stride))
      self.in_channel = channel*block.expansion

    for _ in range(1,block_num):
      layers.append(block(self.in_channel,channel))

    return nn.Sequential(*layers) # 非关键字参数

  def forward(self,x):
    x = self.conv1(x)
    x = self.bn1(x)
    x = self.relu(x)
    x = self.maxpool(x)

    x = self.layer1(x)
    x = self.layer2(x)
    x = self.layer3(x)
    x = self.layer4(x)

    if self.include_top:
      x = self.avgpool(x)
      x = torch.flatten(x,1)
      x = self.fc(x)

    return x


def resnet18(num_classes=1000,include_top=True):
  return ResNet(BasicBlock,[2,2,2,2],num_classes=num_classes,include_top=include_top)

def resnet34(num_classes=1000,include_top=True):
  return ResNet(BasicBlock,[3,4,6,3],num_classes=num_classes,include_top=include_top)

def resnet50(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,4,6,3],num_classes=num_classes,include_top=include_top)

def resnet101(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,4,23,3],num_classes=num_classes,include_top=include_top)

def resnet152(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,8,36,3],num_classes=num_classes,include_top=include_top)

Part2 ResNeXt

1 leitura de papel

Com base no ResNet, o artigo propôs o conceito de "cardinalidade (o tamanho do conjunto de conversão)" e apontou que "cardinalidade" é outro fator importante que afeta o efeito de treinamento além da profundidade e largura da rede e aumenta a "cardinalidade " isso A operação é mais eficiente do que aumentar a profundidade e a largura da rede, e aumentar o tamanho dos hiperparâmetros nem sempre melhora o efeito do treinamento; portanto, aumentar o tamanho dos hiperparâmetros aumentará a dificuldade e a incerteza do treinamento. Em contraste , O design de estrutura de rede inteligente pode alcançar melhores resultados do que simplesmente aumentar a profundidade da rede existente.

A partir dessa perspectiva, o artigo melhora a estrutura de rede do ResNet, empilhando várias convoluções da mesma estrutura como um bloco residual, e acredita que essa estrutura pode evitar problemas de overfitting. A entrada do bloco residual será dividida em várias partes de tamanho igual e, em seguida, a mesma operação de convolução será realizada em cada parte e os resultados serão integrados para formar a saída. Comparado com a estrutura Inception, este módulo de estrutura é mais conciso e fácil de implementar.

Após experimentos, descobriu-se que o ResNeXt de 101 camadas é mais preciso que o ResNet de 200 camadas, e a complexidade é apenas metade deste último. As principais características do ResNeXt são as seguintes:

  • Rede convolucional multi-ramificação
  • convolução de grupo
  • Redes Convolucionais Comprimidas
  • transformação aditiva

A construção da estrutura de bloco residual do ResNeXt precisa seguir as duas diretrizes a seguir:

  1. Mapas espaciais do mesmo tamanho compartilham hiperparâmetros
  2. Cada vez que o mapa espacial é reduzido por um fator de 2, a largura do bloco também é multiplicada por um fator de 2

2 Estrutura da rede

Comparado com o ResNet, o ResNeXt melhora a estrutura do bloco com base no ResNet, e o backbone é alterado para a convolução do grupo. Sob o mesmo valor de cálculo, o ResNeXt tem uma taxa de erro menor

 A convolução de grupo tem menos parâmetros do que a convolução tradicional. Quando a dimensão de saída é a mesma que a dimensão de entrada, é equivalente a atribuir um kernel de convolução com um canal de 1 para cada canal da matriz de recursos de entrada para convolução.

As três formas a seguir são completamente equivalentes em computação.

 ResNet50 e ResNeXt50:

Somente quando o número de camadas de blocos é maior ou igual a 3, um bloco de convolução de grupo significativo pode ser construído, portanto, essa melhoria tem pouco efeito no ResNet raso

3 Crie ResNet com base no PyTorch

 Link do código: (colab) Build ResNeXt baseado em PyTorch

# 基于PyTorch搭建ResNeXt

from torch.nn.modules.batchnorm import BatchNorm2d
import torch
import torch.nn as nn


# 浅层的残差结构(不变)
class BasicBlock(nn.Module):
  expansion = 1 # 主分支中卷积核个数是否发生变化
  def __init__(self,in_channel,out_channel,stride=1,downsample=None):
    super(BasicBlock,self).__init__()
    self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,
                kernel_size=3,stride=stride,padding=1,bias=False)
    self.bn1 = nn.BatchNorm2d(out_channel)
    self.relu = nn.ReLU()
    self.conv2 = nn.Conv2d(in_channels=out_channel,out_channels=out_channel,
                kernel_size=3,stride=1,padding=1,bias=False)
    self.bn2 = nn.BatchNorm2d(out_channel)
    self.downsample = downsample

  def forward(self,x):
    identity = x
    # 如果传入了下采样,就是虚线的残差块
    if self.downsample is not None:
      identity = self.downsample(x)

    out = self.conv1(x)
    out = self.bn1(out)
    out = self.relu(out)

    out = self.conv2(out)
    out = self.bn2(out)

    out += identity
    out = self.relu(out)

    return out


# 深层的残差结构
class Bottleneck(nn.Moudule):
  expansion = 4
  def __init__(self,in_channel,out_channel,stride=1,downsample=None,groups=1,width_per_group=64):
    super(Bottleneck,self).__init__()

    width = int(out_channel*(width_per_group/64.))*groups

    # 降维
    self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=width,
                kernel_size=1,stride=1,bias=False)
    self.bn1 = nn.BatchNorm2d(out_channel)

    self.conv2 = nn.Conv2d(in_channels=width,out_channels=width,groups=groups,
                kernel_size=3,stride=stride,padding=1,bias=False)
    self.bn2 = nn.BatchNorm2d(width)

    # 升维
    self.conv3 = nn.Conv2d(in_channels=width,out_channels=out_channel*self.expansion,
                kernel_size=1,stride=1,bias=False)
    self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)

    self.relu = nn.ReLU(inplace=True)
    self.downsample = downsample

  def forward(self,x):
    identity = x
    # 如果传入了下采样,就是虚线的残差块
    if self.downsample is not None:
      identity = self.downsample(x)

    out = self.conv1(x)
    out = self.bn1(out)
    out = self.relu(out)

    out = self.conv2(out)
    out = self.bn2(out)
    out = self.relu(out)

    out = self.conv3(out)
    out = self.bn3(out)

    out += identity
    out = self.relu(out)

    return out



# ResNet
class ResNeXt(nn.Module):
  def __init__(self,block,blocks_num,num_classes=1000,include_top=True):
    super(ResNeXt,self).__init__()
    self.include_top = include_top
    self.in_channel = 64

    self.conv1 = nn.Conv2d(3,self.in_channel,kernel_size=7,stride=2,padding=3,bias=False)
    self.bn1 = nn.BatchNorm2d(self.in_channel)
    self.relu = nn.ReLU(inplace=True)
    self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)

    self.layer1 = self._make_layer(block,64,blocks_num[0])
    self.layer2 = self._make_layer(block,128,blocks_num[1],stride=2)
    self.layer3 = self._make_layer(block,256,blocks_num[2],stride=2)
    self.layer4 = self._make_layer(block,512,blocks_num[3],stride=2)

    # 如果包含全连接层
    if self.include_top:
      self.avgpool = nn.AdaptiveAvgPool2d((1,1))
      self.fc = nn.Linear(512*block.expansion,num_classes)

    for m in self.modules():
      if isinstance(m,nn.Conv2d):
        nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')

  def _make_layer(self,block,channel,block_num,stride=1):
    downsample = None
    if stride!=1 or self.in_channel!=channel*block.expansion:
      downsample = nn.Sequential(
          nn.Conv2d(self.in_channel,channel*block.expansion,kernel_size=1,stride=stride,bias=False),
          nn.BatchNorm2d(channel*block.expansion)
      )
      layers = []
      layers.append(block(self.in_channel,channel,downsample=downsample,
                stride=stride,groups=self.groups,width_per_group=self.width_per_group))
      self.in_channel = channel*block.expansion

    for _ in range(1,block_num):
      layers.append(block(self.in_channel,channel,groups=self.groups,
                width_per_group=self.width_per_group))

    return nn.Sequential(*layers) # 非关键字参数

  def forward(self,x):
    x = self.conv1(x)
    x = self.bn1(x)
    x = self.relu(x)
    x = self.maxpool(x)

    x = self.layer1(x)
    x = self.layer2(x)
    x = self.layer3(x)
    x = self.layer4(x)

    if self.include_top:
      x = self.avgpool(x)
      x = torch.flatten(x,1)
      x = self.fc(x)

    return x


def resnet18(num_classes=1000,include_top=True):
  return ResNet(BasicBlock,[2,2,2,2],num_classes=num_classes,include_top=include_top)

def resnet34(num_classes=1000,include_top=True):
  return ResNet(BasicBlock,[3,4,6,3],num_classes=num_classes,include_top=include_top)

def resnet50(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,4,6,3],num_classes=num_classes,include_top=include_top)

def resnet101(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,4,23,3],num_classes=num_classes,include_top=include_top)

def resnet152(num_classes=1000,include_top=True):
  return ResNet(Bottleneck,[3,8,36,3],num_classes=num_classes,include_top=include_top)


def resnext50_32_4d(num_classes=1000,include_top=True):
  groups = 32
  width_per_group = 4
  return ResNeXt(Bottleneck,[3,4,6,3],num_classes=num_classes,include_top=include_top,
                groups=groups,width_per_group=width_per_group)
  
def resnext101_32_8d(num_classes=1000,include_top=True):
  groups = 32
  width_per_group = 8
  return ResNeXt(Bottleneck,[3,4,23,3],num_classes=num_classes,include_top=include_top,
                groups=groups,width_per_group=width_per_group)

Exercício de código da parte 3: guerra de cães e gatos

Link do código: (colab) guerra de gato e cachorro

carregador de dados:

data_dir = '/content/drive/MyDrive/Colab Notebooks/cat_dog/'
train_dir = data_dir+'train/'
test_dir = data_dir+'test/'
val_dir = data_dir+'val/'

train_imgs = os.listdir(train_dir)
train_labels = []


normalize = transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])

transform = transforms.Compose([transforms.Resize([32,32]),transforms.ToTensor(),normalize])

class CatDogDataset(Dataset):
  def __init__(self, root, transform=None):
    self.root = root
    self.transform = transform
    # 正样本
    self.imgs = os.listdir(self.root)
    self.labels = []
    
    for img in self.imgs:
      if img.split('_')[0]=='cat':
        self.labels.append(0)
      if img.split('_')[0]=='dog':
        self.labels.append(1)

  def __len__(self):
    return len(self.imgs)

  def __getitem__(self, index):
    label = self.labels[index]
    img_dir = self.root + str(self.imgs[index])
    img = Image.open(img_dir)

    # transform?
    if self.transform is not None:
      img = self.transform(img)

    return img,torch.from_numpy(np.array(label))  # 返回数据+标签

LeNet5:

class LeNet5(nn.Module):
    def __init__(self): 
        super(LeNet5, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
                            
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = x.view(-1, 32*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

Parâmetros relacionados:

Treinamento (ResNet34 à esquerda, LeNet5 à direita):

 

 

Gerar arquivo csv de resultado:

Resultados de pontuação: 

 

Pode-se ver que ResNet34 é melhor que LeNet5 ao usar os mesmos parâmetros de otimização e número de rodadas de treinamento.

Parte 4 perguntas para pensar

1. Aprendizagem residual

Suponha que o mapeamento básico seja h(x), e os mapeamentos das duas ramificações do bloco residual sejam f(x) e x respectivamente, então h(x)=x+f(x), f(x) é o principal ramo a ser aprendido Mapeamento, esta estrutura vai ajudar a realização e pré-processamento de mapeamento de identidade e aliviar o problema de degradação da rede. Ao derivar o gradiente, há h'(x)=1+f'(x), o que também pode aliviar o problema do desaparecimento do gradiente.

2. O princípio da normalização em lote

A rede neural convolucional contém muitas camadas ocultas, e os parâmetros de cada camada mudarão com o treinamento. A distribuição de entrada da camada oculta sempre mudará, o que reduzirá a velocidade de aprendizado e o gradiente da função de ativação será saturado. A normalização é uma ideia para resolver este problema, mas se o uso da normalização for muito, o cálculo do processo de treinamento será muito complicado, e se for muito pequeno, não será eficaz. Portanto, o BN é proposto. BN primeiro divide os dados em vários lotes. Em seguida, executa uma operação de normalização em cada lote.

3. Por que a convolução de grupo pode melhorar a precisão? Agora que a convolução de grupo pode melhorar a precisão e reduzir a quantidade de cálculo, o número de pontuações não pode ser o maior possível?

De acordo com o conteúdo do artigo da ResNeXt, ele apontou que o propósito original da convolução de grupo é facilitar o treinamento de um modelo em várias GPUs ao mesmo tempo. A convolução de grupo pode reduzir a quantidade de cálculo, mas há poucas evidências de que a convolução de grupo possa melhorar a precisão. Mesmo agora, não encontrei evidências relevantes por enquanto. A melhoria de ResNet para ResNeXt propõe principalmente o conceito de cardinalidade. Pessoalmente, acho que se a convolução de grupo pode melhorar a precisão, é porque a convolução de grupo pode realizar o aprendizado em grupo, aprendendo informações mais profundas, semelhantes às redes neurais convolucionais. rede e tradicional camadas totalmente conectadas.

O número de grupos na convolução do grupo não deve ser muito grande. Muitos grupos tornarão a extração de recursos muito fragmentada, o que não é propício para a extração de recursos principais.

Acho que você gosta

Origin blog.csdn.net/qq_55708326/article/details/125957382
Recomendado
Clasificación