Od rozwiązywania równań do głębokiego uczenia się: samouczek Pythona TensorFlow
Opublikowany: 2022-03-11Ostatnio w świecie sztucznej inteligencji nastąpiły niezwykłe postępy, od szeroko nagłośnionych postępów w autonomicznych samochodach po maszyny, które teraz tworzą imitacje Chopina lub są po prostu naprawdę dobre w grach wideo.
Kluczem do tych postępów jest szereg narzędzi, które pomagają uzyskać głębokie uczenie się i inne modele uczenia maszynowego, z Torch, Caffe i Theano na czele. Jednak odkąd Google Brain przeszedł na oprogramowanie open source w listopadzie 2015 r. z własnym frameworkiem TensorFlow, zauważyliśmy, że popularność tej biblioteki oprogramowania gwałtownie wzrosła i stała się najpopularniejszym frameworkiem do głębokiego uczenia się.
Dlaczego tak się stało? Powodem jest bogactwo dostępnego wsparcia i dokumentacji, gotowość produkcyjna, łatwość dystrybucji obliczeń na różnych urządzeniach oraz doskonałe narzędzie do wizualizacji: TensorBoard.
Ostatecznie TensorFlow udaje się połączyć wszechstronny i elastyczny zestaw funkcji technicznych z dużą łatwością użytkowania.
W tym artykule poznasz mechanikę tego narzędzia, używając go do rozwiązania ogólnego problemu numerycznego, zupełnie poza tym, co zwykle wiąże się z uczeniem maszynowym, przed wprowadzeniem jego zastosowań w uczeniu głębokim za pomocą prostej implementacji sieci neuronowej.
Zanim zaczniesz
Zakłada się podstawową znajomość metod uczenia maszynowego. Jeśli chcesz nadrobić zaległości, sprawdź ten bardzo przydatny post.
Ponieważ będziemy demonstrować API Pythona, zrozumienie Numpy jest również korzystne.
Aby skonfigurować TensorFlow, postępuj zgodnie z instrukcjami znajdującymi się tutaj.
Jeśli używasz systemu Windows, należy zauważyć, że w momencie pisania tego tekstu musisz używać Pythona 3.4+, a nie 2.7.
Następnie, gdy będziesz gotowy, powinieneś być w stanie zaimportować bibliotekę za pomocą:
import tensorflow as tf
Krok 1 z 2 rozwiązania TensorFlow: Utwórz wykres
Konstrukcja programów TensorFlow generalnie składa się z dwóch głównych kroków, z których pierwszym jest zbudowanie wykresu obliczeniowego, który będzie opisywał obliczenia, które chcesz przeprowadzić, ale w rzeczywistości nie wykonuje ich ani nie przechowuje żadnych wartości.
Jak w przypadku każdego grafu, mamy węzły i krawędzie. Krawędzie reprezentują tensory, tensor reprezentujący n-wymiarową tablicę. Na przykład tensor o wymiarze (lub rang w języku TensorFlow) 0 jest skalarem, rang 1 wektorem, rangą 2 macierzą i tak dalej.
Węzły reprezentują operacje, które generują tensor wyjściowy, przyjmując w razie potrzeby tensory jako dane wejściowe. Takie operacje obejmują dodawanie ( tf.add
), mnożenie macierzy ( tf.matmul
), a także tworzenie stałych ( tf.constant
).
Połączmy więc kilka z nich dla naszego pierwszego wykresu.
a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)
Tutaj stworzyliśmy trzy operacje, dwie z nich do tworzenia stałych tablic jednowymiarowych.
Typy danych są wywnioskowane z przekazanego argumentu wartości lub można je oznaczyć argumentem dtype
. Gdybym nie zrobił tego dla b
, to int32
zostałby wywnioskowany, a błąd zgłoszony jako tf.add
próbowałby zdefiniować dodatek na dwóch różnych typach.
Krok 2 z 2 rozwiązania TensorFlow: Wykonaj operacje
Wykres jest zdefiniowany, ale aby faktycznie wykonać na nim jakiekolwiek obliczenia (lub na dowolnej jego części), musimy ustawić sesję TensorFlow.
sess = tf.Session()
Alternatywnie, jeśli uruchamiamy sesję w interaktywnej powłoce, takiej jak IPython, używamy:
sess = tf.InteractiveSession()
Metoda run
na obiekcie sesji jest jednym ze sposobów oceny tensora.
Dlatego, aby ocenić obliczenia dodawania zdefiniowane powyżej, przekazujemy „total”, tensor do pobrania, który reprezentuje wynik operacji tf.add
.
print(sess.run(total)) # [ 5.5 8. ]
W tym momencie przedstawiamy klasę Variable firmy TensorFlow. Podczas gdy stałe są stałą częścią definicji wykresu, zmienne można aktualizować. Konstruktor klasy wymaga wartości początkowej, ale nawet wtedy zmienne potrzebują operacji, aby jawnie je zainicjować przed wykonaniem jakichkolwiek innych operacji na nich.
Zmienne przechowują stan wykresu w konkretnej sesji, więc powinniśmy obserwować, co dzieje się z wieloma sesjami korzystającymi z tego samego wykresu, aby lepiej zrozumieć zmienne.
# Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()
Przygotowaliśmy wykres i dwie sesje.
Po wykonaniu inicjalizacji w obu sesjach (jeśli nie uruchomimy tego, a następnie ocenimy zmienną, natkniemy się na błąd) wykonujemy operację przypisania tylko na jednej sesji. Jak widać, wartość zmiennej utrzymuje się, ale nie pomiędzy sesjami.
Dostarczanie wykresu do rozwiązywania problemów numerycznych
Inną ważną koncepcją TensorFlow jest symbol zastępczy. Podczas gdy zmienne przechowują stan, symbole zastępcze służą do definiowania, jakich danych wejściowych może oczekiwać wykres oraz ich typu danych (i opcjonalnie ich kształtu). Następnie możemy wprowadzić dane do wykresu za pomocą tych symboli zastępczych, gdy wykonujemy obliczenia.
Wykres TensorFlow zaczyna przypominać sieci neuronowe, które ostatecznie chcemy wytrenować, ale wcześniej wykorzystajmy te koncepcje do rozwiązania typowego problemu numerycznego ze świata finansów.
Załóżmy, że chcemy znaleźć y
w równaniu takim jak to:
dla danego v
(ze stałymi C
i P
).
Jest to wzór do obliczania stopy zwrotu do terminu zapadalności ( y
) dla obligacji o wartości rynkowej v
, kapitale P
i kuponie C
wypłacanym co pół roku, ale z przepływami pieniężnymi zdyskontowanymi za pomocą ciągłej kapitalizacji.
Zasadniczo musimy rozwiązać takie równanie metodą prób i błędów, a następnie wybierzemy metodę bisekcji, aby wyzerować naszą końcową wartość dla y
.
Najpierw zamodelujemy ten problem jako wykres TensorFlow.
C
i P
są stałymi stałymi i stanowią część definicji naszego wykresu. Chcemy mieć proces, który udoskonala dolne i górne granice y
. Dlatego te granice (oznaczone a
b
) są dobrymi kandydatami na zmienne, które należy zmieniać po każdym odgadnięciu y
(przyjętego jako a
b
).
# Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )
Mamy więc teraz listę operacji i zmiennych, z których każda może być oceniana w odniesieniu do konkretnej sesji. Niektóre z tych operacji polegają na innych operacjach, które mają zostać uruchomione, więc uruchomienie, powiedzmy, v_guess
uruchomi reakcję łańcuchową, aby inne tensory, takie jak C
i P
, zostały ocenione jako pierwsze.

Niektóre z tych operacji opierają się na symbolu zastępczym, dla którego należy określić wartość, więc w jaki sposób faktycznie wprowadzamy tę wartość?
Odbywa się to za pomocą argumentu feed_dict
w samej funkcji run
.
Jeśli chcemy obliczyć a_
, wstawiamy wartość naszego symbolu zastępczego v_target
, w ten sposób:
sess.run(a_, feed_dict={v_target: 100})
dając nam 0.0.
Podłączamy v_target
130 i otrzymujemy -10.0.
Jest to nasza operacja „krokowa”, która w rzeczywistości wymaga wykonania wszystkich innych operacji jako warunku wstępnego iw efekcie wykonuje cały wykres. Jest to również operacja, która faktycznie zmienia rzeczywisty stan w trakcie naszej sesji. Dlatego im częściej uruchamiamy ten krok, tym bardziej stopniowo b
a
kierunku rzeczywistej wartości y
.
Powiedzmy, że nasza wartość v
w naszym równaniu jest równa 95. Rozpocznijmy sesję i wykonajmy na niej nasz wykres 100 razy.
# Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})
Jeśli teraz ocenimy tensor y
, otrzymamy coś przypominającego pożądaną odpowiedź
print(sess.run(y)) # 0.125163
Sieci neuronowe
Teraz, gdy znamy już mechanikę TensorFlow, możemy połączyć to z kilkoma dodatkowymi operacjami uczenia maszynowego wbudowanymi w TensorFlow, aby wytrenować prostą sieć neuronową.
Tutaj chcielibyśmy sklasyfikować punkty danych w układzie współrzędnych 2D w zależności od tego, czy mieszczą się one w określonym regionie — okręgu o promieniu 0,5 wyśrodkowanym na początku.
Oczywiście można to konkretnie zweryfikować, po prostu sprawdzając dany punkt (a,b)
, jeśli a^2 + b^2 < 0.5
, ale na potrzeby tego eksperymentu z uczeniem maszynowym chcielibyśmy zamiast tego przekazać a zestaw treningowy: seria losowych punktów i to, czy mieszczą się w naszym zamierzonym regionie. Oto jeden ze sposobów na stworzenie tego:
import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)
Stworzymy sieć neuronową o następujących cechach:
- Składa się z warstwy wejściowej z dwoma węzłami, w których zasilamy naszą serię dwuwymiarowych wektorów zawartych w „losowych_punktach”. Będzie to reprezentowane przez symbol zastępczy oczekujący na dane treningowe.
- Warstwa wyjściowa będzie również miała dwa węzły, więc musimy wprowadzić naszą serię etykiet treningowych („is_inside_circle”) do symbolu zastępczego dla skalara, a następnie przekonwertować te wartości na jednowymiarowy wektor dwuwymiarowy.
- Będziemy mieli jedną ukrytą warstwę składającą się z trzech węzłów, więc będziemy musieli użyć zmiennych dla naszych macierzy wag i wektorów obciążenia, ponieważ są to wartości, które należy doprecyzować podczas wykonywania treningu.
INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Aby uzupełnić definicję naszego wykresu, definiujemy kilka operacji, które pomogą nam wytrenować zmienne w celu uzyskania lepszego klasyfikatora. Obejmują one obliczenia macierzy, funkcje aktywacji i optymalizator.
LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Po skonfigurowaniu naszego wykresu nadszedł czas, aby skonfigurować sesję i uruchomić „train_step” (który uruchamia również wszystkie wymagane operacje). Niektóre z tych operacji używają symboli zastępczych, więc należy podać wartości dla nich. Ten etap uczenia reprezentuje jedną epokę w naszym algorytmie uczenia się i jako taki jest zapętlony przez liczbę epok, które chcemy uruchomić. W celach informacyjnych możemy uruchomić inne części wykresu, takie jak tensor „straty”.
EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))
Po wytrenowaniu algorytmu możemy wprowadzić punkt i uzyskać dane wyjściowe sieci neuronowej w następujący sposób:
sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]
Możemy sklasyfikować punkt jako poza okręgiem, jeśli pierwszy element wektora wyjściowego jest większy niż 0,5, w przeciwnym razie wewnątrz.
Uruchamiając tensor output_layer
dla wielu punktów, możemy uzyskać wyobrażenie o tym, jak uczący się wyobraża sobie region zawierający pozytywnie sklasyfikowane punkty. Warto poeksperymentować z rozmiarem zestawu treningowego, szybkością uczenia się i innymi parametrami, aby zobaczyć, jak blisko możemy zbliżyć się do zamierzonego okręgu.
Szybkość uczenia się: 0,01
Epoki: 1000
Szybkość uczenia się: 0,01
Epoki: 1000
Szybkość uczenia się: 0,01
Epoki: 10000
Szybkość uczenia się: 0,001
Epoki: 10000
Zakończyć
To dobra lekcja, że zwiększony zestaw treningowy lub ilość epok nie gwarantuje dobrego ucznia — szybkość uczenia się powinna być odpowiednio dostosowana.
Mamy nadzieję, że te demonstracje dały dobry wgląd w podstawowe zasady TensorFlow i zapewniły solidną podstawę do wdrożenia bardziej złożonych technik.
Nie omawialiśmy takich pojęć, jak Tensorboard lub trenowanie naszych modeli na procesorach graficznych, ale są one dobrze omówione w dokumentacji TensorFlow. W dokumentacji można znaleźć wiele przepisów, które pomogą Ci przyspieszyć wykonywanie ekscytujących zadań głębokiego uczenia się przy użyciu tej potężnej platformy!