Large Scale Machine Learning (Big Data)/fr
Dans le domaine de l’apprentissage profond, la mise à l'échelle en termes de quantité de données est rendue possible par l'utilisation de stratégies de traitement en très petits lots et de solveurs itératifs du premier ordre. En apprentissage de réseaux de neurones profonds, le code s’exécute plus ou moins de la même manière, qu’il s’agisse de quelques milliers ou de centaines de millions d’exemples : quelques exemples sont chargés à partir d’une source (disque, mémoire, source à distance, etc.), les gradients sont calculés pendant les itérations, ce qui modifie les paramètres du modèle au fur et à mesure. Par contre, avec plusieurs trousses logicielles d'apprentissage machine traditionnel, notamment scikit-learn, écrire du code pour faire de l'entraînement à large échelle est souvent complexe. Pour plusieurs algorithmes qui conviennent à des modèles communs comme les modèles linéaires généralisés (GLM pour Generalized Linear Models) et les machines à vecteurs de support (SVM pour Support Vector Machines), leur implémentation par défaut exige que la totalité de l’ensemble d’apprentissage soit chargée en mémoire et n’offre aucune fonctionnalité de parallélisme par fils ou par processus. De plus, certaines de ces implémentations se basent sur des solveurs plutôt exigeants en mémoire qui, pour bien fonctionner, exigent une quantité de mémoire plusieurs fois supérieure à la taille de l’ensemble de données à entraîner.
Nous examinons ici des options pour adapter les méthodes d'apprentissage machine traditionnel à de très grands ensembles de données, particulièrement lorsque l'utilisation d'un nœud à grande mémoire s'avère insuffisante ou que le traitement séquentiel est excessivement long.
Scikit-learn¶
Scikit-learn est un module Python pour l’apprentissage machine basé sur SciPy et distribué sous la licence BSD-3-Clause. La trousse logicielle est dotée d’une interface de programmation (API) intuitive qui simplifie la construction de chaînes de traitement complexes pour l'apprentissage machine. Toutefois, plusieurs implémentations des méthodes GLM et SVM supposent que l’ensemble d’entraînement est complètement chargé en mémoire, ce qui n'est pas toujours souhaitable. De plus, certains de ces algorithmes utilisent par défaut des solveurs très exigeants en mémoire. Dans certains cas, les suggestions suivantes permettent de contourner ces limitations.
Solveurs du gradient stochastique¶
Si votre ensemble de données est assez petit pour être chargé au complet en mémoire, mais que pendant l’entraînement vous obtenez des erreurs de mémoire insuffisante (OOM pour Out-Of-Memory), le problème est probablement dû à un solveur trop exigeant en mémoire. Avec scikit-learn, plusieurs méthodes offrent en option des variations de l’algorithme du gradient stochastique et le remplacement du solveur par défaut par un solveur du gradient stochastique est souvent une solution facile.
Dans les exemples suivants, une régression de crête (ridge regression) utilise le solveur par défaut et un solveur du gradient stochastique. Pour observer l’utilisation de la mémoire, lancez la commande htop dans le terminal lorsque le programme Python s’exécute.
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
X,y = make_regression(n_samples=100000, n_features=10000, n_informative=50)
model = Ridge()
model.fit(X,y)
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
X,y = make_regression(n_samples=100000, n_features=10000, n_informative=50)
model = Ridge(solver='saga')
model.fit(X,y)
Une autre option qui réduit encore plus l'utilisation de la mémoire est de travailler avec SGDRegressor plutôt qu’avec Ridge. Cette classe implémente plusieurs types de modèles linéaires généralisés (GLM) pour les régressions avec comme solveur l’algorithme du gradient stochastique (SGD). Il est cependant important de noter que SGDRegressor fonctionne uniquement lorsque le résultat est unidimensionnel (scalaire).
from sklearn.datasets import make_regression
from sklearn.linear_model import SGDRegressor
X,y = make_regression(n_samples=100000, n_features=10000, n_informative=50)
model = SGDRegressor(penalty='l2') # set penalty='l2' to perform a ridge regression
model.fit(X,y)
Apprentissage en lots¶
Dans les cas où votre ensemble de données est trop grand pour la mémoire disponible, ou juste assez grand pour ne pas laisser assez de mémoire pour l'entraînement, il est possible de garder les données sur disque et de les charger par lots, comme c’est le cas avec les trousses logicielles d’apprentissage profond. Scikit-learn appelle cette technique l'out-of-core learning (apprentissage hors mémoire) et c’est une option viable quand l’estimateur offre la méthode partial_fit. Dans les exemples ci-dessous, l’apprentissage hors mémoire se fait par des itérations sur des ensembles de données enregistrés sur disque.
Dans le premier exemple, nous utilisons SGDClassifier pour ajuster un classifieur linéaire SVM avec des lots de données en provenance d’une paire de vecteurs numpy. Les vecteurs sont stockés sur disque dans des fichiers npy qui seront mappés en mémoire. Puisque SGDClassifier possède la méthode partial_fit, les itérations peuvent se faire dans les grands fichiers en mémoire en ne chargeant que des petits lots à la fois en provenance des vecteurs. Chaque appel à partial_fit exécutera alors une époque de l’algorithme du gradient stochastique sur un lot de données.
import numpy as np
from sklearn.linear_model import SGDClassifier
def batch_loader(X, y, batch_size):
return ((X[idx:idx + batch_size],y[idx:idx + batch_size]) for idx in range(0, len(X), batch_size)) # function returns a Generator
inputs = np.memmap('./x_array.npy',dtype='float64',shape=(100000,10000))
targets = np.memmap('./y_array.npy',dtype='int8',shape=(100000,))
model = SGDClassifier(loss='hinge') # Using loss='hinge' is equivalent to fitting a Linear SVM
for batch in batch_loader(inputs, targets, batch_size=512):
X,y = batch
model.partial_fit(X,y)
Une autre méthode de stockage des données est d’utiliser des fichiers CSV. Dans le prochain exemple, l'entraînement d’un modèle de régression lasso se fait par la lecture par lots de données à partir d’un fichier CSV avec la trousse logicielle pandas.
import pandas as pd
from sklearn.linear_model import SGDRegressor
model = SGDRegressor(penalty='l1')
for batch in pd.read_csv("./data.csv", chunksize=512, iterator=True):
X = batch.drop('target', axis=1)
y = batch['target']
model.partial_fit(X,y)
Snap ML¶
Snap ML est une bibliothèque d'apprentissage machine propriétaire développée par IBM, qui prend en charge plusieurs modèles classiques et s'adapte facilement à des ensembles de données comportant des milliards d'exemples et/ou de variables. Elle permet l'entraînement distribué, l'accélération par GPU et l'utilisation de structures creuses. Son interface de programmation (API) est très similaire à celle de scikit-learn et peut la remplacer pour la gestion d'ensembles de données massifs.
Installation¶
Wheels ajoutés récemment¶
Pour connaître la plus récente version de Snap ML que nous avons construite, exécutez :
Pour plus d'information, voir Wheels disponibles.Installer le wheel¶
L'option à privilégier est d'utiliser le wheel Python comme suit :
1. Chargez un module Python avec module load python.
2. Créez et activez un environnement virtuel Python.
3. Installez Snap ML dans l'environnement virtuel avec pip install.
Multifil¶
Tous les estimateurs de Snap ML prennent en charge le parallélisme par threads, contrôlé par le paramètre n_jobs. En définissant ce paramètre à la valeur correspondant au nombre de cœurs disponibles pour votre tâche, on peut généralement observer une accélération par rapport à l'implémentation du même estimateur avec scikit-learn. Voici comment se compare la performance de Ridge entre scikit-learn et Snap ML.
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
from snapml import LinearRegression
import time
X,y = make_regression(n_samples=100000, n_features=10000, n_informative=50)
model_sk = Ridge(solver='saga')
print("Running Ridge with sklearn...")
tik = time.perf_counter()
model_sk.fit(X,y)
tok = time.perf_counter()
print(f"sklearn took {tok-tik:0.2f} seconds to fit.")
model_snap = LinearRegression(penalty='l2',n_jobs=4)
print("Running Ridge with SnapML...")
tik = time.perf_counter()
model_snap.fit(X,y)
tok = time.perf_counter()
print(f"SnapML took {tok-tik:0.2f} seconds to fit.")
Entraînement sur GPU¶
Tous les estimateurs de Snap ML prennent en charge l'accélération d’un ou plusieurs GPU. Pour l’entraînement avec un GPU, le paramètre est use_gpu=True. Pour l’entraînement avec plusieurs GPU, le paramètre est aussi use_gpu, et la liste des ID des GPU disponibles est passée à device_ids. Par exemple, pour une tâche qui demande deux GPU, device_ids=[0,1] utilisera les deux GPU. Le prochain exemple fait la même comparaison que dans la section précédente, mais pour l'entraînement d'un classifieur SVM avec un noyau non linéaire.
from sklearn.datasets import make_classification
from sklearn.svm import SVC
from snapml import SupportVectorMachine
import time
X,y = make_classification(n_samples=100000, n_features=10000, n_classes=3, n_informative=50)
model_sk = SVC(kernel='rbf') #sklearn's SVM fit-time scales at least quadratically with the number of samples... this will take a loooong time.
print("Running SVM Classifier with sklearn...")
tik = time.perf_counter()
model_sk.fit(X,y)
tok = time.perf_counter()
print(f"sklearn took {tok-tik:0.2f} seconds to fit.")
model_snap = SupportVectorMachine(kernel='rbf',n_jobs=4)
print("Running SVM Classifier with SnapML without GPU...")
tik = time.perf_counter()
model_snap.fit(X,y)
tok = time.perf_counter()
print(f"SnapML took {tok-tik:0.2f} seconds to fit without GPU.")
model_snap_gpu = SupportVectorMachine(kernel='rbf',n_jobs=4, use_gpu=True)
print("Running SVM Classifier with SnapML with GPU...")
tik = time.perf_counter()
model_snap_gpu.fit(X,y)
tok = time.perf_counter()
print(f"SnapML took {tok-tik:0.2f} seconds to fit with GPU.")
Entraînement hors mémoire¶
Tous les estimateurs de Snap ML utilisent par défaut des solveurs itératifs du premier ordre, tels que le SGD. Il est donc possible de faire des entraînements par lots sans avoir à charger en mémoire les ensembles de données au complet. Par contre, Snap ML accepte les entrées de vecteurs numpy par mappage en mémoire, contrairement à scikit-learn.
import numpy as np
from snapml import LogisticRegression
X = np.memmap('./x_array.npy',dtype='float64',shape=(100000,10000))
y = np.memmap('./y_array.npy',dtype='int8',shape=(100000,))
model = LogisticRegression(n_jobs=4)
model.fit(X,y)
MPI¶
Snap ML offre des implémentations distribuées de plusieurs estimateurs. Pour utiliser le mode distribué, exécutez un script Python avec mpirun ou srun.
Spark ML¶
Spark ML est une bibliothèque basée sur Apache Spark qui permet la mise à l'échelle de plusieurs méthodes d'apprentissage machine pour d'énormes quantités de données et sur plusieurs nœuds, sans qu'il soit nécessaire de distribuer des ensembles de données ou de créer du code distribué ou parallèle. Elle inclut plusieurs outils utiles en algèbre linéaire et en statistique. Avant de reproduire les exemples de la documentation Spark ML, consultez notre tutoriel sur la soumission de tâches Spark.