Skip to content

Commit

Permalink
Added valid and fixed padding to conv2d layer (#43)
Browse files Browse the repository at this point in the history
Added valid and fixed padding to conv2d layer.

Contributors: Adam Hillier

Co-authored-by: Vincent Tjeng <vincent.tjeng@gmail.com>
  • Loading branch information
samuelemarro and vtjeng authored May 4, 2020
1 parent d23bdb2 commit f0ab4cb
Show file tree
Hide file tree
Showing 3 changed files with 584 additions and 113 deletions.
115 changes: 93 additions & 22 deletions src/net_components/layers/conv2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ using JuMP
using ConditionalJuMP

export Conv2d
export Padding, SamePadding, ValidPadding

struct SamePadding end
Base.show(io::IO, p::SamePadding) = print(io, "same")
struct ValidPadding end
Base.show(io::IO, p::ValidPadding) = print(io, "valid")

FixedPadding = Union{Int, Tuple{Int, Int}, Tuple{Int, Int, Int, Int}}
Padding = Union{SamePadding, ValidPadding, FixedPadding}

"""
$(TYPEDEF)
Expand All @@ -18,25 +27,30 @@ $(FIELDS)
filter::Array{T, 4}
bias::Array{U, 1}
stride::V
padding::Padding

function Conv2d{T, U, V}(filter::Array{T, 4}, bias::Array{U, 1}, stride::V) where {T<:JuMPReal, U<:JuMPReal, V<:Int64}
function Conv2d{T, U, V}(filter::Array{T, 4}, bias::Array{U, 1}, stride::V, padding::Padding) where {T<:JuMPReal, U<:JuMPReal, V<:Int64}
(filter_height, filter_width, filter_in_channels, filter_out_channels) = size(filter)
bias_out_channels = length(bias)
@assert(
filter_out_channels == bias_out_channels,
"For this convolution layer, number of output channels in filter, $filter_out_channels, does not match number of output channels in bias, $bias_out_channels."
)
return new(filter, bias, stride)
return new(filter, bias, stride, padding)
end

end

function Conv2d(filter::Array{T, 4}, bias::Array{U, 1}, stride::V, padding::Padding) where {T<:JuMPReal, U<:JuMPReal, V<:Int64}
Conv2d{T, U, V}(filter, bias, stride, padding)
end

function Conv2d(filter::Array{T, 4}, bias::Array{U, 1}, stride::V) where {T<:JuMPReal, U<:JuMPReal, V<:Int64}
Conv2d{T, U, V}(filter, bias, stride)
Conv2d{T, U, V}(filter, bias, stride, SamePadding())
end

function Conv2d(filter::Array{T, 4}, bias::Array{U, 1}) where {T<:JuMPReal, U<:JuMPReal}
Conv2d(filter, bias, 1)
Conv2d(filter, bias, 1, SamePadding())
end

"""
Expand All @@ -59,8 +73,9 @@ end
function Base.show(io::IO, p::Conv2d)
(filter_height, filter_width, filter_in_channels, filter_out_channels) = size(p.filter)
stride = p.stride
padding = p.padding
print(io,
"Conv2d($filter_in_channels, $filter_out_channels, kernel_size=($(filter_height), $(filter_width)), stride=($(stride), $(stride)), padding=same)"
"Conv2d($filter_in_channels, $filter_out_channels, kernel_size=($(filter_height), $(filter_width)), stride=($(stride), $(stride)), padding=$(padding))"
)
end

Expand All @@ -86,15 +101,81 @@ function increment!(s::JuMP.AffExpr, input_val::Real, filter_val::JuMP.Variable)
push!(s, Float64(input_val), filter_val)
end

function compute_output_parameters(
in_height::Int, in_width::Int,
filter_height::Int, filter_width::Int, stride::Int,
padding::FixedPadding
)::Tuple{NTuple{2, Int}, NTuple{2, Int}}
(top_padding, bottom_padding, left_padding, right_padding) = compute_padding_values(padding)
out_height = round(Int, (in_height + top_padding + bottom_padding - filter_height) / stride, RoundDown) + 1
out_width = round(Int, (in_width + left_padding + right_padding - filter_width) / stride, RoundDown) + 1

output_size = (out_height, out_width)
filter_offset = (top_padding, left_padding)
return (output_size, filter_offset)
end

function compute_output_parameters(
in_height::Int, in_width::Int,
filter_height::Int, filter_width::Int, stride::Int,
padding::SamePadding
)::Tuple{NTuple{2, Int}, NTuple{2, Int}}
out_height = round(Int, in_height/stride, RoundUp)
out_width = round(Int, in_width/stride, RoundUp)
pad_along_height = max((out_height - 1)*stride + filter_height - in_height, 0)
pad_along_width = max((out_width - 1)*stride + filter_width - in_width, 0)
filter_height_offset = round(Int, pad_along_height/2, RoundDown)
filter_width_offset = round(Int, pad_along_width/2, RoundDown)

output_size = (out_height, out_width)
filter_offset = (filter_height_offset, filter_width_offset)
return (output_size, filter_offset)
end

function compute_output_parameters(
in_height::Int, in_width::Int,
filter_height::Int, filter_width::Int, stride::Int,
padding::ValidPadding
)::Tuple{NTuple{2, Int}, NTuple{2, Int}}
out_height = round(Int, (in_height + 1 - filter_height) / stride, RoundUp)
out_width = round(Int, (in_width + 1 - filter_width) / stride, RoundUp)
return((out_height, out_width), (0, 0))
end

function compute_padding_values(
padding::Int
)::NTuple{4, Int}
return (padding, padding, padding, padding)
end

function compute_padding_values(
padding::NTuple{2, Int}
)::NTuple{4, Int}
(y_padding, x_padding) = padding
return (y_padding, y_padding, x_padding, x_padding)
end

function compute_padding_values(
padding::NTuple{4, Int}
)::NTuple{4, Int}
return padding
end

"""
$(SIGNATURES)
Computes the result of convolving `input` with the `filter` and `bias` stored in `params`.
Mirrors `tf.nn.conv2d` from the `tensorflow` package, with `strides = [1, 1, 1, 1],
padding = 'SAME'`.
Mirrors `tf.nn.conv2d` from the `tensorflow` package, with
`strides = [1, params.stride, params.stride, 1]`.
Supports three types of padding:
- 'same': Specify via `SamePadding()`. Padding is added so that the output has the same size as the input.
- 'valid': Specify via `FixedPadding()`. No padding is added.
- 'fixed': Specify via:
- A single integer, interpreted as padding for both axes
- A tuple of two integers, interpreted as (y_padding, x_padding)
- A tuple of four integers, interpreted as (top, bottom, left, right)
# Throws
* AssertionError if `input` and `filter` are not compatible.
Expand All @@ -104,34 +185,24 @@ function conv2d(
params::Conv2d{U, V}) where {T<:JuMPReal, U<:JuMPReal, V<:JuMPReal}

if T<:JuMPLinearType || U<:JuMPLinearType || V<:JuMPLinearType
Memento.info(MIPVerify.LOGGER, "Applying $(params) ... ")
info(MIPVerify.LOGGER, "Applying $(params) ... ")
end
filter = params.filter
stride = params.stride
padding = params.padding

(batch, in_height, in_width, input_in_channels) = size(input)
(filter_height, filter_width, filter_in_channels, filter_out_channels) = size(filter)

@assert(
input_in_channels == filter_in_channels,
input_in_channels == filter_in_channels,
"Number of channels in input, $input_in_channels, does not match number of channels, $filter_in_channels, that filters operate on."
)

out_height = round(Int, in_height/stride, RoundUp)
out_width = round(Int, in_width/stride, RoundUp)
output_size = (batch, out_height, out_width, filter_out_channels)

# Considered using offset arrays here, but could not get it working.
((out_height, out_width), (filter_height_offset, filter_width_offset)) = compute_output_parameters(in_height, in_width, filter_height, filter_width, stride, padding)
output_size = (batch, out_height, out_width, filter_out_channels)

# Calculating appropriate offsets so that center of kernel is matched with
# cell at which correlation is being calculated. Note that tensorflow
# chooses a specific convention for a dimension with even size which we
# replicate here.
pad_along_height = max((out_height - 1)*stride + filter_height - in_height, 0)
pad_along_width = max((out_width - 1)*stride + filter_width - in_width, 0)
filter_height_offset = round(Int, pad_along_height/2, RoundDown)
filter_width_offset = round(Int, pad_along_width/2, RoundDown)

W = Base.promote_op(+, V, Base.promote_op(*, T, U))
output = Array{W}(undef, output_size)

Expand Down
8 changes: 5 additions & 3 deletions src/utils/import_weights.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export get_matrix_params, get_conv_params, get_example_network_params
"""
$(SIGNATURES)
Helper function to import the parameters for a layer carrying out matrix multiplication
Helper function to import the parameters for a layer carrying out matrix multiplication
(e.g. fully connected layer / softmax layer) from `param_dict` as a
[`Linear`](@ref) object.
Expand Down Expand Up @@ -62,13 +62,15 @@ function get_conv_params(
expected_size::NTuple{4, Int};
matrix_name::String = "weight",
bias_name::String = "bias",
expected_stride::Integer = 1
expected_stride::Integer = 1,
padding::Padding = SamePadding()
)::Conv2d

params = Conv2d(
param_dict["$layer_name/$matrix_name"],
dropdims(param_dict["$layer_name/$bias_name"], dims=1),
expected_stride
expected_stride,
padding
)

check_size(params, expected_size)
Expand Down
Loading

0 comments on commit f0ab4cb

Please sign in to comment.