diff --git a/README.md b/README.md index f1ddbcc..f0f67fd 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,14 @@ The compatibility test is shown as below. The checked item means this package pe ## Update reports -### 0.1.5 @ 3/9/2021 +### 0.1.5 @ 3/14/2021 1. Add `DecoderNet` to our standard `module` protocol. 2. Fix some bugs of `data.h5py` and `data.preprocs`. 3. Make `draw.setFigure` enhanced by `contextlib`. 4. Add a title in `Readme.md`. +5. Fix typos and bugs in `data` and `modules`. +6. Add properties `nlayers`, `input_size` for networks in `modules`. ### 0.1.2 @ 2/27/2021 diff --git a/mdnc/__init__.py b/mdnc/__init__.py index 65af74d..200df32 100644 --- a/mdnc/__init__.py +++ b/mdnc/__init__.py @@ -17,10 +17,13 @@ ################################################################ # Update reports: # --------------- -# 0.1.5 @ 3/9/2021 +# 0.1.5 @ 3/14/2021 # 1. Add DecoderNet to our standard module protocol. # 2. Fix some bugs of data.h5py and data.preprocs. # 3. Make draw.setFigure enhanced by contextlib. +# 4. Add a title in `Readme.md`. +# 5. Fix typos and bugs in `data` and `modules`. +# 6. Add properties `nlayers`, `input_size` for networks in `modules`. # 0.1.2 @ 2/27/2021 # 1. Fix more feature problems in `contribs.torchsummary`. # 2. Fix bugs and finish `data.preprocs`. diff --git a/mdnc/data/__init__.py b/mdnc/data/__init__.py index 289795b..90e9f49 100644 --- a/mdnc/data/__init__.py +++ b/mdnc/data/__init__.py @@ -29,6 +29,8 @@ ################################################################ # Update reports: # --------------- +# 0.1.5 @ 3/14/2021 +# 1. Fix typos and bugs. # 0.1.2 @ 2/27/2021 # 1. Finish preprocs. # - Update the implementation of preprocs.ProcAbstract. diff --git a/mdnc/data/__main__.py b/mdnc/data/__main__.py index bd66b23..ae54661 100644 --- a/mdnc/data/__main__.py +++ b/mdnc/data/__main__.py @@ -300,7 +300,7 @@ def test_1d(self): plt.show() def test_2d(self): - x = engine.preprocs.ProcNSTScaler(parent=engine.preprocs.ProcLifter(a=1.0, parent=engine.preprocs.ProcPad(pad_width=((0, 0), (10, -10), (-10, 10)), mode='constant', constant_values=0.0))) + x = engine.preprocs.ProcNSTScaler(dim=2, parent=engine.preprocs.ProcLifter(a=1.0, parent=engine.preprocs.ProcPad(pad_width=((0, 0), (10, -10), (-10, 10)), mode='constant', constant_values=0.0))) with open(self.file_name, 'wb') as f: pickle.dump(x, f) with open(self.file_name, 'rb') as f: diff --git a/mdnc/data/h5py.py b/mdnc/data/h5py.py index 72de61e..224dd4c 100644 --- a/mdnc/data/h5py.py +++ b/mdnc/data/h5py.py @@ -783,8 +783,7 @@ def open(self, file_name, enable_read=None): if self.__in_context: raise RuntimeError('data.h5py: Should not open a file when the saver is managing a context, because there is already an opened file. Try to exit the context or create a new different saver.') file_name, file_ext = os.path.splitext(file_name) - if file_ext != '.h5': - file_name += '.h5' + file_name += '.h5' self.close() if enable_read is None: enable_read = self.enable_read diff --git a/mdnc/data/preprocs.py b/mdnc/data/preprocs.py index 63a8357..d959016 100644 --- a/mdnc/data/preprocs.py +++ b/mdnc/data/preprocs.py @@ -404,8 +404,8 @@ def __init__(self, shift=None, scale=None, axis=-1, inds=None, parent=None): self.axis = axis def preprocess(self, x): - xmean = np.mean(x, axis=self.axis) if self.shift is None else self.shift - xscale = np.amax(np.abs(x - xmean), axis=self.axis) if self.scale is None else self.scale + xmean = np.mean(x, axis=self.axis, keepdims=True) if self.shift is None else self.shift + xscale = np.amax(np.abs(x - xmean), axis=self.axis, keepdims=True) if self.scale is None else self.scale self.set_mem('xmean', xmean) self.set_mem('xscale', xscale) return (x - xmean) / xscale @@ -426,7 +426,7 @@ class ProcNSTScaler(ProcAbstract): https://stackoverflow.com/a/49317610 ''' - def __init__(self, dim=2, kernel_length=9, epsilon=1e-6, inds=None, parent=None): + def __init__(self, dim, kernel_length=9, epsilon=1e-6, inds=None, parent=None): '''Initialization. Arguments: dim: the dimension of the input data (to be normalized). @@ -439,6 +439,8 @@ def __init__(self, dim=2, kernel_length=9, epsilon=1e-6, inds=None, parent=None) be used as the parent of the current instance. ''' super().__init__(inds=inds, parent=parent) + if dim not in (1, 2, 3): + raise ValueError('data.preprocs: The argument "dim" requires to be 1, 2, or 3.') self.__dim = dim self.__kernel_length = kernel_length self.epsilon = epsilon @@ -888,11 +890,11 @@ def __split_pad_width(pad_width): return tuple(pad_width_), tuple(crop_width) else: raise ValueError('data.preprocs: the crop arguments could not get separated from the pad arguments. The given arguments "pad_width" may be not valid.') - + @property def pad_width(self): - return getattr - + return object.__getattribute__(self, '_ProcPad__pad_width_') + @pad_width.setter def pad_width(self, value): self.__pad_width, self.__crop_width = self.__split_pad_width(value) diff --git a/mdnc/data/webtools.py b/mdnc/data/webtools.py index 98e85df..70d40c5 100644 --- a/mdnc/data/webtools.py +++ b/mdnc/data/webtools.py @@ -332,6 +332,18 @@ def init_set_list(file_name='web-data'): 'dataset_file_name_01.txt', 'dataset_file_name_02.txt' ] + }, + { + 'tag': 'test', + 'asset': 'test-datasets-2.tar.xz', + 'items': [ + 'test_data_h5converter.h5', + 'test_data_h5cparser.h5', + 'test_data_h5cparser_seq.h5', + 'test_data_h5gparser.h5', + 'test_data_h5seqconverter1.h5', + 'test_data_h5seqconverter2.h5' + ] } ], 'user': 'cainmagi', diff --git a/mdnc/modules/__init__.py b/mdnc/modules/__init__.py index 5403a85..1255ec2 100644 --- a/mdnc/modules/__init__.py +++ b/mdnc/modules/__init__.py @@ -16,6 +16,9 @@ # 0.1.5 @ 3/2/2021 # 1. Add DecoderNet to conv, resnet. # 2. Rename ConvNet by EncoderNet in conv, resnet. +# 3. Fix typos and bugs. +# 4. Add `nlayers` for all networks. Add `input_size` for +# decoders. # 0.1.0 @ 2/26/2021 # 1. Create sub-packages: conv, resnet. ################################################################ diff --git a/mdnc/modules/__main__.py b/mdnc/modules/__main__.py index 37f66b1..10cb4cd 100644 --- a/mdnc/modules/__main__.py +++ b/mdnc/modules/__main__.py @@ -69,6 +69,7 @@ def test_networks(self): print('modules.modules: Test {0}d networks.'.format(order)) for net in self.networks: test_module = net(order=order, in_planes=input_size[0]) + print('{0} with {1} layers along its depth.'.format(type(test_module).__name__, test_module.nlayers)) torchsummary.summary(test_module, input_size=input_size, device='cpu') del test_module @@ -77,6 +78,7 @@ def test_decodernets(self): print('modules.modules: Test {0}d decoders.'.format(order)) for net in self.net_decs: test_module = net(order=order, in_length=2, out_size=out_size[1:]) + print('{0} with {1} layers along its depth.'.format(type(test_module).__name__, test_module.nlayers)) torchsummary.summary(test_module, input_size=(2, ), device='cpu') del test_module diff --git a/mdnc/modules/conv.py b/mdnc/modules/conv.py index b83fa80..5369898 100644 --- a/mdnc/modules/conv.py +++ b/mdnc/modules/conv.py @@ -2,18 +2,20 @@ # -*- coding: UTF-8 -*- ''' ################################################################ -# Modules - 1D convolutional network +# Modules - convolutional network # @ Modern Deep Network Toolkits for pyTorch # Yuchen Jin @ cainmagi@gmail.com # Requirements: (Pay attention to version) # python 3.5+ # pyTorch 1.0.0+ -# This module is the definition of the 1D convolutional network. +# This module is the definition of the convolutional network. # The network could be initialized here and used for training # and processing. ################################################################ ''' +import functools + import torch import torch.nn as nn @@ -71,7 +73,7 @@ def __init__(self, order, in_planes, out_planes, kernel_size=3, stride=1, paddin ''' super().__init__() ConvNd = get_convnd(order=order) - is_stride = check_is_stride(stride) + is_stride = check_is_stride(stride, output_size=output_size, scaler=scaler) seq = [] if normalizer == 'null': if (not is_stride) or scaler == 'down': @@ -183,7 +185,7 @@ def __init__(self, order, in_planes, out_planes, hidden_planes=None, _ConvModernNd(order, (in_planes + ex_planes) if i == 0 else hidden_planes, hidden_planes, kernel_size=kernel_size, padding=padding, stride=1, scaler='down') ) - self.conv_scale = _ConvModernNd(order, hidden_planes, out_planes, + self.conv_scale = _ConvModernNd(order, hidden_planes if stack_level > 1 else (in_planes + ex_planes), out_planes, kernel_size=kernel_size, padding=padding, stride=stride, scaler=scaler) @staticmethod @@ -235,13 +237,14 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_plane of convolutional layers of a stage. The stage numer, i.e. the depth of the network is the length of this list. Arguments (optional): + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__() if len(layers) < 2: raise ValueError('modules.conv: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -273,7 +276,16 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_plane self.conv_up_list.append( _BlockConvStkNd(order, channel, channel, hidden_planes=channel, kernel_size=ksize, padding=psize, stride=1, stack_level=layers[0], ex_planes=channel, scaler='down')) - self.conv_final = ConvNd(channel, in_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + self.conv_final = ConvNd(channel, out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + 2 * y, self.__layers[:-1], self.__layers[-1]) + 2 + return n_layers def forward(self, x): x = self.conv_first(x) @@ -314,6 +326,7 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_plane super().__init__() if len(layers) < 2: raise ValueError('modules.conv: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -345,7 +358,16 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_plane self.conv_up_list.append( _BlockConvStkNd(order, channel, channel, hidden_planes=channel, kernel_size=ksize, padding=psize, stride=1, stack_level=layers[0], scaler='down')) - self.conv_final = ConvNd(channel, in_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + self.conv_final = ConvNd(channel, out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + 2 * y, self.__layers[:-1], self.__layers[-1]) + 2 + return n_layers @staticmethod def cropping(x, x_ref_s): @@ -399,6 +421,7 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_lengt super().__init__() if len(layers) < 2: raise ValueError('modules.conv: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -422,12 +445,23 @@ def __init__(self, order, channel, layers, kernel_size=3, in_planes=1, out_lengt if self.is_out_vector: self.fc = nn.Linear(channel, out_length, bias=True) + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + y, self.__layers, 0) + 2 + return n_layers + def forward(self, x): for layer in self.netbody: x = layer(x) if self.is_out_vector: x = torch.flatten(x, 1) return self.fc(x) + else: + return x class _DecoderNetNd(nn.Module): @@ -462,12 +496,17 @@ def __init__(self, order, channel, layers, out_size, kernel_size=3, in_length=2, super().__init__() if len(layers) < 2: raise ValueError('modules.conv: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers + self.__in_length = in_length ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) ksize, psize, stride = cal_kernel_padding(kernel_size) self.__order = order + if isinstance(out_size, int): + out_size = (out_size, ) * order self.shapes = cal_scaled_shapes(out_size, level=len(layers), stride=stride) channels = tuple(map(lambda n: channel * (2 ** n), range(len(layers) - 1, -1, -1))) + self.__in_channel = channels[0] # Require to convert the vector into channels self.is_in_vector = (in_length is not None and in_length > 0) @@ -489,6 +528,22 @@ def __init__(self, order, channel, layers, out_size, kernel_size=3, in_length=2, self.netbody = netbody self.conv_last = ConvNd(channels[-1], out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + @property + def input_size(self): + if self.is_in_vector: + return (self.__in_length, ) + else: + return (self.__in_channel, *self.shapes[-1]) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + y, self.__layers, 0) + (3 if self.is_in_vector else 2) + return n_layers + @staticmethod def cropping(x, x_ref_s): x_size = x.shape[2:] @@ -672,9 +727,9 @@ def __init__(self, channel, layers, kernel_size=3, in_planes=1, out_planes=1): of convolutional layers of a stage. The stage numer, i.e. the depth of the network is the length of this list. Arguments (optional): + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(1, channel=channel, layers=layers, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) @@ -698,9 +753,9 @@ def __init__(self, channel, layers, kernel_size=3, in_planes=1, out_planes=1): of convolutional layers of a stage. The stage numer, i.e. the depth of the network is the length of this list. Arguments (optional): + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(order=2, channel=channel, layers=layers, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) @@ -724,9 +779,9 @@ def __init__(self, channel, layers, kernel_size=3, in_planes=1, out_planes=1): of convolutional layers of a stage. The stage numer, i.e. the depth of the network is the length of this list. Arguments (optional): + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(order=3, channel=channel, layers=layers, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) @@ -1267,7 +1322,7 @@ def decnet16(out_size, order=2, **kwargs): '''Constructs a conv.DecoderNet-16 model. Configurations: Network depth: 5 - Stage details: [2, 2, 3, 3, 3] + Stage details: [3, 3, 2, 2, 2] First channel number: 64 Arguments: out_size: the output shape of the network. @@ -1276,7 +1331,7 @@ def decnet16(out_size, order=2, **kwargs): Other Arguments (see mdnc.modules.conv.DecoderNet*d): in_length, out_planes, kernel_size ''' - model = __get_decnet_nd(order)(64, [2, 2, 3, 3, 3], out_size=out_size, **kwargs) + model = __get_decnet_nd(order)(64, [3, 3, 2, 2, 2], out_size=out_size, **kwargs) return model diff --git a/mdnc/modules/resnet.py b/mdnc/modules/resnet.py index 97c20e1..4d4bfab 100644 --- a/mdnc/modules/resnet.py +++ b/mdnc/modules/resnet.py @@ -2,13 +2,13 @@ # -*- coding: UTF-8 -*- ''' ################################################################ -# Modules - 1D residual network +# Modules - residual network # @ Modern Deep Network Toolkits for pyTorch # Yuchen Jin @ cainmagi@gmail.com # Requirements: (Pay attention to version) # python 3.5+ # pyTorch 1.0.0+ -# This module is the definition of the 1D residual network. The +# This module is the definition of the residual network. The # network could be initialized here and used for training and # processing. # The codes are inspired by: @@ -16,6 +16,8 @@ ################################################################ ''' +import functools + import torch import torch.nn as nn @@ -28,6 +30,16 @@ 'DecoderNet1d', 'DecoderNet2d', 'DecoderNet3d', 'decnet13', 'decnet33', 'decnet48', 'decnet63'] +def get_block_depth(block): + block = block.casefold() + if block == 'bottleneck': + return 3 + elif block == 'plain': + return 2 + else: + raise ValueError('module.resnet: The argument "block" should be "plain" or "bottleneck".') + + class _BlockFactory: '''N-D block factory class for the residual network The block factory used for building any specialized block. @@ -90,7 +102,7 @@ def res_branch(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, order, normalizer, activator, layer_order. ''' ConvNd = get_convnd(order=self.order) - is_stride = check_is_stride(stride) + is_stride = check_is_stride(stride, output_size=output_size, scaler=scaler) seq = [] if self.normalizer == 'null': if (not is_stride) or scaler == 'down': @@ -162,7 +174,7 @@ def in_branch(self, in_planes, out_planes, stride=1, output_size=None, scaler='d Arguments (inherited): order, normalizer. ''' - if check_is_stride(stride) or in_planes != out_planes: + if check_is_stride(stride, output_size=output_size, scaler=scaler) or in_planes != out_planes: ConvNd = get_convnd(order=self.order) seq = [] if self.normalizer in 'null': @@ -220,7 +232,7 @@ def __init__(self, order, in_planes, out_planes, kernel_size=3, stride=1, paddin Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -284,7 +296,7 @@ def __init__(self, order, in_planes, out_planes, kernel_size=3, stride=1, paddin Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -350,11 +362,11 @@ def __init__(self, order, in_planes, out_planes, block='bottleneck', also used as the base of the following channels. If not set, would use "out_planes" as the default value. - kernel_size: the kernel size of the convolutional layers. - padding: the padding size of the convolutional layers. - stride: the stride size of the convolutional layers. - stack_level: the number of convolutional layers in this block, - requiring to be >= 1. + kernel_size: the kernel size of the residual blocks. + padding: the padding size of the residual blocks. + stride: the stride size of the residual blocks. + stack_level: the number of residual blocks in this block, requiring + to be >= 1. ex_planes: the channel number of the second input data. This value is =0 in most time, but >0 during the decoding phase of the U-Net. the extra input would be concatenated @@ -388,7 +400,7 @@ def __init__(self, order, in_planes, out_planes, block='bottleneck', Block(order, (in_planes + ex_planes) if i == 0 else hidden_planes, hidden_planes, kernel_size=kernel_size, padding=padding, stride=1, scaler='down') ) - self.conv_scale = Block(order, hidden_planes, out_planes, + self.conv_scale = Block(order, hidden_planes if stack_level > 1 else (in_planes + ex_planes), out_planes, kernel_size=kernel_size, padding=padding, stride=stride, scaler=scaler) @staticmethod @@ -442,13 +454,15 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in Arguments (optional): block: the block type, could be: - bottleneck, - plain + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__() if len(layers) < 2: raise ValueError('modules.resnet: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers + self.__block_depth = get_block_depth(block) ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -480,7 +494,16 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in self.conv_up_list.append( _BlockResStkNd(order, channel, channel, hidden_planes=channel, block=block, kernel_size=ksize, padding=psize, stride=1, stack_level=layers[0], ex_planes=channel, scaler='down')) - self.conv_final = ConvNd(channel, in_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + self.conv_final = ConvNd(channel, out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + 2 * self.__block_depth * y, self.__layers[:-1], self.__block_depth * self.__layers[-1]) + 2 + return n_layers def forward(self, x): x = self.conv_first(x) @@ -523,6 +546,8 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in super().__init__() if len(layers) < 2: raise ValueError('modules.resnet: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers + self.__block_depth = get_block_depth(block) ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -554,7 +579,16 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in self.conv_up_list.append( _BlockResStkNd(order, channel, channel, hidden_planes=channel, block=block, kernel_size=ksize, padding=psize, stride=1, stack_level=layers[0], scaler='down')) - self.conv_final = ConvNd(channel, in_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + self.conv_final = ConvNd(channel, out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + 2 * self.__block_depth * y, self.__layers[:-1], self.__block_depth * self.__layers[-1]) + 2 + return n_layers @staticmethod def cropping(x, x_ref_s): @@ -610,6 +644,8 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in super().__init__() if len(layers) < 2: raise ValueError('modules.resnet: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers + self.__block_depth = get_block_depth(block) ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) @@ -633,12 +669,23 @@ def __init__(self, order, channel, layers, block='bottleneck', kernel_size=3, in if self.is_out_vector: self.fc = nn.Linear(channel, out_length, bias=True) + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + self.__block_depth * y, self.__layers, 0) + 2 + return n_layers + def forward(self, x): for layer in self.netbody: x = layer(x) if self.is_out_vector: x = torch.flatten(x, 1) return self.fc(x) + else: + return x class _DecoderNetNd(nn.Module): @@ -675,12 +722,18 @@ def __init__(self, order, channel, layers, out_size, block='bottleneck', kernel_ super().__init__() if len(layers) < 2: raise ValueError('modules.conv: The argument "layers" should contain at least 2 values, but provide "{0}"'.format(layers)) + self.__layers = layers + self.__block_depth = get_block_depth(block) + self.__in_length = in_length ConvNd = get_convnd(order=order) ksize_e, psize_e, _ = cal_kernel_padding(kernel_size, ksize_plus=2) ksize, psize, stride = cal_kernel_padding(kernel_size) self.__order = order + if isinstance(out_size, int): + out_size = (out_size, ) * order self.shapes = cal_scaled_shapes(out_size, level=len(layers), stride=stride) channels = tuple(map(lambda n: channel * (2 ** n), range(len(layers) - 1, -1, -1))) + self.__in_channel = channels[0] # Require to convert the vector into channels self.is_in_vector = (in_length is not None and in_length > 0) @@ -702,6 +755,22 @@ def __init__(self, order, channel, layers, out_size, block='bottleneck', kernel_ self.netbody = netbody self.conv_last = ConvNd(channels[-1], out_planes, kernel_size=ksize_e, stride=1, padding=psize_e, bias=True) + @property + def input_size(self): + if self.is_in_vector: + return (self.__in_length, ) + else: + return (self.__in_channel, *self.shapes[-1]) + + @property + def nlayers(self): + '''Return number of convolutional layers along the depth. + ''' + if len(self.__layers) == 0: + return 0 + n_layers = functools.reduce(lambda x, y: x + self.__block_depth * y, self.__layers, 0) + (3 if self.is_in_vector else 2) + return n_layers + @staticmethod def cropping(x, x_ref_s): x_size = x.shape[2:] @@ -746,7 +815,7 @@ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, ou Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -794,7 +863,7 @@ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, ou Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -842,7 +911,7 @@ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, ou Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -890,7 +959,7 @@ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, ou Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -986,7 +1055,7 @@ def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, padding=1, ou Arguments (optional): kernel_size: the kernel size of this layer. stride: the stride size of this layer. - padding: the padding size of the convolutional layer. + padding: the padding size of the residual block. output_size: the size of the output data. This option is only used when "scaler=up". When setting this value, the size of the up-sampling would be given explicitly and @@ -1034,9 +1103,9 @@ def __init__(self, channel, layers, block='bottleneck', kernel_size=3, in_planes Arguments (optional): block: the block type, could be: - bottleneck, - plain + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(1, channel=channel, layers=layers, block=block, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) @@ -1062,9 +1131,9 @@ def __init__(self, channel, layers, block='bottleneck', kernel_size=3, in_planes Arguments (optional): block: the block type, could be: - bottleneck, - plain + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(2, channel=channel, layers=layers, block=block, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) @@ -1090,9 +1159,9 @@ def __init__(self, channel, layers, block='bottleneck', kernel_size=3, in_planes Arguments (optional): block: the block type, could be: - bottleneck, - plain + kernel_size: the kernel size of each block. in_planes: the channel number of the input data. out_planes: the channel number of the output data. - kernel_size: the kernel size of each block. ''' super().__init__(3, channel=channel, layers=layers, block=block, kernel_size=kernel_size, in_planes=in_planes, out_planes=out_planes) diff --git a/mdnc/modules/utils.py b/mdnc/modules/utils.py index 73fac77..0e9b059 100644 --- a/mdnc/modules/utils.py +++ b/mdnc/modules/utils.py @@ -80,7 +80,9 @@ def get_adaptive_pooling(order=2, out_size=1): raise ValueError('modules.utils: The argument "order" could only be 1, 2, or 3.') -def check_is_stride(stride): +def check_is_stride(stride, output_size=None, scaler='down'): + if output_size is not None and scaler == 'up': + return True if isinstance(stride, (list, tuple)): for s in stride: if s > 1: