From cccff293781c0dffaa88e7d037efa4de08ef76b7 Mon Sep 17 00:00:00 2001 From: Roland Grinis Date: Tue, 13 Jul 2021 19:48:21 +0100 Subject: [PATCH] deep learning example --- kmath-noa/README.md | 121 +++++++++++++++++- kmath-noa/build.gradle.kts | 3 +- .../java/space/kscience/kmath/noa/JNoa.java | 2 +- .../space/kscience/kmath/noa/algebras.kt | 10 +- .../resources/space_kscience_kmath_noa_JNoa.h | 4 +- .../kscience/kmath/noa/TestJitModules.kt | 55 ++++++++ kmath-noa/src/test/resources/data.pt | Bin 0 -> 4575 bytes kmath-noa/src/test/resources/jitscripts.py | 52 ++++++++ kmath-noa/src/test/resources/loss.pt | Bin 0 -> 6630 bytes kmath-noa/src/test/resources/net.pt | Bin 0 -> 7558 bytes 10 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 kmath-noa/src/test/kotlin/space/kscience/kmath/noa/TestJitModules.kt create mode 100644 kmath-noa/src/test/resources/data.pt create mode 100644 kmath-noa/src/test/resources/jitscripts.py create mode 100644 kmath-noa/src/test/resources/loss.pt create mode 100644 kmath-noa/src/test/resources/net.pt diff --git a/kmath-noa/README.md b/kmath-noa/README.md index 0774a9800..502169f20 100644 --- a/kmath-noa/README.md +++ b/kmath-noa/README.md @@ -45,6 +45,8 @@ To load the native library you will need to add to the VM options: ## Usage +###Linear Algebra + We implement the tensor algebra interfaces from [kmath-tensors](../kmath-tensors): ```kotlin @@ -62,10 +64,9 @@ NoaFloat { tensorU dot (diagonalEmbedding(tensorS) dot tensorV.transpose(-2, -1)) } ``` - +### Automatic Differentiation The [AutoGrad](https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html) engine is exposed: - ```kotlin NoaFloat { // Create a quadratic function @@ -88,11 +89,122 @@ NoaFloat { val hessianAtX = expressionAtX.autoHessian(tensorX) } ``` +### Deep Learning +You can train any [TorchScript](https://pytorch.org/docs/stable/jit.html) model. +For example, you can build in `python` the following neural network +and prepare the training data: +```python +import torch +n_tr = 7 +n_val = 300 +x_val = torch.linspace(-5, 5, n_val).view(-1, 1) +y_val = torch.sin(x_val) +x_train = torch.linspace(-3.14, 3.14, n_tr).view(-1, 1) +y_train = torch.sin(x_train) + torch.randn_like(x_train) * 0.1 + +class Data(torch.nn.Module): + def __init__(self): + super(Data, self).__init__() + self.register_buffer('x_val', x_val) + self.register_buffer('y_val', y_val) + self.register_buffer('x_train', x_train) + self.register_buffer('y_train', y_train) + +class Net(torch.nn.Module): + def __init__(self): + super(Net, self).__init__() + self.l1 = torch.nn.Linear(1, 10, bias = True) + self.l2 = torch.nn.Linear(10, 10, bias = True) + self.l3 = torch.nn.Linear(10, 1, bias = True) + + def forward(self, x): + x = self.l1(x) + x = torch.relu(x) + x = self.l2(x) + x = torch.relu(x) + x = self.l3(x) + return x + +class Loss(torch.nn.Module): + def __init__(self, target): + super(Loss, self).__init__() + self.register_buffer('target', target) + self.loss = torch.nn.MSELoss() + + def forward(self, x): + return self.loss(x, self.target) + +# Generated TorchScript modules and serialise +torch.jit.script(Data()).save('data.pt') +torch.jit.script(Net()).save('net.pt') +torch.jit.script(Loss(y_train)).save('loss.pt') +``` + +You can then load the modules into `kotlin` and train them: +```kotlin +NoaFloat { + + // Load the serialised JIT modules + // The training data + val dataModule = loadJitModule("data.pt") + // The DL model + val netModule = loadJitModule("net.pt") + // The loss function + val lossModule = loadJitModule("loss.pt") + + // Get the tensors from the module + val xTrain = dataModule.getBuffer("x_train") + val yTrain = dataModule.getBuffer("y_train") + val xVal = dataModule.getBuffer("x_val") + val yVal = dataModule.getBuffer("y_val") + + // Set the model in training mode + netModule.train(true) + // Loss function set for training regime + lossModule.setBuffer("target", yTrain) + + // Compute the predictions + val yPred = netModule.forward(xTrain) + // Compute the training error + val loss = lossModule.forward(yPred) + println(loss) + + // Set-up the Adam optimiser with learning rate 0.005 + val optimiser = netModule.adamOptimiser(0.005) + + // Train for 250 epochs + repeat(250){ + // Clean gradients + optimiser.zeroGrad() + // Use forwardAssign to for better memory management + netModule.forwardAssign(xTrain, yPred) + lossModule.forwardAssign(yPred, loss) + // Backward pass + loss.backward() + // Update model parameters + optimiser.step() + if(it % 50 == 0) + println("Training loss: $loss") + } + + // Finally validate the model + // Compute the predictions for the validation features + netModule.forwardAssign(xVal, yPred) + // Set the loss for the true values + lossModule.setBuffer("target", yVal) + // Compute the loss on validation dataset + lossModule.forwardAssign(yPred, loss) + println("Validation loss: $loss") + } + +``` + +### Custom memory management Native memory management relies on scoping with [NoaScope](src/main/kotlin/space/kscience/kmath/noa/memory/NoaScope.kt) -which is readily within an algebra context. +which is readily available within an algebra context. Manual management is also possible: ```kotlin // Create a scope @@ -107,4 +219,5 @@ val tensor = NoaFloat(scope){ scope.disposeAll() // Attempts to use tensor here is undefined behaviour -``` \ No newline at end of file +``` +Contributed by [Roland Grinis](https://github.com/grinisrit) diff --git a/kmath-noa/build.gradle.kts b/kmath-noa/build.gradle.kts index ce4db4337..85933eb4b 100644 --- a/kmath-noa/build.gradle.kts +++ b/kmath-noa/build.gradle.kts @@ -160,8 +160,7 @@ tasks["compileJava"].dependsOn(buildCpp) tasks { withType{ - systemProperty("java.library.path", //"$home/devspace/noa/cmake-build-release/kmath") - "$cppBuildDir/kmath") + systemProperty("java.library.path", "$cppBuildDir/kmath") } } diff --git a/kmath-noa/src/main/java/space/kscience/kmath/noa/JNoa.java b/kmath-noa/src/main/java/space/kscience/kmath/noa/JNoa.java index 17677c01f..24e3c2b05 100644 --- a/kmath-noa/src/main/java/space/kscience/kmath/noa/JNoa.java +++ b/kmath-noa/src/main/java/space/kscience/kmath/noa/JNoa.java @@ -298,7 +298,7 @@ class JNoa { public static native long forwardPass(long jitModuleHandle, long tensorHandle); - public static native void forwardPassAssign(long jitModuleHandle, long tensorHandle); + public static native void forwardPassAssign(long jitModuleHandle, long featuresHandle, long predsHandle); public static native long getModuleParameter(long jitModuleHandle, String name); diff --git a/kmath-noa/src/main/kotlin/space/kscience/kmath/noa/algebras.kt b/kmath-noa/src/main/kotlin/space/kscience/kmath/noa/algebras.kt index 94a2a0192..2d880656b 100644 --- a/kmath-noa/src/main/kotlin/space/kscience/kmath/noa/algebras.kt +++ b/kmath-noa/src/main/kotlin/space/kscience/kmath/noa/algebras.kt @@ -141,11 +141,11 @@ protected constructor(protected val scope: NoaScope) : public abstract fun loadJitModule(path: String, device: Device = Device.CPU): NoaJitModule - public fun NoaJitModule.forward(parameters: Tensor): TensorType = - wrap(JNoa.forwardPass(jitModuleHandle, parameters.tensor.tensorHandle)) + public fun NoaJitModule.forward(features: Tensor): TensorType = + wrap(JNoa.forwardPass(jitModuleHandle, features.tensor.tensorHandle)) - public fun NoaJitModule.forwardAssign(parameters: TensorType): Unit = - JNoa.forwardPassAssign(jitModuleHandle, parameters.tensorHandle) + public fun NoaJitModule.forwardAssign(features: TensorType, predictions: TensorType): Unit = + JNoa.forwardPassAssign(jitModuleHandle, features.tensorHandle, predictions.tensorHandle) public fun NoaJitModule.getParameter(name: String): TensorType = wrap(JNoa.getModuleParameter(jitModuleHandle, name)) @@ -154,7 +154,7 @@ protected constructor(protected val scope: NoaScope) : JNoa.setModuleParameter(jitModuleHandle, name, parameter.tensor.tensorHandle) public fun NoaJitModule.getBuffer(name: String): TensorType = - wrap(JNoa.getModuleParameter(jitModuleHandle, name)) + wrap(JNoa.getModuleBuffer(jitModuleHandle, name)) public fun NoaJitModule.setBuffer(name: String, buffer: Tensor): Unit = JNoa.setModuleBuffer(jitModuleHandle, name, buffer.tensor.tensorHandle) diff --git a/kmath-noa/src/main/resources/space_kscience_kmath_noa_JNoa.h b/kmath-noa/src/main/resources/space_kscience_kmath_noa_JNoa.h index fedf394fa..f058e859e 100644 --- a/kmath-noa/src/main/resources/space_kscience_kmath_noa_JNoa.h +++ b/kmath-noa/src/main/resources/space_kscience_kmath_noa_JNoa.h @@ -1122,10 +1122,10 @@ JNIEXPORT jlong JNICALL Java_space_kscience_kmath_noa_JNoa_forwardPass /* * Class: space_kscience_kmath_noa_JNoa * Method: forwardPassAssign - * Signature: (JJ)V + * Signature: (JJJ)V */ JNIEXPORT void JNICALL Java_space_kscience_kmath_noa_JNoa_forwardPassAssign - (JNIEnv *, jclass, jlong, jlong); + (JNIEnv *, jclass, jlong, jlong, jlong); /* * Class: space_kscience_kmath_noa_JNoa diff --git a/kmath-noa/src/test/kotlin/space/kscience/kmath/noa/TestJitModules.kt b/kmath-noa/src/test/kotlin/space/kscience/kmath/noa/TestJitModules.kt new file mode 100644 index 000000000..0d6fbdbdc --- /dev/null +++ b/kmath-noa/src/test/kotlin/space/kscience/kmath/noa/TestJitModules.kt @@ -0,0 +1,55 @@ +/* + * 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.noa + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertTrue + +class TestJitModules { + + private val resources = File("").resolve("src/test/resources") + private val dataPath = resources.resolve("data.pt").absolutePath + private val netPath = resources.resolve("net.pt").absolutePath + private val lossPath = resources.resolve("loss.pt").absolutePath + + @Test + fun testOptimisation() = NoaFloat { + + setSeed(SEED) + + val dataModule = loadJitModule(dataPath) + val netModule = loadJitModule(netPath) + val lossModule = loadJitModule(lossPath) + + val xTrain = dataModule.getBuffer("x_train") + val yTrain = dataModule.getBuffer("y_train") + val xVal = dataModule.getBuffer("x_val") + val yVal = dataModule.getBuffer("y_val") + + netModule.train(true) + lossModule.setBuffer("target", yTrain) + + val yPred = netModule.forward(xTrain) + val loss = lossModule.forward(yPred) + val optimiser = netModule.adamOptimiser(0.005) + + repeat(250){ + optimiser.zeroGrad() + netModule.forwardAssign(xTrain, yPred) + lossModule.forwardAssign(yPred, loss) + loss.backward() + optimiser.step() + } + + netModule.forwardAssign(xVal, yPred) + lossModule.setBuffer("target", yVal) + lossModule.forwardAssign(yPred, loss) + + assertTrue(loss.value() < 0.1) + }!! + +} \ No newline at end of file diff --git a/kmath-noa/src/test/resources/data.pt b/kmath-noa/src/test/resources/data.pt new file mode 100644 index 0000000000000000000000000000000000000000..17eee0d289faddf449a5b68085cb2cf85992bad5 GIT binary patch literal 4575 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 Q-rm9bt|@-3*VkP8U+V>5{{R30 literal 0 HcmV?d00001 diff --git a/kmath-noa/src/test/resources/jitscripts.py b/kmath-noa/src/test/resources/jitscripts.py new file mode 100644 index 000000000..b199deef8 --- /dev/null +++ b/kmath-noa/src/test/resources/jitscripts.py @@ -0,0 +1,52 @@ +import torch + +torch.manual_seed(987654) + +n_tr = 7 +n_val = 300 + +x_val = torch.linspace(-5, 5, n_val).view(-1, 1) +y_val = torch.sin(x_val) +x_train = torch.linspace(-3.14, 3.14, n_tr).view(-1, 1) +y_train = torch.sin(x_train) + torch.randn_like(x_train) * 0.1 + + +class Data(torch.nn.Module): + def __init__(self): + super(Data, self).__init__() + self.register_buffer('x_val', x_val) + self.register_buffer('y_val', y_val) + self.register_buffer('x_train', x_train) + self.register_buffer('y_train', y_train) + + +class Net(torch.nn.Module): + def __init__(self): + super(Net, self).__init__() + self.l1 = torch.nn.Linear(1, 10, bias = True) + self.l2 = torch.nn.Linear(10, 10, bias = True) + self.l3 = torch.nn.Linear(10, 1, bias = True) + + def forward(self, x): + x = self.l1(x) + x = torch.relu(x) + x = self.l2(x) + x = torch.relu(x) + x = self.l3(x) + return x + +class Loss(torch.nn.Module): + def __init__(self, target): + super(Loss, self).__init__() + self.register_buffer('target', target) + self.loss = torch.nn.MSELoss() + + def forward(self, x): + return self.loss(x, self.target) + + +torch.jit.script(Data()).save('data.pt') + +torch.jit.script(Net()).save('net.pt') + +torch.jit.script(Loss(y_train)).save('loss.pt') diff --git a/kmath-noa/src/test/resources/loss.pt b/kmath-noa/src/test/resources/loss.pt new file mode 100644 index 0000000000000000000000000000000000000000..091dd58e9bc2edcf18d591fede10d4960addd2a5 GIT binary patch literal 6630 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 literal 0 HcmV?d00001 diff --git a/kmath-noa/src/test/resources/net.pt b/kmath-noa/src/test/resources/net.pt new file mode 100644 index 0000000000000000000000000000000000000000..f1ec8b1f279da7c62a7553f7c20dc43371edc9bc GIT binary patch literal 7558 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 literal 0 HcmV?d00001