Artikel kali ini sedikit berbeda karena sebelumnya banyak membahas ke arah jaringan komputer, devops, dan semisalnya maka untuk kali ini saya akan membahas bagaimana implementasi jaringan syaraf tiruan dengan menggunakan python. Kenapa kok sekarang membahas jaringan yang berbeda dari biasanya ?. Itu di luar batasan masalah kalau istilah risetnya. Akan saya bahas nanti di artikel yang berbeda. Oke kita lanjutkan pembahasan.
Artikel ini berasal dari artikel asli pada tautan berikut Implementing a Neural Network from Scratch. Saya alihbahasakan ke bahasa Indonesia dan mengubah beberapa hal untuk memudahkan bagi diri saya pribadi memahami jaringan syaraf tiruan. Bagi yang ingin mengetahui lebih lengkap alangkah baiknya ikuti tautan diatas. Bagi yang tidak sabar ingin mencoba bisa langsung ke code jupyter pada tautan berikut jaringan syaraf awal.
Saya berharap kawan-kawan sebelumnya sudah memahami kalkulus dasar dan konsep dari pembelajaran mesin seperti klasifikasi dan regularisasi. Walaupun secara ideal akan lebih baik jika sudah memahami bagaimana teknik optimasi. Akan tetapi tidak masalah jika memang sama sekali belum paham tapi minimal ada ketertarikan untuk tahu lebih banyak maka itu sudah lebih baik. Semoga artikel kali ini cukup menarik.
Saya punya prinsip jika ingin mengetahui sebuah algoritma maka menulisnya dari awal dengan kode akan mempercepat pemahaman. Hal ini berlaku juga dengan mempelajari jaringan syaraf tiruan. Dengan menuliskan kodenya dari awal akan membantu kita untuk memahami bagaimana sebuah jaringan syaraf tiruan bekerja dan selanjutnya kita implementasi bagaimana membuat model yang efektif.
A. Membangkitkan Data
Sebelumnya kita akan membangkitkan data untuk rekayasa dan modifikasi. Untuk membangkitkan data kita bisa menggunakan pustaka dari scikit-learn. Untuk yang ini kita tidak perlu memprogram dari awal. Kita akan menggunakan fungsi make_moons dari data yang sudah ada di scikit-learn. Apa yang ditampilkan pada artikel ini berupa potongan-potongan kode saja. Untuk lengkapnya silakan merujuk pada github saya.
X, y = datasets.make_moons(200, noise=0.20)
Plt.scatter(X[:, 0], X[:, 1], s= 40, c=y, cmap = plt.cm.Spectral)
Dataset pada Gambar 1 yang telah dibangkitkan memiliki dua buah kelas yang diwakili dengan titik warna biru dan warna merah. Karena ini hanyalah sebuah contoh data. Kita bisa juga menganalogikan titik biru adalah pasien yang sehat dan titik merah adalah pasien yang terdiagnosis terkena penyakit dengan sumbu x dan y merupakan hasil pengukuran secara medis.
Tujuan kita adalah bagaimana melatih classifier agar dapat memprediksi mana yang sakit dan mana yang tidak dengan koordinat x dan y. Jika diperhatikan dengan baik plot Gambar 1, data bercampur dan tidak sepenuhnya terpisah dengan jelas (linearly separable) sehingga kita tidak bisa menggaris dengan garis lurus terhadap kedua kelas. Hasil kesimpulan ini membuktikan, kita tidak bisa menggunakan classifier yang bersifat linier seperti Logistic Regression tidak bisa dilakukan kecuali dilakukan secara manual non-linier.
Dari sini kita juga dapat mengetahui salah satu keunggulan dari jaringan syaraf tiruan adalah kita tidak perlu khawatir untuk melakukan seleksi fitur. Lapisan tersembunyi (hidden layer) dari sebuah jaringan syaraf akan melakukannya untuk kita.
B. Logistic Regression
Untuk menjelaskan bahwa Logistic Regression seperti yang disebutkan diatas tidak bisa mengklasifikasikan dengan baik, maka gambar berikut akan lebih memperjelas maksudnya. Kode masih menggunakan scikit-learn.
clf = linear_model.LogisticRegressionCV()
clf.fit(X, y)
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")
Perhatikan baik-baik Gambar 2 diatas. Algoritma Logistic Regression telah mempelajari data yang diberikan dan mencoba untuk memisahkan mana data yang tergolong berwarna biru dan mana yang berwarna merah dengan sebuah garis lurus. Jika diperhatikan dengan seksama kita akan melihat ada data berwarna biru yang dianggap merah begitu pula sebaliknya. Sehingga data yang ada tidak cocok menggunakan Logistic Regression.
C. Melatih Jaringan Syaraf
Selanjutnya kita akan mencoba menggunakan jaringan syaraf tiruan dengan membangun tiga buah lapisan jaringan, dengan satu buah lapisan masukan, satu buah lapisan tersembunyi, dan satu buah lapisan keluaran. Jumlah neuron ditentukan sesuai dengan besar dimensi data yang dimiliki dalam hal ini data yang dibangkitkan adalah 2 dimensi maka kita gunakan 2.
Hal yang sama pada lapisan keluaran ditentukan oleh berapa kelas yang dimiliki data kita. Jumlahnya masih sama 2 yakni biru (tidak sakit) dan merah (sakit). Lapisan input pun juga ada 2 yang mewakili sumbu x dan y dengan keluaran bisa 0 (tidak sakit) dan 1 (sakit). Jika digambarkan kurang lebih sebagai berikut:
Bagaimana dengan lapisan tersembunyi ?. Semakin besar tentu saja akan semakin komplek dan tentu saja konsekuensinya adalah membutuhkan komputasi yang lebih untuk melakukan prediksi dan pembelajaran. Catatan yang perlu diperhatikan juga adalah semakin besar parameter yang digunakan akan berpotensi overfitting. Penjelasan mudahnya, overfitting adalah prediksi yang berjalan baik pada data yang kita miliki dan akurasi tinggi. Akan tetapi ketika dihadapkan pada data lain maka akan menurun akurasinya. Penjelasan overfitting akan saya coba jelaskan pada artikel lainnya dengan lebih detil.
Lalu bagaimana memilih lapisan yang tersembunyi ?. Ada beberapa rekomendasi yang bisa digunakan dan hal ini tentu saja tergantung permasalahan yang ada. Pemilihan jumlah lapisan tersembunyi lebih bersifat seni (ini debatable). Kita akan menerangkan lebih lanjut.
Dalam jaringan syaraf, dibutuhkan juga yang namanya fungsi aktivasi untuk lapisan tersembunyi. Fungsi aktivasi ini bekerja dari lapisan masukan menuju keluaran. Beberapa diantara fungsi aktivasi yang biasa digunakan diantaranya adalah tanh, sigmoid, relu. Pada kasus ini kita akan menggunakan tanh.
D. Bagaimana Jaringan Syaraf Tiruan Melakukan Prediksi
Prediksi pada jaringan syaraf tiruan akan menggunakan propagasi ke depan yang merupakan kumpulan perkalian beberapa matrix dan implementasi dari fungsi aktivasi diatas. Jika x adalah input dari 2 dimensi maka kita dapat mendefinisikan y (prediksi data) sebagai berikut:
z1 = xW1 + b1
a1 = tanh(z1)
z2 = a1W2 + b2
a2 = y = softmax(z2)
z1 adalah lapisan masukan dari lapisan masukan i dan ai adalah lapisan keluaran dari i setelah implementasi fungsi aktivasi. W1, b1, W2, b2 adalah parameter dari jaringan yang harus dipelajari dari data. Ini bisa dianalogikan dengan transformasi matrix antar lapisan pada jaringan. Melihat perkalian matrix diatas, kita dapat mengetahui dimensi dari matrix yang ada. Apabila kita menggunakan 100 neuron pada lapisan tersembunyi maka W1 E R2*100, bi E R100, W2 E R100*2, b2.
E. Fungsi Rugi
Pada pembelajaran mesin dengan menggunakan jaringan syaraf tiruan kita mengenal yang namanya fungsi rugi (loss function). Fungsi rugi ini adalah perbedaan antara hasil keluaran dan prediksi keluaran. Ada beberapa metode yang digunakan untuk menentukan fungsi rugi. Pada implementasi kali ini akan menggunakan Cross Entropy Loss / Negative Log Likelihood. Ini adalah metode yang umum digunakan untuk permasalahan klasifikasi.
training_set_size = len(X) # Besar training set
nn_input_dim = 2 # dimensi lapisan masukan
nn_ouput_dim = 2 # dimensi lapisan keluaran
learning_rate = 0.01
regularization = 0.01
Kemudian implementasi dari fungsi rugi pada data yang sudah dibangkitkan.
def loss_func(model):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis = 1, keepdims = True)
# menghitung fungsi rugi
correct_logprobs = -np.log(probs[range(training_set_size), y])
data_loss = np.sum(correct_logprobs)
data_loss += regularization / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
return 1./training_set_size * data_loss
Kita juga membuat sebuah fungsi untuk menghitung prediksi keluaran sebagai berikut.
def predict(model, x):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
z1 = x.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = exp_scores / np.sum(exp_scores, axis = 1, keepdims = True)
return np.argmax(probs, axis = 1)
Berikut adalah fungsi melatih dari jaringan. Fungsi ini menggunakan gradient descent.
# Membangun model dan mengembalikan hasil model
# - nn_hdim: jumlah neuron pada lapisan tersembunyi
# - num_gd: jumlah yang melewati training data
# - print_loss: jika True akan menampilkan nilai rugi setiap iterasi per 1000 kali
def build_model(nn_hdim, num_gd=20000, print_loss=False):
# Inisialisasi parameter.
np.random.seed(0)
W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
b1 = np.zeros((1, nn_hdim))
W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
b2 = np.zeros((1, nn_output_dim))
# mengembalikan hasil model
model = {}
# Gradient descent untuk setiap batch
for i in range(0, num_gd):
# Propagasi ke depan
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# Propagasi ke belakang
delta3 = probs
delta3[range(num_examples), y] -= 1
dW2 = (a1.T).dot(delta3)
db2 = np.sum(delta3, axis=0, keepdims=True)
delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
dW1 = np.dot(X.T, delta2)
db1 = np.sum(delta2, axis=0)
# regularization
dW2 += reg_lambda * W2
dW1 += reg_lambda * W1
# perbarui parameter gradient descent
W1 += -epsilon * dW1
b1 += -epsilon * db1
W2 += -epsilon * dW2
b2 += -epsilon * db2
# Memberikan nilai parameter baru
model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
# Menampilkan nilai fungsi rugi (opsional)
if print_loss and i % 1000 == 0:
print("Rugi selepas iterasi ke %i: %f" %(i, loss_func(model)))
return model
G. Jaringan Syaraf Tiruan dengan 3 Buah Lapisan Tersembunyi
Uji coba jaringan syaraf dengan menggunakan 3 buah lapisan tersembunyi.
# Membangun model jaringan dengan tiga buah lapisan tersembunyi
model = build_model(3, print_loss=True)
# menampilkan plot
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Plot denga lapisan tersembunyi sebanyak 3")
Hasilnya cukup baik. Model yang dibangun bisa menentukan batas dan membagi sesuai dengan kelas.
H. Modifikasi Jumlah Lapisan Tersembunyi
Contoh diatas kita menggunakan lapisan tersembunyi sebanyak 3. Berikut kita akan lakukan uji coba dengan menggunakan lapisan tersembunyi 1, 2, 3, 4, 5, 20, 50.
plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
plt.subplot(5, 2, i+1)
plt.title('Jumlah lapisan tersembunyi %d' % nn_hdim)
model = build_model(nn_hdim)
plot_decision_boundary(lambda x: predict(model, x))
plt.show()
Dari hasil diatas kita temukan dengan menggunakan dimensi kecil kita bisa melihat tren dari data yang ada akan tetapi jika dimensi yang digunakan terlalu besar akan menyebabkan overfitting. Pada artikel mendatang kita akan mencoba beberapa kombinasi dari jaringan syaraf untuk mengetahui lebih dalam.
Selamat mencoba