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:
Hemos completado el estudio del módulo de datos en las lecciones anteriores y ahora comenzamos a estudiar el módulo de modelo.
Repasemos la red LeNet que utilizamos en el ejemplo anterior de clasificación de RMB:
Diagrama de estructura del modelo LeNet:
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.Module
la 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:
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*3
tensor 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*3
realiza una operación de convolución en un tensor para obtener un 28*28*6
tensor 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.
A continuación, aprenderemos cómo construir un modelo a través del ejemplo anterior de clasificación RMB dos. Tomando lenet.py
LeNet como ejemplo, la herencia nn.Module
debe 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.py
la 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.
Todos los modelos de red se heredan nn.Module
y 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:
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.Module
Hay 8 atributos que se utilizan para administrar todo el modelo, todos los cuales son OrderDict
(diccionarios ordenados). En el método LeNet
de __init__()
la clase principal, se llamará al método de nn.Module
para __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.
Veamos cómo se agrega cada submódulo a _modules
las propiedades de LeNet. Por self.conv1 = nn.Conv2d(3, 6, 5)
ejemplo, cuando corremos hasta esta línea, primero Step Into ingresa Conv2d
a la estructura y luego Step Out. Haga clic derecho Evaluate Expression
para ver nn.Conv2d(3, 6, 5)
propiedades.
Como se mencionó anteriormente, Conv2d
también es un módulo. Los atributos internos _modules
están vacíos. _parameters
Los 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.
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.Module
que interceptará todas las operaciones de asignación de atributos de clase ( self.conv1
atributos 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).
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
.
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.
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.
features
Los submódulos son los siguientes y cada capa de red utiliza el número de serie como clave :
Al realizar la propagación hacia adelante, forward()
se ingresará la función de LeNet y Sequetial
se llamará primero al primer contenedor: self.features
Dado que self.features
tambié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.ModuleDict
Es nn.Module
un 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 .
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 PyTorch
la biblioteca de visión por computadora torchvision.models
, se utiliza nn.Sequential
para 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.
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:
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
3.1.2.2d convolución
3.1.3.3d convolución
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:
- 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:
- 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:
- 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:
- 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:
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):
- Salida cuando set.seed(2):
- Salida cuando set.seed(3):
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:
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:
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.
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ño−1 )×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=( hen−1 )×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])
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.
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:
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.
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])
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])
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*44∗4 imágenes y use un2 ∗ 2 2*22∗2 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:
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.
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:
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
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∗( 1−y )
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
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 _miX−mi−x _=1+mi− 2 x2+1Fórmula del gradiente
:
y ′ = 1 − y 2 y^{\prime}=1-y^2y′=1−y2
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
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:
-
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.