Replies: 2 comments 32 replies
-
I like this a lot, because it's trying to deal with the same things that we're trying to solve in KernelFuntions.jl by insisting that everything subtype
I'm pro- this. Yay for pooling resources. I was wondering: suppose that I have a collection of things of type |
Beta Was this translation helpful? Give feedback.
-
I've outlined a proposal for an AbstractArraysOfArrays (or other name) API here: JuliaStats/StatsBase.jl#518 (comment) @torfjelde and @ToucheSir I think such a common API for nested arrays would provide a good basis for efficient batched (and possibly GPU-compatible) transformations. |
Beta Was this translation helpful? Give feedback.
-
19th of February, 2020 was a dark day in the history of Bijectors.jl. An absolute idiot decided it was a good idea to encode the "dimensionality" in the bijector type, i.e.
Bijector{N}
. Since then the code has been riddled with `N` everywhere and seemingly redundant implementations for different inputs, all of which could have avoided if it wasn't for this idiot.This idiot was me. And now I'm back, still an idiot, but an idiot who has been burned one too many times.
Why do we care
In Bijectors.jl we have
logabsdetjac
for which we need to disambiguate whether aMatrix
is a single matrix input, i.e. the bijector is acting on the space of matrices, in which case the return-value is aReal
, or if it's a collection of vectors (reals), in which case the return-value is aVector
(Matrix
).More about this can be found in the original issue: #35.
Current implementation
The solution of the above issue was to include the dimensionality of the input-space in the bijector, i.e.
Bijector
becameBijector{N}
, with0
representingReal
.This works but it sucks, and leads to a lot of redundant code, e.g.
Bijectors.jl/src/bijectors/exp_log.jl
Lines 33 to 40 in 694db6f
And it's really annoying to deal with this when implementing a new bijector.
Aaaaand there are sooo many examples of users mistakenly applying bijectors to the "wrong" dimensionality (also we need docs; but I'll address that separately to this issue).
New solution?
Make
Bijector{N}
intoBijector
again! Easy.And introduce a simple
Batch
struct:Batch-computation in Bijectors.jl then goes as follows, where
b
is aBijector
andxs
is a "collection of inputs":xs
inBatch
, i.e.batch = Batch(xs)
.Batch
where we simply unwrap usingvalue(batch)
, compute, and then wrap againBatch(out)
, e.g. the default method oflogabsdetjac
could belogabsdetjac(b::Bijector, xs::VectorBatch) = Batch(map(x -> logabsdetjac(b, x), value(xs)))
.This is nice because it allows the following:
VectorBatch
we have defaults usingBatch(map(..., value(xs)))
while forArrayBatch
we have defaults usingmapslices
1 assuming the last dimension is the batch-dimension2.value(xs)
, e.g.Exp
(as an elementwise transformation) can be applied to any array-size by using broadcasting and so it would make sense to implement this specifically as(b::Exp)(xs::ArrayBatch) = Batch(b(value(xs)))
and(b::Exp)(x) = exp.(x)
3.Batch
:)b = Reshape(in_size, out_size)
withlength(in_size) == 1
andlength(out_size) == 2
. Thenys = b(ArrayBatch(xs))
wheresize(xs) == in_size
will be aBatch
now containing an array of(out_size..., num_batches)
:Batch{Matrix{T}}
becameBatch{Array{T, 3}}
. This needs to be supported since we don't want to assume that the input-type is the same as the output-type.So all in all this looks really nice!
Issues
(b::Exp)(xs::ArrayBatch)
and(b::Exp)(x)
won't work. Instead we'll have to do(b::Exp)(x::AbstractArray{<:Real})
to resolve the ambiguity. Not cool. Ways to possibly address:Bijector
, e.g._transform
and_logabsdetjac
. This way we can implement generic behavior in the "non-private" methods, e.g.logabsdetjac
. This is a bit annoying for users when implementing their own bijectors, but it does simplify things significantly + allows further extensions that those mentioned here4(b::Exp)(x::AbstractArray{<:Real})
. I don't like this. E.g. forNamedBijector
we don't want to do this since the implementation really only relies on the input havinggetproperty
with the corresponding symbols, i.e. if we don't specify the type we can even pass it aComponentArray
from ComponentArrays.jl and it will just work.map
as in KernelFunctions.jl. This is better than (2), but not to my fancy. This can get a bit nasty in Bijectors.jl IMO, since we have to overloadmap(::typeof(logabsdetjac), b::Bijector, xs::Batch)
, which seems to break with what you'd expect frommap
.To discuss
ColVecs
andRowVecs
in KernelFunctions.jl (can be read about here: https://github.com/JuliaGaussianProcesses/KernelFunctions.jl/blob/wct/docs-update/docs/src/api.md#why-abstractvectors-everywhere). A couple of differences:map
on batches.ColVecs
andRowVecs
are subtypes ofAbstractVector{T}
. This is a good idea, as outlined in the docs linked above. It might be a good idea to also apply here, i.e. makeAbstractBatch{T} <: AbstractVector{T} end
whereT
represents the type of each of the elements in the batch. This gives us a natural interpretation of certain things you expect from a batch-type, e.g. length, indexing, iteration. It also means that if we implement iteration, we can potentally interact nicely with other packages that already useAbstractVector{T}
to represent a batch.ColVecs
, batches using both packages will just work.Footnotes
1 A couple of notes on this:
mapslices
isn't supported by CUDA.jl properly (JuliaGPU/CUDA.jl#807) so maybemapslices
isn't the best idea.2 Actually, it might be a better idea to make these batch-types separate, and allow specification of the dimensionality to iterate over, e.g. something like
ArrayBatch(value, dim)
. It might also be a good idea to separate these for other reasons, i.e. to specify orientation likeColVecs
andRowVecs
in KernelFunctions.jl.3 This particular example won't work because of method-ambiguity, but we'll get back to this later.
4 E.g. add checks for arguments, allow usage of
@thunk
from ChainRulesCore.jl so that we only have to define_forward(b::MyBijector, x)
and then_transform(b::MyBijector, x) = _forward(b, x).rv
+ similarly for_logabsdetjac
, with the default implforward(b::MyBijector, x) = unthunked(_forward(b, x))
. This idea was brought to my attention by @devmotion in a previous issue.Beta Was this translation helpful? Give feedback.
All reactions