3.2. Hyperparameter-Tuning eines Schätzers#

Hyperparameter sind Parameter, die nicht direkt innerhalb von Schätzern gelernt werden. In scikit-learn werden sie als Argumente an den Konstruktor der Schätzerklassen übergeben. Typische Beispiele sind C, kernel und gamma für den Support Vector Classifier, alpha für Lasso usw.

Es ist möglich und empfohlen, den Hyperparameterraum nach der besten Kreuzvalidierungsbewertung zu durchsuchen.

Jeder Parameter, der bei der Konstruktion eines Schätzers übergeben wird, kann auf diese Weise optimiert werden. Um die Namen und aktuellen Werte aller Parameter für einen gegebenen Schätzer zu finden, verwenden Sie

estimator.get_params()

Eine Suche besteht aus

  • einem Schätzer (Regressor oder Klassifikator wie sklearn.svm.SVC());

  • einem Parameterraum;

  • einer Methode zum Suchen oder Abtasten von Kandidaten;

  • einem Kreuzvalidierungsschema; und

  • einer Bewertungsfunktion.

In scikit-learn werden zwei allgemeine Ansätze zur Parametersuche bereitgestellt: für gegebene Werte durchsucht GridSearchCV erschöpfend alle Parameterkombinationen, während RandomizedSearchCV eine gegebene Anzahl von Kandidaten aus einem Parameterraum mit einer spezifizierten Verteilung abtasten kann. Beide Werkzeuge haben Gegenstücke mit sukzessiver Halbierung, HalvingGridSearchCV und HalvingRandomSearchCV, die bei der Suche nach einer guten Parameterkombination deutlich schneller sein können.

Nachdem wir diese Werkzeuge beschrieben haben, detaillieren wir bewährte Vorgehensweisen, die für diese Ansätze gelten. Einige Modelle erlauben spezialisierte, effiziente Strategien zur Parametersuche, die in Alternativen zur Brute-Force-Parametersuche beschrieben werden.

Beachten Sie, dass es üblich ist, dass eine kleine Teilmenge dieser Parameter einen großen Einfluss auf die prädiktive oder rechnerische Leistung des Modells haben kann, während andere auf ihren Standardwerten belassen werden können. Es wird empfohlen, die Docstring der Schätzerklasse zu lesen, um ein besseres Verständnis ihres erwarteten Verhaltens zu erhalten, möglicherweise durch Lesen des enthaltenen Literaturverweises.

3.2.2. Zufällige Hyperparameter-Optimierung#

Während die Verwendung eines Gitters von Parametereinstellungen derzeit die am weitesten verbreitete Methode zur Parameteroptimierung ist, haben andere Suchmethoden günstigere Eigenschaften. RandomizedSearchCV implementiert eine zufällige Suche über Parameter, wobei jede Einstellung aus einer Verteilung über mögliche Parameterwerte abgetastet wird. Dies hat zwei Hauptvorteile gegenüber einer erschöpfenden Suche:

  • Ein Budget kann unabhängig von der Anzahl der Parameter und möglichen Werte gewählt werden.

  • Das Hinzufügen von Parametern, die die Leistung nicht beeinflussen, verringert nicht die Effizienz.

Die Angabe, wie Parameter abgetastet werden sollen, erfolgt mit einem Wörterbuch, sehr ähnlich der Angabe von Parametern für GridSearchCV. Zusätzlich wird ein Rechenbudget, d. h. die Anzahl der abgetasteten Kandidaten oder Abtastiterationen, mit dem Parameter n_iter spezifiziert. Für jeden Parameter kann entweder eine Verteilung über mögliche Werte oder eine Liste diskreter Auswahlmöglichkeiten (die gleichmäßig abgetastet werden) angegeben werden.

{'C': scipy.stats.expon(scale=100), 'gamma': scipy.stats.expon(scale=.1),
  'kernel': ['rbf'], 'class_weight':['balanced', None]}

Dieses Beispiel verwendet das Modul scipy.stats, das viele nützliche Verteilungen zur Abtastung von Parametern enthält, wie z. B. expon, gamma, uniform, loguniform oder randint.

Prinzipiell kann jede Funktion übergeben werden, die eine rvs (random variate sample) Methode zur Abtastung eines Wertes bereitstellt. Ein Aufruf der rvs Funktion sollte bei aufeinanderfolgenden Aufrufen unabhängige Zufallsstichproben aus möglichen Parameterwerten liefern.

Warnung

Die Verteilungen in scipy.stats vor Version scipy 0.16 erlauben keine Angabe eines Zufallszustands. Stattdessen verwenden sie den globalen Numpy-Zufallszustand, der über np.random.seed gesetzt oder über np.random.set_state konfiguriert werden kann. Ab scikit-learn 0.18 setzt das Modul sklearn.model_selection jedoch den vom Benutzer bereitgestellten Zufallszustand, wenn scipy >= 0.16 ebenfalls verfügbar ist.

Für kontinuierliche Parameter wie C oben ist es wichtig, eine kontinuierliche Verteilung anzugeben, um die Zufälligkeit voll auszunutzen. Auf diese Weise führt eine Erhöhung von n_iter immer zu einer feineren Suche.

Eine kontinuierliche log-uniforme Zufallsvariable ist die kontinuierliche Version eines log-skalierten Parameters. Zum Beispiel kann zur Angabe des Äquivalents von C von oben loguniform(1, 100) anstelle von [1, 10, 100] verwendet werden.

Spiegelbildlich zum obigen Beispiel in der Gitter-Suche können wir eine kontinuierliche Zufallsvariable spezifizieren, die log-gleichmäßig zwischen 1e0 und 1e3 verteilt ist.

from sklearn.utils.fixes import loguniform
{'C': loguniform(1e0, 1e3),
 'gamma': loguniform(1e-4, 1e-3),
 'kernel': ['rbf'],
 'class_weight':['balanced', None]}

Beispiele

Referenzen

  • Bergstra, J. und Bengio, Y., Random search for hyper-parameter optimization, The Journal of Machine Learning Research (2012)

3.2.3. Suche nach optimalen Parametern mit sukzessiver Halbierung#

Scikit-learn bietet auch die Schätzer HalvingGridSearchCV und HalvingRandomSearchCV, die verwendet werden können, um einen Parameterraum mittels sukzessiver Halbierung [1] [2] zu durchsuchen. Sukzessive Halbierung (SH) ist wie ein Turnier unter Kandidaten-Parameterkombinationen. SH ist ein iterativer Auswahlprozess, bei dem alle Kandidaten (die Parameterkombinationen) in der ersten Iteration mit geringem Ressourcenaufwand bewertet werden. Nur einige dieser Kandidaten werden für die nächste Iteration ausgewählt, der mehr Ressourcen zugewiesen wird. Für das Parameter-Tuning sind die Ressourcen typischerweise die Anzahl der Trainingsstichproben, es kann aber auch ein beliebiger numerischer Parameter wie n_estimators in einem Random Forest sein.

Hinweis

Die gewählte Ressourcensteigerung sollte groß genug sein, damit eine deutliche Verbesserung der Bewertungen unter Berücksichtigung der statistischen Signifikanz erzielt wird.

Wie in der nachstehenden Abbildung gezeigt, überleben nur einige Kandidaten bis zur letzten Iteration. Dies sind die Kandidaten, die sich in allen Iterationen konstant unter den am besten bewerteten Kandidaten befanden. Jeder Iteration wird eine steigende Menge an Ressourcen pro Kandidat zugewiesen, hier die Anzahl der Stichproben.

../_images/sphx_glr_plot_successive_halving_iterations_001.png

Wir beschreiben hier kurz die wichtigsten Parameter, aber jeder Parameter und seine Wechselwirkungen werden im folgenden Dropdown-Bereich detaillierter beschrieben. Der Parameter factor (> 1) steuert die Rate, mit der die Ressourcen wachsen, und die Rate, mit der die Anzahl der Kandidaten sinkt. In jeder Iteration wird die Anzahl der Ressourcen pro Kandidat mit factor multipliziert und die Anzahl der Kandidaten durch denselben Faktor geteilt. Zusammen mit resource und min_resources ist factor der wichtigste Parameter zur Steuerung der Suche in unserer Implementierung, obwohl ein Wert von 3 im Allgemeinen gut funktioniert. factor steuert effektiv die Anzahl der Iterationen in HalvingGridSearchCV und die Anzahl der Kandidaten (standardmäßig) sowie die Iterationen in HalvingRandomSearchCV. aggressive_elimination=True kann auch verwendet werden, wenn die Anzahl der verfügbaren Ressourcen gering ist. Mehr Kontrolle ist durch die Abstimmung des Parameters min_resources möglich.

Diese Schätzer sind noch experimentell: ihre Vorhersagen und ihre API können sich ohne einen Deprecationszyklus ändern. Um sie zu verwenden, müssen Sie explizit enable_halving_search_cv importieren.

>>> from sklearn.experimental import enable_halving_search_cv  # noqa
>>> from sklearn.model_selection import HalvingGridSearchCV
>>> from sklearn.model_selection import HalvingRandomSearchCV

Beispiele

Die folgenden Abschnitte befassen sich mit technischen Aspekten der sukzessiven Halbierung.

Auswahl von min_resources und der Anzahl der Kandidaten#

Neben factor sind die beiden wichtigsten Parameter, die das Verhalten einer sukzessiven Halbierungs-Suche beeinflussen, der Parameter min_resources und die Anzahl der Kandidaten (oder Parameterkombinationen), die ausgewertet werden. min_resources ist die Menge an Ressourcen, die in der ersten Iteration für jeden Kandidaten zugewiesen wird. Die Anzahl der Kandidaten wird direkt in HalvingRandomSearchCV festgelegt und aus dem Parameter param_grid von HalvingGridSearchCV ermittelt.

Betrachten wir einen Fall, in dem die Ressource die Anzahl der Stichproben ist und wir 1000 Stichproben haben. Theoretisch können wir mit min_resources=10 und factor=2 maximal 7 Iterationen mit den folgenden Stichprobenmengen durchführen: [10, 20, 40, 80, 160, 320, 640].

Abhängig von der Anzahl der Kandidaten können wir jedoch weniger als 7 Iterationen durchführen: Wenn wir mit einer kleinen Anzahl von Kandidaten beginnen, kann die letzte Iteration weniger als 640 Stichproben verwenden, was bedeutet, dass nicht alle verfügbaren Ressourcen (Stichproben) genutzt werden. Wenn wir beispielsweise mit 5 Kandidaten beginnen, benötigen wir nur 2 Iterationen: 5 Kandidaten für die erste Iteration, dann 5 // 2 = 2 Kandidaten in der zweiten Iteration, danach wissen wir, welcher Kandidat am besten abschneidet (wir brauchen also keinen dritten). Wir würden höchstens 20 Stichproben verwenden, was eine Verschwendung ist, da wir 1000 Stichproben zur Verfügung haben. Wenn wir hingegen mit einer hohen Anzahl von Kandidaten beginnen, könnten wir am Ende viele Kandidaten in der letzten Iteration haben, was nicht immer ideal ist: es bedeutet, dass viele Kandidaten mit den vollen Ressourcen laufen, was die Prozedur im Grunde auf eine Standard-Suche reduziert.

Im Fall von HalvingRandomSearchCV wird die Anzahl der Kandidaten standardmäßig so eingestellt, dass die letzte Iteration so viele der verfügbaren Ressourcen wie möglich nutzt. Für HalvingGridSearchCV wird die Anzahl der Kandidaten durch den Parameter param_grid bestimmt. Eine Änderung des Wertes von min_resources beeinflusst die Anzahl der möglichen Iterationen und hat somit auch Auswirkungen auf die ideale Anzahl der Kandidaten.

Eine weitere Überlegung bei der Wahl von min_resources ist, ob es einfach ist, gute von schlechten Kandidaten mit geringem Ressourcenaufwand zu unterscheiden. Wenn Sie beispielsweise viele Stichproben benötigen, um zwischen guten und schlechten Parametern zu unterscheiden, wird eine hohe min_resources empfohlen. Wenn die Unterscheidung hingegen auch mit geringem Stichprobenaufwand klar ist, dann ist eine geringe min_resources möglicherweise vorzuziehen, da sie die Berechnung beschleunigen würde.

Beachten Sie im obigen Beispiel, dass die letzte Iteration nicht die maximalen verfügbaren Ressourcen nutzt: Es sind 1000 Stichproben verfügbar, aber höchstens 640 werden verwendet. Standardmäßig versuchen sowohl HalvingRandomSearchCV als auch HalvingGridSearchCV, in der letzten Iteration so viele Ressourcen wie möglich zu nutzen, wobei die Einschränkung besteht, dass diese Ressourcenmenge ein Vielfaches von sowohl min_resources als auch factor sein muss (diese Einschränkung wird im nächsten Abschnitt klar). HalvingRandomSearchCV erreicht dies durch Abtasten der richtigen Anzahl von Kandidaten, während HalvingGridSearchCV dies durch korrektes Setzen von min_resources erreicht.

Menge der Ressourcen und Anzahl der Kandidaten in jeder Iteration#

In jeder Iteration i wird jedem Kandidaten eine bestimmte Menge an Ressourcen zugewiesen, die wir als n_resources_i bezeichnen. Diese Menge wird durch die Parameter factor und min_resources wie folgt gesteuert (wobei factor größer als 1 ist)

n_resources_i = factor**i * min_resources,

oder äquivalent

n_resources_{i+1} = n_resources_i * factor

wobei min_resources == n_resources_0 die in der ersten Iteration verwendete Ressourcenmenge ist. factor definiert auch die Proportionen der Kandidaten, die für die nächste Iteration ausgewählt werden.

n_candidates_i = n_candidates // (factor ** i)

oder äquivalent

n_candidates_0 = n_candidates
n_candidates_{i+1} = n_candidates_i // factor

In der ersten Iteration verwenden wir also min_resources Ressourcen n_candidates Mal. In der zweiten Iteration verwenden wir min_resources * factor Ressourcen n_candidates // factor Mal. Die dritte Iteration multipliziert wieder die Ressourcen pro Kandidat und dividiert die Anzahl der Kandidaten. Dieser Prozess stoppt, wenn die maximale Menge an Ressourcen pro Kandidat erreicht ist, oder wenn wir den besten Kandidaten identifiziert haben. Der beste Kandidat wird in der Iteration identifiziert, die factor oder weniger Kandidaten evaluiert (siehe unten für eine Erklärung).

Hier ist ein Beispiel mit min_resources=3 und factor=2, beginnend mit 70 Kandidaten

n_resources_i

n_candidates_i

3 (=min_resources)

70 (=n_candidates)

3 * 2 = 6

70 // 2 = 35

6 * 2 = 12

35 // 2 = 17

12 * 2 = 24

17 // 2 = 8

24 * 2 = 48

8 // 2 = 4

48 * 2 = 96

4 // 2 = 2

Wir können feststellen, dass

  • der Prozess in der ersten Iteration stoppt, die factor=2 Kandidaten evaluiert: Der beste Kandidat ist der beste aus diesen 2 Kandidaten. Es ist nicht notwendig, eine zusätzliche Iteration durchzuführen, da diese nur einen Kandidaten evaluieren würde (nämlich den besten, den wir bereits identifiziert haben). Aus diesem Grund möchten wir im Allgemeinen, dass die letzte Iteration höchstens factor Kandidaten evaluiert. Wenn die letzte Iteration mehr als factor Kandidaten evaluiert, dann reduziert sich diese letzte Iteration auf eine reguläre Suche (wie in RandomizedSearchCV oder GridSearchCV).

  • jede n_resources_i ein Vielfaches von sowohl factor als auch min_resources ist (was durch seine Definition oben bestätigt wird).

Die in jeder Iteration verwendete Ressourcenmenge finden Sie im Attribut n_resources_.

Auswahl einer Ressource#

Standardmäßig wird die Ressource in Bezug auf die Anzahl der Stichproben definiert. Das heißt, jede Iteration wird eine steigende Anzahl von Stichproben zum Trainieren verwenden. Sie können jedoch manuell einen Parameter als Ressource angeben, indem Sie den Parameter resource verwenden. Hier ist ein Beispiel, bei dem die Ressource in Bezug auf die Anzahl der Schätzer eines Random Forest definiert ist.

>>> from sklearn.datasets import make_classification
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.experimental import enable_halving_search_cv  # noqa
>>> from sklearn.model_selection import HalvingGridSearchCV
>>> import pandas as pd
>>> param_grid = {'max_depth': [3, 5, 10],
...               'min_samples_split': [2, 5, 10]}
>>> base_estimator = RandomForestClassifier(random_state=0)
>>> X, y = make_classification(n_samples=1000, random_state=0)
>>> sh = HalvingGridSearchCV(base_estimator, param_grid, cv=5,
...                          factor=2, resource='n_estimators',
...                          max_resources=30).fit(X, y)
>>> sh.best_estimator_
RandomForestClassifier(max_depth=5, n_estimators=24, random_state=0)

Beachten Sie, dass es nicht möglich ist, auf einen Parameter zu budgetieren, der Teil des Parametergitters ist.

Erschöpfung der verfügbaren Ressourcen#

Wie oben erwähnt, hängt die in jeder Iteration verwendete Ressourcenmenge vom Parameter min_resources ab. Wenn Sie viele Ressourcen zur Verfügung haben, aber mit einer geringen Anzahl von Ressourcen beginnen, könnten einige davon verschwendet werden (d. h. nicht genutzt werden).

>>> from sklearn.datasets import make_classification
>>> from sklearn.svm import SVC
>>> from sklearn.experimental import enable_halving_search_cv  # noqa
>>> from sklearn.model_selection import HalvingGridSearchCV
>>> import pandas as pd
>>> param_grid= {'kernel': ('linear', 'rbf'),
...              'C': [1, 10, 100]}
>>> base_estimator = SVC(gamma='scale')
>>> X, y = make_classification(n_samples=1000)
>>> sh = HalvingGridSearchCV(base_estimator, param_grid, cv=5,
...                          factor=2, min_resources=20).fit(X, y)
>>> sh.n_resources_
[20, 40, 80]

Der Suchprozess wird höchstens 80 Ressourcen verwenden, während unser maximaler verfügbarer Ressourcenbetrag n_samples=1000 beträgt. Hier haben wir min_resources = r_0 = 20.

Für HalvingGridSearchCV ist der Parameter min_resources standardmäßig auf 'exhaust' gesetzt. Das bedeutet, dass min_resources automatisch so eingestellt wird, dass die letzte Iteration so viele Ressourcen wie möglich nutzen kann, innerhalb des Limits von max_resources.

>>> sh = HalvingGridSearchCV(base_estimator, param_grid, cv=5,
...                          factor=2, min_resources='exhaust').fit(X, y)
>>> sh.n_resources_
[250, 500, 1000]

min_resources wurde hier automatisch auf 250 gesetzt, was dazu führt, dass die letzte Iteration alle Ressourcen nutzt. Der genaue Wert hängt von der Anzahl der Kandidatenparameter, von max_resources und von factor ab.

Für HalvingRandomSearchCV kann die Erschöpfung der Ressourcen auf 2 Arten erfolgen:

  • durch Setzen von min_resources='exhaust', genau wie bei HalvingGridSearchCV;

  • durch Setzen von n_candidates='exhaust'.

Beide Optionen sind gegenseitig ausschließend: Die Verwendung von min_resources='exhaust' erfordert die Kenntnis der Anzahl der Kandidaten, und symmetrisch erfordert n_candidates='exhaust' die Kenntnis von min_resources.

Im Allgemeinen führt die Erschöpfung der Gesamtzahl der Ressourcen zu einem besseren endgültigen Kandidatenparameter und ist geringfügig zeitaufwendiger.

3.2.3.1. Aggressive Eliminierung von Kandidaten#

Mit dem Parameter aggressive_elimination können Sie den Suchprozess dazu zwingen, in der letzten Iteration mit weniger als factor Kandidaten zu enden.

Codebeispiel für aggressive Eliminierung#

Idealerweise möchten wir, dass die letzte Iteration factor Kandidaten evaluiert. Dann müssen wir nur noch den besten auswählen. Wenn die Anzahl der verfügbaren Ressourcen im Verhältnis zur Anzahl der Kandidaten gering ist, muss die letzte Iteration möglicherweise mehr als factor Kandidaten evaluieren.

>>> from sklearn.datasets import make_classification
>>> from sklearn.svm import SVC
>>> from sklearn.experimental import enable_halving_search_cv  # noqa
>>> from sklearn.model_selection import HalvingGridSearchCV
>>> import pandas as pd
>>> param_grid = {'kernel': ('linear', 'rbf'),
...               'C': [1, 10, 100]}
>>> base_estimator = SVC(gamma='scale')
>>> X, y = make_classification(n_samples=1000)
>>> sh = HalvingGridSearchCV(base_estimator, param_grid, cv=5,
...                          factor=2, max_resources=40,
...                          aggressive_elimination=False).fit(X, y)
>>> sh.n_resources_
[20, 40]
>>> sh.n_candidates_
[6, 3]

Da wir nicht mehr als max_resources=40 Ressourcen verwenden können, muss der Prozess in der zweiten Iteration stoppen, die mehr als factor=2 Kandidaten evaluiert.

Wenn aggressive_elimination verwendet wird, eliminiert der Prozess so viele Kandidaten wie nötig unter Verwendung von min_resources Ressourcen.

>>> sh = HalvingGridSearchCV(base_estimator, param_grid, cv=5,
...                            factor=2,
...                            max_resources=40,
...                            aggressive_elimination=True,
...                            ).fit(X, y)
>>> sh.n_resources_
[20, 20, 40]
>>> sh.n_candidates_
[6, 3, 2]

Beachten Sie, dass wir in der letzten Iteration mit 2 Kandidaten enden, da wir während der ersten Iterationen genug Kandidaten eliminiert haben, unter Verwendung von n_resources = min_resources = 20.

3.2.3.2. Analyse der Ergebnisse mit dem Attribut cv_results_#

Das Attribut cv_results_ enthält nützliche Informationen zur Analyse der Ergebnisse einer Suche. Es kann mit df = pd.DataFrame(est.cv_results_) in einen Pandas-DataFrame umgewandelt werden. Das Attribut cv_results_ von HalvingGridSearchCV und HalvingRandomSearchCV ähnelt dem von GridSearchCV und RandomizedSearchCV, mit zusätzlichen Informationen, die sich auf den Prozess der sukzessiven Halbierung beziehen.

Beispiel für einen (abgeschnittenen) Ausgabe-DataFrame:#

iter

n_resources

mean_test_score

params

0

0

125

0.983667

{‘criterion’: ‘log_loss’, ‘max_depth’: None, ‘max_features’: 9, ‘min_samples_split’: 5}

1

0

125

0.983667

{'Kriterium': 'gini', 'max_tiefe': None, 'max_merkmale': 8, 'min_stichproben_aufteilung': 7}

2

0

125

0.983667

{'Kriterium': 'gini', 'max_tiefe': None, 'max_merkmale': 10, 'min_stichproben_aufteilung': 10}

3

0

125

0.983667

{'Kriterium': 'log_loss', 'max_tiefe': None, 'max_merkmale': 6, 'min_stichproben_aufteilung': 6}

15

2

500

0.951958

{'Kriterium': 'log_loss', 'max_tiefe': None, 'max_merkmale': 9, 'min_stichproben_aufteilung': 10}

16

2

500

0.947958

{'Kriterium': 'gini', 'max_tiefe': None, 'max_merkmale': 10, 'min_stichproben_aufteilung': 10}

17

2

500

0.951958

{'Kriterium': 'gini', 'max_tiefe': None, 'max_merkmale': 10, 'min_stichproben_aufteilung': 4}

18

3

1000

0.961009

{'Kriterium': 'log_loss', 'max_tiefe': None, 'max_merkmale': 9, 'min_stichproben_aufteilung': 10}

19

3

1000

0.955989

{'Kriterium': 'gini', 'max_tiefe': None, 'max_merkmale': 10, 'min_stichproben_aufteilung': 4}

Jede Zeile entspricht einer gegebenen Parameterkombination (einem Kandidaten) und einer gegebenen Iteration. Die Iteration wird durch die Spalte iter angegeben. Die Spalte n_resources gibt an, wie viele Ressourcen verwendet wurden.

In dem obigen Beispiel ist die beste Parameterkombination {'criterion': 'log_loss', 'max_depth': None, 'max_features': 9, 'min_samples_split': 10}, da sie die letzte Iteration (3) mit dem höchsten Score: 0,96 erreicht hat.

Referenzen