Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added valid and fixed padding. #43

Merged
merged 11 commits into from
May 4, 2020
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