6.10.94-stable

Algebra

To demonstrate greycat’s machine learning capabilities, let’s start by building a dataset with correlated outputs and inputs

    @library("algebra");
    use util;
    use compute;
    use ml;
    use nn;


    var inputs = 2;
    var outputs = 1;
    var setSize = 1000;
    var tensorType = TensorType::f64;

    var random: Random = Random::new();
    random.setSeed(42);

    var tensorInput = Tensor {};
    tensorInput.init(tensorType, [setSize, inputs]);
    var index = [0, 0]; // index needs to be an int array of size = tensor dimensions
    do {
        tensorInput.set(index, random.uniformf(0.1, 1.0));
    } while (tensorInput.incPos(index)); // loop over flattened tensor positions

    // regression or autoencoder output
    var tensorOutput = Tensor {};
    tensorOutput.init(tensorType, [setSize, outputs]);
    index = [0, 0]; // index needs to be an int array of size = tensor dimensions
    var corelationCoefficients = [0.2, 0.6]; // set the corelation that we would like to determine later
    do {
        var noise = random.uniformf(0.0, 1.0) * (1 - corelationCoefficients[0] - corelationCoefficients[1]); // add some noise so the data does not perfectly corelate
        var val = corelationCoefficients[0] * tensorInput.get([index[0], 0]) + corelationCoefficients[1] * tensorInput.get([index[0], 1]) + noise;
        tensorOutput.set(index, val);
    } while (tensorOutput.incPos(index));

    // classification 1 hot encoded output
    var classes = 3;
    var tensorClassOutput = Tensor {};
    tensorClassOutput.init(tensorType, [setSize, classes]);
    var c: int;
    for (var i = 0; i < setSize; i++) {
        c = random.uniform(0, classes - 1); // select random class
        for ( var j= 0; j < classes; j++){
            if(j==c){
                tensorClassOutput.set([i, j], 1.0);
            } else{
                tensorClassOutput.set([i, j], 0.0);
            }

        }
    }

GaussianND (Gaussian n-dimensional)

Let’s build a Gaussian Profile from the inputs and outputs, and then crop the profile to also get the input and output profiles separately, and finally, we can get the correlation between the inputs and outputs

    var combinedTensor = Tensor {};
    combinedTensor.init(tensorType, [setSize, inputs + outputs]);
    for (var i = 0; i < setSize; i++) {
        // copy the input and output tensors into a single tensor
        combinedTensor.copyElementsFrom(tensorInput, i * inputs, inputs, i * (inputs + outputs));
        combinedTensor.copyElementsFrom(tensorOutput, i * outputs, outputs, i * (inputs + outputs) + inputs);
    }

    var profile = GaussianND {};
    // Learn the profile from the combined tensor
    profile.learn(combinedTensor);

    // Will return a table showing the correlation between inputs + outputs where
    // Col:0 = input-0, Col:1 = input-1, Col:2 = output-0
    // Row:0 = input-0, Row:1 = input-1, Row:2 = output-0
    var correlationTable = profile.correlation().toTable();
    println(correlationTable);


    // Crop the profile to get the input profiles
    var inputProfile = profile.crop(0, inputs - 1);

    // Crop the profile to get the output profiles
    var outputProfile = profile.crop(inputs, inputs + outputs - 1);

Printing the correlationTablem you will able to see the correlation information in the data attribute.

{"_type":"core.Table","meta":[{"_type":"core.TableColumnMeta","type":"core.float","size":3,"min":0.0518855985,"max":1.0,"avg":0.4624200185,"std":0.4866576421,"index":false,"header":null},{"_type":"core.TableColumnMeta","type":"core.float","size":3,"min":0.0518855985,"max":1.0,"avg":0.6509063759,"std":0.5211313808,"index":false,"header":null},{"_type":"core.TableColumnMeta","type":"core.float","size":3,"min":0.3353744572,"max":1.0,"avg":0.7454026622,"std":0.3585398777,"index":false,"header":null}],"data":[[1.0,0.0518855985,0.3353744572],[0.0518855985,1.0,0.9008335293],[0.3353744572,0.9008335293,1.0]]}

PCA

PCA is a dimensionality reduction technique that can be used to reduce the number of dimensions in a dataset while retaining most of the variance in the data. Requires a learned GaussianND to utilize, and then we can set the number of dimensions we want to keep.

    var pca = PCA {};
    pca.learn(inputProfile.correlation(), inputProfile.avg(), inputProfile.std(), null);
    pca.set_dim(inputs); 

Regression

Leverage the previously defined Tensor and PCA to start building a regression network that can predict the outputs from the inputs.


    var nnRegression: RegressionNetwork = RegressionNetwork::new(inputs, outputs, tensorType, false, null, 1234);

    // Set the pre and post processes for the network to use PCA and standard scaling respectively
    nnRegression.setPreProcess(PreProcessType::pca_scaling, pca);
    nnRegression.setPostProcess(PostProcessType::standard_scaling, outputProfile);

    nnRegression.addDenseLayer(5, true, ComputeActivationRelu {}, null);
    nnRegression.addDenseLayer(outputs, true, ComputeActivationRelu {}, null);

    nnRegression.setLoss(ComputeRegressionLoss::square, ComputeReduction::auto);
    nnRegression.setOptimizer(ComputeOptimizerAdam {}); 

    var res: ComputeModel = nnRegression.build(true);
    var engine: ComputeEngine = ComputeEngine::new();

    // Initialize the network with a batch size of 100 
    var batchSize = 100;
    nnRegression.initWithBatch(res, engine, null, batchSize);
    var nnInputs = nnRegression.getInput(engine);
    var nnTargets = nnRegression.getTarget(engine);

    // Split the dataset into training and testing sets (80% training, 20% testing) 
    var trainSetSize = (0.8 * setSize) as int;
    var testSetSize = (0.2 * setSize) as int;

    // saving the best model state
    var bestState = ComputeState {};
    var bestLoss : float?;
    
    for (var epoch = 0; epoch < 10; epoch++) {
        // track the loss for each epoch
        var loss : Tensor;
        var trainLossSum : float = 0.0;
        var testLossSum : float = 0.0;
        var trainIterations = trainSetSize / batchSize;
        // train
        for (var i = 0; i < trainIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorOutput, i * batchSize, batchSize, 0);
            nnRegression.miniBatch(engine); // accumulate gradients, optimize after loop
            loss = nnRegression.getDisplayLoss(engine); 
            trainLossSum = loss.sum();
        }
        nnRegression.optimize(engine); // optimize with accumulated gradients

        // test
        var testIterations = testSetSize / batchSize;
        for (var i = 0; i < testIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize + trainSetSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorOutput, i * batchSize + trainSetSize, batchSize, 0);
            nnRegression.test(engine);
            loss = nnRegression.getDisplayLoss(engine);
            testLossSum = loss.sum();
        }println("Epoch ${epoch} loss train: ${trainLossSum} test: ${testLossSum}");
        // saving model state if first epoch or loss reduced
        if (bestLoss == null ||  testLossSum < bestLoss){
            engine.saveState(bestState);
            bestLoss = testLossSum;
        }
    }

    // loading model state
    engine.configure(true); // set to inferecne mode (only forward)
    engine.loadState(bestState);
    
    // inferece using latest test batch
    var tensorOut = nnRegression.predict(engine, nnInputs);
    var arrayOut = [];
    for (var col = 0; col < tensorOut.shape()[1]; col++) {
        arrayOut.add(tensorOut.get([0, col]) as float);
    }
    println("Inference output: ${arrayOut}");
  

Classification


    var nnClassification = ClassificationNetwork::new(inputs, classes, tensorType, false, null, 1234, true, true, false );

    // Set the pre process for the network to use PCA scaling
    nnClassification.setPreProcess(PreProcessType::pca_scaling, pca);

    nnClassification.addDenseLayer(5, true, ComputeActivationRelu {}, null);
    nnClassification.addDenseLayer(classes, true, ComputeActivationRelu {}, null);

    nnClassification.setLoss(ComputeClassificationLoss::categorical_cross_entropy, ComputeReduction::auto);
    nnClassification.setOptimizer(ComputeOptimizerAdam {}); 

    var res: ComputeModel = nnClassification.build(true);
    var engine: ComputeEngine = ComputeEngine::new();

    // Initialize the network with a batch size of 100 
    var batchSize = 100;
    nnClassification.initWithBatch(res, engine, null, batchSize);
    var nnInputs = nnClassification.getInput(engine);
    var nnTargets = nnClassification.getTarget(engine);

    println(nnTargets.type());
    println(tensorClassOutput.type());

    // Split the dataset into training and testing sets (80% training, 20% testing) 
    var trainSetSize = (0.8 * setSize) as int;
    var testSetSize = (0.2 * setSize) as int;

    // saving the best model state
    var bestState = ComputeState {};
    var bestLoss : float?;

    for (var epoch = 0; epoch < 10; epoch++) {
        // track the loss for each epoch
        var loss : Tensor;
        var trainLossSum : float = 0.0;
        var testLossSum : float = 0.0;
        var trainIterations = trainSetSize / batchSize;
        // train
        for (var i = 0; i < trainIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorClassOutput, i * batchSize, batchSize, 0);
            loss = nnClassification.miniBatch(engine); // accumulate gradients, optimize after loop
            trainLossSum = trainLossSum + loss.sum();
        }
        nnClassification.optimize(engine); // optimize with accumulated gradients

        // test
        var testIterations = testSetSize / batchSize;
        for (var i = 0; i < testIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize + trainSetSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorOutput, i * batchSize + trainSetSize, batchSize, 0);
            loss = nnClassification.test(engine);
            testLossSum = testLossSum + loss.sum();
        }
        println("Epoch ${epoch} loss train: ${trainLossSum} test: ${testLossSum}");
        // saving model state if first epoch or loss reduced
        if (bestLoss == null ||  testLossSum < bestLoss){
            engine.saveState(bestState);
            bestLoss = testLossSum;
        }
    }

    // loading model state
    engine.configure(true); // set to inferecne mode (only forward)
    engine.loadState(bestState);
    
    // inferece using latest test batch
    var tensorOut = nnClassification.predict(engine, nnInputs);
    var arrayOut = [];
    for (var col = 0; col < tensorOut.shape()[1]; col++) {
        arrayOut.add(tensorOut.get([0, col]) as float);
    }
    println("Inference output: ${arrayOut}");

Auto encoder


    var nnAutoEnCoder = AutoEncoderNetwork::new(inputs, tensorType, false, null, 1234);

    // Set the pre and post processes for the network to use PCA and standard scaling respectively
    nnAutoEnCoder.setPreProcess(PreProcessType::pca_scaling, pca);
    nnAutoEnCoder.setPostProcess(PostProcessType::standard_scaling, inputProfile);

    nnAutoEnCoder.addDenseLayer(5, true, ComputeActivationRelu {}, null);
    nnAutoEnCoder.addLinearLayer(inputs, true, null);

    nnAutoEnCoder.setLoss(ComputeRegressionLoss::square, ComputeReduction::auto);
    nnAutoEnCoder.setOptimizer(ComputeOptimizerAdam {}); 

    var res: ComputeModel = nnAutoEnCoder.build(true);
    var engine: ComputeEngine = ComputeEngine::new();

    // Initialize the network with a batch size of 100 
    var batchSize = 100;
    nnAutoEnCoder.initWithBatch(res, engine, null, batchSize);
    var nnInputs = nnAutoEnCoder.getInput(engine);
    var nnTargets = nnAutoEnCoder.getTarget(engine);

    // Split the dataset into training and testing sets (80% training, 20% testing) 
    var trainSetSize = (0.8 * setSize) as int;
    var testSetSize = (0.2 * setSize) as int;

    // saving the best model state
    var bestState = ComputeState {};
    var bestLoss : float?;

    // train loop
    for (var epoch = 0; epoch < 10; epoch++) {
        // track the loss for each epoch
        var loss : Tensor;
        var trainLossSum : float = 0.0;
        var testLossSum : float = 0.0;
        var trainIterations = trainSetSize / batchSize;
        // train
        for (var i = 0; i < trainIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorInput, i * batchSize, batchSize, 0); // target same as inputs
            nnAutoEnCoder.miniBatch(engine); // accumulate gradients, optimize after loop
            loss = nnAutoEnCoder.getDisplayLoss(engine); 
            trainLossSum = loss.sum();
        }
        nnAutoEnCoder.optimize(engine); // optimize with accumulated gradients

        // test
        var testIterations = testSetSize / batchSize;
        for (var i = 0; i < testIterations; i++) {
            nnInputs.copyElementsFrom(tensorInput, i * batchSize + trainSetSize, batchSize, 0);
            nnTargets.copyElementsFrom(tensorOutput, i * batchSize + trainSetSize, batchSize, 0);
            nnAutoEnCoder.test(engine);
            loss = nnAutoEnCoder.getDisplayLoss(engine);
            testLossSum = loss.sum();
        }
        println("Epoch ${epoch} loss train: ${trainLossSum} test: ${testLossSum}");

        // saving model state if first epoch or loss reduced
        if (bestLoss == null ||  testLossSum < bestLoss){
            engine.saveState(bestState);
            bestLoss = testLossSum;
        }
    }

    // loading model state
    engine.configure(true); // set to inferecne mode (only forward)
    engine.loadState(bestState);
    
    // inferece using latest test batch
    var tensorEndoded = nnAutoEnCoder.encode(engine, nnInputs);
    println("Shape of encoded tensor determined by hidden layer size: ${tensorEndoded.shape()}");
    var tensorOut = nnAutoEnCoder.decode(engine,tensorEndoded);
    var arrayOut = [];
    for (var col = 0; col < tensorOut.shape()[1]; col++) {
        arrayOut.add(tensorOut.get([0, col]) as float);
    }
    println("Decoded output: ${arrayOut}");