Gradient descent adalah algoritma minimisasi iteratif yang berulang kali melangkah melawan gradien menuju titik terendah suatu fungsi. Topik ini membahas aturan pembaruan, peran learning rate sebagai penentu panjang langkah, implementasi menggunakan NumPy, serta perilaku lintasan pada permukaan mangkuk. Setelah topik ini selesai, pembaruan parameter selama pelatihan model dapat dibaca dari satu rumus yang sama, dan pengaruh learning rate dapat diprediksi sebelum kode dijalankan.
Langkah menurun mengikuti negatif gradien#
Gradien menunjuk arah kenaikan paling curam, sehingga arah penurunan paling curam adalah negatif gradiennya. Gradient descent memanfaatkan fakta itu melalui aturan pembaruan
yaitu titik saat ini digeser sejauh kali gradiennya ke arah yang menurun. Notasi mewakili seluruh parameter yang dioptimasi, baik dua variabel maupun satu juta bobot, dan rumusnya tidak berubah. Karena setiap langkah diambil pada arah penurunan paling curam, nilai pada kasus yang rapi turun terus menuju titik terendah.
Algoritma ini iteratif, artinya hasilnya diperoleh dari pengulangan langkah kecil dan bukan dari satu rumus penyelesaian langsung. Pengulangan dihentikan ketika gradien sudah cukup kecil, jumlah maksimum iterasi tercapai, atau perbaikan nilai fungsi sudah tidak berarti. Titik berhenti yang wajar adalah titik dengan , karena tidak ada arah yang menurun dari titik itu. Untuk permukaan mangkuk, titik itu bertepatan dengan minimum sebenarnya.
Learning rate menentukan panjang langkah#
Faktor disebut learning rate, yaitu kelipatan yang menentukan panjang langkah setiap pembaruan. Learning rate yang kecil menghasilkan langkah pendek, sehingga konvergen dengan lambat, sedangkan learning rate yang terlalu besar membuat titik melompati lembah dan berosilasi, bahkan menjauhi minimum pada nilai yang lebih ekstrem. Untuk fungsi kuadrat dengan kelengkungan terbesar , osilasi mulai terjadi ketika melampaui . Gambar berikut membandingkan lintasan pada dari titik awal yang sama untuk dua learning rate yang berbeda.
import numpy as np
import matplotlib.pyplot as plt
def turun(x0, eta, n):
"""Jejak gradient descent pada f(x) = x^2 dari titik awal x0."""
jejak = [x0]
x = x0
for _ in range(n):
x = x - eta * 2.0 * x
jejak.append(x)
return np.array(jejak)
x = np.linspace(-6.0, 6.0, 200)
fig, axes = plt.subplots(1, 2, figsize=(6, 3))
for ax, eta, judul in zip(axes, (0.1, 1.1), ("learning rate = 0.1", "learning rate = 1.1")):
jejak = turun(2.0, eta, 5)
ax.plot(x, x**2, color="#1a5fb4", label="$f(x) = x^2$")
ax.plot(jejak, jejak**2, "o-", color="#c01c28", markersize=4, linewidth=1)
ax.set_xlabel("x")
ax.set_title(judul)
ax.axhline(0.0, color="#000000", linewidth=0.8)
axes[0].set_ylabel("f(x)")
fig.tight_layout()
fig.savefig("langkah-learning-rate.svg")Panel kiri menggunakan learning rate 0.1, sehingga setiap langkah memangkas posisi menjadi 0.8 kalinya dan lintasannya menurun rapi menuju minimum. Panel kanan menggunakan learning rate 1.1, sehingga setiap langkah melewati minimum dengan jarak yang makin jauh dan lintasannya melebar keluar. Kedua panel berasal dari fungsi yang sama, titik awal yang sama, dan aturan pembaruan yang sama, dan hanya learning rate-nya yang berbeda. Perbedaan hasil sebesar itu menjelaskan mengapa learning rate dianggap hyperparameter yang paling berpengaruh pada pelatihan model.
Implementasi dengan NumPy#
Implementasi gradient descent hanya membutuhkan fungsi, gradiennya, titik awal, learning rate, dan pengulangan. Berkas berikut menurunkan fungsi mangkuk yang titik terendahnya sudah diketahui, yaitu dengan nilai . Dengan titik awal dan jumlah iterasi yang sama, dua learning rate dicoba, dan nilai yang dicetak dibulatkan agar jejaknya mudah dibaca.
import numpy as np
def g(v):
"""Fungsi mangkuk dengan titik terendah di (2, -1) bernilai 4."""
return (v[0] - 2.0)**2 + (v[1] + 1.0)**2 + 4.0
def grad_g(v):
"""Gradien analitik g, yaitu (2(x - 2), 2(y + 1))."""
return np.array([2.0 * (v[0] - 2.0), 2.0 * (v[1] + 1.0)])
for eta in (0.15, 1.2):
x = np.array([0.0, 0.0])
print(f"--- learning rate {eta} ---")
for k in range(11):
if k % 2 == 0:
print(f"iterasi {k:2d} : x = {np.round(x, 4)}, g(x) = {np.round(g(x), 4)}")
x = x - eta * grad_g(x)--- learning rate 0.15 ---
iterasi 0 : x = [0. 0.], g(x) = 9.0
iterasi 2 : x = [ 1.02 -0.51], g(x) = 5.2005
iterasi 4 : x = [ 1.5198 -0.7599], g(x) = 4.2882
iterasi 6 : x = [ 1.7647 -0.8824], g(x) = 4.0692
iterasi 8 : x = [ 1.8847 -0.9424], g(x) = 4.0166
iterasi 10 : x = [ 1.9435 -0.9718], g(x) = 4.004
--- learning rate 1.2 ---
iterasi 0 : x = [0. 0.], g(x) = 9.0
iterasi 2 : x = [-1.92 0.96], g(x) = 23.208
iterasi 4 : x = [-5.6832 2.8416], g(x) = 77.7895
iterasi 6 : x = [-13.0591 6.5295], g(x) = 287.4696
iterasi 8 : x = [-27.5158 13.7579], g(x) = 1092.9767
iterasi 10 : x = [-55.8509 27.9255], g(x) = 4187.4128Dengan learning rate 0.15, titik bergerak menuju dan nilai turun dari 9.0 hingga 4.004 yang semakin dekat ke nilai terendahnya 4. Dengan learning rate 1.2, titik justru menjauh, dan nilai membengkak dari 9.0 menjadi lebih dari empat ribu hanya dalam sepuluh iterasi. Kedua jalannya menggunakan gradien yang sama, sehingga perbedaan hasilnya sepenuhnya berasal dari panjang langkah. Jejak seperti ini menjadi cara cepat mendiagnosis learning rate sebelum algoritma diterapkan pada fungsi yang minimumnya belum diketahui.
Demo interaktif: menyetel learning rate#
Demo berikut menjalankan gradient descent pada peta kontur mangkuk . Atur slider learning rate, tekan tombol Satu langkah berkali-kali untuk melihat setiap pembaruan satu per satu, atau tekan 20 langkah untuk menjalankannya sekaligus. Dengan learning rate 0.2, lintasannya menuju pusat secara rapi. Dengan 1.0, titiknya berosilasi pada lingkaran yang sama, dan dengan 1.2, titiknya terpental keluar peta. Tekan Ulang untuk kembali ke titik awal setelah setiap percobaan.
Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Gradient descent pada peta kontur f(x, y) = x² + y²</title>
<style>
body { font-family: sans-serif; margin: 12px; color: #241f31; }
.baris { display: flex; align-items: center; gap: 10px; margin: 8px 0; flex-wrap: wrap; }
input[type="range"] { width: 170px; }
button { padding: 5px 10px; cursor: pointer; }
#peta { background: #f6f5f4; border-radius: 6px; }
#status { margin-top: 8px; padding: 10px 12px; border-radius: 6px; background: #f6f5f4; }
</style>
</head>
<body>
<div class="baris">
<label for="eta">learning rate = <span id="veta">0.2</span></label>
<input id="eta" type="range" min="0.05" max="1.2" step="0.05" value="0.2">
<button id="satu" type="button">Satu langkah</button>
<button id="banyak" type="button">20 langkah</button>
<button id="ulang" type="button">Ulang</button>
</div>
<canvas id="peta" width="420" height="300"></canvas>
<div id="status"></div>
<script>
var skala = 34; // piksel per satuan
var cx = 210, cy = 150; // letak titik asal pada kanvas
var titik = [3.0, 3.0];
var jejak = [[3.0, 3.0]];
function f(x, y) { return x * x + y * y; }
function gambar() {
var kanvas = document.getElementById("peta");
var ctx = kanvas.getContext("2d");
ctx.clearRect(0, 0, kanvas.width, kanvas.height);
// sumbu x dan y
ctx.strokeStyle = "#c9c5c1";
ctx.beginPath();
ctx.moveTo(0, cy); ctx.lineTo(kanvas.width, cy);
ctx.moveTo(cx, 0); ctx.lineTo(cx, kanvas.height);
ctx.stroke();
// garis kontur pada level f = 1, 4, 9, 16 (jari-jari 1, 2, 3, 4)
var jari = [1, 2, 3, 4];
for (var k = 0; k < jari.length; k++) {
ctx.beginPath();
ctx.arc(cx, cy, jari[k] * skala, 0, 2 * Math.PI);
ctx.strokeStyle = "#9a9996";
ctx.stroke();
ctx.fillStyle = "#77767b";
ctx.fillText("f = " + (jari[k] * jari[k]), cx + jari[k] * skala - 36, cy - 4);
}
// jejak lintasan yang sudah dilalui
ctx.strokeStyle = "#c01c28";
ctx.beginPath();
for (var i = 0; i < jejak.length; i++) {
var px = cx + jejak[i][0] * skala;
var py = cy - jejak[i][1] * skala;
if (i === 0) { ctx.moveTo(px, py); } else { ctx.lineTo(px, py); }
}
ctx.stroke();
// titik saat ini
var lx = cx + titik[0] * skala, ly = cy - titik[1] * skala;
var luar = lx < 0 || lx > kanvas.width || ly < 0 || ly > kanvas.height;
if (!luar) {
ctx.beginPath();
ctx.arc(lx, ly, 5, 0, 2 * Math.PI);
ctx.fillStyle = "#c01c28";
ctx.fill();
}
var info = "iterasi " + (jejak.length - 1) + " : titik (" + titik[0].toFixed(2) +
", " + titik[1].toFixed(2) + "), f = " + f(titik[0], titik[1]).toFixed(2);
if (luar) { info += " (titik keluar peta)"; }
document.getElementById("status").textContent = info;
}
function satuLangkah() {
var eta = parseFloat(document.getElementById("eta").value);
// gradien f(x, y) = x^2 + y^2 adalah (2x, 2y), sehingga tiap pembaruan:
// titik = titik - eta * (2 * titik)
titik = [titik[0] * (1 - 2 * eta), titik[1] * (1 - 2 * eta)];
jejak.push([titik[0], titik[1]]);
gambar();
}
document.getElementById("satu").addEventListener("click", satuLangkah);
document.getElementById("banyak").addEventListener("click", function () {
for (var i = 0; i < 20; i++) { satuLangkah(); }
});
document.getElementById("ulang").addEventListener("click", function () {
titik = [3.0, 3.0];
jejak = [[3.0, 3.0]];
gambar();
});
document.getElementById("eta").addEventListener("input", function () {
document.getElementById("veta").textContent =
parseFloat(document.getElementById("eta").value).toFixed(2);
});
gambar();
</script>
</body>
</html>Di mana hal ini ditemui dalam kode#
Pelatihan hampir semua model machine learning menggunakan versi gradient descent sebagai penggeraknya. Backpropagation menyediakan gradien loss, kemudian optimizer mengambil alih untuk memperbarui bobot pada setiap iterasi. Varian stochastic gradient descent menghitung gradien dari sebagian kecil data setiap langkah, sedangkan optimizer seperti momentum dan Adam menyesuaikan arah maupun panjang langkah berdasarkan riwayat gradien. Semua varian itu tetap berpegang pada rumus dasar yang sama, yaitu pengurangan kelipatan gradien dari parameter.
Learning rate muncul sebagai hyperparameter yang paling sering disetel, mulai dari nilai tetap hingga penjadwal yang menurunkannya seiring iterasi. Apabila loss pelatihan membesar atau menghasilkan nan, learning rate yang terlalu besar menjadi penyebab utama, dan langkah pertama pemecahan masalahnya adalah mengecilkan nilainya. Sebaliknya, loss yang turun terlalu lambat menandakan langkah yang terlalu pendek. Dua gejala itu dijelaskan langsung oleh lintasan pada gambar dan demo di atas.
Kesalahan umum#
Tiga kesalahan muncul berulang ketika gradient descent pertama kali diterapkan pada masalah nyata. Ketiganya dapat dikenali dari perilaku jejaknya dan diperbaiki dengan penyetelan sederhana.
- Loss naik atau menghasilkan
nansetelah beberapa iterasi. Penyebabnya adalah learning rate yang terlalu besar sehingga setiap langkah melompati lembah, dan perbaikannya adalah mengecilkan learning rate, misalnya sepuluh kali lebih kecil. - Loss nyaris tidak turun setelah ratusan iterasi. Penyebabnya adalah langkah yang terlalu pendek, dan perbaikannya adalah memperbesar learning rate secara bertahap sambil memantau jejaknya.
- Loss bertambah terus karena algoritma mendaki alih-alih menurun. Penyebabnya adalah tanda pembaruan yang terbalik, dan perbaikannya adalah memastikan pembaruan berbentuk pengurangan dari parameter.
Ringkasan#
- Gradient descent memperbarui parameter dengan , yaitu langkah melawan gradien secara berulang.
- Learning rate menentukan panjang langkah, dengan nilai kecil yang konvergen lambat dan nilai besar yang berosilasi atau menjauhi minimum.
- Implementasinya hanya membutuhkan fungsi, gradien, titik awal, learning rate, dan pengulangan, dengan jejak yang dapat diverifikasi pada fungsi yang minimumnya diketahui.
- Optimizer pada pelatihan model merupakan varian dari aturan yang sama, dan learning rate menjadi hyperparameter pertama yang disetel ketika loss berperilaku tidak wajar.
Referensi#
- Mathematics for Machine Learning — Cambridge University Press — Bab optimasi berbasis gradien membahas gradient descent beserta pengaruh learning rate terhadap konvergensi.
- Calculus Volume 3 — OpenStax — Latar turunan berarah yang menjelaskan mengapa arah menurun paling curam adalah negatif gradien.
- NumPy 2.1 documentation — NumPy project — Operasi array yang digunakan pada implementasi pembaruan parameter.