ReactiveBayes / ExponentialFamily.jl

ExponentialFamily.jl is a Julia package that extends the functionality of Distributions.jl by providing a collection of exponential family distributions and customized implementations.
https://reactivebayes.github.io/ExponentialFamily.jl/
MIT License
12 stars 2 forks source link

Conversion Error with Categorical Distribution in ExponentialFamily.jl #202

Closed Nimrais closed 1 month ago

Nimrais commented 1 month ago

When attempting to create and manipulate an ExponentialFamilyDistribution with a Categorical distribution with natural parameters container ArrayPartition. The code fails during conversion. I think it's because NaturalToMean, but maybe it's supposed to be so however such behavior is quite inconvenient.

using RecursiveArrayTools
using ExponentialFamily
using Distributions

nat_params = ArrayPartition([1, 2], [3])
ef  = ExponentialFamilyDistribution(
    Categorical, nat_params, 3, nothing
)

@show ExponentialFamily.isproper(ef)
# true
@show convert(Distribution, ef)

Error

ERROR: MethodError: no method matching (::MeanToNatural{…})(::ExponentialFamilyDistribution{…})

Closest candidates are:
  (::MeanToNatural)(::Any, ::Nothing)
   @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:27
  (::MeanToNatural{T})(::AbstractVector, ::Nothing) where T<:Distribution
   @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:410
  (::MeanToNatural{Categorical{P} where P<:Real})(::Tuple{Any}, ::Any)
   @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/distributions/categorical.jl:48
  ...

Stacktrace:
 [1] macro expansion
   @ show.jl:1181 [inlined]
 [2] top-level scope
   @ ~/repos/ReactiveBayes/ExponentialFamily.jl/categorical_bug.jl:11
Some type information was truncated. Use `show(err)` to see complete types.

ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}}

Closest candidates are:
  convert(::Type{ArrayPartition{T, S}}, ::ArrayPartition{<:Any, <:Tuple{Vararg{Any, N}}}) where {N, T, S<:Tuple{Vararg{Any, N}}}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/K1bCr/src/array_partition.jl:543
  convert(::Type{T}, ::T) where T
   @ Base Base.jl:84
  convert(::Type{T}, ::T) where T<:AbstractArray
   @ Base abstractarray.jl:16
  ...

Stacktrace:
  [1] Categorical{Float64, ArrayPartition{…}}(xs::Base.OneTo{Int64}, ps::ArrayPartition{Float64, Tuple{…}}; check_args::Bool)
    @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:34
  [2] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:24 [inlined]
  [3] #_#115
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:31 [inlined]
  [4] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:29 [inlined]
  [5] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:34 [inlined]
  [6] convert(::Type{…}, ef::ExponentialFamilyDistribution{…})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:876
  [7] convert
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:849 [inlined]
  [8] mean(ef::ExponentialFamilyDistribution{Categorical{P} where P<:Real, ArrayPartition{Int64, Tuple{…}}, Int64, Nothing})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:910
  [9] macro expansion
    @ show.jl:1181 [inlined]
 [10] top-level scope
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/categorical_bug.jl:11
Some type information was truncated. Use `show(err)` to see complete types.

ExponentialFamily.isproper(ef) = true
ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}}

Closest candidates are:
  convert(::Type{ArrayPartition{T, S}}, ::ArrayPartition{<:Any, <:Tuple{Vararg{Any, N}}}) where {N, T, S<:Tuple{Vararg{Any, N}}}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/K1bCr/src/array_partition.jl:543
  convert(::Type{T}, ::T) where T
   @ Base Base.jl:84
  convert(::Type{T}, ::T) where T<:AbstractArray
   @ Base abstractarray.jl:16
  ...

Stacktrace:
  [1] Categorical{Float64, ArrayPartition{…}}(xs::Base.OneTo{Int64}, ps::ArrayPartition{Float64, Tuple{…}}; check_args::Bool)
    @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:34
  [2] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:24 [inlined]
  [3] #_#115
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:31 [inlined]
  [4] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:29 [inlined]
  [5] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:34 [inlined]
  [6] convert(::Type{…}, ef::ExponentialFamilyDistribution{…})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:876
  [7] convert
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:849 [inlined]
  [8] mean(ef::ExponentialFamilyDistribution{Categorical{P} where P<:Real, ArrayPartition{Int64, Tuple{…}}, Int64, Nothing})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:910
  [9] macro expansion
    @ show.jl:1181 [inlined]
 [10] top-level scope
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/categorical_bug.jl:11
Some type information was truncated. Use `show(err)` to see complete types.

ExponentialFamily.isproper(ef) = true
ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}}

Closest candidates are:
  convert(::Type{ArrayPartition{T, S}}, ::ArrayPartition{<:Any, <:Tuple{Vararg{Any, N}}}) where {N, T, S<:Tuple{Vararg{Any, N}}}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/K1bCr/src/array_partition.jl:543
  convert(::Type{T}, ::T) where T
   @ Base Base.jl:84
  convert(::Type{T}, ::T) where T<:AbstractArray
   @ Base abstractarray.jl:16
  ...

Stacktrace:
  [1] Categorical{Float64, ArrayPartition{…}}(xs::Base.OneTo{Int64}, ps::ArrayPartition{Float64, Tuple{…}}; check_args::Bool)
    @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:34
  [2] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:24 [inlined]
  [3] #_#115
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:31 [inlined]
  [4] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:29 [inlined]
  [5] DiscreteNonParametric
    @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:34 [inlined]
  [6] convert(::Type{…}, ef::ExponentialFamilyDistribution{…})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:876
  [7] convert
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:849 [inlined]
  [8] mean(ef::ExponentialFamilyDistribution{Categorical{P} where P<:Real, ArrayPartition{Int64, Tuple{…}}, Int64, Nothing})
    @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:910
  [9] macro expansion
    @ show.jl:1181 [inlined]
 [10] top-level scope
    @ ~/repos/ReactiveBayes/ExponentialFamily.jl/categorical_bug.jl:11
Some type information was truncated. Use `show(err)` to see complete types.

ExponentialFamily.isproper(ef) = true
ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}}

Closest candidates are:
  convert(::Type{ArrayPartition{T, S}}, ::ArrayPartition{<:Any, <:Tuple{Vararg{Any, N}}}) where {N, T, S<:Tuple{Vararg{Any, N}}}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/K1bCr/src/array_partition.jl:543
  convert(::Type{T}, ::T) where T
   @ Base Base.jl:84
  convert(::Type{T}, ::T) where T<:AbstractArray
   @ Base abstractarray.jl:16
  ...

Stacktrace:
 [1] Categorical{Float64, ArrayPartition{…}}(xs::Base.OneTo{Int64}, ps::ArrayPartition{Float64, Tuple{…}}; check_args::Bool)
   @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:34
 [2] DiscreteNonParametric
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:24 [inlined]
 [3] #_#115
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:31 [inlined]
 [4] DiscreteNonParametric
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:29 [inlined]
 [5] DiscreteNonParametric
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:34 [inlined]
 [6] convert(::Type{…}, ef::ExponentialFamilyDistribution{…})
   @ ExponentialFamily ~/repos/ReactiveBayes/ExponentialFamily.jl/src/exponential_family.jl:876
 [7] macro expansion
   @ show.jl:1181 [inlined]
 [8] top-level scope
   @ ~/repos/ReactiveBayes/ExponentialFamily.jl/categorical_bug.jl:11
Some type information was truncated. Use `show(err)` to see complete types.
Nimrais commented 1 month ago

Or maybe it's rather because convert propagate also the natural parameters container type.

bvdmitri commented 1 month ago

Good catch @Nimrais ! To me it seems like Categorical from Distributions.jl cannot be constructed from ArrayPartition, can you try that directly with just Distributions.jl?

also I see another bug in your example, the natural parameters are actually not proper (the last parameter must always be zero)

Nimrais commented 1 month ago

yeah, sure there more then one bug :)

bvdmitri commented 1 month ago

The solution would be to write a specialized convert method specifically for Categorical that would call ‘collect’ on the parameters before passing it to the constructor from Distributions.jl

Nimrais commented 1 month ago

Yeah seems you are right.

julia> Categorical(ArrayPartition([1/3, 1/3], [1/3]))
ERROR: MethodError: Cannot `convert` an object of type Vector{Float64} to an object of type ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}}

Closest candidates are:
  convert(::Type{ArrayPartition{T, S}}, ::ArrayPartition{<:Any, <:Tuple{Vararg{Any, N}}}) where {N, T, S<:Tuple{Vararg{Any, N}}}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/K1bCr/src/array_partition.jl:543
  convert(::Type{T}, ::T) where T
   @ Base Base.jl:84
  convert(::Type{T}, ::T) where T<:AbstractArray
   @ Base abstractarray.jl:16
  ...

Stacktrace:
 [1] Categorical{Float64, ArrayPartition{…}}(xs::Base.OneTo{Int64}, ps::ArrayPartition{Float64, Tuple{…}}; check_args::Bool)
   @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:34
 [2] DiscreteNonParametric
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/discretenonparametric.jl:24 [inlined]
 [3] #_#115
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:31 [inlined]
 [4] DiscreteNonParametric
   @ ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:29 [inlined]
 [5] (Categorical{P} where P<:Real)(p::ArrayPartition{Float64, Tuple{Vector{Float64}, Vector{Float64}}})
   @ Distributions ~/.julia/packages/Distributions/ji8PW/src/univariate/discrete/categorical.jl:34
 [6] top-level scope
   @ REPL[1]:1
Some type information was truncated. Use `show(err)` to see complete types.
Nimrais commented 1 month ago

I think we can also open a bug in Distributions.jl because it seems strange that one can not create a distribution with an ArrayPartition parameters.

Nimrais commented 1 month ago

I have opened an issue there https://github.com/JuliaStats/Distributions.jl/issues/1878

Nimrais commented 1 month ago

The solution would be to write a specialized convert method specifically for Categorical that would call ‘collect’ on the parameters before passing it to the constructor from Distributions.jl

Yeah we can do it, and once Distributions.jl will start to support ArrayPartition we would be able to remove it