Katastrofalne zdarzenia związane z NaN mogą czasami wystąpić podczas programu TensorFlow, paraliżując procesy uczenia modelu. Przyczyny takich zdarzeń są często niejasne, zwłaszcza w przypadku modeli o nietrywialnych rozmiarach i złożoności. Aby ułatwić debugowanie tego typu błędów modelu, TensorBoard 2.3+ (wraz z TensorFlow 2.3+) zapewnia wyspecjalizowany pulpit nawigacyjny o nazwie Debugger V2. Tutaj pokazujemy, jak korzystać z tego narzędzia, pracując nad prawdziwym błędem dotyczącym NaN w sieci neuronowej napisanej w TensorFlow.
Techniki zilustrowane w tym samouczku mają zastosowanie do innych rodzajów czynności związanych z debugowaniem, takich jak sprawdzanie kształtów tensora środowiska wykonawczego w złożonych programach. Ten samouczek koncentruje się na sieciach NaN ze względu na ich stosunkowo wysoką częstotliwość występowania.
Obserwując błąd
Kod źródłowy programu TF2, który będziemy debugować, jest dostępny na GitHub . Przykładowy program jest również spakowany 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 . Ten przykład celowo wykorzystuje niskopoziomowe API TF2 do definiowania niestandardowych konstrukcji warstw, funkcji straty i pętli treningowej, ponieważ prawdopodobieństwo błędów NaN jest wyższe, gdy używamy tego bardziej elastycznego, ale bardziej podatnego na błędy API, niż gdy używamy łatwiejszego -to-use, 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 bliskim szansy (~0,1) po pierwszym kroku. Z pewnością nie tak ma się zachowywać uczenie modelu: 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
...
Przypuszcza się, że problem ten jest spowodowany niestabilnością numeryczną, taką jak NaN lub nieskończoność. Jak jednak możemy potwierdzić, że tak jest naprawdę i jak znaleźć operację TensorFlow (op) odpowiedzialną za generowanie niestabilności numerycznej? Aby odpowiedzieć na te pytania, oprzyjmy program z błędami za pomocą Debuggera V2.
Oprzyrządowanie kodu TensorFlow za pomocą debugera 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ącej linii na 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ę szybkiego wykonania, budowanie grafów wykonywane przez @tf.function , wykonanie grafó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 niejasnych błędów.
tf.debugging.experimental.enable_dump_debug_info(
"/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 tensora ang. eager lub in-graph tensor. „FULL_HEALTH” to tryb, który przechwytuje następujące informacje o każdym tensorze typu zmiennoprzecinkowego (np. powszechnie widziany typ float32 i mniej popularny typ dfloat16 ):
- 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ść (+∞) iNaN.
Tryb „FULL_HEALTH” jest odpowiedni do debugowania błędów związanych z NaN i nieskończonością. Zobacz poniżej inne obsługiwane tensor_debug_mode .
Argument circular_buffer_size kontroluje liczbę zdarzeń tensora zapisywanych w logdir. Wartość domyślna to 1000, co powoduje zapisanie na dysku tylko ostatnich 1000 tensorów przed końcem oprzyrządowanego programu TF2. To domyślne zachowanie zmniejsza obciążenie debugera, poświęcając kompletność danych debugowania. Jeśli preferowana jest kompletność, jak w tym przypadku, możemy wyłączyć bufor cykliczny, ustawiając argument na wartość ujemną (np. tutaj -1).
Przykład debug_mnist_v2 wywołuje enable_dump_debug_info() przez przekazanie do niej flag wiersza poleceń. Aby ponownie uruchomić nasz problematyczny program TF2 z włączonym instrumentem debugowania, wykonaj następujące czynności:
python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
--dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH
Uruchamianie interfejsu graficznego Debugger V2 w TensorBoard
Uruchomienie programu z instrumentacją debugera tworzy katalog dziennika w /tmp/tfdbg2_logdir. Możemy uruchomić TensorBoard i skierować go na logdir za pomocą:
tensorboard --logdir /tmp/tfdbg2_logdir
W przeglądarce internetowej przejdź do strony TensorBoard pod adresem http://localhost:6006. Wtyczka „Debugger V2” będzie domyślnie nieaktywna, więc wybierz ją z menu „Nieaktywne wtyczki” w prawym górnym rogu. Po wybraniu powinien wyglądać następująco:

Korzystanie z graficznego interfejsu użytkownika Debugger V2 w celu znalezienia głównej przyczyny NaN
Interfejs graficzny Debugger V2 w TensorBoard jest podzielony na sześć sekcji:
- Alerty : ta lewa górna sekcja 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 zasługuje na uwagę. W naszym przypadku ta sekcja wyróżnia 499 zdarzeń 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 wartościach tensora wewnętrznego. 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 pole na osi czasu jest oznaczone początkową literą nazwy operacji lub wykresu (np. „T” dla operacji „TensorSliceDataset”, „m” dla „modelu”
tf.function). Możemy poruszać się po tej osi czasu za pomocą przycisków nawigacyjnych i paska przewijania nad osią czasu. - Wykonywanie wykresu : Ta sekcja, znajdująca się w prawym górnym rogu GUI, będzie centralnym punktem naszego zadania debugowania. Zawiera historię wszystkich tensorów zmiennoprzecinkowych typu dtype obliczonych wewnątrz wykresów (tj. skompilowanych przez
@tf-functions). - Struktura wykresu (dolna połowa górnej środkowej sekcji), kod źródłowy (dolna lewa sekcja) i stos śledzenia (dolna prawa sekcja) są początkowo puste. Ich zawartość zostanie zapełniona, gdy wejdziemy w interakcję z GUI. Te trzy sekcje będą również odgrywać ważną rolę w naszym zadaniu debugowania.
Po zorientowaniu się w organizacji interfejsu użytkownika, podejmijmy następujące kroki, aby dotrzeć do sedna, dlaczego pojawiły się NaN. Najpierw kliknij alert NaN/∞ w sekcji Alerty. Spowoduje to automatyczne przewinięcie listy 600 tensorów wykresów w sekcji Graph Execution i skupienie się na #88, który jest Log o nazwie Log:0 generowanym przez op. Wyraźny różowo-czerwony kolor podkreśla element -∞ wśród 1000 elementów tensora 2D float32. Jest to pierwszy tensor w historii środowiska wykonawczego programu TF2, który zawierał NaN lub nieskończoność: tensory obliczone wcześniej nie zawierały NaN ani ∞; wiele (w rzeczywistości większość) tensorów obliczonych później zawiera NaN. Możemy to potwierdzić, przewijając listę Graph Execution w górę iw dół. Ta obserwacja daje mocną wskazówkę, że Log op jest źródłem niestabilności numerycznej w tym programie TF2.

Dlaczego w tym Log pojawia się -∞? Odpowiedź na to pytanie wymaga zbadania wkładu do op. Kliknięcie na nazwę tensora ( Log:0 ) wywołuje prostą, ale pouczającą wizualizację otoczenia Log op na jego wykresie TensorFlow w sekcji Graph Structure. Zwróć uwagę na kierunek przepływu informacji od góry do dołu. Sam op jest pokazany pogrubioną czcionką pośrodku. Bezpośrednio nad nim widzimy, że operacja zastępcza dostarcza jedynego wejścia do operacji Log . Gdzie jest tensor generowany przez ten probs zastępczy na liście Graph Execution? Używając żółtego koloru tła jako pomocy wizualnej, możemy zobaczyć, że tensor probs:0 znajduje się trzy rzędy powyżej tensora Log:0 , czyli w rzędzie 85.

Dokładniejsze przyjrzenie się liczbowemu rozkładowi tensora probs:0 w wierszu 85 pokazuje, dlaczego jego konsument Log:0 daje -∞: Spośród 1000 elementów probs:0 , jeden element ma wartość 0. -∞ jest wynik obliczenia logarytmu naturalnego 0! Jeśli w jakiś sposób uda nam się zapewnić, że Log op zostanie wystawiony tylko na pozytywne dane wejściowe, będziemy w stanie zapobiec wystąpieniu NaN/∞. Można to osiągnąć, stosując przycinanie (np. za pomocą tf.clip_by_value() ) na probs probówek typu Placeholder.
Zbliżamy się do rozwiązania błędu, ale jeszcze nie do końca. Aby zastosować poprawkę, musimy wiedzieć, skąd w kodzie źródłowym Pythona pochodzi Log op i jego wejście zastępcze. Debugger V2 zapewnia pierwszorzędną obsługę śledzenia operacji grafu i zdarzeń wykonania do ich źródła. Kiedy kliknęliśmy tensor Log:0 w Graph Executions, sekcja Stack Trace została wypełniona oryginalnym śladem stosu podczas tworzenia 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ń debugowania. Ramką zainteresowania jest wiersz 216 z debug_mnist_v2.py (tj. plik Pythona, który faktycznie próbujemy debugować). Kliknięcie „Linia 216” powoduje wyświetlenie odpowiedniego wiersza kodu w sekcji Kod źródłowy.

To w końcu prowadzi nas do kodu źródłowego, który utworzył problematyczny Log op z jego wejścia probs . Jest to nasza niestandardowa funkcja straty kategorycznej entropii krzyżowej ozdobiona @tf.function , a zatem przekształcona w wykres TensorFlow. Placeholder op probs odpowiada pierwszemu argumentowi wejściowemu funkcji straty. Operacja Log jest tworzona za pomocą wywołania API tf.math.log().
Poprawka tego błędu polegająca na obcinaniu wartości będzie wyglądać mniej więcej tak:
diff = -(labels *
tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))
Rozwiąże to niestabilność numeryczną w tym programie TF2 i spowoduje pomyślne przeszkolenie MLP. Innym możliwym podejściem do naprawy niestabilności numerycznej jest użycie tf.keras.losses.CategoricalCrossentropy .
To kończy naszą podróż od zaobserwowania 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 ciekawą i graficzną historię wykonania oprzyrządowanego programu TF2, w tym podsumowania liczbowe wartości tensorów i powiązania między operacjami, tensorami i ich oryginalnym kodem źródłowym.
Kompatybilność sprzętowa Debugera V2
Debugger V2 obsługuje podstawowy sprzęt szkoleniowy, w tym procesor i GPU. Obsługiwane jest również szkolenie na wielu procesorach graficznych z tf.distributed.MirroredStrategy . Wsparcie dla TPU jest wciąż na wczesnym etapie i wymaga dzwonienia
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 Debuggera V2, zgłoś błędy na naszej stronie problemów w serwisie GitHub .
Kompatybilność API Debugera V2
Debugger V2 jest zaimplementowany na stosunkowo niskim poziomie stosu oprogramowania TensorFlow, a zatem jest kompatybilny z tf.keras , tf.data i innymi interfejsami API zbudowanymi na niższych poziomach TensorFlow. Debugger V2 jest również wstecznie kompatybilny z TF1, chociaż oś czasu wykonania Eager będzie pusta dla dzienników debugowania generowanych przez programy TF1.
Wskazówki dotyczące korzystania z interfejsu API
Często zadawane pytanie dotyczące tego debugowania API dotyczy tego, gdzie w kodzie TensorFlow należy wstawić wywołanie enable_dump_debug_info() . Zazwyczaj interfejs API powinien być wywoływany w programie TF2 możliwie jak najwcześniej, najlepiej po wierszach importu Pythona i przed rozpoczęciem tworzenia i wykonywania wykresów. Zapewni to pełne pokrycie wszystkich operacji i wykresów, które zasilają Twój model i jego szkolenie.
Obecnie 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 obciążeniem wydajnością debugowanego programu. Proszę zapoznać się z sekcją args w dokumentacji enable_dump_debug_info() .
Narzut na wydajność
Interfejs API debugowania wprowadza obciążenie wydajnościowe do oprzyrządowanego programu TensorFlow. Obciążenie zależy od tensor_debug_mode , typu sprzętu i charakteru oprzyrządowanego programu TensorFlow. Jako punkt odniesienia, na GPU, tryb NO_TENSOR dodaje 15% narzutu podczas uczenia modelu transformatora w rozmiarze partii 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 obciążenie jest nieco niższe. W przypadku TPU obciążenie jest obecnie wyższe.
Związek z innymi interfejsami API debugowania TensorFlow
Zwróć uwagę, że TensorFlow oferuje inne narzędzia i interfejsy API do debugowania. Takie interfejsy API można przeglądać w przestrzeni nazw tf.debugging.* na stronie dokumentacji interfejsu API. Wśród tych interfejsów API najczęściej używanym jest tf.print() . Kiedy należy użyć Debuggera V2, a kiedy zamiast tego użyć tf.print() ? tf.print() jest wygodne w przypadku, gdy
- dokładnie wiemy, które tensory wydrukować,
- wiemy, gdzie dokładnie w kodzie źródłowym wstawić te
tf.print(), - liczba takich tensorów nie jest zbyt duża.
W innych przypadkach (np. sprawdzanie wielu wartości tensorów, sprawdzanie wartości tensorów generowanych przez wewnętrzny kod TensorFlow i szukanie ź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 sprawdzania tensorów chętnych i grafów. Dodatkowo dostarcza informacji o strukturze wykresu i lokalizacji kodu, które są poza możliwościami tf.print() .
Innym interfejsem API, którego można użyć do debugowania problemów związanych z ∞ 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 monitoruje tylko ∞ i NaN podczas działania TensorFlow i wyświetla błędy z lokalizacją kodu źródłowego, gdy tylko jakakolwiek operacja wygeneruje tak złe wartości liczbowe. Ma niższy 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.
Katastrofalne zdarzenia związane z NaN mogą czasami wystąpić podczas programu TensorFlow, paraliżując procesy uczenia modelu. Przyczyny takich zdarzeń są często niejasne, zwłaszcza w przypadku modeli o nietrywialnych rozmiarach i złożoności. Aby ułatwić debugowanie tego typu błędów modelu, TensorBoard 2.3+ (wraz z TensorFlow 2.3+) zapewnia wyspecjalizowany pulpit nawigacyjny o nazwie Debugger V2. Tutaj pokazujemy, jak korzystać z tego narzędzia, pracując nad prawdziwym błędem dotyczącym NaN w sieci neuronowej napisanej w TensorFlow.
Techniki zilustrowane w tym samouczku mają zastosowanie do innych rodzajów czynności związanych z debugowaniem, takich jak sprawdzanie kształtów tensora środowiska wykonawczego w złożonych programach. Ten samouczek koncentruje się na sieciach NaN ze względu na ich stosunkowo wysoką częstotliwość występowania.
Obserwując błąd
Kod źródłowy programu TF2, który będziemy debugować, jest dostępny na GitHub . Przykładowy program jest również spakowany 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 . Ten przykład celowo wykorzystuje niskopoziomowe API TF2 do definiowania niestandardowych konstrukcji warstw, funkcji straty i pętli treningowej, ponieważ prawdopodobieństwo błędów NaN jest wyższe, gdy używamy tego bardziej elastycznego, ale bardziej podatnego na błędy API, niż gdy używamy łatwiejszego -to-use, 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 bliskim szansy (~0,1) po pierwszym kroku. Z pewnością nie tak ma się zachowywać uczenie modelu: 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
...
Przypuszcza się, że problem ten jest spowodowany niestabilnością numeryczną, taką jak NaN lub nieskończoność. Jak jednak możemy potwierdzić, że tak jest naprawdę i jak znaleźć operację TensorFlow (op) odpowiedzialną za generowanie niestabilności numerycznej? Aby odpowiedzieć na te pytania, oprzyjmy program z błędami za pomocą Debuggera V2.
Oprzyrządowanie kodu TensorFlow za pomocą debugera 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ącej linii na 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ę szybkiego wykonania, budowanie grafów wykonywane przez @tf.function , wykonanie grafó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 niejasnych błędów.
tf.debugging.experimental.enable_dump_debug_info(
"/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 tensora ang. eager lub in-graph tensor. „FULL_HEALTH” to tryb, który przechwytuje następujące informacje o każdym tensorze typu zmiennoprzecinkowego (np. powszechnie widziany typ float32 i mniej popularny typ dfloat16 ):
- 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ść (+∞) iNaN.
Tryb „FULL_HEALTH” jest odpowiedni do debugowania błędów związanych z NaN i nieskończonością. Zobacz poniżej inne obsługiwane tensor_debug_mode .
Argument circular_buffer_size kontroluje liczbę zdarzeń tensora zapisywanych w logdir. Wartość domyślna to 1000, co powoduje zapisanie na dysku tylko ostatnich 1000 tensorów przed końcem oprzyrządowanego programu TF2. To domyślne zachowanie zmniejsza obciążenie debugera, poświęcając kompletność danych debugowania. Jeśli preferowana jest kompletność, jak w tym przypadku, możemy wyłączyć bufor cykliczny, ustawiając argument na wartość ujemną (np. tutaj -1).
Przykład debug_mnist_v2 wywołuje enable_dump_debug_info() przez przekazanie do niej flag wiersza poleceń. Aby ponownie uruchomić nasz problematyczny program TF2 z włączonym instrumentem debugowania, wykonaj następujące czynności:
python -m tensorflow.python.debug.examples.v2.debug_mnist_v2 \
--dump_dir /tmp/tfdbg2_logdir --dump_tensor_debug_mode FULL_HEALTH
Uruchamianie interfejsu graficznego Debugger V2 w TensorBoard
Uruchomienie programu z instrumentacją debugera tworzy katalog dziennika w /tmp/tfdbg2_logdir. Możemy uruchomić TensorBoard i skierować go na logdir za pomocą:
tensorboard --logdir /tmp/tfdbg2_logdir
W przeglądarce internetowej przejdź do strony TensorBoard pod adresem http://localhost:6006. Wtyczka „Debugger V2” będzie domyślnie nieaktywna, więc wybierz ją z menu „Nieaktywne wtyczki” w prawym górnym rogu. Po wybraniu powinien wyglądać następująco:

Korzystanie z graficznego interfejsu użytkownika Debugger V2 w celu znalezienia głównej przyczyny NaN
Interfejs graficzny Debugger V2 w TensorBoard jest podzielony na sześć sekcji:
- Alerty : ta lewa górna sekcja 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 zasługuje na uwagę. W naszym przypadku ta sekcja wyróżnia 499 zdarzeń 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 wartościach tensora wewnętrznego. 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 pole na osi czasu jest oznaczone początkową literą nazwy operacji lub wykresu (np. „T” dla operacji „TensorSliceDataset”, „m” dla „modelu”
tf.function). Możemy poruszać się po tej osi czasu za pomocą przycisków nawigacyjnych i paska przewijania nad osią czasu. - Wykonywanie wykresu : Ta sekcja, znajdująca się w prawym górnym rogu GUI, będzie centralnym punktem naszego zadania debugowania. Zawiera historię wszystkich tensorów zmiennoprzecinkowych typu dtype obliczonych wewnątrz wykresów (tj. skompilowanych przez
@tf-functions). - Struktura wykresu (dolna połowa górnej środkowej sekcji), kod źródłowy (dolna lewa sekcja) i stos śledzenia (dolna prawa sekcja) są początkowo puste. Ich zawartość zostanie zapełniona, gdy wejdziemy w interakcję z GUI. Te trzy sekcje będą również odgrywać ważną rolę w naszym zadaniu debugowania.
Po zorientowaniu się w organizacji interfejsu użytkownika, podejmijmy następujące kroki, aby dotrzeć do sedna, dlaczego pojawiły się NaN. Najpierw kliknij alert NaN/∞ w sekcji Alerty. Spowoduje to automatyczne przewinięcie listy 600 tensorów wykresów w sekcji Graph Execution i skupienie się na #88, który jest Log o nazwie Log:0 generowanym przez op. Wyraźny różowo-czerwony kolor podkreśla element -∞ wśród 1000 elementów tensora 2D float32. Jest to pierwszy tensor w historii środowiska wykonawczego programu TF2, który zawierał NaN lub nieskończoność: tensory obliczone wcześniej nie zawierały NaN ani ∞; wiele (w rzeczywistości większość) tensorów obliczonych później zawiera NaN. Możemy to potwierdzić, przewijając listę Graph Execution w górę iw dół. Ta obserwacja daje mocną wskazówkę, że Log op jest źródłem niestabilności numerycznej w tym programie TF2.

Dlaczego w tym Log pojawia się -∞? Odpowiedź na to pytanie wymaga zbadania wkładu do op. Kliknięcie na nazwę tensora ( Log:0 ) wywołuje prostą, ale pouczającą wizualizację otoczenia Log op na jego wykresie TensorFlow w sekcji Graph Structure. Zwróć uwagę na kierunek przepływu informacji od góry do dołu. Sam op jest pokazany pogrubioną czcionką pośrodku. Bezpośrednio nad nim widzimy, że operacja zastępcza dostarcza jedynego wejścia do operacji Log . Gdzie jest tensor generowany przez ten probs zastępczy na liście Graph Execution? Używając żółtego koloru tła jako pomocy wizualnej, możemy zobaczyć, że tensor probs:0 znajduje się trzy rzędy powyżej tensora Log:0 , czyli w rzędzie 85.

Dokładniejsze przyjrzenie się liczbowemu rozkładowi tensora probs:0 w wierszu 85 pokazuje, dlaczego jego konsument Log:0 daje -∞: Spośród 1000 elementów probs:0 , jeden element ma wartość 0. -∞ jest wynik obliczenia logarytmu naturalnego 0! Jeśli w jakiś sposób uda nam się zapewnić, że Log op zostanie wystawiony tylko na pozytywne dane wejściowe, będziemy w stanie zapobiec wystąpieniu NaN/∞. Można to osiągnąć, stosując przycinanie (np. za pomocą tf.clip_by_value() ) na probs probówek typu Placeholder.
Zbliżamy się do rozwiązania błędu, ale jeszcze nie do końca. Aby zastosować poprawkę, musimy wiedzieć, skąd w kodzie źródłowym Pythona pochodzi Log op i jego wejście zastępcze. Debugger V2 zapewnia pierwszorzędną obsługę śledzenia operacji grafu i zdarzeń wykonania do ich źródła. Kiedy kliknęliśmy tensor Log:0 w Graph Executions, sekcja Stack Trace została wypełniona oryginalnym śladem stosu podczas tworzenia 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ń debugowania. Ramką zainteresowania jest wiersz 216 z debug_mnist_v2.py (tj. plik Pythona, który faktycznie próbujemy debugować). Kliknięcie „Linia 216” powoduje wyświetlenie odpowiedniego wiersza kodu w sekcji Kod źródłowy.

To w końcu prowadzi nas do kodu źródłowego, który utworzył problematyczny Log op z jego wejścia probs . Jest to nasza niestandardowa funkcja straty kategorycznej entropii krzyżowej ozdobiona @tf.function , a zatem przekształcona w wykres TensorFlow. Placeholder op probs odpowiada pierwszemu argumentowi wejściowemu funkcji straty. Operacja Log jest tworzona za pomocą wywołania API tf.math.log().
Poprawka tego błędu polegająca na obcinaniu wartości będzie wyglądać mniej więcej tak:
diff = -(labels *
tf.math.log(tf.clip_by_value(probs), 1e-6, 1.))
Rozwiąże to niestabilność numeryczną w tym programie TF2 i spowoduje pomyślne przeszkolenie MLP. Innym możliwym podejściem do naprawy niestabilności numerycznej jest użycie tf.keras.losses.CategoricalCrossentropy .
To kończy naszą podróż od zaobserwowania 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 ciekawą i graficzną historię wykonania oprzyrządowanego programu TF2, w tym podsumowania liczbowe wartości tensorów i powiązania między operacjami, tensorami i ich oryginalnym kodem źródłowym.
Kompatybilność sprzętowa Debugera V2
Debugger V2 obsługuje podstawowy sprzęt szkoleniowy, w tym procesor i GPU. Obsługiwane jest również szkolenie na wielu procesorach graficznych z tf.distributed.MirroredStrategy . Wsparcie dla TPU jest wciąż na wczesnym etapie i wymaga dzwonienia
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 Debuggera V2, zgłoś błędy na naszej stronie problemów w serwisie GitHub .
Kompatybilność API Debugera V2
Debugger V2 jest zaimplementowany na stosunkowo niskim poziomie stosu oprogramowania TensorFlow, a zatem jest kompatybilny z tf.keras , tf.data i innymi interfejsami API zbudowanymi na niższych poziomach TensorFlow. Debugger V2 jest również wstecznie kompatybilny z TF1, chociaż oś czasu wykonania Eager będzie pusta dla dzienników debugowania generowanych przez programy TF1.
Wskazówki dotyczące korzystania z interfejsu API
Często zadawane pytanie dotyczące tego debugowania API dotyczy tego, gdzie w kodzie TensorFlow należy wstawić wywołanie enable_dump_debug_info() . Zazwyczaj interfejs API powinien być wywoływany w programie TF2 możliwie jak najwcześniej, najlepiej po wierszach importu Pythona i przed rozpoczęciem tworzenia i wykonywania wykresów. Zapewni to pełne pokrycie wszystkich operacji i wykresów, które zasilają Twój model i jego szkolenie.
Obecnie 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 obciążeniem wydajnością debugowanego programu. Proszę zapoznać się z sekcją args w dokumentacji enable_dump_debug_info() .
Narzut na wydajność
Interfejs API debugowania wprowadza obciążenie wydajnościowe do oprzyrządowanego programu TensorFlow. Obciążenie zależy od tensor_debug_mode , typu sprzętu i charakteru oprzyrządowanego programu TensorFlow. Jako punkt odniesienia, na GPU, tryb NO_TENSOR dodaje 15% narzutu podczas uczenia modelu transformatora w rozmiarze partii 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 obciążenie jest nieco niższe. W przypadku TPU obciążenie jest obecnie wyższe.
Związek z innymi interfejsami API debugowania TensorFlow
Zwróć uwagę, że TensorFlow oferuje inne narzędzia i interfejsy API do debugowania. Takie interfejsy API można przeglądać w przestrzeni nazw tf.debugging.* na stronie dokumentacji interfejsu API. Wśród tych interfejsów API najczęściej używanym jest tf.print() . Kiedy należy użyć Debuggera V2, a kiedy zamiast tego użyć tf.print() ? tf.print() jest wygodne w przypadku, gdy
- dokładnie wiemy, które tensory wydrukować,
- wiemy, gdzie dokładnie w kodzie źródłowym wstawić te
tf.print(), - liczba takich tensorów nie jest zbyt duża.
W innych przypadkach (np. sprawdzanie wielu wartości tensorów, sprawdzanie wartości tensorów generowanych przez wewnętrzny kod TensorFlow i szukanie ź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 sprawdzania tensorów chętnych i grafów. Dodatkowo dostarcza informacji o strukturze wykresu i lokalizacji kodu, które są poza możliwościami tf.print() .
Innym interfejsem API, którego można użyć do debugowania problemów związanych z ∞ 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 monitoruje tylko ∞ i NaN podczas działania TensorFlow i wyświetla błędy z lokalizacją kodu źródłowego, gdy tylko jakakolwiek operacja wygeneruje tak złe wartości liczbowe. Ma niższy 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.