Skip to content

Commit

Permalink
dynaml.tensorflow: Added Dynamic System simulation and inference
Browse files Browse the repository at this point in the history
 - FiniteHorizonCTRNN: A continuous time recurrent neural network layer.
 - Loss for time slices of multivariate time series.
  • Loading branch information
mandar2812 committed Mar 9, 2018
1 parent fcc51d5 commit 429b06f
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
* */
package io.github.mandar2812.dynaml.tensorflow.layers

import org.platanios.tensorflow.api.{Output, Shape, tf}
import org.platanios.tensorflow.api.learn.Mode
import org.platanios.tensorflow.api.learn.layers.Layer
import org.platanios.tensorflow.api.ops.variables.{Initializer, RandomNormalInitializer}

/**
* Represents a Continuous Time Recurrent Neural Network (CTRNN)
* The layer simulates the discretized dynamics of the CTRNN for
* a fixed number of time steps.
*
* @author mandar2812 date: 2018/03/06
* */
case class FiniteHorizonCTRNN(
override val name: String, units: Int,
horizon: Int, timestep: Double,
weightsInitializer: Initializer = RandomNormalInitializer(),
biasInitializer: Initializer = RandomNormalInitializer(),
gainInitializer: Initializer = RandomNormalInitializer(),
timeConstantInitializer: Initializer = RandomNormalInitializer()) extends
Layer[Output, Output](name) {

override val layerType: String = "FHCTRNN"

override protected def _forward(input: Output, mode: Mode): Output = {

val weights = tf.variable("Weights", input.dataType, Shape(units, units), weightsInitializer)
val timeconstant = tf.variable("TimeConstant", input.dataType, Shape(units, units), timeConstantInitializer)
val gain = tf.variable("Gain", input.dataType, Shape(units, units), timeConstantInitializer)
val bias = tf.variable("Bias", input.dataType, Shape(units), biasInitializer)

tf.stack(
(1 to horizon).scanLeft(input)((x, _) => {
val decay = x.tensorDot(timeconstant.multiply(-1d), Seq(1), Seq(0))
val interaction = x.tensorDot(gain, Seq(1), Seq(0)).add(bias).tanh.tensorDot(weights, Seq(1), Seq(0))

x.add(decay.multiply(timestep)).add(interaction.multiply(timestep))
}).tail,
axis = -1)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
* */
package io.github.mandar2812.dynaml.tensorflow.learn

import org.platanios.tensorflow.api.learn.Mode
import org.platanios.tensorflow.api.learn.layers.Loss
import org.platanios.tensorflow.api.ops.Output

/**
* L2 loss for a time slice of a multivariate time series
*
* @author mandar2812 date 9/03/2018
* */
case class MVTimeSeriesLoss(override val name: String)
extends Loss[(Output, Output)](name) {
override val layerType: String = "L2Loss"

override protected def _forward(input: (Output, Output), mode: Mode): Output = {
input._1.subtract(input._2).square.mean(axes = 0).sum()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
* */
package io.github.mandar2812.dynaml.tensorflow.utils

import com.quantifind.charts.Highcharts.{regression, title, xAxis, yAxis}
import io.github.mandar2812.dynaml.evaluation.RegressionMetricsTF
import io.github.mandar2812.dynaml.tensorflow.dtf
import org.platanios.tensorflow.api._

/**
* Generalisation of [[RegressionMetricsTF]] to more complex output structures, like
* matrix outputs/labels.
*
* @author mandar2812 date: 9/03/2018
* */
class GenRegressionMetricsTF(preds: Tensor, targets: Tensor) extends RegressionMetricsTF(preds, targets) {
private val num_outputs =
if (preds.shape.toTensor().size == 1) 1
else preds.shape.toTensor()(0 :: -1).prod().scalar.asInstanceOf[Int]

private lazy val (_ , rmse , mae, corr) = GenRegressionMetricsTF.calculate(preds, targets)

private lazy val modelyield =
(preds.max(axes = 0) - preds.min(axes = 0)).divide(targets.max(axes = 0) - targets.min(axes = 0))

override protected def run(): Tensor = dtf.stack(Seq(rmse, mae, corr, modelyield), axis = -1)

override def generatePlots(): Unit = {
println("Generating Plot of Fit for each target")

if(num_outputs == 1) {
val (pr, tar) = (
scoresAndLabels._1.entriesIterator.map(_.asInstanceOf[Float]),
scoresAndLabels._2.entriesIterator.map(_.asInstanceOf[Float]))

regression(pr.zip(tar).toSeq)

title("Goodness of fit: "+name)
xAxis("Predicted "+name)
yAxis("Actual "+name)

} else {
(0 until num_outputs).foreach(output => {
val (pr, tar) = (
scoresAndLabels._1(::, output).entriesIterator.map(_.asInstanceOf[Float]),
scoresAndLabels._2(::, output).entriesIterator.map(_.asInstanceOf[Float]))

regression(pr.zip(tar).toSeq)
})
}
}
}

object GenRegressionMetricsTF {

protected def calculate(preds: Tensor, targets: Tensor): (Tensor, Tensor, Tensor, Tensor) = {
val error = targets.subtract(preds)

println("Shape of error tensor: "+error.shape.toString()+"\n")

val num_instances = error.shape(0)
val rmse = error.square.mean(axes = 0).sqrt

val mae = error.abs.mean(axes = 0)

val corr = {

val mean_preds = preds.mean(axes = 0)

val mean_targets = targets.mean(axes = 0)

val preds_c = preds.subtract(mean_preds)

val targets_c = targets.subtract(mean_targets)

val (sigma_t, sigma_p) = (targets_c.square.mean(axes = 0).sqrt, preds_c.square.mean(axes = 0).sqrt)

preds_c.multiply(targets_c).mean(axes = 0).divide(sigma_t.multiply(sigma_p))
}

(error, rmse, mae, corr)
}
}

0 comments on commit 429b06f

Please sign in to comment.