Cannot use size(::Array{Int, 2}): ERROR: LoadError: UndefRefError: access to undefined reference

question

#1

I keep getting undefreferror whenever I call function size. Here is my code. The error is at the first if in function forward. Full error message is:

ERROR: LoadError: UndefRefError: access to undefined reference
 in #forward#55(::Array{Any,1}, ::Function, ::NN.CrossEntropyLoss, ::Array{Float64,2}, ::Array{Int64,2}) at /Users/haonanchen/Documents/CS/NN.jl/src/layers/CrossEntropyLoss.jl:37
 in forward(::NN.CrossEntropyLoss, ::Array{Float64,2}, ::Array{Int64,2}) at /Users/haonanchen/Documents/CS/NN.jl/src/layers/CrossEntropyLoss.jl:36
...


type CrossEntropyLoss <: LossCriteria
  x :: Array{Float64}
  y :: Array{Float64}
  classes :: Int64

  function CrossEntropyLoss()
    return new(Float64[], Float64[], 0)
  end
end

function init(l::CrossEntropyLoss, p::Union{Layer,Void}, config::Dict{String,Any}; kwargs...)
  if p==nothing || typeof(p)<:InputLayer
    error("Loss functions cannot be the first layer; makes no sense")
  end
  out_size = getOutputSize(p)
  l.classes = out_size[2]
  l.x      = Array{Float64}(out_size)
  l.y      = Array{Float64}(out_size)
end

function convert_to_one_hot(old_label::Array{Int, 2})
  m = size(old_label)[1]
  new_label::Array{Int,2}
  new_label=zeros(Int64,size(old_label,2)[1],l.classes)
  for i=1:m
    new_label[i][old_label[i]+1] = 1;
  end
end

"""
Invaraint for this to work:
  Label needs to be either a vector specifying each item's class or a matrix made up with multiple one hot vectors.
  In the former case, labels need to be in the range of 0:classes-1.
"""
function forward(l::CrossEntropyLoss, Y::Array{Float64,2}, label::Array{Int, 2}; kwargs...)
  if size(label)[2] == 1
    # convert one-dim label to one hot vectors
    label = convert_to_one_hot(label)
  end
  # from now on label is guaranteed to be of one-hot
  @assert size(Y) = size(label)
  loss = zeros(m)
  m,n = size(Y)
  for i=1:m
    log_sum = 0;
    for j=1:n
      p = Y[i,j]
      q = label[i,j]
      log_sum+=q*log(q/p)
    end
    loss = log_sum/n
    loss[i]=loss
  end
  x = Y;
  y = loss;
  # generate prediction
  pred = zeros(m)
  for i=1:m
    pred[i] = findmax(Y[i,:])[2]-1
  end
  return loss, pred
end

"""
for each row x, let x_i be j^th element, loss(x)=log(q_i/x_i)/n+...(other elements)
thus d(loss_j)/dx_j=1/n*x_j/q = x_j/(q_j*n)
where j is the num of classes,
"""
function backward(l::CrossEntropyLoss, label::Array{Int, 2};kwargs...)
  dldx = zeros(classes)
  m,n=size(l.x)
  for i=1:m
    for j=:1:n
      dlidx=l.x[i,j]/(label[i,j]*classes)
      dldlx[j]+=dlidx
    end
  end
  return dldx
end

#2

Please post an example that can actually be run and try remove as much unnecessary code as possible.


#3

My code is rather complicated and this class lies in about 4-5 layers under the main class. I have no idea which code should be removed since the error is extremely bizarre. Within the function forward, same error is raised whenever size() is used. I even tried in forward create a new array a=rand(3,4) and call size(a), which also results in the same error. I have no idea how to duplicate the error, and I am expecting it to be caused by some small syntax error in my code which could be discovered by looking solely at this class, if it’s not a bug with julia.


#4

new_label::Array{Int,2} That’s your problem line in convert_to_one_hot. new_label was not defined, hence the error ERROR: LoadError: UndefRefError: access to undefined reference.

Edited to add: ::T is a type assertion not a declaration.


#5

That only resulted in a deprecated syntax warning, which is removed once I remove that line and added a local to its next line, and the program still result in the old error. The line that triggers the error, as I mentioned in the original post, is the first line that calls size in the function forward.


#6

@assert size(Y) = size(label) should be ==

Edited to add: also not sure what version of Julia you are using, but this new_label[i][old_label[i]+1] would give a bounds error in v0.5.

julia> zeros(Int,3,3)[1][2]
ERROR: BoundsError
 in getindex(::Int64, ::Int64) at .\number.jl:21

#7

That actually solves the error! Julia’s faulty error message misguided for the entire evening. Not quite sure why the error in that line got reported way before. Error message is really not helping much here. Thank you!


#8

I think this is yet another manifestation of a Julia feature that I have complained about several times in this forum. The statement @assert size(Y) = size(label) actually redefines the function size. The new definition is nonsense, but it means that any invocation of size elsewhere in the function (even before the erroneous redefinition!) will not call Base.size. I made a similar mistake in one of my codes last year, accidentally redefining + because of a one-character typo, and the error I received was separated by many lines from the typo.

Let me again request of the core Julia developers that you change the language to require more verbiage when redefining functions in Base so that it is not so easy to do it unintentionally.


#9

Note that this does NOT redefine a function. It shadows the global definition which is a must in order to make adding Base functions not being a breaking change.


#10

Is it possible to make it so that the error message would be less misleading next time?


#11

@yuyichao, I really think you should brainstorm about this with your fellow core developers. Surely there is a compromise solution.

For example, there could be a warning when a user-defined function shadows a base function, especially if the shadowing definition appears at the non-global scope. Furthermore, there could be a macro to suppress the warning. And furthermore, the warning could appear only for one-line redefinitions (i.e., no warning for the more explicit: function size(..)) And @compat could also suppress the warning. How much breakage would result in this case?


#12

I get the following in v0.5 REPL. Seems pretty on point.

julia> @assert f(a) = rand()
ERROR: TypeError: non-boolean (#f) used in boolean context

#13

@mohamed82008, the problem occurs if the redefinition is inside a function. For example, the code below gives an error on the println statement rather than on the erroneous redefinition. And the error given is the same mystery error that the original poster saw.

module test_assert

function f(x,y)
    println("size of x is ", size(x))
    @assert size(x) = size(y)
    nothing
end

end

#14

Interesting. Good thing I never trust the error messages anyway!


#15

LUL thats bad practice tho


#16

Probably but occasionally useful.


#17

Definitely not. This happens all the time.

All of these are the job for a linter, and I definitely agree it should be added there if not already.

And that’s why we have many people posting questions where the answer is clearly in the error message.


#18

@yuyichao, if this statement in your previous message: “And that’s why we have many people posting questions where the answer is clearly in the error message” is referring to the current thread, then I disagree. The error message seen by the original poster is obscure. Furthermore, the first responder, @kristoffer.carlsson, is an experienced Julia programmer, and he also did not spot the mistake (nor did I).

Again, I request that you do not dismiss this issue so quickly. An important objective in language design is to make it difficult for the programmer to shoot himself/herself in the foot.


#19

No and that’s why I explicitly quote what I’m responding to.

And again, I definitely agree this is a good thing for a linter. Adding warning for things that can be useful and can be bad is always anony.


#20

And since the OP didn’t cross link I’ll repeat my comment from https://github.com/JuliaLang/julia/issues/22269#issuecomment-306871697 . The error message in this case is particularly bad due to https://github.com/JuliaLang/julia/issues/20016 . The error message should be ERROR: UndefVarError: size not defined. Though still not pointing to the exact location, it is much more clear what’s the issue is and we can certainly print a better error message about it just like what we do for a method error when the name refer to a method in the enclosing scope/base. That will not require printing annoy warnings which we should not do.