Wróć do katalogu
Season 55 17 Odcinki 1h 6m 2026

PyTorch Fundamentals

Wersja 2.11 — Edycja 2026. Kompleksowy kurs audio na temat budowania modeli deep learning przy użyciu PyTorch w wersji 2.11. Obejmuje Tensors, Autograd, Neural Networks, Optimizers, DataLoaders oraz kompilator PyTorch.

Frameworki AI/ML Python Core Nauka o danych
PyTorch Fundamentals
Teraz odtwarzane
Click play to start
0:00
0:00
1
Istota PyTorch
Odkryj podstawowy cel PyTorch i to, co odróżnia go od tradycyjnych bibliotek matematycznych. Ten odcinek wyjaśnia rolę Tensors, Autograd i akceleracji GPU w nowoczesnym deep learningu.
3m 49s
2
Zrozumienie Tensors w PyTorch
Zanurz się w Tensors, podstawową strukturę danych w PyTorch. Dowiedz się, jak łączą one surowe dane z Neural Networks i płynnie współdzielą pamięć z tablicami Numpy.
4m 10s
3
Operacje na Tensors i pamięć
Dowiedz się, jak wydajnie manipulować Tensors. Ten odcinek obejmuje operacje arytmetyczne, konkatenację, transfery między urządzeniami oraz wpływ operacji in-place na pamięć.
3m 49s
4
Magia Autograd
Poznaj silnik, który umożliwia deep learning w PyTorch. Dowiedz się, jak Autograd dynamicznie śledzi operacje i automatycznie oblicza złożone pochodne.
4m 00s
5
Kontrolowanie śledzenia gradientów
Odkryj, jak wyłączyć śledzenie gradientów w PyTorch, aby zaoszczędzić pamięć i przyspieszyć obliczenia. Jest to kluczowe przy uruchamianiu inferencji i zamrażaniu parametrów modelu.
3m 39s
6
Datasets i przetwarzanie danych
Dowiedz się, jak oddzielić przetwarzanie danych od architektury modelu za pomocą klasy Dataset w PyTorch. Omawiamy lazy loading i niestandardowe struktury zbiorów danych.
3m 53s
7
DataLoaders i batching
Uwolnij pełną prędkość swojego sprzętu, opakowując Datasets w DataLoaders. Dowiedz się, jak grupować w batche, tasować i wieloprocesowo przetwarzać strumienie danych.
3m 39s
8
Transformacje danych
Odkryj, jak wstępnie przetwarzać surowe dane w locie, zanim trafią do Twojej Neural Network. Omawiamy transformacje z biblioteki torchvision, takie jak ToTensor, oraz niestandardowe funkcje Lambda.
4m 14s
9
Projektowanie sieci z nn.Module
Poznaj strukturalny schemat każdej Neural Network w PyTorch. Dowiedz się, jak tworzyć podklasy nn.Module, definiować warstwy podczas inicjalizacji i kierować danymi w forward pass.
3m 59s
10
Warstwy Linear i aktywacje
Zajrzyj do wnętrza Neural Network. Rozkładamy na czynniki pierwsze moduł nn.Linear i wyjaśniamy, dlaczego nieliniowe funkcje aktywacji, takie jak ReLU, są matematycznie niezbędne.
3m 56s
11
Kontener nn.Sequential
Usprawnij swój kod PyTorch za pomocą kontenera nn.Sequential. Dowiedz się, jak czysto łączyć ze sobą warstwy i badać parametry swojego modelu.
3m 50s
12
Zrozumienie Loss Functions
Zanim AI będzie mogło się uczyć, musi zmierzyć swoje błędy. Zagłębiamy się w Loss Functions w PyTorch, porównując CrossEntropyLoss dla klasyfikacji i MSELoss dla regresji.
3m 53s
13
Optimizers i spadek gradientu
Sprawdź, jak optimizer aktualizuje wagi modelu, aby zmniejszyć błąd. Poznaj kluczowy, trzyetapowy taniec funkcji zero_grad(), backward() i step().
3m 56s
15
Walidacja i inferencja
Oceniaj swój model obiektywnie. Dowiedz się, jak przełączyć sieć w tryb ewaluacji, zamrozić gradienty i uzyskać dokładne predykcje na niewidzianych wcześniej danych.
3m 35s
16
Zapisywanie i ładowanie modeli
Nie trać ciężko wypracowanych postępów! Omawiamy najbezpieczniejsze sposoby serializacji wag modelu za pomocą state_dict i ich bezpiecznego ponownego ładowania.
3m 38s
17
Zwiększanie prędkości z torch.compile
Odblokuj kluczową funkcję PyTorch 2.0. Dowiedz się, jak dekorator torch.compile wykonuje JIT-compiles Twojego kodu Python do zoptymalizowanych kernels, zapewniając ogromne przyspieszenie.
3m 49s
18
Kompilatory i Graph Breaks
Zajrzyj pod maskę kompilatora PyTorch. Omawiamy Graph Breaks, dynamiczny przepływ sterowania i powody, dla których torch.compile odnosi sukces tam, gdzie starsze systemy zawiodły.
4m 15s

Odcinki

1

Istota PyTorch

3m 49s

Odkryj podstawowy cel PyTorch i to, co odróżnia go od tradycyjnych bibliotek matematycznych. Ten odcinek wyjaśnia rolę Tensors, Autograd i akceleracji GPU w nowoczesnym deep learningu.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 1 z 18. Piszesz złożony model matematyczny w Pythonie, ale kiedy go skalujesz, całkowicie dławi twój procesor. Musisz odpalić te obliczenia na sprzęcie równoległym i na bieżąco liczyć wszystkie pochodne, ale przepisanie wszystkiego w języku niskiego poziomu zajęłoby tygodnie. Ten problem rozwiązuje sama natura PyTorcha. Kiedy pierwszy raz patrzysz na PyTorcha, często masz wrażenie, że to po prostu NumPy. Tworzysz arraye, mnożysz macierze i manipulujesz liczbami. To wizualne podobieństwo na początku bywa bardzo mylące. Ludzie zakładają, że PyTorch to po prostu kolejna standardowa biblioteka matematyczna. Wcale tak nie jest. O ile standardowe biblioteki matematyczne są tworzone pod obliczenia numeryczne typu CPU-bound, PyTorch został zaprojektowany od podstaw tak, aby wykorzystać sprzęt równoległy i budować dynamiczne grafy obliczeniowe. Podstawowym budulcem tego frameworka jest tensor. Tensor to w zasadzie wielowymiarowy array. Jeśli masz siatkę liczb reprezentującą obraz, falę dźwiękową albo blok tekstu, przechowujesz ją w tensorze. Kluczowa różnica między standardowym arrayem a tensorem w PyTorch polega na tym, gdzie te dane mogą żyć i się wykonywać. Tensory mogą płynnie przenosić się z pamięci systemowej twojego komputera do GPU. Weźmy na warsztat potężne mnożenie macierzy. Masz dwie siatki zawierające miliony liczb. Jeśli każesz standardowemu CPU je pomnożyć, przetworzy tę matematykę sekwencyjnie albo w bardzo małych batchach. Proces dławi się i zacina. Ponieważ tensory są specjalnie zaprojektowane pod akcelerację sprzętową, możesz wysłać dokładnie te same dane do GPU. GPU zawiera tysiące małych rdzeni zaprojektowanych do jednoczesnego wykonywania operacji matematycznych. Potężne obliczenia, które na CPU zajmują minuty, na GPU kończą się błyskawicznie. PyTorch działa tu jak most, tłumacząc twój standardowy kod w Pythonie na instrukcje dla tego równoległego sprzętu. Szybki sprzęt to tylko połowa sukcesu w machine learningu. Trenowanie sieci neuronowej wymaga ciągłego liczenia pochodnych. Musisz dokładnie wiedzieć, jak podkręcenie jednej zmiennej wpływa na twój ostateczny output, co oznacza ciągłe liczenie gradientów. Robienie tego ręcznie dla modelu z miliardami parametrów jest po prostu niemożliwe. I tu dochodzimy do drugiego filaru PyTorcha, czyli Autograd. Autograd to silnik do automatycznego różniczkowania. Kiedy wykonujesz operacje matematyczne na tensorach, PyTorch nie tylko oblicza ostateczny wynik. Po cichu buduje w tle pewną mapę. Zapisuje każde dodawanie, mnożenie i transformację danych w dynamicznym grafie obliczeniowym. Kiedy docierasz do końca swoich obliczeń, po prostu prosisz framework o policzenie gradientów. PyTorch idzie wstecz przez ten niewidzialny graf, automatycznie stosując regułę łańcuchową. Dostajesz dokładne pochodne dla każdego pojedynczego parametru w twoim modelu, bez pisania jakiegokolwiek kodu do różniczkowania. Ponieważ ten graf jest budowany dynamicznie w locie, dostosowuje się do twojego kodu. Jeśli standardowy loop w Pythonie albo if-statement zmieni przepływ twoich danych, graf natychmiast się dostosuje. Prawdziwa moc PyTorcha nie polega tylko na tym, że działa szybko albo liczy pochodne. Daje ci prędkość wykonywania superkomputera i matematyczną precyzję zautomatyzowanego silnika do różniczkowania, a to wszystko całkowicie ukryte za czytelnym, zwykłym Pythonem. Jeśli chcesz pomóc w tworzeniu kolejnych odcinków, możesz wyszukać DevStoriesEU na Patreon i wesprzeć nasz program. To wszystko w tym odcinku. Dzięki za słuchanie i buduj dalej!
2

Zrozumienie Tensors w PyTorch

4m 10s

Zanurz się w Tensors, podstawową strukturę danych w PyTorch. Dowiedz się, jak łączą one surowe dane z Neural Networks i płynnie współdzielą pamięć z tablicami Numpy.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 2 z 18. Każdy obraz, fala dźwiękowa i dokument tekstowy, który przekazujesz do sieci neuronowej, ostatecznie zamienia się w dokładnie tę samą strukturę danych. Skoro to wszystko jest tylko siatką liczb, możesz się zastanawiać, po co nam wyspecjalizowany obiekt, zamiast polegać na standardowych tablicach. Odpowiedź kryje się w zrozumieniu tensorów w PyTorchu. Tensor to wyspecjalizowana struktura danych, która wygląda i zachowuje się bardzo podobnie do tablicy lub macierzy. W PyTorchu tensory są uniwersalną walutą. Przechowują twoje surowe inputy, outputy generowane przez model oraz wewnętrzne parametry samej sieci neuronowej. Ludzie często zakładają, że tensory są całkowicie identyczne z tablicami NumPy. Rzeczywiście wyglądają podobnie i mają wiele wspólnych zachowań. Kluczową różnicą jest to, jakie możliwości odblokowują tensory. Podczas gdy standardowa tablica siedzi w głównej pamięci systemu i działa na CPU, tensor jest zbudowany tak, aby łatwo przenieść go na GPU, w celu uzyskania potężnej akceleracji sprzętowej. Tensory mają też wbudowane mechanizmy wymagane do śledzenia gradientów, co pozwala sieciom neuronowym na uczenie się. Możesz zainicjować tensor na kilka sposobów. Najbardziej bezpośrednią drogą jest przekazanie surowych danych, takich jak standardowa lista w Pythonie, prosto do konstruktora tensora. Możesz też utworzyć nowy tensor na podstawie już istniejącego. Kiedy to zrobisz, nowy tensor automatycznie dziedziczy właściwości oryginału, co oznacza, że będzie miał te same wymiary i typ danych, chyba że jawnie je nadpiszesz. Alternatywnie, jeśli potrzebujesz tylko placeholdera, możesz zdefiniować shape, czyli prostą kolekcję liczb reprezentujących wymiary, których potrzebujesz, i poprosić PyTorch o wygenerowanie tensora wypełnionego losowymi liczbami, samymi jedynkami lub samymi zerami na podstawie tego shape'u. Kiedy już masz tensor, często będziesz sprawdzać jego trzy główne atrybuty. Pierwszy to shape, który mówi ci o dokładnym rozmiarze tensora w każdym wymiarze. Drugi to typ danych, który wskazuje rodzaj przechowywanych w nim liczb, takich jak 32-bitowe floaty lub integery. Trzeci to atrybut device. Mówi ci on, gdzie fizycznie znajduje się teraz tensor – czy to na CPU, czy na konkretnym GPU. Musisz tego pilnować, ponieważ PyTorch wymaga, aby tensory znajdowały się na tym samym device, zanim będą mogły wejść ze sobą w interakcję. Tensory i standardowe tablice często muszą ze sobą współpracować, co prowadzi nas do tak zwanego NumPy bridge. Tensory znajdujące się na CPU mogą w rzeczywistości współdzielić swoją pamięć z tablicą NumPy. Załóżmy, że ładujesz zdjęcie w wysokiej rozdzielczości za pomocą standardowej biblioteki do przetwarzania obrazów w Pythonie. Ten obraz ładuje się do pamięci twojego systemu jako standardowa tablica NumPy. Możesz przekazać tę tablicę do PyTorcha za pomocą dedykowanej funkcji, która tworzy tensor z NumPy. PyTorch nie duplikuje bazowych danych pikseli do nowego bloku pamięci. Po prostu opakowuje istniejący adres pamięci swoim własnym interfejsem tensora. Zmiana wartości w tensorze natychmiast zmienia wartość w tablicy NumPy i odwrotnie. Ta konwersja typu zero-copy oszczędza zarówno pamięć, jak i czas przetwarzania. Kiedy skończysz przepuszczać dane przez swój model i musisz przekazać wyniki z powrotem do standardowego narzędzia do wizualizacji, wywołujesz jedną metodę na tensorze, aby wystawić go z powrotem jako tablicę NumPy, korzystając z dokładnie tej samej współdzielonej pamięci. Prawdziwa moc tensora to nie tylko przechowywanie siatki liczb, ale też przenoszenie specyficznego kontekstu sprzętowego i struktury pamięci, potrzebnych do bezproblemowego przepchnięcia surowych danych przez sieć neuronową. Dzięki za wysłuchanie, miłego kodowania wszystkim!
3

Operacje na Tensors i pamięć

3m 49s

Dowiedz się, jak wydajnie manipulować Tensors. Ten odcinek obejmuje operacje arytmetyczne, konkatenację, transfery między urządzeniami oraz wpływ operacji in-place na pamięć.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 3 z 18. Pojedynczy znak podkreślenia w twoim kodzie może zaoszczędzić gigabajty pamięci, ale może też po cichu zepsuć całą twoją sieć neuronową. Wiedza o tym, kiedy użyć tego podkreślenia, sprowadza się do zrozumienia operacji na tensorach i pamięci. Wyobraź sobie praktyczny scenariusz. Masz trzy oddzielne wektory cech reprezentujące dane tekstowe, audio i obrazowe. Chcesz je połączyć i pomnożyć przez macierz wag. Domyślnie PyTorch tworzy tensory na CPU. Ale do ciężkich obliczeń na macierzach chcesz użyć akceleratora sprzętowego. Możesz sprawdzić, czy GPU jest dostępne, używając wbudowanych mechanizmów frameworka. Jeśli tak, przenosisz swoje tensory, wywołując na nich metodę to. Przekazujesz do tej metody nazwę docelowego urządzenia, na przykład string cuda. Wtedy PyTorch kopiuje tensor z systemowego RAM-u do dedykowanej pamięci twojej karty graficznej. Mając tensory na odpowiednim sprzęcie, musisz połączyć te trzy oddzielne wektory cech w jeden. Zrobisz to, używając funkcji concatenate, często zapisywanej jako cat. Przekazujesz jej listę swoich tensorów i określasz wymiar. Jeśli połączysz je wzdłuż wymiaru kolumn, twoje trzy wąskie tensory zostaną złączone obok siebie, tworząc jeden szerszy tensor. Masz teraz zunifikowany input, który znajduje się w pamięci GPU. PyTorch obsługuje ponad sto różnych operacji, ale podstawą jest arytmetyka. Aby przetworzyć twój połączony wektor cech, musisz go pomnożyć przez macierz wag. Możesz użyć metody matmul albo po prostu symbolu at jako wygodnego skrótu. Wykonuje to prawdziwe matematyczne mnożenie macierzy, obliczając iloczyny skalarne wierszy i kolumn, i zwraca zupełnie nowy tensor zawierający wyniki. Czasami zamiast tego potrzebujesz operacji element-wise. Załóżmy, że chcesz nałożyć binarną maskę na swój tensor, wymuszając wyzerowanie niektórych wartości. W tym celu używasz metody mul albo standardowego operatora gwiazdki. To nie wykonuje mnożenia macierzy. Po prostu mnoży pierwszy element tensora A przez pierwszy element tensora B, drugi przez drugi i tak dalej. Za każdym razem, gdy wykonujesz operacje takie jak mnożenie macierzy czy dodawanie element-wise, PyTorch alokuje nową pamięć na wynik. Kiedy operujesz na milionach parametrów, to błyskawicznie zużywa dostępną pamięć twojego sprzętu. I tutaj musisz uważać. PyTorch udostępnia operacje in-place, żeby zarządzać narzutem pamięci. Każda operacja zakończona podkreśleniem działa in-place. Jeśli użyjesz standardowej metody add, dostajesz nowy tensor. Jeśli użyjesz metody add z podkreśleniem, PyTorch bezpośrednio nadpisuje wartości wewnątrz istniejącego tensora. Zużycie pamięci pozostaje dokładnie takie samo. Choć operacje in-place są bardzo wydajne pamięciowo, są też niebezpieczne. Kiedy nadpisujesz tensor, kasujesz jego poprzednie wartości. Sieci neuronowe polegają na pełnym zapisie poprzednich stanów, żeby obliczać pochodne podczas fazy uczenia. Jeśli nadpiszesz tensor używając operacji in-place, niszczysz historię obliczeń, której system potrzebuje do zaktualizowania modelu. Zarezerwuj operacje in-place do formatowania danych, zanim trafią do twojego modelu, i trzymaj się standardowych operacji podczas treningu, żeby zachować nienaruszoną historię obliczeń. To wszystko w tym odcinku. Dzięki za wysłuchanie i twórz dalej!
4

Magia Autograd

4m 00s

Poznaj silnik, który umożliwia deep learning w PyTorch. Dowiedz się, jak Autograd dynamicznie śledzi operacje i automatycznie oblicza złożone pochodne.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 4 z 18. Trenowanie sieci neuronowej oznacza obliczanie pochodnej błędu względem milionów parametrów. Robienie tego ręcznie wymagałoby zapisania wielu stron obliczeń i ciągłego przepisywania kodu przy każdej zmianie architektury modelu. PyTorch rozwiązuje ten problem, śledząc twoje obliczenia w tle. Ta koncepcja jest znana jako magia autogradu. Autograd to wbudowany w PyTorcha silnik do różniczkowania. Automatycznie oblicza gradienty dla dowolnego grafu obliczeniowego. Żeby zobaczyć, jak to działa, wyobraź sobie standardową transformację liniową. Masz tensor wejściowy zawierający twoje dane, macierz wag i wektor biasu. Celem jest obliczenie outputu, porównanie go z rzeczywistą wartością docelową i obliczenie błędu, czyli loss. Twoje dane wejściowe są stałe, więc nie potrzebujesz ich pochodnych. Jednak tensory wag i biasu będą później aktualizowane, co oznacza, że absolutnie potrzebujesz ich gradientów. Sygnalizujesz to PyTorchowi, ustawiając flagę requires grad na true podczas tworzenia tych tensorów parametrów. To mówi silnikowi autograd, żeby zaczął je śledzić. Kiedy wykonujesz operacje na tych śledzonych tensorach — mnożąc input przez wagi, dodając bias i obliczając końcowy loss — PyTorch robi dwie rzeczy naraz. Oblicza rzeczywisty wynik liczbowy i jednocześnie konstruuje skierowany graf acykliczny, czyli DAG. W tym grafie twoje początkowe tensory to liście, a zastosowane operacje matematyczne to korzenie. Każdy nowy tensor utworzony przez operację ma atrybut, który przechowuje referencję do funkcji, która go utworzyła. To mówi silnikowi autograd dokładnie, jak obliczyć pochodną dla tego konkretnego matematycznego kroku. Ten graf nie jest statyczną strukturą definiowaną na początku twojego skryptu. PyTorch buduje DAG dynamicznie, od zera, podczas każdej pojedynczej iteracji. Kiedy uruchamiasz forward pass, zupełnie nowy graf jest konstruowany w locie. To dynamiczne wykonywanie oznacza, że twoja sieć może zmieniać swoje zachowanie w każdym kroku. Możesz używać standardowego control flow z Pythona, jak instrukcje if czy pętle, a silnik bez problemu prześledzi ścieżkę, którą dane faktycznie pokonały podczas tego konkretnego uruchomienia. Kiedy twój forward pass wygeneruje końcowy tensor loss, uruchamiasz obliczanie gradientów, wywołując metodę backward na tym lossie. Autograd natychmiast przechodzi przez graf w odwrotnym kierunku. Używa reguły łańcuchowej do obliczenia pochodnych loss względem każdego tensora, który ma flagę requires grad ustawioną na true. Następnie bierze te obliczone wartości i zapisuje je w atrybucie grad twoich tensorów wag i biasu. Cały ten skomplikowany rachunek różniczkowy jest całkowicie wyabstrahowany. Czasami chcesz tylko przepuścić dane przez model bez obliczania gradientów, na przykład podczas ewaluacji wytrenowanego modelu. Śledzenie historii wymaga dodatkowej pamięci i mocy obliczeniowej. Możesz całkowicie powstrzymać autograd przed budowaniem grafu, umieszczając swój blok kodu w context managerze torch no grad. To tymczasowo zatrzymuje śledzenie i wykonuje obliczenia znacznie szybciej. Prawdziwą mocą autogradu jest to, że zamienia dowolny kod w Pythonie we w pełni różniczkowalną strukturę matematyczną, a ty nie musisz w ogóle ręcznie pisać wzorów na pochodne. Dzięki za wysłuchanie. Trzymajcie się wszyscy.
5

Kontrolowanie śledzenia gradientów

3m 39s

Odkryj, jak wyłączyć śledzenie gradientów w PyTorch, aby zaoszczędzić pamięć i przyspieszyć obliczenia. Jest to kluczowe przy uruchamianiu inferencji i zamrażaniu parametrów modelu.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 5 z 18. Jeśli uruchomisz nowo wytrenowany model na dużym batchu obrazów testowych bez zmiany jednego konkretnego ustawienia, twoja aplikacja w końcu się wysypie przez błąd out-of-memory. Mimo że robisz tylko predykcje, model potajemnie rezerwuje pamięć, żeby zapamiętać każdą wykonaną operację matematyczną, aż system padnie. Kontrolowanie gradient tracking to sposób, żeby temu zapobiec. Domyślnie tensory w PyTorchu są stworzone do uczenia. Jeśli tensor ma ustawione wymaganie gradientu na true, PyTorch śledzi każdą wykonaną na nim operację. Buduje w tle computation graph, łącząc inputy, wagi i outputy, żeby móc później obliczyć gradienty podczas backpropagation. Ten silnik śledzący jest świetny do trenowania, ale generuje spory overhead. Po zakończeniu trenowania twoje priorytety się zmieniają. Załóżmy, że właśnie skończyłeś trenować klasyfikator obrazów i musisz odpalić predykcje na milionie nowych obrazków. Nie musisz już aktualizować wag modelu. Potrzebujesz tylko forward pass. Jeśli zostawisz ten mechanizm śledzący włączony, PyTorch zbuduje bezużyteczny, ogromny computation graph dla tego miliona obrazów, pożerając twój RAM i spowalniając cykle obliczeniowe. Żeby temu zapobiec, masz do dyspozycji dwa główne narzędzia. Pierwsze to context manager o nazwie torch dot no grad. Używasz go do owrapowania całych bloków kodu. Kiedy umieścisz swój forward pass w bloku no grad, mówisz PyTorchowi, żeby tymczasowo wyłączył silnik śledzący. Żadne operacje wykonane wewnątrz tego bloku nie zostaną zarejestrowane. Nawet jeśli tensory wejściowe są normalnie śledzone, outputy utworzone wewnątrz bloku będą miały ustawione wymaganie gradientu na false. To twoje narzędzie do odpalania ewaluacji, testowania albo masowych predykcji. Wyłącza graf dla wszystkiego, co znajduje się w jego scope. Drugie narzędzie to metoda detach. Podczas gdy no grad ogarnia bloki kodu, detach działa na pojedynczych tensorach. Wywołanie detach na tensorze zwraca nowy tensor, który współdzieli dokładnie te same dane co oryginał, ale jest całkowicie odłączony od computation graph. Nie ma żadnej historii. Ludzie często mylą, kiedy użyć którego z nich. Używaj context managera torch dot no grad, kiedy chcesz wyciszyć śledzenie dla sekwencji operacji, na przykład przechodząc z trenowania do inference. Używaj metody detach, kiedy aktywnie budujesz computation graph podczas trenowania, ale musisz wyciągnąć z niego jeden konkretny tensor. Częsty use case dla detach to sytuacja, gdy musisz przekazać tensor do innej biblioteki w Pythonie, takiej jak NumPy, która nie rozumie computation graphs z PyTorcha. Najpierw robisz detach na tensorze, pozbywając się bagażu śledzenia, a potem przekazujesz surowe liczby. Wyłączenie gradient tracking to również kluczowa technika do zamrażania parametrów. Jeśli robisz fine-tuning ogromnego, pre-trained modelu, prawdopodobnie nie chcesz trenować całości od zera. Możesz przeiterować przez bazowe warstwy modelu i ustawić ich wymagania gradientu na false. PyTorch całkowicie przestaje je śledzić. Podczas backward pass, te zamrożone warstwy nie będą obliczać gradientów i nie będą się aktualizować, co oszczędza ogromne ilości pamięci i drastycznie przyspiesza twój proces fine-tuningu. Gradient tracking to ciężka, przemysłowa machina zaprojektowana ściśle do uczenia. Zawsze, gdy tensor nie musi się uczyć, wyłącz tę machinę, żeby odzyskać pamięć i prędkość. To wszystko w tym odcinku. Dzięki za wysłuchanie i buduj dalej!
6

Datasets i przetwarzanie danych

3m 53s

Dowiedz się, jak oddzielić przetwarzanie danych od architektury modelu za pomocą klasy Dataset w PyTorch. Omawiamy lazy loading i niestandardowe struktury zbiorów danych.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 6 z 18. Twój model jest tylko tak dobry, jak dane, którymi go karmisz, ale co zrobisz, gdy twój dataset ma rozmiar terabajta, a twoja maszyna ma tylko szesnaście gigabajtów RAM-u? Odpowiedź leży w tym, jak pobierasz te dane. Ten odcinek omawia Datasety i Data Handling. Logika przetwarzania danych może szybko zamienić się w bałagan. Jeśli wrzucisz kod do czytania, dekodowania i formatowania plików bezpośrednio do swojej pętli treningowej, twój projekt stanie się kruchy i trudny w utrzymaniu. PyTorch zachęca cię do oddzielenia tych kwestii. Chcesz, aby przygotowanie danych było całkowicie oddzielone od algorytmu treningowego. Aby to osiągnąć, PyTorch udostępnia prymityw o nazwie Dataset, znajdujący się w module torch dot utils dot data. Klasa Dataset działa jak ustandaryzowany wrapper dla twoich surowych danych. Aby obsługiwać twoje własne, specyficzne pliki, tworzysz customową klasę, która dziedziczy po tym prymitywie. Budując customowy dataset, musisz zaimplementować trzy konkretne metody. Są to init, len i getitem. Metoda init uruchamia się dokładnie raz, kiedy tworzysz obiekt datasetu. To tutaj konfigurujesz swoje katalogi i ścieżki. Częstym błędem początkujących jest próba załadowania wszystkich właściwych danych do pamięci właśnie w tym miejscu. Nie rób tego. Jeśli masz pięćdziesiąt tysięcy obrazów w wysokiej rozdzielczości, wczytanie ich wszystkich do pamięci podczas inicjalizacji natychmiast zcrashuje twoją maszynę. Zamiast tego, użyj init do załadowania lekkiego indeksu. Na przykład, możesz wczytać plik CSV, który zawiera nazwy plików z obrazami w jednej kolumnie, a odpowiadające im tekstowe labele w drugiej. Budujesz po prostu mapę, a nie trzymasz całe terytorium. Następna jest metoda len. Zwraca ona po prostu całkowitą liczbę próbek w twoim datasecie. Jeśli twój plik CSV ma pięćdziesiąt tysięcy wierszy, ta metoda zwraca liczbę pięćdziesiąt tysięcy. System polega na tym, aby znać absolutne granice twoich dostępnych danych, dzięki czemu nie zażąda indeksu, który nie istnieje. Czarna robota dzieje się w metodzie getitem. Ta funkcja jest zaprojektowana tak, aby załadować i zwrócić pojedynczą próbkę pod konkretnym, żądanym indeksem. Kiedy system potrzebuje próbki numer czterdzieści dwa, wywołuje getitem i przekazuje ten numer. Twój kod wyszukuje wiersz czterdziesty drugi w pliku CSV, który załadowałeś wcześniej. Odczytuje string ze ścieżką do pliku z tego wiersza. Wtedy, i tylko wtedy, uzyskuje dostęp do dysku, znajduje plik i dekoduje właściwe piksele obrazu do pamięci. Pobiera label z tego samego wiersza CSV i zwraca obraz oraz label razem jako tuple. Ta technika to lazy loading. Zużywasz pamięć tylko na ten konkretny fragment danych, którego potrzebujesz, dokładnie w momencie, gdy jesteś gotowy go przetworzyć. Dzięki odizolowaniu tej logiki wewnątrz metody getitem, twój kod treningowy nigdy nie musi wiedzieć, czy dane pochodzą z lokalnego dysku twardego, strumienia sieciowego, czy ze skomplikowanej bazy danych. Po prostu żąda indeksu i otrzymuje ustandaryzowany output. Oddzielenie mechanizmu tego, jak dane są pobierane, od tego, jak są konsumowane, to fundament skalowalnego kodu machine learningowego. Jeśli uważasz te odcinki za pomocne i chcesz wesprzeć program, możesz wyszukać DevStoriesEU na Patreon. To wszystko w tym odcinku. Dzięki za wysłuchanie i twórz dalej!
7

DataLoaders i batching

3m 39s

Uwolnij pełną prędkość swojego sprzętu, opakowując Datasets w DataLoaders. Dowiedz się, jak grupować w batche, tasować i wieloprocesowo przetwarzać strumienie danych.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 7 z 18. GPU są niesamowicie szybkie, ale pozostaną całkowicie bezczynne, jeśli twój CPU nie będzie w stanie dostarczyć im danych wystarczająco szybko. W pętlach treningowych wąskim gardłem często nie jest matematyka, ale ładowanie kolejnego zestawu plików z dysku. Rozwiązaniem tego problemu jest oddzielenie pobierania danych od działania modelu za pomocą DataLoaderów i batchingu. Łatwo pomylić role Datasetu i DataLoadera. Dataset ma dokładnie jedno zadanie: pobieranie pojedynczego elementu i jego etykiety. Nie wie nic o szerszym procesie treningowym. DataLoader to wrapper dla tego Datasetu. Działa jako menedżer odpowiedzialny za organizowanie tych pojedynczych elementów w grupy, losowanie ich kolejności i wykorzystanie wielu procesów do ich efektywnego ładowania. Podczas treningu modele rzadko analizują jeden punkt danych na raz. Aktualizują swoje wewnętrzne wagi na podstawie grupy elementów przetwarzanych jednocześnie, znanej jako minibatch. Takie podejście sprawia, że proces treningowy jest bardziej stabilny i w pełni wykorzystuje moc równoległego przetwarzania sprzętu. Aby ręcznie zbudować minibatch, musiałbyś napisać pętlę, która pobierałaby pojedyncze próbki, stackowała je w większą strukturę tensora i obsługiwała edge case'y, takie jak to, że ostatni batch jest mniejszy od pozostałych. DataLoader obsługuje to wszystko automatycznie. Inicjujesz DataLoader, przekazując mu swój obiekt Dataset i parametr o nazwie batch size. Jeśli ustawisz batch size na 64, DataLoader pobierze 64 różne elementy z Datasetu, skonsoliduje je w jeden tensor i zaserwuje je wszystkie naraz. W twoim kodzie DataLoader zachowuje się jak standardowy pythonowy iterable. Iterujesz po nim w pętli. Za każdym razem, gdy pętla idzie do przodu, DataLoader zwraca przez yield kolejny kompletny batch danych i odpowiadający mu batch etykiet. Przekazujesz również parametr shuffle. Jeśli sieć neuronowa przetwarza dane treningowe za każdym razem w dokładnie tej samej sekwencji, może zapamiętać tę konkretną sekwencję zamiast uczyć się rzeczywistych cech. Ustawienie shuffle na true mówi DataLoaderowi, żeby zrandomizował kolejność elementów Datasetu na początku każdego epocha. Kiedy każdy batch zostanie zwrócony przez yield, a Dataset się wyczerpie, pętla się kończy. Gdy następnym razem iterujesz po DataLoaderze, generuje on zupełnie nową, zrandomizowaną sekwencję. To jest ta część, która ma największe znaczenie. DataLoader przyjmuje również parametr określający liczbę workerów. Kiedy używasz wielu workerów, DataLoader odpala w tle procesy CPU, aby pobierać dane. Wyobraź sobie karmienie sieci neuronowej tymi 64 obrazami. Podczas gdy twój GPU jest zajęty obliczaniem gradientów dla obecnego batcha, workery CPU w tle jednocześnie odczytują, dekodują i stackują kolejne 64 obrazy. Zanim GPU skończy swój obecny matematyczny krok, kolejny batch danych już czeka w pamięci. GPU nigdy nie głoduje. Wysokowydajna pętla treningowa izoluje powolną i nieprzewidywalną rzeczywistość operacji dyskowych od szybkiej i ustrukturyzowanej rzeczywistości treningu modelu. DataLoader zapewnia tę izolację, zamieniając zbiór pojedynczych plików w ciągły, zrównoleglony pipeline minibatchy. To wszystko w tym odcinku. Dzięki za wysłuchanie i buduj dalej!
8

Transformacje danych

4m 14s

Odkryj, jak wstępnie przetwarzać surowe dane w locie, zanim trafią do Twojej Neural Network. Omawiamy transformacje z biblioteki torchvision, takie jak ToTensor, oraz niestandardowe funkcje Lambda.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 8 z 18. Sieci neuronowe operują tylko na liczbach, ale twoje rzeczywiste dane to zazwyczaj chaotyczny zbiór surowych plików graficznych i kategorii tekstowych. Jeśli będziesz pisać pętle ręcznie, żeby przekonwertować każdy pojedynczy obraz i etykietę przed rozpoczęciem treningu, twój kod szybko stanie się kruchym i nieczytelnym bałaganem. Data Transformations to mechanizm, który rozwiązuje ten problem, automatycznie konwertując surowe dane do formatu gotowego dla modelu dokładnie wtedy, gdy jest to potrzebne. Dane rzadko kiedy docierają gotowe do uczenia maszynowego. Musisz je przekształcić do określonego formatu tensora przed podaniem ich do sieci. PyTorch radzi sobie z tym w przejrzysty sposób, aplikując transformacje w locie podczas procesu ładowania danych. Kiedy inicjalizujesz dataset, szczególnie w bibliotekach takich jak torchvision, definiujesz te modyfikacje za pomocą dwóch konkretnych argumentów. Argumentu transform używasz wyłącznie dla swoich input features, takich jak surowe obrazy. Argumentu target transform używasz wyłącznie dla swoich etykiet. To kluczowe, aby trzymać je osobno, ponieważ operują niezależnie na różnych połówkach twoich danych. Najpierw przyjrzyjmy się input features. Załóżmy, że masz dataset surowych obrazów PIL. Sieć neuronowa nie potrafi bezpośrednio odczytać obiektu obrazu PIL. Aby to naprawić, przekazujesz wbudowaną w torchvision transformację o nazwie ToTensor do argumentu transform. Kiedy dataset wczytuje obraz, ToTensor automatycznie wykonuje dwa kroki. Po pierwsze, konwertuje obraz PIL na float tensor w PyTorchu. Po drugie, skaluje wartości intensywności pikseli. Piksele surowego obrazu zazwyczaj przyjmują wartości od zera do dwustu pięćdziesięciu pięciu. Operacja ToTensor normalizuje te wartości w dół do zakresu float między zero a jeden. Dataset aplikuje tę operację dokładnie w momencie pobierania każdego obrazu. To załatwia nam inputy, ale co z outputami? Etykiety w twoim datasecie mogą być prostymi integerami reprezentującymi różne kategorie. Na przykład liczba trzy może oznaczać psa. Ale żeby obliczyć loss podczas treningu, twój model często wymaga, aby te etykiety były wektorami z one-hot encodingiem, a nie pojedynczymi integerami. Oznacza to, że potrzebujesz tablicy, w której wszystkie wartości to zero, z wyjątkiem indeksu reprezentującego poprawną klasę, który jest ustawiony na jeden. Aby obsłużyć taką customową logikę, PyTorch udostępnia transformacje Lambda. Transformacja Lambda opakowuje dowolną funkcję zdefiniowaną przez użytkownika, dzięki czemu może być zaaplikowana podczas ładowania danych. Piszesz krótką funkcję, która przyjmuje twoją etykietę w postaci integera jako input. Wewnątrz tej funkcji tworzysz tensor zer odpowiadający całkowitej liczbie kategorii w twoim datasecie. Następnie używasz wewnętrznej operacji PyTorcha, aby zrobić scatter wartości jeden na konkretny indeks, który odpowiada twojej etykiecie w postaci integera. Przekazujesz tę customową funkcję do transformacji Lambda, a następnie przypisujesz ją do argumentu target transform twojego datasetu. To tworzy wysoce wydajny pipeline. Worker thread pobiera pojedynczy surowy rekord z twojego dysku. Obraz trafia do argumentu transform, przechodzi przez ToTensor i wychodzi jako znormalizowany float tensor. Jednocześnie kategoria w postaci integera trafia do argumentu target transform, wykonuje twoją customową funkcję Lambda i zamienia się w wektor z one-hot encodingiem. Oba elementy są teraz sformatowane matematycznie i przekazywane bezpośrednio do twojego modelu. Prawdziwą siłą tej architektury jest separation of concerns. Dzięki podpięciu tych transformacji danych bezpośrednio do definicji datasetu, twoja właściwa pętla treningowa pozostaje całkowicie ślepa na chaotyczną rzeczywistość twoich surowych plików. To wszystko w tym odcinku. Dzięki za wysłuchanie i keep building!
9

Projektowanie sieci z nn.Module

3m 59s

Poznaj strukturalny schemat każdej Neural Network w PyTorch. Dowiedz się, jak tworzyć podklasy nn.Module, definiować warstwy podczas inicjalizacji i kierować danymi w forward pass.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 9 z 18. Każda sieć neuronowa w PyTorchu, od prostego klasyfikatora obrazów po rozbudowany model językowy, ma dokładnie ten sam podstawowy schemat. Jeśli nie rozumiesz, jak ten schemat organizuje dane i logikę, będziesz walczyć z frameworkiem na każdym kroku. Projektowanie sieci przy użyciu nn.Module to sposób na opanowanie tej struktury. nn.Module to klasa bazowa dla wszystkich komponentów sieci neuronowych w PyTorchu. Działa jak uniwersalny kontener. Kiedy budujesz własny model, tworzysz klasę dziedziczącą po nn.Module. To dziedziczenie automatycznie daje twojej klasie możliwość śledzenia własnych parametrów, obliczania gradientów i płynnej integracji z resztą ekosystemu PyTorcha. Umożliwia to również tworzenie zagnieżdżonych architektur. Możesz umieszczać moduły w innych modułach, tworząc drzewo warstw, które moduł nadrzędny śledzi i którym zarządza jako pojedynczą jednostką. Wyobraź sobie tworzenie pustego szkieletu zupełnie nowego klasyfikatora obrazów. Zbudowanie tego szkieletu wymaga zdefiniowania dwóch konkretnych metod: metody initialize i metody forward. PyTorch wymusza ścisłe rozdzielenie odpowiedzialności między tymi dwoma etapami. Pierwsza to metoda initialize. Pomyśl o niej jak o swoim magazynie. Kiedy tworzona jest instancja klasy, ta metoda uruchamia się dokładnie raz. Używasz jej do zadeklarowania wszystkich pojedynczych warstw i operacji matematycznych, których twój model będzie ostatecznie potrzebował. Nie przetwarzasz tu jeszcze żadnych rzeczywistych danych. Po prostu zdejmujesz z półki komponenty strukturalne, konfigurujesz ich wejściowe i wyjściowe kształty, a następnie zapisujesz je jako wewnętrzne zmienne w swojej klasie. Kolejna to metoda forward. To twoja aktywna linia montażowa. Metoda forward przyjmuje wejściowy tensor i dokładnie określa, jak podróżuje on przez przed chwilą zadeklarowany magazyn. Zapisujesz sekwencję operacji krok po kroku. Bierzesz wejściowy tensor obrazu, przekazujesz go do operacji flatten, wrzucasz ten wynik do serii warstw dense, i ostatecznie zwracasz wyjściowe predykcje. Każdy własny model musi definiować tę metodę forward, aby ustalić przepływ danych. To prowadzi do częstej pułapki. Ponieważ jawnie napisałeś logikę przepływu danych w metodzie o nazwie forward, naturalnym odruchem jest przekazanie danych poprzez wywołanie model kropka forward. Nie rób tego. Musisz wywołać model bezpośrednio, tak jakby był zwykłą funkcją, przekazując swoje dane wejściowe prosto do utworzonego obiektu modelu. Pod spodem, bezpośrednie wykonanie obiektu modelu uruchamia kilka krytycznych hooków w tle, których PyTorch potrzebuje do zarządzania stanem sieci. Bezpośrednie wywołanie metody forward omija te hooki i spowoduje nieoczekiwane zachowanie podczas twojej training loop. Kiedy twoja klasa jest już zdefiniowana i utworzyłeś obiekt modelu, masz działającą sieć. Jednak domyślnie PyTorch tworzy ten obiekt i wszystkie jego wewnętrzne wagi w pamięci CPU twojego systemu. Aby trenować z realistycznymi prędkościami, musisz przenieść tę architekturę na akcelerator. Robisz to, sprawdzając, czy dostępne jest GPU z CUDA lub dedykowany układ, taki jak Apple MPS, i przypisując ten cel sprzętowy do zmiennej device. Następnie wywołujesz metodę to na swoim modelu, przekazując tę zmienną device. To jedno polecenie natychmiast przenosi wszystkie zainicjowane parametry modelu ze standardowej pamięci do szybkiej pamięci twojego akceleratora sprzętowego. Cechą definiującą nn.Module jest to, jak wymusza on czystą granicę architektoniczną między statycznymi komponentami, które twój model posiada w pamięci, a dynamiczną ścieżką, którą pokonują twoje dane w celu przetworzenia. Dzięki za wysłuchanie. Trzymajcie się.
10

Warstwy Linear i aktywacje

3m 56s

Zajrzyj do wnętrza Neural Network. Rozkładamy na czynniki pierwsze moduł nn.Linear i wyjaśniamy, dlaczego nieliniowe funkcje aktywacji, takie jak ReLU, są matematycznie niezbędne.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 10 z 18. Jeśli połączysz ze sobą sto warstw sieci neuronowej bez jednego konkretnego matematycznego triku, cała sieć matematycznie zapadnie się do pojedynczej prostej. Winowajcą jest algebra liniowa, a rozwiązanie wymaga zrozumienia warstw liniowych i aktywacji. Sieć neuronowa to w gruncie rzeczy sekwencja operacji matematycznych na tensorach. Najpopularniejszą operacją jest warstwa liniowa, zdefiniowana w PyTorchu jako nn.Linear. Ten moduł aplikuje transformację afiniczną do danych wejściowych. Przechowuje dwa wewnętrzne tensory, których uczy się w czasie: wagi i biasy. Kiedy dane przez nią przechodzą, warstwa mnoży input przez macierz wag i dodaje bias. Weź standardowy obraz w skali szarości o wymiarach 28 na 28 pikseli. Zanim warstwa liniowa będzie mogła go przetworzyć, spłaszczasz tę dwuwymiarową siatkę do jednowymiarowej tablicy 784 liczb. Przekazujesz tę tablicę 784 wartości do warstwy nn.Linear, skonfigurowanej tak, żeby zwrócić 512 feature'ów. Pod spodem PyTorch tworzy macierz wag, która mapuje 784 inputy na 512 outputów. Mnoży wartości twoich pikseli przez te wagi, sumuje je, dodaje bias, żeby przesunąć wynik, i zwraca 512 nowych liczb. Podczas treningu PyTorch na bieżąco aktualizuje te wagi i biasy. Stanowią one właściwą pamięć twojego modelu. Możesz założyć, że głęboka sieć neuronowa to po prostu długa sekwencja takich warstw liniowych, ułożonych jedna za drugą. To jest ta najważniejsza część. Jeśli połączysz w chain wiele operacji nn.Linear bez niczego pomiędzy, matematyka się uprości. Macierz A pomnożona przez macierz B to po prostu kolejna macierz C. Zestawienie dziesięciu warstw liniowych ma dokładnie taką samą pojemność matematyczną, jak obliczenie jednej pojedynczej warstwy liniowej. Twoja głęboka sieć redukuje się do płaskiego, liniowego równania, całkowicie niezdolnego do uczenia się złożonych wzorców z prawdziwego świata. Żeby zatrzymać to matematyczne zapadanie się, wprowadzasz nieliniowość bezpośrednio po warstwie liniowej. Nazywamy je funkcjami aktywacji. Najczęściej używaną aktywacją w PyTorchu jest nn.ReLU, co jest skrótem od Rectified Linear Unit. Kiedy warstwa liniowa obliczy swoje 512 outputów, przekazujesz ten tensor bezpośrednio do funkcji ReLU. Logika ReLU jest brutalnie prosta. Patrzy na każdą liczbę w tensorze. Jeśli liczba jest mniejsza od zera, ReLU zmienia ją na dokładnie zero. Jeśli liczba jest równa zero lub dodatnia, ReLU zostawia ją całkowicie w spokoju. To pojedyncze załamanie w zerze niszczy liniowość. Zapobiega to matematycznemu scaleniu się kolejnej warstwy liniowej z poprzednią. Wymuszając zera dla wartości ujemnych, ReLU tworzy również sparse representations. Oznacza to, że dla każdego inputu aktywuje się tylko określony podzbiór neuronów, co sprawia, że sieć jest bardzo wydajna. Przepływ danych jest spójny. Twój spłaszczony obraz wchodzi do warstwy liniowej, zostaje przekształcony przez wagi i biasy, a następnie trafia na aktywację ReLU, gdzie ujemne outputy są odrzucane. Następnie możesz bezpiecznie przekazać ten aktywowany tensor do drugiej warstwy liniowej, żeby wyciągnąć głębsze, bardziej abstrakcyjne wzorce. Warstwa liniowa określa, jak duże matematyczne znaczenie nadać każdemu inputowi, ale to funkcja aktywacji nadaje sieci rzeczywistą geometrię, niezbędną do nauczenia się nieprzewidywalnych kształtów prawdziwych danych. Dzięki za spędzenie ze mną tych kilku minut. Do usłyszenia następnym razem, trzymaj się.
11

Kontener nn.Sequential

3m 50s

Usprawnij swój kod PyTorch za pomocą kontenera nn.Sequential. Dowiedz się, jak czysto łączyć ze sobą warstwy i badać parametry swojego modelu.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 11 z 18. Pisanie customowych metod forward dla każdej sieci neuronowej szybko staje się żmudne, gdy po prostu układasz standardowe warstwy. Nie zawsze musisz ręcznie routować dane z jednej funkcji do drugiej. Czasami wystarczy po prostu spiąć warstwy ze sobą jak klocki LEGO. I dokładnie to robi kontener nn.Sequential. Kontener nn.Sequential to uporządkowany pipeline modułów sieci neuronowej. Kiedy przekazujesz dane do tego kontenera, przepływają one przez wewnętrzne moduły dokładnie w takiej kolejności, w jakiej zostały dodane. Wyobraź sobie budowanie standardowego, trójwarstwowego Multilayer Perceptronu. Zazwyczaj zdefiniowałbyś swoje warstwy liniowe i funkcje aktywacji w metodzie inicjalizacyjnej, a potem napisał customową metodę forward. W tej metodzie forward jawnie pobrałbyś input, przekazał go do pierwszej warstwy, wrzucił w aktywację ReLU, przekazał ten wynik do drugiej warstwy, zastosował kolejne ReLU i podał to na ostatnią warstwę. Z Sequential całkowicie pomijasz metodę forward. Tworzysz instancję kontenera i przekazujesz do niego swoje moduły bezpośrednio jako argumenty. Podajesz moduł Linear, potem moduł ReLU, drugi moduł Linear, kolejne ReLU i finałowy moduł Linear. PyTorch automatycznie ogarnia routing danych. Output pierwszego modułu od razu staje się inputem dla drugiego, automatycznie przechodząc dalej w dół chaina. Ten kontener jest bardzo wydajny, ale ma twarde ograniczenie. Jest przeznaczony ściśle do liniowego, prostego przepływu danych. Nie poradzi sobie ze złożonymi architekturami, które wymagają rozgałęzień, wielu inputów czy skip connections. Jeśli budujesz coś w stylu Residual Network, gdzie dane omijają pewne warstwy i są dodawane z powrotem później, Sequential się nie sprawdzi. Dla każdej nieliniowej topologii nadal musisz napisać customowy moduł z dedykowaną metodą forward. Kiedy już połączysz swoje warstwy w chain, często musisz sprawdzić, co właśnie zbudowałeś. Każda warstwa w twoim kontenerze Sequential jest subklasą nn.Module, co oznacza, że PyTorch automatycznie rejestruje i śledzi cały jej state. Aby podejrzeć ten state, używasz metody named_parameters. Wywołanie named_parameters na twoim modelu zwraca iterator po wszystkich wagach i biasach wewnątrz. Każdy element, który zwraca, to prosta para: nazwa parametru i sam tensor parametru. Ponieważ użyłeś kontenera Sequential bez jawnego nazywania warstw, PyTorch generuje numeryczne nazwy na podstawie ich indeksu. Zobaczysz nazwy takie jak zero dot weight dla wag pierwszej warstwy Linear, albo zero dot bias dla jej biasów. Towarzyszący mu tensor zawiera rzeczywiste wartości liczbowe, shape macierzy oraz informację, czy wymaga ona obliczania gradientu. Iterowanie po named_parameters to standardowy sposób na weryfikację twojej architektury. Możesz szybko wyprintować rozmiar każdej macierzy wag, aby upewnić się, że wymiary inputu i outputu idealnie do siebie pasują w całym chainie, zanim w ogóle zaczniesz przepuszczać przez system prawdziwe dane. Prawdziwa moc kontenera Sequential w połączeniu ze śledzeniem parametrów polega na tym, że PyTorch bierze na siebie całą czarną robotę związaną z zarządzaniem statem i routingiem danych, pozwalając ci skupić się wyłącznie na shape'ie twojej sieci. To tyle na dzisiaj. Do usłyszenia następnym razem!
12

Zrozumienie Loss Functions

3m 53s

Zanim AI będzie mogło się uczyć, musi zmierzyć swoje błędy. Zagłębiamy się w Loss Functions w PyTorch, porównując CrossEntropyLoss dla klasyfikacji i MSELoss dla regresji.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 12 z 18. Aby nauczyć sieć neuronową dawać poprawne odpowiedzi, musisz najpierw rygorystycznie zmierzyć, jak bardzo się myli. Jeśli nie potrafisz skwantyfikować błędu, twój model nie będzie w stanie się na nim uczyć. To prowadzi nas do zrozumienia loss functions. Kiedy niewytrenowana sieć przetwarza dane, jej output to w zasadzie zgadywanka. Loss function ocenia to zgadnięcie. Mierzy stopień rozbieżności między outputem wygenerowanym przez model, a absolutną prawdą, czyli target value. Output loss function to zawsze pojedynczy skalar. Cały twój proces treningowy istnieje po to, żeby zepchnąć tę jedną liczbę jak najbliżej zera. Ponieważ różne zadania machine learningowe mają różne definicje błędu, PyTorch udostępnia kilka różnych loss functions. Jeśli budujesz model regresji do przewidywania wartości ciągłej, na przykład jutrzejszej temperatury, mierzysz dystans między swoim trafem a rzeczywistą temperaturą. Do tego używasz Mean Square Error, co w PyTorchu nazywa się nn.MSELoss. Ale z klasyfikacją jest inaczej. Załóżmy, że masz model kategoryzujący zdjęcia ubrań na dziesięć modowych kategorii. Model analizuje zdjęcie płaszcza i zwraca dziesięć surowych score'ów, po jednym dla każdej możliwej kategorii. Te surowe, nieznormalizowane score'y nazywamy logitami. Prawdziwa odpowiedź to po prostu pojedynczy integer, reprezentujący właściwą klasę. Nie możesz po prostu odjąć indeksu klasy od surowego score'u. Zamiast tego potrzebujesz funkcji, która ukarze model za dawanie niskich score'ów właściwej klasie i wysokich score'ów niewłaściwym klasom. Do klasyfikacji standardowym narzędziem jest nn.CrossEntropyLoss. Inicjalizujesz swoją loss function, przekazujesz do niej dziesięć surowych logitów z modelu razem z prawidłowym labelem w postaci integera, a ona zwraca ci skalarną karę. I to jest ta najważniejsza część. Kryje się tu ogromna pułapka na developerów. W wielu podręcznikach do machine learningu, sieć klasyfikacyjna kończy się warstwą softmax. Softmax wymusza na surowych logitach zgrabny rozkład prawdopodobieństwa, w którym wszystkie score'y sumują się dokładnie do jedynki. Z tego powodu developerzy często ręcznie dodają operację softmax na samym końcu swojego modelu w PyTorchu. Jeśli używasz nn.CrossEntropyLoss, zrobienie tego jest błędem. W PyTorchu nn.CrossEntropyLoss automatycznie aplikuje wewnętrznie funkcję LogSoftmax przed obliczeniem negative log likelihood. Jest zbudowana tak, aby bezpośrednio przyjmować surowe, nieznormalizowane logity. Jeśli twój model zwraca prawdopodobieństwa, bo już zastosowałeś softmax, przekazanie ich do nn.CrossEntropyLoss oznacza, że wykonujesz te obliczenia dwa razy. To kompresuje twoje gradienty, drastycznie spowalnia trening i rujnuje zdolność twojego modelu do efektywnego uczenia się. Zasada, o której musisz pamiętać, jest taka, że twoja sieć neuronowa powinna po prostu zwracać surowe liczby. Zostaw outputy swojego modelu surowe, przekaż je prosto do nn.CrossEntropyLoss i pozwól PyTorchowi wykonać najcięższą robotę, zamieniając te logity w sensowną karę. Dzięki za wysłuchanie, happy coding wszystkim!
13

Optimizers i spadek gradientu

3m 56s

Sprawdź, jak optimizer aktualizuje wagi modelu, aby zmniejszyć błąd. Poznaj kluczowy, trzyetapowy taniec funkcji zero_grad(), backward() i step().

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 13 z 18. Najczęstszym błędem podczas trenowania w PyTorch nie są złe dane ani zła architektura. Jest nim zapominanie o wyczyszczeniu starych obliczeń, przez co twoja sieć wymyka się spod kontroli. Dzisiaj omówimy optimizery i Gradient Descent, które odpowiadają dokładnie za to, jak twój model uczy się na błędach. Twój model robi predykcję, a ty obliczasz loss, żeby sprawdzić, jak bardzo się pomylił. Teraz musisz zaktualizować wewnętrzne wagi sieci neuronowej, żeby kolejna predykcja była trochę dokładniejsza. Ten proces modyfikowania parametrów w celu zminimalizowania lossa nazywamy optymalizacją. Optimizer to konkretny algorytm, który steruje tym, jak zmieniają się te wagi. Aby skonfigurować optimizera, musisz podać mu dwie rzeczy. Po pierwsze, przekazujesz mu iterable zawierający parametry modelu, które chcesz dostosować. Po drugie, podajesz learning rate. Learning rate to podstawowy hiperparametr, który kontroluje wielkość zmian aplikowanych do wag. Jeśli learning rate jest zbyt mały, optimizer robi mikroskopijne kroki, przez co trenowanie jest boleśnie wolne. Jeśli learning rate jest zbyt duży, optimizer przestrzeliwuje optymalne wartości, co prowadzi do szalonego, nieprzewidywalnego zachowania. Standardowym algorytmem do tego zadania jest Stochastic Gradient Descent, czyli SGD. Ocenia on nachylenie twojej funkcji loss i robi krok w przeciwnym kierunku, aby zejść w stronę najniższego możliwego błędu. Kiedy zainicjalizujesz optimizera SGD swoimi parametrami i learning rate, właściwe aktualizacje odbywają się w ścisłej, trzyetapowej sekwencji. Krok pierwszy to wyczyszczenie stanu. Wywołujesz komendę zero grad na optimizerze. To właśnie tutaj kryje się ten powszechny błąd. PyTorch domyślnie akumuluje gradienty. Kiedy oblicza nowe gradienty, nie nadpisuje starych; po prostu dodaje nowe liczby do istniejących sum. Jeśli pominiesz ten krok zero grad, obliczenia dla twojego obecnego batcha zostaną zepsute przez pozostałości z poprzedniego batcha. Zawsze zeruj gradienty, zanim zrobisz cokolwiek innego. Krok drugi to obliczanie nowych gradientów. Bierzesz obliczoną wartość lossa i wywołujesz na niej komendę backward. To uruchamia backpropagation. PyTorch wędruje wstecz przez architekturę twojej sieci. Oblicza pochodną lossa względem każdego pojedynczego parametru. W zasadzie sprawdza dokładnie, jak bardzo każda pojedyncza waga przyczyniła się do całkowitego błędu. Te obliczone gradienty są przechowywane bezpośrednio w obiektach parametrów. Krok trzeci to zastosowanie poprawki. Wywołujesz komendę step na optimizerze. Optimizer patrzy na gradienty zapisane w każdym parametrze podczas backward pass. Mnoży te gradienty przez learning rate, żeby wyliczyć dokładny rozmiar korekty, a następnie aktualizuje właściwe wagi w pamięci. Ten cykl powtarza się dla każdego batcha. Zerujesz gradienty, obliczasz backward loss, wywołujesz step na optimizerze. Kluczowym detalem do zapamiętania jest to, że optimizer aktualizuje tylko te parametry, które zostały mu jawnie przekazane podczas konfiguracji. Jeśli musisz zamrozić warstwę w swojej sieci, po prostu pomijasz jej parametry podczas inicjalizacji optimizera, a te wagi pozostaną na stałe niezmienione. Przy okazji, jeśli chcesz wesprzeć podcast, możesz wyszukać DevStoriesEU na Patreonie. Dzięki za wysłuchanie. Trzymajcie się wszyscy.
15

Walidacja i inferencja

3m 35s

Oceniaj swój model obiektywnie. Dowiedz się, jak przełączyć sieć w tryb ewaluacji, zamrozić gradienty i uzyskać dokładne predykcje na niewidzianych wcześniej danych.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 15 z 18. Twój model może działać idealnie na danych treningowych, ale jedynym prawdziwym testem AI jest to, jak radzi sobie z nieznanym. Jeśli polegasz wyłącznie na feedbacku, który widzi twój optimizer, możesz po prostu budować bardzo drogi bank pamięci. Aby sprawdzić, czy twój model faktycznie generalizuje się na prawdziwy świat, potrzebujesz walidacji i inference'u. W fazie treningu patrzysz na training loss. Ta liczba istnieje po to, by kierować wewnętrznym optimizerem. Zmusza model do dostosowania wag, aż błąd matematyczny się zmniejszy. Ale niski training loss wcale nie oznacza, że masz dobry model. Oznacza to po prostu, że model bardzo dobrze odpowiada na pytania, które już widział. Validation accuracy to zupełnie osobna metryka, która mówi ludziom, czy model potrafi robić poprawne predykcje na całkowicie nowych danych. Aby uzyskać tę metrykę, musisz odpalić validation loop na dedykowanym test datasecie. Przed wrzuceniem choćby jednego kawałka danych testowych do sieci, musisz zmienić stan modelu. Robisz to, wywołując metodę eval na swoim obiekcie modelu. Wywołanie eval przełącza sieć w evaluation mode. Niektóre wewnętrzne warstwy zachowują się inaczej podczas treningu, a inaczej podczas inference'u. Wywołanie eval zmusza je do zablokowania swojego zachowania, dzięki czemu twoje predykcje pozostają spójne. Jeśli pominiesz ten krok, twoje wyniki testów będą całkowicie niewiarygodne. To tyle, jeśli chodzi o stan modelu. Następnie musisz kontrolować sam silnik, wyłączając gradient tracking. Robisz to, owijając swój validation code w context manager no grad. Podczas treningu PyTorch stale buduje computational graph w pamięci, zapisując historię każdej operacji, aby móc później obliczyć gradienty. W validation loop jesteś już całkowicie po treningu. Nie chcesz aktualizować wag. Blok no grad mówi PyTorchowi, żeby przestał śledzić historię. To jest ta najważniejsza część. Wyłączenie trackingu zapobiega przypadkowym aktualizacjom twojego modelu, ale też zwalnia ogromną ilość pamięci i drastycznie przyspiesza obliczenia. Wewnątrz tego bloku no grad, sama logika jest prosta. Iterujesz po swoim test datasecie w batchach. Dla każdego batcha przepuszczasz dane wejściowe przez model. Model oblicza forward pass i zwraca swoje surowe predykcje. Jeśli robisz klasyfikację, model nie zwraca ładnej, tekstowej etykiety. Zamiast tego zwraca listę liczbowych wyników dla każdej pojedynczej kategorii, którą zna. Aby dowiedzieć się, którą kategorię faktycznie wybrał model, potrzebujesz funkcji argmax. Argmax patrzy na listę surowych wyników i znajduje najwyższą liczbę. Następnie zwraca pozycję indeksu tego najwyższego wyniku. Ten indeks to twoja wybrana predykcja klasy. Kiedy masz już predykcje modelu, porównujesz je bezpośrednio z prawdziwymi etykietami dostarczonymi przez test dataset. Liczysz dokładnie, ile predykcji pasuje do prawdziwych etykiet. Utrzymujesz bieżącą sumę tych poprawnych dopasowań przez wszystkie batche. Kiedy pętla się kończy, dzielisz całkowitą liczbę poprawnych predykcji przez całkowitą liczbę elementów w test datasecie. Wynik to twój końcowy procent accuracy. Training loop zmusza twój model do dopasowania się do danych historycznych, ale surowe ograniczenia validation loop udowadniają, czy ten model jest faktycznie użyteczny. Dzięki za wysłuchanie. Trzymajcie się wszyscy.
16

Zapisywanie i ładowanie modeli

3m 38s

Nie trać ciężko wypracowanych postępów! Omawiamy najbezpieczniejsze sposoby serializacji wag modelu za pomocą state_dict i ich bezpiecznego ponownego ładowania.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 16 z 18. Trenowanie klasyfikatora obrazów przez pięćdziesiąt epok może zająć tygodnie i pochłonąć tysiące dolarów na compute. Jednak w momencie, gdy twój skrypt w Pythonie kończy się wykonywać, wszystkie te ciężko wypracowane wzorce całkowicie znikają z pamięci. Aby chronić tę inwestycję, potrzebujesz sposobu na zapisanie swoich postępów na dysku. I właśnie ten problem rozwiązuje zapisywanie i ładowanie modeli. W każdym modelu PyTorcha znajduje się wewnętrzny dictionary zwany state dict. Ten dictionary mapuje każdą warstwę twojej sieci na odpowiadające jej tensory parametrów. Przechowuje on rzeczywiste weights i biases, których twój model nauczył się podczas trenowania. Struktura modelu to po prostu kod, ale state dict to jego inteligencja. Aby utrwalić swój model, wyciągasz ten dictionary i zapisujesz go do pliku. Robisz to za pomocą funkcji o nazwie torch dot save. Przekazujesz do niej dwie rzeczy. Po pierwsze, state dict twojego modelu. Po drugie, ścieżkę do pliku, w którym chcesz go zapisać, która tradycyjnie używa rozszerzenia dot pth. W jednej linijce kodu, twój pięćdziesięcioepokowy przebieg treningowy jest bezpiecznie zapisany na dysku twardym jako pojedynczy plik, zawierający wyłącznie surowe dane tensorowe. Możesz znaleźć w internecie przykłady, które całkowicie pomijają state dict i przekazują obiekt modelu bezpośrednio do torch dot save. Nie rób tego. Zapisywanie całego modelu w dużej mierze opiera się na serializacji pickle w Pythonie. Wiąże to zapisany plik z dokładną strukturą katalogów i definicjami klas obecnymi w momencie tworzenia pliku. Jeśli później zrobisz refactor kodu lub przeniesiesz plik, model po prostu się nie załaduje. Trzymanie się state dict jest znacznie bezpieczniejsze i o wiele bardziej niezawodne, ponieważ zapisujesz tylko dane, a nie kod. Kiedy nadchodzi czas na deploy twojego klasyfikatora do produkcyjnego inference, musisz odwrócić ten proces. Ponieważ zapisałeś tylko weights, PyTorch musi wiedzieć, jak wygląda struktura sieci. Zaczynasz od utworzenia całkowicie pustej instancji klasy twojego modelu. To daje ci architektoniczną powłokę. Następnie wywołujesz torch dot load i podajesz ścieżkę do pliku, aby wczytać dictionary z powrotem do pamięci. Wywołując torch dot load, musisz przestrzegać kluczowej, nowoczesnej best practice. Zawsze przekazuj argument weights only ustawiony na true. Pliki pickle w Pythonie mogą zawierać dowolny kod wykonywalny. Jeśli pobierzesz pre-trained model z internetu i załadujesz go w ciemno, może to uruchomić złośliwe skrypty na twojej maszynie. Ustawienie weights only na true ogranicza loader do deserializacji tylko standardowych tensorów PyTorcha, zapewniając bezpieczeństwo twojego systemu. Na koniec, gdy twój pusty model jest gotowy, a bezpieczny dictionary załadowany, wywołujesz load state dict na modelu i przekazujesz do niego ten dictionary. PyTorch mapuje załadowane weights na odpowiednie warstwy w tej pustej powłoce. Twój model jest teraz w pełni przywrócony i gotowy do robienia predykcji. Nigdy nie powierzaj swojej inwestycji w trenowanie kruchemu, zserializowanemu obiektowi; zawsze oddzielaj architekturę w swoim kodzie od wyuczonych parametrów na dysku. Dzięki za spędzony czas. Mam nadzieję, że dowiedziałeś się czegoś nowego.
17

Zwiększanie prędkości z torch.compile

3m 49s

Odblokuj kluczową funkcję PyTorch 2.0. Dowiedz się, jak dekorator torch.compile wykonuje JIT-compiles Twojego kodu Python do zoptymalizowanych kernels, zapewniając ogromne przyspieszenie.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 17 z 18. Spędzasz tygodnie na modyfikowaniu architektury modelu, żeby uzyskać pięcioprocentowe przyspieszenie. Ale często prawdziwym wąskim gardłem wcale nie jest matematyka. To sam Python i ciągłe, nieefektywne przesyłanie danych między pamięcią a GPU. Naprawienie tego nie wymaga przepisywania całego codebase'u. Dzisiaj przyjrzymy się, jak drastycznie zwiększyć prędkość za pomocą torch dot compile. Ta funkcja, wprowadzona w PyTorch 2.0, przenosi twój kod ze standardowego wykonywania do mocno zoptymalizowanego workflow. Często zakłada się, że przyspieszenie PyTorcha na produkcji oznacza pisanie customowych kerneli w C plus plus albo fundamentalną zmianę architektury. Wcale tak nie jest. Nie zmieniasz niczego w swoim modelu. Po prostu opakowujesz swój istniejący model w jedno wywołanie funkcji. Żeby zrozumieć, dlaczego daje to tak ogromny skok wydajności, musisz przyjrzeć się temu, jak PyTorch działa standardowo. Standardowy PyTorch działa w eager mode. Wykonuje dokładnie to, co mu każesz, dokładnie wtedy, kiedy o to prosisz, jedną operację na raz. Jeśli twój kod każe GPU dodać dwa tensory, pomnożyć wynik przez kolejny tensor i zastosować funkcję aktywacji, eager mode traktuje to jako trzy odizolowane zdarzenia. W każdym kroku GPU odczytuje dane ze swojej pamięci głównej, wykonuje obliczenia i zapisuje z powrotem wynik pośredni. Przepustowość pamięci GPU jest ograniczona. To ciągłe przesyłanie danych zajmuje znacznie więcej czasu niż same obliczenia. Kiedy przepuszczasz swój model przez funkcję compile, PyTorch zmienia taktykę. Używa wewnętrznego narzędzia o nazwie TorchDynamo, żeby przechwycić twoje operacje do grafu obliczeniowego przed ich wykonaniem. Patrząc na szerszą sekwencję, znajduje nieefektywności. Następnie używa backendowego kompilatora, żeby wygenerować nową, mocno zoptymalizowaną wersję twoich operacji. Główną techniką, której używa, jest kernel fusion. Zamiast odczytywać i zapisywać dane w pamięci trzy osobne razy, skompilowany kod łączy te kroki. GPU odczytuje dane raz, trzyma je w swoich najszybszych wewnętrznych rejestrach, wykonuje dodawanie, mnożenie i aktywację jedno po drugim, a następnie zapisuje ostateczny wynik tylko raz. Overhead Pythona znika, a wąskie gardło pamięci zostaje ominięte. Implementacja jest prosta. Tworzysz instancję modelu jak zwykle. Następnie wywołujesz torch dot compile, przekazujesz swój model i przypisujesz output do nowej zmiennej. Przepuszczasz swoje dane przez tę skompilowaną wersję. Jeśli TorchDynamo napotka jakąś nietypową konstrukcję Pythona, której nie potrafi bezpiecznie zoptymalizować, nie wysypuje twojego programu. Po prostu zostawia tę małą sekcję w standardowym eager mode i kompiluje resztę. Kiedy będziesz to benchmarkować, zwróć uwagę na pierwsze uruchomienie. Pierwszy przebieg trwa znacznie dłużej, ponieważ właściwa kompilacja dzieje się dokładnie wtedy, kiedy docierają pierwsze dane. Ale przy drugim przebiegu, czas inferencji drastycznie spada. Nie musisz już wybierać między elastycznością eager mode podczas developmentu, a czystą prędkością skompilowanego backendu na produkcji. To wszystko w tym odcinku. Dzięki za wysłuchanie i koduj dalej!
18

Kompilatory i Graph Breaks

4m 15s

Zajrzyj pod maskę kompilatora PyTorch. Omawiamy Graph Breaks, dynamiczny przepływ sterowania i powody, dla których torch.compile odnosi sukces tam, gdzie starsze systemy zawiodły.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. Podstawy PyTorcha, odcinek 18 z 18. Starsze kompilatory AI wymagały idealnie przewidywalnych ścieżek wykonania i spektakularnie się wysypywały, jeśli wrzuciłeś do modelu złożone, dynamiczne konstrukcje w Pythonie. Wystarczyło, że zmieniłeś jedną instrukcję warunkową, a cały proces kompilacji po prostu crashował. PyTorch dwa zero obsługuje dokładnie ten sam dowolny kod bez żadnego narzekania. Silnik stojący za tą elastycznością opiera się na tym, jak kompilator obsługuje graph breaks. Jeśli pracowałeś ze starszym narzędziem do kompilacji, TorchScript, to wiesz, że wymagało ono sztywnych struktur kodu. TorchScript opierał się na ścisłym, statycznym typowaniu i przewidywalnym execution. Jeśli twój model miał mocno dynamiczny control flow, opierał się na standardowych słownikach Pythona albo odwoływał się do zewnętrznych bibliotek nietensorowych, TorchScript często go odrzucał. Inżynierowie często musieli przepisywać spore fragmenty architektury modelu tylko po to, żeby zadowolić kompilator. PyTorch dwa zero podchodzi do tego zupełnie inaczej. Zamiast wymagać statycznego kodu z góry, natywny kompilator dynamicznie analizuje execution twojego kodu w Pythonie. Przechwytuje wszystkie operacje matematyczne, które może bezpiecznie zoptymalizować, i pakuje je w bardzo wydajny computational graph. Nieuchronnie kompilator trafi na kod, którego nie da się łatwo zmapować na zoptymalizowaną strukturę grafu. Kiedy natrafi na taką nieprzewidywalną logikę, triggeruje graph break. Graph break to nie jest błąd ani crash. To po prostu mechanizm typu fallback. Oznacza to, że kompilator płynnie oddaje kontrolę z powrotem do standardowego eager execution w PyTorchu dla tego konkretnego fragmentu kodu. Wyobraź sobie funkcję, w której odpalasz serię ciężkich mnożeń macierzy, a po nich instrukcję if w Pythonie, która sprawdza średnią wartość tensora, żeby zdecydować o kolejnej operacji. Ten warunek jest data-dependent. Ścieżka wykonania jest całkowicie nieznana aż do momentu, gdy wartości tensora zostaną obliczone w runtime. Kiedy tracujesz tę funkcję kompilatorem, analizuje on flow. Bierze mnożenia macierzy występujące przed warunkiem i kompiluje je w szybki, zoptymalizowany sub-graph. Następnie trafia na tę problematyczną instrukcję if. Ponieważ nie potrafi przewidzieć wyniku, tworzy graph break. Kompilator pozwala standardowemu Pythonowi wykonać ten warunek w eager mode. Kiedy warunek zostanie ewaluowany i ścieżka wybrana, kompilator odzyskuje kontrolę, bierze pozostałe operacje i kompiluje je w drugi zoptymalizowany sub-graph. System automatycznie segmentuje twój kod. Otrzymujesz skompilowane wyspy szybkich obliczeń matematycznych, rozdzielone standardowymi mostami w Pythonie. A twój model nadal działa płynnie. I to jest najważniejsza część. Prawdopodobnie zobaczysz logi wydajności wskazujące na graph breaks w twojej architekturze. Chociaż zazwyczaj chcesz je zminimalizować, żeby wycisnąć maksymalną szybkość execution, istnieją one wyłącznie jako safety net. Gwarantują, że twój kod zawsze wygeneruje poprawny wynik matematyczny, nawet jeśli nie da się połączyć każdej pojedynczej linijki w jeden kernel. Główną zmianą w podejściu do nowoczesnej kompilacji w PyTorchu jest priorytetyzacja płynnego execution nad całkowitą optymalizacją. To zapewnia, że silnik dostosowuje się do twojej dowolnej logiki, zamiast zmuszać twoją logikę do dostosowania się do silnika. To kończy naszą serię o PyTorchu. Gorąco zachęcam cię do przejrzenia oficjalnej dokumentacji, wypróbowania tych narzędzi do kompilacji w praktyce i odwiedzenia DEV STORIES DOT EU, żeby zasugerować tematy do przyszłych serii. Chciałbym też poświęcić chwilę, żeby podziękować ci za słuchanie – to bardzo nam pomaga. Trzymaj się!