In Machine Learning con il termine di regressione si fa riferimento all'apprendimento di una target function (o modello) a valori numerici continui. Questo modello può essere lineare se la funzione che lo rappresenta è una funzione del tipo:
y=w<sub>0</sub>+w<sub>1</sub>x<sub>1</sub>+w<sub>2</sub>x<sub>2</sub>+...+w<sub>n</sub>x<sub>n</sub>
dove i simboli
w<sub>0</sub>,w<sub>1</sub>,...,w<sub>n</sub>
rappresentano i pesi, o variabili del modello, mentre
x<sub>1</sub>,x<sub>2</sub>,...,x<sub>n</sub>
sono le caratteristiche numeriche dei dati in input.
La regressione diventa logistica se oltre ad utilizzare un modello lineare introduciamo anche una funzione sigmoide a valori in [0,1], detta anche funzione logistica, sulla variabile y
di output del modello lineare:
sigmoid(y) = 1 / (1 + e<sup>-y</sup>)
Quindi, nel caso di un modello di regressione lineare l'output da confrontare con i dati di training in un apprendimento supervisionato sarà il valore y
, mentre nel caso di regressione logistica sarà sigmoid(y)
.
Un esempio di regressione lineare
Iniziamo con il recuperare un dataset per addestrare il nostro modello lineare realizzato con Deeplearning4j.
Il dataset è reperibile al seguente URL: Concrete Compressive Strength. In esso abbiamo 1.030 istanze, 8 valori di input che fanno riferimento agli ingredienti del calcestruzzo e un unico valore di output che indica la resistenza
del calcestruzzo alla compressione.
Il nostro obiettivo è provare ad individuare un modello lineare che sia in grado di prevedere con buona approssimazione questa resistenza, dati i valori in input. Non utilizziamo direttamente il file Excel scaricato dal sito ma lo trasformiamo in un file csv con la virgola come separatore.
Portare i dati sulla stessa scala
Tutte le caratteristiche devono essere portate sulla stessa scala. La maggior parte degli algoritmi di Machine Learning si comporta molto meglio se effettuiamo questo tipo di operazione. L'importanza di tale aspetto può essere illustrata con un esempio.
Immaginiamo di avere due caratteristiche, una con valori da 0 a 10 e l'altra con valori da 0 a 100.000. Quando consideriamo la funzione di errore da minimizzare, l'algoritmo sarà impegnato molto di più ad ottimizzare i pesi il cui errore fa riferimento alla caratteristica con valori più grandi piuttosto che a quella con valori più piccoli, portando ad uno sbilanciamento nell'apprendimento.
Per poter scalare opportunamente i valori dei dati in input abbiamo due metodologie a disposizione: normalizzazione e standardizzazione. La normalizzazione consiste in una riduzione in scala su un intervallo [0,1], un caso particolare di scalatura min-max.
Con la standardizzazione centriamo la colonna della caratteristica in modo tale che abbia valore medio nullo e varianza unitaria. Abbiamo cosi una distribuzione di probabilità dei valori di tipo normale, dalla quale è più facile derivare i pesi. Rispetto alla scalatura min-max la standardizzazione mantiene informazioni utili riguardo alle anomalie e rende l'algoritmo meno sensibile a queste ultime.
Il progetto
Per poter utilizzare Deeplearning4j, possiamo creare un progetto Maven con il seguente pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>it.deeplearning</groupId>
<artifactId>deeplearning</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<dl4j.version>1.0.0-M1</dl4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-native-platform</artifactId>
<version>${dl4j.version}</version>
</dependency>
<!--<dependency>
<groupId>org.nd4j</groupId>
<artifactId>nd4j-cuda-10.2</artifactId>
<version>1.0.0-beta7</version>
</dependency>-->
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-core</artifactId>
<version>${dl4j.version}</version>
</dependency>
</dependencies>
</project>
La dipendenza org.nd4j può essere utilizzata al posto di quella nativa se intendiamo lavorare su GPU.
Definiamo una classe Regression
per la regressione lineare ed inseriamo al suo interno un metodo per la lettura del dataset e sua standardizzazione.
Come ultimo task, effettuiamo un split del dataset in modo tale che una sua parte sia utilizzata per il training e l'altra per il testing.
import org.datavec.api.records.reader.RecordReader;
import org.datavec.api.records.reader.impl.csv.CSVRecordReader;
import org.datavec.api.split.FileSplit;
import org.deeplearning4j.datasets.datavec.RecordReaderDataSetIterator;
import org.deeplearning4j.nn.api.OptimizationAlgorithm;
import org.deeplearning4j.nn.conf.MultiLayerConfiguration;
import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
import org.deeplearning4j.nn.conf.layers.OutputLayer;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.nn.weights.WeightInit;
import org.deeplearning4j.optimize.listeners.ScoreIterationListener;
import org.nd4j.common.io.ClassPathResource;
import org.nd4j.evaluation.regression.RegressionEvaluation;
import org.nd4j.linalg.activations.Activation;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.dataset.SplitTestAndTrain;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.nd4j.linalg.dataset.api.preprocessor.DataNormalization;
import org.nd4j.linalg.dataset.api.preprocessor.NormalizerStandardize;
import org.nd4j.linalg.learning.config.Nesterovs;
import org.nd4j.linalg.lossfunctions.LossFunctions;
import java.io.IOException;
public class Regression {
private final int BATCH_SIZE;
private final int ATTRIBUTES;
private final double FRACTION_TRAIN;
private final int SEED;
public Regression(int batchSize, int attributes, double fractionTrain, int seed){
BATCH_SIZE = batchSize;
ATTRIBUTES = attributes;
FRACTION_TRAIN = fractionTrain;
SEED = seed;
}
public DataSet[] readingData(String path){
try (RecordReader recordReader = new CSVRecordReader(0, ',')) {
recordReader.initialize(new FileSplit(new ClassPathResource(path).getFile()));
DataSetIterator iterator = new RecordReaderDataSetIterator(recordReader, 1030, ATTRIBUTES, 1);
// Dataset completamente in memoria
DataSet allData = iterator.next();
allData.shuffle(SEED);
DataNormalization normalizer = new NormalizerStandardize();
// Standardizzazione anche sui valori di output
normalizer.fitLabel(true);
SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(FRACTION_TRAIN);
DataSet[] dataSet = new DataSet[]{testAndTrain.getTrain(), testAndTrain.getTest()};
// Recupero dati statistici
normalizer.fit(dataSet[0]);
// Standardizzazione
normalizer.transform(dataSet[0]);
normalizer.transform(dataSet[1]);
return dataSet;
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
...
}
Questa classe riceverà in input la dimensione del batch per la fase di training, il numero di attributi di input del dataset, la frazione dei dati di training ed un seed necessario
per mescolarli.