Banyaknya Aplikasi Gradient Descent di TensorFlow

Diterbitkan: 2022-03-11

TensorFlow Google adalah salah satu alat terkemuka untuk melatih dan menerapkan model pembelajaran mendalam. Ini mampu mengoptimalkan arsitektur jaringan saraf yang sangat kompleks dengan ratusan juta parameter, dan dilengkapi dengan beragam alat untuk akselerasi perangkat keras, pelatihan terdistribusi, dan alur kerja produksi. Fitur-fitur canggih ini dapat membuatnya tampak menakutkan dan tidak perlu di luar domain pembelajaran mendalam.

Namun TensorFlow dapat diakses dan digunakan untuk masalah sederhana yang tidak terkait langsung dengan pelatihan model pembelajaran mendalam. Pada intinya, TensorFlow hanyalah pustaka yang dioptimalkan untuk operasi tensor (vektor, matriks, dll.) dan operasi kalkulus yang digunakan untuk melakukan penurunan gradien pada urutan perhitungan yang berubah-ubah. Ilmuwan data yang berpengalaman akan mengenali “gradient descent” sebagai alat dasar untuk matematika komputasi, tetapi biasanya memerlukan penerapan kode dan persamaan khusus aplikasi. Seperti yang akan kita lihat, di sinilah arsitektur "diferensiasi otomatis" modern TensorFlow masuk.

Kasus Penggunaan TensorFlow

  • Contoh 1: Regresi Linier dengan Gradient Descent di TensorFlow 2.0
    • Apa Itu Gradient Descent?
  • Contoh 2: Vektor Satuan Menyebar Secara Maksimal
  • Contoh 3: Menghasilkan Input AI Bermusuhan
  • Pikiran Akhir: Optimasi Keturunan Gradien
  • Penurunan Gradien di TensorFlow: Dari Menemukan Minimum hingga Menyerang Sistem AI

Contoh 1: Regresi Linier dengan Gradient Descent di TensorFlow 2.0

Contoh 1 Buku Catatan

Sebelum masuk ke kode TensorFlow, penting untuk memahami penurunan gradien dan regresi linier.

Apa Itu Gradient Descent?

Dalam istilah yang paling sederhana, ini adalah teknik numerik untuk menemukan input ke sistem persamaan yang meminimalkan outputnya. Dalam konteks pembelajaran mesin, sistem persamaan itu adalah model kami, inputnya adalah parameter model yang tidak diketahui, dan outputnya adalah fungsi kerugian yang harus diminimalkan, yang menunjukkan seberapa besar kesalahan yang ada antara model dan data kami. Untuk beberapa masalah (seperti regresi linier), ada persamaan untuk secara langsung menghitung parameter yang meminimalkan kesalahan kami, tetapi untuk sebagian besar aplikasi praktis, kami memerlukan teknik numerik seperti penurunan gradien untuk sampai pada solusi yang memuaskan.

Poin terpenting dari artikel ini adalah bahwa penurunan gradien biasanya memerlukan pengaturan persamaan dan penggunaan kalkulus untuk mendapatkan hubungan antara fungsi kerugian dan parameter kita. Dengan TensorFlow (dan alat diferensiasi otomatis modern apa pun), kalkulus ditangani untuk kami, sehingga kami dapat fokus merancang solusi, dan tidak perlu menghabiskan waktu untuk implementasinya.

Berikut tampilan pada masalah regresi linier sederhana. Kami memiliki sampel tinggi (h) dan berat (w) dari 150 laki-laki dewasa, dan mulai dengan tebakan yang tidak sempurna dari kemiringan dan simpangan baku dari garis ini. Setelah sekitar 15 iterasi penurunan gradien, kami sampai pada solusi yang mendekati optimal.

Dua animasi yang disinkronkan. Sisi kiri menunjukkan sebar tinggi-berat, dengan garis pas yang dimulai jauh dari data, lalu dengan cepat bergerak ke arahnya, melambat sebelum menemukan kecocokan akhir. Ukuran yang tepat menunjukkan grafik kerugian versus iterasi, dengan setiap frame menambahkan iterasi baru ke grafik. Kerugian dimulai di atas bagian atas grafik pada 2.000, tetapi dengan cepat mendekati garis kerugian minimum dalam beberapa iterasi dalam apa yang tampak seperti kurva logaritmik.

Mari kita lihat bagaimana kami menghasilkan solusi di atas menggunakan TensorFlow 2.0.

Untuk regresi linier, kami mengatakan bahwa bobot dapat diprediksi dengan persamaan linier tinggi.

w-subscript-i,pred sama dengan alpha dot-product h-subscript-i ditambah beta.

Kami ingin menemukan parameter dan (kemiringan dan intersep) yang meminimalkan kesalahan kuadrat rata-rata (kerugian) antara prediksi dan nilai sebenarnya. Jadi fungsi kerugian kami (dalam hal ini, "kesalahan kuadrat rata-rata," atau MSE) terlihat seperti ini:

MSE sama dengan satu per N kali jumlah dari i sama dengan satu ke N kuadrat selisih antara w-subscript-i,true dan w-subscript-i,pred.

Kita dapat melihat bagaimana kesalahan kuadrat rata-rata mencari beberapa garis tidak sempurna, dan kemudian dengan solusi eksaknya (α=6.04, =-230.5).

Tiga salinan dari plot sebar tinggi-berat yang sama, masing-masing dengan garis pas yang berbeda. Yang pertama memiliki w = 4,00 * h + -120.0 dan kerugian 1057.0; garis di bawah data dan kurang curam dari itu. Yang kedua memiliki w = 2,00 * h + 70,0 dan kehilangan 720,8; garisnya berada di dekat bagian atas titik data, dan bahkan tidak terlalu curam. Yang ketiga memiliki w = 60,4 * h + -230,5 dan kerugian 127,1; garis melewati titik-titik data sedemikian rupa sehingga mereka tampak berkerumun merata di sekitarnya.

Mari wujudkan ide ini dengan TensorFlow. Hal pertama yang harus dilakukan adalah mengkodekan fungsi loss menggunakan tensor dan fungsi 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

Ini terlihat cukup sederhana. Semua operator aljabar standar kelebihan beban untuk tensor, jadi kami hanya perlu memastikan variabel yang kami optimalkan adalah tensor, dan kami menggunakan metode tf.* untuk hal lain.

Kemudian, yang harus kita lakukan adalah memasukkan ini ke dalam lingkaran penurunan gradien:

 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]

Mari luangkan waktu sejenak untuk menghargai betapa rapinya ini. Penurunan gradien memerlukan penghitungan turunan dari fungsi kerugian sehubungan dengan semua variabel yang kami coba optimalkan. Kalkulus seharusnya terlibat, tetapi kami tidak benar-benar melakukan semua itu. Keajaibannya adalah kenyataan bahwa:

  1. TensorFlow membuat grafik komputasi dari setiap penghitungan yang dilakukan di bawah tf.GradientTape() .
  2. TensorFlow mengetahui cara menghitung turunan (gradien) dari setiap operasi, sehingga dapat menentukan bagaimana variabel apa pun dalam grafik komputasi memengaruhi variabel lainnya.

Bagaimana proses terlihat dari titik awal yang berbeda?

Grafik tersinkronisasi yang sama seperti sebelumnya, tetapi juga disinkronkan dengan pasangan grafik serupa di bawahnya untuk perbandingan. Grafik iterasi-rugi pasangan bawah serupa tetapi tampaknya menyatu lebih cepat; garis pas yang sesuai dimulai dari atas titik data daripada di bawah, dan lebih dekat ke tempat peristirahatan terakhirnya.

Penurunan gradien menjadi sangat dekat dengan MSE optimal, tetapi sebenarnya konvergen ke kemiringan dan intersep yang secara substansial berbeda dari yang optimal dalam kedua contoh. Dalam beberapa kasus, ini hanyalah penurunan gradien yang konvergen ke minimum lokal, yang merupakan tantangan inheren dengan algoritma penurunan gradien. Tetapi regresi linier terbukti hanya memiliki satu minimum global. Jadi bagaimana kita berakhir di lereng yang salah dan mencegat?

Dalam hal ini, masalahnya adalah kita terlalu menyederhanakan kode demi demonstrasi. Kami tidak menormalkan data kami, dan parameter kemiringan memiliki karakteristik yang berbeda dari parameter intersep. Perubahan kecil dalam kemiringan dapat menghasilkan perubahan besar dalam kerugian, sementara perubahan kecil dalam intersep memiliki efek yang sangat kecil. Perbedaan besar dalam skala parameter yang dapat dilatih ini menyebabkan kemiringan mendominasi perhitungan gradien, dengan parameter intersep hampir diabaikan.

Jadi penurunan gradien secara efektif menemukan kemiringan terbaik yang sangat dekat dengan tebakan intersep awal. Dan karena kesalahannya sangat dekat dengan optimal, gradien di sekitarnya sangat kecil, sehingga setiap iterasi yang berurutan hanya bergerak sedikit. Menormalkan data kami terlebih dahulu akan secara dramatis meningkatkan fenomena ini, tetapi itu tidak akan menghilangkannya.

Ini adalah contoh yang relatif sederhana, tetapi kita akan melihat di bagian selanjutnya bahwa kemampuan "diferensiasi otomatis" ini dapat menangani beberapa hal yang cukup rumit.

Contoh 2: Vektor Satuan Menyebar Secara Maksimal

Contoh 2 Buku Catatan

Contoh berikut ini didasarkan pada latihan pembelajaran mendalam yang menyenangkan dalam kursus pembelajaran mendalam yang saya ambil tahun lalu.

Inti masalahnya adalah kita memiliki “variasional auto-encoder” (VAE) yang dapat menghasilkan wajah realistis dari sekumpulan 32 angka yang terdistribusi normal. Untuk identifikasi tersangka, kami ingin menggunakan VAE untuk menghasilkan serangkaian wajah (teoretis) yang beragam untuk dipilih oleh seorang saksi, kemudian mempersempit pencarian dengan menghasilkan lebih banyak wajah yang mirip dengan yang dipilih. Untuk latihan ini, disarankan untuk mengacak himpunan vektor awal, tetapi saya ingin menemukan keadaan awal yang optimal.

Kita dapat merumuskan masalah seperti ini: Diberikan ruang 32 dimensi, temukan himpunan vektor satuan X yang tersebar secara maksimal. Dalam dua dimensi, ini mudah dihitung dengan tepat. Tetapi untuk tiga dimensi (atau 32 dimensi!), tidak ada jawaban langsung. Namun, jika kita dapat menentukan fungsi kerugian yang tepat yaitu minimum ketika kita telah mencapai keadaan target kita, mungkin penurunan gradien dapat membantu kita sampai di sana.

Dua grafik. Grafik kiri, Keadaan Awal untuk Semua Eksperimen, memiliki titik pusat yang terhubung ke titik lain, hampir semuanya membentuk setengah lingkaran di sekitarnya; satu titik berdiri kira-kira di seberang setengah lingkaran. Grafik kanan, Target State, seperti roda, dengan jari-jari tersebar merata.

Kami akan mulai dengan kumpulan 20 vektor acak seperti yang ditunjukkan di atas dan bereksperimen dengan tiga fungsi kerugian yang berbeda, masing-masing dengan kompleksitas yang meningkat, untuk menunjukkan kemampuan TensorFlow.

Pertama-tama mari kita definisikan loop pelatihan kita. Kami akan meletakkan semua logika TensorFlow di bawah metode self.calc_loss() , dan kemudian kami dapat dengan mudah mengganti metode itu untuk setiap teknik, mendaur ulang loop ini.

 # 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

Teknik pertama yang harus dicoba adalah yang paling sederhana. Kami mendefinisikan metrik penyebaran yang merupakan sudut dari vektor yang paling dekat satu sama lain. Kami ingin memaksimalkan penyebaran, tetapi konvensional untuk membuatnya menjadi masalah minimalisasi. Jadi kami hanya mengambil negatif dari metrik spread:

 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

Beberapa keajaiban Matplotlib akan menghasilkan visualisasi.

Sebuah animasi pergi dari keadaan awal ke keadaan target. Satu-satunya titik tetap, dan sisa jari-jari dalam setengah lingkaran bergiliran bergerak maju mundur, perlahan menyebar dan tidak mencapai jarak yang sama bahkan setelah 1.200 iterasi.

Ini kikuk (secara harfiah!) tetapi berhasil. Hanya dua dari 20 vektor yang diperbarui pada satu waktu, memperbesar jarak di antara mereka hingga tidak lagi menjadi yang paling dekat, kemudian beralih untuk meningkatkan sudut antara dua vektor terdekat yang baru. Hal penting untuk diperhatikan adalah bahwa ia bekerja . Kami melihat bahwa TensorFlow dapat meneruskan gradien melalui metode tf.reduce_min() dan metode tf.acos() untuk melakukan hal yang benar.

Mari kita coba sesuatu yang sedikit lebih rumit. Kita tahu bahwa pada solusi optimal, semua vektor harus memiliki sudut yang sama terhadap tetangga terdekatnya. Jadi mari kita tambahkan "varians sudut minimum" ke fungsi kerugian.

 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 

Sebuah animasi pergi dari keadaan awal ke keadaan target. Satu-satunya jari-jari tidak tetap, dengan cepat bergerak ke arah sisa jari-jari dalam setengah lingkaran; alih-alih menutup dua celah di kedua sisi yang berbicara, kegugupan sekarang menutup satu celah besar seiring waktu. Equidistance di sini juga tidak cukup tercapai setelah 1.200 iterasi.

Vektor tunggal ke utara itu sekarang dengan cepat bergabung dengan rekan-rekannya, karena sudut ke tetangga terdekatnya sangat besar dan meningkatkan suku varians yang sekarang diminimalkan. Tetapi pada akhirnya masih didorong oleh sudut minimum global yang tetap lambat untuk meningkat. Ide yang harus saya tingkatkan ini umumnya berfungsi dalam kasus 2D ini, tetapi tidak dalam dimensi yang lebih tinggi.

Tetapi terlalu fokus pada kualitas upaya matematis ini tidak ada gunanya. Lihat berapa banyak operasi tensor yang terlibat dalam penghitungan mean dan varians, dan bagaimana TensorFlow berhasil melacak dan membedakan setiap komputasi untuk setiap komponen dalam matriks input. Dan kami tidak perlu melakukan kalkulus manual. Kami baru saja menggabungkan beberapa matematika sederhana, dan TensorFlow melakukan kalkulus untuk kami.

Akhirnya, mari kita coba satu hal lagi: solusi berbasis kekuatan. Bayangkan bahwa setiap vektor adalah planet kecil yang ditambatkan ke titik pusat. Setiap planet memancarkan gaya yang menolaknya dari planet lain. Jika kita menjalankan simulasi fisika model ini, kita harus berakhir pada solusi yang kita inginkan.

Hipotesis saya adalah bahwa penurunan gradien harus bekerja juga. Pada solusi optimal, gaya tangen di setiap planet dari setiap planet lain harus dibatalkan menjadi gaya nol bersih (jika bukan nol, planet-planet akan bergerak). Jadi mari kita hitung besarnya gaya pada setiap vektor dan gunakan penurunan gradien untuk mendorongnya menuju nol.

Pertama, kita perlu mendefinisikan metode yang menghitung gaya menggunakan metode 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

Kemudian, kami mendefinisikan fungsi kerugian kami menggunakan fungsi gaya di atas. Kami mengumpulkan gaya total pada setiap vektor dan menghitung besarnya. Pada solusi optimal kami, semua gaya harus dihilangkan dan kami harus memiliki gaya nol.

 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)) 

Sebuah animasi pergi dari keadaan awal ke keadaan target. Beberapa frame pertama melihat gerakan cepat di semua jari-jari, dan setelah hanya 200 iterasi atau lebih, gambar keseluruhan sudah cukup dekat dengan target. Hanya 700 iterasi yang ditampilkan secara total; setelah ke-300, sudut-sudut berubah hanya dengan cermat pada setiap bingkai.

Solusi tidak hanya bekerja dengan baik (selain beberapa kekacauan dalam beberapa frame pertama), tetapi kredit sebenarnya diberikan kepada TensorFlow. Solusi ini melibatkan banyak loop for , pernyataan if , dan jaringan kalkulasi yang sangat besar, dan TensorFlow berhasil melacak gradien melalui semuanya untuk kami.

Contoh 3: Menghasilkan Input AI Bermusuhan

Contoh 3 Buku Catatan

Pada titik ini, pembaca mungkin berpikir, "Hei! Postingan ini tidak seharusnya tentang pembelajaran mendalam!" Namun secara teknis, pendahuluan mengacu pada melampaui " melatih model pembelajaran mendalam". Dalam hal ini, kami tidak melatih , tetapi mengeksploitasi beberapa sifat matematika dari jaringan saraf dalam yang telah dilatih sebelumnya untuk mengelabuinya agar memberi kami hasil yang salah. Ini ternyata jauh lebih mudah dan efektif dari yang dibayangkan. Dan yang diperlukan hanyalah gumpalan pendek kode TensorFlow 2.0 lainnya.

Kita mulai dengan mencari pengklasifikasi gambar untuk diserang. Kami akan menggunakan salah satu solusi teratas untuk Kompetisi Kaggle Anjing vs. Kucing; khusus, solusi yang disajikan oleh Kaggler "uysimty." Semua pujian untuk mereka karena menyediakan model kucing-vs-anjing yang efektif dan menyediakan dokumentasi yang bagus. Ini adalah model yang kuat yang terdiri dari 13 juta parameter di 18 lapisan jaringan saraf. (Pembaca dipersilakan untuk membaca lebih lanjut tentang itu di buku catatan yang sesuai.)

Harap dicatat bahwa tujuannya di sini bukan untuk menyoroti kekurangan apa pun dalam jaringan khusus ini, tetapi untuk menunjukkan bagaimana jaringan saraf standar dengan sejumlah besar input rentan.

Terkait: Logika Suara dan Model AI Monotonik

Dengan sedikit mengutak-atik, saya dapat mengetahui cara memuat model dan melakukan pra-proses gambar untuk diklasifikasikan olehnya.

Lima contoh gambar, masing-masing anjing atau kucing, dengan klasifikasi dan tingkat kepercayaan yang sesuai. Tingkat kepercayaan yang ditampilkan berkisar dari 95 persen hingga 100 persen.

Ini terlihat seperti pengklasifikasi yang sangat solid! Semua klasifikasi sampel benar dan kepercayaan di atas 95%. Ayo serang!

Kami ingin menghasilkan gambar yang jelas-jelas kucing tetapi pengklasifikasi memutuskan bahwa itu adalah anjing dengan kepercayaan diri tinggi. Bagaimana kita bisa melakukan itu?

Mari kita mulai dengan gambar kucing yang diklasifikasikan dengan benar, lalu cari tahu bagaimana modifikasi kecil di setiap saluran warna (nilai 0-255) dari piksel input yang diberikan memengaruhi output pengklasifikasi akhir. Memodifikasi satu piksel mungkin tidak akan banyak membantu, tetapi mungkin penyesuaian kumulatif dari semua 128x128x3 = 49.152 nilai piksel akan mencapai tujuan kita.

Bagaimana kita tahu cara untuk mendorong setiap piksel? Selama pelatihan jaringan saraf normal, kami mencoba meminimalkan kehilangan antara label target dan label yang diprediksi, menggunakan penurunan gradien di TensorFlow untuk memperbarui 13 juta parameter gratis secara bersamaan. Dalam hal ini, kami akan membiarkan 13 juta parameter tetap, dan menyesuaikan nilai piksel dari input itu sendiri.

Apa fungsi kerugian kita? Nah, betapa miripnya gambar itu dengan kucing! Jika kita menghitung turunan dari nilai cat sehubungan dengan setiap piksel input, kita tahu cara mana untuk mendorong masing-masing untuk meminimalkan probabilitas klasifikasi cat.

 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

Magic Matplotlib sekali lagi membantu memvisualisasikan hasilnya.

Gambar kucing sampel asli bersama dengan 4 iterasi, dengan klasifikasi, "Kucing 99,0%," "Kucing 67,3%," "Anjing 71,7%," "Anjing 94,3%," dan "Anjing 99,4%," masing-masing.

Wow! Bagi mata manusia, masing-masing gambar ini identik. Namun setelah empat iterasi, kami meyakinkan pengklasifikasi bahwa ini adalah seekor anjing, dengan kepercayaan 99,4 persen!

Mari kita pastikan ini bukan kebetulan dan ini juga bekerja di arah lain.

Contoh gambar anjing asli bersama dengan 4 iterasi, dengan klasifikasi, "Anjing 98,4%", "Anjing 83,9%", "Anjing 54,6%", "Kucing 90,4%", dan "Kucing 99,8%". Seperti sebelumnya, perbedaannya tidak terlihat dengan mata telanjang.

Kesuksesan! Pengklasifikasi awalnya memperkirakan ini dengan benar sebagai anjing dengan kepercayaan 98,4 persen, dan sekarang percaya itu adalah kucing dengan kepercayaan 99,8 persen.

Terakhir, mari kita lihat contoh tambalan gambar dan lihat bagaimana perubahannya.

Tiga kisi baris dan kolom piksel, menunjukkan nilai numerik untuk saluran merah setiap piksel. Patch gambar kiri menunjukkan sebagian besar kotak kebiruan, menyoroti nilai 218 atau lebih rendah, dengan beberapa kotak merah (219 ke atas) berkerumun di sudut kanan bawah. Bagian tengah, halaman gambar "korban", menunjukkan tata letak berwarna dan bernomor yang sangat mirip. Patch gambar sebelah kanan menunjukkan perbedaan numerik antara dua lainnya, dengan perbedaan hanya berkisar dari -4 hingga +4, dan termasuk beberapa nol.

Seperti yang diharapkan, tambalan terakhir sangat mirip dengan aslinya, dengan setiap piksel hanya bergeser -4 ke +4 dalam nilai intensitas saluran merah. Pergeseran ini tidak cukup bagi manusia untuk membedakan perbedaan, tetapi sepenuhnya mengubah output dari classifier.

Pikiran Akhir: Optimasi Keturunan Gradien

Sepanjang artikel ini, kita telah melihat penerapan gradien secara manual ke parameter yang dapat dilatih demi kesederhanaan dan transparansi. Namun, di dunia nyata, ilmuwan data harus langsung menggunakan pengoptimal , karena mereka cenderung jauh lebih efektif, tanpa menambahkan kode apa pun.

Ada banyak pengoptimal populer, termasuk RMSprop, Adagrad, dan Adadelta, tetapi yang paling umum mungkin adalah Adam . Kadang-kadang, mereka disebut "metode tingkat pembelajaran adaptif" karena mereka secara dinamis mempertahankan tingkat pembelajaran yang berbeda untuk setiap parameter. Banyak dari mereka menggunakan istilah momentum dan perkiraan turunan orde tinggi, dengan tujuan keluar dari minimum lokal dan mencapai konvergensi yang lebih cepat.

Dalam animasi yang dipinjam dari Sebastian Ruder, kita dapat melihat jalur berbagai pengoptimal menuruni permukaan yang hilang. Teknik manual yang telah kami tunjukkan paling sebanding dengan "SGD." Pengoptimal berperforma terbaik tidak akan sama untuk setiap permukaan yang hilang; namun, pengoptimal yang lebih canggih biasanya berkinerja lebih baik daripada yang lebih sederhana.

Peta kontur animasi, menunjukkan jalur yang diambil oleh enam metode berbeda untuk bertemu pada titik target. SGD sejauh ini adalah yang paling lambat, mengambil kurva stabil dari titik awalnya. Momentum awalnya menjauh dari target, kemudian berselang-seling melintasi jalurnya sendiri dua kali sebelum menuju ke arahnya tidak sepenuhnya secara langsung, dan tampaknya melampaui target dan kemudian mundur. NAG serupa, tetapi tidak menyimpang jauh dari target dan hanya berselang satu kali, umumnya mencapai target lebih cepat dan lebih sedikit melakukan overshoot. Adagrad memulai dengan garis lurus yang paling keluar jalur, tetapi sangat cepat berbelok ke arah bukit yang menjadi target, dan melengkung ke arah itu lebih cepat daripada tiga yang pertama. Adadelta memiliki jalur yang serupa, tetapi dengan kurva yang lebih mulus; itu menyalip Adagrad dan tetap di depannya setelah sekitar satu detik pertama. Akhirnya, Rmsprop mengikuti jalur yang sangat mirip dengan Adadelta, tetapi sedikit condong lebih dekat ke target sejak awal; terutama, jalannya jauh lebih stabil, membuatnya tertinggal di belakang Adagrad dan Adadelta untuk sebagian besar animasi; tidak seperti lima lainnya, tampaknya memiliki dua lompatan cepat dan tiba-tiba dalam dua arah yang berbeda di dekat akhir animasi sebelum berhenti bergerak, sementara yang lain, pada saat terakhir, terus perlahan merayap di sepanjang target.

Namun, menjadi ahli dalam pengoptimalan jarang berguna—bahkan bagi mereka yang tertarik untuk menyediakan layanan pengembangan kecerdasan buatan. Ini adalah penggunaan waktu pengembang yang lebih baik untuk membiasakan diri dengan pasangan, hanya untuk memahami bagaimana mereka meningkatkan penurunan gradien di TensorFlow. Setelah itu, mereka hanya dapat menggunakan Adam secara default dan mencoba yang berbeda hanya jika model mereka tidak konvergen.

Untuk pembaca yang benar-benar tertarik dengan bagaimana dan mengapa pengoptimal ini bekerja, ikhtisar Ruder—di mana animasi muncul—adalah salah satu sumber daya terbaik dan terlengkap tentang topik tersebut.

Mari kita perbarui solusi regresi linier kita dari bagian pertama untuk menggunakan pengoptimal. Berikut ini adalah kode penurunan gradien asli menggunakan gradien manual.

 # 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]

Sekarang, ini kode yang sama menggunakan pengoptimal sebagai gantinya. Anda akan melihat bahwa hampir tidak ada kode tambahan (baris yang diubah disorot dengan warna biru):

 # 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))

Itu dia! Kami mendefinisikan pengoptimal RMSprop di luar loop penurunan gradien, dan kemudian kami menggunakan metode optimizer.apply_gradients() setelah setiap perhitungan gradien untuk memperbarui parameter yang dapat dilatih. Pengoptimal didefinisikan di luar loop karena akan melacak gradien historis untuk menghitung suku tambahan seperti momentum dan turunan tingkat tinggi.

Mari kita lihat tampilannya dengan pengoptimal RMSprop .

Mirip dengan pasangan animasi yang disinkronkan sebelumnya; garis yang dipasang dimulai di atas tempat istirahatnya. Grafik kerugian menunjukkan hampir konvergen setelah hanya lima iterasi.

Tampak hebat! Sekarang mari kita coba dengan pengoptimal Adam .

Scatterplot lain yang disinkronkan dan animasi grafik kerugian yang sesuai. Grafik kerugian menonjol dari yang lain karena tidak terus mendekati minimum; sebaliknya, itu menyerupai jalur bola yang memantul. Garis pas yang sesuai pada scatterplot dimulai di atas titik sampel, berayun ke bagian bawahnya, lalu mundur tetapi tidak setinggi itu, dan seterusnya, dengan setiap perubahan arah menjadi lebih dekat ke posisi tengah.

Wah, apa yang terjadi di sini? Tampaknya mekanika momentum di Adam menyebabkannya melampaui solusi optimal dan berbalik arah beberapa kali. Biasanya, mekanika momentum ini membantu dengan permukaan kerugian yang kompleks, tetapi ini merugikan kita dalam kasus sederhana ini. Ini menekankan saran untuk menjadikan pilihan pengoptimal sebagai salah satu hyperparameter yang harus disesuaikan saat melatih model Anda.

Siapa pun yang ingin mendalami pembelajaran akan ingin mengenal pola ini, karena pola ini digunakan secara luas dalam arsitektur TensorFlow kustom, di mana ada kebutuhan untuk memiliki mekanisme kerugian kompleks yang tidak mudah tercakup dalam alur kerja standar. Dalam contoh penurunan gradien TensorFlow sederhana ini, hanya ada dua parameter yang dapat dilatih, tetapi ini diperlukan saat bekerja dengan arsitektur yang berisi ratusan juta parameter untuk dioptimalkan.

Penurunan Gradien di TensorFlow: Dari Menemukan Minimum hingga Menyerang Sistem AI

Semua cuplikan kode dan gambar dihasilkan dari buku catatan di repo GitHub yang sesuai. Ini juga berisi ringkasan semua bagian, dengan tautan ke masing-masing buku catatan, untuk pembaca yang ingin melihat kode lengkapnya. Demi menyederhanakan pesan, banyak detail yang terlewatkan yang dapat ditemukan dalam dokumentasi sebaris yang ekstensif.

Saya harap artikel ini memberikan wawasan dan membuat Anda berpikir tentang cara menggunakan penurunan gradien di TensorFlow. Bahkan jika Anda tidak menggunakannya sendiri, mudah-mudahan ini memperjelas cara kerja semua arsitektur jaringan saraf modern—membuat model, menentukan fungsi kerugian, dan menggunakan penurunan gradien agar sesuai dengan model ke kumpulan data Anda.


Lencana Mitra Google Cloud.

Sebagai Partner Google Cloud, para ahli Toptal bersertifikasi Google tersedia untuk perusahaan sesuai permintaan untuk proyek terpenting mereka.