JavaRush /Blog Java /Random-FR /Apprentissage automatique pour les développeurs Java, par...

Apprentissage automatique pour les développeurs Java, partie 2

Publié dans le groupe Random-FR
Apprentissage automatique pour les développeurs Java, partie 1
Apprentissage automatique pour les développeurs Java, partie 2 - 1

Estimation de la fonction objective

Rappelons que la fonction cible , également appelée fonction de prédiction, est le résultat du processus de préparation ou d'entraînement. Mathématiquement, le défi est de trouver une fonction qui prend une variable en entrée хet renvoie la valeur prédite у.
Apprentissage automatique pour les développeurs Java, parties 2 - 2
En apprentissage automatique, une fonction de coût (J(θ))est utilisée pour calculer la valeur d'erreur ou le « coût » d'une fonction objectif donnée.
Apprentissage automatique pour les développeurs Java, parties 2 à 3
La fonction de coût montre dans quelle mesure le modèle s'adapte aux données de formation. Pour déterminer le coût de la fonction objectif présentée ci-dessus, il est nécessaire de calculer l'erreur quadratique de chaque exemple de maison (i). L'erreur est la distance entre la valeur calculée уet la valeur réelle yde la maison de l'exemple i.
Apprentissage automatique pour les développeurs Java, parties 2 à 4
Par exemple, le prix réel d'une maison d'une superficie de 1330 = 6 500 000 € . Et la différence entre le prix de l'immobilier prévu par la fonction objectif entraînée est de 7 032 478 € : la différence (ou erreur) est de 532 478 € . Vous pouvez également voir cette différence dans le graphique ci-dessus. La différence (ou erreur) est affichée sous forme de lignes rouges pointillées verticales pour chaque paire de formation prix-zone. Après avoir calculé le coût de la fonction objectif entraînée, vous devez additionner l'erreur quadratique de chaque maison de l'exemple et calculer la valeur principale. Plus la valeur du prix est petite (J(θ)), plus les prédictions de notre fonction objectif seront précises. Le listing 3 montre une implémentation Java simple d'une fonction de coût qui prend en entrée une fonction objectif, une liste de données d'entraînement et les étiquettes qui leur sont associées. Les valeurs de prédiction seront calculées en boucle et l'erreur sera calculée en soustrayant la valeur du prix réel (extraite de l'étiquette). Plus tard, le carré des erreurs sera additionné et la valeur de l'erreur sera calculée. Le coût sera renvoyé sous forme de valeur de typedouble :

Liste-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;
}
Vous souhaitez en savoir plus sur Java ? Rejoignez le groupe Développeur Java !

Apprentissage de la fonction cible

Bien que la fonction de coût permette d'évaluer la qualité de la fonction objectif et des paramètres thêta, vous devez toujours trouver les paramètres thêta les plus adaptés. Vous pouvez utiliser l'algorithme de descente de gradient pour cela.

Descente graduelle

La descente de gradient minimise la fonction de coût. Cela signifie qu'il est utilisé pour trouver les paramètres thêta qui ont le coût minimum (J(θ))en fonction des données d'entraînement. Voici un algorithme simplifié pour calculer de nouvelles valeurs thêta plus appropriées :
Apprentissage automatique pour les développeurs Java, parties 2 à 5
Ainsi, les paramètres du vecteur thêta s'amélioreront à chaque itération de l'algorithme. Le coefficient d'apprentissage α précise le nombre de calculs à chaque itération. Ces calculs peuvent être effectués jusqu'à ce que de « bonnes » valeurs thêta soient trouvées. Par exemple, la fonction de régression linéaire ci-dessous a trois paramètres thêta :
Apprentissage automatique pour les développeurs Java, parties 2 à 6
A chaque itération, une nouvelle valeur sera calculée pour chacun des paramètres thêta : , , et . Après chaque itération, une nouvelle implémentation plus appropriée peut être créée en utilisant le nouveau vecteur thêta 0 , θ 1 , θ 2 } . Le listing -4 montre le code Java pour l'algorithme de décroissance du gradient. Theta pour la fonction de régression sera formé à l'aide de données d'entraînement, de données de marqueurs et de taux d'apprentissage . Le résultat sera une fonction objectif améliorée utilisant des paramètres thêta. La méthode sera appelée encore et encore, en transmettant la nouvelle fonction objectif et les nouveaux paramètres thêta des calculs précédents. Et ces appels seront répétés jusqu'à ce que la fonction objectif configurée atteigne un plateau minimum : θ0θ1θ2LinearRegressionFunction(α)train()

Liste-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);
}
Pour garantir que le coût diminue continuellement, vous pouvez exécuter la fonction de coût J(θ)après chaque étape de formation. Après chaque itération, le coût devrait diminuer. Si cela ne se produit pas, cela signifie que la valeur du coefficient d’apprentissage est trop grande et que l’algorithme n’a tout simplement pas atteint la valeur minimale. Dans un tel cas, l’algorithme de décroissance du gradient échoue. Les graphiques ci-dessous montrent la fonction objectif en utilisant les nouveaux paramètres thêta calculés, en commençant par le vecteur thêta de départ {1.0, 1.0}. La colonne de gauche montre le tracé de la fonction de prédiction après 50 itérations ; colonne du milieu après 200 répétitions ; et la colonne de droite après 1000 répétitions. À partir de ceux-ci, nous pouvons voir que le prix diminue après chaque itération et que la nouvelle fonction objectif s’adapte de mieux en mieux. Après 500 à 600 répétitions, les paramètres thêta ne changent plus de manière significative et le prix atteint un plateau stable. Après cela, la précision de la fonction cible ne peut plus être améliorée de cette manière.
Apprentissage automatique pour les développeurs Java, parties 2 à 7
Dans ce cas, même si le coût ne diminue plus significativement après 500-600 itérations, la fonction objectif n’est toujours pas optimale. Cela indique une divergence . En apprentissage automatique, le terme « incohérence » est utilisé pour indiquer que l'algorithme d'apprentissage ne trouve pas de tendances sous-jacentes dans les données. Sur la base de l'expérience réelle, on peut s'attendre à une réduction du prix au mètre carré pour les propriétés plus grandes. Nous pouvons en conclure que le modèle utilisé pour le processus d’apprentissage de la fonction cible ne s’adapte pas suffisamment aux données. Cet écart est souvent dû à une simplification excessive du modèle. Cela s'est produit dans notre cas, la fonction objectif est trop simple et pour l'analyse, elle utilise un seul paramètre - la superficie de la maison. Mais ces informations ne suffisent pas pour prédire avec précision le prix d’une maison.

Ajouter des fonctionnalités et les faire évoluer

Si vous constatez que votre fonction objectif ne correspond pas au problème que vous essayez de résoudre, il faut l’ajuster. Une manière courante de corriger les incohérences consiste à ajouter des fonctionnalités supplémentaires au vecteur de fonctionnalités. Dans l’exemple du prix d’une maison, vous pouvez ajouter des caractéristiques comme le nombre de pièces ou encore l’âge de la maison. Autrement dit, au lieu d'utiliser un vecteur avec une valeur de caractéristique {size}pour décrire une maison, vous pouvez utiliser un vecteur avec plusieurs valeurs, par exemple. {size, number-of-rooms, age}. Dans certains cas, le nombre de caractéristiques dans les données d'entraînement disponibles n'est pas suffisant. Ensuite, cela vaut la peine d’essayer d’utiliser des caractéristiques polynomiales calculées à partir de caractéristiques existantes. Par exemple, vous avez la possibilité d'étendre la fonction objectif de détermination du prix d'une maison afin qu'elle inclue une caractéristique calculée en mètres carrés (x2) :
Apprentissage automatique pour les développeurs Java, parties 2 à 8
L'utilisation de plusieurs fonctionnalités nécessite une mise à l'échelle des fonctionnalités , qui est utilisée pour standardiser la plage entre différentes fonctionnalités. Ainsi, la plage de valeurs pour l'attribut size 2 est nettement plus grande que la plage de valeurs pour l'attribut size. Sans mise à l'échelle des fonctionnalités, la taille 2 influencera indûment la fonction de coût. L'erreur introduite par l' attribut size 2 sera nettement plus grande que l'erreur introduite par l'attribut size. Un algorithme simple de mise à l'échelle des fonctionnalités est donné ci-dessous :
Apprentissage automatique pour les développeurs Java, parties 2 à 9
Cet algorithme est implémenté dans la classe FeaturesScalingdans l'exemple de code ci-dessous. Le cours FeaturesScalingprésente une méthode commerciale pour créer une fonction de mise à l'échelle adaptée aux données d'entraînement. En interne, les instances de données d'entraînement sont utilisées pour calculer les valeurs moyennes, minimales et maximales. La fonction résultante prend le vecteur de caractéristiques et en produit un nouveau avec les caractéristiques mises à l'échelle. La mise à l'échelle des fonctionnalités est nécessaire à la fois pour le processus d'apprentissage et pour le processus de prédiction, comme indiqué ci-dessous :
// создание массива данных
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);
À mesure que de plus en plus de fonctionnalités sont ajoutées, l'adéquation à la fonction objectif augmente, mais soyez prudent. Si vous allez trop loin et ajoutez trop de fonctionnalités, vous risquez de finir par apprendre une fonction objective surajustée.

Sur-appariement et validation croisée

Le surajustement se produit lorsque la fonction objectif ou le modèle s'adapte trop bien aux données d'entraînement, à tel point qu'il capture le bruit ou les variations aléatoires dans les données d'entraînement. Un exemple de surajustement est présenté dans le graphique le plus à droite ci-dessous :
Apprentissage automatique pour les développeurs Java, parties 2 à 10
Cependant, un modèle de surajustement fonctionne très bien sur les données d'entraînement, mais aura de mauvais résultats sur des données réelles inconnues. Il existe plusieurs façons d’éviter le surapprentissage.
  • Utilisez un ensemble de données plus important pour la formation.
  • Utilisez moins de fonctionnalités, comme le montrent les graphiques ci-dessus.
  • Utilisez un algorithme d'apprentissage automatique amélioré qui prend en compte la régularisation.
Si un algorithme de prédiction surajuste les données d'entraînement, il est nécessaire d'éliminer les caractéristiques qui ne profitent pas à sa précision. La difficulté est de trouver des caractéristiques qui ont un effet plus significatif que d’autres sur la précision de la prédiction. Comme le montrent les graphiques, le surajustement peut être déterminé visuellement à l'aide de graphiques. Cela fonctionne bien pour les graphiques avec 2 ou 3 coordonnées, il devient difficile de tracer et d'évaluer le graphique si vous utilisez plus de 2 fonctionnalités. Lors de la validation croisée, vous testez à nouveau les modèles après la formation en utilisant des données inconnues de l'algorithme une fois le processus de formation terminé. Les données étiquetées disponibles doivent être divisées en 3 ensembles :
  • données d'entraînement;
  • données de vérification ;
  • données de test.
Dans ce cas, 60 pour cent des enregistrements étiquetés caractérisant les maisons doivent être utilisés dans le processus de formation des variantes de l'algorithme cible. Après le processus de formation, la moitié des données restantes (non utilisées auparavant) doivent être utilisées pour vérifier que l'algorithme cible formé fonctionne bien sur les données inconnues. En règle générale, l'algorithme qui fonctionne mieux que les autres est sélectionné pour être utilisé. Les données restantes sont utilisées pour calculer la valeur d'erreur pour le modèle final sélectionné. Il existe d'autres techniques de validation croisée, telles que k-fold . Cependant, je ne les décrirai pas dans cet article.

Outils d'apprentissage automatique et framework Weka

La plupart des frameworks et bibliothèques fournissent une vaste collection d’algorithmes d’apprentissage automatique. De plus, ils fournissent une interface pratique de haut niveau pour la formation, les tests et le traitement des modèles de données. Weka est l'un des frameworks les plus populaires pour la JVM. Weka est une bibliothèque Java pratique qui contient des tests graphiques pour valider des modèles. L'exemple ci-dessous utilise la bibliothèque Weka pour créer un ensemble de données d'entraînement contenant des fonctionnalités et des étiquettes. Méthode setClassIndex()- pour le marquage. Dans Weka, une étiquette est définie comme une 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);
...
L'ensemble de données et l'exemple d'objet peuvent être enregistrés et chargés à partir d'un fichier. Weka utilise ARFF (Attribute Relation File Format) qui est pris en charge par les benchmarks graphiques de Weka. Cet ensemble de données est utilisé pour entraîner une fonction objective connue sous le nom de classificateur dans Weka. Tout d’abord, vous devez définir la fonction objectif. Le code ci-dessous LinearRegressioncréera une instance du classificateur. Ce classificateur sera formé à l'aide du buildClassifier(). La méthode buildClassifier()sélectionne les paramètres thêta en fonction des données d'entraînement à la recherche du meilleur modèle cible. Avec Weka, vous n'avez pas à vous soucier de définir le taux d'apprentissage ou le nombre d'itérations. Weka effectue également la mise à l'échelle des fonctionnalités de manière indépendante.
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
Une fois ces réglages effectués, la fonction objectif peut être utilisée pour prédire le prix de la maison, comme indiqué ci-dessous :
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 propose un cours Evaluationpour tester un classificateur ou un modèle formé. Dans le code ci-dessous, un tableau sélectionné de données de validation est utilisé pour éviter les faux résultats. Les résultats des mesures (coût de l'erreur) seront affichés sur la console. En règle générale, les résultats de l'évaluation sont utilisés pour comparer des modèles qui ont été formés à l'aide de différents algorithmes d'apprentissage automatique, ou de variantes de ceux-ci :
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
L'exemple ci-dessus utilise la régression linéaire, qui prédit des valeurs numériques, telles que le prix d'une maison, sur la base des valeurs d'entrée. La régression linéaire prend en charge la prédiction de valeurs numériques continues. Pour prédire les valeurs binaires (« Oui » et « Non »), vous devez utiliser d'autres algorithmes d'apprentissage automatique. Par exemple, arbre de décision, réseaux de neurones ou régression logistique.
// использование логистической регрессии
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);
Vous pouvez utiliser l'un de ces algorithmes, par exemple, pour prédire si un message électronique est du spam, prédire la météo ou prédire si une maison se vendra bien. Si vous souhaitez apprendre à votre algorithme à prédire la météo ou la rapidité avec laquelle une maison se vendra, vous avez besoin d'un ensemble de données différent, par ex.topseller:
// использование атрибута маркера topseller instead of атрибута маркера цена
ArrayList<string> classVal = new ArrayList<>();
classVal.add("true");
classVal.add("false");

Attribute topsellerAttribute = new Attribute("topsellerLabel", classVal);
attributes.add(topsellerAttribute);
Cet ensemble de données sera utilisé pour former un nouveau classificateur topseller. Une fois entraîné, l’appel de prédiction doit renvoyer un index de classe de jeton qui peut être utilisé pour obtenir la valeur prédite.
int idx = (int) targetFunction.classifyInstance(unlabeledInstances.get(0));
String prediction = classVal.get(idx);

Conclusion

Bien que l'apprentissage automatique soit étroitement lié aux statistiques et utilise de nombreux concepts mathématiques, la boîte à outils d'apprentissage automatique vous permet de commencer à intégrer l'apprentissage automatique dans vos programmes sans connaissance approfondie des mathématiques. Cependant, mieux vous comprendrez les algorithmes d'apprentissage automatique sous-jacents, tels que l'algorithme de régression linéaire que nous avons exploré dans cet article, plus vous serez en mesure de choisir le bon algorithme et de l'ajuster pour des performances optimales. Traduction de l’anglais. Auteur : Gregor Roth, architecte logiciel, JavaWorld.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION