Jak zastosować obcinanie gradientu w TensorFlow?

96

Biorąc pod uwagę przykładowy kod .

Chciałbym wiedzieć, jak zastosować obcinanie gradientu w tej sieci w sieci RNN, gdzie istnieje możliwość eksplozji gradientów.

tf.clip_by_value(t, clip_value_min, clip_value_max, name=None)

To jest przykład, który można wykorzystać, ale gdzie mam go wprowadzić? W obronie RNN

    lstm_cell = rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0)
    # Split data because rnn cell needs a list of inputs for the RNN inner loop
    _X = tf.split(0, n_steps, _X) # n_steps
tf.clip_by_value(_X, -1, 1, name=None)

Ale to nie ma sensu, ponieważ tensor _X jest wejściem, a nie gradem, który ma być przycięty?

Czy muszę w tym celu zdefiniować własny Optimizer, czy też istnieje prostsza opcja?

Arsenal Fanatic
źródło

Odpowiedzi:

143

Obcinanie gradientu musi nastąpić po obliczeniu gradientów, ale przed ich zastosowaniem w celu zaktualizowania parametrów modelu. W twoim przykładzie obie te rzeczy są obsługiwane przez AdamOptimizer.minimize()metodę.

Aby przyciąć gradienty, musisz jawnie obliczyć, przyciąć i zastosować je zgodnie z opisem w tej sekcji w dokumentacji API TensorFlow . W szczególności musisz zastąpić wywołanie minimize()metody czymś podobnym do następującego:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
gvs = optimizer.compute_gradients(cost)
capped_gvs = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs)
Styrke
źródło
4
Styrke, dzięki za wiadomość. Czy wiesz, jakie są następne kroki, aby faktycznie przeprowadzić iterację optymalizatora? Zwykle instancja optymalizatora jest tworzona jako, optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) a następnie wykonywana jest iteracja optymalizatora, optimizer.run()ale użycie optimizer.run()nie wydaje się działać w tym przypadku?
applecider
6
Ok, optimizer.apply_gradients(capped_gvs)x = optimizer.apply_gradients(capped_gvs)x.run(...)
rozumiem,
3
Krzyczę do @ remi-cuingnet za miłą sugestię edycji . (Co niestety zostało odrzucone przez pochopnych recenzentów)
Styrke
To daje mi UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.Więc w jakiś sposób moje rzadkie gradienty są zamieniane na gęste. Masz pomysł, jak rozwiązać ten problem?
Pekka
8
Właściwie właściwą drogą do przycinania gradientów (zgodnie z dokumentacją tensorflow, informatykami i logiką) jest tf.clip_by_global_norm, jak sugeruje @danijar
gdelab
116

Pomimo tego, co wydaje się popularne, prawdopodobnie chcesz przyciąć cały gradient według jego globalnej normy:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimize = optimizer.apply_gradients(zip(gradients, variables))

Przycinanie każdej macierzy gradientu indywidualnie zmienia ich względną skalę, ale jest również możliwe:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients = [
    None if gradient is None else tf.clip_by_norm(gradient, 5.0)
    for gradient in gradients]
optimize = optimizer.apply_gradients(zip(gradients, variables))

W TensorFlow 2 taśma oblicza gradienty, optymalizatory pochodzą z Keras i nie musimy przechowywać operacji aktualizacji, ponieważ działa ona automatycznie bez przekazywania jej do sesji:

optimizer = tf.keras.optimizers.Adam(1e-3)
# ...
with tf.GradientTape() as tape:
  loss = ...
variables = ...
gradients = tape.gradient(loss, variables)
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimizer.apply_gradients(zip(gradients, variables))
danijar
źródło
10
Dobry przykład z clip_by_global_norm()! Jest to również opisane the correct way to perform gradient clippingw dokumentach tensorflow
MZHm
9
@Escachator Jest to empiryczne i zależy od Twojego modelu i ewentualnie zadania. To, co robię, to wizualizować normę gradientu, tf.global_norm(gradients)aby zobaczyć jego zwykły zakres, a następnie przycinać nieco powyżej tego, aby wartości odstające nie zepsuły treningu.
danijar
1
czy nadal zadzwoniłbyś opt.minimize()po, czy zadzwoniłbyś do czegoś innego, jak opt.run()sugeruje to w niektórych komentarzach do innych odpowiedzi?
reese0106
3
@ reese0106 Nie, optimizer.minimize(loss)to tylko skrót do obliczania i stosowania gradientów. Możesz uruchomić przykład w mojej odpowiedzi z sess.run(optimize).
danijar,
1
Więc gdybym używał tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)funkcji eksperymentalnej, Twój optimizezastąpiłby mytrain_op poprawny? Teraz moja, train_op = optimizer.minimize(loss, global_step=global_step))więc próbuję się upewnić, że odpowiednio się dostosuję ...
reese0106,
10

Faktycznie jest to właściwie wyjaśnione w dokumentacji. :

Wywołanie minimal () zajmuje się zarówno obliczaniem gradientów, jak i zastosowaniem ich do zmiennych. Jeśli chcesz przetworzyć gradienty przed ich zastosowaniem, możesz zamiast tego użyć optymalizatora w trzech krokach:

  • Oblicz gradienty za pomocą compute_gradients ().
  • Przetwarzaj gradienty, jak chcesz.
  • Zastosuj przetworzone gradienty za pomocą apply_gradients ().

W podanym przez nich przykładzie wykorzystują te 3 kroki:

# Create an optimizer.
opt = GradientDescentOptimizer(learning_rate=0.1)

# Compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)

# grads_and_vars is a list of tuples (gradient, variable).  Do whatever you
# need to the 'gradient' part, for example cap them, etc.
capped_grads_and_vars = [(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars]

# Ask the optimizer to apply the capped gradients.
opt.apply_gradients(capped_grads_and_vars)

Oto MyCapperdowolna funkcja, która ogranicza twój gradient. Lista przydatnych funkcji (innych niżtf.clip_by_value() ) znajduje się tutaj .

Salvador Dali
źródło
czy nadal zadzwoniłbyś opt.minimize()po, czy zadzwoniłbyś do czegoś innego, jak opt.run()sugeruje to w niektórych komentarzach do innych odpowiedzi?
reese0106
@ reese0106 Nie, musisz przypisać opt.apply_gradients(...)zmienną taką jak train_stepna przykład (tak jak w przypadku opt.minimize(). A w głównej pętli wywołujesz to jak zwykle, aby trenowaćsess.run([train_step, ...], feed_dict)
dsalaj
Należy pamiętać, że gradient jest zdefiniowany jako wektor pochodnych strat we wszystkich parametrach modelu. TensorFlow reprezentuje ją jako listę Pythona, która zawiera krotkę dla każdej zmiennej i jej gradientu. Oznacza to, że aby przyciąć normę gradientu, nie możesz przycinać każdego tensora osobno, musisz od razu rozważyć listę (np. Używając tf.clip_by_global_norm(list_of_tensors)).
danijar
8

Dla tych, którzy chcieliby zrozumieć ideę obcinania gradientu (według normy):

Za każdym razem, gdy norma gradientu jest większa niż określony próg, obcinamy normę gradientu tak, aby pozostawała w granicach progu. Ten próg jest czasami ustawiony na 5.

Niech gradient będzie równy g, a wartość max_norm_threshold będzie równa j .

Teraz, jeśli || g || > j , robimy:

g = ( j * g ) / || g ||

To jest implementacja wykonana w tf.clip_by_norm

kmario23
źródło
jeśli muszę ręcznie wybrać próg, czy jest jakaś powszechna metoda, aby to zrobić?
ningyuwhut
To rodzaj czarnej magii sugerowanej w niektórych artykułach. W przeciwnym razie musisz przeprowadzić wiele eksperymentów i dowiedzieć się, który z nich działa lepiej.
kmario23
4

IMO najlepszym rozwiązaniem jest opakowanie twojego optymalizatora dekoratorem estymatora TF tf.contrib.estimator.clip_gradients_by_norm:

original_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
optimizer = tf.contrib.estimator.clip_gradients_by_norm(original_optimizer, clip_norm=5.0)
train_op = optimizer.minimize(loss)

W ten sposób musisz to zdefiniować tylko raz i nie uruchamiać go po każdym obliczeniu gradientu.

Dokumentacja: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm

Ido Cohn
źródło
2

Gradient Clipping zasadniczo pomaga w przypadku eksplodujących lub zanikających gradientów. Powiedzmy, że twoja strata jest zbyt duża, co spowoduje wykładnicze gradienty przepływające przez sieć, co może skutkować wartościami Nan. Aby temu zaradzić, obcinamy gradienty w określonym zakresie (od -1 do 1 lub w dowolnym zakresie zgodnie z warunkiem).

clipped_value=tf.clip_by_value(grad, -range, +range), var) for grad, var in grads_and_vars

gdzie grads _and_vars to pary gradientów (które obliczasz za pomocą tf.compute_gradients) i ich zmienne, do których zostaną zastosowane.

Po obcięciu po prostu stosujemy jego wartość za pomocą optymalizatora. optimizer.apply_gradients(clipped_value)

Raj
źródło