Source code for torch_tools.models._fc_net

"""A fully connected neural network model."""

from typing import Optional, Tuple, Union, List

from torch.nn import Module, Sequential

from torch_tools.models._blocks_1d import DenseBlock, InputBlock

from torch_tools.models._argument_processing import process_hidden_sizes

# pylint: disable=too-many-arguments, too-many-positional-arguments


[docs] class FCNet(Sequential): """Fully-connected neural network. An optional input block, which applies batch normalisation and dropout to the inputs, followed by a series of fully-connected blocks consisting of ``Linear``, ``BatchNorm1d`` and ``LeakyReLU`` layers, followed by a final ``Linear`` output layer. Parameters ---------- in_feats : int Number of input features to the model. out_feats : int Number of output features (classes). hidden_sizes : Tuple[int, ...], optional The sizes of the hidden layers (or ``None``). input_bnorm : bool, optional Should we apply batch-normalisation to the input batches? input_dropout : float, optional The dropout probability to apply to the inputs (not included if zero). hidden_dropout : float, optional The Dropout probability at each hidden layer (not included if zero). hidden_bnorm : bool, optional Should we include batch norms in the hidden layers? negative_slope : float, optional The negative slope argument to use in the ``LeakyReLU`` layers. Examples -------- >>> from torch_tools import FCNet >>> FCNet(in_feats=256, out_feats=2, hidden_sizes=(128, 64, 32), input_bnorm=True, input_dropout=0.1, hidden_dropout=0.25, hidden_bnorm=True, negative_slope=0.2) """ def __init__( self, in_feats: int, out_feats: int, hidden_sizes: Optional[Tuple[int, ...]] = None, input_bnorm: bool = False, input_dropout: float = 0.0, hidden_dropout: float = 0.25, hidden_bnorm: bool = True, negative_slope: float = 0.1, ): """Build `DenseClassifier`.""" super().__init__( *self._list_all_blocks( in_feats, out_feats, process_hidden_sizes(hidden_sizes), input_bnorm, input_dropout, hidden_dropout, hidden_bnorm, negative_slope, ) ) def _list_all_blocks( self, in_feats: int, out_feats: int, hidden_sizes: Union[Tuple[int, ...], None], input_bnorm: bool, input_dropout: float, hidden_dropout: float, hidden_bnorm: bool, negative_slope: float, ) -> Sequential: """Put all of the model's blocks in a `Sequential`. Parameters ---------- in_feats : int The number of input features. out_feats : int The number of output features the model should produce. hidden_sizes : List[int] or None The sizes of the hidden layers in the model. input_bnorm : bool Should we apply a batchnorm to the input? input_dropout : float Dropout probability to apply to the input (not included if 0.0). hidden_dropout : float The dropout probability to apply at the hidden layers (not included if 0.0). hidden_bnorm : bool Whether or not to apply batchnorm in the hidden layers. negative_slope : float The negative slope to use in the `LeakyReLU`s. Returns ------- Sequential The model's blocks arranged in a `Sequential`. """ in_block_list = self._input_block_in_list( in_feats, input_bnorm, input_dropout, ) dense_block_list = self._list_dense_blocks( in_feats, out_feats, hidden_dropout, hidden_bnorm, negative_slope, hidden_sizes, ) return Sequential(*in_block_list, *dense_block_list) def _input_block_in_list( self, in_feats: int, input_bnorm: bool, input_dropout: float, ) -> List[Module]: """Put the input block in a list if it's needed. Parameters ---------- in_feats : int The number of inputs features the model should take. input_bnorm : bool Bool determining whether or not batchnorm should be applied to the input. input_dropout : float The dropout probability to apply to the input (no included if 0.0). Returns ------- List[Module] The input block in a list, if needed, otherwise the list is empty. """ if (input_bnorm is False) and (input_dropout == 0.0): return [] return [InputBlock(in_feats, input_bnorm, input_dropout)] @staticmethod def _get_feature_sizes( in_feats: int, out_feats: int, hidden_sizes: Union[Tuple[int, ...], None], ) -> Tuple[Tuple[int, ...], Tuple[int, ...]]: """List the input and output sizes of each block. Parameters ---------- in_feats : int Number of input features to the network. out_feats : int Number of output features the model should produce. hidden_sizes : Tuple[int, ...] or None The sizes of the hidden layers. Returns ------- Tuple[int, ...] Tuple of input feature sizes to each block. Tuple[int, ...] Tuple of output sizes for each block. """ hidden_feats = hidden_sizes if hidden_sizes is not None else () feature_sizes = (in_feats,) + hidden_feats + (out_feats,) return feature_sizes[:-1], feature_sizes[1:] def _list_dense_blocks( self, in_feats: int, out_feats: int, dropout_prob: float, batch_norms: bool, negative_slope: float, hidden_sizes: Union[Tuple[int, ...], None], ) -> List[Module]: """List the dense layers in the model. Parameters ---------- in_feats : int Number of inputs features to this first DenseBlock. out_feats : int Number of output classes the final block should produce. dropout_prob : float The dropout probability (if zero, not included). batch_norms : bool Should we include batchnorms in the DenseBlocks? negative_slope : float The negative slope to use in the leaky relu layers. hidden_sizes : Tuple[int] Sizes of the hidden layers in the model. Returns ------- blocks All of the model's dense blocks in a list. """ in_sizes, out_sizes = self._get_feature_sizes( in_feats, out_feats, hidden_sizes, ) finals = (len(in_sizes) - 1) * [False] + [True] blocks: List[Module] blocks = [] for in_size, out_size, final in zip(in_sizes, out_sizes, finals): blocks.append( DenseBlock( in_size, out_size, final_block=final, dropout_prob=dropout_prob, batch_norm=batch_norms, negative_slope=negative_slope, ) ) return blocks