From 5a6037df7ed51536cf4f590a860cfc80fd9e5d63 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 09:46:04 +0200 Subject: [PATCH 01/11] Add `KernelTensorSum` --- docs/src/kernels.md | 1 + src/KernelFunctions.jl | 4 +- src/kernels/kerneltensorsum.jl | 102 ++++++++++++++++++++++++++++++++ src/kernels/overloads.jl | 3 + test/kernels/kerneltensorsum.jl | 67 +++++++++++++++++++++ test/kernels/overloads.jl | 2 +- test/runtests.jl | 1 + 7 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/kernels/kerneltensorsum.jl create mode 100644 test/kernels/kerneltensorsum.jl diff --git a/docs/src/kernels.md b/docs/src/kernels.md index 8f19091c6..ec1a05dd8 100644 --- a/docs/src/kernels.md +++ b/docs/src/kernels.md @@ -124,6 +124,7 @@ TransformedKernel ScaledKernel KernelSum KernelProduct +KernelTensorSum KernelTensorProduct NormalizedKernel ``` diff --git a/src/KernelFunctions.jl b/src/KernelFunctions.jl index 63205b5bf..7ddd75b58 100644 --- a/src/KernelFunctions.jl +++ b/src/KernelFunctions.jl @@ -15,9 +15,10 @@ export LinearKernel, PolynomialKernel export RationalKernel, RationalQuadraticKernel, GammaRationalKernel export PiecewisePolynomialKernel export PeriodicKernel, NeuralNetworkKernel -export KernelSum, KernelProduct, KernelTensorProduct +export KernelSum, KernelProduct, KernelTensorSum, KernelTensorProduct export TransformedKernel, ScaledKernel, NormalizedKernel export GibbsKernel +export ⊕ export Transform, SelectTransform, @@ -108,6 +109,7 @@ include("kernels/normalizedkernel.jl") include("matrix/kernelmatrix.jl") include("kernels/kernelsum.jl") include("kernels/kernelproduct.jl") +include("kernels/kerneltensorsum.jl") include("kernels/kerneltensorproduct.jl") include("kernels/overloads.jl") include("kernels/neuralkernelnetwork.jl") diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kerneltensorsum.jl new file mode 100644 index 000000000..65a4ce0a4 --- /dev/null +++ b/src/kernels/kerneltensorsum.jl @@ -0,0 +1,102 @@ +""" + KernelTensorSum + +Tensor sum of kernels. + +# Definition + +For inputs ``x = (x_1, \\ldots, x_n)`` and ``x' = (x'_1, \\ldots, x'_n)``, the tensor +sum of kernels ``k_1, \\ldots, k_n`` is defined as +```math +k(x, x'; k_1, \\ldots, k_n) = \\sum_{i=1}^n k_i(x_i, x'_i). +``` + +# Construction + +The simplest way to specify a `KernelTensorSum` is to use the `⊕` operator (can be typed by `\\oplus`). +```jldoctest tensorproduct +julia> k1 = SqExponentialKernel(); k2 = LinearKernel(); X = rand(5, 2); + +julia> kernelmatrix(k1 ⊕ k2, RowVecs(X)) == kernelmatrix(k1, X[:, 1]) + kernelmatrix(k2, X[:, 2]) +true +``` + +You can also specify a `KernelTensorSum` by providing kernels as individual arguments +or as an iterable data structure such as a `Tuple` or a `Vector`. Using a tuple or +individual arguments guarantees that `KernelTensorSum` is concretely typed but might +lead to large compilation times if the number of kernels is large. +```jldoctest tensorproduct +julia> KernelTensorSum(k1, k2) == k1 ⊕ k2 +true + +julia> KernelTensorSum((k1, k2)) == k1 ⊕ k2 +true + +julia> KernelTensorSum([k1, k2]) == k1 ⊕ k2 +true +``` +""" +struct KernelTensorSum{K} <: Kernel + kernels::K +end + +function KernelTensorSum(kernel::Kernel, kernels::Kernel...) + return KernelTensorSum((kernel, kernels...)) +end + +@functor KernelTensorSum + +Base.length(kernel::KernelTensorSum) = length(kernel.kernels) + +function (kernel::KernelTensorSum)(x, y) + if !(length(x) == length(y) == length(kernel)) + throw(DimensionMismatch("number of kernels and number of features +are not consistent")) + end + return sum(k(xi, yi) for (k, xi, yi) in zip(kernel.kernels, x, y)) +end + +function validate_domain(k::KernelTensorSum, x::AbstractVector) + return dim(x) == length(k) || + error("number of kernels and groups of features are not consistent") +end + +function kernelmatrix(k::KernelTensorSum, x::AbstractVector) + validate_domain(k, x) + return mapreduce(kernelmatrix, +, k.kernels, slices(x)) +end + +function kernelmatrix(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) + validate_domain(k, x) + return mapreduce(kernelmatrix, +, k.kernels, slices(x), slices(y)) +end + +function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector) + validate_domain(k, x) + return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x)) +end + +function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) + validate_domain(k, x) + return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x), slices(y)) +end + +function Base.:(==)(x::KernelTensorSum, y::KernelTensorSum) + return ( + length(x.kernels) == length(y.kernels) && + all(kx == ky for (kx, ky) in zip(x.kernels, y.kernels)) + ) +end + +Base.show(io::IO, kernel::KernelTensorSum) = printshifted(io, kernel, 0) + +function printshifted(io::IO, kernel::KernelTensorSum, shift::Int) + print(io, "Tensor sum of ", length(kernel), " kernels:") + for k in kernel.kernels + print(io, "\n") + for _ in 1:(shift + 1) + print(io, "\t") + end + printshifted(io, k, shift + 2) + end +end diff --git a/src/kernels/overloads.jl b/src/kernels/overloads.jl index 3285c3dd4..6b0ce7058 100644 --- a/src/kernels/overloads.jl +++ b/src/kernels/overloads.jl @@ -1,7 +1,10 @@ +function ⊕ end + for (M, op, T) in ( (:Base, :+, :KernelSum), (:Base, :*, :KernelProduct), (:TensorCore, :tensor, :KernelTensorProduct), + (:KernelFunctions, :⊕, :KernelTensorSum), ) @eval begin $M.$op(k1::Kernel, k2::Kernel) = $T(k1, k2) diff --git a/test/kernels/kerneltensorsum.jl b/test/kernels/kerneltensorsum.jl new file mode 100644 index 000000000..8bb17a9a0 --- /dev/null +++ b/test/kernels/kerneltensorsum.jl @@ -0,0 +1,67 @@ +@testset "kerneltensorsum" begin + rng = MersenneTwister(123456) + u1 = rand(rng, 10) + u2 = rand(rng, 10) + v1 = rand(rng, 5) + v2 = rand(rng, 5) + + # kernels + k1 = SqExponentialKernel() + k2 = ExponentialKernel() + kernel1 = KernelTensorSum(k1, k2) + kernel2 = KernelTensorSum([k1, k2]) + + @test kernel1 == kernel2 + @test kernel1.kernels == (k1, k2) === KernelTensorSum((k1, k2)).kernels + for (_k1, _k2) in Iterators.product( + (k1, KernelTensorSum((k1,)), KernelTensorSum([k1])), + (k2, KernelTensorSum((k2,)), KernelTensorSum([k2])), + ) + @test kernel1 == _k1 ⊕ _k2 + end + @test length(kernel1) == length(kernel2) == 2 + @test string(kernel1) == ( + "Tensor sum of 2 kernels:\n" * + "\tSquared Exponential Kernel (metric = Euclidean(0.0))\n" * + "\tExponential Kernel (metric = Euclidean(0.0))" + ) + @test_throws DimensionMismatch kernel1(rand(3), rand(3)) + + @testset "val" begin + for (x, y) in (((v1, u1), (v2, u2)), ([v1, u1], [v2, u2])) + val = k1(x[1], y[1]) + k2(x[2], y[2]) + + @test kernel1(x, y) == kernel2(x, y) == val + end + end + + # Standardised tests. + TestUtils.test_interface(kernel1, ColVecs{Float64}) + TestUtils.test_interface(kernel1, RowVecs{Float64}) + TestUtils.test_interface( + KernelTensorSum(WhiteKernel(), ConstantKernel(; c=1.1)), ColVecs{String} + ) + test_ADs( + x -> KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))), + rand(1); + dims=[2, 2], + ) + types = [ColVecs{Float64,Matrix{Float64}}, RowVecs{Float64,Matrix{Float64}}] + test_interface_ad_perf(2.1, StableRNG(123456), types) do c + KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=c)) + end + test_params(KernelTensorSum(k1, k2), (k1, k2)) + + @testset "single kernel" begin + kernel = KernelTensorSum(k1) + @test length(kernel) == 1 + + @testset "eval" begin + for (x, y) in (((v1,), (v2,)), ([v1], [v2])) + val = k1(x[1], y[1]) + + @test kernel(x, y) == val + end + end + end +end diff --git a/test/kernels/overloads.jl b/test/kernels/overloads.jl index eb79d41f8..3a0bee1be 100644 --- a/test/kernels/overloads.jl +++ b/test/kernels/overloads.jl @@ -5,7 +5,7 @@ k2 = SqExponentialKernel() k3 = RationalQuadraticKernel() - for (op, T) in ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct)) + for (op, T) in ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct), (⊕, KernelTensorSum)) if T === KernelTensorProduct v2_1 = rand(rng, 2) v2_2 = rand(rng, 2) diff --git a/test/runtests.jl b/test/runtests.jl index caf43cb91..c33d67300 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -125,6 +125,7 @@ include("test_utils.jl") include("kernels/kernelproduct.jl") include("kernels/kernelsum.jl") include("kernels/kerneltensorproduct.jl") + include("kernels/kerneltensorsum.jl") include("kernels/overloads.jl") include("kernels/scaledkernel.jl") include("kernels/transformedkernel.jl") From 2be0d375153eacb39c4b6a0d0b9820431ae37e29 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 12:54:10 +0200 Subject: [PATCH 02/11] Fix `KernelTensorSum` overload test --- test/kernels/overloads.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/kernels/overloads.jl b/test/kernels/overloads.jl index 3a0bee1be..456cd7796 100644 --- a/test/kernels/overloads.jl +++ b/test/kernels/overloads.jl @@ -5,8 +5,9 @@ k2 = SqExponentialKernel() k3 = RationalQuadraticKernel() - for (op, T) in ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct), (⊕, KernelTensorSum)) - if T === KernelTensorProduct + for (op, T) in + ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct), (⊕, KernelTensorSum)) + if T === KernelTensorProduct || T === KernelTensorSum v2_1 = rand(rng, 2) v2_2 = rand(rng, 2) v3_1 = rand(rng, 3) From a39f81fdb902ca33a85b0158e9e1954f4b8d4e87 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 14:05:21 +0200 Subject: [PATCH 03/11] Improve coverage and error message of `validate_domain(::KernelTensorSum, ...)` --- src/kernels/kerneltensorsum.jl | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kerneltensorsum.jl index 65a4ce0a4..cbc8d1ebf 100644 --- a/src/kernels/kerneltensorsum.jl +++ b/src/kernels/kerneltensorsum.jl @@ -49,16 +49,21 @@ end Base.length(kernel::KernelTensorSum) = length(kernel.kernels) function (kernel::KernelTensorSum)(x, y) - if !(length(x) == length(y) == length(kernel)) - throw(DimensionMismatch("number of kernels and number of features -are not consistent")) + if !((nx = length(x)) == (ny = length(y)) == (nkernerls = length(k))) + throw(DimensionMismatch("number of kernels ($nkernels) and number of features +(x=$nx, y=$ny) are not consistent")) end return sum(k(xi, yi) for (k, xi, yi) in zip(kernel.kernels, x, y)) end +function validate_domain(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) + return (dx = dim(x)) == (dy = dim(y)) == (nkernels = length(k)) || error( + "number of kernels ($nkernels) and group of features (x=$dx), y=$dy) are not consistent", + ) +end + function validate_domain(k::KernelTensorSum, x::AbstractVector) - return dim(x) == length(k) || - error("number of kernels and groups of features are not consistent") + return validate_domain(k, x, x) end function kernelmatrix(k::KernelTensorSum, x::AbstractVector) @@ -67,7 +72,7 @@ function kernelmatrix(k::KernelTensorSum, x::AbstractVector) end function kernelmatrix(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) - validate_domain(k, x) + validate_domain(k, x, y) return mapreduce(kernelmatrix, +, k.kernels, slices(x), slices(y)) end @@ -77,7 +82,7 @@ function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector) end function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) - validate_domain(k, x) + validate_domain(k, x, y) return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x), slices(y)) end From 03ce5b5c3257bcc493098eb54056ff8fd6ce5c87 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 14:17:23 +0200 Subject: [PATCH 04/11] Fix typo --- src/kernels/kerneltensorsum.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kerneltensorsum.jl index cbc8d1ebf..71dd3500a 100644 --- a/src/kernels/kerneltensorsum.jl +++ b/src/kernels/kerneltensorsum.jl @@ -49,7 +49,7 @@ end Base.length(kernel::KernelTensorSum) = length(kernel.kernels) function (kernel::KernelTensorSum)(x, y) - if !((nx = length(x)) == (ny = length(y)) == (nkernerls = length(k))) + if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(k))) throw(DimensionMismatch("number of kernels ($nkernels) and number of features (x=$nx, y=$ny) are not consistent")) end From 106ef5f9323e40bd6f47e4884c23d0c5a1afcd1c Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 14:17:50 +0200 Subject: [PATCH 05/11] Fix formatting --- src/kernels/kerneltensorsum.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kerneltensorsum.jl index 71dd3500a..5e5dae0a9 100644 --- a/src/kernels/kerneltensorsum.jl +++ b/src/kernels/kerneltensorsum.jl @@ -50,8 +50,11 @@ Base.length(kernel::KernelTensorSum) = length(kernel.kernels) function (kernel::KernelTensorSum)(x, y) if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(k))) - throw(DimensionMismatch("number of kernels ($nkernels) and number of features -(x=$nx, y=$ny) are not consistent")) + throw( + DimensionMismatch( + "number of kernels ($nkernels) and number of features (x=$nx, y=$ny) are not consistent", + ), + ) end return sum(k(xi, yi) for (k, xi, yi) in zip(kernel.kernels, x, y)) end From 668fcfd60bd8b3bcbad1292716f7bf84f4138cf3 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 10 May 2023 15:04:03 +0200 Subject: [PATCH 06/11] Fix typo: k -> kernel --- src/kernels/kerneltensorsum.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kerneltensorsum.jl index 5e5dae0a9..1283a5e05 100644 --- a/src/kernels/kerneltensorsum.jl +++ b/src/kernels/kerneltensorsum.jl @@ -49,7 +49,7 @@ end Base.length(kernel::KernelTensorSum) = length(kernel.kernels) function (kernel::KernelTensorSum)(x, y) - if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(k))) + if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(kernel))) throw( DimensionMismatch( "number of kernels ($nkernels) and number of features (x=$nx, y=$ny) are not consistent", From d1a18b29f9b589d40742f529de358acb4b0077c7 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 17 May 2023 19:22:23 +0200 Subject: [PATCH 07/11] Rename `KernelTensorSum` -> `KernelIndependentSum` --- docs/src/kernels.md | 2 +- src/KernelFunctions.jl | 4 +- ...eltensorsum.jl => kernelindependentsum.jl} | 50 +++++++++---------- src/kernels/overloads.jl | 2 +- ...eltensorsum.jl => kernelindependentsum.jl} | 20 ++++---- test/kernels/overloads.jl | 10 ++-- test/runtests.jl | 2 +- 7 files changed, 47 insertions(+), 43 deletions(-) rename src/kernels/{kerneltensorsum.jl => kernelindependentsum.jl} (56%) rename test/kernels/{kerneltensorsum.jl => kernelindependentsum.jl} (70%) diff --git a/docs/src/kernels.md b/docs/src/kernels.md index ec1a05dd8..915eb17a3 100644 --- a/docs/src/kernels.md +++ b/docs/src/kernels.md @@ -124,7 +124,7 @@ TransformedKernel ScaledKernel KernelSum KernelProduct -KernelTensorSum +KernelIndependentSum KernelTensorProduct NormalizedKernel ``` diff --git a/src/KernelFunctions.jl b/src/KernelFunctions.jl index 7ddd75b58..f02b5dc14 100644 --- a/src/KernelFunctions.jl +++ b/src/KernelFunctions.jl @@ -15,7 +15,7 @@ export LinearKernel, PolynomialKernel export RationalKernel, RationalQuadraticKernel, GammaRationalKernel export PiecewisePolynomialKernel export PeriodicKernel, NeuralNetworkKernel -export KernelSum, KernelProduct, KernelTensorSum, KernelTensorProduct +export KernelSum, KernelProduct, KernelIndependentSum, KernelTensorProduct export TransformedKernel, ScaledKernel, NormalizedKernel export GibbsKernel export ⊕ @@ -109,7 +109,7 @@ include("kernels/normalizedkernel.jl") include("matrix/kernelmatrix.jl") include("kernels/kernelsum.jl") include("kernels/kernelproduct.jl") -include("kernels/kerneltensorsum.jl") +include("kernels/kernelindependentsum.jl") include("kernels/kerneltensorproduct.jl") include("kernels/overloads.jl") include("kernels/neuralkernelnetwork.jl") diff --git a/src/kernels/kerneltensorsum.jl b/src/kernels/kernelindependentsum.jl similarity index 56% rename from src/kernels/kerneltensorsum.jl rename to src/kernels/kernelindependentsum.jl index 1283a5e05..307916496 100644 --- a/src/kernels/kerneltensorsum.jl +++ b/src/kernels/kernelindependentsum.jl @@ -1,7 +1,7 @@ """ - KernelTensorSum + KernelIndependentSum -Tensor sum of kernels. +Independent sum of kernels. # Definition @@ -13,42 +13,42 @@ k(x, x'; k_1, \\ldots, k_n) = \\sum_{i=1}^n k_i(x_i, x'_i). # Construction -The simplest way to specify a `KernelTensorSum` is to use the `⊕` operator (can be typed by `\\oplus`). -```jldoctest tensorproduct +The simplest way to specify a `KernelIndependentSum` is to use the `⊕` operator (can be typed by `\\oplus`). +```jldoctest independentsum julia> k1 = SqExponentialKernel(); k2 = LinearKernel(); X = rand(5, 2); julia> kernelmatrix(k1 ⊕ k2, RowVecs(X)) == kernelmatrix(k1, X[:, 1]) + kernelmatrix(k2, X[:, 2]) true ``` -You can also specify a `KernelTensorSum` by providing kernels as individual arguments +You can also specify a `KernelIndependentSum` by providing kernels as individual arguments or as an iterable data structure such as a `Tuple` or a `Vector`. Using a tuple or -individual arguments guarantees that `KernelTensorSum` is concretely typed but might +individual arguments guarantees that `KernelIndependentSum` is concretely typed but might lead to large compilation times if the number of kernels is large. -```jldoctest tensorproduct -julia> KernelTensorSum(k1, k2) == k1 ⊕ k2 +```jldoctest independentsum +julia> KernelIndependentSum(k1, k2) == k1 ⊕ k2 true -julia> KernelTensorSum((k1, k2)) == k1 ⊕ k2 +julia> KernelIndependentSum((k1, k2)) == k1 ⊕ k2 true -julia> KernelTensorSum([k1, k2]) == k1 ⊕ k2 +julia> KernelIndependentSum([k1, k2]) == k1 ⊕ k2 true ``` """ -struct KernelTensorSum{K} <: Kernel +struct KernelIndependentSum{K} <: Kernel kernels::K end -function KernelTensorSum(kernel::Kernel, kernels::Kernel...) - return KernelTensorSum((kernel, kernels...)) +function KernelIndependentSum(kernel::Kernel, kernels::Kernel...) + return KernelIndependentSum((kernel, kernels...)) end -@functor KernelTensorSum +@functor KernelIndependentSum -Base.length(kernel::KernelTensorSum) = length(kernel.kernels) +Base.length(kernel::KernelIndependentSum) = length(kernel.kernels) -function (kernel::KernelTensorSum)(x, y) +function (kernel::KernelIndependentSum)(x, y) if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(kernel))) throw( DimensionMismatch( @@ -59,46 +59,46 @@ function (kernel::KernelTensorSum)(x, y) return sum(k(xi, yi) for (k, xi, yi) in zip(kernel.kernels, x, y)) end -function validate_domain(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) +function validate_domain(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) return (dx = dim(x)) == (dy = dim(y)) == (nkernels = length(k)) || error( "number of kernels ($nkernels) and group of features (x=$dx), y=$dy) are not consistent", ) end -function validate_domain(k::KernelTensorSum, x::AbstractVector) +function validate_domain(k::KernelIndependentSum, x::AbstractVector) return validate_domain(k, x, x) end -function kernelmatrix(k::KernelTensorSum, x::AbstractVector) +function kernelmatrix(k::KernelIndependentSum, x::AbstractVector) validate_domain(k, x) return mapreduce(kernelmatrix, +, k.kernels, slices(x)) end -function kernelmatrix(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) +function kernelmatrix(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) validate_domain(k, x, y) return mapreduce(kernelmatrix, +, k.kernels, slices(x), slices(y)) end -function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector) +function kernelmatrix_diag(k::KernelIndependentSum, x::AbstractVector) validate_domain(k, x) return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x)) end -function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) +function kernelmatrix_diag(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) validate_domain(k, x, y) return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x), slices(y)) end -function Base.:(==)(x::KernelTensorSum, y::KernelTensorSum) +function Base.:(==)(x::KernelIndependentSum, y::KernelIndependentSum) return ( length(x.kernels) == length(y.kernels) && all(kx == ky for (kx, ky) in zip(x.kernels, y.kernels)) ) end -Base.show(io::IO, kernel::KernelTensorSum) = printshifted(io, kernel, 0) +Base.show(io::IO, kernel::KernelIndependentSum) = printshifted(io, kernel, 0) -function printshifted(io::IO, kernel::KernelTensorSum, shift::Int) +function printshifted(io::IO, kernel::KernelIndependentSum, shift::Int) print(io, "Tensor sum of ", length(kernel), " kernels:") for k in kernel.kernels print(io, "\n") diff --git a/src/kernels/overloads.jl b/src/kernels/overloads.jl index 6b0ce7058..04026ae93 100644 --- a/src/kernels/overloads.jl +++ b/src/kernels/overloads.jl @@ -4,7 +4,7 @@ for (M, op, T) in ( (:Base, :+, :KernelSum), (:Base, :*, :KernelProduct), (:TensorCore, :tensor, :KernelTensorProduct), - (:KernelFunctions, :⊕, :KernelTensorSum), + (:KernelFunctions, :⊕, :KernelIndependentSum), ) @eval begin $M.$op(k1::Kernel, k2::Kernel) = $T(k1, k2) diff --git a/test/kernels/kerneltensorsum.jl b/test/kernels/kernelindependentsum.jl similarity index 70% rename from test/kernels/kerneltensorsum.jl rename to test/kernels/kernelindependentsum.jl index 8bb17a9a0..04d77992c 100644 --- a/test/kernels/kerneltensorsum.jl +++ b/test/kernels/kernelindependentsum.jl @@ -8,14 +8,14 @@ # kernels k1 = SqExponentialKernel() k2 = ExponentialKernel() - kernel1 = KernelTensorSum(k1, k2) - kernel2 = KernelTensorSum([k1, k2]) + kernel1 = KernelIndependentSum(k1, k2) + kernel2 = KernelIndependentSum([k1, k2]) @test kernel1 == kernel2 - @test kernel1.kernels == (k1, k2) === KernelTensorSum((k1, k2)).kernels + @test kernel1.kernels == (k1, k2) === KernelIndependentSum((k1, k2)).kernels for (_k1, _k2) in Iterators.product( - (k1, KernelTensorSum((k1,)), KernelTensorSum([k1])), - (k2, KernelTensorSum((k2,)), KernelTensorSum([k2])), + (k1, KernelIndependentSum((k1,)), KernelIndependentSum([k1])), + (k2, KernelIndependentSum((k2,)), KernelIndependentSum([k2])), ) @test kernel1 == _k1 ⊕ _k2 end @@ -39,21 +39,21 @@ TestUtils.test_interface(kernel1, ColVecs{Float64}) TestUtils.test_interface(kernel1, RowVecs{Float64}) TestUtils.test_interface( - KernelTensorSum(WhiteKernel(), ConstantKernel(; c=1.1)), ColVecs{String} + KernelIndependentSum(WhiteKernel(), ConstantKernel(; c=1.1)), ColVecs{String} ) test_ADs( - x -> KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))), + x -> KernelIndependentSum(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))), rand(1); dims=[2, 2], ) types = [ColVecs{Float64,Matrix{Float64}}, RowVecs{Float64,Matrix{Float64}}] test_interface_ad_perf(2.1, StableRNG(123456), types) do c - KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=c)) + KernelIndependentSum(SqExponentialKernel(), LinearKernel(; c=c)) end - test_params(KernelTensorSum(k1, k2), (k1, k2)) + test_params(KernelIndependentSum(k1, k2), (k1, k2)) @testset "single kernel" begin - kernel = KernelTensorSum(k1) + kernel = KernelIndependentSum(k1) @test length(kernel) == 1 @testset "eval" begin diff --git a/test/kernels/overloads.jl b/test/kernels/overloads.jl index 456cd7796..1981c3381 100644 --- a/test/kernels/overloads.jl +++ b/test/kernels/overloads.jl @@ -5,9 +5,13 @@ k2 = SqExponentialKernel() k3 = RationalQuadraticKernel() - for (op, T) in - ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct), (⊕, KernelTensorSum)) - if T === KernelTensorProduct || T === KernelTensorSum + for (op, T) in ( + (+, KernelSum), + (*, KernelProduct), + (⊗, KernelTensorProduct), + (⊕, KernelIndependentSum), + ) + if T === KernelTensorProduct || T === KernelIndependentSum v2_1 = rand(rng, 2) v2_2 = rand(rng, 2) v3_1 = rand(rng, 3) diff --git a/test/runtests.jl b/test/runtests.jl index c33d67300..61ab61a3f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -125,7 +125,7 @@ include("test_utils.jl") include("kernels/kernelproduct.jl") include("kernels/kernelsum.jl") include("kernels/kerneltensorproduct.jl") - include("kernels/kerneltensorsum.jl") + include("kernels/kernelindependentsum.jl") include("kernels/overloads.jl") include("kernels/scaledkernel.jl") include("kernels/transformedkernel.jl") From 36b4d9e2644e4992852270e9bf7c7b2ca947d415 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 31 May 2023 10:40:14 +0200 Subject: [PATCH 08/11] Fix `KernelIndependentSum` pretty printing --- src/kernels/kernelindependentsum.jl | 2 +- test/kernels/kernelindependentsum.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kernels/kernelindependentsum.jl b/src/kernels/kernelindependentsum.jl index 307916496..3922f0bd9 100644 --- a/src/kernels/kernelindependentsum.jl +++ b/src/kernels/kernelindependentsum.jl @@ -99,7 +99,7 @@ end Base.show(io::IO, kernel::KernelIndependentSum) = printshifted(io, kernel, 0) function printshifted(io::IO, kernel::KernelIndependentSum, shift::Int) - print(io, "Tensor sum of ", length(kernel), " kernels:") + print(io, "Independent sum of ", length(kernel), " kernels:") for k in kernel.kernels print(io, "\n") for _ in 1:(shift + 1) diff --git a/test/kernels/kernelindependentsum.jl b/test/kernels/kernelindependentsum.jl index 04d77992c..064e1cf47 100644 --- a/test/kernels/kernelindependentsum.jl +++ b/test/kernels/kernelindependentsum.jl @@ -1,4 +1,4 @@ -@testset "kerneltensorsum" begin +@testset "kernelindependentsum" begin rng = MersenneTwister(123456) u1 = rand(rng, 10) u2 = rand(rng, 10) @@ -21,7 +21,7 @@ end @test length(kernel1) == length(kernel2) == 2 @test string(kernel1) == ( - "Tensor sum of 2 kernels:\n" * + "Independent sum of 2 kernels:\n" * "\tSquared Exponential Kernel (metric = Euclidean(0.0))\n" * "\tExponential Kernel (metric = Euclidean(0.0))" ) From 8d0da0af8b969faee1c3d18008c23cb28b65425e Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 31 May 2023 12:52:06 +0200 Subject: [PATCH 09/11] Revert "Rename `KernelTensorSum` -> `KernelIndependentSum`" This reverts commit d1a18b29f9b589d40742f529de358acb4b0077c7. --- docs/src/kernels.md | 2 +- src/KernelFunctions.jl | 4 +- ...elindependentsum.jl => kerneltensorsum.jl} | 52 +++++++++---------- src/kernels/overloads.jl | 2 +- ...elindependentsum.jl => kerneltensorsum.jl} | 22 ++++---- test/kernels/overloads.jl | 10 ++-- test/runtests.jl | 2 +- 7 files changed, 45 insertions(+), 49 deletions(-) rename src/kernels/{kernelindependentsum.jl => kerneltensorsum.jl} (54%) rename test/kernels/{kernelindependentsum.jl => kerneltensorsum.jl} (68%) diff --git a/docs/src/kernels.md b/docs/src/kernels.md index 915eb17a3..ec1a05dd8 100644 --- a/docs/src/kernels.md +++ b/docs/src/kernels.md @@ -124,7 +124,7 @@ TransformedKernel ScaledKernel KernelSum KernelProduct -KernelIndependentSum +KernelTensorSum KernelTensorProduct NormalizedKernel ``` diff --git a/src/KernelFunctions.jl b/src/KernelFunctions.jl index f02b5dc14..7ddd75b58 100644 --- a/src/KernelFunctions.jl +++ b/src/KernelFunctions.jl @@ -15,7 +15,7 @@ export LinearKernel, PolynomialKernel export RationalKernel, RationalQuadraticKernel, GammaRationalKernel export PiecewisePolynomialKernel export PeriodicKernel, NeuralNetworkKernel -export KernelSum, KernelProduct, KernelIndependentSum, KernelTensorProduct +export KernelSum, KernelProduct, KernelTensorSum, KernelTensorProduct export TransformedKernel, ScaledKernel, NormalizedKernel export GibbsKernel export ⊕ @@ -109,7 +109,7 @@ include("kernels/normalizedkernel.jl") include("matrix/kernelmatrix.jl") include("kernels/kernelsum.jl") include("kernels/kernelproduct.jl") -include("kernels/kernelindependentsum.jl") +include("kernels/kerneltensorsum.jl") include("kernels/kerneltensorproduct.jl") include("kernels/overloads.jl") include("kernels/neuralkernelnetwork.jl") diff --git a/src/kernels/kernelindependentsum.jl b/src/kernels/kerneltensorsum.jl similarity index 54% rename from src/kernels/kernelindependentsum.jl rename to src/kernels/kerneltensorsum.jl index 3922f0bd9..c4b2a58f9 100644 --- a/src/kernels/kernelindependentsum.jl +++ b/src/kernels/kerneltensorsum.jl @@ -1,7 +1,7 @@ """ - KernelIndependentSum + KernelTensorSum -Independent sum of kernels. +Tensor sum of kernels. # Definition @@ -13,42 +13,42 @@ k(x, x'; k_1, \\ldots, k_n) = \\sum_{i=1}^n k_i(x_i, x'_i). # Construction -The simplest way to specify a `KernelIndependentSum` is to use the `⊕` operator (can be typed by `\\oplus`). -```jldoctest independentsum +The simplest way to specify a `KernelTensorSum` is to use the `⊕` operator (can be typed by `\\oplus`). +```jldoctest tensorsum julia> k1 = SqExponentialKernel(); k2 = LinearKernel(); X = rand(5, 2); julia> kernelmatrix(k1 ⊕ k2, RowVecs(X)) == kernelmatrix(k1, X[:, 1]) + kernelmatrix(k2, X[:, 2]) true ``` -You can also specify a `KernelIndependentSum` by providing kernels as individual arguments +You can also specify a `KernelTensorSum` by providing kernels as individual arguments or as an iterable data structure such as a `Tuple` or a `Vector`. Using a tuple or -individual arguments guarantees that `KernelIndependentSum` is concretely typed but might +individual arguments guarantees that `KernelTensorSum` is concretely typed but might lead to large compilation times if the number of kernels is large. -```jldoctest independentsum -julia> KernelIndependentSum(k1, k2) == k1 ⊕ k2 +```jldoctest tensorsum +julia> KernelTensorSum(k1, k2) == k1 ⊕ k2 true -julia> KernelIndependentSum((k1, k2)) == k1 ⊕ k2 +julia> KernelTensorSum((k1, k2)) == k1 ⊕ k2 true -julia> KernelIndependentSum([k1, k2]) == k1 ⊕ k2 +julia> KernelTensorSum([k1, k2]) == k1 ⊕ k2 true ``` """ -struct KernelIndependentSum{K} <: Kernel +struct KernelTensorSum{K} <: Kernel kernels::K end -function KernelIndependentSum(kernel::Kernel, kernels::Kernel...) - return KernelIndependentSum((kernel, kernels...)) +function KernelTensorSum(kernel::Kernel, kernels::Kernel...) + return KernelTensorSum((kernel, kernels...)) end -@functor KernelIndependentSum +@functor KernelTensorSum -Base.length(kernel::KernelIndependentSum) = length(kernel.kernels) +Base.length(kernel::KernelTensorSum) = length(kernel.kernels) -function (kernel::KernelIndependentSum)(x, y) +function (kernel::KernelTensorSum)(x, y) if !((nx = length(x)) == (ny = length(y)) == (nkernels = length(kernel))) throw( DimensionMismatch( @@ -59,47 +59,47 @@ function (kernel::KernelIndependentSum)(x, y) return sum(k(xi, yi) for (k, xi, yi) in zip(kernel.kernels, x, y)) end -function validate_domain(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) +function validate_domain(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) return (dx = dim(x)) == (dy = dim(y)) == (nkernels = length(k)) || error( "number of kernels ($nkernels) and group of features (x=$dx), y=$dy) are not consistent", ) end -function validate_domain(k::KernelIndependentSum, x::AbstractVector) +function validate_domain(k::KernelTensorSum, x::AbstractVector) return validate_domain(k, x, x) end -function kernelmatrix(k::KernelIndependentSum, x::AbstractVector) +function kernelmatrix(k::KernelTensorSum, x::AbstractVector) validate_domain(k, x) return mapreduce(kernelmatrix, +, k.kernels, slices(x)) end -function kernelmatrix(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) +function kernelmatrix(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) validate_domain(k, x, y) return mapreduce(kernelmatrix, +, k.kernels, slices(x), slices(y)) end -function kernelmatrix_diag(k::KernelIndependentSum, x::AbstractVector) +function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector) validate_domain(k, x) return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x)) end -function kernelmatrix_diag(k::KernelIndependentSum, x::AbstractVector, y::AbstractVector) +function kernelmatrix_diag(k::KernelTensorSum, x::AbstractVector, y::AbstractVector) validate_domain(k, x, y) return mapreduce(kernelmatrix_diag, +, k.kernels, slices(x), slices(y)) end -function Base.:(==)(x::KernelIndependentSum, y::KernelIndependentSum) +function Base.:(==)(x::KernelTensorSum, y::KernelTensorSum) return ( length(x.kernels) == length(y.kernels) && all(kx == ky for (kx, ky) in zip(x.kernels, y.kernels)) ) end -Base.show(io::IO, kernel::KernelIndependentSum) = printshifted(io, kernel, 0) +Base.show(io::IO, kernel::KernelTensorSum) = printshifted(io, kernel, 0) -function printshifted(io::IO, kernel::KernelIndependentSum, shift::Int) - print(io, "Independent sum of ", length(kernel), " kernels:") +function printshifted(io::IO, kernel::KernelTensorSum, shift::Int) + print(io, "Tensor sum of ", length(kernel), " kernels:") for k in kernel.kernels print(io, "\n") for _ in 1:(shift + 1) diff --git a/src/kernels/overloads.jl b/src/kernels/overloads.jl index 04026ae93..6b0ce7058 100644 --- a/src/kernels/overloads.jl +++ b/src/kernels/overloads.jl @@ -4,7 +4,7 @@ for (M, op, T) in ( (:Base, :+, :KernelSum), (:Base, :*, :KernelProduct), (:TensorCore, :tensor, :KernelTensorProduct), - (:KernelFunctions, :⊕, :KernelIndependentSum), + (:KernelFunctions, :⊕, :KernelTensorSum), ) @eval begin $M.$op(k1::Kernel, k2::Kernel) = $T(k1, k2) diff --git a/test/kernels/kernelindependentsum.jl b/test/kernels/kerneltensorsum.jl similarity index 68% rename from test/kernels/kernelindependentsum.jl rename to test/kernels/kerneltensorsum.jl index 064e1cf47..b8be6c965 100644 --- a/test/kernels/kernelindependentsum.jl +++ b/test/kernels/kerneltensorsum.jl @@ -1,4 +1,4 @@ -@testset "kernelindependentsum" begin +@testset "kerneltensorsum" begin rng = MersenneTwister(123456) u1 = rand(rng, 10) u2 = rand(rng, 10) @@ -8,14 +8,14 @@ # kernels k1 = SqExponentialKernel() k2 = ExponentialKernel() - kernel1 = KernelIndependentSum(k1, k2) - kernel2 = KernelIndependentSum([k1, k2]) + kernel1 = KernelTensorSum(k1, k2) + kernel2 = KernelTensorSum([k1, k2]) @test kernel1 == kernel2 - @test kernel1.kernels == (k1, k2) === KernelIndependentSum((k1, k2)).kernels + @test kernel1.kernels == (k1, k2) === KernelTensorSum((k1, k2)).kernels for (_k1, _k2) in Iterators.product( - (k1, KernelIndependentSum((k1,)), KernelIndependentSum([k1])), - (k2, KernelIndependentSum((k2,)), KernelIndependentSum([k2])), + (k1, KernelTensorSum((k1,)), KernelTensorSum([k1])), + (k2, KernelTensorSum((k2,)), KernelTensorSum([k2])), ) @test kernel1 == _k1 ⊕ _k2 end @@ -39,21 +39,21 @@ TestUtils.test_interface(kernel1, ColVecs{Float64}) TestUtils.test_interface(kernel1, RowVecs{Float64}) TestUtils.test_interface( - KernelIndependentSum(WhiteKernel(), ConstantKernel(; c=1.1)), ColVecs{String} + KernelTensorSum(WhiteKernel(), ConstantKernel(; c=1.1)), ColVecs{String} ) test_ADs( - x -> KernelIndependentSum(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))), + x -> KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))), rand(1); dims=[2, 2], ) types = [ColVecs{Float64,Matrix{Float64}}, RowVecs{Float64,Matrix{Float64}}] test_interface_ad_perf(2.1, StableRNG(123456), types) do c - KernelIndependentSum(SqExponentialKernel(), LinearKernel(; c=c)) + KernelTensorSum(SqExponentialKernel(), LinearKernel(; c=c)) end - test_params(KernelIndependentSum(k1, k2), (k1, k2)) + test_params(KernelTensorSum(k1, k2), (k1, k2)) @testset "single kernel" begin - kernel = KernelIndependentSum(k1) + kernel = KernelTensorSum(k1) @test length(kernel) == 1 @testset "eval" begin diff --git a/test/kernels/overloads.jl b/test/kernels/overloads.jl index 1981c3381..456cd7796 100644 --- a/test/kernels/overloads.jl +++ b/test/kernels/overloads.jl @@ -5,13 +5,9 @@ k2 = SqExponentialKernel() k3 = RationalQuadraticKernel() - for (op, T) in ( - (+, KernelSum), - (*, KernelProduct), - (⊗, KernelTensorProduct), - (⊕, KernelIndependentSum), - ) - if T === KernelTensorProduct || T === KernelIndependentSum + for (op, T) in + ((+, KernelSum), (*, KernelProduct), (⊗, KernelTensorProduct), (⊕, KernelTensorSum)) + if T === KernelTensorProduct || T === KernelTensorSum v2_1 = rand(rng, 2) v2_2 = rand(rng, 2) v3_1 = rand(rng, 3) diff --git a/test/runtests.jl b/test/runtests.jl index 61ab61a3f..c33d67300 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -125,7 +125,7 @@ include("test_utils.jl") include("kernels/kernelproduct.jl") include("kernels/kernelsum.jl") include("kernels/kerneltensorproduct.jl") - include("kernels/kernelindependentsum.jl") + include("kernels/kerneltensorsum.jl") include("kernels/overloads.jl") include("kernels/scaledkernel.jl") include("kernels/transformedkernel.jl") From e0164241800a904ce20a76d345d8fd6e6ef99ad2 Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 31 May 2023 12:55:59 +0200 Subject: [PATCH 10/11] =?UTF-8?q?Add=20non-Unicode=20alternative=20for=20?= =?UTF-8?q?=E2=8A=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/kernels/overloads.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kernels/overloads.jl b/src/kernels/overloads.jl index 6b0ce7058..a4075304e 100644 --- a/src/kernels/overloads.jl +++ b/src/kernels/overloads.jl @@ -1,4 +1,5 @@ -function ⊕ end +function kernel_sum end +const ⊕ = kernel_sum for (M, op, T) in ( (:Base, :+, :KernelSum), From 598fbc7bf7236c311661ce930cecc5105b91643a Mon Sep 17 00:00:00 2001 From: Martin Cornejo Date: Wed, 31 May 2023 14:56:34 +0200 Subject: [PATCH 11/11] Fix typo: `kernel_sum` -> `tensor_sum` --- src/kernels/overloads.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kernels/overloads.jl b/src/kernels/overloads.jl index a4075304e..e609a92bc 100644 --- a/src/kernels/overloads.jl +++ b/src/kernels/overloads.jl @@ -1,5 +1,5 @@ -function kernel_sum end -const ⊕ = kernel_sum +function tensor_sum end +const ⊕ = tensor_sum for (M, op, T) in ( (:Base, :+, :KernelSum),