Tutoriel ([wiki: FMin rev: a663e] dans la bibliothèque Python hyperopt pour optimiser les espaces de recherche gênants dans les dimensions réelles, discrètes et conditionnelles (https://github.com/hyperopt/hyperopt) (https://github.com/hyperopt/hyperopt/wiki/FMin/a663e64546eb5cd3ed462618dcc1e41863ad8688)) a été traduit par google. Licence
Cette page est un tutoriel sur l'utilisation basique de hyperopt.fmin ()
.
Décrit comment écrire une fonction objectif que fmin peut optimiser et comment écrire un espace de recherche que fmin peut rechercher.
Le travail d'Hyperopt est de trouver la meilleure valeur de fonction stochastique possible pour une valeur scalaire plutôt que l'ensemble des arguments possibles pour cette fonction. Alors que de nombreux packages d'optimisation s'attendent à ce que ces entrées soient dérivées de l'espace vectoriel, Hyperopt vous encourage à décrire votre espace de recherche plus en détail. En fournissant plus d'informations sur l'endroit où votre fonction est définie et où se trouve la valeur optimale, les algorithmes d'hyperopt peuvent être recherchés plus efficacement.
La façon d'utiliser hyperopt est d'écrire:
Ce didacticiel (le plus basique) vous montrera comment créer des fonctions et des espaces de recherche à l'aide de la base de données d'essai par défaut et de l'algorithme de recherche aléatoire factice. La section (1) traite de différentes conventions d'appel pour la communication entre la fonction objectif et hyperopt. La section (2) concerne la description de l'espace de recherche.
Vous pouvez faire des recherches parallèles en remplaçant la base de données «Trials» par la base de données «MongoTrials». Il existe une autre page wiki sur l'utilisation de mongodb pour la recherche parallèle.
Choisir un algorithme de recherche est aussi simple que de passer ʻalgo = hyperopt.tpe.suggest au lieu de ʻalgo = hyperopt.random.suggest
. L'algorithme de recherche est en fait un objet appelable et son constructeur accepte les arguments de configuration, ce qui concerne la manière dont l'algorithme de recherche est sélectionné.
Hyperopt offre plusieurs niveaux de flexibilité et de complexité accrues lors de la spécification pour minimiser la fonction objectif. Questions à réfléchir en tant que designer
Dans les prochaines sections, nous examinerons différentes manières de mettre en œuvre des objectifs qui minimisent l'objectif quadratique pour une seule variable. Dans chaque section, recherchez dans la plage -10 à +10. Ceci peut être décrit dans * espace de recherche *.
space = hp.uniform('x', -10, 10)
Below, Section 2, covers how to specify search spaces that are more complicated.
Le protocole le plus simple pour la communication entre l'algorithme d'optimisation d'hyperopt et la fonction objectif est que la fonction objectif reçoit un point valide de l'espace de recherche et a une virgule flottante * perte * (également appelée utilité négative) associée à ce point. revenir.
from hyperopt import fmin, tpe, hp
best = fmin(fn=lambda x: x ** 2,
space=hp.uniform('x', -10, 10),
algo=tpe.suggest,
max_evals=100)
print best
Ce protocole a l'avantage d'être très lisible et facile à taper. Comme vous pouvez le voir, c'est presque une doublure. L'inconvénient de ce protocole est (1) Ce type de fonction ne permet pas de renvoyer des informations supplémentaires sur chaque évaluation dans la base de données de test. Et (2) Ce type de fonction ne peut pas interagir avec des algorithmes de recherche ou d'autres évaluations de fonctions parallèles. L'exemple suivant montre pourquoi vous souhaitez effectuer ces opérations.
Si la fonction objectif est complexe et prend du temps à s'exécuter, vous souhaiterez peut-être enregistrer davantage d'informations statistiques et de diagnostic, ainsi que la dernière perte en virgule flottante. Dans de tels cas, la fonction fmin peut gérer le dictionnaire comme valeur de retour. Cela signifie que votre fonction de perte peut renvoyer un dictionnaire qui imbrique toutes les statistiques et diagnostics souhaités. La réalité est un peu moins flexible que cela. Par exemple, lors de l'utilisation de mongodb, le dictionnaire doit être un document JSON valide. Néanmoins, il existe une grande flexibilité pour stocker les résultats auxiliaires spécifiques au domaine.
Lorsque la fonction objectif renvoie un dictionnaire, la fonction fmin recherche des paires clé / valeur spéciales dans la valeur de retour et les transmet à l'algorithme d'optimisation. Il existe deux paires clé-valeur requises.
status
--Une des clés pour hyperopt.STATUS_STRINGS
. Par exemple, «ok» pour une terminaison réussie ou «échec» si aucune fonction n'est définie.loss
- La valeur de la fonction à virgule flottante que vous essayez de minimiser. Si le statut est «ok», il doit être présent.La fonction fmin répond également à certaines touches d'options:
loss_variance
--float --Incertitude de la fonction objectif probabilistetrue_loss
--float --Lors de l'optimisation des hyperparamètres, enregistrer l'erreur de généralisation du modèle avec ce nom vous donnera une sortie plus claire de la routine de traçage intégrée.true_loss_variance
--float - Incertitude d'erreur de généralisationLes dictionnaires utilisent une variété de mécanismes de stockage back-end, vous devez donc vous assurer qu'ils sont compatibles avec JSON. S'il s'agit d'un graphe avec une arborescence de dictionnaire, liste, tapple, nombre, chaîne de caractères, date et heure, il n'y a pas de problème.
** Astuce: ** Pour stocker des tableaux numpy, envisagez de les sérialiser en chaînes et de les enregistrer en tant que pièces jointes.
L'écriture de la fonction ci-dessus dans un style qui renvoie un dictionnaire ressemble à ceci:
import pickle
import time
from hyperopt import fmin, tpe, hp, STATUS_OK
def objective(x):
return {'loss': x ** 2, 'status': STATUS_OK }
best = fmin(objective,
space=hp.uniform('x', -10, 10),
algo=tpe.suggest,
max_evals=100)
print best
Pour voir réellement le but de renvoyer un dictionnaire, modifiez la fonction objective pour en retourner et passez un argument explicite trial
à fmin
.
import pickle
import time
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
def objective(x):
return {
'loss': x ** 2,
'status': STATUS_OK,
# -- store other results like this
'eval_time': time.time(),
'other_stuff': {'type': None, 'value': [0, 1, 2]},
# -- attachments are handled differently
'attachments':
{'time_module': pickle.dumps(time.time)}
}
trials = Trials()
best = fmin(objective,
space=hp.uniform('x', -10, 10),
algo=tpe.suggest,
max_evals=100,
trials=trials)
print best
Dans ce cas, l'appel à fmin est le même que précédemment, mais vous pouvez passer l'objet d'essai directement pour inspecter toutes les valeurs de retour calculées pendant l'expérience.
Ainsi, par exemple:
trial.trials
--Liste de dictionnaires représentant toutes les recherchestrial.results
--Liste des dictionnaires renvoyés par'objectif 'lors de la recherchetrial.losses ()
- Liste flottante des pertes (pour chaque essai "ok")trial.statuses ()
--Liste des chaînes d'étatVous pouvez enregistrer cet objet d'essai, le transmettre à une routine de traçage intégrée ou l'analyser avec votre propre code personnalisé.
«Les pièces jointes» sont gérées par un mécanisme spécial qui vous permet d'utiliser le même code pour «Trials» et «MongoTrials».
Vous pouvez obtenir une pièce jointe d'essai comme celle-ci. Cela obtiendra l'attachement 'time_module' pour le 5e essai
msg = trials.trial_attachments(trials.trials[5])['time_module']
time_module = pickle.loads(msg)
les pièces jointes sont de grandes chaînes, donc si vous utilisez MongoTrials, vous n'avez pas besoin de télécharger plus que ce dont vous avez besoin. Les chaînes peuvent également être attachées globalement à l'ensemble de l'objet d'essai via des essais. les pièces jointes se comportent comme un dictionnaire chaîne-chaîne.
** N.B. ** Actuellement, les pièces jointes spécifiques à la version d'évaluation des objets Trials sont placées dans le dictionnaire des pièces jointes pour la même version d'évaluation globale, mais sont sujettes à modification à l'avenir et ne s'appliquent pas aux MongoTrials.
Il est possible que fmin ()
donne à votre fonction objectif le handle de mongodb utilisé dans des expériences parallèles. Ce mécanisme vous permet de mettre à jour la base de données avec des résultats partiels et de communiquer avec d'autres processus parallèles qui évaluent différents points. La fonction objectif peut même ajouter de nouveaux points de recherche, tels que random.suggest
.
Les techniques de base sont:
fmin_pass_expr_memo_ctrl
Utilisation des décorateurspyll.rec_eval
dans votre propre fonction pour créer un point d'espace de recherche à partir de ʻexpr et
memo`.ctrl
, une instance de hyperopt.Ctrl
, pour communiquer avec d'autres objets d'essai.Je ne le couvrirai pas dans ce court didacticiel, mais je voudrais mentionner ce qui est possible avec la base de code actuelle. Il comprend également des sources hyperopt, des tests unitaires et des exemples de projets tels que hyperopt-convnet. Veuillez m'envoyer un e-mail ou soumettre un problème github pour accélérer cette partie du code.
L'espace de recherche se compose d'expressions fonctionnelles imbriquées qui contiennent des expressions probabilistes. La représentation probabiliste est un hyperparamètre. L'échantillonnage à partir de ce programme stochastique imbriqué définit un algorithme de recherche aléatoire. L'algorithme d'optimisation des hyperparamètres fonctionne en remplaçant la logique "d'échantillonnage" habituelle par une stratégie de recherche adaptative et ne tente pas réellement d'échantillonner à partir de la distribution spécifiée dans l'espace de recherche.
Il est préférable de considérer l'espace de recherche comme un programme d'échantillonnage d'arguments probabiliste. Par exemple
from hyperopt import hp
space = hp.choice('a',
[
('case 1', 1 + hp.lognormal('c1', 0, 1)),
('case 2', hp.uniform('c2', -10, 10))
])
Le résultat de l'exécution de ce code est la variable «espace» qui référence le graphique de l'identificateur d'expression et ses arguments. Rien n'a été réellement échantillonné. C'est juste un graphique qui décrit comment échantillonner des points. Le code pour travailler avec ce type de graphe de représentation est dans hyperopt.pyll
, et nous appelons ces graphes graphes
pyll` ou programmes * pyll *.
Si vous le souhaitez, l'espace échantillon peut être échantillonné et évalué.
import hyperopt.pyll.stochastic
print hyperopt.pyll.stochastic.sample(space)
Cet espace de recherche, décrit par «espace», a trois paramètres:
Une chose à noter ici est que toutes les expressions probabilistes optimisables ont un * label * comme premier argument. Ces étiquettes sont utilisées pour renvoyer les choix de paramètres à l'appelant et sont utilisées en interne de différentes manières.
Une autre chose à noter est l'utilisation de taples au centre du graphe (autour de chaque «cas 1» et «cas 2»). Les listes, dictionnaires et tapples sont tous mis à niveau vers des "expressions fonctionnelles déterministes" et font partie du programme stochastique de l'espace de recherche.
Le troisième notable est l'expression numérique 1 + hp.lognormal ('c1', 0, 1)
intégrée dans la description de l'espace de recherche. En ce qui concerne l'algorithme d'optimisation, il n'y a aucune différence en ajoutant 1 directement à l'espace de recherche et 1 dans la logique de la fonction objectif elle-même. Les concepteurs peuvent choisir où placer ce traitement pour obtenir le type de modularité dont ils ont besoin. Le résultat d'une expression intermédiaire dans l'espace de recherche peut être n'importe quel objet Python, même lorsqu'il est optimisé en parallèle à l'aide de mongodb. Il est facile d'ajouter un nouveau type de représentation non probabiliste à la description de l'espace de recherche (voir la section 2.3 ci-dessous).
Quatrièmement, «c1» et «c2» sont des exemples appelés paramètres conditionnels. Chacun de «c1» et «c2» affiche uniquement les nombres de l'échantillon renvoyés pour une valeur particulière de «a». Si "a" vaut 0, "c1" est utilisé mais "c2" n'est pas utilisé. Si "a" vaut 1, "c2" est utilisé mais "c1" n'est pas utilisé. Lorsque cela a du sens, vous devez encoder les paramètres comme conditionnels de cette manière, plutôt que d'ignorer simplement les paramètres de la fonction objectif. Vous pouvez rechercher plus efficacement si vous trouvez que «c1» peut ne pas affecter la fonction objectif (car cela n'affecte pas les arguments de la fonction objectif).
Les formules probabilistes actuellement reconnues par l'algorithme d'optimisation d'hyperopt sont:
hp.choice(label, options)
Renvoie l'une des options. Doit être une liste ou un tapple. Les éléments des «options» peuvent être imbriqués dans leurs propres expressions probabilistes. Dans ce cas, la sélection probabiliste qui n'apparaît que dans quelques options est le paramètre * condition *.
hp.randint(label, upper)
Renvoie un nombre aléatoire dans la plage [0, supérieur). La sémantique de cette distribution est qu'il n'y a pas de corrélation dans la fonction de perte entre les valeurs entières proches par rapport aux valeurs entières plus éloignées. C'est une bonne distribution, par exemple, pour décrire une graine aléatoire. Si la fonction de perte est probablement en corrélation avec des valeurs entières proches, vous devez utiliser l'une des distributions continues «quantifiées», telles que «quniform», «qloguniform», «qnormal» ou «qlognormal».
hp.uniform(label, low, high)
Renvoie une valeur uniformément entre «faible» et «élevé».
Lors de l'optimisation, cette variable est contrainte par l'espacement des deux côtés.
hp.quniform(label, low, high, q)
Renvoie une valeur telle que round (uniform (low, high) / q) * q
.
Un peu «lisse» dans l'objectif, mais convient aux valeurs discrètes qui doivent être contraintes à la fois au-dessus et en dessous.
hp.loguniform(label, low, high)
Renvoie une distribution uniforme logarithmique telle que ʻexp (uniform (low, high)) `.
Lorsqu'elle est optimisée, cette variable est contrainte à l'intervalle «[exp (faible), exp (élevé)]».
hp.qloguniform(label, low, high, q)
round (exp (uniform (low, high)) / q) * Renvoie une valeur comme q
.
Convient aux variables discrètes dont le but est «lisse» et dont la taille de valeur est plus douce, mais limitée à la fois en haut et en bas.
hp.normal(label, mu, sigma)
Renvoie la valeur réelle qui est normalement distribuée avec la moyenne mu et l'écart type sigma. Lors de l'optimisation, il s'agit d'une variable sans contrainte.
hp.qnormal(label, mu, sigma, q)
round (normal (mu, sigma) / q) * Renvoie une valeur comme q
.
Prend probablement une valeur proche de mu, mais convient fondamentalement à des variables discrètes illimitées.
hp.lognormal(label, mu, sigma)
Renvoie la valeur dessinée selon ʻexp (normal (mu, sigma)) `de sorte que la valeur logarithmique de la valeur de retour soit normalement distribuée. Lorsqu'elle est optimisée, cette variable est limitée aux valeurs positives.
hp.qlognormal(label, mu, sigma, q)
round (exp (normal (mu, sigma))) / q) * Renvoie une valeur comme q
.
Convient aux variables discrètes dont le but est lissé et lissé par la taille de la variable délimitée d'un côté.
2.2 A Search Space Example: scikit-learn
Pour voir toutes ces possibilités en action, voyons comment scikit-learn décrit les espaces d'hyperparamètres de l'algorithme de classification. (Cette idée a été développée à hyperopt-sklearn.)
from hyperopt import hp
space = hp.choice('classifier_type', [
{
'type': 'naive_bayes',
},
{
'type': 'svm',
'C': hp.lognormal('svm_C', 0, 1),
'kernel': hp.choice('svm_kernel', [
{'ktype': 'linear'},
{'ktype': 'RBF', 'width': hp.lognormal('svm_rbf_width', 0, 1)},
]),
},
{
'type': 'dtree',
'criterion': hp.choice('dtree_criterion', ['gini', 'entropy']),
'max_depth': hp.choice('dtree_max_depth',
[None, hp.qlognormal('dtree_max_depth_int', 3, 1, 1)]),
'min_samples_split': hp.qlognormal('dtree_min_samples_split', 2, 1, 1),
},
])
Vous pouvez utiliser des nœuds comme des arguments pour la fonction pyll (voir pyll). Si vous souhaitez en savoir plus à ce sujet, veuillez soumettre un problème github.
En termes simples, vous décorez simplement les fonctions de niveau supérieur (c'est-à-dire adaptées aux pickles) à utiliser via l'objet scope
.
import hyperopt.pyll
from hyperopt.pyll import scope
@scope.define
def foo(a, b=0):
print 'runing foo', a, b
return a + b / 2
# -- this will print 0, foo is called as usual.
print foo(0)
#Dans la description de l'espace de recherche, comme Python normal`foo`Peut être utilisé.
#Ces deux appels n'appellent pas foo,
#Enregistrez uniquement que vous devez appeler foo pour évaluer le graphique.
space1 = scope.foo(hp.uniform('a', 0, 10))
space2 = scope.foo(hp.uniform('a', 0, 10), hp.normal('b', 0, 1))
# -- this will print an pyll.Apply node
print space1
# -- this will draw a sample by running foo()
print hyperopt.pyll.stochastic.sample(space1)
Si possible, il faut éviter d'ajouter de nouveaux types de représentations probabilistes pour décrire l'espace de recherche de paramètres. Pour que tous les algorithmes de recherche fonctionnent dans tous les espaces, les algorithmes de recherche doivent correspondre au type d'hyperparamètre qui décrit l'espace. En tant que responsable de la bibliothèque, j'ouvre la possibilité qu'une sorte d'expression soit ajoutée de temps en temps, mais comme je l'ai dit, je veux éviter autant que possible. L'ajout d'un nouveau type de représentation probabiliste n'est pas l'une des façons dont l'hyperopt est extensible.
Copyright (c) 2013, James Bergstra All rights reserved.
Recommended Posts