Actualizado en Pytorch 1.5
Puedes encontrar el código aquí.
Pytorch es un marco de aprendizaje profundo de código abierto que proporciona una forma inteligente de crear modelos de aprendizaje automático. Incluso si la documentación está bien hecha, sigo viendo que la mayoría de las personas no escriben código bien organizado en PyTorch.
Hoy veremos cómo utilizar los tres componentes principales de PyTorch: Module, Sequential and ModuleList
. Comenzaremos con un ejemplo y lo mejoraremos iterativamente.
Todas estas cuatro clases están contenidas en torch.nn
import torch . nn as nn
# nn.Module
# nn.Sequential
# nn.Module
El Módulo es el bloque de construcción principal, define la clase base para todas las redes neuronales y DEBE subclasificarla.
Creemos un clasificador CNN clásico como ejemplo:
import torch . nn . functional as F
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , n_classes ):
super (). __init__ ()
self . conv1 = nn . Conv2d ( in_c , 32 , kernel_size = 3 , stride = 1 , padding = 1 )
self . bn1 = nn . BatchNorm2d ( 32 )
self . conv2 = nn . Conv2d ( 32 , 64 , kernel_size = 3 , stride = 1 , padding = 1 )
self . bn2 = nn . BatchNorm2d ( 64 )
self . fc1 = nn . Linear ( 64 * 28 * 28 , 1024 )
self . fc2 = nn . Linear ( 1024 , n_classes )
def forward ( self , x ):
x = self . conv1 ( x )
x = self . bn1 ( x )
x = F . relu ( x )
x = self . conv2 ( x )
x = self . bn2 ( x )
x = F . relu ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . fc1 ( x )
x = F . sigmoid ( x )
x = self . fc2 ( x )
return x
model = MyCNNClassifier ( 1 , 10 )
print ( model )
MyCNNClassifier(
(conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(fc1): Linear(in_features=50176, out_features=1024, bias=True)
(fc2): Linear(in_features=1024, out_features=10, bias=True)
)
Este es un clasificador muy simple con una parte de codificación que usa dos capas con 3x3 convs + batchnorm + relu y una parte de decodificación con dos capas lineales. Si no es nuevo en PyTorch, es posible que haya visto este tipo de codificación antes, pero existen dos problemas.
Si queremos agregar una capa, tenemos que volver a escribir mucho código en __init__
y en la función forward
. Además, si tenemos algún bloque común que queremos usar en otro modelo, por ejemplo, 3x3 conv + batchnorm + relu, tenemos que escribirlo nuevamente.
Sequential es un contenedor de módulos que se pueden apilar y ejecutar al mismo tiempo.
Puedes notar que tenemos que almacenar todo en self
. Podemos usar Sequential
para mejorar nuestro código.
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , n_classes ):
super (). __init__ ()
self . conv_block1 = nn . Sequential (
nn . Conv2d ( in_c , 32 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn . BatchNorm2d ( 32 ),
nn . ReLU ()
)
self . conv_block2 = nn . Sequential (
nn . Conv2d ( 32 , 64 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn . BatchNorm2d ( 64 ),
nn . ReLU ()
)
self . decoder = nn . Sequential (
nn . Linear ( 64 * 28 * 28 , 1024 ),
nn . Sigmoid (),
nn . Linear ( 1024 , n_classes )
)
def forward ( self , x ):
x = self . conv_block1 ( x )
x = self . conv_block2 ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , 10 )
print ( model )
MyCNNClassifier(
(conv_block1): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(conv_block2): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(decoder): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
(2): Linear(in_features=1024, out_features=10, bias=True)
)
)
Mucho mejor ¿eh?
¿Notaste que conv_block1
y conv_block2
se ven casi iguales? ¡Podríamos crear una función que devuelva un nn.Sequential
para incluso simplificar el código!
def conv_block ( in_f , out_f , * args , ** kwargs ):
return nn . Sequential (
nn . Conv2d ( in_f , out_f , * args , ** kwargs ),
nn . BatchNorm2d ( out_f ),
nn . ReLU ()
)
Entonces podemos simplemente llamar a esta función en nuestro Módulo
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , n_classes ):
super (). __init__ ()
self . conv_block1 = conv_block ( in_c , 32 , kernel_size = 3 , padding = 1 )
self . conv_block2 = conv_block ( 32 , 64 , kernel_size = 3 , padding = 1 )
self . decoder = nn . Sequential (
nn . Linear ( 64 * 28 * 28 , 1024 ),
nn . Sigmoid (),
nn . Linear ( 1024 , n_classes )
)
def forward ( self , x ):
x = self . conv_block1 ( x )
x = self . conv_block2 ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , 10 )
print ( model )
MyCNNClassifier(
(conv_block1): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(conv_block2): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(decoder): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
(2): Linear(in_features=1024, out_features=10, bias=True)
)
)
¡Aún más limpio! ¡Aún así conv_block1
y conv_block2
son casi iguales! Podemos fusionarlos usando nn.Sequential
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , n_classes ):
super (). __init__ ()
self . encoder = nn . Sequential (
conv_block ( in_c , 32 , kernel_size = 3 , padding = 1 ),
conv_block ( 32 , 64 , kernel_size = 3 , padding = 1 )
)
self . decoder = nn . Sequential (
nn . Linear ( 64 * 28 * 28 , 1024 ),
nn . Sigmoid (),
nn . Linear ( 1024 , n_classes )
)
def forward ( self , x ):
x = self . encoder ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , 10 )
print ( model )
MyCNNClassifier(
(encoder): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
(decoder): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
(2): Linear(in_features=1024, out_features=10, bias=True)
)
)
self.encoder
ahora contiene el stand conv_block
. Hemos desacoplado la lógica de nuestro modelo y la hacemos más fácil de leer y reutilizar. Nuestra función conv_block
se puede importar y utilizar en otro modelo.
¿Qué pasa si podemos agregar nuevas capas en self.encoder
, codificarlas no es conveniente?
self . encoder = nn . Sequential (
conv_block ( in_c , 32 , kernel_size = 3 , padding = 1 ),
conv_block ( 32 , 64 , kernel_size = 3 , padding = 1 ),
conv_block ( 64 , 128 , kernel_size = 3 , padding = 1 ),
conv_block ( 128 , 256 , kernel_size = 3 , padding = 1 ),
)
¿Sería bueno si pudiéramos definir los tamaños como una matriz y crear automáticamente todas las capas sin escribir cada una de ellas? Afortunadamente podemos crear una matriz y pasarla a Sequential
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , n_classes ):
super (). __init__ ()
self . enc_sizes = [ in_c , 32 , 64 ]
conv_blocks = [ conv_block ( in_f , out_f , kernel_size = 3 , padding = 1 )
for in_f , out_f in zip ( self . enc_sizes , self . enc_sizes [ 1 :])]
self . encoder = nn . Sequential ( * conv_blocks )
self . decoder = nn . Sequential (
nn . Linear ( 64 * 28 * 28 , 1024 ),
nn . Sigmoid (),
nn . Linear ( 1024 , n_classes )
)
def forward ( self , x ):
x = self . encoder ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , 10 )
print ( model )
MyCNNClassifier(
(encoder): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
(decoder): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
(2): Linear(in_features=1024, out_features=10, bias=True)
)
)
Analicémoslo. Creamos una matriz self.enc_sizes
que contiene los tamaños de nuestro codificador. Luego creamos una matriz conv_blocks
iterando los tamaños. Como tenemos que darle a la cabina un tamaño grande y uno grande para cada capa, zip
la matriz de tamaños consigo misma moviéndola en uno.
Para que quede claro, eche un vistazo al siguiente ejemplo:
sizes = [ 1 , 32 , 64 ]
for in_f , out_f in zip ( sizes , sizes [ 1 :]):
print ( in_f , out_f )
1 32
32 64
Luego, como Sequential
no acepta una lista, la descomponemos usando el operador *
.
¡Tada! Ahora, si solo queremos agregar un tamaño, podemos agregar fácilmente un nuevo número a la lista. Es una práctica común convertir el tamaño en un parámetro.
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , enc_sizes , n_classes ):
super (). __init__ ()
self . enc_sizes = [ in_c , * enc_sizes ]
conv_blocks = [ conv_block ( in_f , out_f , kernel_size = 3 , padding = 1 )
for in_f , out_f in zip ( self . enc_sizes , self . enc_sizes [ 1 :])]
self . encoder = nn . Sequential ( * conv_blocks )
self . decoder = nn . Sequential (
nn . Linear ( 64 * 28 * 28 , 1024 ),
nn . Sigmoid (),
nn . Linear ( 1024 , n_classes )
)
def forward ( self , x ):
x = self . encoder ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , [ 32 , 64 , 128 ], 10 )
print ( model )
MyCNNClassifier(
(encoder): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(2): Sequential(
(0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
(decoder): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
(2): Linear(in_features=1024, out_features=10, bias=True)
)
)
Podemos hacer lo mismo con la parte del decodificador.
def dec_block ( in_f , out_f ):
return nn . Sequential (
nn . Linear ( in_f , out_f ),
nn . Sigmoid ()
)
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , enc_sizes , dec_sizes , n_classes ):
super (). __init__ ()
self . enc_sizes = [ in_c , * enc_sizes ]
self . dec_sizes = [ 64 * 28 * 28 , * dec_sizes ]
conv_blocks = [ conv_block ( in_f , out_f , kernel_size = 3 , padding = 1 )
for in_f , out_f in zip ( self . enc_sizes , self . enc_sizes [ 1 :])]
self . encoder = nn . Sequential ( * conv_blocks )
dec_blocks = [ dec_block ( in_f , out_f )
for in_f , out_f in zip ( self . dec_sizes , self . dec_sizes [ 1 :])]
self . decoder = nn . Sequential ( * dec_blocks )
self . last = nn . Linear ( self . dec_sizes [ - 1 ], n_classes )
def forward ( self , x ):
x = self . encoder ( x )
x = x . view ( x . size ( 0 ), - 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , [ 32 , 64 ], [ 1024 , 512 ], 10 )
print ( model )
MyCNNClassifier(
(encoder): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
(decoder): Sequential(
(0): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
)
(1): Sequential(
(0): Linear(in_features=1024, out_features=512, bias=True)
(1): Sigmoid()
)
)
(last): Linear(in_features=512, out_features=10, bias=True)
)
Seguimos el mismo patrón, creamos un nuevo bloque para la parte de decodificación, lineal + sigmoide, y le pasamos un array con los tamaños. Tuvimos que agregar un self.last
ya que no queremos activar la salida.
¡Ahora incluso podemos dividir nuestro modelo en dos! Codificador + Decodificador
class MyEncoder ( nn . Module ):
def __init__ ( self , enc_sizes ):
super (). __init__ ()
self . conv_blocks = nn . Sequential ( * [ conv_block ( in_f , out_f , kernel_size = 3 , padding = 1 )
for in_f , out_f in zip ( enc_sizes , enc_sizes [ 1 :])])
def forward ( self , x ):
return self . conv_blocks ( x )
class MyDecoder ( nn . Module ):
def __init__ ( self , dec_sizes , n_classes ):
super (). __init__ ()
self . dec_blocks = nn . Sequential ( * [ dec_block ( in_f , out_f )
for in_f , out_f in zip ( dec_sizes , dec_sizes [ 1 :])])
self . last = nn . Linear ( dec_sizes [ - 1 ], n_classes )
def forward ( self , x ):
return self . dec_blocks ()
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , enc_sizes , dec_sizes , n_classes ):
super (). __init__ ()
self . enc_sizes = [ in_c , * enc_sizes ]
self . dec_sizes = [ self . enc_sizes [ - 1 ] * 28 * 28 , * dec_sizes ]
self . encoder = MyEncoder ( self . enc_sizes )
self . decoder = MyDecoder ( self . dec_sizes , n_classes )
def forward ( self , x ):
x = self . encoder ( x )
x = x . flatten ( 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , [ 32 , 64 ], [ 1024 , 512 ], 10 )
print ( model )
MyCNNClassifier(
(encoder): MyEncoder(
(conv_blocks): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
)
(decoder): MyDecoder(
(dec_blocks): Sequential(
(0): Sequential(
(0): Linear(in_features=50176, out_features=1024, bias=True)
(1): Sigmoid()
)
(1): Sequential(
(0): Linear(in_features=1024, out_features=512, bias=True)
(1): Sigmoid()
)
)
(last): Linear(in_features=512, out_features=10, bias=True)
)
)
Tenga en cuenta que MyEncoder
y MyDecoder
también podrían ser funciones que devuelvan un nn.Sequential
. Prefiero usar el primer patrón para modelos y el segundo para bloques de construcción.
Al dividir nuestro módulo en submódulos, es más fácil compartir el código, depurarlo y probarlo .
ModuleList
le permite almacenar Module
como una lista. Puede ser útil cuando necesita iterar a través de una capa y almacenar/usar cierta información, como en U-net.
La principal diferencia entre Sequential
es que ModuleList
no tiene un método forward
, por lo que las capas internas no están conectadas. Suponiendo que necesitamos cada salida de cada capa en el decodificador, podemos almacenarla de la siguiente manera:
class MyModule ( nn . Module ):
def __init__ ( self , sizes ):
super (). __init__ ()
self . layers = nn . ModuleList ([ nn . Linear ( in_f , out_f ) for in_f , out_f in zip ( sizes , sizes [ 1 :])])
self . trace = []
def forward ( self , x ):
for layer in self . layers :
x = layer ( x )
self . trace . append ( x )
return x
model = MyModule ([ 1 , 16 , 32 ])
import torch
model ( torch . rand (( 4 , 1 )))
[ print ( trace . shape ) for trace in model . trace ]
torch.Size([4, 16])
torch.Size([4, 32])
[None, None]
¿Qué pasa si queremos cambiar a LearkyRelu
en nuestro conv_block
? Podemos usar ModuleDict
para crear un diccionario de Module
y cambiar dinámicamente Module
cuando queramos
def conv_block ( in_f , out_f , activation = 'relu' , * args , ** kwargs ):
activations = nn . ModuleDict ([
[ 'lrelu' , nn . LeakyReLU ()],
[ 'relu' , nn . ReLU ()]
])
return nn . Sequential (
nn . Conv2d ( in_f , out_f , * args , ** kwargs ),
nn . BatchNorm2d ( out_f ),
activations [ activation ]
)
print ( conv_block ( 1 , 32 , 'lrelu' , kernel_size = 3 , padding = 1 ))
print ( conv_block ( 1 , 32 , 'relu' , kernel_size = 3 , padding = 1 ))
Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): LeakyReLU(negative_slope=0.01)
)
Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
¡Terminemos con todo!
def conv_block ( in_f , out_f , activation = 'relu' , * args , ** kwargs ):
activations = nn . ModuleDict ([
[ 'lrelu' , nn . LeakyReLU ()],
[ 'relu' , nn . ReLU ()]
])
return nn . Sequential (
nn . Conv2d ( in_f , out_f , * args , ** kwargs ),
nn . BatchNorm2d ( out_f ),
activations [ activation ]
)
def dec_block ( in_f , out_f ):
return nn . Sequential (
nn . Linear ( in_f , out_f ),
nn . Sigmoid ()
)
class MyEncoder ( nn . Module ):
def __init__ ( self , enc_sizes , * args , ** kwargs ):
super (). __init__ ()
self . conv_blocks = nn . Sequential ( * [ conv_block ( in_f , out_f , kernel_size = 3 , padding = 1 , * args , ** kwargs )
for in_f , out_f in zip ( enc_sizes , enc_sizes [ 1 :])])
def forward ( self , x ):
return self . conv_blocks ( x )
class MyDecoder ( nn . Module ):
def __init__ ( self , dec_sizes , n_classes ):
super (). __init__ ()
self . dec_blocks = nn . Sequential ( * [ dec_block ( in_f , out_f )
for in_f , out_f in zip ( dec_sizes , dec_sizes [ 1 :])])
self . last = nn . Linear ( dec_sizes [ - 1 ], n_classes )
def forward ( self , x ):
return self . dec_blocks ()
class MyCNNClassifier ( nn . Module ):
def __init__ ( self , in_c , enc_sizes , dec_sizes , n_classes , activation = 'relu' ):
super (). __init__ ()
self . enc_sizes = [ in_c , * enc_sizes ]
self . dec_sizes = [ 32 * 28 * 28 , * dec_sizes ]
self . encoder = MyEncoder ( self . enc_sizes , activation = activation )
self . decoder = MyDecoder ( dec_sizes , n_classes )
def forward ( self , x ):
x = self . encoder ( x )
x = x . flatten ( 1 ) # flat
x = self . decoder ( x )
return x
model = MyCNNClassifier ( 1 , [ 32 , 64 ], [ 1024 , 512 ], 10 , activation = 'lrelu' )
print ( model )
MyCNNClassifier(
(encoder): MyEncoder(
(conv_blocks): Sequential(
(0): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): LeakyReLU(negative_slope=0.01)
)
(1): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): LeakyReLU(negative_slope=0.01)
)
)
)
(decoder): MyDecoder(
(dec_blocks): Sequential(
(0): Sequential(
(0): Linear(in_features=1024, out_features=512, bias=True)
(1): Sigmoid()
)
)
(last): Linear(in_features=512, out_features=10, bias=True)
)
)
Entonces, en resumen.
Module
cuando tenga un bloque grande compuesto por varios bloques más pequeñosSequential
cuando desee crear un pequeño bloque a partir de capasModuleList
cuando necesite iterar a través de algunas capas o bloques de construcción y hacer algoModuleDict
cuando necesite parametrizar algunos bloques de su modelo, por ejemplo una función de activación¡Eso es todo amigos!
gracias por leer