Przejdź do treści

Artykuł · Technologia

Liczby losowe w uczeniu maszynowym i AI

Podział danych, inicjalizacja wag, dropout i augmentacja opierają się na liczbach losowych. Do tego ziarno i powtarzalność wyników.

Opublikowano Aktualizacja ok. 10 minut czytania

Model uczenia maszynowego ma znajdować w danych regularności, a mimo to na prawie każdym etapie jego budowy pojawiają się liczby losowe. Losowo dzieli się dane na zbiór treningowy i testowy, losowo ustawia wagi sieci przed treningiem, losowo wybiera porcje danych do kolejnych kroków uczenia i losowo wyłącza część neuronów.

W tym artykule opisujemy, po co w każdym z tych miejsc jest losowość i co by się stało bez niej. Pokazujemy też, jak ustawić ziarno, żeby wynik dało się powtórzyć, bo to w praktyce najczęstsze pytanie osób zaczynających pracę z modelami.

Gdzie pojawia się losowość

Podział danych
Losowy wybór przykładów do zbioru treningowego, walidacyjnego i testowego.
Optymalizacja
Losowa kolejność przykładów i losowe mini-batche w metodzie SGD.
Inicjalizacja
Losowe wartości początkowe wag, które różnicują neurony.
Regularyzacja
Dropout i dodawanie szumu, które utrudniają modelowi zapamiętanie danych.
Zespoły modeli
Losowe próbki danych i cech w lasach losowych i baggingu.
Augmentacja
Losowe obroty, przycięcia i zmiany kolorów obrazów treningowych.

Do tego dochodzi uczenie ze wzmocnieniem, w którym agent czasem celowo wybiera losowe działanie zamiast najlepszego znanego, żeby sprawdzić, czy nie istnieje lepsza strategia. Wszystkie te liczby pochodzą ze zwykłych generatorów pseudolosowych. Jak działają, opisujemy w artykule o algorytmach losowania w komputerach.

Podział i mieszanie danych

Model ocenia się na danych, których nie widział podczas treningu. Dlatego przed treningiem dzieli się zbiór na część treningową i testową. Podział musi być losowy: jeśli dane są posortowane, na przykład według daty albo klasy, odcięcie ostatnich 20 procent dałoby zbiór testowy zupełnie niepodobny do treningowego.

Python, scikit-learn
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,     # 20% danych do testu
    stratify=y,        # zachowaj proporcje klas w obu częściach
    random_state=42,   # ziarno: ten sam podział przy każdym uruchomieniu
)

Parametr random_state to ziarno. Z tą samą wartością podział wychodzi identyczny przy każdym uruchomieniu, co pozwala uczciwie porównywać modele. To samo dotyczy walidacji krzyżowej, w której dane dzieli się losowo na kilka części i każda z nich po kolei służy do testu.

SGD i mini-batche

Sieć uczy się, poprawiając wagi w kierunku, w którym maleje błąd. Klasyczny spadek gradientu liczy ten kierunek na całym zbiorze danych, co przy milionach przykładów jest bardzo wolne. Stochastyczny spadek gradientu (SGD) liczy go na małej, losowo wybranej porcji danych, czyli mini-batchu.

  1. Przemieszaj zbiór treningowy w losowej kolejności.
  2. Weź kolejną porcję, na przykład 32 przykłady.
  3. Oblicz błąd i jego gradient tylko dla tej porcji.
  4. Popraw wagi o mały krok w kierunku przeciwnym do gradientu.
  5. Gdy skończą się porcje, zacznij nową epokę od ponownego przemieszania danych.

Gradient z małej porcji jest tylko przybliżeniem, więc kolejne kroki są trochę chaotyczne. Ten szum ma jednak zalety: ułatwia wyjście z płytkich minimów i, jak sugerują badania, takie jak praca Smitha i Le z 2018 roku, sprzyja rozwiązaniom, które lepiej działają na nowych danych. Popularne optymalizatory, takie jak SGD z momentem, RMSprop i Adam, różnią się sposobem wyliczania kroku, ale wszystkie korzystają z tych samych losowych mini-batchy.

Losowość pomaga też przy wyborze hiperparametrów, na przykład tempa uczenia. James Bergstra i Yoshua Bengio pokazali w 2012 roku, że losowe próbkowanie ustawień często znajduje dobre wartości szybciej niż sprawdzanie regularnej siatki, bo zwykle tylko kilka hiperparametrów naprawdę ma znaczenie.

Inicjalizacja wag

Przed treningiem wagi sieci trzeba czymś wypełnić. Zera nie działają: wszystkie neurony w warstwie liczyłyby wtedy to samo i dostawały te same poprawki, więc do końca treningu pozostałyby identyczne. Losowe wartości początkowe łamią tę symetrię.

Ważna jest też skala. Zbyt duże wagi sprawiają, że sygnał rośnie z warstwy na warstwę, a zbyt małe, że zanika. Xavier Glorot i Yoshua Bengio pokazali w 2010 roku, jak dobrać rozrzut wag do liczby wejść i wyjść warstwy. Kaiming He i współpracownicy dopasowali tę metodę w 2015 roku do funkcji aktywacji ReLU.

Popularne metody losowej inicjalizacji wag
Metoda Odchylenie standardowe wag Zwykle z aktywacją
Xavier (Glorot) √(2 / (nwe + nwy)) tanh, sigmoid
He (Kaiming) √(2 / nwe) ReLU i pochodne
Ortogonalna macierz wag jest losową macierzą ortogonalną sieci rekurencyjne

nwe i nwy to liczba wejść i wyjść warstwy. Wagi losuje się z rozkładu normalnego o średniej zero i podanym odchyleniu. Istnieją też wersje z rozkładem jednostajnym.

Python, PyTorch
import torch.nn as nn

class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 10)
        # inicjalizacja He dla warstwy z ReLU
        nn.init.kaiming_normal_(self.fc1.weight, nonlinearity='relu')
        # biasy mogą zostać zerami, symetrię łamią już wagi
        nn.init.zeros_(self.fc1.bias)

    def forward(self, x):
        return self.fc2(nn.functional.relu(self.fc1(x)))

Dropout

Duża sieć potrafi zapamiętać dane treningowe zamiast nauczyć się ogólnych reguł. Dropout utrudnia to w prosty sposób: w każdym kroku treningu losowo wyłącza część neuronów, zwykle od 20 do 50 procent. Sieć nie może więc polegać na żadnym pojedynczym neuronie i musi rozłożyć wiedzę na wiele z nich. Metodę dokładnie opisali Nitish Srivastava, Geoffrey Hinton i współpracownicy w 2014 roku.

Wypróbuj: dropout w małej sieci

Sieć z czterema wejściami, dwiema warstwami ukrytymi po sześć neuronów i dwoma wyjściami. Wyłączone neurony są wyblakłe.

Każdy krok trenuje więc trochę inną, mniejszą sieć. Podczas przewidywania wyłączanie się kończy i działają wszystkie neurony. Żeby skala wyjść się zgadzała, wartości przeskalowuje się: w PyTorchu i Kerasie dzieje się to już podczas treningu, przez podzielenie wyjść pozostałych neuronów przez prawdopodobieństwo ich pozostawienia.

Python, PyTorch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Dropout(p=0.5),   # w treningu wyłącza losowo 50% neuronów
    nn.Linear(256, 10),
)

model.train()   # dropout włączony
model.eval()    # dropout wyłączony, np. przy testach i przewidywaniu

Na tej samej zasadzie działają pokrewne metody. DropConnect wyłącza losowo pojedyncze połączenia zamiast neuronów. Stochastic depth pomija w czasie treningu całe bloki sieci rezydualnej. Prostszym sposobem jest dodawanie losowego szumu do wejść albo wag.

Lasy losowe i bagging

Pojedyncze drzewo decyzyjne łatwo dopasowuje się do przypadkowych szczegółów danych. Las losowy, opisany przez Leo Breimana w 2001 roku, buduje wiele drzew i łączy ich głosy. Żeby drzewa się od siebie różniły, korzysta z dwóch rodzajów losowości.

Bagging
Każde drzewo uczy się na losowej próbce danych, wylosowanej ze zwracaniem z całego zbioru. Ten sam przykład może trafić do próbki kilka razy, a inny wcale.
Losowe cechy
Przy każdym podziale drzewo wybiera najlepszą cechę tylko spośród losowo wybranego podzbioru cech.

Błędy pojedynczych drzew są wtedy w dużej mierze niezależne i przy głosowaniu się znoszą. Metoda extremely randomized trees idzie dalej i losuje także progi podziału.

Python, scikit-learn
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(
    n_estimators=100,      # liczba drzew
    max_features='sqrt',   # liczba losowo wybieranych cech przy podziale
    bootstrap=True,        # każde drzewo dostaje losową próbkę ze zwracaniem
    random_state=42,
)
rf.fit(X_train, y_train)
print(rf.score(X_test, y_test))

Augmentacja danych

Zebranie nowych danych jest drogie, ale z istniejących można zrobić wiele wersji. Kot obrócony o kilka stopni albo trochę przyciemniony nadal jest kotem. Augmentacja losowo przekształca przykłady treningowe przy każdym ich użyciu, więc model rzadko widzi dwa razy dokładnie to samo zdjęcie.

Dobór przekształceń też bywa losowy. AutoAugment szuka najlepszego zestawu przy pomocy uczenia ze wzmocnieniem. RandAugment upraszcza to do losowego wyboru kilku przekształceń o zadanej sile, a AugMix losowo łączy kilka łańcuchów przekształceń w jeden obraz.

Ziarno i powtarzalność wyników

Skoro losowość pojawia się w tylu miejscach, dwa treningi tego samego modelu na tych samych danych dają trochę inne wyniki. Xavier Bouthillier i współpracownicy pokazali w 2021 roku, że sama zmiana ziarna potrafi przesunąć wynik modelu na tyle, że różnice między porównywanymi metodami w publikacjach bywają mniejsze niż ten rozrzut.

Żeby wynik dało się powtórzyć, ziarno ustawia się we wszystkich bibliotekach, z których korzysta program, bo każda ma własny generator:

Python, ustawienie ziarna
import random
import numpy as np
import torch

SEED = 42
random.seed(SEED)                          # moduł random z biblioteki standardowej
np.random.seed(SEED)                       # NumPy
torch.manual_seed(SEED)                    # PyTorch, CPU i GPU
torch.use_deterministic_algorithms(True)   # deterministyczne operacje na GPU

# TensorFlow i Keras: jedno wywołanie
# ustawia ziarno dla random, NumPy i TensorFlow
# keras.utils.set_random_seed(SEED)

Nawet z ustawionym ziarnem część operacji na kartach graficznych może dawać minimalnie różne wyniki, bo kolejność sumowania liczb zmiennoprzecinkowych bywa niedeterministyczna. Tryb deterministyczny to wyłącza, kosztem szybkości. Przy ważnych porównaniach lepiej zrobić coś więcej: wytrenować model z kilkoma różnymi ziarnami i podać średnią oraz rozrzut wyników.

Prace źródłowe

Jeśli chcesz poczytać więcej, te publikacje opisują metody z artykułu u źródła:

  1. 2001

    Leo Breiman

    „Random Forests”. Opis lasów losowych.

  2. 2010

    Glorot, Bengio

    „Understanding the difficulty of training deep feedforward neural networks”. Inicjalizacja Xaviera.

  3. 2012

    Bergstra, Bengio

    „Random Search for Hyper-Parameter Optimization”. Losowe przeszukiwanie hiperparametrów.

  4. 2014

    Srivastava i in.

    „Dropout: A Simple Way to Prevent Neural Networks from Overfitting”.

  5. 2015

    He i in.

    „Delving Deep into Rectifiers”. Inicjalizacja He dla sieci z ReLU.

  6. 2018

    Smith, Le

    „A Bayesian Perspective on Generalization and Stochastic Gradient Descent”.

  7. 2018

    Zhang i in.

    „mixup: Beyond Empirical Risk Minimization”.

  8. 2021

    Bouthillier i in.

    „Accounting for Variance in Machine Learning Benchmarks”. Wpływ ziarna na wyniki porównań.

O tym, jak przez lata zmieniały się same generatory, piszemy w artykule o historii generatorów liczb losowych. Losowe liczby do własnych eksperymentów wylosujesz w generatorze liczb, a gotowe zestawy danych w generatorze danych testowych.

Często zadawane pytania

Dlaczego wag sieci neuronowej nie ustawia się na zero?

Jeśli wszystkie wagi w warstwie mają tę samą wartość, wszystkie neurony liczą to samo i dostają te same poprawki podczas uczenia. Pozostają identyczne przez cały trening, więc warstwa zachowuje się jak jeden neuron.

Losowe wartości początkowe sprawiają, że neurony od początku się różnią i mogą nauczyć się różnych cech.

Co oznacza random_state w scikit-learn?

To ziarno generatora liczb losowych używanego przez funkcję albo model. Ta sama wartość random_state daje ten sam podział danych i ten sam model przy każdym uruchomieniu, co ułatwia porównywanie wyników.

Bez niego każde uruchomienie może dać trochę inny wynik.

Czy dropout działa podczas przewidywania?

Nie. Neurony są wyłączane losowo tylko podczas treningu. Przy przewidywaniu sieć używa wszystkich neuronów, a skala wyjść jest wyrównana tak, żeby odpowiadała tej z treningu.

W PyTorchu przełącza się to metodami model.train() i model.eval().

Dlaczego ten sam model daje różne wyniki przy kolejnych uruchomieniach?

Bo losowe są inicjalizacja wag, kolejność danych w mini-batchach, dropout i augmentacja. Każde uruchomienie z innym ziarnem prowadzi trening trochę inną drogą.

Żeby wyniki dało się powtórzyć, ustawia się ziarno we wszystkich używanych bibliotekach, a przy obliczeniach na GPU włącza się dodatkowo tryb deterministyczny.

Czy do uczenia maszynowego potrzebny jest generator kryptograficzny?

Nie. Uczenie maszynowe potrzebuje dużo liczb szybko i często w powtarzalny sposób, więc korzysta ze zwykłych generatorów pseudolosowych z ustawianym ziarnem.

Generator kryptograficzny jest potrzebny tam, gdzie liczy się bezpieczeństwo, na przykład przy kluczach i hasłach.