JavaRush /Java Blog /Random-IT /Machine Learning per sviluppatori Java, parte 2

Machine Learning per sviluppatori Java, parte 2

Pubblicato nel gruppo Random-IT
Machine Learning per sviluppatori Java, parte 1
Machine Learning per sviluppatori Java, parte 2 - 1

Stima della funzione obiettivo

Ricordiamo che la funzione target , detta anche funzione di previsione, è il risultato del processo di preparazione o addestramento. Matematicamente, la sfida è trovare una funzione che prenda una variabile come input хe restituisca il valore previsto у.
Machine Learning per sviluppatori Java, parte 2 - 2
Nell'apprendimento automatico, una funzione di costo (J(θ))viene utilizzata per calcolare il valore di errore o "costo" di una determinata funzione obiettivo.
Machine Learning per sviluppatori Java, parte 2 - 3
La funzione di costo mostra quanto bene il modello si adatta ai dati di addestramento. Per determinare il costo della funzione obiettivo mostrata sopra, è necessario calcolare l'errore quadrato di ciascuna casa di esempio (i). L'errore è la distanza tra il valore calcolato уe il valore reale ydella casa dell'esempio i.
Machine Learning per sviluppatori Java, parte 2 - 4
Ad esempio, il prezzo reale di una casa con una superficie di 1330 = 6.500.000 € . E la differenza tra il prezzo della casa previsto dalla funzione obiettivo addestrata è 7.032.478 € : la differenza (o errore) è 532.478 € . Puoi anche vedere questa differenza nel grafico qui sopra. La differenza (o l'errore) viene mostrata come linee rosse tratteggiate verticali per ciascuna coppia di allenamento per area di prezzo. Dopo aver calcolato il costo della funzione obiettivo addestrata, è necessario sommare l'errore quadrato per ciascuna casa nell'esempio e calcolare il valore principale. Minore è il valore del prezzo (J(θ)), più accurate saranno le previsioni della nostra funzione obiettivo. Il Listato 3 mostra una semplice implementazione Java di una funzione di costo che prende come input una funzione obiettivo, un elenco di dati di training ed etichette ad essi associati. I valori di previsione verranno calcolati in loop e l'errore verrà calcolato sottraendo il valore del prezzo reale (preso dall'etichetta). Successivamente verrà sommato il quadrato degli errori e calcolato il valore dell'errore. Il costo verrà restituito come valore di tipo double:

Elenco-3

public static double cost(Function<ltDouble[], Double> targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels) {
 int m = dataset.size();
 double sumSquaredErrors = 0;

 // рассчет квадрата ошибки («разницы») для каждого тренировочного примера и //добавление его к сумме
 for (int i = 0; i < m; i++) {
 // получаем вектор признаков из текущего примера
 Double[] featureVector = dataset.get(i);
 // предсказываем meaning и вычисляем ошибку базируясь на реальном
 //значении (метка)
 double predicted = targetFunction.apply(featureVector);
 double label = labels.get(i);
 double gap = predicted - label;
 sumSquaredErrors += Math.pow(gap, 2);
 }

 // Вычисляем и возращаем meaning ошибки (чем меньше тем лучше)
 return (1.0 / (2 * m)) * sumSquaredErrors;
}
Interessato a leggere su Java? Unisciti al gruppo di sviluppatori Java !

Apprendimento della funzione target

Sebbene la funzione di costo aiuti a valutare la qualità della funzione obiettivo e dei parametri theta, è comunque necessario trovare i parametri theta più adatti. A questo scopo è possibile utilizzare l'algoritmo di discesa del gradiente.

Discesa gradiente

La discesa del gradiente minimizza la funzione di costo. Ciò significa che viene utilizzato per trovare i parametri theta che hanno il costo minimo (J(θ))in base ai dati di training. Ecco un algoritmo semplificato per calcolare valori theta nuovi e più appropriati:
Machine Learning per sviluppatori Java, parte 2 - 5
Pertanto, i parametri del vettore theta miglioreranno ad ogni iterazione dell'algoritmo. Il coefficiente di apprendimento α specifica il numero di calcoli ad ogni iterazione. Questi calcoli possono essere eseguiti finché non si trovano valori theta "buoni". Ad esempio, la funzione di regressione lineare riportata di seguito ha tre parametri theta:
Machine Learning per sviluppatori Java, parte 2 - 6
Ad ogni iterazione, verrà calcolato un nuovo valore per ciascuno dei parametri theta: , e . Dopo ogni iterazione, è possibile creare una nuova implementazione più appropriata utilizzando il nuovo vettore theta 0 , θ 1 , θ 2 } . Il Listato -4 mostra il codice Java per l'algoritmo di decadimento del gradiente. Theta per la funzione di regressione verrà addestrato utilizzando dati di addestramento, dati marcatori e tasso di apprendimento . Il risultato sarà una funzione obiettivo migliorata utilizzando i parametri theta. Il metodo verrà richiamato più e più volte, passando la nuova funzione obiettivo e i nuovi parametri theta dai calcoli precedenti. E queste chiamate verranno ripetute finché la funzione obiettivo configurata non raggiungerà un plateau minimo: θ0θ1θ2LinearRegressionFunction(α)train()

Elenco-4

public static LinearRegressionFunction train(LinearRegressionFunction targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels,
 double alpha) {
 int m = dataset.size();
 double[] thetaVector = targetFunction.getThetas();
 double[] newThetaVector = new double[thetaVector.length];

 // вычисление нового значения тета для каждого element тета массива
 for (int j = 0; j < thetaVector.length; j++) {
 // сумируем разницу ошибки * признак
 double sumErrors = 0;
 for (int i = 0; i < m; i++) {
 Double[] featureVector = dataset.get(i);
 double error = targetFunction.apply(featureVector) - labels.get(i);
 sumErrors += error * featureVector[j];
 }

 //вычисляем новые значения тета
 double gradient = (1.0 / m) * sumErrors;
 newThetaVector[j] = thetaVector[j] - alpha * gradient;
 }

 return new LinearRegressionFunction(newThetaVector);
}
Per garantire che il costo diminuisca continuamente, è possibile eseguire la funzione di costo J(θ)dopo ogni passaggio di training. Dopo ogni iterazione, il costo dovrebbe diminuire. Se ciò non accade significa che il valore del coefficiente di apprendimento è troppo grande e l'algoritmo ha semplicemente mancato il valore minimo. In tal caso, l'algoritmo di decadimento del gradiente fallisce. I grafici seguenti mostrano la funzione obiettivo utilizzando i nuovi parametri theta calcolati, a partire dal vettore theta iniziale {1.0, 1.0}. La colonna di sinistra mostra il grafico della funzione di previsione dopo 50 iterazioni; colonna centrale dopo 200 ripetizioni; e la colonna di destra dopo 1000 ripetizioni. Da questi possiamo vedere che il prezzo diminuisce dopo ogni iterazione e la nuova funzione obiettivo si adatta sempre meglio. Dopo 500-600 ripetizioni, i parametri theta non cambiano più in modo significativo e il prezzo raggiunge un plateau stabile. Successivamente, la precisione della funzione target non può essere migliorata in questo modo.
Machine Learning per sviluppatori Java, parte 2 - 7
In questo caso, anche se il costo non diminuisce più in modo significativo dopo 500-600 iterazioni, la funzione obiettivo non è ancora ottimale. Ciò indica una discrepanza . Nell'apprendimento automatico, il termine "incoerenza" viene utilizzato per indicare che l'algoritmo di apprendimento non trova le tendenze sottostanti nei dati. Sulla base dell'esperienza reale, per le proprietà più grandi è probabile che ci si aspetti una riduzione del prezzo al metro quadrato. Da ciò possiamo concludere che il modello utilizzato per il processo di apprendimento della funzione target non si adatta abbastanza bene ai dati. La discrepanza è spesso dovuta ad un’eccessiva semplificazione del modello. Questo è successo nel nostro caso, la funzione obiettivo è troppo semplice e per l'analisi utilizza un unico parametro: l'area della casa. Ma queste informazioni non sono sufficienti per prevedere con precisione il prezzo di una casa.

Aggiungere funzionalità e ridimensionarle

Se scopri che la tua funzione obiettivo non corrisponde al problema che stai cercando di risolvere, deve essere modificata. Un modo comune per correggere l'incoerenza è aggiungere funzionalità aggiuntive al vettore delle funzionalità. Nell'esempio del prezzo di una casa puoi aggiungere caratteristiche come il numero di stanze o l'età della casa. Cioè, invece di utilizzare un vettore con un valore di caratteristica {size}per descrivere una casa, è possibile utilizzare un vettore con diversi valori, ad esempio, {size, number-of-rooms, age}. In alcuni casi, il numero di caratteristiche nei dati di addestramento disponibili non è sufficiente. Quindi vale la pena provare a utilizzare le caratteristiche polinomiali calcolate utilizzando quelle esistenti. Ad esempio, hai la possibilità di estendere la funzione obiettivo per determinare il prezzo di una casa in modo che includa una caratteristica calcolata di metri quadrati (x2):
Machine Learning per sviluppatori Java, parte 2 - 8
L'utilizzo di più funzionalità richiede il ridimensionamento delle funzionalità , che viene utilizzato per standardizzare l'intervallo tra funzionalità diverse. Pertanto, l'intervallo di valori per l'attributo size 2 è significativamente più ampio dell'intervallo di valori per l'attributo size. Senza il ridimensionamento delle caratteristiche, la dimensione 2 influenzerà indebitamente la funzione di costo. L'errore introdotto dall'attributo size 2 sarà significativamente più grande dell'errore introdotto dall'attributo size. Di seguito è riportato un semplice algoritmo di ridimensionamento delle funzionalità:
Machine Learning per sviluppatori Java, parte 2 - 9
Questo algoritmo è implementato nella classe FeaturesScalingnel codice di esempio seguente. La lezione FeaturesScalingpresenta un metodo commerciale per creare una funzione di ridimensionamento sintonizzata sui dati di training. Internamente, le istanze dei dati di training vengono utilizzate per calcolare i valori medi, minimi e massimi. La funzione risultante prende il vettore delle caratteristiche e ne produce uno nuovo con le caratteristiche in scala. Il ridimensionamento delle funzionalità è necessario sia per il processo di apprendimento che per il processo di previsione, come mostrato di seguito:
// создание массива данных
List<ltDouble[]> dataset = new ArrayList<>();
dataset.add(new Double[] { 1.0, 90.0, 8100.0 }); // feature vector of house#1
dataset.add(new Double[] { 1.0, 101.0, 10201.0 }); // feature vector of house#2
dataset.add(new Double[] { 1.0, 103.0, 10609.0 }); // ...
//...

// создание меток
List<ltDouble> labels = new ArrayList<>();
labels.add(249.0); // price label of house#1
labels.add(338.0); // price label of house#2
labels.add(304.0); // ...
//...

// создание расширенного списка признаков
Function<ltDouble[], Double[]> scalingFunc = FeaturesScaling.createFunction(dataset);
List<ltDouble[]> scaledDataset = dataset.stream().map(scalingFunc).collect(Collectors.toList());

// создаем функцию которая инициализирует теты и осуществляет обучение //используя коэффициент обучения 0.1

LinearRegressionFunction targetFunction = new LinearRegressionFunction(new double[] { 1.0, 1.0, 1.0 });
for (int i = 0; i < 10000; i++) {
 targetFunction = Learner.train(targetFunction, scaledDataset, labels, 0.1);
}

// делаем предсказание стоимости дома с площадью 600 m2
Double[] scaledFeatureVector = scalingFunc.apply(new Double[] { 1.0, 600.0, 360000.0 });
double predictedPrice = targetFunction.apply(scaledFeatureVector);
Man mano che vengono aggiunte sempre più funzionalità, l'adattamento alla funzione obiettivo aumenta, ma fai attenzione. Se vai troppo oltre e aggiungi troppe funzionalità, potresti finire per apprendere una funzione obiettivo che è troppo adatta.

Over-matching e convalida incrociata

L'overfitting si verifica quando la funzione obiettivo o il modello si adatta troppo bene ai dati di addestramento, al punto da catturare rumore o variazioni casuali nei dati di addestramento. Un esempio di overfitting è mostrato nel grafico più a destra di seguito:
Machine Learning per sviluppatori Java, parte 2 - 10
Tuttavia, un modello di overfitting funziona molto bene sui dati di addestramento, ma avrà prestazioni scarse su dati reali sconosciuti. Esistono diversi modi per evitare un adattamento eccessivo.
  • Utilizza un set di dati più ampio per l'addestramento.
  • Utilizza meno funzionalità come mostrato nei grafici sopra.
  • Utilizza un algoritmo di machine learning migliorato che tenga conto della regolarizzazione.
Se un algoritmo di previsione si adatta eccessivamente ai dati di addestramento, è necessario eliminare le funzionalità che non ne favoriscono l'accuratezza. La difficoltà è trovare caratteristiche che abbiano un effetto più significativo sull’accuratezza della previsione rispetto ad altre. Come mostrato nei grafici, il sovraadattamento può essere determinato visivamente utilizzando i grafici. Funziona bene per grafici con 2 o 3 coordinate, diventa difficile tracciare e valutare il grafico se si utilizzano più di 2 funzioni. Nella convalida incrociata, esegui nuovamente il test dei modelli dopo l'addestramento utilizzando dati sconosciuti all'algoritmo al termine del processo di addestramento. I dati etichettati disponibili devono essere divisi in 3 insiemi:
  • dati di allenamento;
  • dati di verifica;
  • dati di test.
In questo caso, il 60% dei record etichettati che caratterizzano le case dovrebbero essere utilizzati nel processo di addestramento delle varianti dell'algoritmo target. Dopo il processo di addestramento, la metà dei dati rimanenti (non utilizzati in precedenza) dovrebbe essere utilizzata per verificare che l'algoritmo target addestrato funzioni bene sui dati sconosciuti. In genere, viene selezionato per l'uso l'algoritmo che offre prestazioni migliori rispetto ad altri. I dati rimanenti vengono utilizzati per calcolare il valore di errore per il modello finale selezionato. Esistono altre tecniche di convalida incrociata, come k-fold . Tuttavia non li descriverò in questo articolo.

Strumenti di machine learning e framework Weka

La maggior parte dei framework e delle librerie forniscono un'ampia raccolta di algoritmi di apprendimento automatico. Inoltre, forniscono una comoda interfaccia di alto livello per l'addestramento, il test e l'elaborazione dei modelli di dati. Weka è uno dei framework più popolari per la JVM. Weka è una pratica libreria Java che contiene test grafici per la validazione dei modelli. L'esempio seguente utilizza la libreria Weka per creare un set di dati di addestramento che contiene funzionalità ed etichette. Metodo setClassIndex(): per contrassegnare. In Weka, un'etichetta è definita come una classe:
// определяем атрибуты для признаков и меток
ArrayList<ltAttribute> attributes = new ArrayList<>();
Attribute sizeAttribute = new Attribute("sizeFeature");
attributes.add(sizeAttribute);
Attribute squaredSizeAttribute = new Attribute("squaredSizeFeature");
attributes.add(squaredSizeAttribute);
Attribute priceAttribute = new Attribute("priceLabel");
attributes.add(priceAttribute);


// создаем и заполняем список признаков 5000 примеров
Instances trainingDataset = new Instances("trainData", attributes, 5000);
trainingDataset.setClassIndex(trainingSet.numAttributes() - 1);
Instance instance = new DenseInstance(3);

instance.setValue(sizeAttribute, 90.0);
instance.setValue(squaredSizeAttribute, Math.pow(90.0, 2));
instance.setValue(priceAttribute, 249.0);
trainingDataset.add(instance);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 101.0);
...
Il set di dati e l'oggetto campione possono essere salvati e caricati da un file. Weka utilizza ARFF (Attribute Relation File Format) che è supportato dai benchmark grafici di Weka. Questo set di dati viene utilizzato per addestrare una funzione obiettivo nota come classificatore in Weka. Innanzitutto bisogna definire la funzione obiettivo. Il codice seguente LinearRegressioncreerà un'istanza del classificatore. Questo classificatore verrà addestrato utilizzando buildClassifier(). Il metodo buildClassifier()seleziona i parametri theta in base ai dati di training alla ricerca del miglior modello target. Con Weka non devi preoccuparti di impostare la velocità di apprendimento o il numero di iterazioni. Weka esegue anche il ridimensionamento delle funzionalità in modo indipendente.
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
Una volta effettuate queste impostazioni, la funzione obiettivo può essere utilizzata per prevedere il prezzo della casa, come mostrato di seguito:
Instances unlabeledInstances = new Instances("predictionset", attributes, 1);
unlabeledInstances.setClassIndex(trainingSet.numAttributes() - 1);
Instance unlabeled = new DenseInstance(3);
unlabeled.setValue(sizeAttribute, 1330.0);
unlabeled.setValue(squaredSizeAttribute, Math.pow(1330.0, 2));
unlabeledInstances.add(unlabeled);

double prediction = targetFunction.classifyInstance(unlabeledInstances.get(0));
Weka fornisce una classe Evaluationper testare un classificatore o modello addestrato. Nel codice seguente viene utilizzata una matrice selezionata di dati di convalida per evitare risultati falsi. I risultati della misurazione (costo dell'errore) verranno visualizzati sulla console. In genere, i risultati della valutazione vengono utilizzati per confrontare modelli addestrati utilizzando diversi algoritmi di machine learning o variazioni di questi:
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
L'esempio precedente utilizza la regressione lineare, che prevede valori numerici, come il prezzo di una casa, in base ai valori di input. La regressione lineare supporta la previsione di valori numerici continui. Per prevedere i valori binari (“Sì” e “No”), è necessario utilizzare altri algoritmi di apprendimento automatico. Ad esempio, albero decisionale, reti neurali o regressione logistica.
// использование логистической регрессии
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);
Puoi utilizzare uno di questi algoritmi, ad esempio, per prevedere se un messaggio di posta elettronica è spam, o prevedere il tempo, o prevedere se una casa venderà bene. Se vuoi insegnare al tuo algoritmo a prevedere il tempo o quanto velocemente verrà venduta una casa, hai bisogno di un set di dati diverso, ad es.topseller:
// использование атрибута маркера topseller instead of атрибута маркера цена
ArrayList<string> classVal = new ArrayList<>();
classVal.add("true");
classVal.add("false");

Attribute topsellerAttribute = new Attribute("topsellerLabel", classVal);
attributes.add(topsellerAttribute);
Questo set di dati verrà utilizzato per addestrare un nuovo classificatore topseller. Una volta addestrata, la chiamata di previsione dovrebbe restituire un indice della classe token che può essere utilizzato per ottenere il valore previsto.
int idx = (int) targetFunction.classifyInstance(unlabeledInstances.get(0));
String prediction = classVal.get(idx);

Conclusione

Sebbene l'apprendimento automatico sia strettamente correlato alla statistica e utilizzi molti concetti matematici, il toolkit di apprendimento automatico ti consente di iniziare a integrare l'apprendimento automatico nei tuoi programmi senza una conoscenza approfondita della matematica. Tuttavia, meglio comprendi gli algoritmi di machine learning sottostanti, come l'algoritmo di regressione lineare che abbiamo esplorato in questo articolo, più sarai in grado di scegliere l'algoritmo giusto e ottimizzarlo per prestazioni ottimali. Traduzione dall'inglese. Autore: Gregor Roth, architetto software, JavaWorld.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION