Siri Belajar AI: Mari tengok LSTM Pulak
Bahagian 1

Pada tahun 2019, 5 orang juara dunia dari kumpulan OG terpaksa berhadapan dengan realiti kemajuan teknologi. Permainan video yang selama ini menjadi balairong tempat mereka bersemayam kini sudahpun dicerobohi. Penceroboh itu datang dari dunia maya dan terus merampas mahkota yang berada di atas kepala mereka. Ya, terlalu dramatik sangat pendahuluannya, tapi hakikatnya itulah yang berlaku. 5 orang juara dunia Dota terpaksa mengaku kalah di tangan kecerdasan buatan (AI) yang dibangunkan oleh syarikat OpenAI. Model yang dipanggil sebagai OpenAI Five.
Banyak kisah yang kita boleh kupas dan ulas dari peristiwa di atas. Tapi saya hanya nak fokus pada satu sahaja, model ataupun arkitektur jaringan neural yang mereka gunakan. Untuk pengetahuan para pembaca, model yang berdiri di belakang OpenAI Five. Dan model itulah yang kita ingin fokus pada hari ini
Apa itu LSTM
LSTM membawa maksud long-short term memory. Kalau dalam bahasa melayunya kita panggil memori panjang dan pendek (eh, betul ke?). Ha…tak apalah. Asalkan para pembaca faham. Kata kunci yang penting dalam sini sebenarnya adalah memori. Memori ini pada asalnya diperkenalkan untuk menyelesaikan masalah yang dibawa oleh RNN.
Masih ingat lagi tak pasal RNN dalam siri yang lalu? Boleh baca di sini: https://medium.com/@maercaestro/siri-belajar-ai-mari-kita-kenal-jaringan-neural-berturut-recurrent-neural-network-bahagian-2-335ffd05efbe
Dalam tu kan kita sudah belajar mengenai masalah utama yang dibawa oleh RNN. Gradient meletup dan juga gradient menghilang (vanishing and exploding gradient). Oleh kerana itulah dua orang saintis, Sepp Hochreiter dan juga Jürgen Schmidhuber memperkenalkan model LSTM pada tahun 2017 bagi meneyelesaikan masalah gradient meletup dan juga menghilang
Seperti yang saya cerita dalam siri yang lalu, model RNN menggunakan hidden state (keadaan tersembunyi) untuk menyimpant maklumat yang datang dari langkah masa sebelum itu (previous time step). Isunya, hidden state dalam RNN menyimpan hampir kesemua maklumat termasuklah nilai pemberat dan bias yang ada. Ini membawa masalah ketika proses backpropagation, kerana nilai gradien itu didarabkan dengan gradien yang datang dari hidden-hidden state yang lain lalu menyebabkan gradien itu meletup (menjadi terlalu besar), ataupun menghilang (menjadi terlalu kecil).
LSTM menyelesaikan masalah ini dengan memperkenalkan konsep memori. Lebih tepat lagi, LSTM tidak hanya bergantung kepada keadaan tersembunyi (hidden state). Ia juga menambah satu lagi keadaan, iaitu keadaan sel (cell state). Cell state berfungsi sebagai penyimpan memori jangka masa panjang (long-term), dan ia meletakkan hidden state sebagai penyimpan memori jangka masa pendek.
Disamping itu, LSTM juga menambah pagar (gate) pada arkitekture ini. Fungsi gate adalah untuk mengenalpasti memori apa yang perlu disimpan, apa yang berguna untuk dijadikan sebagai output dan apa yang perlu dilupakan. Kerana itulah, ada tiga jenis gate
Pagar Lupa (Forget Gate)
Pagar Masuk (Input Gate)
Pagar Output/Keluar (Output Gate)

Ok….nak faham betul-betul, mari kita tengok formula matematik yang membentuk arkitektur LSTM
Matematik di sebalik LSTM
Macam saya cerita tadi, kita ada beberapa komponen dalam LSTM. Setiap satunya ada fungsi matematiknya yang tersendiri. Kita mula dengan
Pagar Lupa (Forget Gate)
Menentukan maklumat mana yang perlu dilupakan daripada keadaan sel (cell state).
Pagar Lupa (Forget Gate)
Menentukan maklumat mana yang perlu dilupakan daripada keadaan sel (cell state).
Kemas Kini Keadaan Sel (Cell State Update)
Mengemas kini keadaan sel dengan menggabungkan maklumat dari pagar lupa (forget gate )dan pagar masuk (input gate).
Pintu Keluar (Output Gate)
Menentukan output pada langkah masa sekarang (current time step).
Mari kita cuba kod pulak
Ok, banyak sangat formula nak ingat ni. Boleh pecah kepala nak ingat. Mari kita cuba aplikasikan dalam kod, dan kita visualkan untuk lebih mudah memahami.
Pertama sekali, mari kita tengok berapa banyak pembolehubah (variable) yang kita ada. Mari kita senaraikan pembolehubah itu di bawah:
Input dim (dimensi input) dan Hidden dim (dimensi tersembunyi)
Pemberat dan bias untuk pagar lupa (forget gate)
Pemberat dan bias untuk pagar masuk (input gate)
Pemberat dan bias untuk pagar keluar (output gate)
Keadaan tersembunyi dan keadaan sel (hidden state dan cell state)
Pemberat dan bias untuk calon keadaan sel (cell state candidate)
Jadi, mari kita cuba bina variable-variable ini di dalam model LSTM kita.
#kita import dulu library yang diperlukan
import numpy as np #ini sahaja library yang kita perlukanMari kita senaraikan semua pembolehubah (variable) yang diperlukan dalam LSTM kita, mari kita letakkan semuanya dalam satu kelas (class). Kali ini kita akan menggunakan PooP. Bukan najis tapi Python Object Oriented Programming, huhu. Penggunaan kelas memudahkan sesiapa membaca kod dan memudahkan penggunaan fungsi yang ada dalam kelas.
class LSTM:
def __init__(self, dim_masuk, dim_sembunyi):
"""
Ini merupakan satu kelas/class LSTM yang dinina dengan hanya menggunakan
library numpy tanpa menggunakan pytorch. Ada dua fungsi dalam kelas ini,
pertama kita akan letakkan definisi bagi semua pembolehubah yang akan digunakan.
Kedua, kita akan letakkan fungsi langkah, bagi menentukan arah perjalanan
pembolehubah LSTM kita
"""
#1. dimensi masuk dan tersembunyi
self.dim_masuk = dim_masuk
self.dim_sembunyi = dim_sembunyi
#2. pemberat dan bias untuk pagar lupa (forget gate)
self.Wl = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01 #didarabkan dengan 0.01 bagi mengurangkan nilai awal
self.bl = np.zeros((dim_sembunyi,1)) #letak satu nilai sahaja sebab bias memang hanya ada 1
#3. pemberat dan bias untuk pagar masuk (input gate)
self.Wi = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bi = np.zeros((dim_sembunyi,1))
#4. pemberat dan bias untuk candidate cell
self.Wc = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bc = np.zeros((dim_sembunyi,1))
#5. pemberat dan bias untuk pagar keluar (output gate)
self.Wo = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bo = np.zeros((dim_sembunyi,1))
#6. keadaan tersembunyi dan keadaan sel (hidden state and cell state)
self.h_sebelum = np.zeros((dim_sembunyi,1)) #untuk keadaan tersembunyi (hidden state)
self.C_sebelum = np.zeros((dim_sembunyi,1)) #untuk keadaan sel (cell state)Ok, kita dah senaraikan semua pembolehubah dan juga semua nilai-nilai awal. Kesemuanya dibentuk dalam satu tatasusunan data (array). Langkah seterusnya adalah untuk melengkapkan kelas LSTM kita dengan fungsi langkah (step) yang membbentuk kesemua pembolehubah ini dalam satu fungsi. Tapi sebelum itu, kita perlu buat dulu fungsi-fungsi pembantu. Ini merupakan fungsi-fungsi pengaktifan (activation function) yang sangat penting dalam jaringan neural.
Saya minta maaf kepada anda sebab tak pernah kongsi pasal fungsi pengaktifan ini secara mendalam, tapi saya ada perincikan setiap satu dalam buku saya yang akan datang, AI Untuk Pemula. Tapi buat masa ini, kita hanya perlu kod sahaja setiap fungsi pengaktifan ini.
Ada 4 fungsi pengaktifan,
Sigmoid
Sigmoid Derivatif
Tanh
Tanh Derivatif
Nantilah, saya akan ulas setiap satu fungsi pengaktifan dengan lebih mendalam
# Fungsi pengaktifan sigmoid
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# Derivatif sigmoid (untuk latihan, jika diperlukan)
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# Fungsi pengaktifan tanh
def tanh(x):
return np.tanh(x)
# Derivatif tanh
def tanh_derivative(x):
return 1 - np.tanh(x)**2Seterusnya, mari kita lengkapkan kelas kita dengan fungsi langkah seperti di bawah:
def langkah(self, x_t):
# Gabungkan input semasa dan keadaan tersembunyi sebelumnya
gabung = np.vstack((self.h_sebelum, x_t))
# pagar lupa
f_t = sigmoid(np.dot(self.Wl, gabung) + self.bl)
# pagar masuk
i_t = sigmoid(np.dot(self.Wi, gabung) + self.bi)
C_tilde = tanh(np.dot(self.Wc, gabung) + self.bc) #calon keadaan sel
# Keadaan Sel (Cell State)
C_t = f_t * self.C_sebelum + i_t * C_tilde
# pagar keluar
o_t = sigmoid(np.dot(self.Wo, gabung) + self.bo)
h_t = o_t * tanh(C_t)
# Simpan keadaan semasa untuk langkah seterusnya
self.h_prev = h_t
self.C_prev = C_t
return h_t, C_tJadi kalau kita lengkapkan keseluruhan class LSTM kita, kodnya akan terlihat seperti di bawah:
class LSTM:
def __init__(self, dim_masuk, dim_sembunyi):
"""
Ini merupakan satu kelas/class LSTM yang dinina dengan hanya menggunakan
library numpy tanpa menggunakan pytorch. Ada dua fungsi dalam kelas ini,
pertama kita akan letakkan definisi bagi semua pembolehubah yang akan digunakan.
Kedua, kita akan letakkan fungsi langkah, bagi menentukan arah perjalanan
pembolehubah LSTM kita
"""
#1. dimensi masuk dan tersembunyi
self.dim_masuk = dim_masuk
self.dim_sembunyi = dim_sembunyi
#2. pemberat dan bias untuk pagar lupa (forget gate)
self.Wl = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01 #didarabkan dengan 0.01 bagi mengurangkan nilai awal
self.bl = np.zeros((dim_sembunyi,1)) #letak satu nilai sahaja sebab bias memang hanya ada 1
#3. pemberat dan bias untuk pagar masuk (input gate)
self.Wi = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bi = np.zeros((dim_sembunyi,1))
#4. pemberat dan bias untuk candidate cell
self.Wc = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bc = np.zeros((dim_sembunyi,1))
#5. pemberat dan bias untuk pagar keluar (output gate)
self.Wo = np.random.randn(dim_sembunyi, dim_masuk+dim_sembunyi) * 0.01
self.bo = np.zeros((dim_sembunyi,1))
#6. keadaan tersembunyi dan keadaan sel (hidden state and cell state)
self.h_sebelum = np.zeros((dim_sembunyi,1)) #untuk keadaan tersembunyi (hidden state)
self.C_sebelum = np.zeros((dim_sembunyi,1)) #untuk keadaan sel (cell state)
def langkah(self, x_t):
# Gabungkan input semasa dan keadaan tersembunyi sebelumnya
gabung = np.vstack((self.h_sebelum, x_t))
# pagar lupa
f_t = sigmoid(np.dot(self.Wl, gabung) + self.bl)
# pagar masuk
i_t = sigmoid(np.dot(self.Wi, gabung) + self.bi)
C_tilde = tanh(np.dot(self.Wc, gabung) + self.bc) #calon keadaan sel
# Keadaan Sel (Cell State)
C_t = f_t * self.C_sebelum + i_t * C_tilde
# pagar keluar
o_t = sigmoid(np.dot(self.Wo, gabung) + self.bo)
h_t = o_t * tanh(C_t)
# Simpan keadaan semasa untuk langkah seterusnya
self.h_prev = h_t
self.C_prev = C_t
return h_t, C_tJadi, bila class kita sudahpun lengkap, kita boleh gunakannya seperti di bawah:
# Contoh penggunaan
if __name__ == "__main__":
# Dimensi input (contoh: 3 ciri) dan dimensi tersembunyi (contoh: 4 unit)
dim_input = 3
dim_sembunyi = 4
# Data input (contoh: tetingkap gelongsor dengan 3 elemen)
inputs = [np.random.randn(dim_input, 1) for _ in range(5)]
# Model LSTM
lstm = LSTM(dim_input, dim_sembunyi)
print("Output dari LSTM:")
for t, x_t in enumerate(inputs):
h_t, C_t = lstm.langkah(x_t)
print(f"Langkah {t + 1} - h_t: {h_t.flatten()}, C_t: {C_t.flatten()}")Jika semuanya berfungsi dengan baik, anda akan mendapat output keadaan tersembunyi dan keadaan sel yang diterjemahkan dalam vektor/tatasusunan seperti di bawah:
Ok, itu sahaja untuk kali ini. Kita dah tahu apa itu LSTM, kenapa ia perlu dibina. Apa ciri-ciri yang ada di dalamnya, matematik yang berdiri di sebaliknya, dan kita juga dah membina model ini secara asas tanpa menggunakan library. Tapi ini hanyalah asas, kita belum lagi menggunakan LSTM untuk sebarang aplikasi. Itu sesuatu yang akan kita kupas dalam siri yang akan datang.
Selamat tinggal….semoga berjumpa lagi…










