Construcción del modelo ch03-PyTorch.

0 Prefacio

1. Pasos de creación del modelo y nn.Módulo

En las secciones anteriores, aprendimos sobre el módulo de datos de PyTorch y cómo PyTorch lee datos del disco duro, luego preprocesa los datos, los mejora y finalmente los convierte en forma tensorial y los ingresa en nuestro modelo. En el modelo profundo, se realizan una serie de operaciones matemáticas complejas en los tensores para finalmente obtener información para tareas como clasificación, segmentación y detección de objetivos. En esta sección, aprenderemos sobre la creación de modelos en PyTorch y conceptos relacionados de nn.Module.

1.1 Pasos de creación del modelo de red.

Antes de aprender a crear un modelo, revisemos los 5 pasos del entrenamiento del modelo de aprendizaje automático mencionados anteriormente:

Insertar descripción de la imagen aquí

Hemos completado el estudio del módulo de datos en las lecciones anteriores y ahora comenzamos a estudiar el módulo de modelo.

Insertar descripción de la imagen aquí

Repasemos la red LeNet que utilizamos en el ejemplo anterior de clasificación de RMB:

Diagrama de estructura del modelo LeNet:

Insertar descripción de la imagen aquí

Se puede ver que la red LeNet consta de 7 capas: capa de convolución 1, capa de agrupación 1, capa de convolución 2, capa de agrupación 2 y 3 capas completamente conectadas. Al crear LeNet, primero debe construir estos submódulos. Después de construir estas 7 capas de subred, las conectaremos en un orden determinado. Finalmente, envuélvalos para obtener nuestra red LeNet.

En PyTorch, LeNet es un concepto de Módulo, y su capa de subred también es un concepto de Módulo, y todos pertenecen a nn.Modulela clase. Por lo tanto, uno nn.Module(por ejemplo: LeNet) puede contener muchos submódulos (por ejemplo: capa convolucional, capa de agrupación, etc.).

A continuación observamos el proceso de creación del modelo desde la perspectiva de gráficos computacionales:

Insertar descripción de la imagen aquí

Hay dos conceptos principales en los gráficos computacionales: nodos y aristas. Entre ellos, los nodos representan tensores (datos) y los bordes representan operaciones. LeNet puede verse como un conjunto de operaciones tensoriales en su conjunto: recibe un 32*32*3tensor y, después de una serie de operaciones complejas, genera un vector de longitud 10 como probabilidad de clasificación. Dentro de LeNet, se compone de una serie de capas de subred. Por ejemplo, la capa de convolución 1 32*32*3realiza una operación de convolución en un tensor para obtener un 28*28*6tensor y lo utiliza como entrada de la siguiente capa de subred. esto A través de una propagación directa continua, finalmente se calcula la probabilidad de salida. En el aprendizaje profundo, este proceso se llama propagación hacia adelante .

Analizamos el modelo de red LeNet desde la perspectiva de la estructura de la red y el gráfico computacional, y aprendimos sobre los dos elementos de la construcción de un modelo: construir submódulos y empalmar submódulos.

Insertar descripción de la imagen aquí

A continuación, aprenderemos cómo construir un modelo a través del ejemplo anterior de clasificación RMB dos. Tomando lenet.pyLeNet como ejemplo, la herencia nn.Moduledebe implementar __init__()métodos y forward()métodos. Entre ellos, __init__()los submódulos se crean en el método y forward()los submódulos se empalman en el método.

Construye el modelo:

# ============================ step 2/5 模型 ============================
net = LeNet(classes=2)
net.initialize_weights()

Clase LeNet:

class LeNet(nn.Module):
    # 构建子模块
    def __init__(self, classes):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, classes)

    # 拼接子模块
    def forward(self, x):
        out = F.relu(self.conv1(x))
        out = F.max_pool2d(out, 2)
        out = F.relu(self.conv2(out))
        out = F.max_pool2d(out, 2)
        out = out.view(out.size(0), -1)
        out = F.relu(self.fc1(out))
        out = F.relu(self.fc2(out))
        out = self.fc3(out)
        return out

    def initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.xavier_normal_(m.weight.data)
                if m.bias is not None:
                    m.bias.data.zero_()
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight.data, 0, 0.1)
                m.bias.data.zero_()

Cuando llamamos para net = LeNet(classes=2)crear un modelo, __init__()se llamará al método para crear el submódulo del modelo.

Cuando llamamos durante el entrenamiento outputs = net(inputs), ingresaremos module.pyla función call() de (la clase principal de LeNet):

    def __call__(self, *input, **kwargs):
        for hook in self._forward_pre_hooks.values():
            result = hook(self, input)
            if result is not None:
                if not isinstance(result, tuple):
                    result = (result,)
                input = result
        if torch._C._get_tracing_state():
            result = self._slow_forward(*input, **kwargs)
        else:
            result = self.forward(*input, **kwargs)
        ...
        ...
        ...

Finalmente, result = self.forward(*input, **kwargs)se llamará a la función, que irá a la forward()función del modelo para la propagación hacia adelante.

Hay 4 módulos incluidos en torch.nn, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí
Todos los modelos de red se heredan nn.Moduley los siguientes módulos se centrarán en ellos nn.Module.

1.2. nn.Módulo

En el módulo modelo, tenemos un concepto muy importante: nn.Module. Todos nuestros modelos y capas de red heredan de la clase nn.Module, por lo que debemos comprenderla. Antes de aprender nn.Module, echemos un vistazo a varios módulos relacionados con él:

Insertar descripción de la imagen aquí

El primero es torch.nn, que es un módulo de red neuronal de PyTorch. Tiene muchos submódulos. Aquí debemos comprender 4 de ellos: nn.Parameter、nn.Module、nn.functional 和 nn.init. En esta lección nos centraremos primero en nn.Module.

  • nn.Módulo

Hay 8 propiedades importantes en nn.Module para gestionar todo el modelo:

nn.ModuleHay 8 atributos que se utilizan para administrar todo el modelo, todos los cuales son OrderDict(diccionarios ordenados). En el método LeNetde __init__()la clase principal, se llamará al método de nn.Modulepara __init__()crear estos 8 atributos.

self._parameters = OrderedDict()
self._buffers = OrderedDict()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self._forward_pre_hooks = OrderedDict()
self._state_dict_hooks = OrderedDict()
self._load_state_dict_pre_hooks = OrderedDict()
self._modules = OrderedDict()

Comprenda principalmente las propiedades:

  • parámetros: Gestión de almacenamiento nn. Clase de parámetro.
  • módulos: Gestión de almacenamiento nn. Clase de módulo (submodelo).
  • buffers: atributos del buffer de gestión de almacenamiento, como running_mean en la capa BN.
  • ***_hooks: función de enlace de gestión de almacenamiento.

Aquí, nos centramos en dos de los atributos: parámetros y módulos.

Se crearon cinco submódulos en y se heredan de , es decir LeNet, un módulo contiene múltiples submódulos.__init__()nn.Conv2d()nn.Linear()nn.module

class LeNet(nn.Module):
	# 子模块创建
    def __init__(self, classes):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, classes)
        ...
        ...
        ...

Cuando se llama a net = LeNet(classes=2) para crear un modelo, el atributo de módulos del objeto net contiene estos 5 módulos de subred.

Insertar descripción de la imagen aquí
Veamos cómo se agrega cada submódulo a _moduleslas propiedades de LeNet. Por self.conv1 = nn.Conv2d(3, 6, 5)ejemplo, cuando corremos hasta esta línea, primero Step Into ingresa Conv2da la estructura y luego Step Out. Haga clic derecho Evaluate Expressionpara ver nn.Conv2d(3, 6, 5)propiedades.

Insertar descripción de la imagen aquí

Como se mencionó anteriormente, Conv2dtambién es un módulo. Los atributos internos _modulesestán vacíos. _parametersLos atributos contienen los parámetros que se pueden aprender de la capa convolucional. El tipo de estos parámetros es Parámetro y se hereda de Tensor.

Insertar descripción de la imagen aquí
En este punto, solo se ha completado la creación del módulo nn.Conv2d(3, 6, 5). Aún no se ha asignado ningún valor self.conv1 . Hay un mecanismo interno nn.Moduleque interceptará todas las operaciones de asignación de atributos de clase ( self.conv1atributos de clase) e ingresará __setattr__()a la función. Podemos entrar nuevamente por Step Into __setattr__().

 def __setattr__(self, name, value):
        def remove_from(*dicts):
            for d in dicts:
                if name in d:
                    del d[name]

        params = self.__dict__.get('_parameters')
        if isinstance(value, Parameter):
            if params is None:
                raise AttributeError(
                    "cannot assign parameters before Module.__init__() call")
            remove_from(self.__dict__, self._buffers, self._modules)
            self.register_parameter(name, value)
        elif params is not None and name in params:
            if value is not None:
                raise TypeError("cannot assign '{}' as parameter '{}' "
                                "(torch.nn.Parameter or None expected)"
                                .format(torch.typename(value), name))
            self.register_parameter(name, value)
        else:
            modules = self.__dict__.get('_modules')
            if isinstance(value, Module):
                if modules is None:
                    raise AttributeError(
                        "cannot assign module before Module.__init__() call")
                remove_from(self.__dict__, self._parameters, self._buffers)
                modules[name] = value
            elif modules is not None and name in modules:
                if value is not None:
                    raise TypeError("cannot assign '{}' as child module '{}' "
                                    "(torch.nn.Module or None expected)"
                                    .format(torch.typename(value), name))
                modules[name] = value
            ...
            ...
            ...

Aquí, se determina si el tipo de valor es Parámetro o Módulo y se almacena en el diccionario ordenado correspondiente.

Aquí, el tipo de nn.Conv2d(3, 6, 5) es Módulo, por lo que se ejecutará módulos [nombre] = valor. La clave es el nombre del atributo de clase conv1 y el valor es nn.Conv2d(3, sesenta y cinco).


El mecanismo de construcción de atributos de nn.Module: al asignar atributos en la clase de módulo, primero será interceptado porfunción setattr , que determina el tipo de datos que se asignará:

  • Si el valor asignado es de la clase nn.Parameter, se almacena en el diccionario de parámetros para su gestión;
  • Si el valor asignado es la clase nn.Module, se almacena en el diccionario de módulos para su gestión.

nn.Resumen del módulo:

  • Un módulo puede contener varios submódulos.
    • Por ejemplo: el módulo LeNet contendrá algunas capas convolucionales, capas de agrupación y otros submódulos.
  • Un módulo es equivalente a una operación y debe implementar la función forward().
    • Desde la perspectiva de un gráfico computacional, un módulo recibe un tensor y, después de una serie de operaciones complejas, genera probabilidad u otros datos. Por lo tanto, necesitamos implementar una función de propagación hacia adelante.
  • Cada módulo cuenta con 8 diccionarios ordenados (OrderedDict) para gestionar sus propiedades.
    • Aquí, los más utilizados son el diccionario de parámetros y el diccionario de módulos .

1.3 Resumen

En esta sección, aprendimos el concepto de nn.Module y los dos elementos de la creación de modelos. En la siguiente sección, aprenderemos sobre los contenedores y la construcción de AlexNet.

2. Modelo de contenedor y construcción de AlexNet.

En la sección anterior, aprendimos cómo construir un modelo. Hay dos elementos en el proceso de construcción de un modelo: construir submódulos y empalmar submódulos. Además, existe un concepto muy importante a la hora de construir un modelo: contenedores modelo (Containers). En esta lección aprenderemos sobre el modelo de contenedor y la construcción de AlexNet.

2.1 Modelo de contenedor

Además de los módulos anteriores, otro concepto importante son los contenedores modelo (Contenedores). Hay tres contenedores de uso común y todos estos contenedores heredan de nn.Module.

  • nn.Sequential: envuelve varias capas de red en orden
  • nn.ModuleList: envuelve múltiples capas de red como la lista de Python y se puede iterar
  • nn.ModuleDict: envuelve múltiples capas de red como el dict de Python, especificando un nombre para cada capa de red en forma de (clave, valor).

Insertar descripción de la imagen aquí

2.1.1.nn.Secuencial
  • nn.Sequential es un contenedor de nn.Module, que se utiliza para 按顺序envolver un conjunto de capas de red.

En el aprendizaje automático tradicional, un paso es la ingeniería de características: necesitamos extraer características de los datos y luego ingresarlas en el clasificador para su predicción. En la era del aprendizaje profundo, el concepto de ingeniería de características se ha debilitado y los dos pasos de extracción y clasificación de características se han integrado en una red neuronal. En una red neuronal convolucional, la capa convolucional anterior y la capa de agrupación pueden considerarse como la parte de extracción de características, mientras que la siguiente capa completamente conectada puede considerarse como la parte clasificadora. Por ejemplo, LeNet se puede dividir en dos partes: extracción de características y clasificador, que se pueden empaquetar por separado nn.Seuqtial.

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

nn.Sequential empaqueta un conjunto de capas de red en secuencia en un todo, que puede considerarse como un submódulo del modelo. En el aprendizaje automático tradicional, existe un paso llamado ingeniería de características: necesitamos diseñar características manualmente e ingresarlas en el clasificador para su clasificación. En la era del aprendizaje profundo, el concepto de ingeniería de características se ha debilitado, especialmente en las redes neuronales convolucionales. No necesitamos diseñar características de imágenes manualmente, por el contrario, podemos dejar que la red neuronal convolucional aprenda características automáticamente y las agregue en Al final, se utilizan varias capas completamente conectadas para generar resultados de clasificación. En las primeras redes neuronales, el clasificador utilizado para la clasificación estaba compuesto por capas completamente conectadas, por lo que en la era del aprendizaje profundo, generalmente se acostumbra usar la capa completamente conectada como límite para dividir el modelo de red en módulos de extracción de características y clasificación. módulos. Dividir un modelo grande puede facilitar la administración según módulos: por ejemplo, en el modelo LeNet anterior, podemos empaquetar múltiples capas convolucionales y capas de agrupación en un extractor de características, y combinar las siguientes capas completamente conectadas. Empaquételo en un clasificador y finalmente empaquete Estos dos módulos en una red neuronal LeNet completa. En PyTorch, podemos usar nn.Sequential para completar estos procesos de empaquetado.

Ejemplo de código:

class LeNetSequential(nn.Module):
    def __init__(self, classes):
        super(LeNetSequential, self).__init__()

        self.features = nn.Sequential(
            nn.Conv2d(3, 6, 5),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2),)

        self.classifier = nn.Sequential(
            nn.Linear(16*5*5, 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, classes),)

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size()[0], -1)
        x = self.classifier(x)
        return x
 
    net = LeNetSequential(classes=2)

    fake_img = torch.randn((4, 3, 32, 32), dtype=torch.float32)

    output = net(fake_img)

    print(net)
    print(output)        

Producción:

LeNetSequetial(
  (features): Sequential(
    (0): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (4): ReLU()
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=400, out_features=120, bias=True)
    (1): ReLU()
    (2): Linear(in_features=120, out_features=84, bias=True)
    (3): ReLU()
    (4): Linear(in_features=84, out_features=2, bias=True)
  )
)
tensor([[0.0413, 0.0061],
        [0.0484, 0.0132],
        [0.0089, 0.0006],
        [0.0297, 0.0040]], grad_fn=<AddmmBackward0>)

Analiza esta red:

La entrada proporcionada es un tensor de imagen falso de tamaño (4, 3, 32, 32), donde 4 representa el tamaño del lote, 3 representa el número de canales de imagen (RGB) y 32x32 representa el ancho y alto de la imagen.

1. La primera capa convolucional (nn.Conv2d(3, 6, 5)):

  • Entrada: (4, 3, 32, 32)
  • Salida: (4, 6, 28, 28). El canal de salida es 6, el tamaño del núcleo de convolución es 5x5 y no hay relleno, por lo que (32 - 5 + 1)/1 = 28.
  • Dimensiones de los parámetros: (6, 3, 5, 5), incluidos 6 canales de salida, 3 canales de entrada y un núcleo de convolución de 5x5. Los parámetros se comparten.

Por favor agregue la descripción de la imagen.

2.Función de activación ReLU:

  • Entrada: (4, 6, 28, 28)
  • Salida: (4, 6, 28, 28). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

3. Capa de agrupación máxima (nn.MaxPool2d(kernel_size=2, stride=2)):

  • Entrada: (4, 6, 28, 28)
  • Salida: (4, 6, 14, 14). La capa de agrupación reduce a la mitad el ancho y el alto de la entrada.
  • Sin parámetros.

4. La segunda capa convolucional (nn.Conv2d(6, 16, 5)):

  • Entrada: (4, 6, 14, 14)
  • Salida: (4, 16, 10, 10). El canal de salida es 16, el tamaño del núcleo de convolución es 5x5 y no hay relleno, por lo que (14 - 5 + 1)/1 = 10.
  • Dimensiones de los parámetros: (16, 6, 5, 5), incluidos 16 canales de salida, 6 canales de entrada y un núcleo de convolución de 5x5. Los parámetros se comparten.

5.Función de activación ReLU:

  • Entrada: (4, 16, 10, 10)
  • Salida: (4, 16, 10, 10). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

6. Capa de agrupación máxima (nn.MaxPool2d(kernel_size=2, stride=2)):

  • Entrada: (4, 16, 10, 10)
  • Salida: (4, 16, 5, 5). La capa de agrupación reduce a la mitad el ancho y el alto de la entrada.
  • Sin parámetros.

7. Aplanar la salida: Aplana la salida para introducirla en la capa completamente conectada.

  • Entrada: (4, 16, 5, 5)
  • Salida: (4, 400). Aquí, 16x5x5=400.

8. La primera capa completamente conectada (nn.Linear(1655, 120)):

  • Entrada: (4, 400)
  • Salida: (4, 120)
  • Dimensiones de los parámetros: (120, 400), incluidos 120 nodos de salida y 400 nodos de entrada.

9.Función de activación ReLU:

  • Entrada: (4, 120)
  • Salida: (4, 120). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

10. La segunda capa completamente conectada (nn.Linear(120, 84)):

  • Entrada: (4, 120)
  • Salida: (4, 84)
  • Dimensiones de los parámetros: (84, 120), incluidos 84 nodos de salida y 120 nodos de entrada.

11.Función de activación ReLU:

  • Entrada: (4, 84)
  • Salida: (4, 84). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

12. La tercera capa completamente conectada (nn.Linear (84, clases)):

  • Entrada: (4, 84)
  • Salida: (4, 2), donde clases = 2.
  • Dimensiones de los parámetros: (2, 84), incluidos 2 nodos de salida y 84 nodos de entrada.

Finalmente, la dimensión de la salida de la red es (4, 2), lo que significa que para cada imagen de entrada, obtenemos un vector de salida de tamaño 2, que puede usarse para tareas de clasificación. En este ejemplo, la clasificación de salida es una clasificación binaria. En el modelo LeNetSequential, existen las siguientes capas que contienen parámetros:

1. La primera capa convolucional (nn.Conv2d(3, 6, 5)):

  • Dimensiones de los parámetros: (6, 3, 5, 5)
  • Número de parámetros: 6 * 3 * 5 * 5 = 450

2. La segunda capa convolucional (nn.Conv2d(6, 16, 5)):

  • Dimensiones de los parámetros: (16, 6, 5, 5)
  • Número de parámetros: 16 * 6 * 5 * 5 = 2400

3. La primera capa completamente conectada (nn.Linear(1655, 120)):

  • Dimensiones de los parámetros: (120, 400)
  • Número de parámetros: 120 * 400 = 48000

4. La segunda capa completamente conectada (nn.Linear(120, 84)):

  • Dimensiones de los parámetros: (84, 120)
  • Número de parámetros: 84 * 120 = 10080

5. La tercera capa completamente conectada (nn.Linear (84, clases)):

  • Dimensiones de los parámetros: (2, 84), donde clases = 2.
  • Número de parámetros: 2 * 84 = 168

Ahora sumamos el número de parámetros para todas las capas:
450 + 2400 + 48000 + 10080 + 168 = 61098

Por lo tanto, el número total de parámetros que deben aprenderse en toda la red LeNetSequential es 61.098.


Durante la inicialización, nn.Sequetial llamará al método __init__() para agregar cada submódulo a su propio atributo _modules. Como puede ver aquí, el parámetro que pasamos puede ser una lista o un OrderDict. Si es un OrderDict, entonces use la clave en OrderDict; de lo contrario, use un número como clave (el caso de OrderDict se mencionará a continuación).

    def __init__(self, *args):
        super(Sequential, self).__init__()
        if len(args) == 1 and isinstance(args[0], OrderedDict):
            for key, module in args[0].items():
                self.add_module(key, module)
        else:
            for idx, module in enumerate(args):
                self.add_module(str(idx), module)

Una vez completada la inicialización de la red, hay dos submódulos: características y clasificador.

Insertar descripción de la imagen aquí

featuresLos submódulos son los siguientes y cada capa de red utiliza el número de serie como clave :

Insertar descripción de la imagen aquí

Al realizar la propagación hacia adelante, forward()se ingresará la función de LeNet y Sequetialse llamará primero al primer contenedor: self.featuresDado que self.featurestambién es un módulo, se llamará a la función __call__(), que llama

result = self.forward(*input, **kwargs), ingrese la función forward() de nn.Seuqetial y llame a todos los módulos aquí en secuencia.

    def forward(self, input):
        for module in self:
            input = module(input)
        return input

Como puede ver arriba, en nn.Sequential, cada módulo de capa de subred se indexa utilizando un número de serie, es decir, utilizando números como claves. Una vez que aumenta el número de capas de red, es difícil encontrar una capa de red específica, en este caso se puede utilizar OrderDict (diccionario ordenado). utilizado en el código


class LeNetSequentialOrderDict(nn.Module):
    def __init__(self, classes):
        super(LeNetSequentialOrderDict, self).__init__()

        self.features = nn.Sequential(OrderedDict({
    
    
            'conv1': nn.Conv2d(3, 6, 5),
            'relu1': nn.ReLU(inplace=True),
            'pool1': nn.MaxPool2d(kernel_size=2, stride=2),

            'conv2': nn.Conv2d(6, 16, 5),
            'relu2': nn.ReLU(inplace=True),
            'pool2': nn.MaxPool2d(kernel_size=2, stride=2),
        }))

        self.classifier = nn.Sequential(OrderedDict({
    
    
            'fc1': nn.Linear(16*5*5, 120),
            'relu3': nn.ReLU(),

            'fc2': nn.Linear(120, 84),
            'relu4': nn.ReLU(inplace=True),

            'fc3': nn.Linear(84, classes),
        }))

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


net = LeNetSequentialOrderDict(classes=2)
fake_img = torch.randn((4, 3, 32, 32), dtype=torch.float32)
output = net(fake_img)
print(net)
print(output)

Producción:

LeNetSequentialOrderDict(
  (features): Sequential(
    (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    (relu1): ReLU(inplace=True)
    (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (relu2): ReLU(inplace=True)
    (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (fc1): Linear(in_features=400, out_features=120, bias=True)
    (relu3): ReLU()
    (fc2): Linear(in_features=120, out_features=84, bias=True)
    (relu4): ReLU(inplace=True)
    (fc3): Linear(in_features=84, out_features=2, bias=True)
  )
)
tensor([[ 0.0525, -0.0253],
        [ 0.0452, -0.0292],
        [ 0.0359, -0.0491],
        [ 0.0413, -0.0322]], grad_fn=<AddmmBackward0>)

nn.Sequential es un contenedor de nn.Module, utilizado para empaquetar un grupo de capas de red en orden, tiene las dos características siguientes.

  • Secuencialidad: cada capa de red se construye estrictamente en secuencia.
  • Reenvío incorporado (): en el reenvío incorporado, la operación de propagación hacia adelante se realiza secuencialmente a través del bucle for.
2.1.2.nn.Lista de módulos

nn.ModuleList es un contenedor de nn.Module, que se utiliza para envolver un conjunto de capas de red y 迭代llamar a la capa de red en el camino. Existen principalmente 3 métodos:

  • 添加append(): capa de red detrás de ModuleList .
  • extender(): 拼接dos ModuleLists.
  • insert(): especifica la posición de la capa de red en ModuleList 插入.

El siguiente código utiliza la generación de listas para iterar y crear 20 capas completamente conectadas, lo cual es muy conveniente, pero cada capa de red debe llamarse manualmente en la función forward (). Ejemplo de código:

class ModuleList(nn.Module):
    def __init__(self):
        super(ModuleList, self).__init__()
        # 构建 20 个全连接层
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])

    def forward(self, x):
        for i, linear in enumerate(self.linears):
            x = linear(x)
        return x


net = ModuleList()
print(net)
fake_data = torch.ones((10, 10))
output = net(fake_data)
print(output)

Producción:

ModuleList(
  (linears): ModuleList(
    (0): Linear(in_features=10, out_features=10, bias=True)
    (1): Linear(in_features=10, out_features=10, bias=True)
    (2): Linear(in_features=10, out_features=10, bias=True)
    (3): Linear(in_features=10, out_features=10, bias=True)
    (4): Linear(in_features=10, out_features=10, bias=True)
    (5): Linear(in_features=10, out_features=10, bias=True)
    (6): Linear(in_features=10, out_features=10, bias=True)
    (7): Linear(in_features=10, out_features=10, bias=True)
    (8): Linear(in_features=10, out_features=10, bias=True)
    (9): Linear(in_features=10, out_features=10, bias=True)
    (10): Linear(in_features=10, out_features=10, bias=True)
    (11): Linear(in_features=10, out_features=10, bias=True)
    (12): Linear(in_features=10, out_features=10, bias=True)
    (13): Linear(in_features=10, out_features=10, bias=True)
    (14): Linear(in_features=10, out_features=10, bias=True)
    (15): Linear(in_features=10, out_features=10, bias=True)
    (16): Linear(in_features=10, out_features=10, bias=True)
    (17): Linear(in_features=10, out_features=10, bias=True)
    (18): Linear(in_features=10, out_features=10, bias=True)
    (19): Linear(in_features=10, out_features=10, bias=True)
  )
)
tensor([[-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383],
        [-0.3349, -0.0728, -0.3669,  0.2553, -0.1117,  0.1883,  0.3698,  0.1728,
         -0.2658, -0.0383]], grad_fn=<AddmmBackward0>)
2.1.3.nn.ModuleDict

nn.ModuleDictEs nn.Moduleun contenedor que envuelve un conjunto de capas de red y 索引llama a la capa de red en el camino. Métodos principales:

  • clear(): Borrar ModuleDict.
  • items(): Devuelve un iterable de pares clave-valor.
  • llaves(): Devuelve la clave del diccionario.
  • valores(): Devuelve el valor del diccionario.
  • pop(): devuelve un par de valores clave y los elimina del diccionario.

Ejemplo de código:

class ModuleDict(nn.Module):
    def __init__(self):
        super(ModuleDict, self).__init__()
        self.choices = nn.ModuleDict({
    
    
            'conv': nn.Conv2d(10, 10, 3),
            'pool': nn.MaxPool2d(3)
        })

        self.activations = nn.ModuleDict({
    
    
            'relu': nn.ReLU(),
            'prelu': nn.PReLU()
        })

    def forward(self, x, choice, act):
        x = self.choices[choice](x)
        x = self.activations[act](x)
        return x


net = ModuleDict()
fake_img = torch.randn((4, 10, 32, 32))
output = net(fake_img, 'conv', 'relu')
print(net)
# print(output)

Producción:

ModuleDict(
  (choices): ModuleDict(
    (conv): Conv2d(10, 10, kernel_size=(3, 3), stride=(1, 1))
    (pool): MaxPool2d(kernel_size=3, stride=3, padding=0, dilation=1, ceil_mode=False)
  )
  (activations): ModuleDict(
    (relu): ReLU()
    (prelu): PReLU(num_parameters=1)
  )
)

Resumen del contenedor

  • nn.Sequential: 顺序性cada capa de red se ejecuta estrictamente en secuencia, y a menudo se usa para la construcción de bloques.
  • nn.ModuleList: 迭代性a menudo se usa para una gran cantidad de construcciones repetidas de capas de red, y la construcción repetida se logra mediante bucles for.
  • nn.ModuleDict: 索引性, utilizado a menudo para capas de red opcionales.

2.2 Construcción de AlexNet

AlexNet: En 2012, ganó el 分类任务campeonato ImageNet con una tasa de precisión que superó en más de 10 puntos porcentuales al segundo lugar. Desde entonces, las redes neuronales convolucionales se han vuelto populares en el mundo, lo que es una contribución que hace época.

Las características de AlexNet son las siguientes:

  • Utilice ReLU: reemplace la función de activación saturada (por ejemplo: Sigmoide) para reducir la desaparición de gradientes.
  • Utilice LRN (Normalización de respuesta local): normalice los datos y reduzca la desaparición del gradiente.
  • Abandono: mejora la robustez de la capa totalmente conectada y aumenta la capacidad de generalización de la red.
  • Aumento de datos: TenCrop, modificación de color.
  • Referencia: Clasificación de ImageNet con redes neuronales convolucionales profundas

La estructura de red de AlexNet se puede dividir en dos partes: características y clasificador .

Insertar descripción de la imagen aquí

AlexNet utiliza:

  • Los métodos de convolución, agrupación, convolución y apilamiento de agrupaciones se utilizan para extraer características de datos.
  • Luego se conectan tres capas completamente conectadas para su clasificación.

Aquí, podemos aplicar los conceptos en nn.Sequential para empaquetar la parte de agrupación convolucional anterior en un módulo de características y la parte posterior completamente conectada en un módulo clasificador, descomponiendo así una red compleja en un módulo de extracción de características y un módulo de clasificación.

En el código de AlexNet en PyTorchla biblioteca de visión por computadora torchvision.models, se utiliza nn.Sequentialpara encapsular la capa de red. Implementación AlexNet incorporada de PyTorch en torchvision.models:

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

Ejemplo de código:

alexnet = torchvision.models.AlexNet()
print(alexnet)

Producción:

AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
  (classifier): Sequential(
    (0): Dropout(p=0.5, inplace=False)
    (1): Linear(in_features=9216, out_features=4096, bias=True)
    (2): ReLU(inplace=True)
    (3): Dropout(p=0.5, inplace=False)
    (4): Linear(in_features=4096, out_features=4096, bias=True)
    (5): ReLU(inplace=True)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

Análisis de la estructura de la red:

Primero, analizaremos el tamaño de la dimensión del flujo de datos y el tamaño de la dimensión de los parámetros de cada capa. La entrada proporcionada es un tensor de imagen de tamaño (1, 3, 512, 512), donde 1 representa el tamaño del lote, 3 representa el número de canales de imagen (RGB) y 512x512 representa el ancho y alto de la imagen.

1. La primera capa convolucional (nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2)):

  • Entrada: (1, 3, 512, 512)
  • Salida: (1, 64, 128, 128). El canal de salida es 64, el tamaño del núcleo de convolución es 11x11, el paso es 4 y el relleno es 2, por lo que el ancho y el alto se reducen en ((512+4-11)/4)+1=128.
  • Dimensiones de los parámetros: (64, 3, 11, 11), incluidos 64 canales de salida, 3 canales de entrada y un núcleo de convolución 11x11.

2.Función de activación ReLU:

  • Entrada: (1, 64, 128, 128)
  • Salida: (1, 64, 128, 128). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

3. Capa de agrupación máxima (nn.MaxPool2d(kernel_size=3, stride=2)):

  • Entrada: (1, 64, 128, 128)
  • Salida: (1, 64, 63, 63). La capa de agrupación reduce el ancho y el alto de la entrada. ((128-3)/2)+1=63.
  • Sin parámetros.

4. La segunda capa convolucional (nn.Conv2d(64, 192, kernel_size=5, padding=2)):

  • Entrada: (1, 64, 63, 63)
  • Salida: (1, 192, 63, 63). Los canales de salida son 192, el tamaño del núcleo es 5x5 y el relleno es 2, por lo que el ancho y el alto siguen siendo los mismos.
  • Dimensiones de los parámetros: (192, 64, 5, 5), incluidos 192 canales de salida, 64 canales de entrada y un núcleo de convolución de 5x5.

5.Función de activación ReLU:

  • Entrada: (1, 192, 63, 63)
  • Salida: (1, 192, 63, 63). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

6. Capa de agrupación máxima (nn.MaxPool2d(kernel_size=3, stride=2)):

  • Entrada: (1, 192, 63, 63)
  • Salida: (1, 192, 31, 31). La capa de agrupación reduce el ancho y el alto de la entrada. ((63-3)/2)+1=31.
  • Sin parámetros.

7. La tercera capa convolucional (nn.Conv2d(192, 384, kernel_size=3, padding=1)):

  • Entrada: (1, 192, 31, 31)
  • Salida: (1, 384, 31, 31). Los canales de salida son 384, el tamaño del núcleo es 3x3 y el relleno es 1, por lo que el ancho y el alto siguen siendo los mismos.
  • Dimensiones de los parámetros: (384, 192, 3, 3), incluidos 384 canales de salida, 192 canales de entrada y un núcleo de convolución de 3x3.

8.Función de activación ReLU:

  • Entrada: (1, 384, 31, 31)
  • Salida: (1, 384, 31, 31). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

9. La cuarta capa convolucional (nn.Conv2d(384, 256, kernel_size=3, padding=1)):

  • Entrada: (1, 384, 31, 31)
  • Salida: (1, 256, 31, 31). Los canales de salida son 256, el tamaño del núcleo es 3x3 y el relleno es 1, por lo que el ancho y el alto siguen siendo los mismos.
  • Dimensiones de los parámetros: (256, 384, 3, 3), incluidos 256 canales de salida, 384 canales de entrada y un núcleo de convolución 3x3.

10.Función de activación ReLU:

  • Entrada: (1, 256, 31, 31)
  • Salida: (1, 256, 31, 31). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

11. La quinta capa convolucional (nn.Conv2d(256, 256, kernel_size=3, padding=1)):

  • Entrada: (1, 256, 31, 31)
  • Salida: (1, 256, 31, 31). Los canales de salida son 256, el tamaño del núcleo es 3x3 y el relleno es 1, por lo que el ancho y el alto siguen siendo los mismos.
  • Dimensiones de los parámetros: (256, 256, 3, 3), incluidos 256 canales de salida, 256 canales de entrada y un núcleo de convolución 3x3.

12.Función de activación ReLU:

  • Entrada: (1, 256, 31, 31)
  • Salida: (1, 256, 31, 31). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

13. Capa de agrupación máxima (nn.MaxPool2d(kernel_size=3, stride=2)):

  • Entrada: (1, 256, 31, 31)
  • Salida: (1, 256, 15, 15). La capa de agrupación reduce el ancho y el alto de la entrada. ((31-3)/2)+1=15.
  • Sin parámetros.

14. Capa de agrupación promedio adaptativa (nn.AdaptiveAvgPool2d((6, 6))):

  • Entrada: (1, 256, 15, 15)
  • Salida: (1, 256, 6, 6). Esta capa cambia el tamaño del ancho y alto del tensor de entrada a las dimensiones de destino (6, 6).
  • Sin parámetros.

15. La primera capa completamente conectada (nn.Linear (256 * 6 * 6, 4096)):

  • Entrada: (1, 256 * 6 * 6). Aplana el tensor antes de pasarlo a la capa completamente conectada.
  • Salida: (1, 4096).
  • Dimensiones de los parámetros: (4096, 256 * 6 * 6), incluidos 4096 nodos de salida y 256 * 6 * 6 nodos de entrada.

16.Función de activación ReLU:

  • Entrada: (1, 4096)
  • Salida: (1, 4096). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

17. La segunda capa completamente conectada (nn.Linear(4096, 4096)):

  • Entrada: (1, 4096)
  • Salida: (1, 4096).
  • Dimensiones de los parámetros: (4096, 4096), incluidos 4096 nodos de salida y 4096 nodos de entrada.

18.Función de activación ReLU:

  • Entrada: (1, 4096)
  • Salida: (1, 4096). ReLU no cambia las dimensiones de la entrada.
  • Sin parámetros.

19. La tercera capa completamente conectada (nn.Linear (4096, num_classes)):

  • Entrada: (1, 4096)
  • Salida: (1, núm_clases). En este ejemplo, num_classes tiene como valor predeterminado 1000, por lo que las dimensiones de salida son (1, 1000).
  • Dimensiones de los parámetros: (1000, 4096), incluidos 1000 nodos de salida y 4096 nodos de entrada.

Ahora, calculemos la cantidad total de parámetros que deben aprenderse:

  • Primera capa convolucional: 64 * 3 * 11 * 11 = 23,296
  • Segunda capa convolucional: 192 * 64 * 5 * 5 = 307,200
  • Tercera capa convolucional: 384 * 192 * 3 * 3 = 663,552
  • Cuarta capa convolucional: 256 * 384 * 3 * 3 = 884,736
  • Quinta capa convolucional: 256 * 256 * 3 * 3 = 589,824
  • La primera capa completamente conectada: 4096 * 256 * 6 * 6 = 37,748,736
  • Segunda capa completamente conectada: 4096 * 4096 = 16,777,216
  • Tercera capa completamente conectada: 1000 * 4096 = 4.096.000

Sumando todos los parámetros obtenemos: 23,296 + 307,200 + 663,552 + 884,736 + 589,824 + 37,748,736 + 16,777,216 + 4,096,000 = 60,990,560.

Por lo tanto, toda la red AlexNet necesita aprender un total de 60.990.560 parámetros.

La capa Abandono (Abandono(p=0.5, inplace=False)) no cambia el tamaño de los parámetros. El abandono es una técnica de regularización que establece aleatoriamente la salida de algunas neuronas en cero durante el entrenamiento para evitar el sobreajuste. Durante las fases de prueba e inferencia, la capa de abandono no tiene ningún impacto en los datos de entrada. La capa de abandono no tiene parámetros de aprendizaje, por lo que no afectará la cantidad de parámetros que deben aprenderse.

2.3 Resumen

En esta sección, aprendimos sobre 3 contenedores de modelos diferentes: Sequential, ModuleList, ModuleDict y la construcción de AlexNet. En la próxima lección, aprenderemos el uso específico de la capa de red en nn.

3.nn capa de red: capa convolucional

En la lección anterior, aprendimos cómo construir un modelo de red neuronal en PyTorch, así como los contenedores comúnmente utilizados en el proceso de construcción de una red: Sequential, ModuleList y ModuleDict. Al comienzo de esta lección, aprenderemos sobre las capas de red comunes en PyTorch, ahora nos centraremos en la capa convolucional.

3.1 Convolución 1d/2d/3d

Operación de convolución (Convolución): el núcleo de convolución se desliza sobre la señal de entrada (imagen) y la multiplicación y suma se realizan en las posiciones correspondientes. Núcleo de convolución (Kernel): también conocido como filtro/filtro, puede considerarse como un determinado patrón/característica determinada.

El proceso de convolución es similar a usar una plantilla para encontrar áreas similares en la imagen: cuanto más similar sea al modo de núcleo de convolución, mayor será el valor de activación, logrando así la extracción de características. Entonces, en el aprendizaje profundo, podemos pensar en los núcleos de convolución como extractores de características.

Por favor agregue la descripción de la imagen.

La siguiente figura es una visualización del núcleo de convolución de AlexNet. Descubrimos que el núcleo de convolución en realidad aprende 边缘、条纹、色彩estos patrones detallados:

Insertar descripción de la imagen aquí

Esto verifica aún más que el núcleo de convolución esté basado en imágenes 某种特征提取器y que el modelo aprenda completamente los patrones de características específicas.

Dimensión de convolución (Dimensión): Generalmente , un núcleo de convolución se desliza a lo largo de varias dimensiones en una señal, que es una convolución de varias dimensiones.

3.1.1.1d convolución

Por favor agregue la descripción de la imagen.

3.1.2.2d convolución

Por favor agregue la descripción de la imagen.

3.1.3.3d convolución

Por favor agregue la descripción de la imagen.

Se puede ver que un núcleo de convolución que se desliza a lo largo de varias dimensiones en una señal es una convolución multidimensional. Tenga en cuenta que aquí enfatizamos un núcleo de convolución y una señal, porque generalmente involucraremos operaciones de convolución que involucran múltiples núcleos de convolución y múltiples señales. En este caso, ¿cómo juzgar la dimensión de la convolución? Aquí primero podemos pensar una vez.

3.2 Convolución-nn.Conv2d()

Función nn.Conv2d
: realiza convolución bidimensional en múltiples señales planas bidimensionales.

nn.Conv2d(
    in_channels,
    out_channels,
    kernel_size,
    stride=1,
    padding=0,
    dilation=1,
    groups=1,
    bias=True,
    padding_mode='zeros'
)

Los principales parámetros:

  • in_channels: número de canal de entrada.
  • out_channels: el número de canales de salida, equivalente al número de núcleos de convolución.
  • kernel_size: tamaño del kernel de convolución.
  • Zancada: longitud del paso. Aquí hay una convolución con un paso de 2:

Por favor agregue la descripción de la imagen.

  • relleno: número de rellenos. Comúnmente utilizado para mantener coincidentes los tamaños de las imágenes de entrada y salida, se puede utilizar para aumentar la resolución de la imagen de salida:

Por favor agregue la descripción de la imagen.

  • dilatación: tamaño de convolución dilatada. A menudo se utiliza en tareas de segmentación de imágenes para aumentar el campo receptivo, es decir, un píxel de la imagen de salida corresponde a un área más grande de la imagen de entrada:

Por favor agregue la descripción de la imagen.

  • grupos: el número de grupos para la convolución grupal. A menudo se utiliza para aligerar modelos. Por ejemplo, Alexnet utilizó dos conjuntos de operaciones de convolución debido a limitaciones de hardware en ese momento:

Insertar descripción de la imagen aquí

  • parcialidad: parcialidad. Es necesario agregar un término de compensación cuando se genera el valor de respuesta final.

Cálculo del tamaño de la convolución:

Insertar descripción de la imagen aquí

Ejemplo de código, aquí el canal de entrada es 3, el canal de salida es 1, el tamaño del núcleo de convolución es
el núcleo de convolución 3 × 3 nn.Conv2d (3, 1, 3) y el
método nn.init.xavier_normal () se utiliza para inicializar el peso del valor de la red. El código se muestra a continuación:

import os
import torch.nn as nn
from PIL import Image
from torchvision import transforms
from matplotlib import pyplot as plt
from tools.common_tools import transform_invert, set_seed

set_seed(3)  # 设置随机种子,用于调整卷积核权值的状态。

# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255

# convert to tensor
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W

# ========================= create convolution layer ==========================
conv_layer = nn.Conv2d(3, 1, 3)   # input:(i, o, size) weights:(o, i , h, w)
nn.init.xavier_normal_(conv_layer.weight.data)

# calculation
img_conv = conv_layer(img_tensor)

# =========================== visualization ==================================
print("卷积前尺寸:{}\n卷积后尺寸:{}".format(img_tensor.shape, img_conv.shape))
img_conv = transform_invert(img_conv[0, 0:1, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_conv, cmap='gray')
plt.subplot(121).imshow(img_raw)
plt.show()

Resultado de salida:

卷积前尺寸:torch.Size([1, 3, 512, 512])
卷积后尺寸:torch.Size([1, 1, 510, 510])
  • Salida cuando set.seed(1):

Insertar descripción de la imagen aquí

  • Salida cuando set.seed(2):

Insertar descripción de la imagen aquí

  • Salida cuando set.seed(3):

Insertar descripción de la imagen aquí

Se puede ver que la salida correspondiente a diferentes pesos del núcleo de convolución es diferente. Por lo general, configuramos varios núcleos de convolución en la capa convolucional para extraer diferentes características.

En el ejemplo anterior, utilizamos un núcleo de convolución tridimensional para implementar una convolución 2D:

Por favor agregue la descripción de la imagen.
Insertar descripción de la imagen aquí

Nuestra entrada es una imagen RGB 2D que contiene 3 canales de color. Luego, crearemos 3 núcleos de convolución bidimensionales, con diferentes canales correspondientes a diferentes núcleos de convolución. Agregamos los resultados de convolución de los tres canales y luego agregamos el término de sesgo para obtener el resultado de convolución final.

3.3 Convolución transpuesta-nn.ConvTranspose

转置卷积 (Transpose Convolution) 又称为 反卷积 (Deconvolution)Nota 1 o Convolución fraccionada, que se usa comúnmente en tareas de segmentación de imágenes y se usa principalmente para muestrear imágenes (UpSample).

(Nota 1: la deconvolución de la que estamos hablando aquí es diferente de la deconvolución en el sistema de señales).

¿Por qué se llama convolución transpuesta?

Convolución normal:
Por favor agregue la descripción de la imagen.

Supongamos que el tamaño de la imagen es 4 × 4 4 \times 44×4 , el núcleo de convolución es3 × 3 3 \times 33×3 , relleno= 0 =0=0 , zancada= 1 =1=1 .

  • Imagen: I 16 × 1 I_{16 \times 1}I16 × 1, donde 16 es el número total de píxeles de la imagen de entrada y 1 representa el número de imágenes.
    . Núcleo de convolución: K 4 × 16 K_{4 \times 16}k4 × 16, donde 4 es el número total de píxeles en la imagen de salida y 16 se obtiene sumando ceros a los 9 elementos del núcleo de convolución.
  • Ejemplo: O 4 × 1 = K 1 × 16 × I 16 × 1 O_{4 \times 1}=K_{1 \times 16} \times I_{16 \times 1}oh4 × 1=k1 × 16×I16 × 1

  • Convolución transpuesta : muestreo superior, la imagen de salida es más grande que la imagen de entrada.

Por favor agregue la descripción de la imagen.

Supongamos que el tamaño de la imagen es 2 × 2 2 \times 22×2 , el núcleo de convolución es3 × 3 3 \times 33×3 , relleno= 0 =0=0 , zancada= 1 =1=1 .

  • Imagen: I 4 × 1 I_{4 \times 1}I4 × 1, donde 4 es el número total de píxeles de la imagen de entrada y 1 representa el número de imágenes.
    . Núcleo de convolución: K 16 × 4 K_{16 \times 4}k16 × 4, donde 16 es el número total de píxeles de la imagen de salida y 4 se obtiene eliminando parte de los 9 elementos del núcleo de convolución.
    . Salida: O 16 × 1 = K 16 × 4 × I 4 × 1 O_{16 \times 1}=K_{16 \times 4} \times I_{4 \times 1}oh16 × 1=k16 × 4×I4 × 1

Se puede ver que los tamaños del núcleo de convolución de la convolución transpuesta y la convolución normal tienen forma transpuesta , por eso la llamamos convolución transpuesta. Tenga en cuenta que los dos solo se transponen en forma, pero sus pesos son completamente diferentes. En otras palabras, el proceso de convolución es irreversible, es decir, después de la convolución y luego la convolución transpuesta, la imagen resultante es completamente diferente de la imagen inicial.

  • nn.ConvTranspose2d

Función: Transponer convolución para lograr un muestreo superior.

nn.ConvTranspose2d(
    in_channels,
    out_channels,
    kernel_size,
    stride=1,
    padding=0,
    output_padding=0,
    groups=1,
    bias=True,
    dilation=1,
    padding_mode='zeros'
)

Los principales parámetros:

  • in_channels: número de canal de entrada.
  • out_channels: Número de canales de salida.
  • kernel_size: tamaño del kernel de convolución.
  • Zancada: longitud del paso.
  • relleno: número de rellenos.
  • dilatación: tamaño de convolución dilatada.
  • grupos: configuraciones de convolución agrupadas.
  • parcialidad: parcialidad.

Cálculo del tamaño:

  • Versión simplificada (sin relleno ni dilatación):

tamaño de salida = ( en tamaño − 1 ) × zancada + tamaño del núcleo \mathrm{out}_{\text{size}} = (\mathrm{in}_{\text{size}} -1)\times \mathrm{ zancada} + \mathrm{núcleo}_{\text{tamaño}}afueratamaño=( entamaño1 )×paso+núcleotamaño

  • versión completa:

H fuera = ( H adentro − 1 ) × zancada [ 0 ] − H_{\text{fuera}} = (H_{\text{in}}-1) \times \mathrm{zancada}[0] -hfuera=( hen1 )×zancada [ 0 ] 2 × relleno [ 0 ] + dilatación [ 0 ] × ( tamaño del núcleo [ 0 ] − 1 ) + salida _ relleno [ 0 ] + 1 2 \times \mathrm{padding}[0] + \mathrm{dilatación }[0] \times( \text{kernelsize}[0]-1) + \mathrm{output\_padding}[0]+ 12×relleno [ 0 ]+dilatación [ 0 ]×( tamaño del kernel [ 0 ]1 )+relleno_salida [ 0 ]+1
ejemplo de código:

import os
import torch.nn as nn
from PIL import Image
from torchvision import transforms
from matplotlib import pyplot as plt
from tools.common_tools import transform_invert, set_seed

set_seed(3)  # 设置随机种子,用于调整卷积核权值的状态。

# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255

# convert to tensor
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W

# ========================= create convolution layer ==========================
conv_layer = nn.ConvTranspose2d(3, 1, 3, stride=2)   # input:(i, o, size)
nn.init.xavier_normal_(conv_layer.weight.data)

# calculation
img_conv = conv_layer(img_tensor)

# =========================== visualization ==================================
print("卷积前尺寸:{}\n卷积后尺寸:{}".format(img_tensor.shape, img_conv.shape))
img_conv = transform_invert(img_conv[0, 0:1, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_conv, cmap='gray')
plt.subplot(121).imshow(img_raw)
plt.show()

Resultado de salida:

卷积前尺寸:torch.Size([1, 3, 512, 512])
卷积后尺寸:torch.Size([1, 1, 1025, 1025])

Insertar descripción de la imagen aquí

Se puede ver que después del muestreo ascendente de convolución transpuesta, la imagen tiene un fenómeno extraño: hay muchas cuadrículas en la imagen de salida. Esto se denomina 棋盘效应 (Checkerboard Artifacts)debido a la superposición desigual en las convoluciones transpuestas. Para obtener una explicación y una solución al efecto tablero de ajedrez, consulte el artículo Deconvolución y artefactos de tablero de ajedrez.

3.4 Resumen

En esta sección, aprendimos sobre la capa convolucional en el módulo nn. En la próxima lección, aprenderemos sobre otras capas de red comunes en el módulo nn.

4.nn capa de red: capa de agrupación, capa completamente conectada y capa de función de activación

En la sección anterior, aprendimos sobre la capa convolucional en la capa de red. En esta sección, continuaremos estudiando otras capas de red: capas de agrupación, capas lineales y capas de función de activación.

4.1 Capa de agrupación-Capa de agrupación

El papel de la agrupación se refleja en la reducción de resolución:

  • 保留显著特征、降低特征维度,增大 kernel 的感受野
  • Otro punto que vale la pena señalar: la agrupación también puede proporcionar cierta invariancia de rotación.

La capa de agrupación puede reducir la dimensionalidad de la información de características extraída. Por un lado, hace que el mapa de características sea más pequeño, simplificando la complejidad del cálculo de la red y evitando el sobreajuste hasta cierto punto; por otro lado, comprime las características y extrae las características. principales características.

Hay dos métodos: agrupación máxima y agrupación promedio.

Por favor agregue la descripción de la imagen.

Operación de agrupación (Pooling)收集 : Unión de señales 总结, similar a una piscina que recolecta recursos hídricos, de ahí el nombre de capa de agrupación.

  • "Coleccionar": más se vuelve menos.
  • "resumen": máximo/promedio.

Agrupación máxima versus agrupación promedio:

Insertar descripción de la imagen aquí

4.1.1.nn.MaxPool2d

Función: Realizar la máxima agrupación de señales bidimensionales (imágenes).

nn.MaxPool2d(
    kernel_size,
    stride=None,
    padding=0,
    dilation=1,
    return_indices=False,
    ceil_mode=False
)

Los principales parámetros:

  • kernel_size: tamaño del kernel de agrupación.
  • Zancada: longitud del paso. Tamaño del paso, generalmente consistente con kernel_size
  • relleno: número de rellenos. El ancho de relleno se utiliza principalmente para ajustar el tamaño del mapa de características de salida. Generalmente, después de configurar el relleno en un valor apropiado, los tamaños de las imágenes de entrada y salida permanecen sin cambios.
  • dilatación: tamaño del intervalo del núcleo de agrupación. Tamaño del intervalo de agrupación, el valor predeterminado es 1. A menudo se utiliza en tareas de segmentación de imágenes, principalmente para mejorar el campo receptivo.
  • ceil_mode: si el tamaño se redondea hacia arriba. Se utiliza para calcular el tamaño del mapa de características de salida; la configuración predeterminada es redondear hacia abajo. El valor predeterminado es Falso y el tamaño se redondea hacia abajo. Cuando es Verdadero, el tamaño se redondea hacia arriba.
  • return_indices: registra el índice de píxeles agrupados. Generalmente se usa cuando se desagrupa al máximo el muestreo ascendente. Cuando es Verdadero, devuelve el índice del píxel utilizado por la agrupación máxima. Los índices de estos registros generalmente se usan durante la agrupación anti-máxima. Al desagrupar un mapa de características pequeño en un mapa de características grande, ¿dónde se coloca cada píxel?

La siguiente figura (a)representa la desagrupación, (b)el muestreo ascendente (c)y la deconvolución.

Insertar descripción de la imagen aquí

Ejemplo de código:

import os
import torch
import torch.nn as nn
from torchvision import transforms
from matplotlib import pyplot as plt
from PIL import Image
from tools.common_tools import transform_invert, set_seed

set_seed(1)  # 设置随机种子

# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255

# convert to tensor
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W

# ========================== create maxpool layer =============================
maxpool_layer = nn.MaxPool2d((2, 2), stride=(2, 2))   # input:(i, o, size) weights:(o, i , h, w)
img_pool = maxpool_layer(img_tensor)

# ================================= visualization =============================
print("池化前尺寸:{}\n池化后尺寸:{}".format(img_tensor.shape, img_pool.shape))
img_pool = transform_invert(img_pool[0, 0:3, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_pool)
plt.subplot(121).imshow(img_raw)
plt.show()

Resultado de salida:


池化前尺寸:torch.Size([1, 3, 512, 512])
池化后尺寸:torch.Size([1, 3, 256, 256])

Insertar descripción de la imagen aquí

Se puede ver que el tamaño de la imagen después de la agrupación máxima se reduce a la mitad sin ninguna reducción significativa en la calidad de la imagen. Por lo tanto, la operación de agrupación puede eliminar información redundante en la imagen y reducir la cantidad de cálculos posteriores.

4.1.2.nn.AvgPool2d

Función: Realizar agrupación promedio de señales bidimensionales (imágenes).

nn.AvgPool2d(
    kernel_size,
    stride=None,
    padding=0,
    ceil_mode=False,
    count_include_pad=True,
    divisor_override=None
)

Los principales parámetros:

  • kernel_size: tamaño del kernel de agrupación.

  • Zancada: longitud del paso. Generalmente consistente con kernel_size

  • relleno: número de rellenos. El ancho de relleno se utiliza principalmente para ajustar el tamaño del mapa de características de salida. Generalmente, después de configurar el relleno en un valor apropiado, los tamaños de las imágenes de entrada y salida permanecen sin cambios.

  • dilatación: tamaño del intervalo de agrupación, el valor predeterminado es 1. A menudo se utiliza en tareas de segmentación de imágenes, principalmente para mejorar el campo receptivo.

  • ceil_mode: las dimensiones están redondeadas hacia arriba. El valor predeterminado es Falso y el tamaño se redondea hacia abajo. Cuando es Verdadero, el tamaño se redondea hacia arriba.

  • count_include_pad: si se debe utilizar el valor de relleno para el cálculo del promedio. Si se deben tener en cuenta los valores de relleno al calcular el promedio

  • divisor_override: factor de división. El número de píxeles se utiliza como denominador al calcular el promedio. Factor de división. Al calcular el promedio, el numerador es la suma de los valores de píxeles y el denominador es el número predeterminado de valores de píxeles. Si se establece divisor_override, cambie el denominador a divisor_override.

Ejemplo de código:

import os
import torch
import torch.nn as nn
from torchvision import transforms
from matplotlib import pyplot as plt
from PIL import Image
from tools.common_tools import transform_invert, set_seed

set_seed(1)  # 设置随机种子

# ================================= load img ==================================
path_img = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lena.png")
img = Image.open(path_img).convert('RGB')  # 0~255

# convert to tensor
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
img_tensor.unsqueeze_(dim=0)    # C*H*W to B*C*H*W

# ========================== create avgpool layer =============================
avgpoollayer = nn.AvgPool2d((2, 2), stride=(2, 2))   # input:(i, o, size) weights:(o, i , h, w)
img_pool = avgpoollayer(img_tensor)

# =============================== visualization ===============================
print("池化前尺寸:{}\n池化后尺寸:{}".format(img_tensor.shape, img_pool.shape))
img_pool = transform_invert(img_pool[0, 0:3, ...], img_transform)
img_raw = transform_invert(img_tensor.squeeze(), img_transform)
plt.subplot(122).imshow(img_pool)
plt.subplot(121).imshow(img_raw)
plt.show()

Resultado de salida:

池化前尺寸:torch.Size([1, 3, 512, 512])
池化后尺寸:torch.Size([1, 3, 256, 256])

Insertar descripción de la imagen aquí

Nuevamente, el tamaño de la imagen se reduce a la mitad sin ninguna pérdida notable de calidad. Además, si comparamos cuidadosamente los resultados de la agrupación máxima y la agrupación promedio, podemos encontrar que la imagen después de la agrupación máxima será más clara, mientras que la imagen después de la agrupación promedio será más oscura, esto se debe a que las dos operaciones de agrupación usan Causado por diferentes métodos de cálculo (cuanto mayor sea el valor de píxel, más brillante será la imagen).

4.1.3.Uso de divisor_override

Ahora, veamos el uso de factores de división. Aquí inicializamos un 4 ∗ 4 4*444 imágenes y use un2 ∗ 2 2*222 ventana, el tamaño del paso se establece en 2.

Agrupación promedio normal:

img_tensor = torch.ones((1, 1, 4, 4))
avgpool_layer = nn.AvgPool2d((2, 2), stride=(2, 2))
img_pool = avgpool_layer(img_tensor)

print("raw_img:\n{}\npooling_img:\n{}".format(img_tensor, img_pool))

Resultado de salida:

raw_img:
tensor([[[[1., 1., 1., 1.],
          [1., 1., 1., 1.],
          [1., 1., 1., 1.],
          [1., 1., 1., 1.]]]])
pooling_img:
tensor([[[[1., 1.],
          [1., 1.]]]])

Calcule el valor de píxel agrupado:

1 + 1 + 1 + 1 4 = 1 \frac{1+1+1+1}{4}=141+1+1+1=1

Agrupación promedio con divisor_override=3:

img_tensor = torch.ones((1, 1, 4, 4))
avgpool_layer = nn.AvgPool2d((2, 2), stride=(2, 2), divisor_override=3)
img_pool = avgpool_layer(img_tensor)

print("raw_img:\n{}\npooling_img:\n{}".format(img_tensor, img_pool))

Resultado de salida:

raw_img:
tensor([[[[1., 1., 1., 1.],
          [1., 1., 1., 1.],
          [1., 1., 1., 1.],
          [1., 1., 1., 1.]]]])
pooling_img:
tensor([[[[1.3333, 1.3333],
          [1.3333, 1.3333]]]])

Calcule el valor de píxel agrupado:

1 + 1 + 1 + 1 3 = 1.3333 \frac{1+1+1+1}{3}=1.333331+1+1+1=1.3333

Hasta ahora, hemos aprendido sobre la agrupación máxima y la agrupación promedio, que son procesos de reducción de resolución de imágenes, es decir, ingresar imágenes de mayor tamaño y generar imágenes de menor tamaño. A continuación aprenderemos sobre la desagrupación, que convierte imágenes de pequeño tamaño en imágenes de gran tamaño.

4.1.4.nn.MaxUnpool2d

Función: Máximo muestreo ascendente de desagrupación de señales bidimensionales (imágenes).

nn.MaxUnpool2d(
    kernel_size,
    stride=None,
    padding=0
)

forward(self, input, indices, output_size=None)

Los principales parámetros:

  • kernel_size: tamaño del kernel de agrupación.
  • Zancada: longitud del paso. Tamaño del paso, generalmente consistente con kernel_size
  • relleno: número de rellenos.

Desagrupación máxima:

Insertar descripción de la imagen aquí

Los primeros codificadores automáticos y las tareas de segmentación de imágenes implicaban operaciones de muestreo ascendente. El método comúnmente utilizado en ese momento era el muestreo ascendente de desagrupación máxima. La mitad izquierda de la imagen de arriba es el proceso de agrupación máxima, el original 4 × 4 4 \times 44×Después de agrupar al máximo 4 imágenes, obtenemos2 × 2 2 \times 22×2 , y luego de pasar por una serie de capas de red, ingresa al decodificador de muestreo ascendente en la mitad derecha de la figura anterior, es decir, se aumenta el muestreo de una imagen más pequeña para obtener una imagen más grande. Llegados a este punto, una de las preguntas que surgen es: ¿dónde debemos colocar los valores de los píxeles? Por ejemplo:2 × 2 2 \times 22×2 El 3 en la esquina superior izquierda de la imagen debe colocarse en el4 × 4 4 \times 4 final.4׿Cuál de los 4 píxeles en la parte superior izquierda de la imagen 4 ? En este momento, podemos usar el índice de píxeles agrupados registrado durante el proceso de agrupación máxima anterior para colocar 3 en el original anterior 4 × 4 4 \times44×4La posición correspondiente al valor máximo entre los 4 píxeles en la esquina superior izquierda de la imagen.

Ejemplo de código:

# pooling
img_tensor = torch.randint(high=5, size=(1, 1, 4, 4), dtype=torch.float)
maxpool_layer = nn.MaxPool2d((2, 2), stride=(2, 2), return_indices=True)
img_pool, indices = maxpool_layer(img_tensor)

# unpooling
img_reconstruct = torch.randn_like(img_pool, dtype=torch.float)
maxunpool_layer = nn.MaxUnpool2d((2, 2), stride=(2, 2))
img_unpool = maxunpool_layer(img_reconstruct, indices)

print("raw_img:\n{}\nimg_pool:\n{}".format(img_tensor, img_pool))
print("img_reconstruct:\n{}\nimg_unpool:\n{}".format(img_reconstruct, img_unpool))

Resultado de salida:

raw_img:
tensor([[[[0., 4., 4., 3.],
          [3., 3., 1., 1.],
          [4., 2., 3., 4.],
          [1., 3., 3., 0.]]]])
img_pool:
tensor([[[[4., 4.],
          [4., 4.]]]])
img_reconstruct:
tensor([[[[-1.0276, -0.5631],
          [-0.8923, -0.0583]]]])
img_unpool:
tensor([[[[ 0.0000, -1.0276, -0.5631,  0.0000],
          [ 0.0000,  0.0000,  0.0000,  0.0000],
          [-0.8923,  0.0000,  0.0000, -0.0583],
          [ 0.0000,  0.0000,  0.0000,  0.0000]]]])

Aquí inicializamos un 4 × 4 4 \times 44×4 imágenes y use un2 × 2 2 \times 22×Una ventana de 2 , con el tamaño del paso establecido en 2. Primero, realizamos una agrupación máxima y registramos el índice del píxel más grande. Luego, realizamos la desagrupación, donde la entrada de la desagrupación es la misma que el tamaño de la imagen obtenida después de la agrupación máxima anterior, y la ventana y el tamaño de paso de la capa de desagrupación son consistentes con la capa de agrupación máxima anterior. Finalmente, pasamos la entrada y el índice a la capa de separación, lo que da como resultado una imagen con las mismas dimensiones que la imagen original.

4.2 Capa lineal-Capa lineal

La capa lineal también se llama capa completamente conectada , en la que cada neurona está conectada a todas las neuronas de la capa anterior para lograr una combinación lineal/transformación lineal de la capa anterior.

Al clasificar una red neuronal convolucional, generalmente usamos una capa completamente conectada para procesar las características antes de la salida. En PyTorch, la capa completamente conectada también se llama capa lineal, porque si no se considera la naturaleza no lineal de la función de activación, entonces la capa completamente conectada La capa conectada es una combinación lineal de los datos de entrada.

Insertar descripción de la imagen aquí

Cada neurona está conectada a todas las neuronas de la capa anterior y el método de cálculo de cada neurona es un proceso de suma ponderada de la capa anterior. Por lo tanto, las capas lineales se pueden implementar mediante la multiplicación de matrices. Tenga en cuenta que hemos ignorado temporalmente el término de sesgo en la figura anterior.

nn.Función lineal
: Combinación lineal de señales unidimensionales (vectores).

nn.Linear(in_features, out_features, bias=True)

Los principales parámetros:

  • in_features: ingrese el número de nodos.
  • out_features: Número de nodos de salida.
  • sesgo: si se requiere sesgo.

Fórmula de cálculo:

y = x PESO + por = xW^T + by=x anchot+b

Ejemplo de código:

inputs = torch.tensor([[1., 2, 3]])
linear_layer = nn.Linear(3, 4)
linear_layer.weight.data = torch.tensor([[1., 1., 1.],
                                         [2., 2., 2.],
                                         [3., 3., 3.],
                                         [4., 4., 4.]])
linear_layer.bias.data.fill_(0.5)
output = linear_layer(inputs)

print(inputs, inputs.shape)
print(linear_layer.weight.data, linear_layer.weight.data.shape)
print(output, output.shape)

Resultado de salida:

tensor([[1., 2., 3.]]) torch.Size([1, 3])
tensor([[1., 1., 1.],
        [2., 2., 2.],
        [3., 3., 3.],
        [4., 4., 4.]]) torch.Size([4, 3])
tensor([[ 6.5000, 12.5000, 18.5000, 24.5000]], grad_fn=<AddmmBackward0>) torch.Size([1, 4])

4.3 Capa de función de activación-Capa de activación

La función de activación (función de activación) realiza transformaciones no lineales en características y le da un significado especial a las redes neuronales multicapa 深度.

Si no hay transformación no lineal, debido a la asociatividad de la multiplicación de matrices, la combinación de múltiples capas lineales es equivalente a una capa lineal:

Insertar descripción de la imagen aquí

En el último paso anterior, debido a la asociatividad de la multiplicación de matrices, primero podemos combinar y multiplicar las tres matrices de peso de la derecha para obtener una matriz de peso grande W. Entonces podemos ver que nuestra salida es en realidad la entrada X multiplicada por una matriz de peso grande W.

Por lo tanto, la capa lineal completamente conectada de tres capas aquí es en realidad equivalente a una capa completamente conectada de una capa, esto se debe a la combinación de la multiplicación de matrices en operaciones lineales, y no introducimos una función de activación no lineal aquí. Si se agrega una función de activación no lineal, esta conclusión ya no será cierta, por lo que decimos que la función de activación le da a la red neuronal multicapa un significado profundo.

4.3.1.nn.Sigmoideo

Insertar descripción de la imagen aquí

Fórmula de cálculo:
y = 1 1 + e − xy=\frac{1}{1+e^{-x}}y=1+mi−x _1
梯度公式:
y ′ = y ∗ ( 1 − y ) y^{\prime}=y *(1-y) y=y( 1y )
Características:

  • El valor de salida está en (0, 1) (0,1)( 0 ,1 ) , consistente con propiedades probabilísticas.
  • El rango de derivada es [0, 0,25] [0,0,25][ 0 ,0,25 ] , lo que fácilmente conduce a la desaparición del gradiente.
  • La salida es una media distinta de cero, lo que destruirá la distribución de datos.
4.3.2.nn.tanh

Insertar descripción de la imagen aquí
Ejemplos:
y = sin ⁡ x cos ⁡ x = ex − e − xex + e − x = 2 1 + e − 2 x + 1 y=\frac{\sin x}{\cos x}=\frac{e ^xe^{-x}}{e^x+e^{-x}}=\frac{2}{1+e^{-2 x}}+1y=porqueXpecadox=miX+mi−x _miXmi−x _=1+mi2 x2+1Fórmula del gradiente
:
y ′ = 1 − y 2 y^{\prime}=1-y^2y=1y2
características:

  • El valor de salida está en ( − 1 , 1 ) (-1,1)( -1 , _1 ) , los datos se ajustan a la media 0.
  • El rango de derivada es (0, 1) (0,1)( 0 ,1 ) , es fácil hacer que el gradiente desaparezca.
4.3.3.nn.ReLU

Insertar descripción de la imagen aquí

Fórmula de cálculo:
y = max ⁡ ( 0 , x ) y=\max (0, x)y=máximo ( 0 ,x )
梯度公式:
y ′ = { 1 , x > 0 indefinido, , x = 0 0 , x < 0 y^{\prime}= \begin{cases}1, & x>0 \\ \text { indefinido, }, & x=0 \\ 0, & x<0\end{casos}y= 1 , indefinido,  ,0 ,X>0X=0X<0
característica:

  • Los valores de salida son todos positivos y el semieje negativo da como resultado neuronas muertas.
  • La derivada es 1, que puede aliviar la desaparición del gradiente, pero puede provocar fácilmente una explosión del gradiente.

Para resolver el problema de las neuronas muertas en el semieje negativo de la función de activación ReLU, existen varias formas de mejorarlo:

Insertar descripción de la imagen aquí

  • nn.LeakyReLU

    • pendiente_negativa: Pendiente del semieje negativo. Curva azul, pendiente muy pequeña.
  • nn.PReLU

    • init: puede aprender la pendiente.
  • nn.RReLU

    • inferior: límite inferior de distribución uniforme.
    • superior: límite superior de distribución uniforme.

4.4 Resumen

En esta sección, aprendimos sobre la capa de agrupación, la capa lineal y la capa de función de activación en el módulo nn. En la capa de agrupación, hay agrupación máxima normal, agrupación media y antiagrupación comúnmente utilizadas en tareas de segmentación de imágenes: MaxUnpool; en la función de activación, aprendimos Sigmoid, Tanh y Relu, así como varias variantes de Relu como LeakyReLU, PRELU, RReLU. En la siguiente sección, aprenderemos sobre la inicialización de los pesos de la capa de red.

Supongo que te gusta

Origin blog.csdn.net/fb_941219/article/details/130095702
Recomendado
Clasificación