[Introducción al aprendizaje profundo de OUC] Registro de aprendizaje de la semana 3: ResNet+ResNeXt

Parte 1 ResNet

1 lectura de papel

Se sabe que cuanto más profundo es el número de capas de la red, más difícil es entrenar.Para resolver este problema, este documento propone un módulo de aprendizaje residual.Este módulo estructural es fácil de optimizar y puede lograr una alta precisión cuando la profundidad es muy profundo

El entrenamiento de redes profundas se enfrenta principalmente a dos problemas, uno es la desaparición de gradiente/explosión de gradiente, y el otro es la degradación de la red. El primero se puede resolver usando operaciones de normalización, ajustando optimizadores y tasas de aprendizaje, etc., mientras que el segundo hace que la red más profundo en capas El efecto de entrenamiento alcanza la saturación cuando , y esta degradación de la red no es causada por el sobreajuste.

Por lo tanto, a partir de la transformación de identidad, este documento propone un módulo de aprendizaje residual para resolver el problema de degradación de la red bajo esta red profunda, de modo que los módulos estructurales en la red profunda no solo puedan realizar una transformación no lineal para obtener mejores resultados de entrenamiento, sino también realizar una transformación de identidad para mantener el efecto de entrenamiento actual lo suficientemente bueno.

ResNet tiene principalmente las siguientes características:

  1. Representación residual
  2. Conexión de acceso directo (realizar transformación de identidad)

2 Estructura de la red

Lo más destacado de la red :

  • Estructura de red profunda (avance 1000)
  • módulo residual
  • Deseche el abandono y use la normalización por lotes para acelerar el entrenamiento

 Problemas que enfrentan las estructuras tradicionales de redes profundas :

  • Gradientes que se desvanecen o explotan
  • problema de degradacion

estructura residual :

La estructura residual de la izquierda se usa para la red menos profunda (ResNet 34) y el cuello de botella de la derecha se usa para ResNet 50/101/152. La forma de la matriz de características de salida de la rama principal y el atajo deben ser iguales.

El kernel de convolución 1*1 en BottleNeck se usa para reducir la dimensión, primero reduce la dimensión y luego aumenta la dimensión, lo que es beneficioso para reducir la cantidad de parámetros.

El atajo punteado en el diagrama esquemático de la estructura ResNet indica que la estructura residual aquí tiene una mejora de dimensión, y se agrega un núcleo de convolución 1*1 para la mejora de dimensión al atajo correspondiente.

La ResNet del documento original es diferente de la ResNet oficial implementada por PyTorch : en la rama principal de la estructura residual punteada en el documento original, el tamaño de paso de la convolución 1*1 es 2 y el tamaño de paso de la convolución 3*3 es 1; mientras que el PyTorch oficial En la implementación, el tamaño de paso de la convolución 1*1 es 1, y el tamaño de paso de la convolución 3*3 es 2, lo que mejora ligeramente la tasa de precisión.

Normalización por lotes (BN) : ajusta el mapa de funciones en un lote para cumplir con la ley de distribución con un valor medio de 0 y una varianza de 1. Tanto el valor medio como la varianza son vectores, y las dimensiones y profundidades corresponden a estadísticas en el futuro. propagación γ y β están entrenados en sentido inverso durante la propagación. ( Explicación detallada de la normalización por lotes y el experimento pytorch_Sunflower's Mung Bean Blog-CSDN Blog_batchnormalization pytorch )

 

 Cuando utilice BN, preste atención a :

  • training=True durante el entrenamiento, training=False durante la verificación, que puede controlarse mediante los métodos model.train() y model.eval() para crear modelos en pytorch
  • Cuanto mayor sea el tamaño del lote, más cerca estarán la media y la varianza de la media y la varianza de todo el conjunto de entrenamiento.
  • Se recomienda colocar la capa bn entre la capa convolucional (Conv) y la capa de activación (como Relu), y la capa convolucional no debe usar sesgo

La ventaja del aprendizaje por transferencia : puede entrenar rápidamente mejores resultados, incluso si el conjunto de datos es pequeño, puede lograr el efecto deseado (preste atención al método de procesamiento previo al entrenamiento)

Métodos comunes de transferencia de aprendizaje : entrenar todos los parámetros después de cargar los pesos; solo entrenar las últimas capas de parámetros después de cargar los pesos; agregar capas completamente conectadas después de cargar los pesos

3 Cree ResNet basado en PyTorch

Enlace de código: (colab) Build ResNet basado en 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)

parte 2 resultado

1 lectura de papel

Sobre la base de ResNet, el documento propuso el concepto de "cardinalidad (el tamaño del conjunto de conversión)", y señaló que la "cardinalidad" es otro factor importante que afecta el efecto de entrenamiento además de la profundidad y el ancho de la red, y aumenta la "cardinalidad". " esta operación es más eficiente que aumentar la profundidad y el ancho de la red, y aumentar el tamaño de los hiperparámetros no siempre mejora el efecto del entrenamiento, por lo que el aumento del tamaño de los hiperparámetros aumentará la dificultad y la incertidumbre del entrenamiento. Por el contrario , El diseño inteligente de la estructura de la red puede lograr mejores resultados que simplemente aumentar la profundidad de la red existente.

Desde esta perspectiva, el documento mejora la estructura de red de ResNet, apilando múltiples convoluciones de la misma estructura como un bloque residual, y cree que esta estructura puede evitar mejor los problemas de sobreajuste. La entrada del bloque residual se dividirá en varias partes de igual tamaño, y luego se realizará la misma operación de convolución en cada parte, y los resultados se integrarán para formar la salida. En comparación con la estructura Inception, este módulo de estructura es más conciso y fácil de implementar.

Después de los experimentos, se descubrió que ResNeXt de 101 capas es más preciso que ResNet de 200 capas, y la complejidad es solo la mitad de la última. Las principales características de ResNeXt son las siguientes:

  • Red convolucional de múltiples ramas
  • convolución de grupo
  • Redes convolucionales comprimidas
  • transformación aditiva

La construcción de la estructura de bloques residuales de ResNeXt debe seguir las siguientes dos pautas:

  1. Los mapas espaciales del mismo tamaño comparten hiperparámetros
  2. Cada vez que se reduce la muestra del mapa espacial por un factor de 2, el ancho del bloque también se multiplica por un factor de 2

2 Estructura de la red

En comparación con ResNet, ResNeXt mejora la estructura de bloque sobre la base de ResNet, y la red troncal se cambia a convolución de grupo.Bajo la misma cantidad de cálculo, ResNeXt tiene una tasa de error más baja.

 La convolución de grupo tiene menos parámetros que la convolución tradicional. Cuando la dimensión de salida es la misma que la dimensión de entrada, es equivalente a asignar un núcleo de convolución con un canal de 1 a cada canal de la matriz de características de entrada para la convolución.

Las siguientes tres formas son completamente equivalentes en el cálculo.

 ResNet50 y ResNeXt50:

Solo cuando el número de capas de bloque es mayor o igual a 3, se puede construir un bloque de convolución de grupo significativo, por lo que esta mejora tiene poco efecto en la ResNet poco profunda.

3 Cree ResNet basado en PyTorch

 Enlace de código: (colab) Build ResNeXt basado en 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)

Ejercicio de código de la parte 3: guerra de perros y gatos

Enlace de código: (colab) guerra de perros y gatos

cargador de datos:

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:

Entrenamiento (ResNet34 a la izquierda, LeNet5 a la derecha):

 

 

Generar archivo csv de resultado:

Resultados de puntuación: 

 

Se puede ver que ResNet34 es mejor que LeNet5 cuando se usan los mismos parámetros de optimización y el mismo número de rondas de entrenamiento.

Parte 4 preguntas de pensamiento

1. Aprendizaje residual

Suponga que el mapeo básico es h(x), y los mapeos de las dos ramas del bloque residual son f(x) yx respectivamente, entonces h(x)=x+f(x), f(x) es el principal Rama para aprender Mapeo, esta estructura ayudará a la realización y preprocesamiento del mapeo de identidad, y aliviará el problema de la degradación de la red. Al derivar el gradiente, hay h'(x)=1+f'(x), que también puede aliviar el problema de la desaparición del gradiente.

2. El principio de normalización por lotes

La red neuronal convolucional contiene muchas capas ocultas, y los parámetros de cada capa cambiarán con el entrenamiento. La distribución de entrada de la capa oculta siempre cambiará, lo que reducirá la velocidad de aprendizaje y se saturará el gradiente de la función de activación. La normalización es una idea para resolver este problema, pero si el uso de la normalización es demasiado, el cálculo del proceso de entrenamiento será muy complicado, y si es demasiado pequeño, no será efectivo, por lo que se propone BN. primero divide los datos en varios lotes y luego realiza una operación de normalización en cada lote.

3. ¿Por qué la convolución de grupo puede mejorar la precisión? Ahora que la convolución de grupo puede mejorar la precisión y reducir la cantidad de cálculo, ¿no puede ser el número de puntajes lo más grande posible?

Según el contenido del documento de ResNeXt, señaló que el propósito original de la convolución de grupos es facilitar el entrenamiento de un modelo en múltiples GPU al mismo tiempo. La convolución de grupos puede reducir la cantidad de cálculo, pero hay poca evidencia de que la convolución de grupos pueda mejorar la precisión Incluso ahora, no he encontrado evidencia relevante por el momento. La mejora de ResNet a ResNeXt propone principalmente el concepto de cardinalidad. Personalmente, creo que si la convolución de grupos puede mejorar la precisión, es porque la convolución de grupos puede realizar un aprendizaje grupal, por lo tanto, aprender información más profunda, similar a las redes neuronales convolucionales. capas completamente conectadas.

El número de grupos en la convolución de grupo no debe ser demasiado. Demasiados grupos harán que la extracción de características sea muy fragmentada, lo que no es propicio para la extracción de características clave.

Supongo que te gusta

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