NNI – czyli jak znaleźć optymalne parametry sieci neuronowej?

 In Technicznie

Poszukiwanie optymalnych parametrów sieci neuronowej jest najczęściej ostatnim etapem przygotowywania modelu sieci neuronowej.
Przemyślana architektura, duży zbiór danych oraz potężne zaplecze obliczeniowe nie gwarantują nam uzyskania oczekiwanej dokładności modelu w dostarczaniu rozwiązań naszego problemu.
Oprócz samego zrozumienia istoty tych parametrów, ważne jest również umiejętne nimi żonglowanie.
Poszukiwanie ich można porównać do obliczania nieco mniej złożonej sieci, dla której szukamy jak najlepszego przyrostu dokładności w naszym bazowym modelu. Do tego celu idealnie nadaje się narzędzie NNI (Neural Network Intelligence).

Czym jest NNI?

NNI to opensource’owy projekt pod banderą Microsoftu, który podczas pisania tego posta był nadal w fazie rozwojowej (v0.5.2).
Mimo to funkcjonalności, jakie oferuje, wystarczają do skutecznego, zautomatyzowanego przeszukiwanie ustalonego zakresu hiperparametrów naszych sieci.

Wydaje się, że na tę chwilę jedyne istotne mankamenty tego narzędzia to brak wsparcia w usuwaniu procesów ze skryptami uczącymi po ręcznym zatrzymaniu eksperymentu lub po wystąpieniu błędu wewnętrznego (#501) oraz brak wsparcia w równomiernym rozdysponowaniu mocy obliczeniowej na kartach graficznych w przypadku, gdy nasz model wykorzystuje jedynie niewielki procent pamięci VRAM (#608 #938).

Nie zapominajmy, że narzędzie to jest nadal w fazie rozwojowej i do rozwiązania większości problemów przyczynić się można samemu.

Największe zalety:

  • Obsługa wielu popularnych bibliotek pythonowych dla uczenia maszynowego,
  • Możliwość wykorzystania wielu zaimplementowanych już algorytmów tuningu parametrów uczenia NN,
  • Możliwość uruchamiania eksperymentów na lokalnej maszynie, rozpraszania ich w chmurze oraz własnej infrastrukturze PAI.

 

Jak utworzyć eksperyment w NNI?

Na wstępie chciałbym zasugerować, by środowisko dla NNI przygotować, wykorzystując oprogramowanie do wirtualizacji. To właśnie dzięki temu z łatwością będzie można zarządzać zależnościami (jeżeli eksperyment uruchamiamy na fizycznych maszynach). Nie jest to krok niezbędny, aczkolwiek bardzo ułatwiający zarządzanie i obsługę.

Osobiście polecam do tego celu kontenery Dockerowe. W następnym wpisie pokażę, jak utworzyć kontener dla obliczeń z wykorzystaniem GPU.

Wymagania

  • System operacyjny Linux (Ubuntu 16.04 lub wyżej), MacOS (10.14.1)  (dla użytkowników Windowsa polecam skorzystać z możliwości oferowanych przez kontenery Dockerowe)
  • Programy python3  (>= 3.5), pip
  • Minimalne wymagania sprzętowe

Instalacja NNI

Po pierwsze, musimy zainstalować sam program.

Ponieważ mamy zainstalowany już wcześniej program pip , wystarczy, że uruchomimy polecenie:

python3 -m pip install --upgrade nni

Konfiguracja eksperymentu

Kolejnym krokiem będzie skonfigurowanie eksperymentu NNI, co w standardowym podejściu sprowadza się do utworzenia dwóch plików:

    1. searchspace.json – plik zawierający zakresy poszukiwań poszczególnych parametrów naszego modelu lub procesu uczenia sieci neuronowej. Oto przykład takiej konfiguracji zaczerpnięty z oficjalnej dokumentacji:
      {
       "dropout_rate": {"_type": "uniform", "_value": [0.1, 0.5]}, 
       "conv_size": {"_type": "choice", "_value": [2, 3, 5, 7]},
       "hidden_size": {"_type": "choice", "_value": [124, 512, 1024]},
       "batch_size": {"_type": "choice", "_value": [50, 250, 500]},
       "learning_rate": {"_type": "uniform", "_value": [0.0001, 0.1]}
      }

      Kolejne klucze to nazwy zmiennych, do których odwoływać się będziemy w naszym kodzie. W przypisanych im obiektach znaleźć możemy typ poszukiwania – _type  oraz zakres – _value . Wybranie typu choice spowoduje losowe wykorzystywanie elementów z listy pod kluczem _value , natomiast wybranie typu uniform da nam możliwość otrzymania losowych wartości z zakresu, którego ramy określamy w polu _value . Pozostałe dostępne typy opisane są w dokumentacji.

    2. config.yaml  – plik konfiguracyjny określający warunki uruchomienia eksperymentu oraz kilka metadanych. Podstawowy szablon ze zmiennymi niezbędnymi do uruchomienia lokalnego eksperymentu  wygląda następująco:
      authorName: Ermlab.com
      experimentName: Test experiment
      trialConcurrency: 2
      maxExecDuration: 512h
      maxTrialNum: 16
      
      #choice: local, remote, pai, kubeflow
      trainingServicePlatform: local
      searchSpacePath: searchspace.json
      
      #choice: true, false
      useAnnotation: false
      tuner:
        #choice: TPE, Random, Anneal, Evolution
        builtinTunerName: TPE
        classArgs:
          #choice: maximize, minimize
          optimize_mode: maximize
        gpuNum: 0
      trial:
        command: python3 -m main
        codeDir: /home/ermlab/TEST_NNI/
        gpuNum: 0
      

      Ponieważ możliwych konfiguracji jest zbyt dużo jak na tak krótki wpis, po szczegółowe wyjaśnienie poszczególnych zmiennych odsyłam tutaj.

Konfiguracja skryptu trenującego

Jako że konfigurację NNI mamy już za sobą, kolejnym krokiem będzie utworzenie bądź też modyfikacja istniejącego skryptu.

Ze względu na zachowanie przejrzystości przykładu załóżmy, że nasz skrypt do uczenia wygląda tak:

import random
from time import sleep

import nni


class Model:
    def __init__(self, learning_rate: float, dropout: float, batch_size: int, epochs: int):
        self.learning_rate = learning_rate
        self.dropout = dropout
        self.batch_size = batch_size
        self.epochs = epochs

        self.metrics = []

    def train(self):
        for epoch in range(self.epochs):
            # Do some real stuff here
            print(f'Epoch no {epoch}')
            metric: float = random.random()
            self.metrics.append(metric)

            sleep(1)
            nni.report_intermediate_result(metric)

        nni.report_final_result(max(self.metrics))


if __name__ == '__main__':
    PARAMETERS = nni.get_next_parameter()
    print(f'Loaded parameters: {PARAMETERS}')
    model = Model(
        learning_rate=PARAMETERS.get('learning_rate'),
        dropout=PARAMETERS.get('dropout'),
        batch_size=PARAMETERS.get('batch_size'),
        epochs=PARAMETERS.get('epochs', 100)
    )
    print('Starting the training')
    model.train()

Uruchomienie eksperymentu

Po pierwsze, upewnijmy się, czy w środowisku, w którym będziemy uruchamiać eksperyment, NNI zostało zainstalowane poprawnie:

#nnictl --version
0.5.2

Następnie uruchommy eksperyment poleceniem nnictl create -c config.yaml . Jeżeli wszystko jest skonfigurowane poprawnie, po wejściu na wyświetlony w konsoli adres panelu do zarządzania eksperymentem powinniśmy zobaczyć ekrany podobne do tych, zamieszczonym poniżej:

 

Podsumowanie

NNI jest bardzo wygodnym oraz intuicyjnym narzędziem do poszukiwania optymalnych parametrów sieci neuronowych. Przejrzystość wyników oraz możliwość wizualizacji najlepszych kombinacji parametrów z pewnością ułatwiają ich analizę. Jednakże największą zaletą NNI jest prawdopodobnie brak potrzeby ręcznego modyfikowania ustawień treningu po każdym zakończonym treningu, co w rezultacie umożliwia prawie pełną automatyzację wytrenowania modelu wynikowego.

Podsumowując, porównywanie wyników treningu, wczesne zatrzymywanie źle rokujących sesji treningowych oraz algorytmy konsekwentnie optymalizujące dobór parametrów to najistotniejsze, a przede wszystkim bardzo przydatne cechy tego oprogramowania.

Recent Posts