//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Sat Jun 13 13:37:06 EDT 2020 * @see LICENSE (MIT style license file). * * @title Model: Feed-Forward Neural Network for Time Series */ package scalation.analytics package forecaster import scala.collection.mutable.Set import scalation.linalgebra.{FunctionV_2V, MatriD, MatrixD, VectoD, VectorD} import scalation.linalgebra.VectorD.one import scalation.math.{double_exp, noDouble} import scalation.plot.{Plot, PlotM} import scalation.random.{Normal, Random} import scalation.stat.Statistic import scalation.util.banner import ActivationFun._ //import ImputeBackward.impute import ImputeMean.impute import MatrixTransform._ import PredictorMat2.{rescaleX, rescaleY} //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_3L1_4TSy` class uses a neural network to fit the lagged data. * Lag columns ranging from 'lag1' (inclusive) to 'lag2' (inclusive) are added before * delegating the problem to the `NeuralNet_3L1` class. A constant term for intercept * can be added (@see 'allForms' method). * @param y_ the output/response m vector (training data consisting of m output values) * the initial output/response vector (before lag term expansion) * @param nz the number of nodes in hidden layer (-1 => use default formula) * @param fname_ the feature/variable names (if null, use x_j's) * @param hparam the hyper-parameters for the model/network * @param f0 the activation function family for layers 1->2 (input to hidden) * @param f1 the activation function family for layers 2->3 (hidden to output) * @param itran the inverse transformation function returns responses to original scale */ class NeuralNet_3L1_4TSy (y_ : VectoD, nz: Int = -1, fname_ : Strings = null, hparam: HyperParameter = NeuralNet_3L1_4TS.hp, f0: AFF = f_tanh, f1: AFF = f_id, itran: FunctionV_2V = null) extends NeuralNet_3L1 (Regression4TS.allForms_y (y_, hparam ("lag1").toInt, hparam ("lag2").toInt, true), y_, nz, fname_, hparam, f0, f1, itran) with ForecasterMat { private val DEBUG = true // debug flag modelName = s"NeuralNet_3L1_4TSy($nx)" // name of the modeling technique //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Compute the error (difference between actual and forecasted) and useful * diagnostics for the test dataset. * @param ym the mean of the actual response/output vector (full/training) * @param yy the actual response/output vector (full/testing) * @param yf the forecasted response/output vector (full/testing) */ override def eval (ym: Double, yy: VectoD, yf: VectoD): NeuralNet_3L1_4TSy = { if (DEBUG) { println (s"eval: ym = $ym") println (s"eval: yy.dim = ${yy.dim}") println (s"eval: yf.dim = ${yf.dim}") } // if if (ee == null) ee = new MatrixD (yy.dim, yy.dim) ee(0) = yy - yf // compute residual/error vector e, i.e., ee(0) fit.diagnose (ee(0), yy, yf, null, ym) // compute diagnostics this } // eval def forecastAll (xe: MatriD = getX, h: Int = 1): VectoD = { val yf = new VectorD (m) for (t <- 0 until m) yf(t) = forecast (xe, t, h) if (DEBUG) println (s"yf = $yf") yf } // forecastAll //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Produce a forecast for 'h' steps ahead into the future. * Note: the forecasts for time 't = 0, ... , h-1' will be duplicates. * @param xe the relevant expanded data matrix * @param t the time for the forecast * @param h the forecasting horizon, number of steps ahead to produce forecast */ def forecast (xe: MatriD, t: Int, h: Int = 1): Double = { val tt = math.max (0, t+1-h) // time @ row h-1 back in matrix predict (xe(tt)) } // forecast } // NeuralNet_3L1_4TSy class import NeuralNet_3L1_4TS.hp //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_3L1_4TSy` companion object provides factory functions and functions * for creating functional forms. */ object NeuralNet_3L1_4TSy extends ModelFactory { private val DEBUG = true // debug flag //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Create a `NeuralNet_3L1_4TSy` object from a response vector. * This factory function provides data rescaling. * @see `ModelFactory` * @param y the output/response m-vector (before expansion) * @param nz the number of nodes in hidden layer (-1 => use default formula) * @param fname the feature/variable names (if null, use x_j's) * @param hparam the hyper-parameters for the model/network * @param f0 the activation function family for layers 1->2 (input to hidden) * @param f1 the activation function family for layers 2->3 (hidden to output) */ def apply (y: VectoD, nz: Int = -1, fname: Strings = null, hparam: HyperParameter = NeuralNet_3L1_4TS.hp, f0: AFF = f_tanh, f1: AFF = f_id): NeuralNet_3L1_4TSy = { val hp2 = if (hparam == null) hp else hparam var itran: FunctionV_2V = null // inverse transform -> original scale // FIX - the rescaling is not working val y_s = if (f1.bounds != null) { val y_i = rescaleY (y, f1); itran = y_i._2; y_i._1 } else y if (DEBUG) println (s" scaled: y = $y_s") new NeuralNet_3L1_4TSy (y_s, nz, fname, hp2, f0, f1, itran) } // apply //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The number of terms include current value and lag one value. * when there are no cross-terms. * @param k number of features/predictor variables (not counting intercept) */ override def numTerms (k: Int) = 2 * k + 1 // FIX } // NeuralNet_3L1_4TSy object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_3L1_4TSyTest` object is used to test the `NeuralNet_3L1_4TSy` class. * > runMain scalation.analytics.forecaster.NeuralNet_3L1_4TSTest */ object NeuralNet_3L1_4TSyTest extends App { val ran = Random () val n = 100 val y = VectorD (for (i <- 0 until n) yield i + 10.0 * ran.gen) banner ("Build AR(1) Model") val ar = new AR (y) // time series data ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (null, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Select model based on ACF and PACF") ar.plotFunc (ar.acF, "ACF") ar.plotFunc (ar.pacF, "PACF") banner ("NeuralNet_3L1_4TSy") val hp2 = NeuralNet_3L1_4TS.hp.updateReturn (("lag1", 1.0), ("lag2", 2.0)) val nn = new NeuralNet_3L1_4TSy (y, hparam = hp2) nn.analyze () println (nn.report) val yp = nn.predict () new Plot (null, y, yp, "Plot of y, yp_nn vs. t", true) } // NeuralNet_3L1_4TSyTest object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_3L1_4TSyTest2` object is used to test the `NeuralNet_3L1_4TSy` class. * The order 'p' hyper-parameter should be re-assigned (try 1, 2, 3, ...). * > runMain scalation.analytics.forecaster.NeuralNet_3L1_4TSyTest2 */ object NeuralNet_3L1_4TSyTest2 extends App { val noise = Normal (0.0, 1.0) val n = 30 val t = VectorD.range (0, n) val y = VectorD (for (i <- 0 until n) yield i + noise.gen) val p = 2 // try different values for p banner ("Build AR($p) Model") ARMA.hp("p") = p // reassign hyper-parameter p val ar = new AR (y) // time series data ar.train (null, y) // train for AR(p) model ar.eval () // evaluate the QoF println (ar.report) new Plot (t, y, ar.predictAll (), s"Plot of y, AR($p) vs. t", true) banner ("Make Forecasts") val steps = 10 // number of steps for the forecasts val ar_f = ar.forecast (steps) println (s"$steps-step ahead forecasts using AR($p) model = $ar_f") val tf = VectorD.range (n, n + steps) new Plot (tf, ar_f, null, s"Plot ar($p) forecasts vs. t", true) banner ("NeuralNet_3L1_4TSy") val hp2 = NeuralNet_3L1_4TS.hp.updateReturn (("lag1", 1.0), ("lag2", 2.0)) val nn = new NeuralNet_3L1_4TSy (y, hparam = hp2) nn.analyze () println (nn.report) // val yp = nn.predict () val yp = nn.forecastAll () new Plot (null, y, yp, "Plot of y, yp_nn vs. t", true) } // NeuralNet_3L1_4TSyTest2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_3L1_4TSyTest3` object is used to test the `NeuralNet_3L1_4TSy` class. * Forecasting lake levels. * @see cran.r-project.org/web/packages/fpp/fpp.pdf * > runMain scalation.analytics.forecaster.NeuralNet_3L1_4TSyTest3 */ object NeuralNet_3L1_4TSyTest3 extends App { import ForecasterVec.{t, y} banner ("Build AR(1) Model") val ar = new AR (y) // time series data ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), s"Plot of y, AR(1) vs. t", true) banner ("Select model based on ACF and PACF") ar.plotFunc (ar.acF, "ACF") ar.plotFunc (ar.pacF, "PACF") banner ("NeuralNet_3L1_4TSy") val hp2 = NeuralNet_3L1_4TS.hp.updateReturn (("lag1", 1.0), ("lag2", 2.0)) val nn = NeuralNet_3L1_4TSy (y, hparam = hp2) val xe = nn.getX println (s"xe = $xe") nn.train2 () nn.eval () println (nn.report) // val yp = nn.predict () val yp = nn.forecastAll () new Plot (null, y, yp, "Plot of y, yp_nn vs. t", true) banner ("Rolling Validation") val stats = SimpleRollingValidation.crossValidate3 (nn, kt_ = 1) // val stats = RollingValidation.crossValidate3 (nn, kt_ = 2) Fit.showQofStatTable (stats) } // NeuralNet_3L1_4TSyTest3 object