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

Does Metalhead.classify support pretrained models other than VGG19 ? #30

Closed
terasakisatoshi opened this issue Oct 10, 2018 · 13 comments
Closed

Comments

@terasakisatoshi
Copy link

Hi, I'm trying to use pretrained model stored in this repository

VGG,
ResNet,
GoogleNet,
DenseNet
and so on.

I wrote the following code to see what happens.

using Metalhead
using Images
vgg = VGG19()
classify(vgg, "elephant.jpeg")

This works fine showing a kind ofelephant as result.

How about the other model e.g. DenseNet ?

dense = DenseNet()
classify(dense,"elephant.jpeg")
ERROR: MethodError: no method matching conv(::Array{Float32,4}, ::Array{Float64,4}; stride=(2, 2), pad=(3, 3), dilation=(1, 1))
Closest candidates are:
  conv(::AbstractArray, ::Flux.Tracker.TrackedArray; kw...) at /home/terasaki/.julia/packages/Flux/WgSRx/src/tracker/array.jl:342
  conv(::Flux.Tracker.TrackedArray, ::AbstractArray; kw...) at /home/terasaki/.julia/packages/Flux/WgSRx/src/tracker/array.jl:343
  conv(::A<:AbstractArray, ::A<:AbstractArray; pad, stride, dilation) where A<:AbstractArray at /home/terasaki/.julia/packages/NNlib/0EAe7/src/conv.jl:28
Stacktrace:
 [1] #_forward#433(::Base.Iterators.Pairs{Symbol,Tuple{Int64,Int64},Tuple{Symbol,Symbol,Symbol},NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}}, ::Function, ::typeof(NNlib.conv), ::Array{Float32,4}, ::TrackedArray{…,Array{Float64,4}}) at /home/terasaki/.julia/packages/Flux/WgSRx/src/tracker/array.jl:345
 [2] (::getfield(Flux.Tracker, Symbol("#kw##_forward")))(::NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}, ::typeof(Flux.Tracker._forward), ::typeof(NNlib.conv), ::Array{Float32,4}, ::TrackedArray{…,Array{Float64,4}}) at ./none:0
 [3] #track#1(::Base.Iterators.Pairs{Symbol,Tuple{Int64,Int64},Tuple{Symbol,Symbol,Symbol},NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}}, ::Function, ::typeof(NNlib.conv), ::Array{Float32,4}, ::Vararg{Any,N} where N) at /home/terasaki/.julia/packages/Flux/WgSRx/src/tracker/Tracker.jl:50
 [4] (::getfield(Flux.Tracker, Symbol("#kw##track")))(::NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}, ::typeof(Flux.Tracker.track), ::typeof(NNlib.conv), ::Array{Float32,4}, ::Vararg{Any,N} where N) at ./none:0
 [5] #conv#431(::Base.Iterators.Pairs{Symbol,Tuple{Int64,Int64},Tuple{Symbol,Symbol,Symbol},NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}}, ::Function, ::Array{Float32,4}, ::TrackedArray{…,Array{Float64,4}}) at /home/terasaki/.julia/packages/Flux/WgSRx/src/tracker/array.jl:342
 [6] (::getfield(NNlib, Symbol("#kw##conv")))(::NamedTuple{(:stride, :pad, :dilation),Tuple{Tuple{Int64,Int64},Tuple{Int64,Int64},Tuple{Int64,Int64}}}, ::typeof(NNlib.conv), ::Array{Float32,4}, ::TrackedArray{…,Array{Float64,4}}) at ./none:0
 [7] (::Flux.Conv{2,typeof(identity),TrackedArray{…,Array{Float64,4}},TrackedArray{…,Array{Float64,1}}})(::Array{Float32,4}) at /home/terasaki/.julia/packages/Flux/WgSRx/src/layers/conv.jl:44
 [8] (::getfield(Flux, Symbol("##60#61")))(::Array{Float32,4}, ::Flux.Conv{2,typeof(identity),TrackedArray{…,Array{Float64,4}},TrackedArray{…,Array{Float64,1}}}) at /home/terasaki/.julia/packages/Flux/WgSRx/src/layers/basic.jl:31
 [9] mapfoldl_impl(::typeof(identity), ::getfield(Flux, Symbol("##60#61")), ::NamedTuple{(:init,),Tuple{Array{Float32,4}}}, ::Array{Any,1}) at ./reduce.jl:43
 [10] Chain at ./reduce.jl:70 [inlined]
 [11] DenseNet at /home/terasaki/.julia/packages/Metalhead/fYeSU/src/densenet.jl:94 [inlined]
 [12] forward(::DenseNet, ::Array{ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}},2}) at /home/terasaki/.julia/packages/Metalhead/fYeSU/src/utils.jl:70
 [13] classify(::DenseNet, ::String) at /home/terasaki/.julia/packages/Metalhead/fYeSU/src/utils.jl:98
 [14] top-level scope at none:0

also ResNet, GoogleNet show similar error.

ERROR: MethodError: no method matching conv(::Array{Float32,4}, ::Array{Float64,4}; stride=(2, 2), pad=(3, 3), dilation=(1, 1))

Does Metalhead.classify support pretrained models other than VGG19 ?

@terasakisatoshi
Copy link
Author

On my PC, I'm using Julia1.0.1 with the following libraries

(v1.0) pkg> st
    Status `~/.julia/environments/v1.0/Project.toml`
  [c52e3926] Atom v0.7.8
  [fbb218c0] BSON v0.2.1
  [336ed68f] CSV v0.4.1
  [150eb455] CoordinateTransformations v0.5.0
  [3a865a2d] CuArrays v0.8.0
  [a93c6f00] DataFrames v0.14.1
  [ff70ec99] FastStyleTransfer v0.0.0 [`~/.julia/dev/FastStyleTransfer`]
  [587475ba] Flux v0.6.7+ #master (https://github.com/FluxML/Flux.jl.git)
  [7073ff75] IJulia v1.12.0
  [6218d12a] ImageMagick v0.7.1
  [916415d5] Images v0.16.0
  [682c06a0] JSON v0.19.0
  [e5e0dc1b] Juno v0.5.3
  [dbeba491] Metalhead v0.3.0
  [438e738f] PyCall v1.18.4

@terasakisatoshi
Copy link
Author

I found handle data as Array{Float64,N} works for ResNet. But result looks strange that beyonds my understanding.

using Flux: onecold
using Metalhead: ResNet, VGG19
using Images

# Take the len-by-len square of pixels at the center of image `im`
function center_crop(im, len)
    l2 = div(len,2)
    adjust = len % 2 == 0 ? 1 : 0
    return im[div(end,2)-l2:div(end,2)+l2-adjust,div(end,2)-l2:div(end,2)+l2-adjust]
end

# Resize an image such that its smallest dimension is the given length
function resize_smallest_dimension(im, len)
    reduction_factor = len/minimum(size(im)[1:2])
    new_size = size(im)
    new_size = (
            round(Int, size(im,1)*reduction_factor),
            round(Int, size(im,2)*reduction_factor),
    )
    if reduction_factor < 1.0
        # Images.jl's imresize() needs to first lowpass the image, it won't do it for us
        im = imfilter(im, KernelFactors.gaussian(0.75/reduction_factor), Inner())
    end
    return imresize(im, new_size)
end

function preprocess(im::AbstractMatrix{<:AbstractRGB})
    # Resize such that smallest edge is 256 pixels long
    im = resize_smallest_dimension(im, 256)

    # Center-crop to 224x224
    im = center_crop(im, 224)

    # Convert to channel view and normalize (these coefficients taken
    # from PyTorch's ImageNet normalization code)
    μ = [0.485, 0.456, 0.406]
    σ = [0.229, 0.224, 0.225]
    im = (channelview(im) .- μ)./σ

    # Convert from CHW (Image.jl's channel ordering) to WHCN (Flux.jl's ordering)
    # convert eltype as Float64
    return Float64.(permutedims(im, (3, 2, 1))[:,:,:,:].*255)
end

const label_txt = expanduser("~/.julia/packages/Metalhead/rGGAv/datasets/meta/ILSVRC_synset_mappings.txt")
imagenet_labels = String[]

for (idx, line) in enumerate(eachline(label_txt))
    synset = line[1:9]
    label = line[11:end]
    push!(imagenet_labels, label)
end

function main()
    model = ResNet()
    preprocessed = preprocess(load("elephant.jpeg"))
    result = imagenet_labels[onecold(model(preprocessed))][1]
    @show result
end

main()
julia> main()
result = "chain"
"chain"

julia> main()
result = "spotlight, spot"
"spotlight, spot"

julia> main()
result = "gong, tam-tam"
"gong, tam-tam"

@avik-pal
Copy link
Member

@terasakisatoshi There is some issue with the pretrained weights of Resnet and Densenet that were imported using ONNX. So I am not surprised by the incorrect results.
But I am not sure as to why you are getting different results every time.
And for the Float64 and Float32, there is a PR in NNlib which will remove this type constraints and it should resolve that issue.

@ekinakyurek
Copy link

ekinakyurek commented Dec 11, 2018

Sorry to bother you too much today,

Why don't you load batchnorm parameters in ResNet example?

@avik-pal
Copy link
Member

IIRC I had discussed this with @ayush1999. The onnx file imported had no batchnorm parameters. That's the reason the resnet doesn't perform as expected.

@ekinakyurek
Copy link

When I dump the .bson file, there are values such that:

Symbol("gpu_0/res3_1_branch2a_bn_b_0")                                        Symbol("gpu_0/res4_5_branch2b_w_0")
Symbol("gpu_0/res3_1_branch2a_bn_riv_0")                                      Symbol("gpu_0/res4_5_branch2c_bn_b_0")
Symbol("gpu_0/res3_1_branch2a_bn_rm_0")                                       Symbol("gpu_0/res4_5_branch2c_bn_riv_0")
Symbol("gpu_0/res3_1_branch2a_bn_s_0")                                        Symbol("gpu_0/res4_5_branch2c_bn_rm_0")

I believe:

bn_b -> batchnorm bias parameter
bn_s -> batchnorm scale parameter
bn_rm -> batchnorm running mean/momentum
bn_riv -> batchnorm running variance

@avik-pal
Copy link
Member

Thanks. I will add them as soon as possible. :)

@avik-pal
Copy link
Member

@ekinakyurek I have updated the Resnet Model. U can checkout the newmodels branch if you need to use it.

@ekinakyurek
Copy link

ekinakyurek commented Dec 17, 2018

I couldn't see newmodels branch. Is it working ?

@avik-pal
Copy link
Member

Here's the branch https://github.com/avik-pal/Metalhead.jl/tree/newmodels. Yes, it is giving meaningful predictions for the images that I tried. Though I haven't done a full accuracy measure.

@sungjuGit
Copy link

Maybe there is something wrong on my end but ResNet50 is not giving correct results. A picture of an elephant, for example, is classified as ""Persian cat". VGG19 seems to work fine. There might still be an issue with importing the pre-trained weights? Also, could you coordinate merging the whole thing with all the fixes under the default FluxML/Metalhead.jl? Very much appreciated.

@natema
Copy link
Contributor

natema commented Jun 18, 2020

IIRC I had discussed this with @ayush1999. The onnx file imported had no batchnorm parameters. That's the reason the resnet doesn't perform as expected.

I recently failed to import ResNet with ONNX.jl, and then succeeded with ONNXmutable.jl.
Then, I tried to compare ResNet50 imported with ONNXmutable.jl (call it resnetonnx) with the one imported with Metalhead.resnet50() (call it resnetmetal).
Probably I grossly misunderstand what kind of inputs the two networks expect.
At least, if I feed a rand(Float32, 224,224,3,1) to resnetmetal, it outputs a probability distribution. On the contrary, resnetonnx returns a vector of numbers which go from ca. -3 to ca. 6...

@darsnack
Copy link
Member

The latest release no longer has the classify interfaces. For the core issue that the pre-trained models were producing incorrect outputs, we are aware of the issue. The current release no longer provides pre-trained models, since we don't want to release incorrect models. We're working on re-training all models with Flux to provide working options in future releases.

I'm closing this issue for now, so that we only have #72 to track the incorrect results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants