//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Wed Oct 28 20:43:47 EDT 2020 * @see LICENSE (MIT style license file). * * @title Model Part: Convolutional Net Layer */ package scalation.analytics package classifier import scala.math.{min, max} import scalation.linalgebra.{MatriD, MatrixD, VectoD, VectorD, VectoI, VectorI} import scalation.random.RandomVecD import scalation.util.banner //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Filter` class holds a filter for averaging a region of an input vector. * @param height the height of the filter * @param width the width of the filter */ class Filter1D (height: Int = 5, width: Int = 5) { private val rvg = RandomVecD (width, 2.0) // random vector genertor private var vec = rvg.gen // the filter's vector //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update the parameters, i.e., the filter's vector. * @param vec the new vector parameters */ def update (vec2: VectoD): Unit = { vec = vec2 } } // Filter1D class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Filter` object provides the convolution operator. */ object Filter1D { def conv (x: VectoD, c: VectoD): VectoD = { val n = c.dim val phi = new VectorD (x.dim + 1 - n) for (j <- phi.range) phi(j) = x.slice (j, j+n) dot c phi } // conv def conv (x: MatriD, c: VectoD): MatriD = { MatrixD (for (i <- x.range1) yield conv (x(i), c)) } // conv def pool (x: VectoD, s: Int = 2): VectoD = { val p = new VectorD (x.dim / s) for (j <- p.range) { val jj = s*j p(j) = x.slice (jj, jj+s).max () } // for p } // pool } // Filter1D object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_1D` class implements a Convolutionsl Net Layer, meant to be * part of Convolutionsl Net classifier. The classifier is trained using a data * tensor 'x' and a classification vector 'y'. * @param x the input/data matrix with instances stored in rows * @param y the response/classification vector, where y_i = class for row i of matrix x * @param nf the number of filters for this convolutional layer * @param width the width of the filters */ class ConvNetLayer_1D (x: MatriD, y: VectoI, nf: Int = 1, width: Int = 5) { // private var c = new VectorD (width) // parameters (weights & biases) in to hid // private var b = new NetParam (weightMat (nz, ny), new VectorD (ny)) // parameters (weights & biases) hid to out // println (s"Create a ConvNetLayer_1D with $nx input, $nf filters and $ny output nodes") private val filt = Array.fill (nf)(new Filter1D (width)) // array of filters //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Filter the 'i'-th input vector with the 'f'-th filter. * @param i the index of the 'i'th row of the tensor * @param f the index of the 'f'th filter */ def filter (i: Int, f: Int): VectoD = { val xi = x(i) val ft = filt(f) val xf = new VectorD (xi.dim - width) // for (j <- xf.range) xf(j) = ft.dot (xi, j) xf } // filter //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update filter 'f's parameters. * @param f the index for the filter * @param vec2 the new paramters for the filter's vector */ def updateFilterParams (f: Int, vec2: VectoD): Unit = { filt(f).update (vec2) } // updateFilterParams //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the parameters 'c' and 'b'. */ // def parameters: NetParams = Array (c, b) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /* Given training data 'x_' and 'y_', fit the parametera 'a' and 'b'. * This is a simple algorithm that iterates over several epochs using gradient descent. * It does not use batching nor a sufficient stopping rule. * In practice, use the 'train' or 'train2' methods that use better optimizers. * @param x_ the training/full data/input matrix * @param y_ the training/full response/output matrix * def train0 (x_ : MatriD = x, y_ : MatriD = y): ConvNetLayer_1D = { println (s"train0: eta = $eta") var sse0 = Double.MaxValue // hold prior value of sse for (epoch <- 1 to maxEpochs) { // iterate over each epoch var z = f0.fM (conv (x_, c)) // Z = f(XA) var yp = f1.fM (b * z) // Yp = f(ZB) ee = yp - y_ // negative of the error matrix val d1 = f1.dM (yp) ** ee // delta matrix for y val d0 = f0.dM (z) ** (d1 * b.w.t) // delta matrix for z a -= (x_.t * d0 * eta, d0.mean * eta) // update 'a' weights & biases b -= (z.t * d1 * eta, d1.mean * eta) // update 'b' weights & biases val sse = sseF (y_, b * f1.fM (f0.fM (a * x_))) if (DEBUG) println (s"train0: parameters for $epoch th epoch: b = $b, sse = $sse") if (sse > sse0) return this // return early if moving up sse0 = sse // save prior sse this } // for this } // train0 */ } // ConvNetLayer_1D class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_1D` compnanion object provides factory functions for the * `ConvNetLayer_1D` class. */ object ConvNetLayer_1D { // TBD } // ConvNetLayer_1D object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_1DTest` object is used to test the `ConvNetLayer_1D` class. * Test using the simple example from section 11.10 of ScalaTion textbook. * > runMain scalation.analytics.classifier.ConvNetLayer_1DTest */ object ConvNetLayer_1DTest extends App { val x = VectorD (1, 2, 3, 4, 5) val c = VectorD (1, 2) val z = Filter1D.conv (x, c) val p = Filter1D.pool (z) println (s"input x = $x") println (s"filter c = $c") println (s"feature map z = $z") println (s"pooled p = $p") } // ConvNetLayer_1DTest