Wie optimiere ich ein neuronales Netz?

Eine unserer wichtigsten Aufgaben als Experten für Machine Learning ist das Optimieren von Modellen. Gerade für künstliche neuronale Netze ist das eine Aufgabe, die sehr unübersichtlich erscheinen kann. In den folgenden Wochen werden wir an dieser Stelle Einblick in unsere Arbeitsweise geben.

Ein neuronales Netz zu optimieren ist auch auf dem neusten Stand der Technik noch viel mehr Kunst als Wissenschaft. Das bedeutet, dass es keine “Kochrezepte” gibt, anhand derer man den Aufbau eines neuronalen Netzes perfekt bestimmen kann. Gute Data Scientists zeichnen sich durch einen großen Erfahrungsschatz im Bereich von Modellwahl, -paramterisierung und -training aus. Fortschritte in diesen Bereichen werden häufig von universitären und auch kommerziellen Forschungseinrichtungen erzielt. Doch auch hier – sieht man einmal von der Entdeckung vollkommen neuer Algorithmen, Modellen oder Techniken ab – werden Durchbrüche nur durch das Verändern, Erweitern und Neuzusammenfügen bestehender Strukturen erreicht. Mit empirischen Erkenntnissen aus großangelegten Experimenten entstehen neue, bessere und performantere Netzarchitekturen. Viele der Techniken, mit denen heutzutage Verbesserungen in neuronalen Netzen gefunden werden, sind auch für Nichtwissenschaftler anwendbar. In diesem Blogpost wollen wir uns eine recht einfache Möglichkeit der Optimierung genauer anschauen.

Optimieren von Hyperparametern mittels Gridsearch

Im Machine-Learning wird zwischen zwei Arten von Parametern unterschieden. Zum einen gibt es die Parameter, die das Machine-Learning Modell auf dem gegebenen Datensatz lernt, wie z.B. die Gewichte in einem Neuronalen Netz und zum anderen sogenannte Hyperparameter.

Hyperparameter sind Parameter, die vor der Durchführung des gewählten Ansatzes an das Model übergeben werden. Diese definieren damit die Eigenschaften des gewählten Models. Dabei kann es sich beispielsweise um verschiedene Aktivierungsfunktionen oder die Anzahl der Hidden Layers in einem Neuronalen Netz handeln.

Hyperparameter beeinflussen das Training eines neuronalen Netzes extrem. Vor allem Rechenzeit, Speicheraufwand und Netzgenauigkeit können so beeinflusst werden. Durch Trainingsläufe mit verschiedenen Konfigurationen kann man so eine optimale Auswahl der Hyperparameter finden. Wiederholte Trainingsläufe nehmen häufig sehr viel Zeit in Anspruch, was natürlich besonders dann interessant ist, wenn für diese Zeit gezahlt werden muss. Viele Erkenntnisse können jedoch auch auf andere, ähnliche Probleme übertragen werden. Zur Veranschaulichung wollen wir hier ein konkretes Netz optimieren. Dieses Netz soll letztendlich zur Vorhersage von Brustkrebs auf dem Breast Cancer Wisconsin (Diagnostic) Data Set dienen. (Quelle: https://www.kaggle.com/uciml/breast-cancer-wisconsin-data)

Im Datensatz sind verschiedene Werte zu Bestimmung von Brustkrebs gegeben, sowie die Diagnose ob es sich tatsächlich um Brustkrebs handelt oder nicht. Mithilfe eines Neuronalen Netzes wollen wir nun anhand der gegebenen Eigenschaften des Datensatzes Krebs vorhersagen. Dazu spalten wir den Datensatz in Trainings und Testdaten auf, um unser Modell nur mit Daten zu testen die es noch nie gesehen hat. Dann legen wir fest welcher Trainingslauf mit welchen Hyperparametern ausgeführt wird. Parameter die hier variabel sind, und somit unser “Grid” in fünf Dimensionen aufspannen, sind

  • Anzahl der Knoten im Hidden Layer: 1, 32, 64
  • Aktivierungsfunktionen: linear, relu
  • Anteil Dropout im Hidden Layer: 0, 0.3, 0.6
  • Batch Größe: 4, 16, 32
  • Learning Rate: 0.01, 0.1, 0.5, 1

Leichter kann man sich dieses Gitter im zweidimensionalen Raum vorstellen. Wir nehmen also nur die ersten beiden Parameter. Der Parameterraum aus Batchsize und Dropout würde dann so aussehen:

Implementieren von Gridsearch mit Keras

Für unser Beispiel soll eine einfaches neuronales Netz genügen. Wir schreiben dieses mit Hilfe von Keras, einer High-Level-API für Neuronale Netze.


def get_model(hidden_layer_units=32, activation='relu', learning_rate=0.01, dropout_value=0) -> Model:
    input_layer = layers.Input(shape=(config.FEATURE_DIMENSIONALITY,))

    hidden_layer = layers.Dropout(rate=dropout_value)(input_layer)
    hidden_layer = layers.Dense(units=hidden_layer_units, activation=activation)(hidden_layer)
    output_layer = layers.Dense(units=config.NUM_CLASSES, activation='softmax')(hidden_layer)

    model = engine.Model(inputs=[input_layer], outputs=[output_layer])
    optimizer = SGD(lr=learning_rate)
    model.compile(optimizer=optimizer,
                  loss='categorical_crossentropy', metrics=['categorical_accuracy'])

    return model

In Python-Code kann man Gridsearch folgendermaßen umsetzen:

# unsere Parameter
grid = [ACTIVATIONS, BATCH_SIZE, DROPOUT_VALUES, LEARNING_RATE, UNIT_LIST]
# hier werden Kombinationen aus den Parametern erstellt
params = itertools.product(*grid)
features, labels, weights = get_data()
# Für alle Parameterkombinationen...
for i, current_params in enumerate(params):
    # Für reproduzierbare Ergebnisse müssen random seeds gesetzt werden
    np.random.seed = 42
    random.seed = 42
    tensorflow.set_random_seed = 42
    activation, batch_size, dropout_value, learning_rate, num_hidden_units = current_params

    # Hier wird das Model gebaut
    model = get_model(
        num_hidden_units,
        activation,
        learning_rate,
        dropout_value
    )

    # wir speichern die initialen Gewichte (Parameter),
    # um nicht ein komplett neues Modell bauen zu müssen, 
    # was recht lange dauern kann
    initial_weights = model.get_weights()

    # der Trainingslauf wird wiederholt, um ein zuverlässigeres 
    # Ergebnis zu erhalten
    for _ in range(repetitions):
        # Tatsächliches Training des Netzes
        model.fit(
            features, 
            labels, 
            batch_size=batch_size, 
            epochs=25, 
            validation_split=.2, 
            callbacks=[custom_callback], 
            class_weight=weights
        )
        # Modell zurücksetzen
        model.set_weights(initial_weights) 

Einfachere Gridsearch mit scikit-learn

Dieser selbstgeschriebene Prozess kann auch mithilfe von scikit-learn schneller erreicht werden. Dazu muss das Neuronale Netz jedoch in einen sklearn Estimator verwandelt werden und die Hyperparameter in einer bestimmten Form sein. Dies ist glücklicherweise sehr einfach:


from keras.wrappers.scikit_learn import KerasClassifier
sklearn_estimator = KerasClassifier(
    get_model,
    num_hidden_units,
    activation,
    learning_rate,
    dropout_value
)
sklearn_hyper_params = {
    batch_size=BATCH_SIZE
}

Mit diesem Estimator kann nun sklearn verwendet werden um nach der besten Hyperparameterkonfiguration zu suchen. Genauere Informationen kann man in der sklearn Dokumentation finden.


sklearn_gridsearch = GridSearchCV(
    estimator=sklearn_estimator, 
    param_grid=sklearn_hyper_params, 
    n_jobs=1, 
    cv=3
)
search_result = sklearn_gridsearch.fit(features, labels)

# beste Kombination ist hier zu finden
best_params = search_result.best_params_

Der Parameter cv gibt die Anzahl der cross validation folds an, eine Technik, mit der man zuverlässigere Aussagen über die Genauigkeit von trainierten Modellen treffen kann. Wir werden in naher Zukunft einen ausführlichen Artikel darüber auf dieser Website veröffentlichen.

Visualisierung der Ergebnisse einer Gridsearch

Für jede Konfiguration, also jeden Knoten auf diesem Gitter wird das Modell trainiert und der Fehlerwert, beziehungsweise die Genauigkeit beobachtet. Für unser Beispiel kann man diese Daten nun plotten. Anschaulicher werden diese Daten, wenn man immer zwei Hyperparameter variieren lässt und die restlichen fixiert. Im Bild unten wurden batch size und dropout gegeneinander aufgetragen.

Dieser Plot lässt die Schlussfolgerung zu, dass für unser Beispiel die Wahl des dropouts stark in die letztendliche Genauigkeit des Modells einfließt. Genauso ist eine höhere batch size dafür verantwortlich, dass das Modell genauer wird. Ihre Schlussfolgerungen können natürlich stark von unseren abweichen und hängen von den Hyperparametern ab, die Sie absuchen, sowie von Ihrem konkreten Anwendungsfall.

Hat Ihnen dieser Artikel geholfen? Verwenden Sie das Konzept Gridsearch anders? Wir freuen uns über Ihre Nachricht an blog@neuroforge.de!