Dlaczego TensorFlow 2 jest znacznie wolniejszy niż TensorFlow 1?


137

Powołany przez wielu użytkowników jako powód przejścia na Pytorch, ale jeszcze nie znalazłem uzasadnienia / wyjaśnienia dla poświęcenia najważniejszej praktycznej jakości, szybkości, dla szybkiego wykonania.

Poniżej przedstawiono wyniki testów porównawczych kodu, TF1 vs. TF2 - z TF1 działającym gdziekolwiek od 47% do 276% szybciej .

Moje pytanie brzmi: co to jest, na poziomie graficznym lub sprzętowym, które powoduje tak znaczne spowolnienie?


Poszukuję szczegółowej odpowiedzi - już znam szerokie koncepcje. Odpowiedni Git

Specyfikacja : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070


Wyniki testu porównawczego :


AKTUALIZACJA : Wyłączenie funkcji Eager Execution według poniższego kodu nie pomaga. Zachowanie jest jednak niespójne: czasem praca w trybie graficznym znacznie pomaga, innym razem działa wolniej w stosunku do Chętnego.

Ponieważ deweloperzy TF nigdzie się nie pojawiają, sam będę badał tę sprawę - mogę śledzić postępy w powiązanej kwestii Github.

AKTUALIZACJA 2 : mnóstwo wyników eksperymentalnych do udostępnienia wraz z objaśnieniami; powinno być zrobione dzisiaj.


Kod testu porównawczego :

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

Zastosowane funkcje :

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))

Czy kiedykolwiek używałeś takiego narzędzia cProfile do analizy, która część czyni je tak inaczej?
zihaozhihao

@zihaozhihao Mam , choć nie specjalnie do tego; według poprzedniego linku i pisania niestandardowego optymalizatora już wiem o różnicach w rozmowach, ale nie rozumiem, dlaczego ktoś jest wolniejszy od drugiego - ani żaden ekspert spoza TF nie rozumie go ze źródła, które, poza tym, że jest splątany bałagan, nie dokumentuje względnych występów. Wymagane są dane wywiadowcze / sprzętowe na poziomie sprzętowym, których profilery nie dostarczą (o ile jestem w stanie ich użyć)
OverLordGoldDragon 18.10.19

czy wersja numpy jest taka sama w obu testach?
chabir

Ojej… Jeśli sam stary Keras był już znacznie wolniejszy niż PyTorch, wyobraź sobie teraz.
Daniel Möller,

czy problem skaluje się wraz z rozmiarem modelu? próbowałeś również uruchomić ten sam test porównawczy na innym systemie operacyjnym?
okawo

Odpowiedzi:


76

AKTUALIZACJA 2/18/2020 : Korzystałem z usług 2.1 i 2.1 na noc; wyniki są mieszane. Wszystkie konfiguracje oprócz jednego (model i rozmiar danych) są tak szybkie lub znacznie szybsze niż najlepsze z TF2 i TF1. Ten, który jest wolniejszy i znacznie wolniejszy, to Large-Large - szczególnie. w wykonaniu graficznym ( 1,6x do 2,5x wolniej ).

Co więcej, istnieją ekstremalne różnice w odtwarzalności między Graph a Eager dla dużego modelu, który przetestowałem - jednego, którego nie można wytłumaczyć losowością / równoległością obliczeniową. Nie mogę obecnie przedstawić odtwarzalnego kodu dla tych oświadczeń w odniesieniu do ograniczeń czasowych, dlatego zdecydowanie zalecam przetestowanie tego dla własnych modeli.

Nie otworzyłem jeszcze na nich problemu z Gitem, ale skomentowałem oryginał - jeszcze nie ma odpowiedzi. Zaktualizuję odpowiedź (odpowiedzi) po osiągnięciu postępu.


OCENA : nie jest , JEŚLI wiesz, co robisz. Ale jeśli tego nie zrobisz , może cię to kosztować wiele - średnio o kilka aktualizacji GPU i przez najgorszy przypadek wielu GPU.


TA ODPOWIEDŹ : ma na celu przedstawienie wysokiego poziomu opisu problemu, a także wskazówek, jak zdecydować o konfiguracji szkolenia specyficznej dla Twoich potrzeb. Szczegółowy opis niskiego poziomu, który obejmuje wszystkie wyniki testów porównawczych + użyty kod, zobacz moją drugą odpowiedź.

Będę aktualizować moje odpowiedzi w / więcej informacji, jeśli się czegoś nauczę - mogę dodać zakładkę / „gwiazdkę” do tego pytania w celach informacyjnych.


STRESZCZENIE : jak potwierdził twórca TensorFlow, Q. Scott Zhu, TF2 skupił się na szybkim wykonywaniu i ścisłej integracji z Keras, co wiązało się z dużymi zmianami w źródle TF - w tym na poziomie graficznym. Korzyści: znacznie rozszerzone możliwości przetwarzania, dystrybucji, debugowania i wdrażania. Koszt niektórych z nich to jednak szybkość.

Sprawa jest jednak bardziej złożona. Nie chodzi tylko o TF1 vs. TF2 - czynniki powodujące znaczne różnice w prędkości pociągów obejmują:

  1. TF2 vs. TF1
  2. Tryb Eager vs. Graph
  3. keras vs. tf.keras
  4. numpyvs. tf.data.Datasetvs. ...
  5. train_on_batch() vs. fit()
  6. GPU vs. CPU
  7. model(x)vs. model.predict(x)vs. ...

Niestety, prawie żadna z powyższych nie jest niezależna od siebie i każda z nich może przynajmniej podwoić czas wykonania w stosunku do drugiej. Na szczęście możesz ustalić, co będzie działało najlepiej systematycznie i za pomocą kilku skrótów - jak pokażę.


CO POWINIENEM ZROBIĆ? Obecnie jedynym sposobem jest - eksperymentowanie z określonym modelem, danymi i sprzętem. Żadna pojedyncza konfiguracja nie zawsze będzie działać najlepiej - jednak polecenia i nie upraszczają wyszukiwania:

>> ZRÓB:

  • train_on_batch()+ numpy+ tf.keras+ TF1 + Eager / Graph
  • train_on_batch()+ numpy+ tf.keras+ TF2 + Wykres
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + Wykres + duży model i dane

>> NIE:

  • fit()+ numpy+ kerasdla małych i średnich modeli i danych
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + chętny
  • train_on_batch()+ numpy+ keras+ TF1 + chętny

  • [Major] tf.python.keras ; może działać 10-100x wolniej, z dużą ilością błędów; więcej informacji

    • Obejmuje to layers, models, optimizers, i związane z "out-of-box" importu użytkowania; operacje, narzędzia i powiązane „prywatne” importy są w porządku - ale dla pewności sprawdź, czy są alty i czy są używane wtf.keras

Odwołaj się do kodu na dole mojej innej odpowiedzi, aby zobaczyć przykład konfiguracji testu porównawczego. Powyższa lista opiera się głównie na tabelach „BENCHMARKS” w drugiej odpowiedzi.


OGRANICZENIA powyższych DO & DON'T:

  • Pytanie to zatytułowane „Dlaczego TF2 jest znacznie wolniejsze niż TF1?” I chociaż jego ciało wyraźnie dotyczy treningu, sprawa nie ogranicza się do niego; wnioskowanie również podlega dużym różnicom prędkości, nawet w obrębie tej samej wersji TF, importu, formatu danych itp. - zobacz tę odpowiedź .
  • RNN prawdopodobnie zmienią siatkę danych w drugiej odpowiedzi, ponieważ zostały ulepszone w TF2
  • Modele przede wszystkim używane Conv1Di Dense- brak RNN, rzadkie dane / cele, wejścia 4 / 5D i inne konfiguracje
  • Dane wejściowe są ograniczone do numpyi tf.data.Dataset, podczas gdy istnieje wiele innych formatów; zobacz inną odpowiedź
  • Użyto GPU; wyniki będą się różnić w zależności od procesora. W rzeczywistości, kiedy zadałem pytanie, moja CUDA nie była poprawnie skonfigurowana, a niektóre wyniki były oparte na procesorze.

Dlaczego TF2 poświęcił najbardziej praktyczną jakość, szybkość, dla chętnego wykonania? Oczywiście nie ma - wykres jest nadal dostępny. Ale jeśli pytanie brzmi „dlaczego w ogóle chętny”:

  • Doskonałe debugowanie : prawdopodobnie natknąłeś się na wiele pytań z pytaniem „jak uzyskać wyniki warstwy pośredniej” lub „jak sprawdzać wagi”; z niecierpliwością jest to (prawie) tak proste jak .__dict__. W przeciwieństwie do tego wykres wymaga znajomości specjalnych funkcji zaplecza - co znacznie komplikuje cały proces debugowania i introspekcji.
  • Szybsze prototypowanie : według pomysłów podobnych do powyższych; szybsze zrozumienie = pozostało więcej czasu na rzeczywistą DL.

JAK WŁĄCZYĆ / WYŁĄCZYĆ EAGERA?

tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

INFORMACJE DODATKOWE :

  • Ostrożnie z _on_batch()metodami w TF2; według twórcy TF nadal używają wolniejszej implementacji, ale nie celowo - tj. należy to naprawić. Zobacz inne odpowiedzi, aby uzyskać szczegółowe informacje.

WNIOSKI DO URZĄDZEŃ TENSORFLOW :

  1. Proszę naprawić train_on_batch(), a aspekt wydajności fit()iteracyjnego wywoływania ; niestandardowe pętle pociągów są ważne dla wielu, szczególnie dla mnie.
  2. Dodaj do dokumentacji / wzmianki o tych różnicach wydajności dla wiedzy użytkowników.
  3. Popraw ogólną szybkość wykonywania, aby powstrzymać podglądanie od skoku do Pytorch.

PODZIĘKOWANIA : Dzięki


AKTUALIZACJE :

  • 14.11.19 - znalazłem model (w mojej prawdziwej aplikacji), który działa wolniej na TF2 dla wszystkich * konfiguracji z danymi wejściowymi Numpy. Różnice wynosiły 13–19%, średnio 17%. Różnice kerasi tf.kerasbyły jednak bardziej dramatyczne: 18-40% , śr. 32% (zarówno TF1, jak i 2). (* - z wyjątkiem chętnych, dla których TF2 OOM'd)

  • 17.11.19 - deweloperzy zaktualizowali on_batch()metody w ostatnim zatwierdzeniu , stwierdzając, że poprawili prędkość - zostaną wydane w TF 2.1 lub będą dostępne teraz jako tf-nightly. Ponieważ nie jestem w stanie uruchomić tego ostatniego, opóźni testowanie do 2.1.

  • 2/20/20 - wyniki prognozy również są warte porównania; Na przykład w TF2 czasy prognozowania procesora mogą obejmować okresowe skoki

3
Co fit_generator? ... Praktycznie nigdy nie chcę, train_on_batcha zarządzanie własną pętlą treningową pomiędzy partiami jest ogromnym, ogromnym anty-wzorem, którego należy unikać nawet przy ogromnych kosztach.
ely

@ely Pozostaje do przetestowania, jak zauważono w mojej drugiej odpowiedzi - ale jeśli cokolwiek przewiduję, będzie to fitw / małe dodatkowe obciążenie przetwarzania danych. Jeśli chodzi o pętle kolejowe, napisałem własną, niestandardową, która ostatecznie przekształciła się w rodzaj API; fit_generatorbrakuje introspekcji, możliwości dostosowania i zapisywania / ładowania - więc dla mnie absolutnie nie. W końcu opublikuję moją pętlę treningową na Github.
OverLordGoldDragon

Brak introspekcji i możliwości dostosowania to dla mnie funkcja, a nie błąd. IDK, do czego odnosi się komentarz zapisz / załaduj? Pośrednie zapisywanie / ładowanie podczas pętli nie sterowanej przez generator danych? (Osobiście cieszę się również, że mogę polegać tylko na wywołaniach zwrotnych i dostrzegłbym potrzebę dalszej dostosowywania jako zapachu kodu, który źle zaprojektował moją pętlę treningową).
ely

@ely To nie jest proste, ale jest konieczne do szkolenia w / złożonych potoków danych wejściowych, funkcji celu i konfiguracji modeli innych niż API (np. zespoły). Introspekcja jest niezbędna do wielu celów debugowania i inżynierii funkcji. Brak zewnętrznego zapisu / obciążenia, a także niemożności i wznowienia pętli pociągu dla drogo obliczeniowych modeli - koszmar. Niezależnie od tego, ostatecznie zależy od twoich konkretnych potrzeb i odejścia od tematu; najpewniejszym sposobem na przetestowanie wydajności fit_generatoraplikacji jest przetestowanie jej.
OverLordGoldDragon

47

TA ODPOWIEDŹ : ma na celu przedstawienie szczegółowego opisu problemu na poziomie wykresu / sprzętu - w tym pętli pociągów TF2 vs. TF1, procesorów danych wejściowych oraz wykonywania w trybie Eager vs. Graph. Aby uzyskać podsumowanie problemu i wytyczne dotyczące rozwiązywania problemów, zobacz moją drugą odpowiedź.


OCENA WYDAJNOŚCI : czasami jedno jest szybsze, a czasem inne, w zależności od konfiguracji. Jeśli chodzi o TF2 vs TF1, są one na średnim poziomie, ale istnieją znaczące różnice w konfiguracji, a TF1 przebija TF2 częściej niż na odwrót. Zobacz „BENCHMARKING” poniżej.


EAGER VS. WYKRES : sedno całej tej odpowiedzi dla niektórych: według moich testów chętny TF2 jest wolniejszy niż TF1. Szczegóły poniżej.

Podstawowa różnica między nimi polega na tym, że: Graph tworzy sieć obliczeniową proaktywnie i wykonuje się, gdy „polecono” - podczas gdy Eager wykonuje wszystko po stworzeniu. Ale historia zaczyna się dopiero tutaj:

  • Chętny NIE jest pozbawiony Grafu i może w rzeczywistości być w większości Grafem, wbrew oczekiwaniom. W dużej mierze jest wykonywany Wykres - obejmuje to wagi modelu i optymalizatora, które stanowią dużą część wykresu.

  • Chętnie odbudowuje część własnego wykresu podczas wykonywania ; bezpośrednia konsekwencja nieukończenia programu Graph - patrz wyniki profilera. Ma to narzut obliczeniowy.

  • Chętny jest wolniejszy z wejściami Numpy ; zgodnie z tym komentarzem i kodem Gita dane wejściowe Numpy w programie Eager obejmują koszty ogólne kopiowania tensorów z procesora na procesor graficzny. Przechodząc przez kod źródłowy, różnice w przetwarzaniu danych są wyraźne; Chętnie przekazuje Numpy bezpośrednio, a Graph przekazuje tensory, które następnie oceniają na Numpy; niepewny dokładnego procesu, ale ten ostatni powinien obejmować optymalizacje na poziomie GPU

  • TF2 Eager jest wolniejszy niż TF1 Eager - to ... niespodziewane. Zobacz wyniki testów porównawczych poniżej. Różnice rozciągają się od nieznacznych do znaczących, ale są spójne. Nie jestem pewien, dlaczego tak jest - jeśli programista TF wyjaśni, zaktualizuje odpowiedź.


TF2 vs. TF1 : cytując odpowiednie części twórcy TF, Q. Scott Zhu, odpowiedź - w / trochę mojego nacisku i przeredagowania:

Chętnie środowisko wykonawcze musi wykonać operację i zwrócić wartość liczbową dla każdego wiersza kodu python. Charakter wykonywania w jednym kroku powoduje, że jest on powolny .

W TF2 Keras wykorzystuje funkcję tf. Do budowania swojego wykresu do treningu, oceny i prognozowania. Nazywamy je „funkcją wykonania” dla modelu. W TF1 „funkcją wykonania” był FuncGraph, który miał wspólny komponent jako funkcja TF, ale ma inną implementację.

Podczas tego procesu w jakiś sposób pozostawiliśmy niepoprawną implementację dla train_on_batch (), test_on_batch () i przewiduj_on_batch () . Nadal są poprawne numerycznie , ale funkcja wykonawcza dla x_on_batch jest czystą funkcją python, a nie pythonową funkcją tf.function. Spowoduje to spowolnienie

W TF2 konwertujemy wszystkie dane wejściowe na plik tf.data.Dataset, dzięki któremu możemy ujednolicić naszą funkcję wykonawczą do obsługi pojedynczego typu danych wejściowych. Konwersja zestawu danych może wiązać się z pewnym narzutem i myślę, że jest to jednorazowy narzut, a nie koszt pojedynczej partii

Z ostatnim zdaniem ostatniego akapitu powyżej i ostatnią klauzulą ​​poniższego akapitu:

Aby przezwyciężyć spowolnienie w trybie chętnym, mamy funkcję @ tf.function, która zamieni funkcję python w wykres. Po wprowadzeniu wartości liczbowej, takiej jak np. Tablica, korpus funkcji tf. jest przekształcany na wykres statyczny, jest optymalizowany i zwraca wartość końcową, która jest szybka i powinna mieć podobną wydajność jak tryb wykresu TF1.

Nie zgadzam się - na podstawie moich wyników profilowania, które pokazują, że przetwarzanie danych wejściowych Eagera jest znacznie wolniejsze niż wykresu. Nie jestem pewien tf.data.Datasetw szczególności, ale Eager wielokrotnie wywołuje wiele takich samych metod konwersji danych - patrz profiler.

Na koniec zatwierdzone przez dev zatwierdzenie: znaczna liczba zmian w celu obsługi pętli Keras v2 .


Pętle pociągowe : w zależności od (1) chętnych vs. wykresów; (2) w formacie danych wejściowych trenuje dokona odrębnego obiegu kolejowego - w TF2, _select_training_loop(), training.py , jeden spośród:

training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
              training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
            training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph

Każda z nich traktuje alokację zasobów inaczej i ma wpływ na wydajność i możliwości.


Pętle pociągowe: fitvs train_on_batch, kerasvstf.keras .: każda z czterech używa różnych pętli pociągowych, choć być może nie w każdej możliwej kombinacji. kerasfitna przykład używa formy fit_loopnp. training_arrays.fit_loop()i train_on_batchmoże z niej korzystać K.function(). tf.kerasma bardziej wyrafinowaną hierarchię opisaną częściowo w poprzedniej sekcji.


Pętle kolejowe: dokumentacja - odpowiednie dokumentowanie źródłowe dotyczące niektórych różnych metod wykonywania:

W przeciwieństwie do innych operacji TensorFlow, nie przekształcamy danych liczbowych pytona na tensory. Ponadto generowany jest nowy wykres dla każdej odrębnej wartości liczbowej pytona

function tworzy osobny wykres dla każdego unikalnego zestawu kształtów wejściowych i typów danych .

Pojedynczy obiekt tf.function może wymagać odwzorowania na wiele wykresów obliczeniowych pod maską. Powinno to być widoczne tylko jako wydajność (wykresy śledzenia mają niezerowe obliczenia i koszty pamięci )


Procesory danych wejściowych : podobnie jak powyżej, procesor jest wybierany indywidualnie, zależnie od wewnętrznych flag ustawionych zgodnie z konfiguracjami środowiska wykonawczego (tryb wykonywania, format danych, strategia dystrybucji). Najprostszy przypadek to Eager, który działa bezpośrednio z tablicami Numpy. Aby zapoznać się z niektórymi konkretnymi przykładami, zobacz tę odpowiedź .


ROZMIAR MODELU, ROZMIAR DANYCH:

  • Jest decydujący; żadna konfiguracja nie ukoronowała się na wszystkich rozmiarach modeli i danych.
  • Ważny jest rozmiar danych w stosunku do wielkości modelu; w przypadku małych danych i modelu może dominować narzut związany z przesyłaniem danych (np. CPU do GPU). Podobnie małe procesory ogólne mogą pracować wolniej na dużych danych w dominującym czasie konwersji danych (patrz convert_to_tensor„PROFILER”)
  • Prędkość różni się w zależności od sposobu przetwarzania zasobów przez pętle pociągowe i procesory danych wejściowych.

BENCHMARKS : zmielone mięso. - Dokument programu Word - Arkusz kalkulacyjny Excel


Terminologia :

  • Liczby bez% to wszystkie sekundy
  • % obliczone jako (1 - longer_time / shorter_time)*100; uzasadnienie: interesuje nas , który czynnik jest szybszy od drugiego; shorter / longerjest w rzeczywistości relacją nieliniową, nieprzydatną do bezpośredniego porównania
  • Oznaczenie%:
    • TF2 vs TF1: +jeśli TF2 jest szybszy
    • GvE (Graph vs. Eager): +jeśli Graph jest szybszy
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5

PROFILER :


PROFILER - Objaśnienie : Profiler IDE Spyder 3.3.6.

  • Niektóre funkcje są powtarzane w gniazdach innych; dlatego trudno jest wyśledzić dokładny rozdział między funkcjami „przetwarzania danych” a „szkoleniem”, więc pewne nakładanie się będzie na siebie widoczne - jak zaznaczono w ostatnim wyniku.

  • % liczby obliczone w czasie wykonywania minus czas kompilacji

  • Czas budowania obliczany przez zsumowanie wszystkich (unikalnych) środowisk uruchomieniowych, które zostały nazwane 1 lub 2 razy
  • Czas pociągu obliczony przez zsumowanie wszystkich (unikalnych) środowisk wykonawczych, które zostały nazwane tą samą # razy co # iteracji i niektórych środowisk uruchomieniowych ich gniazd
  • Funkcje są profilowane zgodnie z ich oryginalnymi nazwami, niestety (tj. _func = funcBędą się profilować jako func), co miesza się w czasie kompilacji - stąd konieczność wykluczenia

TESTOWANIE ŚRODOWISKA :

  • Wykonany kod u dołu z uruchomionymi minimalnymi zadaniami w tle
  • GPU zostało „rozgrzane” z kilkoma iteracjami przed iteracjami czasowymi, jak sugerowano w tym poście
  • CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0 i TensorFlow 2.0.0 zbudowany ze źródła oraz Anaconda
  • Python 3.7.4, Spyder 3.3.6 IDE
  • GTX 1070, Windows 10, 24 GB pamięci RAM DDR4 2,4 MHz, procesor i7-7700HQ 2,8 GHz

METODOLOGIA :

  • Benchmark „mały”, „średni” i „duży” model i rozmiary danych
  • Napraw liczbę parametrów dla każdego rozmiaru modelu, niezależnie od wielkości danych wejściowych
  • Model „Większy” ma więcej parametrów i warstw
  • „Większe” dane mają dłuższą sekwencję, ale takie same batch_sizeinum_channels
  • Modele używać tylko Conv1D, Dense„nauczenia” warstw; Unikano numerów RNN na implem w wersji TF. różnice
  • Zawsze prowadziłem jedno dopasowanie pociągu poza pętlę testu porównawczego, aby pominąć budowanie modelu i wykresu optymalizatora
  • Niewykorzystywanie rzadkich danych (np. layers.Embedding()) Lub rzadkich celów (npSparseCategoricalCrossEntropy()

OGRANICZENIA : „kompletna” odpowiedź wyjaśniałaby każdą możliwą pętlę pociągu i iterator, ale z pewnością jest to poza moją zdolnością czasową, nieistniejącą wypłatą lub ogólną koniecznością. Wyniki są tak dobre, jak metodologia - interpretuj z otwartym umysłem.


KOD :

import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time

from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model 
#from keras.optimizers import Adam
#import keras.backend as K

#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()

def reset_seeds(reset_graph_with_backend=None, verbose=1):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        if verbose:
            print("KERAS AND TENSORFLOW GRAPHS RESET")

    np.random.seed(1)
    random.seed(2)
    if tf.__version__[0] == '2':
        tf.random.set_seed(3)
    else:
        tf.set_random_seed(3)
    if verbose:
        print("RANDOM SEEDS RESET")

print("TF version: {}".format(tf.__version__))
reset_seeds()

def timeit(func, iterations, *args, _verbose=0, **kwargs):
    t0 = time()
    for _ in range(iterations):
        func(*args, **kwargs)
        print(end='.'*int(_verbose))
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_model_small(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
    x     = GlobalAveragePooling1D()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_medium(batch_shape):
    ipt = Input(batch_shape=batch_shape)
    x = ipt
    for filters in [64, 128, 256, 256, 128, 64]:
        x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_large(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
    x     = Conv1D(128, 200, strides=1, padding='valid')(x)
    for _ in range(40):
        x = Conv1D(256,  12, strides=1, padding='same')(x)
    x     = Conv1D(512,  20, strides=2, padding='valid')(x)
    x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
    x     = Conv1D(256,   1, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)    
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), \
           np.random.randint(0, 2, (batch_shape[0], 1))

def make_data_tf(batch_shape, n_batches, iters):
    data = np.random.randn(n_batches, *batch_shape),
    trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
    return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)

batch_shape_small  = (32, 140,   30)
batch_shape_medium = (32, 1400,  30)
batch_shape_large  = (32, 14000, 30)

batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data",  "Medium data",  "Large data"]
model_names = ["Small model", "Medium model", "Large model"]

def test_all(fit=False, tf_dataset=False):
    for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
        for batch_shape, shape_name in zip(batch_shapes, shape_names):
            if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
                continue
            reset_seeds(reset_graph_with_backend=K)
            if tf_dataset:
                data = make_data_tf(batch_shape, iters, iters)
            else:
                data = make_data(batch_shape)
            model = model_fn(batch_shape)

            if fit:
                if tf_dataset:
                    model.train_on_batch(data.take(1))
                    t0 = time()
                    model.fit(data, steps_per_epoch=iters)
                    print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                else:
                    model.train_on_batch(*data)
                    timeit(model.fit, iters, *data, _verbose=1, verbose=0)
            else:
                model.train_on_batch(*data)
                timeit(model.train_on_batch, iters, *data, _verbose=1)
            cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
            del model

test_all(fit=True, tf_dataset=False)

Nie jestem pewien, czy Twój kod jest poprawny. Myślę, że twoje modele zawsze działają w trybie graficznym, ponieważ wywołujesz model.compilebez run_eagerly=Trueargumentów. W trybie chętnym możesz uruchomić część kodu w trybie graficznym za pomocą tf.function. Dlatego myślę, że domyślną implementacją compilejest tworzenie grafu obliczeniowego zamiast uruchamiania go chętnie ze względu na wydajność. Zauważ też, że jeśli twój model jest splotowy, to nie widzisz przyspieszenia w trybie graficznym, ponieważ interakcja Pythona jest minimalna. Jeśli wykonasz wiele operacji matematycznych, może to mieć duże znaczenie (również w zakresie wykorzystania pamięci).
user2781994,

@OverLordGoldDragon, ale w TF 2 tryb chętny jest domyślnie, ale model.compilebez run_eagerly=Truezapewnia tryb wykresu, czy nie?
user2781994,

@OverLordGoldDragon Zgadzam się, że nie wszystkie importowane metody działają w trybie graficznym, ale myślę, że albo muszą model.compilealbo model.fitzapewnić, że szkolenie działa wewnętrznie w trybie graficznym.
user2781994,

@OverLordGoldDragon PRAWDA - „tf.keras.Model.compile przyjmuje trzy ważne argumenty: ... Dodatkowo, aby upewnić się, że model trenuje i ocenia z niecierpliwością, możesz podać run_eagerly=Truejako parametr do kompilacji.” (źródło tensorflow.org/guide/keras/overview ) Dlatego jeśli nie przejdziesz run_eagerly=Truemodelu, MOŻNA uruchomić w trybie graficznym. Nie jestem pewien, co jest decydującym czynnikiem, ale dlaczego nie miałby działać w trybie graficznym, jeśli jest bardziej wydajny niż chętny.
user2781994,

Chcesz więcej dowodów? :) „Domyślnie spróbujemy skompilować Twój model na wykresie statycznym, aby zapewnić najlepszą wydajność wykonywania.” ( github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/… )
2781994
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.