//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Thu Apr 30 21:41:43 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, VectorD, VectoI, VectorI} import scalation.random.RandomMatD import scalation.tenalgebra.Tensor3D import scalation.util.banner //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Filter` class holds a filter for averaging a region of an input matrix. * @param height the height of the filter * @param width the width of the filter */ class Filter (height: Int = 5, width: Int = 5) { private val rmg = RandomMatD (height, width, 2.0) // random matrix genertor private val mat = rmg.gen // the filter's matrix //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The dot product between a region of an input matrix and the filter's matrix. * @param xi the i-th row of the input tensor (e.g., one image matrix) * @param jk the indices for the top-left position of the input matrix region */ def dot (xi: MatriD, jk: (Int, Int)): Double = { var sum = 0.0 for (j <- mat.range1; k <- mat.range2) { sum += xi(j + jk._1, k + jk._2) * mat(j, k) } // for sum } // dot //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update the parameters, i.e., the filter's matrix. * @param mat the new matrix parameters */ def update (mat2: MatriD): Unit = { mat.set (mat2) } } // Filter class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Filter` object provides the convolution operator. */ object Filter { def conv (x: MatriD, c: MatriD): MatriD = { val (m, n) = (c.dim1, c.dim2) val phi = new MatrixD (x.dim1 + 1 - m, x.dim2 + 1 - n) for (j <- phi.range1; k <- phi.range2) { phi(j, k) = (x.slice (j, j+m, k, k+n) ** c).sum } // for phi } // conv def pool (x: MatriD, s: Int = 2): MatriD = { val p = new MatrixD (x.dim1 / s, x.dim2 / s) for (j <- p.range1; k <- p.range2) { val (jj, kk) = (s*j, s*k) p(j, k) = x.slice (jj, jj+s, kk, kk+s).max () } // for p } // pool } // Filter object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_2D` 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'. * Each row in the tensor is classified into one of 'k' classes numbered '0, ...0, k-1'. * Each column, depth in the tensor represents a 2D structure (e.g., an image). * @param x the input/data 3D tensor 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 * @param height the height of the filters * @param width the width of the filters */ class ConvNetLayer_2D (x: Tensor3D, y: VectoI, nf: Int = 5, height: Int = 5, width: Int = 5) { private val filt = Array.fill (nf)(new Filter (height, width)) // array of filters //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Filter the 'i'-th input matrix 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): MatriD = { val xi = x(i) val ft = filt(f) val xf = new MatrixD (xi.dim1 - height, xi.dim2 - width) for (j <- xf.range1; k <- xf.range2) xf(j, k) = ft.dot (xi, (j, k)) xf } // filter //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update filter 'f's parameters. * @param f the index for the filter * @param mat2 the new paramters for the filter's matrix */ def updateFilterParams (f: Int, mat2: MatriD): Unit = { filt(f).update (mat2) } // updateFilterParams } // ConvNetLayer_2D class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_2D` compnanion object provides factory functions for the * `ConvNetLayer_2D` class. */ object ConvNetLayer_2D { // TBD } // ConvNetLayer_2D object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_2DTest` object is used to test the `ConvNetLayer_2D` class. * Test using the simple example from section 11.11 of ScalaTion textbook. * > runMain scalation.analytics.classifier.ConvNetLayer_2DTest */ object ConvNetLayer_2DTest extends App { val x = new MatrixD ((5, 5), 0, 0, 2, 1, 0, 0, 0, 0, 1, 2, 1, 2, 2, 0, 2, 2, 0, 0, 0, 1, 2, 2, 2, 0, 1) val c = new MatrixD ((2, 2), 1, 1, 0, 1) val z = Filter.conv (x, c) val p = Filter.pool (z) println (s"input x = $x") println (s"filter c = $c") println (s"feature map z = $z") println (s"pooled p = $p") } // ConvNetLayer_2DTest //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_2DTest2` object is used to test the `ConvNetLayer_2D` class. * Test the first two images in MNIST. * @see http://rasbt.github.io/mlxtend/user_guide/data/mnist_data * > runMain scalation.analytics.classifier.ConvNetLayer_2DTest2 */ object ConvNetLayer_2DTest2 extends App { // 28-by-28 matrix as 56 rows with 14 columns (2 per marix row) val image1 = new MatrixD ((28, 28), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 51.0, 159.0, 253.0, 159.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 48.0, 238.0, 252.0, 252.0, 252.0, 237.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 54.0, 227.0, 253.0, 252.0, 239.0, 233.0, 252.0, 57.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 60.0, 224.0, 252.0, 253.0, 252.0, 202.0, 84.0, 252.0, 253.0, 122.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 163.0, 252.0, 252.0, 252.0, 253.0, 252.0, 252.0, 96.0, 189.0, 253.0, 167.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 51.0, 238.0, 253.0, 253.0, 190.0, 114.0, 253.0, 228.0, 47.0, 79.0, 255.0, 168.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 48.0, 238.0, 252.0, 252.0, 179.0, 12.0, 75.0, 121.0, 21.0, 0.0, 0.0, 253.0, 243.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 38.0, 165.0, 253.0, 233.0, 208.0, 84.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 253.0, 252.0, 165.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 7.0, 178.0, 252.0, 240.0, 71.0, 19.0, 28.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 253.0, 252.0, 195.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 57.0, 252.0, 252.0, 63.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 253.0, 252.0, 195.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 198.0, 253.0, 190.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 255.0, 253.0, 196.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 76.0, 246.0, 252.0, 112.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 253.0, 252.0, 148.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 252.0, 230.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 7.0, 135.0, 253.0, 186.0, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 252.0, 223.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 7.0, 131.0, 252.0, 225.0, 71.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 252.0, 145.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 48.0, 165.0, 252.0, 173.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 86.0, 253.0, 225.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 114.0, 238.0, 253.0, 162.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 252.0, 249.0, 146.0, 48.0, 29.0, 85.0, 178.0, 225.0, 253.0, 223.0, 167.0, 56.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 252.0, 252.0, 252.0, 229.0, 215.0, 252.0, 252.0, 252.0, 196.0, 130.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 28.0, 199.0, 252.0, 252.0, 253.0, 252.0, 252.0, 233.0, 145.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0, 128.0, 252.0, 253.0, 252.0, 141.0, 37.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) val image2 = new MatrixD (28, 28) println (s"image1 = $image1") // println (s"image2 = $image2") val x = new Tensor3D (2, 28, 28) x(0) = image1 x(1) = image2 val y = VectorI (1, 2) val nf = 2 val cn = new ConvNetLayer_2D (x, y) for (i <- x.range1) { banner (s"filtered image $i") for (f <- 0 until nf) { val xif = cn.filter (i, f) println (s"for i = $i, f = $f: xif = $xif") } // for } // for } // ConvNetLayer_2DTest2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `ConvNetLayer_2DTest3` object is used to test the `ConvNetLayer_2D` class. * Test the MNIST dataset of 10,000 images. * > runMain scalation.analytics.classifier.ConvNetLayer_2DTest3 */ object ConvNetLayer_2DTest3 extends App { // TBD } // ConvNetLayer_2DTest3 object