Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Debugowanie problemów numerycznych w programach TensorFlow przy użyciu TensorBoard Debugger V2

Katastrofalne zdarzenia z udziałem NaN mogą czasami wystąpić podczas programu TensorFlow, paraliżując procesy uczenia modelu. Podstawowa przyczyna takich zdarzeń jest często niejasna, szczególnie w przypadku modeli o nietrywialnych rozmiarach i złożoności. Aby ułatwić debugowanie tego typu błędów modeli, TensorBoard 2.3+ (razem z TensorFlow 2.3+) udostępnia wyspecjalizowany pulpit o nazwie Debugger V2. Tutaj pokazujemy, jak używać tego narzędzia, pracując nad prawdziwym błędem dotyczącym NaN w sieci neuronowej napisanej w TensorFlow.

Techniki przedstawione w tym samouczku mają zastosowanie do innych typów działań związanych z debugowaniem, takich jak sprawdzanie kształtów tensorów środowiska wykonawczego w złożonych programach. Ten samouczek skupia się na NaN ze względu na ich stosunkowo wysoką częstotliwość występowania.

Obserwowanie błędu

Kod źródłowy programu TF2, który będziemy debugować, jest dostępny na GitHub . Przykładowy program jest również umieszczony w pakiecie tensorflow pip (wersja 2.3+) i można go wywołać przez:

 python -m tensorflow.python.debug.examples.v2.debug_mnist_v2
 

Ten program TF2 tworzy wielowarstwową percepcję (MLP) i uczy go rozpoznawania obrazów MNIST . W tym przykładzie celowo użyto niskopoziomowego interfejsu API TF2 do definiowania niestandardowych konstrukcji warstw, funkcji utraty i pętli szkoleniowej, ponieważ prawdopodobieństwo wystąpienia błędów NaN jest większe, gdy używamy tego bardziej elastycznego, ale bardziej podatnego na błędy interfejsu API, niż gdy używamy łatwiejszego -do użytku, ale nieco mniej elastyczne interfejsy API wysokiego poziomu, takie jak tf.keras .

Program drukuje dokładność testu po każdym etapie treningu. W konsoli widzimy, że dokładność testu utknęła na poziomie prawie przypadkowym (~ 0,1) po pierwszym kroku. Z pewnością nie jest to oczekiwane zachowanie modelu uczenia: oczekujemy, że dokładność będzie stopniowo zbliżać się do 1,0 (100%) wraz ze wzrostem kroku.

 Accuracy at step 0: 0.216
Accuracy at step 1: 0.098
Accuracy at step 2: 0.098
Accuracy at step 3: 0.098
...
 

Zgaduje się, że problem ten jest spowodowany niestabilnością liczb, taką jak NaN lub nieskończoność. Jednak w jaki sposób możemy potwierdzić, że tak jest w rzeczywistości i jak znaleźć operację (op) TensorFlow odpowiedzialną za generowanie niestabilności numerycznej? Aby odpowiedzieć na te pytania, oprzyrządujmy błędny program za pomocą Debuggera V2.

Instrumentowanie kodu TensorFlow za pomocą Debuggera V2

tf.debugging.experimental.enable_dump_debug_info() to punkt wejścia API Debuggera V2. Instrumentuje program TF2 za pomocą jednej linii kodu. Na przykład dodanie następującego wiersza w pobliżu początku programu spowoduje zapisanie informacji debugowania w katalogu dziennika (logdir) w / tmp / tfdbg2_logdir. Informacje debugowania obejmują różne aspekty środowiska wykonawczego TensorFlow. W TF2 zawiera pełną historię gorliwego wykonania, budowanie wykresów wykonywane przez funkcję @ tf. , wykonanie wykresów, wartości tensorów generowane przez zdarzenia wykonania, a także lokalizację kodu (ślady stosu Pythona) tych zdarzeń . Bogactwo informacji debugowania umożliwia użytkownikom zawężenie informacji o niejasnych błędach.

 tf.debugging.experimental.enable_dump_debug_info(
    logdir="/tmp/tfdbg2_logdir",
    tensor_debug_mode="FULL_HEALTH",
    circular_buffer_size=-1)
 

Argument tensor_debug_mode kontroluje, jakie informacje Debugger V2 wyodrębnia z każdego tensor_debug_mode lub tensora w grafie. „FULL_HEALTH” to tryb, który przechwytuje następujące informacje o każdym tensorze typu zmiennoprzecinkowego (np. Często spotykany typ float32 i rzadziej spotykany typ bfloat16 dtype ):

  • DType
  • Ranga
  • Całkowita liczba elementów
  • Podział elementów typu zmiennoprzecinkowego na następujące kategorie: ujemna skończona ( - ), zero ( 0 ), dodatnia skończona ( + ), ujemna nieskończoność ( -∞ ), dodatnia nieskończoność ( +∞ ) i NaN .

Tryb „FULL_HEALTH” jest odpowiedni do debugowania błędów dotyczących NaN i nieskończoności. Zobacz poniżej inne obsługiwane tensor_debug_mode s.

W circular_buffer_size kontrole argumentów, ile tensor zdarzenia są zapisywane w logdir. Domyślnie jest to 1000, co powoduje, że tylko ostatnie 1000 tensorów przed końcem oprzyrządowanego programu TF2 jest zapisywane na dysku. To domyślne zachowanie zmniejsza obciążenie debugera, poświęcając kompletność danych debugowania. Jeśli preferowana jest kompletność, tak jak w tym przypadku, możemy wyłączyć bufor cykliczny, ustawiając argument na wartość ujemną (np. -1 tutaj).

Przykład debug_mnist_v2 wywołuje enable_dump_debug_info() , przekazując do niej flagi wiersza poleceń. Aby ponownie uruchomić nasz problematyczny program TF2 z włączoną oprzyrządowaniem do debugowania, wykonaj:

 python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
    --dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH
 

Uruchomienie GUI Debuggera V2 w TensorBoard

Uruchomienie programu z instrumentacją debuggera powoduje utworzenie katalogu dziennika w / tmp / tfdbg2_logdir. Możemy uruchomić TensorBoard i skierować go do logdir za pomocą:

 tensorboard --logdir /tmp/tfdbg2_logdir
 

W przeglądarce internetowej przejdź do strony TensorBoard pod adresem http: // localhost: 6006. Wtyczka „Debugger V2” powinna być domyślnie aktywowana, wyświetlając stronę, która wygląda następująco:

Zrzut ekranu pełnego widoku Debuggera V2

Użycie GUI Debugger V2 do znalezienia głównej przyczyny NaN

Interfejs GUI Debugger V2 w TensorBoard jest podzielony na sześć sekcji:

  • Alerty : ta sekcja w lewym górnym rogu zawiera listę zdarzeń „alertów” wykrytych przez debuger w danych debugowania z oprzyrządowanego programu TensorFlow. Każdy alert wskazuje na pewną anomalię, która wymaga uwagi. W naszym przypadku w tej sekcji wyróżniono zdarzenia 499 NaN / ∞ z wyraźnym różowo-czerwonym kolorem. Potwierdza to nasze podejrzenie, że model nie uczy się z powodu obecności NaN i / lub nieskończoności w jego wewnętrznych wartościach tensora. Wkrótce omówimy te alerty.
  • Oś czasu wykonania Pythona : To jest górna połowa górnej środkowej sekcji. Przedstawia pełną historię gorliwego wykonywania operacji i wykresów. Każde pudełko z osi czasu jest oznaczony przez początkową literę op lub nazwa wykresu (np, „T” do „TensorSliceDataset” op „M” za „model” tf.function ). Możemy poruszać się po tej osi czasu za pomocą przycisków nawigacyjnych i paska przewijania nad osią czasu.
  • Wykonywanie wykresów : ta sekcja znajduje się w prawym górnym rogu GUI i będzie miała kluczowe znaczenie dla naszego zadania debugowania. Zawiera historię wszystkich tensorów typu floating-dtype obliczonych wewnątrz grafów (tj. Skompilowanych przez @tf-function s).
  • Struktura wykresu (dolna połowa górnej środkowej sekcji), kod źródłowy (dolna lewa sekcja) i śledzenie stosu (dolna prawa sekcja) są początkowo puste. Ich zawartość zostanie zapełniona podczas interakcji z GUI. Te trzy sekcje będą również odgrywać ważną rolę w naszym zadaniu debugowania.

Po zorientowaniu się na organizację interfejsu użytkownika, podejmijmy następujące kroki, aby dojść do sedna przyczyn pojawienia się NaN. Najpierw kliknij alert NaN / ∞ w sekcji Alerty. Spowoduje to automatyczne przewinięcie listy 600 tensorów wykresów w sekcji Wykonywanie wykresów i skupienie się na # 88, który jest tensorem o nazwie „Log: 0” wygenerowanym przez Log (logarytm naturalny) op. Wyraźny różowo-czerwony kolor podkreśla element -∞ spośród 1000 elementów tensora 2D float32. Jest to pierwszy tensor w historii wykonywania programu TF2, który zawierał jakikolwiek NaN lub nieskończoność: tensory obliczone przed tym, jak nie zawierają NaN ani ∞; wiele (w rzeczywistości większość) obliczanych później tensorów zawiera NaN. Możemy to potwierdzić, przewijając listę Wykonywanie wykresów w górę iw dół. Ta obserwacja daje mocną wskazówkę, że operacja Log jest źródłem niestabilności numerycznej w tym programie TF2.

Debugger V2: alerty Nan / Infinity i lista wykonania wykresów

Dlaczego ten Log wypluwa -∞? Odpowiedź na to pytanie wymaga przeanalizowania danych wejściowych do op. Kliknięcie nazwy tensora („Log: 0”) powoduje wyświetlenie prostej, ale pouczającej wizualizacji otoczenia Log op na jego wykresie TensorFlow w sekcji Struktura wykresu. Zwróć uwagę na kierunek przepływu informacji od góry do dołu. Sam operacja jest zaznaczona pogrubioną czcionką na środku. Bezpośrednio nad nim widzimy, że operacja zastępcza zapewnia jedyne dane wejściowe do operacji Log . Gdzie na liście Wykonywanie wykresu znajduje się tensor generowany przez ten symbol zastępczy logitów? Używając żółtego koloru tła jako pomocy wizualnej, widzimy, że tensor logits:0 znajduje się dwa rzędy powyżej tensora Log:0 , czyli w wierszu 85.

Debugger V2: Widok struktury wykresu i śledzenie do tensora wejściowego

Dokładniejsze przyjrzenie się numerycznemu rozkładowi tensora „logity: 0” w wierszu 85 ujawnia, dlaczego jego konsument Log:0 daje-produces: Spośród 1000 elementów „logitów: 0” jeden element ma wartość 0. -∞ jest wynikiem obliczenia logarytmu naturalnego z 0! Jeśli uda nam się w jakiś sposób zapewnić, że operacja Log zostanie wystawiona tylko na dodatnie dane wejściowe, będziemy w stanie zapobiec wystąpieniu NaN / ∞. Można to osiągnąć, stosując obcinanie (np. Używając tf.clip_by_value () ) na tensorze Logits Placeholder.

Jesteśmy coraz bliżej rozwiązania błędu, ale jeszcze nie skończyliśmy. Aby zastosować poprawkę, musimy wiedzieć, skąd w kodzie źródłowym Pythona pochodzi operacja Log i jej wejście zastępcze. Debugger V2 zapewnia najwyższej klasy obsługę śledzenia operacji grafowych i zdarzeń wykonania do ich źródła. Kiedy kliknęliśmy tensor Log:0 w wykonaniach wykresu, sekcja Śledzenie stosu została wypełniona oryginalnym śladem stosu utworzenia operacji Log. Ślad stosu jest dość duży, ponieważ zawiera wiele ramek z wewnętrznego kodu TensorFlow (np. Gen_math_ops.py i dumping_callback.py), które możemy bezpiecznie zignorować w przypadku większości zadań związanych z debugowaniem. Interesującą ramką jest linia 216 debug_mnist_v2.py (tj. Plik Pythona, który faktycznie próbujemy debugować). Kliknięcie „Linia 204” powoduje wyświetlenie odpowiedniego wiersza kodu w sekcji Kod źródłowy.

Debugger V2: kod źródłowy i ślad stosu

To ostatecznie prowadzi nas do kodu źródłowego, który utworzył problematyczny Log op na podstawie jego danych wejściowych logits. To jest nasza niestandardowa funkcja @tf.function straty krzyżowej entropii ozdobiona @tf.function i przekształcona w ten sposób w wykres TensorFlow. Symbol zastępczy op „logits” odpowiada pierwszemu argumentowi wejściowemu funkcji utraty. Operacja Log jest tworzona za pomocą wywołania API tf.math.log ().

Poprawka przycinająca wartość tego błędu będzie wyglądać następująco:

   diff = -(labels *
           tf.clip_by_value(tf.math.log(logits), 1e-6, 1.))
 

To rozwiąże niestabilność numeryczną w tym programie TF2 i spowoduje pomyślne trenowanie MLP. Innym możliwym podejściem do naprawienia niestabilności numerycznej jest użycie tf.keras.losses.CategoricalCrossentropy .

Na tym kończy się nasza podróż od obserwacji błędu modelu TF2 do wymyślenia zmiany kodu, która naprawia błąd, wspomagana przez narzędzie Debugger V2, które zapewnia pełny wgląd w gorączkową i graficzną historię wykonania oprzyrządowanego programu TF2, w tym podsumowania liczbowe wartości tensorów i powiązań między operacjami, tensorami i ich oryginalnym kodem źródłowym.

Kompatybilność sprzętowa Debuggera V2

Debugger V2 obsługuje główny sprzęt szkoleniowy, w tym CPU i GPU. Obsługiwane jest również szkolenie z użyciem wielu GPU z tf.distributed.MirroredStrategy . Wsparcie dla TPU jest wciąż na wczesnym etapie i wymaga kontaktu telefonicznego

 tf.config.set_soft_device_placement(True)
 

przed wywołaniem enable_dump_debug_info() . Może mieć również inne ograniczenia dotyczące TPU. Jeśli napotkasz problemy podczas korzystania z Debugger V2, zgłoś je na naszej stronie problemów GitHub .

Zgodność API Debuggera V2

Debugger V2 jest zaimplementowany na stosunkowo niskim poziomie stosu oprogramowania TensorFlow, a zatem jest kompatybilny z tf.keras , tf.data i innymi API zbudowanymi na niższych poziomach TensorFlow. Debugger V2 jest również wstecznie kompatybilny z TF1, chociaż oś czasu zachłannego wykonania będzie pusta dla dzienników debugowania generowanych przez programy TF1.

Wskazówki dotyczące użytkowania interfejsu API

Często zadawane pytanie dotyczące tego debugującego interfejsu API dotyczy tego, gdzie w kodzie TensorFlow należy wstawić wywołanie enable_dump_debug_info() . Zwykle API powinno być wywoływane tak wcześnie, jak to możliwe w programie TF2, najlepiej po liniach importu Pythona i przed rozpoczęciem budowania i wykonywania wykresów. Zapewni to pełne pokrycie wszystkich operacji i wykresów, które zasilają Twój model i jego trening.

Aktualnie obsługiwane tryby tensor_debug_modes to: NO_TENSOR , CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH i SHAPE . Różnią się one ilością informacji wyodrębnionych z każdego tensora i narzutem wydajności do debugowanego programu. Proszę zapoznać się z sekcją argumentów w dokumentacji enable_dump_debug_info() ].

Narzut wydajności

Interfejs API debugowania wprowadza narzut wydajności do oprzyrządowanego programu TensorFlow. Narzut różni się w zależności od tensor_debug_mode , typu sprzętu i charakteru oprzyrządowanego programu TensorFlow. Jako punkt odniesienia, na GPU, tryb NO_TENSOR dodaje 15% narzut podczas uczenia modelu Transformer poniżej rozmiaru wsadu 64. Procentowy narzut dla innych trybów tensor_debug_modes jest wyższy: około 50% dla CURT_HEALTH , CONCISE_HEALTH , FULL_HEALTH i SHAPE tryby. W przypadku procesorów narzut jest nieco niższy. W przypadku TPU narzut jest obecnie wyższy.

Relacja z innymi interfejsami API debugowania TensorFlow

Należy pamiętać, że TensorFlow oferuje inne narzędzia i interfejsy API do debugowania. Możesz przeglądać takie interfejsy API w przestrzeni nazw tf.debugging.* Na stronie dokumentacji API. Wśród tych API najczęściej używaną jest tf.print() . Kiedy należy używać Debugger V2, a kiedy tf.print() ? tf.print() jest wygodna w przypadku, gdy

  1. wiemy dokładnie, które tensory wydrukować,
  2. wiemy, gdzie dokładnie w kodzie źródłowym wstawić te tf.print() ,
  3. liczba takich tensorów nie jest zbyt duża.

W innych przypadkach (np. Badanie wielu wartości tensorów, badanie wartości tensorów generowanych przez wewnętrzny kod TensorFlow i poszukiwanie źródła niestabilności numerycznej, jak pokazaliśmy powyżej), Debugger V2 zapewnia szybszy sposób debugowania. Ponadto Debugger V2 zapewnia ujednolicone podejście do inspekcji tensorów chętnych i wykresów. Dodatkowo dostarcza informacji o strukturze grafu i lokalizacjach kodu, które są poza możliwościami tf.print() .

Innym interfejsem API, którego można użyć do debugowania problemów dotyczących ∞ i NaN, jest tf.debugging.enable_check_numerics() . W przeciwieństwie do enable_dump_debug_info() , enable_check_numerics() nie zapisuje informacji debugowania na dysku. Zamiast tego po prostu monitoruje ∞ i NaN podczas działania TensorFlow i błędy w lokalizacji kodu źródłowego, gdy tylko jakakolwiek operacja wygeneruje tak złe wartości liczbowe. Ma mniejszy narzut wydajności w porównaniu z enable_dump_debug_info() , ale nie zapewnia pełnego śledzenia historii wykonywania programu i nie jest wyposażony w graficzny interfejs użytkownika, taki jak Debugger V2.