# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function
import functools
from collections import namedtuple
import tensorflow as tf
from niftynet.layer import layer_util
from niftynet.layer.base_layer import TrainableLayer
from niftynet.layer.bn import BNLayer
from niftynet.layer.convolution import ConvolutionalLayer
from niftynet.layer.fully_connected import FCLayer
from niftynet.network.base_net import BaseNet
ResNetDesc = namedtuple('ResNetDesc', ['bn', 'fc', 'conv1', 'blocks'])
[docs]class ResNet(BaseNet):
"""
### Description
implementation of Res-Net:
He et al., "Identity Mappings in Deep Residual Networks", arXiv:1603.05027v3
### Building Blocks
[CONV] - Convolutional layer, no activation, no batch norm
(s)[DOWNRES] - Downsample residual block.
Each block is composed of a first bottleneck block with stride s,
followed by n_blocks_per_resolution bottleneck blocks with stride 1.
[FC] - Fully connected layer with nr output channels == num_classes
### Diagram
INPUT --> [CONV] -->(s=1)[DOWNRES] --> (s=2)[DOWNRES]x2 --> BN, ReLU, mean --> [FC] --> OUTPUT
### Constraints
"""
[docs] def __init__(self,
num_classes,
n_features=[16, 64, 128, 256],
n_blocks_per_resolution=10,
w_initializer=None,
w_regularizer=None,
b_initializer=None,
b_regularizer=None,
acti_func='relu',
name='ResNet'):
"""
:param num_classes: int, number of channels of output
:param n_features: array, number of features per ResNet block
:param n_blocks_per_resolution: int, number of BottleneckBlock per DownResBlock
: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: ctivation function to use
:param name: layer name
"""
super(ResNet, 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.n_features = n_features
self.n_blocks_per_resolution = n_blocks_per_resolution
self.Conv = functools.partial(ConvolutionalLayer,
w_initializer=w_initializer,
w_regularizer=w_regularizer,
b_initializer=b_initializer,
b_regularizer=b_regularizer,
preactivation=True,
acti_func=acti_func)
[docs] def create(self):
"""
:return: tuple with batch norm layer, fully connected layer, first conv layer and all residual blocks
"""
bn = BNLayer()
fc = FCLayer(self.num_classes)
conv1 = self.Conv(self.n_features[0],
acti_func=None,
feature_normalization=None)
blocks = []
blocks += [
DownResBlock(self.n_features[1], self.n_blocks_per_resolution, 1,
self.Conv)
]
for n in self.n_features[2:]:
blocks += [
DownResBlock(n, self.n_blocks_per_resolution, 2, self.Conv)
]
return ResNetDesc(bn=bn, fc=fc, conv1=conv1, blocks=blocks)
[docs] def layer_op(self, images, is_training=True, **unused_kwargs):
"""
:param images: tensor, input to the network
:param is_training: boolean, True if network is in training mode
:param unused_kwargs: not in use
:return: tensor, output of the final fully connected layer
"""
layers = self.create()
out = layers.conv1(images, is_training)
for block in layers.blocks:
out = block(out, is_training)
spatial_rank = layer_util.infer_spatial_rank(out)
axis_to_avg = [dim + 1 for dim in range(spatial_rank)]
out = tf.reduce_mean(tf.nn.relu(layers.bn(out, is_training)),
axis=axis_to_avg)
return layers.fc(out)
BottleneckBlockDesc1 = namedtuple('BottleneckBlockDesc1', ['conv'])
BottleneckBlockDesc2 = namedtuple('BottleneckBlockDesc2',
['common_bn', 'conv', 'conv_shortcut'])
[docs]class BottleneckBlock(TrainableLayer):
[docs] def __init__(self, n_output_chns, stride, Conv, name='bottleneck'):
"""
:param n_output_chns: int, number of output channels
:param stride: int, stride to use in the convolutional layers
:param Conv: layer, convolutional layer
:param name: layer name
"""
self.n_output_chns = n_output_chns
self.stride = stride
self.bottle_neck_chns = n_output_chns // 4
self.Conv = Conv
super(BottleneckBlock, self).__init__(name=name)
[docs] def create(self, input_chns):
"""
:param input_chns: int, number of input channel
:return: tuple, with series of convolutional layers
"""
if self.n_output_chns == input_chns:
b1 = self.Conv(self.bottle_neck_chns,
kernel_size=1,
stride=self.stride)
b2 = self.Conv(self.bottle_neck_chns, kernel_size=3)
b3 = self.Conv(self.n_output_chns, 1)
return BottleneckBlockDesc1(conv=[b1, b2, b3])
else:
b1 = BNLayer()
b2 = self.Conv(self.bottle_neck_chns,
kernel_size=1,
stride=self.stride,
acti_func=None,
feature_normalization=None)
b3 = self.Conv(self.bottle_neck_chns, kernel_size=3)
b4 = self.Conv(self.n_output_chns, kernel_size=1)
b5 = self.Conv(self.n_output_chns,
kernel_size=1,
stride=self.stride,
acti_func=None,
feature_normalization=None)
return BottleneckBlockDesc2(common_bn=b1,
conv=[b2, b3, b4],
conv_shortcut=b5)
[docs] def layer_op(self, images, is_training=True):
"""
:param images: tensor, input to the BottleNeck block
:param is_training: boolean, True if network is in training mode
:return: tensor, output of the BottleNeck block
"""
layers = self.create(images.shape[-1])
if self.n_output_chns == images.shape[-1]:
out = layers.conv[0](images, is_training)
out = layers.conv[1](out, is_training)
out = layers.conv[2](out, is_training)
out = out + images
else:
tmp = tf.nn.relu(layers.common_bn(images, is_training))
out = layers.conv[0](tmp, is_training)
out = layers.conv[1](out, is_training)
out = layers.conv[2](out, is_training)
out = layers.conv_shortcut(tmp, is_training) + out
print(out.shape)
return out
DownResBlockDesc = namedtuple('DownResBlockDesc', ['blocks'])
[docs]class DownResBlock(TrainableLayer):
[docs] def __init__(self, n_output_chns, count, stride, Conv, name='downres'):
"""
:param n_output_chns: int, number of output channels
:param count: int, number of BottleneckBlocks to generate
:param stride: int, stride for convolutional layer
:param Conv: Layer, convolutional layer
:param name: layer name
"""
self.count = count
self.stride = stride
self.n_output_chns = n_output_chns
self.Conv = Conv
super(DownResBlock, self).__init__(name=name)
[docs] def create(self):
"""
:return: tuple, containing all the Bottleneck blocks composing the DownRes block
"""
blocks = []
blocks += [BottleneckBlock(self.n_output_chns, self.stride, self.Conv)]
for it in range(1, self.count):
blocks += [BottleneckBlock(self.n_output_chns, 1, self.Conv)]
return DownResBlockDesc(blocks=blocks)
[docs] def layer_op(self, images, is_training):
"""
:param images: tensor, input to the DownRes block
:param is_training: is_training: boolean, True if network is in training mode
:return: tensor, output of the DownRes block
"""
layers = self.create()
out = images
for l in layers.blocks:
out = l(out, is_training)
return out