diff --git a/Project.toml b/Project.toml index 2c0bafd1d..047926f01 100644 --- a/Project.toml +++ b/Project.toml @@ -18,6 +18,7 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" TensorCore = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Tullio = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" ZygoteRules = "700de1a5-db45-46bc-99cf-38207098b444" [compat] diff --git a/docs/src/kernels.md b/docs/src/kernels.md index 56f3cfb9b..d722672ad 100644 --- a/docs/src/kernels.md +++ b/docs/src/kernels.md @@ -100,7 +100,7 @@ GammaRationalKernel ### Spectral Mixture Kernels ```@docs -spectral_mixture_kernel +SpectralMixtureKernel spectral_mixture_product_kernel ``` diff --git a/src/KernelFunctions.jl b/src/KernelFunctions.jl index 37bde4a65..fff9f05ed 100644 --- a/src/KernelFunctions.jl +++ b/src/KernelFunctions.jl @@ -15,6 +15,7 @@ export LinearKernel, PolynomialKernel export RationalKernel, RationalQuadraticKernel, GammaRationalKernel export PiecewisePolynomialKernel export PeriodicKernel, NeuralNetworkKernel +export SpectralMixtureKernel export KernelSum, KernelProduct, KernelTensorProduct export TransformedKernel, ScaledKernel, NormalizedKernel export GibbsKernel @@ -33,7 +34,7 @@ export with_lengthscale export NystromFact, nystrom export gaborkernel -export spectral_mixture_kernel, spectral_mixture_product_kernel +export spectral_mixture_product_kernel export ColVecs, RowVecs @@ -58,6 +59,7 @@ using IrrationalConstants: logtwo, twoπ, invsqrt2 using LogExpFunctions: softplus using StatsBase using TensorCore +using Tullio using ZygoteRules: ZygoteRules, AContext, literal_getproperty, literal_getfield # Hack to work around Zygote type inference problems. @@ -67,6 +69,13 @@ abstract type Kernel end abstract type SimpleKernel <: Kernel end include("utils.jl") + +const VecOfVecs = Union{ColVecs,RowVecs} + +# A general binary op type not respecting Distances metric rules +abstract type AbstractBinaryOp end +const BinaryOp = Union{AbstractBinaryOp,Distances.PreMetric} + include("distances/pairwise.jl") include("distances/dotproduct.jl") include("distances/delta.jl") @@ -120,6 +129,8 @@ include("mokernels/lmm.jl") include("chainrules.jl") include("zygoterules.jl") +include("deprecated.jl") + include("test_utils.jl") function __init__() diff --git a/src/basekernels/sm.jl b/src/basekernels/sm.jl index 833945295..b23e10ae4 100644 --- a/src/basekernels/sm.jl +++ b/src/basekernels/sm.jl @@ -1,24 +1,39 @@ -""" - spectral_mixture_kernel( +@doc raw""" + SpectralMixtureKernel( h::Kernel=SqExponentialKernel(), - αs::AbstractVector{<:Real}, - γs::AbstractMatrix{<:Real}, - ωs::AbstractMatrix{<:Real}, + α::AbstractVector{<:Real}, + γ::AbstractMatrix{<:Real}, + ω::AbstractMatrix{<:Real}, + ) + SpectralMixtureKernel( + h::Kernel=SqExponentialKernel(), + α::AbstractVector{<:Real}, + γ::AbstractVector{<:AbstractVecOrMat{<:Real}}, + ω::AbstractVector{<:AbstractVecOrMat{<:Real}}, ) -where αs are the weights of dimension (A, ), γs is the covariance matrix of -dimension (D, A) and ωs are the mean vectors and is of dimension (D, A). -Here, D is input dimension and A is the number of spectral components. - -`h` is the kernel, which defaults to [`SqExponentialKernel`](@ref) if not specified. +Generalised Spectral Mixture kernel function as described in [1] in equation 6. +This family of functions is dense in the family of stationary real-valued kernels with respect to the pointwise convergence.[1] -Generalised Spectral Mixture kernel function. This family of functions is dense -in the family of stationary real-valued kernels with respect to the pointwise convergence.[1] +## Definition +For inputs ``x, x′ \in \mathbb{R}^D``, the spectral mixture kernel ``\tilde{k}`` with ``K`` mixture components, mixture weights ``\alpha \in \mathbb{R}^K``, linear transformations ``\gamma_1, \ldots, \gamma_K \in \mathbb{R}^D``, and frequencies ``\omega_1, \ldots, \omega_K \in \mathbb{R}^D`` derived from a translation-invariant kernel ``k`` is defined as ```math - κ(x, y) = αs' (h(-(γs' * t)^2) .* cos(π * ωs' * t), t = x - y +\tilde{k}(x, x'; \alpha, \gamma_1, \ldots, \gamma_K, \omega_1, \ldots, \omega_K, k) = \sum_{i=1}^K \alpha_i k(\gamma_i \odot x, \gamma_i \odot y) \cos(2\pi \omega_i^\top (x-y)). ``` +## Arguments +- `h`: Stationary kernel (translation invariant), [`SqExponentialKernel`](@ref) by default +- `α`: Weight vector of each mixture component (should be positive) +- `γ`: Linear transformation of the input for `h`. +- `ω`: Frequencies for the cosine function. (should be positive) + +`γ` and `ω` can be an +- `AbstractMatrix` of dimension `D x K` where `D` is the dimension of the inputs +and `K` is the number of components +- `AbstractVector` of `K` `D`-dimensional `AbstractVector` + + # References: [1] Generalized Spectral Kernels, by Yves-Laurent Kom Samo and Stephen J. Roberts [2] SM: Gaussian Process Kernels for Pattern Discovery and Extrapolation, @@ -29,55 +44,100 @@ in the family of stationary real-valued kernels with respect to the pointwise co [4] http://www.cs.cmu.edu/~andrewgw/pattern/. """ -function spectral_mixture_kernel( +struct SpectralMixtureKernel{ + K<:Kernel,Tα<:AbstractVector,Tγ<:AbstractVector,Tω<:AbstractVector +} <: Kernel + kernel::K + α::Tα + γ::Tγ + ω::Tω + function SpectralMixtureKernel( + h::Kernel, + α::AbstractVector{<:Real}, + γ::AbstractVector{<:AbstractVector}, + ω::AbstractVector{<:AbstractVector}, + ) + (length(α) == length(γ) == length(ω)) || + throw(DimensionMismatch("The dimensions of α, γ, ans ω do not match")) + any(<(0), α) && throw(ArgumentError("At least one element of α is negative")) + any(any.(<(0), ω)) && throw(ArgumentError("At least one element of ω is negative")) + return new{typeof(h),typeof(α),typeof(γ),typeof(ω)}(h, α, γ, ω) + end +end + +@functor SpectralMixtureKernel + +function SpectralMixtureKernel( h::Kernel, - αs::AbstractVector{<:Real}, - γs::AbstractMatrix{<:Real}, - ωs::AbstractMatrix{<:Real}, + α::AbstractVector{<:Real}, + γ::AbstractMatrix{<:Real}, + ω::AbstractMatrix{<:Real}, ) - if !(size(αs, 1) == size(γs, 2) == size(ωs, 2)) - throw(DimensionMismatch("The dimensions of αs, γs, ans ωs do not match")) - end - if size(γs) != size(ωs) - throw(DimensionMismatch("The dimensions of γs ans ωs do not match")) - end + size(γ) == size(ω) || throw(DimensionMismatch("γ and ω have different dimensions")) + return SpectralMixtureKernel(h, α, ColVecs(γ), ColVecs(ω)) +end - return sum(zip(αs, eachcol(γs), eachcol(ωs))) do (α, γ, ω) - a = TransformedKernel(h, LinearTransform(γ')) - b = TransformedKernel(CosineKernel(), LinearTransform(ω')) - return α * a * b +function SpectralMixtureKernel( + αs::AbstractVector{<:Real}, γs::AbstractVecOrMat, ωs::AbstractVecOrMat +) + return SpectralMixtureKernel(SqExponentialKernel(), αs, γs, ωs) +end + +function (κ::SpectralMixtureKernel)(x, y) + xy = x - y + # use pairwise summation (https://github.com/JuliaLang/julia/pull/31020) + broadcasted = Broadcast.broadcasted(κ.α, κ.γ, κ.ω) do α, γ, ω + k = TransformedKernel(κ.kernel, ARDTransform(γ)) + return α * k(x, y) * cospi(2 * dot(ω, xy)) end + return sum(Broadcast.instantiate(broadcasted)) end -function spectral_mixture_kernel( - αs::AbstractVector{<:Real}, γs::AbstractMatrix{<:Real}, ωs::AbstractMatrix{<:Real} -) - return spectral_mixture_kernel(SqExponentialKernel(), αs, γs, ωs) +function Base.show(io::IO, κ::SpectralMixtureKernel) + return print( + io, + "SpectralMixtureKernel Kernel (kernel = ", + κ.kernel, + ", # components = ", + length(κ.α), + ")", + ) end -""" +@doc raw""" spectral_mixture_product_kernel( h::Kernel=SqExponentialKernel(), - αs::AbstractMatrix{<:Real}, - γs::AbstractMatrix{<:Real}, - ωs::AbstractMatrix{<:Real}, + α::AbstractMatrix{<:Real}, + γ::AbstractMatrix{<:Real}, + ω::AbstractMatrix{<:Real}, ) -where αs are the weights of dimension (D, A), γs is the covariance matrix of -dimension (D, A) and ωs are the mean vectors and is of dimension (D, A). -Here, D is input dimension and A is the number of spectral components. - -Spectral Mixture Product Kernel. With enough components A, the SMP kernel +The spectral mixture product is tensor product of spectral mixture kernel applied +on each dimension as described in [1] in equations 13 and 14. +With enough components, the SMP kernel can model any product kernel to arbitrary precision, and is flexible even -with a small number of components [1] +with a small number of components +## Definition -`h` is the kernel, which defaults to [`SqExponentialKernel`](@ref) if not specified. +For inputs ``x, x′ \in \mathbb{R}^D``, the spectral mixture product kernel ``\tilde{k}`` with ``K`` mixture components, mixture weights ``\alpha_1, \alpha_2, \ldots, \alpha_K \in \mathbb{R}^D``, linear transformations ``\gamma_1, \ldots, \gamma_K \in \mathbb{R}^D``, and frequencies ``\omega_1, \ldots, \omega_K \in \mathbb{R}^D`` derived from a translation-invariant kernel ``k`` is defined as ```math - κ(x, y) = Πᵢ₌₁ᴷ Σ(αsᵢᵀ .* (h(-(γsᵢᵀ * tᵢ)²) .* cos(ωsᵢᵀ * tᵢ))), tᵢ = xᵢ - yᵢ + \tilde{k}(x, x'; \alpha_1, \ldots, \alpha_k, \gamma_1, \ldots, \gamma_K, \omega_1, \ldots, \omega_K, k) = \prod_{i=1}^D \sum_{k=1}^K \alpha_{ik} \cdot h(\gamma_{ik} \cdot x_i, \gamma_{ik} \cdot y_i)) \cdot \cos(2\pi \cdot \omega_{ik} \cdot (x_i - y_i)))) ``` +## Arguments +- `h`: Stationary kernel (translation invariant), [`SqExponentialKernel`](@ref) by default +- `α`: Weight of each mixture component for each dimension +- `γ`: Linear transformation of the input for `h`. +- `ω`: Frequencies for the cosine function. + +`α`, `γ` and `ω` can be an +- `AbstractMatrix` of dimension `D x K` where `D` is the dimension of the inputs +and `K` is the number of components +- `AbstractVector` of `D` `K`-dimensional `AbstractVector` + + # References: [1] GPatt: Fast Multidimensional Pattern Extrapolation with GPs, arXiv 1310.5288, 2013, by Andrew Gordon Wilson, Elad Gilboa, @@ -85,21 +145,28 @@ with a small number of components [1] """ function spectral_mixture_product_kernel( h::Kernel, - αs::AbstractMatrix{<:Real}, - γs::AbstractMatrix{<:Real}, - ωs::AbstractMatrix{<:Real}, + α::AbstractMatrix{<:Real}, + γ::AbstractMatrix{<:Real}, + ω::AbstractMatrix{<:Real}, ) - if !(size(αs) == size(γs) == size(ωs)) - throw(DimensionMismatch("The dimensions of αs, γs, ans ωs do not match")) + (size(α) == size(γ) == size(ω)) || + throw(DimensionMismatch("α, γ and ω have different dimensions")) + return spectral_mixture_product_kernel(h, RowVecs(α), RowVecs(γ), RowVecs(ω)) +end + +function spectral_mixture_product_kernel( + h::Kernel, + α::AbstractVector{<:AbstractVector{<:Real}}, + γ::AbstractVector{<:AbstractVector{<:Real}}, + ω::AbstractVector{<:AbstractVector{<:Real}}, +) + return mapreduce(⊗, α, γ, ω) do αᵢ, γᵢ, ωᵢ + return SpectralMixtureKernel(h, αᵢ, permutedims(γᵢ), permutedims(ωᵢ)) end - return KernelTensorProduct( - spectral_mixture_kernel(h, α, reshape(γ, 1, :), reshape(ω, 1, :)) for - (α, γ, ω) in zip(eachrow(αs), eachrow(γs), eachrow(ωs)) - ) end function spectral_mixture_product_kernel( - αs::AbstractMatrix{<:Real}, γs::AbstractMatrix{<:Real}, ωs::AbstractMatrix{<:Real} + α::AbstractVecOrMat, γ::AbstractVecOrMat, ω::AbstractVecOrMat ) - return spectral_mixture_product_kernel(SqExponentialKernel(), αs, γs, ωs) + return spectral_mixture_product_kernel(SqExponentialKernel(), α, γ, ω) end diff --git a/src/deprecated.jl b/src/deprecated.jl new file mode 100644 index 000000000..9b0d728fe --- /dev/null +++ b/src/deprecated.jl @@ -0,0 +1 @@ +@deprecate spectral_mixture_kernel SpectralMixtureKernel diff --git a/src/distances/binaryop.jl b/src/distances/binaryop.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/distances/delta.jl b/src/distances/delta.jl index f41370eae..f25ba633a 100644 --- a/src/distances/delta.jl +++ b/src/distances/delta.jl @@ -1,6 +1,6 @@ -# Delta is not following the PreMetric rules since d(x, x) == 1 -struct Delta <: Distances.UnionPreMetric end +struct Delta <: AbstractBinaryOp end +# Basic definitions (dist::Delta)(a::Number, b::Number) = a == b Base.@propagate_inbounds function (dist::Delta)( a::AbstractArray{<:Number}, b::AbstractArray{<:Number} @@ -14,5 +14,3 @@ Base.@propagate_inbounds function (dist::Delta)( end return a == b end - -Distances.result_type(::Delta, Ta::Type, Tb::Type) = Bool diff --git a/src/distances/dotproduct.jl b/src/distances/dotproduct.jl index 1cef13ab5..f689d3cb1 100644 --- a/src/distances/dotproduct.jl +++ b/src/distances/dotproduct.jl @@ -1,21 +1,22 @@ ## DotProduct is not following the PreMetric rules since d(x, x) != 0 and d(x, y) >= 0 for all x, y -struct DotProduct <: Distances.UnionPreMetric end +struct DotProduct <: AbstractBinaryOp end -@inline function Distances._evaluate(::DotProduct, a::AbstractVector, b::AbstractVector) - @boundscheck if length(a) != length(b) - throw( - DimensionMismatch( - "first array has length $(length(a)) which does not match the length of the second, $(length(b)).", - ), - ) - end - return dot(a, b) +(::DotProduct)(a::AbstractVector, b::AbstractVector) = dot(a, b) + +(::DotProduct)(a::Number, b::Number) = a * b + +function pairwise(::DotProduct, x::ColVecs, y::ColVecs) + return @tullio out[i, j] := x.X[k, i] * y.X[k, j] end -Distances.result_type(::DotProduct, Ta::Type, Tb::Type) = promote_type(Ta, Tb) +function pairwise(::DotProduct, x::RowVecs, y::RowVecs) + return @tullio out[i, j] := x.X[i, k] * y.X[j, k] +end -@inline Distances.eval_op(::DotProduct, a::Real, b::Real) = a * b -@inline function (dist::DotProduct)(a::AbstractArray, b::AbstractArray) - return Distances._evaluate(dist, a, b) +function colwise(::DotProduct, x::RowVecs, y::RowVecs=x) + return @tullio out[i] := x.X[i, k] * y.X[i, k] end -@inline (dist::DotProduct)(a::Number, b::Number) = a * b + +function colwise(::DotProduct, x::ColVecs, y::ColVecs=x) + return @tullio out[i] := x.X[k, i] * y.X[k, i] +end \ No newline at end of file diff --git a/src/distances/pairwise.jl b/src/distances/pairwise.jl index 8b5cb43e7..daa894a81 100644 --- a/src/distances/pairwise.jl +++ b/src/distances/pairwise.jl @@ -1,34 +1,16 @@ # Add our own pairwise function to be able to apply it on vectors -function pairwise(d::PreMetric, X::AbstractVector, Y::AbstractVector) - return broadcast(d, X, permutedims(Y)) +function pairwise(d::BinaryOp, X::AbstractVector, Y::AbstractVector) + return @tullio out[i, j] := d(X[i], Y[j]) end -pairwise(d::PreMetric, X::AbstractVector) = pairwise(d, X, X) +pairwise(d::BinaryOp, X::AbstractVector) = pairwise(d, X, X) -function pairwise!(out::AbstractMatrix, d::PreMetric, X::AbstractVector, Y::AbstractVector) - return broadcast!(d, out, X, permutedims(Y)) +function pairwise!(out::AbstractMatrix, d::BinaryOp, X::AbstractVector, Y::AbstractVector) + return @tullio out[i, j] = d(X[i], Y[j]) end -pairwise!(out::AbstractMatrix, d::PreMetric, X::AbstractVector) = pairwise!(out, d, X, X) - -function pairwise(d::PreMetric, x::AbstractVector{<:Real}) - return Distances_pairwise(d, reshape(x, :, 1); dims=1) -end - -function pairwise(d::PreMetric, x::AbstractVector{<:Real}, y::AbstractVector{<:Real}) - return Distances_pairwise(d, reshape(x, :, 1), reshape(y, :, 1); dims=1) -end - -function pairwise!(out::AbstractMatrix, d::PreMetric, x::AbstractVector{<:Real}) - return Distances.pairwise!(out, d, reshape(x, :, 1); dims=1) -end - -function pairwise!( - out::AbstractMatrix, d::PreMetric, x::AbstractVector{<:Real}, y::AbstractVector{<:Real} -) - return Distances.pairwise!(out, d, reshape(x, :, 1), reshape(y, :, 1); dims=1) -end +pairwise!(out::AbstractMatrix, d::BinaryOp, X::AbstractVector) = pairwise!(out, d, X, X) # Also defines the colwise method for abstractvectors @@ -36,35 +18,14 @@ function colwise(d::PreMetric, x::AbstractVector) return zeros(Distances.result_type(d, x, x), length(x)) # Valid since d(x,x) == 0 by definition end -function colwise(d::PreMetric, x::ColVecs) - return zeros(Distances.result_type(d, x.X, x.X), length(x)) # Valid since d(x,x) == 0 by definition -end - -function colwise(d::PreMetric, x::RowVecs) +function colwise(d::PreMetric, x::VecOfVecs) return zeros(Distances.result_type(d, x.X, x.X), length(x)) # Valid since d(x,x) == 0 by definition end -## The following is a hack for DotProduct and Delta to still work -function colwise(d::Distances.UnionPreMetric, x::ColVecs) - return Distances.colwise(d, x.X, x.X) -end - -function colwise(d::Distances.UnionPreMetric, x::RowVecs) - return Distances.colwise(d, x.X', x.X') -end - -function colwise(d::Distances.UnionPreMetric, x::AbstractVector) - return map(d, x, x) -end - -function colwise(d::PreMetric, x::ColVecs, y::ColVecs) - return Distances.colwise(d, x.X, y.X) -end - -function colwise(d::PreMetric, x::RowVecs, y::RowVecs) - return Distances.colwise(d, x.X', y.X') +function colwise(d::AbstractBinaryOp, x::AbstractVector) + return @tullio out[i] := d(x[i], x[i]) end function colwise(d::PreMetric, x::AbstractVector, y::AbstractVector) - return map(d, x, y) -end + return @tullio out[i] := d(x[i], y[i]) +end \ No newline at end of file diff --git a/src/distances/sinus.jl b/src/distances/sinus.jl index 4bcf4bdf0..1c063f41e 100644 --- a/src/distances/sinus.jl +++ b/src/distances/sinus.jl @@ -1,5 +1,5 @@ -struct Sinus{T} <: Distances.UnionSemiMetric - r::Vector{T} +struct Sinus{T,V<:AbstractVector{T}} <: Distances.SemiMetric + r::V end Distances.parameters(d::Sinus) = d.r diff --git a/src/transform/lineartransform.jl b/src/transform/lineartransform.jl index b61ba6a94..d8a27e36a 100644 --- a/src/transform/lineartransform.jl +++ b/src/transform/lineartransform.jl @@ -2,6 +2,7 @@ LinearTransform(A::AbstractMatrix) Linear transformation of the input realised by the matrix `A`. +If `a` is an `AbstractVector`, `Diagonal(a)` is passed The second dimension of `A` must match the number of features of the target. diff --git a/test/basekernels/sm.jl b/test/basekernels/sm.jl index b91024144..9d6bef672 100644 --- a/test/basekernels/sm.jl +++ b/test/basekernels/sm.jl @@ -3,32 +3,33 @@ v1 = rand(D_in) v2 = rand(D_in) - αs₁ = rand(3) - αs₂ = rand(D_in, 3) - γs = rand(D_in, 3) - ωs = rand(D_in, 3) + K = 3 + αs₁ = rand(K) + αs₂ = rand(D_in, K) + γs = rand(D_in, K) + ωs = rand(D_in, K) - k1 = spectral_mixture_kernel(αs₁, γs, ωs) + k1 = SpectralMixtureKernel(αs₁, γs, ωs) k2 = spectral_mixture_product_kernel(αs₂, γs, ωs) t = v1 - v2 - @test k1(v1, v2) ≈ sum(αs₁ .* exp.(-(t' * γs)' .^ 2 ./ 2) .* cospi.((t' * ωs)')) atol = - 1e-5 + @test k1(v1, v2) ≈ sum( + αs₁[k] * exp(-norm(t .* γs[:, k])^2 / 2) * cospi(2 * dot(ωs[:, k], t)) for k in 1:K + ) @test isapprox( k2(v1, v2), - prod([ + prod( sum( - αs₂[i, :]' .* exp.(-(γs[i, :]' * t[i]) .^ 2 ./ 2) .* - cospi.(ωs[i, :]' * t[i]), - ) for i in 1:length(t) - ],); - atol=1e-5, + αs₂[i, k] * exp(-(γs[i, k] * t[i])^2 / 2) * cospi(2 * ωs[i, k] * t[i]) for + k in 1:K + ) for i in 1:D_in + ), ) - @test_throws DimensionMismatch spectral_mixture_kernel(rand(5), rand(4, 3), rand(4, 3)) - @test_throws DimensionMismatch spectral_mixture_kernel(rand(3), rand(4, 3), rand(5, 3)) + @test_throws DimensionMismatch SpectralMixtureKernel(rand(5), rand(4, 3), rand(4, 3)) + @test_throws DimensionMismatch SpectralMixtureKernel(rand(3), rand(4, 3), rand(5, 3)) @test_throws DimensionMismatch spectral_mixture_product_kernel( rand(5, 3), rand(4, 3), rand(5, 3) ) @@ -38,15 +39,15 @@ x0 = ColVecs(randn(D_in, 3)) x1 = ColVecs(randn(D_in, 3)) x2 = ColVecs(randn(D_in, 2)) - TestUtils.test_interface(k1, x0, x1, x2) - TestUtils.test_interface(k2, x0, x1, x2) + test_interface(k1, x0, x1, x2) + test_interface(k2, x0, x1, x2) end @testset "RowVecs" begin x0 = RowVecs(randn(3, D_in)) x1 = RowVecs(randn(3, D_in)) x2 = RowVecs(randn(2, D_in)) - TestUtils.test_interface(k1, x0, x1, x2) - TestUtils.test_interface(k2, x0, x1, x2) + test_interface(k1, x0, x1, x2) + test_interface(k2, x0, x1, x2) end # test_ADs(x->spectral_mixture_kernel(exp.(x[1:3]), reshape(x[4:18], 5, 3), reshape(x[19:end], 5, 3)), vcat(log.(αs₁), γs[:], ωs[:]), dims = [5,5]) @test_broken "No tests passing (BaseKernel)"