//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Sat Dec 21 12:53:44 EST 2019 * @see LICENSE (MIT style license file). * * @title Model: Neural Network Classifier with 3 Layers (input, hidden and output layers) * @see hebb.mit.edu/courses/9.641/2002/lectures/lecture03.pdf */ package scalation.analytics package classifier import scalation.linalgebra.{FunctionV_2V, MatriD, MatrixD, MatriI, VectoD, VectoI} import scalation.util.banner import ActivationFun._ import PredictorMat2.rescaleX //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_Classif_3L` class supports multi-output, 3-layer (input, hidden and output) * Neural-Network classifiers. Given several input vectors and output vectors (training data), * fit the parameters 'a' and 'b' connecting the layers, so that for a new input vector 'v', * the net can classify the output value, i.e., *

* yp = f1 (b * f0 (a * v)) *

* where 'f0' and 'f1' are the activation functions and the parameter 'a' and 'b' * are the parameters between input-hidden and hidden-output layers. * Note: 'f1' is set to 'f_sigmoid' * @param x the m-by-nx input matrix (training data consisting of m input vectors) * @param y the m output vector (training data consisting of m output integer values) * @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) * the activation function family for layers 2->3 (hidden to output) is sigmoid */ class NeuralNet_Classif_3L (x: MatriD, y: VectoI, nz_ : Int = -1, fname_ : Strings = null, hparam: HyperParameter = NeuralNet_Classif_3L.hp, f0: AFF = f_tanh) extends NeuralNet_3L (x, MatrixD (Seq (y.toDouble)), nz_, fname_, hparam, f0, f_sigmoid) { private val DEBUG = true // debug flag private val cThresh = hparam ("cThresh") // classification/decision threshold private val conf = new ConfusionFit (y) // Quality of Fit (QoF) / Confusion Matrix //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Given a new input vector 'v', classify the output/response value 'f(v)'. * @param v the new input vector */ def classify (v: VectoD): Int = (predict (v) + cThresh).toInt //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Given an input matrix 'v', predict the output/response vector 'f(v)'. * @param v the input matrix */ def classify (v: MatriD = x): VectoI = (predictV (v).col(0) + cThresh).toInt //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Compare the actual class 'y' vector versus the predicted class 'yp' vector, * returning the confusion matrix 'cmat', which for 'k = 2' is *

* yp 0 1 * ---------- * y 0 | tn fp | * 1 | fn tp | * ---------- *

* Note: ScalaTion's confusion matrix is Actual × Predicted, but to swap the position of * actual 'y' (rows) with predicted 'yp' (columns) simply use 'cmat.t', the transpose of 'cmat'. * @see www.dataschool.io/simple-guide-to-confusion-matrix-terminology * @param yp the precicted class values/labels * @param yy the actual class values/labels for full (y) or test (y_e) dataset */ def confusion (yp: VectoI, yy: VectoI = y): MatriI = conf.confusion (yp, yy) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Contract the actual class 'y' vector versus the predicted class 'yp' vector. * @param yp the precicted class values/labels * @param yy the actual class values/labels for full (y) or test (y_e) dataset */ def contrast (yp: VectoI, yy: VectoI = y): Unit = { conf.contrast (yp, yy) } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Produce a summary report with diagnostics and the overall quality of fit. * @param b the parameters of the model * @param show flag indicating whether to print the summary */ def summary (b: VectoD = null, show: Boolean = false): String = conf.summary (b, show) } // NeuralNet_Classif_3L //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_Classif_3L` companion object provides factory functions for buidling three-layer * (one hidden layer) neural network classifiers. Note, 'rescale' is defined in `ModelFactory` * in Model.scala. */ object NeuralNet_Classif_3L extends ModelFactory { private val DEBUG = true // debug flag val hp = Classifier.hp ++ Optimizer.hp // combine the hyper-parameters //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Create a `NeuralNet_3L` for a data matrix and response vector. * @param x the input/data matrix * @param y the output/response vector * @param nz the number of nodes in hidden layer * @param fname the feature/variable names * @param hparam the hyper-parameters * @param f0 the activation function family for layers 1->2 (input to output) */ def apply (x: MatriD, y: VectoI, nz: Int = -1, fname: Strings = null, hparam: HyperParameter = hp, f0: AFF = f_tanh): NeuralNet_Classif_3L = { val x_s = if (rescale) rescaleX (x, f0) else x if (DEBUG) println (s" scaled: x = $x_s \n y = $y") new NeuralNet_Classif_3L (x_s, y, nz, fname, hparam, f0) } // apply } // NeuralNet_Classif_3L object //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NeuralNet_Classif_3LTest` object is used to test the `NeuralNet_Classif_3L` class. * It tests the Neural Network three layer classifier on Diabetes dataset. * > runMain scalation.analytics.classifier.NeuralNet_Classif_3LTest */ object NeuralNet_Classif_3LTest extends App { val fname = BASE_DIR + "diabetes.csv" val xy = MatrixD (fname) val (x, y) = ClassifierReal.pullResponse (xy) val fn = Array ("pregnancies", "glucose", "blood pressure", "skin thickness", "insulin", "BMI", "diabetes pedigree function", "age") // feature names val cn = Array ("tested_positive", "tested_negative") // class names banner ("NeuralNet_Classif_3LTest: diabetes dataset") val hp2 = NeuralNet_Classif_3L.hp.updateReturn (("cThresh", 0.48), ("eta", 0.2)) val nn = NeuralNet_Classif_3L (x, y, -1, fn, hp2) nn.train2 () val yp = nn.classify () nn.confusion (yp) banner ("NeuralNet_Classif_3LTest Results") // nn.contrast (yp) // println (nn.report) println (nn.summary ()) // nn.crossValidateRand (5, true) banner ("NullModel: diabetes dataset") val nm = new NullModel (y) nm.train () val yp0 = nm.classify () nm.confusion (yp0) banner ("NullModel Results") // nm.contrast (yp0) // println (nm.report) println (nm.summary ()) } // NeuralNet_Classif_3LTest object