Wiele zastosowań opadania gradientu w TensorFlow
Opublikowany: 2022-03-11TensorFlow firmy Google jest jednym z wiodących narzędzi do szkolenia i wdrażania modeli uczenia głębokiego. Jest w stanie zoptymalizować szalenie złożone architektury sieci neuronowych z setkami milionów parametrów i jest wyposażony w szeroką gamę narzędzi do przyspieszania sprzętowego, szkolenia rozproszonego i przepływów pracy produkcyjnej. Te potężne funkcje mogą sprawiać wrażenie onieśmielających i niepotrzebnych poza domeną głębokiego uczenia się.
Jednak TensorFlow może być zarówno dostępny, jak i użyteczny w przypadku prostszych problemów niezwiązanych bezpośrednio z trenowaniem modeli uczenia głębokiego. W swej istocie TensorFlow jest po prostu zoptymalizowaną biblioteką dla operacji tensorowych (wektory, macierze itp.) oraz operacji rachunku różniczkowego używanych do wykonywania gradientów na dowolnych sekwencjach obliczeń. Doświadczeni naukowcy zajmujący się danymi rozpoznają „opadanie gradientowe” jako podstawowe narzędzie matematyki obliczeniowej, ale zwykle wymaga to zaimplementowania kodu i równań specyficznych dla aplikacji. Jak zobaczymy, tutaj właśnie wkracza nowoczesna architektura „automatycznego różnicowania” TensorFlow.
Przypadki użycia TensorFlow
- Przykład 1: Regresja liniowa ze spadkiem gradientu w TensorFlow 2.0
- Co to jest zejście gradientowe?
- Przykład 2: Maksymalnie rozłożone wektory jednostkowe
- Przykład 3: Generowanie przeciwstawnych danych wejściowych AI
- Końcowe myśli: optymalizacja zjazdu gradientowego
- Gradientowe opadanie w TensorFlow: od znajdowania minimów do atakowania systemów AI
Przykład 1: Regresja liniowa ze spadkiem gradientu w TensorFlow 2.0
Przykład 1 Notatnik
Przed przejściem do kodu TensorFlow ważne jest, aby zapoznać się z opadaniem gradientowym i regresją liniową.
Co to jest zejście gradientowe?
Mówiąc najprościej, jest to numeryczna technika znajdowania danych wejściowych do układu równań, które minimalizują jego wynik. W kontekście uczenia maszynowego, ten układ równań to nasz model , dane wejściowe to nieznane parametry modelu, a dane wyjściowe to funkcja straty , którą należy zminimalizować, która reprezentuje, jak duży jest błąd między modelem a naszymi danymi. W przypadku niektórych problemów (takich jak regresja liniowa) istnieją równania umożliwiające bezpośrednie obliczenie parametrów, które minimalizują nasz błąd, ale w przypadku większości praktycznych zastosowań, aby uzyskać zadowalające rozwiązanie, potrzebujemy technik numerycznych, takich jak opadanie gradientowe.
Najważniejszym punktem tego artykułu jest to, że opadanie gradientu zwykle wymaga ułożenia naszych równań i użycia rachunku różniczkowego, aby wyprowadzić związek między naszą funkcją straty a naszymi parametrami. Dzięki TensorFlow (i dowolnym nowoczesnym narzędziu do autoróżnicowania) rachunek jest obsługiwany za nas, dzięki czemu możemy skupić się na projektowaniu rozwiązania, a nie tracić czasu na jego wdrożenie.
Oto jak wygląda prosty problem regresji liniowej. Mamy próbkę wzrostu (h) i wagi (w) 150 dorosłych mężczyzn i zaczynamy od niedokładnego odgadnięcia nachylenia i odchylenia standardowego tej linii. Po około 15 iteracjach opadania gradientu dochodzimy do prawie optymalnego rozwiązania.
Zobaczmy, jak wyprodukowaliśmy powyższe rozwiązanie przy użyciu TensorFlow 2.0.
W przypadku regresji liniowej mówimy, że wagi można przewidzieć za pomocą liniowego równania wysokości.
Chcemy znaleźć parametry α i β (nachylenie i przecięcie), które minimalizują średni kwadratowy błąd (stratę) między przewidywaniami a wartościami rzeczywistymi. Zatem nasza funkcja straty (w tym przypadku „błąd średniokwadratowy” lub MSE) wygląda tak:
Możemy zobaczyć, jak wygląda błąd średniokwadratowy dla kilku niedoskonałych linii, a następnie z dokładnym rozwiązaniem (α=6,04, β=-230,5).
Zrealizujmy ten pomysł z TensorFlow. Pierwszą rzeczą do zrobienia jest zakodowanie funkcji straty przy użyciu tensorów i funkcji tf.*
.
def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse
To wygląda całkiem prosto. Wszystkie standardowe operatory algebraiczne są przeciążone dla tensorów, więc musimy tylko upewnić się, że zmienne, które optymalizujemy, są tensorami, a do czegokolwiek innego używamy metod tf.*
.
Następnie wszystko, co musimy zrobić, to umieścić to w pętli opadania gradientu:
def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]
Poświęćmy chwilę, aby docenić, jakie to jest schludne. Spadek gradientu wymaga obliczenia pochodnych funkcji straty w odniesieniu do wszystkich zmiennych, które próbujemy zoptymalizować. Rachunek powinien być w to zaangażowany, ale tak naprawdę nic z tego nie zrobiliśmy. Magia polega na tym, że:
- TensorFlow buduje wykres obliczeniowy każdego obliczenia wykonanego w ramach
tf.GradientTape()
. - TensorFlow wie, jak obliczyć pochodne (gradienty) każdej operacji, dzięki czemu może określić, w jaki sposób dowolna zmienna na wykresie obliczeń wpływa na dowolną inną zmienną.
Jak wygląda proces z różnych punktów wyjścia?
Opadanie gradientowe zbliża się do optymalnego MSE, ale w rzeczywistości zbiega się do znacznie innego nachylenia i przecięcia niż optimum w obu przykładach. W niektórych przypadkach jest to po prostu opadanie gradientowe zbieżne do lokalnego minimum, co jest nieodłącznym wyzwaniem w przypadku algorytmów opadania gradientowego. Ale regresja liniowa ma tylko jedno globalne minimum. Jak więc skończyliśmy na złym nachyleniu i przechwyciliśmy?
W tym przypadku problem polega na tym, że uprościliśmy kod w celach demonstracyjnych. Nie znormalizowaliśmy naszych danych, a parametr nachylenia ma inną charakterystykę niż parametr przecięcia. Niewielkie zmiany nachylenia mogą wywołać ogromne zmiany w stratach, podczas gdy niewielkie zmiany przecięcia mają bardzo mały wpływ. Ta ogromna różnica w skali trenowalnych parametrów prowadzi do tego, że nachylenie dominuje w obliczeniach gradientu, a parametr przecięcia jest prawie ignorowany.
Dzięki temu zniżanie gradientowe skutecznie wyszukuje najlepsze nachylenie bardzo bliskie początkowej przewidywanej wartości przecięcia. A ponieważ błąd jest tak bliski optimum, gradienty wokół niego są bardzo małe, więc każda kolejna iteracja przesuwa się tylko odrobinę. Normalizacja naszych danych w pierwszej kolejności radykalnie poprawiłaby to zjawisko, ale nie wyeliminowałaby go.
Był to stosunkowo prosty przykład, ale w następnych sekcjach zobaczymy, że ta funkcja „automatycznego różnicowania” może poradzić sobie z dość złożonymi sprawami.
Przykład 2: Maksymalnie rozłożone wektory jednostkowe
Przykład 2 Notatnik
Kolejny przykład jest oparty na zabawnym ćwiczeniu z głębokiego uczenia się w kursie głębokiego uczenia się, w którym uczestniczyłem w zeszłym roku.
Istota problemu polega na tym, że mamy „wariacyjny autokoder” (VAE), który może generować realistyczne twarze z zestawu 32 liczb o normalnym rozkładzie. W celu identyfikacji podejrzanego chcemy użyć VAE do stworzenia zróżnicowanego zestawu (teoretycznych) twarzy świadka do wyboru, a następnie zawęzić wyszukiwanie, tworząc więcej twarzy podobnych do wybranych. W tym ćwiczeniu zasugerowano randomizację początkowego zestawu wektorów, ale chciałem znaleźć optymalny stan początkowy.
Możemy sformułować problem w ten sposób: Mając 32-wymiarową przestrzeń, znajdź zbiór X wektorów jednostkowych, które są maksymalnie rozłożone. W dwóch wymiarach łatwo to dokładnie obliczyć. Ale dla trzech wymiarów (lub 32 wymiarów!) nie ma jednoznacznej odpowiedzi. Jeśli jednak uda nam się zdefiniować prawidłową funkcję straty, która jest na poziomie minimum, gdy osiągnęliśmy nasz docelowy stan, być może gradient może nam pomóc w osiągnięciu tego celu.
Zaczniemy od losowego zestawu 20 wektorów, jak pokazano powyżej, i poeksperymentujemy z trzema różnymi funkcjami straty, z których każda ma coraz większą złożoność, aby zademonstrować możliwości TensorFlow.
Najpierw zdefiniujmy naszą pętlę treningową. Umieścimy całą logikę TensorFlow pod self.calc_loss()
, a następnie możemy po prostu nadpisać tę metodę dla każdej techniki, przetwarzając tę pętlę.
# Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients
Pierwsza technika do wypróbowania jest najprostsza. Definiujemy metrykę rozproszenia, która jest kątem wektorów, które są najbliżej siebie. Chcemy zmaksymalizować rozprzestrzenianie się, ale konwencjonalnie sprawiamy, że jest to problem minimalizacji. Więc po prostu bierzemy ujemną wartość wskaźnika spreadu:
class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric
Niektóre magia Matplotlib dadzą wizualizację.
To jest niezgrabne (dosłownie!), ale działa. Tylko dwa z 20 wektorów są aktualizowane na raz, powiększając odstęp między nimi, aż przestaną być najbliższe, a następnie przełączając się na zwiększanie kąta między nowymi dwoma najbliższymi wektorami. Należy zauważyć, że to działa . Widzimy, że TensorFlow był w stanie przekazać gradienty za pomocą metody tf.reduce_min()
i tf.acos()
, aby zrobić to, co należy.
Spróbujmy czegoś bardziej skomplikowanego. Wiemy, że w optymalnym rozwiązaniu wszystkie wektory powinny mieć ten sam kąt do swoich najbliższych sąsiadów. Dodajmy więc „odchylenie minimalnych kątów” do funkcji straty.
class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric
Ten samotny wektor skierowany na północ teraz szybko łączy się ze swoimi rówieśnikami, ponieważ kąt do najbliższego sąsiada jest ogromny i zaostrza składnik wariancji, który jest teraz minimalizowany. Ale ostatecznie jest to ostatecznie napędzane przez globalny minimalny kąt, który powoli rośnie. Pomysły, które muszę ulepszyć to generalnie działają w tym przypadku 2D, ale nie w większych wymiarach.
Ale zbytnie skupianie się na jakości tej matematycznej próby nie ma sensu. Zobacz, ile operacji tensorowych jest zaangażowanych w obliczenia średniej i wariancji oraz jak TensorFlow z powodzeniem śledzi i różnicuje każde obliczenia dla każdego składnika macierzy wejściowej. I nie musieliśmy robić żadnego ręcznego rachunku. Po prostu połączyliśmy trochę prostej matematyki, a TensorFlow zrobił za nas rachunek.
Na koniec spróbujmy jeszcze jednej rzeczy: rozwiązania opartego na sile. Wyobraź sobie, że każdy wektor to mała planeta przywiązana do centralnego punktu. Każda planeta emituje siłę, która odpycha ją od innych planet. Gdybyśmy mieli przeprowadzić symulację fizyki tego modelu, powinniśmy otrzymać pożądane rozwiązanie.
Moja hipoteza jest taka, że zejście gradientowe też powinno działać. W optymalnym rozwiązaniu siła styczna na każdej planecie z każdej innej planety powinna znosić się do zerowej siły netto (gdyby nie była zerem, planety poruszałyby się). Obliczmy więc wielkość siły na każdym wektorze i użyjmy spadku gradientu, aby przesunąć go w kierunku zera.
Najpierw musimy zdefiniować metodę obliczania siły za pomocą metod tf.*
:
class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component
Następnie definiujemy naszą funkcję straty za pomocą funkcji siły powyżej. Akumulujemy siłę wypadkową na każdym wektorze i obliczamy jej wielkość. Przy naszym optymalnym rozwiązaniu wszystkie siły powinny znosić się i powinniśmy mieć siłę zerową.

def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list))
Rozwiązanie nie tylko działa pięknie (poza pewnym chaosem w pierwszych kilku klatkach), ale prawdziwa zasługa należy do TensorFlow. To rozwiązanie obejmowało wiele pętli for
, instrukcję if
i ogromną sieć obliczeń, a TensorFlow z powodzeniem prześledził dla nas gradienty.
Przykład 3: Generowanie przeciwstawnych danych wejściowych AI
Przykład 3 Notatnik
W tym momencie czytelnicy mogą pomyśleć: „Hej! Ten post nie miał dotyczyć głębokiego uczenia się!” Ale technicznie wprowadzenie odnosi się do wyjścia poza „ trening modeli głębokiego uczenia”. W tym przypadku nie trenujemy , ale zamiast tego wykorzystujemy pewne matematyczne właściwości wstępnie wytrenowanej głębokiej sieci neuronowej, aby oszukać ją, aby dała nam złe wyniki. Okazało się to znacznie prostsze i skuteczniejsze niż przypuszczano. Wystarczył kolejny krótki blob kodu TensorFlow 2.0.
Zaczynamy od znalezienia klasyfikatora obrazu do ataku. Wykorzystamy jedno z najlepszych rozwiązań w konkursie Kaggle Dogs vs. Cats; konkretnie rozwiązanie zaprezentowane przez Kagglera „uysimty”. Wszyscy zasługują na to, że dostarczyli skuteczny model kot kontra pies i dostarczyli świetną dokumentację. Jest to potężny model składający się z 13 milionów parametrów w 18 warstwach sieci neuronowej. (Czytelnicy mogą przeczytać więcej na ten temat w odpowiednim zeszycie.)
Proszę zauważyć, że celem nie jest podkreślenie jakichkolwiek niedociągnięć w tej konkretnej sieci, ale pokazanie, jak podatna jest każda standardowa sieć neuronowa z dużą liczbą wejść.
Przy odrobinie majsterkowania udało mi się wymyślić, jak załadować model i wstępnie przetworzyć obrazy, które mają zostać przez niego sklasyfikowane.
To wygląda na naprawdę solidny klasyfikator! Wszystkie klasyfikacje próbek są prawidłowe i mają ufność powyżej 95%. Zaatakujmy to!
Chcemy stworzyć obraz, który jest oczywiście kotem, ale niech klasyfikator zdecyduje, że jest to pies z dużą pewnością siebie. Jak możemy to zrobić?
Zacznijmy od obrazu kota, który jest poprawnie klasyfikowany, a następnie zastanówmy się, jak drobne modyfikacje w każdym kanale koloru (wartości 0-255) danego piksela wejściowego wpływają na końcowy wynik klasyfikatora. Modyfikacja jednego piksela prawdopodobnie nie da wiele, ale być może skumulowane poprawki wszystkich wartości 128x128x3 = 49 152 pikseli osiągną nasz cel.
Skąd wiemy, w jaki sposób wypchnąć każdy piksel? Podczas normalnego treningu sieci neuronowych staramy się zminimalizować utratę między etykietą docelową a etykietą przewidywaną, używając gradientu w TensorFlow do jednoczesnej aktualizacji wszystkich 13 milionów wolnych parametrów. W tym przypadku zamiast tego zostawimy 13 milionów parametrów ustalonych i dostosujemy wartości pikseli samego wejścia.
Jaka jest nasza funkcja straty? Cóż, tak bardzo obraz wygląda jak kot! Jeśli obliczymy pochodną wartości kota w odniesieniu do każdego piksela wejściowego, wiemy, w jaki sposób popchnąć każdy z nich, aby zminimalizować prawdopodobieństwo klasyfikacji kota.
def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad
Magia Matplotlib ponownie pomaga zwizualizować wyniki.
Wow! Dla ludzkiego oka każdy z tych obrazów jest identyczny. Jednak po czterech iteracjach przekonaliśmy klasyfikatora, że to pies, z 99,4% pewnością!
Upewnijmy się, że to nie jest przypadek i działa też w drugą stronę.
Sukces! Klasyfikator pierwotnie przewidział to poprawnie jako pies z 98,4 procentową pewnością, a teraz uważa, że jest to kot z 99,8 procentową pewnością.
Na koniec spójrzmy na przykładową łatkę obrazu i zobaczmy, jak się zmieniła.
Zgodnie z oczekiwaniami, ostateczna łata jest bardzo podobna do oryginału, a każdy piksel przesuwa się tylko z -4 do +4 w wartości intensywności kanału czerwonego. Ta zmiana nie wystarczy, aby człowiek mógł odróżnić różnicę, ale całkowicie zmienia wyjście klasyfikatora.
Końcowe myśli: optymalizacja zjazdu gradientowego
W tym artykule przyjrzeliśmy się ręcznym stosowaniu gradientów do naszych parametrów, które można trenować, ze względu na prostotę i przejrzystość. Jednak w prawdziwym świecie naukowcy zajmujący się danymi powinni od razu zacząć korzystać z optymalizatorów , ponieważ są one zwykle znacznie bardziej efektywne bez dodawania rozrostu kodu.
Istnieje wiele popularnych optymalizatorów, w tym RMSprop, Adagrad i Adadelta, ale najpopularniejszym jest prawdopodobnie Adam . Czasami nazywa się je „metodami adaptacyjnego tempa uczenia się”, ponieważ dynamicznie utrzymują różne tempo uczenia się dla każdego parametru. Wiele z nich używa terminów momentum i przybliża pochodne wyższego rzędu w celu uniknięcia lokalnych minimów i osiągnięcia szybszej zbieżności.
W animacji zapożyczonej od Sebastiana Rudera widzimy ścieżkę różnych optymalizatorów schodzących po powierzchni strat. Zademonstrowane przez nas techniki manualne są najbardziej porównywalne do „SGD”. Najskuteczniejszy optymalizator nie będzie taki sam dla każdej powierzchni strat; jednak bardziej zaawansowane optymalizatory zazwyczaj działają lepiej niż te prostsze.
Jednak bycie ekspertem od optymalizatorów rzadko jest przydatne — nawet dla tych, którzy chcą świadczyć usługi rozwoju sztucznej inteligencji. Lepiej jest wykorzystać czas programistów na zapoznanie się z parą, tylko po to, aby zrozumieć, jak poprawiają one gradient w TensorFlow. Następnie mogą domyślnie używać Adama i wypróbowywać różne tylko wtedy, gdy ich modele nie są zbieżne.
Dla czytelników, którzy są naprawdę zainteresowani tym, jak i dlaczego działają te optymalizatory, przegląd Rudera — w którym pojawia się animacja — jest jednym z najlepszych i najbardziej wyczerpujących zasobów na ten temat.
Zaktualizujmy nasze rozwiązanie regresji liniowej z pierwszej sekcji, aby korzystać z optymalizatorów. Poniżej znajduje się oryginalny kod spadku gradientu przy użyciu gradientów ręcznych.
# Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]
Oto ten sam kod, który używa zamiast tego optymalizatora. Zobaczysz, że to prawie żaden dodatkowy kod (zmienione linie są podświetlone na niebiesko):
# Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))
Otóż to! Zdefiniowaliśmy optymalizator RMSprop
poza pętlą opadania gradientu, a następnie po każdym obliczeniu gradientu użyliśmy metody optimizer.apply_gradients()
w celu zaktualizowania parametrów, które można trenować. Optymalizator jest zdefiniowany poza pętlą, ponieważ będzie śledził historyczne gradienty w celu obliczenia dodatkowych warunków, takich jak pęd i pochodne wyższego rzędu.
Zobaczmy, jak to wygląda z optymalizatorem RMSprop .
Wygląda świetnie! Teraz spróbujmy z optymalizatorem Adama .
Whoa, co się tutaj stało? Wygląda na to, że mechanika pędu u Adama powoduje, że wielokrotnie przeskakuje on nad optymalne rozwiązanie i odwraca kurs. Zwykle ta mechanika pędu pomaga w przypadku złożonych powierzchni strat, ale w tym prostym przypadku szkodzi nam. Podkreśla to radę, aby wybór optymalizatora był jednym z hiperparametrów do dostrojenia podczas uczenia modelu.
Każdy, kto chce zbadać głębokie uczenie, będzie chciał zapoznać się z tym wzorcem, ponieważ jest on szeroko stosowany w niestandardowych architekturach TensorFlow, gdzie istnieje potrzeba posiadania złożonej mechaniki strat, która nie jest łatwa do włączenia w standardowy przepływ pracy. W tym prostym przykładzie ze spadkiem gradientu TensorFlow były tylko dwa parametry, które można trenować, ale jest to konieczne podczas pracy z architekturami zawierającymi setki milionów parametrów do optymalizacji.
Gradientowe opadanie w TensorFlow: od znajdowania minimów do atakowania systemów AI
Wszystkie fragmenty kodu i obrazy zostały utworzone z notebooków w odpowiednim repozytorium GitHub. Zawiera również podsumowanie wszystkich sekcji, z linkami do poszczególnych notatników, dla czytelników, którzy chcą zobaczyć pełny kod. W celu uproszczenia przekazu pominięto wiele szczegółów, które można znaleźć w obszernej dokumentacji wbudowanej.
Mam nadzieję, że ten artykuł był wnikliwy i skłonił Cię do zastanowienia się nad sposobami wykorzystania gradientu w TensorFlow. Nawet jeśli nie używasz go samodzielnie, miejmy nadzieję, że wyjaśni to, jak działają wszystkie nowoczesne architektury sieci neuronowych — utwórz model, zdefiniuj funkcję straty i użyj gradientu, aby dopasować model do zestawu danych.
Jako Partner Google Cloud, certyfikowani przez Google eksperci Toptal są dostępni dla firm na żądanie w ich najważniejszych projektach.