You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.8 KiB
132 lines
4.8 KiB
import paddle
|
|
import inspect
|
|
import logging
|
|
import operator
|
|
import functools
|
|
|
|
class Sequential(paddle.nn.LayerDict):
|
|
"""A sequence of modules with potentially inferring shape on construction.
|
|
If layers are passed with names, these can be referenced with dot notation.
|
|
Arguments
|
|
---------
|
|
input_shape : iterable
|
|
A list or tuple of ints or None, representing the expected shape of an
|
|
input tensor. None represents a variable-length dimension. If no
|
|
``input_shape`` is passed, no shape inference will be performed.
|
|
*layers, **named_layers
|
|
The inputs are treated as a list of layers to be
|
|
applied in sequence. The output shape of each layer is used to
|
|
infer the shape of the following layer. If a tuple is returned,
|
|
only the shape of the first element is used to determine input
|
|
shape of the next layer (e.g. RNN returns output, hidden).
|
|
Example
|
|
-------
|
|
>>> inputs = paddle.rand(10, 40, 50)
|
|
>>> model = Sequential(input_shape=inputs.shape)
|
|
>>> model.append(Linear, n_neurons=100, layer_name="layer1")
|
|
>>> model.append(Linear, n_neurons=200, layer_name="layer2")
|
|
>>> outputs = model(inputs)
|
|
>>> outputs.shape
|
|
paddle.shape([10, 40, 200])
|
|
>>> outputs = model.layer1(inputs)
|
|
>>> outputs.shape
|
|
paddle.shape([10, 40, 100])
|
|
"""
|
|
|
|
def __init__(self, *layers, input_shape=None, **named_layers):
|
|
super().__init__()
|
|
|
|
# Make sure either layers or input_shape is passed
|
|
if not layers and input_shape is None and not named_layers:
|
|
raise ValueError("Must pass either layers or input shape")
|
|
|
|
# Keep track of what layers need "lengths" passed
|
|
self.length_layers = []
|
|
|
|
# Replace None dimensions with arbitrary value
|
|
self.input_shape = input_shape
|
|
if input_shape and None in input_shape:
|
|
self.input_shape = list(input_shape)
|
|
for i, dim in enumerate(self.input_shape):
|
|
|
|
# To reduce size of dummy tensors, use 1 for batch dim
|
|
if i == 0 and dim is None:
|
|
dim = 1
|
|
|
|
# Use 64 as nice round arbitrary value, big enough that
|
|
# halving this dimension a few times doesn't reach 1
|
|
self.input_shape[i] = dim or 256
|
|
|
|
# Append non-named layers
|
|
for layer in layers:
|
|
self.append(layer)
|
|
|
|
# Append named layers
|
|
for name, layer in named_layers.items():
|
|
self.append(layer, layer_name=name)
|
|
|
|
def append(self, layer, *args, layer_name=None, **kwargs):
|
|
"""Add a layer to the list of layers, inferring shape if necessary.
|
|
Arguments
|
|
---------
|
|
layer : A paddle.nn.Module class or object
|
|
If the layer is a class, it should accept an argument called
|
|
``input_shape`` which will be inferred and passed. If the layer
|
|
is a module object, it is added as-is.
|
|
layer_name : str
|
|
The name of the layer, for reference. If the name is in use,
|
|
``_{count}`` will be appended.
|
|
*args, **kwargs
|
|
These are passed to the layer if it is constructed.
|
|
"""
|
|
|
|
# Compute layer_name
|
|
if layer_name is None:
|
|
layer_name = str(len(self))
|
|
elif layer_name in self:
|
|
index = 0
|
|
while f"{layer_name}_{index}" in self:
|
|
index += 1
|
|
layer_name = f"{layer_name}_{index}"
|
|
# Check if it needs to be constructed with input shape
|
|
if self.input_shape:
|
|
argspec = inspect.getfullargspec(layer)
|
|
if "input_shape" in argspec.args + argspec.kwonlyargs:
|
|
input_shape = self.get_output_shape()
|
|
layer = layer(*args, input_shape=input_shape, **kwargs)
|
|
|
|
# Finally, append the layer.
|
|
try:
|
|
self[layer_name] = layer
|
|
# self.add_module(layer_name, layer)
|
|
except TypeError:
|
|
raise ValueError(
|
|
"Must pass `input_shape` at initialization and use "
|
|
"modules that take `input_shape` to infer shape when "
|
|
"using `append()`."
|
|
)
|
|
|
|
def get_output_shape(self):
|
|
"""Returns expected shape of the output.
|
|
Computed by passing dummy input constructed with the
|
|
``self.input_shape`` attribute.
|
|
"""
|
|
with paddle.no_grad():
|
|
dummy_input = paddle.zeros(self.input_shape)
|
|
dummy_output = self(dummy_input)
|
|
return dummy_output.shape
|
|
|
|
def forward(self, x):
|
|
"""Applies layers in sequence, passing only the first element of tuples.
|
|
Arguments
|
|
---------
|
|
x : paddle.Tensor
|
|
The input tensor to run through the network.
|
|
"""
|
|
for layer in self.values():
|
|
x = layer(x)
|
|
if isinstance(x, tuple):
|
|
x = x[0]
|
|
|
|
return x
|