From d0281871fac1f59f4218ea1e4440f1991e742bb1 Mon Sep 17 00:00:00 2001 From: Andrei Kislitsyn Date: Wed, 5 May 2021 14:27:01 +0300 Subject: [PATCH] analytic tests and examples --- .../kmath/tensors/DataSetNormalization.kt | 46 ++++++ .../tensors/api/AnalyticTensorAlgebra.kt | 3 +- .../algebras/DoubleAnalyticTensorAlgebra.kt | 3 +- .../core/TestDoubleAnalyticTensorAlgebra.kt | 152 ++++++++++++++++-- 4 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt new file mode 100644 index 000000000..4d53d940b --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.algebras.BroadcastDoubleTensorAlgebra +import space.kscience.kmath.tensors.core.algebras.DoubleAnalyticTensorAlgebra + +// Dataset normalization + +fun main() { + + // work in context with analytic methods + DoubleAnalyticTensorAlgebra { + // take dataset of 5-element vectors from normal distribution + val dataset = randomNormal(intArrayOf(100, 5)) * 1.5 // all elements from N(0, 1.5) + BroadcastDoubleTensorAlgebra { + dataset += fromArray( + intArrayOf(5), + doubleArrayOf(0.0, 1.0, 1.5, 3.0, 5.0) // rows means + ) + } + + // find out mean and standard deviation of each column + val mean = dataset.mean(0, false) + val std = dataset.std(0, false) + + println("Mean:\n$mean") + println("Standard deviation:\n$std") + + // also we can calculate other statistic as minimum and maximum of rows + println("Minimum:\n${dataset.min(0, false)}") + println("Maximum:\n${dataset.max(0, false)}") + + // now we can scale dataset with mean normalization + val datasetScaled = BroadcastDoubleTensorAlgebra { (dataset - mean) / std } + + // find out mean and std of scaled dataset + + println("Mean of scaled:\n${datasetScaled.mean(0, false)}") + println("Mean of scaled:\n${datasetScaled.std(0, false)}") + } +} \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt index f9b2df45c..7784bfa45 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt @@ -51,7 +51,6 @@ public interface AnalyticTensorAlgebra : */ public fun Tensor.max(dim: Int, keepDim: Boolean): Tensor - /** * @return the mean of all elements in the input tensor. */ @@ -110,7 +109,7 @@ public interface AnalyticTensorAlgebra : public fun Tensor.exp(): Tensor //For information: https://pytorch.org/docs/stable/generated/torch.log.html - public fun Tensor.log(): Tensor + public fun Tensor.ln(): Tensor //For information: https://pytorch.org/docs/stable/generated/torch.sqrt.html public fun Tensor.sqrt(): Tensor diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/DoubleAnalyticTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/DoubleAnalyticTensorAlgebra.kt index 547018498..5580f845f 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/DoubleAnalyticTensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/DoubleAnalyticTensorAlgebra.kt @@ -25,7 +25,6 @@ public object DoubleAnalyticTensorAlgebra : override fun Tensor.max(dim: Int, keepDim: Boolean): DoubleTensor = foldDim({ x -> x.maxOrNull()!! }, dim, keepDim) - override fun Tensor.mean(): Double = this.fold { it.sum() / tensor.numElements } override fun Tensor.mean(dim: Int, keepDim: Boolean): DoubleTensor = @@ -70,7 +69,7 @@ public object DoubleAnalyticTensorAlgebra : override fun Tensor.exp(): DoubleTensor = tensor.map(::exp) - override fun Tensor.log(): DoubleTensor = tensor.map(::ln) + override fun Tensor.ln(): DoubleTensor = tensor.map(::ln) override fun Tensor.sqrt(): DoubleTensor = tensor.map(::sqrt) diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt index 835b8a08a..bebd65dc5 100644 --- a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt @@ -2,35 +2,159 @@ package space.kscience.kmath.tensors.core import space.kscience.kmath.operations.invoke import space.kscience.kmath.tensors.core.algebras.DoubleAnalyticTensorAlgebra -import kotlin.math.abs -import kotlin.math.exp +import space.kscience.kmath.tensors.core.algebras.DoubleAnalyticTensorAlgebra.tan +import kotlin.math.* import kotlin.test.Test import kotlin.test.assertTrue internal class TestDoubleAnalyticTensorAlgebra { val shape = intArrayOf(2, 1, 3, 2) - val buffer = doubleArrayOf(27.1, 20.0, 19.84, 23.123, 0.0, 1.0, 3.23, 133.7, 25.3, 100.3, 11.0, 12.012) + val buffer = doubleArrayOf( + 27.1, 20.0, 19.84, + 23.123, 3.0, 2.0, + + 3.23, 133.7, 25.3, + 100.3, 11.0, 12.012 + ) val tensor = DoubleTensor(shape, buffer) fun DoubleArray.fmap(transform: (Double) -> Double): DoubleArray { return this.map(transform).toDoubleArray() } - fun DoubleArray.epsEqual(other: DoubleArray, eps: Double = 1e-5): Boolean { - for ((elem1, elem2) in this.asSequence().zip(other.asSequence())) { - if (abs(elem1 - elem2) > eps) { - return false - } - } - return true + fun expectedTensor(transform: (Double) -> Double): DoubleTensor { + return DoubleTensor(shape, buffer.fmap(transform)) } @Test fun testExp() = DoubleAnalyticTensorAlgebra { - tensor.exp().let { - assertTrue { shape contentEquals it.shape } - assertTrue { buffer.fmap(::exp).epsEqual(it.mutableBuffer.array())} - } + assertTrue { tensor.exp() eq expectedTensor(::exp) } } + + @Test + fun testLog() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.ln() eq expectedTensor(::ln) } + } + + @Test + fun testSqrt() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.sqrt() eq expectedTensor(::sqrt) } + } + + @Test + fun testCos() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.cos() eq expectedTensor(::cos) } + } + + + @Test + fun testCosh() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.cosh() eq expectedTensor(::cosh) } + } + + @Test + fun testAcosh() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.acosh() eq expectedTensor(::acosh) } + } + + @Test + fun testSin() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.sin() eq expectedTensor(::sin) } + } + + @Test + fun testSinh() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.sinh() eq expectedTensor(::sinh) } + } + + @Test + fun testAsinh() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.asinh() eq expectedTensor(::asinh) } + } + + @Test + fun testTan() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.tan() eq expectedTensor(::tan) } + } + + @Test + fun testAtan() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.atan() eq expectedTensor(::atan) } + } + + @Test + fun testTanh() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.tanh() eq expectedTensor(::tanh) } + } + + @Test + fun testCeil() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.ceil() eq expectedTensor(::ceil) } + } + + @Test + fun testFloor() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor.floor() eq expectedTensor(::floor) } + } + + val shape2 = intArrayOf(2, 2) + val buffer2 = doubleArrayOf( + 1.0, 2.0, + -3.0, 4.0 + ) + val tensor2 = DoubleTensor(shape2, buffer2) + + @Test + fun testMin() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor2.min() == -3.0 } + assertTrue { tensor2.min(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-3.0, 2.0) + )} + assertTrue { tensor2.min(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(1.0, -3.0) + )} + } + + @Test + fun testMax() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor2.max() == 4.0 } + assertTrue { tensor2.max(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(1.0, 4.0) + )} + assertTrue { tensor2.max(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(2.0, 4.0) + )} + } + + @Test + fun testSum() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor2.sum() == 4.0 } + assertTrue { tensor2.sum(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-2.0, 6.0) + )} + assertTrue { tensor2.sum(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(3.0, 1.0) + )} + } + + @Test + fun testMean() = DoubleAnalyticTensorAlgebra { + assertTrue { tensor2.mean() == 1.0 } + assertTrue { tensor2.mean(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-1.0, 3.0) + )} + assertTrue { tensor2.mean(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(1.5, 0.5) + )} + } + } \ No newline at end of file