"""Contains convolution functionality useful for NLP tasks."""__author__='Paul Landes'fromtypingimportList,Tuple,Set,Iterable,Callable,Union,ClassVarfromdataclassesimportdataclass,field,asdictimportloggingimportsysimportitertoolsasitfromioimportTextIOBasefromzensols.configimportWritableimporttorchfromtorchimportnn,Tensorfromzensols.persistimportpersistedfromzensols.deeplearnimport(ActivationNetworkSettings,DropoutNetworkSettings,BatchNormNetworkSettings,)fromzensols.deeplearn.layerimportLayerError,Convolution1DLayerFactoryfromzensols.deeplearn.modelimportBaseNetworkModulelogger=logging.getLogger(__name__)@dataclassclass_Layer(object):"""A layer or action to perform on the convolution layer grouping. """desc:str=field()impl:Union[nn.Module,Callable]=field()def__str__(self)->str:returnf'{self.desc}: {type(self.impl)}'@dataclassclass_LayerSet(object):"""A grouping of alyers for one pass of what's typically called a single convolution layer. """index:int=field()layers:List[_Layer]=field(default_factory=list)defadd(self,desc:str,impl:Callable):self.layers.append(_Layer(desc,impl))def__str__(self)->str:returnf'index: {self.index}, n_layers: {len(self.layers)}'
[docs]@dataclassclassDeepConvolution1dNetworkSettings(ActivationNetworkSettings,DropoutNetworkSettings,Writable):"""Configurable repeated series of 1-dimension convolution, pooling, batch norm and activation layers. This layer is specifically designed for natural language processing task, which is why this configuration includes parameters for token counts. Each layer repeat consists of (based on the ordering in :obj:`applies`): 1. convolution 2. max pool 3. batch 4. activation This class is used directly after embedding (and in conjuction with) a layer class that extends :class:`.EmbeddingNetworkModule`. The lifecycle of this class starts with being instantiated (usually configured using a :class:`~zensols.config.factory.ImportConfigFactory`), then cloned with :meth:`clone` during the initialization on the layer from which it's used. :see: :class:`.DeepConvolution1d` :see :class:`.EmbeddingNetworkModule` """token_length:int=field(default=None)"""The number of tokens processed through the layer (used as the width kernel parameter ``W``). """embedding_dimension:int=field(default=None)"""The dimension of the embedding (word vector) layer (depth dimension ``e``). """token_kernel:int=field(default=2)"""The size of sliding window in number of tokens (width dimension of kernel parameter ``F``). """stride:int=field(default=1)"""The number of cells to skip for each convolution (``S``)."""padding:int=field(default=0)"""The zero'd number of cells on the ends of tokens X embedding neurons (``P``). """pool_token_kernel:int=field(default=2)"""Like ``token_length`` but in the pooling layer."""pool_stride:int=field(default=1)"""Like ``stride`` but in the pooling layer."""pool_padding:int=field(default=0)"""Like ``padding`` but in the pooling layer."""repeats:int=field(default=1)"""Number of times the convolution, max pool, batch, activation layers are repeated. """applies:Tuple[str,...]=field(default=(tuple('convolution batch_norm pool activation dropout'.split())))""""A sequence of strings indicating the order or the layers to apply with default; if a layer is omitted it won't be applied. """@property@persisted('_layer_factories')deflayer_factories(self)->Tuple[Convolution1DLayerFactory,...]:"""The factory used to create convolution layers."""fac=Convolution1DLayerFactory(in_channels=self.embedding_dimension,out_channels=self.token_length,kernel_filter=self.token_kernel,stride=self.stride,padding=self.padding,pool_kernel_filter=self.pool_token_kernel,pool_stride=self.pool_stride,pool_padding=self.pool_padding)facs=list(it.islice(fac.iter_layers(),self.repeats-1))facs.insert(0,fac)returntuple(facs)@propertydefout_shape(self)->Tuple[int,...]:"""The shape of the last convolution pool stacked layer."""returnself[-1].out_pool_shape
[docs]defvalidate(self):"""Validate the dimensionality all layers of the convolutional network. :raise LayerError: if any convolution layer is not valid """conv_factory:Convolution1DLayerFactoryfori,conv_factoryinenumerate(self):err:str=conv_factory.validate(False)iferrisnotNone:raiseLayerError(f'Layer {i} not valid: {err}')
[docs]classDeepConvolution1d(BaseNetworkModule):"""Configurable repeated series of 1-dimension convolution, pooling, batch norm and activation layers. See :meth:`get_layers`. :see: :class:`.DeepConvolution1dNetworkSettings` """MODULE_NAME:ClassVar[str]='conv'
[docs]def__init__(self,net_settings:DeepConvolution1dNetworkSettings,logger:logging.Logger):"""Initialize the deep convolution layer. *Implementation note*: all layers are stored sequentially using a :class:`torch.nn.Sequential` to get normal weight persistance on torch save/loads. :param net_settings: the deep convolution layer configuration :param logger: the logger to use for the forward process in this layer """super().__init__(net_settings,logger)ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'initializing conv layer with with: {net_settings}')layers:List[nn.Module]=[]self.layer_sets:List[_LayerSet]=[]self._create_layers(layers,self.layer_sets)self.seq_layers=nn.Sequential(*layers)
def_create_layers(self,layers:List[nn.Module],layer_sets:List[Tuple[nn.Module,...]]):"""Create the convolution, max pool and batch norm layers used to forward through. :param layers: the layers to populate used in an :class:`torch.nn.Sequential` :param layer_sets: tuples of (conv, pool, batch_norm) layers """layer_factories:Tuple[Convolution1DLayerFactory,...]= \
self.net_settings.layer_factoriesapplies:Tuple[str,...]=self.net_settings.appliesapply_set:Set[str]=set(applies)repeats:int=self.net_settings.repeats# modules and other actions that are the same for each groupactivation:nn.Module=self._forward_activationdropout:nn.Module=self._forward_dropout# create groupings of layers for the specified count; each grouping is# generally called the "convolution layer"n_set:intconv_factory:Convolution1DLayerFactoryforn_set,conv_factoryinenumerate(layer_factories):ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'conv_factory: {conv_factory}')# create (only) the asked for layerslayer_set=_LayerSet(n_set)convolution:nn.Conv1d=Nonepool:nn.MaxPool1d=Nonebatch_norm:nn.BatchNorm1d=Noneif'convolution'inapply_set:convolution=conv_factory.create_conv_layer()ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'conv: {convolution}')if'pool'inapply_set:pool=conv_factory.create_pool_layer()ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'pool: {pool}')if'batch_norm'inapply_set:batch_norm=conv_factory.create_batch_norm_layer()ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'batch_norm: {batch_norm}')desc:strfordescinapplies:layer:Union[Callable,nn.Module]=locals().get(desc)ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'adding abstract layer: {desc} -> {layer}')iflayerisNone:# skip layers not configured or givencontinueifnotisinstance(layer,Callable):raiseLayerError(f'Bad or missing layer: {type(layer)}')layer_set.add(desc,layer)ifisinstance(layer,nn.Module):ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'adding layer: {layer}')# add to the model (PyTorch framework lifecycle actions)layers.append(layer)layer_sets.append(layer_set)ifn_set<(self.net_settings.repeats-1):next_factory:Convolution1DLayerFactory= \
layer_factories[n_set+1]ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'this repeat {conv_factory.out_pool_shape}'+f', next: {next_factory.out_pool_shape}')
[docs]defget_layers(self)->Tuple[Tuple[nn.Module,nn.Module,nn.Module]]:"""Return a tuple of layer sets, with each having the form: ``(convolution, max pool, batch_norm)``. The ``batch_norm`` norm is ``None`` if not configured. """returntuple(self.seq_layers)
def_forward(self,x:torch.Tensor)->torch.Tensor:"""Forward convolution, batch normalization, pool, activation and dropout for those layers that are configured. :see: `Ioffe et al <https://arxiv.org/pdf/1502.03167.pdf>`_ """ls_len:int=len(self.layer_sets)x=x.permute(0,2,1)self._shape_debug('permute',x)ifself.logger.isEnabledFor(logging.DEBUG):self._shape_debug(f'applying {ls_len} layer sets',x)layer_set:_LayerSetfori,layer_setinenumerate(self.layer_sets):ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'applying layer set: {layer_set}')layer:_Layerforlayerinlayer_set.layers:ifself.logger.isEnabledFor(logging.DEBUG):self._debug(f'applying layer: {layer}')x=layer.impl(x)self._shape_debug(layer.desc,x)self._shape_debug(f'repeat {i}',x)self._shape_debug('conv layer sets',x)returnx