Source code for niftynet.network.highres3dnet

# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function

from six.moves import range

from niftynet.layer import layer_util
from niftynet.layer.activation import ActiLayer
from niftynet.layer.base_layer import TrainableLayer
from niftynet.layer.bn import BNLayer
from niftynet.layer.convolution import ConvLayer, ConvolutionalLayer
from niftynet.layer.dilatedcontext import DilatedTensor
from niftynet.layer.elementwise import ElementwiseLayer
from niftynet.network.base_net import BaseNet


[docs]class HighRes3DNet(BaseNet): """ implementation of HighRes3DNet: Li et al., "On the compactness, efficiency, and representation of 3D convolutional networks: Brain parcellation as a pretext task", IPMI '17 ### Building blocks { } - Residual connections: see He et al. "Deep residual learning for image recognition", in CVPR '16 [CONV] - Convolutional layer in form: Activation(Convolution(X)) where X = input tensor or output of previous layer and Activation is a function which includes: a) Batch-Norm b) Activation Function (ReLu, PreLu, Sigmoid, Tanh etc.) c) Drop-out layer by sampling random variables from a Bernouilli distribution if p < 1 [CONV*] - Convolutional layer with no activation function (r)[D-CONV(d)] - Convolutional layer with dilated convolutions with blocks in pre-activation mode: D-Convolution(Activation(X)) see He et al., "Identity Mappings in Deep Residual Networks", ECCV '16 dilation factor = d D-CONV(2) : dilated convolution with dilation factor 2 repeat factor = r e.g. (2)[D-CONV(d)] : 2 dilated convolutional layers in a row [D-CONV] -> [D-CONV] { (2)[D-CONV(d)] } : 2 dilated convolutional layers within residual block ### Diagram INPUT --> [CONV] --> { (3)[D-CONV(1)] } --> { (3)[D-CONV(2)] } --> { (3)[D-CONV(4)] } -> [CONV*] -> Loss ### Constraints - Input image size should be divisible by 8 """
[docs] def __init__(self, num_classes, w_initializer=None, w_regularizer=None, b_initializer=None, b_regularizer=None, acti_func='prelu', name='HighRes3DNet'): """ :param num_classes: int, number of channels of output :param w_initializer: weight initialisation for network :param w_regularizer: weight regularisation for network :param b_initializer: bias initialisation for network :param b_regularizer: bias regularisation for network :param acti_func: activation function to use :param name: layer name """ super(HighRes3DNet, self).__init__( num_classes=num_classes, w_initializer=w_initializer, w_regularizer=w_regularizer, b_initializer=b_initializer, b_regularizer=b_regularizer, acti_func=acti_func, name=name) self.layers = [ {'name': 'conv_0', 'n_features': 16, 'kernel_size': 3}, {'name': 'res_1', 'n_features': 16, 'kernels': (3, 3), 'repeat': 3}, {'name': 'res_2', 'n_features': 32, 'kernels': (3, 3), 'repeat': 3}, {'name': 'res_3', 'n_features': 64, 'kernels': (3, 3), 'repeat': 3}, {'name': 'conv_1', 'n_features': 80, 'kernel_size': 1}, {'name': 'conv_2', 'n_features': num_classes, 'kernel_size': 1}]
[docs] def layer_op(self, images, is_training=True, layer_id=-1, **unused_kwargs): """ :param images: tensor to input to the network. Size has to be divisible by 8 :param is_training: boolean, True if network is in training mode :param layer_id: int, index of the layer to return as output :param unused_kwargs: :return: output of layer indicated by layer_id """ assert layer_util.check_spatial_dims( images, lambda x: x % 8 == 0) # go through self.layers, create an instance of each layer # and plugin data layer_instances = [] ### first convolution layer params = self.layers[0] first_conv_layer = ConvolutionalLayer( n_output_chns=params['n_features'], kernel_size=params['kernel_size'], acti_func=self.acti_func, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name=params['name']) flow = first_conv_layer(images, is_training) layer_instances.append((first_conv_layer, flow)) ### resblocks, all kernels dilated by 1 (normal convolution) params = self.layers[1] with DilatedTensor(flow, dilation_factor=1) as dilated: for j in range(params['repeat']): res_block = HighResBlock( params['n_features'], params['kernels'], acti_func=self.acti_func, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name='%s_%d' % (params['name'], j)) dilated.tensor = res_block(dilated.tensor, is_training) layer_instances.append((res_block, dilated.tensor)) flow = dilated.tensor ### resblocks, all kernels dilated by 2 params = self.layers[2] with DilatedTensor(flow, dilation_factor=2) as dilated: for j in range(params['repeat']): res_block = HighResBlock( params['n_features'], params['kernels'], acti_func=self.acti_func, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name='%s_%d' % (params['name'], j)) dilated.tensor = res_block(dilated.tensor, is_training) layer_instances.append((res_block, dilated.tensor)) flow = dilated.tensor ### resblocks, all kernels dilated by 4 params = self.layers[3] with DilatedTensor(flow, dilation_factor=4) as dilated: for j in range(params['repeat']): res_block = HighResBlock( params['n_features'], params['kernels'], acti_func=self.acti_func, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name='%s_%d' % (params['name'], j)) dilated.tensor = res_block(dilated.tensor, is_training) layer_instances.append((res_block, dilated.tensor)) flow = dilated.tensor ### 1x1x1 convolution layer params = self.layers[4] fc_layer = ConvolutionalLayer( n_output_chns=params['n_features'], kernel_size=params['kernel_size'], acti_func=self.acti_func, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name=params['name']) flow = fc_layer(flow, is_training) layer_instances.append((fc_layer, flow)) ### 1x1x1 convolution layer params = self.layers[5] fc_layer = ConvolutionalLayer( n_output_chns=params['n_features'], kernel_size=params['kernel_size'], acti_func=None, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name=params['name']) flow = fc_layer(flow, is_training) layer_instances.append((fc_layer, flow)) # set training properties if is_training: self._print(layer_instances) return layer_instances[-1][1] return layer_instances[layer_id][1]
def _print(self, list_of_layers): for (op, _) in list_of_layers: print(op)
[docs]class HighResBlock(TrainableLayer): """ This class defines a high-resolution block with residual connections kernels - specify kernel sizes of each convolutional layer - e.g.: kernels=(5, 5, 5) indicate three conv layers of kernel_size 5 with_res - whether to add residual connections to bypass the conv layers """
[docs] def __init__(self, n_output_chns, kernels=(3, 3), acti_func='relu', w_initializer=None, w_regularizer=None, with_res=True, name='HighResBlock'): """ :param n_output_chns: int, number of output channels :param kernels: list of layer kernel sizes :param acti_func: activation function to use :param w_initializer: weight initialisation for network :param w_regularizer: weight regularisation for network :param with_res: boolean, set to True if residual connection are to use :param name: layer name """ super(HighResBlock, self).__init__(name=name) self.n_output_chns = n_output_chns if hasattr(kernels, "__iter__"): # a list of layer kernel_sizes self.kernels = kernels else: # is a single number (indicating single layer) self.kernels = [kernels] self.acti_func = acti_func self.with_res = with_res self.initializers = {'w': w_initializer} self.regularizers = {'w': w_regularizer}
[docs] def layer_op(self, input_tensor, is_training): """ :param input_tensor: tensor, input to the network :param is_training: boolean, True if network is in training mode :return: tensor, output of the residual block """ output_tensor = input_tensor for (i, k) in enumerate(self.kernels): # create parameterised layers bn_op = BNLayer(regularizer=self.regularizers['w'], name='bn_{}'.format(i)) acti_op = ActiLayer(func=self.acti_func, regularizer=self.regularizers['w'], name='acti_{}'.format(i)) conv_op = ConvLayer(n_output_chns=self.n_output_chns, kernel_size=k, stride=1, w_initializer=self.initializers['w'], w_regularizer=self.regularizers['w'], name='conv_{}'.format(i)) # connect layers output_tensor = bn_op(output_tensor, is_training) output_tensor = acti_op(output_tensor) output_tensor = conv_op(output_tensor) # make residual connections if self.with_res: output_tensor = ElementwiseLayer('SUM')(output_tensor, input_tensor) return output_tensor