bguisard / CarND-Vehicle-Detection

Vehicle Detection Project for Udacity's Self-Driving Car ND
5 stars 1 forks source link

No dense connectivity #1

Open titu1994 opened 7 years ago

titu1994 commented 7 years ago

Hi, you sent a PR to me a few months back about corrections to the DenseBlock.

I wanted to tell you that your current implementation (with those corrections) does not actually have any dense connectivity at all.

This is due to your change in the DenseBlock. It should concatenate the x_list, not x and cb. If you concatenate x and cb, you are basically creating a U-Net and not a DenseNetFCN.

The image below is from your script - visualized using Keras. http://imgur.com/a/vJAUa

This is the image with the correction from concatenate([x, cb]) to concatenate(x_list). - https://imgur.com/a/U3NDD

titu1994 commented 7 years ago

Script used to create images (extracted from your script) :

import cv2
import numpy as np

import random
import copy

import keras.backend as K

from keras.layers import Activation, BatchNormalization, Conv2D, Dropout, concatenate
from keras.layers import Dense, AveragePooling2D, GlobalAveragePooling2D, MaxPooling2D
from keras.layers import UpSampling2D, Conv2DTranspose, Reshape
from keras.regularizers import l2

"""
Code for the DenseNet implementation based on:
https://github.com/titu1994/DenseNet/
[1] [Densely Connected Convolutional Networks](https://arxiv.org/pdf/1608.06993v3.pdf)
[2] [The One Hundred Layers Tiramisu: Fully Convolutional DenseNets for Semantic Segmentation](https://arxiv.org/pdf/1611.09326v2.pdf)
"""

def ConvBlock(x, n_filters, bottleneck=False, p=0, decay=1e-4):

    if bottleneck:
        x = BatchNormalization(gamma_regularizer=l2(decay), beta_regularizer=l2(decay))(x)
        x = Activation('relu')(x)
        x = Conv2D(n_filters * 4, 1, padding='same', use_bias=False,
                   kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(x)

    x = BatchNormalization(gamma_regularizer=l2(decay), beta_regularizer=l2(decay))(x)
    x = Activation('relu')(x)
    x = Conv2D(n_filters, 3, padding='same', use_bias=False,
               kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(x)

    if p > 0:
        x = Dropout(p)(x)

    return x

def DenseBlock(x, n_layers, n_filters, growth_rate, p=0, decay=1e-4,
               bottleneck=False, grow_nb_filters=True, return_concat_list=False):

    x_list = [x]

    for i in range(n_layers):
        cb = ConvBlock(x, growth_rate, bottleneck, p, decay)
        x_list.append(cb)

        x = concatenate(x_list)

        if grow_nb_filters:
            n_filters += growth_rate

    if return_concat_list:
        return x, n_filters, x_list
    else:
        return x, n_filters

def TransitionBlock(x, n_filters, compression=1, p=0, decay=1e-4, kernel_sz=3):

    x = BatchNormalization(gamma_regularizer=l2(decay), beta_regularizer=l2(decay))(x)
    x = Activation('relu')(x)
    x = Conv2D(int(n_filters * compression), kernel_sz, padding='same', use_bias=False,
               kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(x)

    if p > 0:
        x = Dropout(p)(x)

    x = AveragePooling2D(pool_size=(2, 2), strides=(2, 2))(x)

    return x

def TransitionUpBlock(x, n_filters, decay=1e-4, up_type='upsampling'):

    if up_type == 'upsampling':
        x = UpSampling2D()(x)
    else:
        x = Conv2DTranspose(n_filters, 3, padding='same', activation='relu',
                            strides=(2, 2), kernel_initializer='he_uniform')(x)
    return x

def get_DenseNet(n_classes, img_input, depth=40, n_dense_blocks=3, growth_rate=12,
                 n_filters=-1, n_layers_per_block=-1, bottleneck=False, compression=1,
                 p=0, decay=1e-4, large_input=False, include_top=True, activation='softmax'):

    assert (depth - 4) % 3 == 0, 'Depth must be 3 N + 4'

    if compression != 0.0:
        assert compression <= 1.0 and compression > 0.0, 'compression range needs to be between 0.0 and 1.0'

    # layers in each dense block
    if type(n_layers_per_block) is list or type(n_layers_per_block) is tuple:
        nb_layers = list(n_layers_per_block)  # Convert tuple to list

        assert len(nb_layers) == (n_dense_blocks + 1), 'If list, nb_layer is used as provided. ' \
                                                       'Note that list size must be (nb_dense_block + 1)'
        final_nb_layer = nb_layers[-1]
        nb_layers = nb_layers[:-1]
    else:
        if n_layers_per_block == -1:
            count = int((depth - 4) / 3)
            nb_layers = [count for _ in range(n_dense_blocks)]
            final_nb_layer = count
        else:
            final_nb_layer = n_layers_per_block
            nb_layers = [n_layers_per_block] * n_dense_blocks

    if bottleneck:
        nb_layers = [int(layer // 2) for layer in nb_layers]

    # compute initial nb_filter if -1, else accept users initial nb_filter
    if n_filters <= 0:
        n_filters = 2 * growth_rate

    # Initial convolution
    if large_input:
        x = Conv2D(n_filters, 7, strides=(2, 2), padding='same', use_bias=False, name='initial_conv2D',
                   kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(img_input)
        x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding='same')(x)
    else:
        x = Conv2D(n_filters, 3, padding='same', use_bias=False, name='initial_conv2D',
                   kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(img_input)

    # Add dense blocks
    for block_idx in range(n_dense_blocks - 1):
        x, n_filters = DenseBlock(x, nb_layers[block_idx], n_filters, growth_rate,
                                  p=p, decay=decay, bottleneck=bottleneck)
        # add transition_block
        x = TransitionBlock(x, n_filters, compression=compression, p=p, decay=decay)

        n_filters = int(n_filters * compression)

    # The last dense_block does not have a transition_block
    x, n_filters = DenseBlock(x, final_nb_layer, n_filters, growth_rate,
                              p=p, decay=decay, bottleneck=bottleneck)

    # Final BN-RELU-POOL
    x = BatchNormalization(gamma_regularizer=l2(decay), beta_regularizer=l2(decay))(x)
    x = Activation('relu')(x)
    x = GlobalAveragePooling2D()(x)

    if include_top:
        x = Dense(n_classes, activation=activation, W_regularizer=l2(decay), b_regularizer=l2(decay))(x)

    return x

def get_FCN_DenseNet(img_input, n_classes=1, n_dense_blocks=5, growth_rate=16,
                     n_layers_per_block=4, compression=1, p=0, decay=1e-4,
                     input_shape=(224, 224, 3), init_conv_filters=48, include_top=True,
                     activation='softmax', upsampling_conv=128, upsampling_type='upsampling',
                     verbose=0):

    upsampling_type = upsampling_type.lower()

    if activation not in ['softmax', 'sigmoid']:
        raise ValueError('activation must be either "softmax" or "sigmoid"')

    if activation == 'sigmoid' and n_classes != 1:
        raise ValueError('sigmoid activation can only be used when classes = 1')

    if compression != 0.0:
        assert compression <= 1.0 and compression > 0.0, 'compression range needs to be between 0.0 and 1.0'

    # check if upsampling_conv has minimum number of filters
    # minimum is set to 12, as at least 3 color channels are needed for correct upsampling
    assert upsampling_conv > 12 and upsampling_conv % 4 == 0, 'Parameter `upsampling_conv` number of channels must ' \
                                                              'be a positive number divisible by 4 and greater ' \
                                                              'than 12'

    # layers in each dense block
    if type(n_layers_per_block) is list or type(n_layers_per_block) is tuple:
        nb_layers = list(n_layers_per_block)  # Convert tuple to list

        assert len(nb_layers) == (n_dense_blocks + 1), 'If list, nb_layer is used as provided. ' \
                                                       'Note that list size must be (nb_dense_block + 1)'

        bottleneck_nb_layers = nb_layers[-1]
        rev_layers = nb_layers[::-1]
        nb_layers.extend(rev_layers[1:])
    else:
        bottleneck_nb_layers = n_layers_per_block
        nb_layers = [n_layers_per_block] * (2 * n_dense_blocks + 1)

    # Initial convolution
    x = Conv2D(init_conv_filters, 3, padding='same', use_bias=False, name='initial_conv2D',
               kernel_initializer='he_uniform', kernel_regularizer=l2(decay))(img_input)

    n_filters = init_conv_filters

    skip_list = []

    # Add dense blocks and transition down blocks
    for block_idx in range(n_dense_blocks):
        if verbose > 0:
            print ("\nAdding Dense Block {}".format(block_idx))
            print ("Layers: {}".format(nb_layers[block_idx]))
            print ("Filters: {}".format(n_filters))
            print ("Growth Rate: {}".format(growth_rate))
        x, n_filters = DenseBlock(x, nb_layers[block_idx], n_filters, growth_rate,
                                  p=p, decay=decay, bottleneck=False)

        # store skip connections
        skip_list.append(x)

        # add transition_block
        if verbose > 0:
            print ("\nAdding TD Block {}".format(block_idx))
            print ("Filters: {}".format(n_filters))
            print ("Compression Rate: {}".format(compression))
        x = TransitionBlock(x, n_filters, compression=compression, p=p, decay=decay, kernel_sz=1)

        n_filters = int(n_filters * compression)

    # The last dense block does not have a transition down block
    # the block below returns the concatenated feature map without the concatenation of the input
    if verbose > 0:
        print ("\nAdding Bottleneck Block")
        print ("Layers: {}".format(bottleneck_nb_layers))
        print ("Filters: {}".format(n_filters))
        print ("Growth Rate: {}".format(growth_rate))

    _, n_filters, concat_list = DenseBlock(x, bottleneck_nb_layers, n_filters, growth_rate,
                                           p=p, decay=decay, bottleneck=False,
                                           return_concat_list=True)
    # Reverse the skip list
    skip_list = skip_list[::-1]

    # Add dense blocks and transition up blocks
    for block_idx in range(n_dense_blocks):
        n_filters_keep = growth_rate * nb_layers[n_dense_blocks + block_idx]

        # Upsamplig block upsamples only the feature maps
        l = concatenate(concat_list[1:])

        if verbose > 0:
            print ("\nAdding TU Block {}".format(block_idx))
            print ("Filters: {}".format(n_filters_keep))
        t = TransitionUpBlock(l, n_filters=n_filters_keep, up_type=upsampling_type)

        # concatenate skip connection with transition block
        x = concatenate([t, skip_list[block_idx]])

        # To prevent feature map size to grow in upsampling dense blocks
        # we set grow_nb_filters to False.
        if verbose > 0:
            print ("\nAdding Dense Block {}".format(block_idx))
            print ("Layers: {}".format(nb_layers[n_dense_blocks + block_idx + 1]))
            print ("Filters: {}".format(growth_rate))
            print ("Growth Rate: {}".format(growth_rate))
        DB_up, n_filters, concat_list = DenseBlock(x, nb_layers[n_dense_blocks + block_idx + 1],
                                                   n_filters=growth_rate, growth_rate=growth_rate,
                                                   p=p, decay=decay, bottleneck=False,
                                                   return_concat_list=True, grow_nb_filters=False)

    if include_top:
        x = Conv2D(n_classes, 1, padding='same', activation='linear', use_bias=False,
                   kernel_regularizer=l2(decay))(DB_up)
        rows, cols, channels = input_shape

        x = Reshape((rows * cols, n_classes))(x)
        x = Activation(activation)(x)
        x = Reshape((rows, cols, n_classes))(x)

    return x

if __name__ == '__main__':
    from keras.models import Model
    from keras.layers import Input

    input_shape = (224, 224, 3)
    img_input = Input(shape=input_shape)

    from keras.utils.vis_utils import plot_model
    model = get_FCN_DenseNet(img_input)
    model = Model(img_input, model)

    model.summary()

    plot_model(model, 'external_test _with_correction.png', show_shapes=False)