Siri Belajar AI: Jaringan Neural Berlingkar (Convolutional Neural Network) Bahagian 2

Dalam siri yang lepas kita sudahpun belajar tentang jaringan neural berlingkar. Kita sudahpun tahu bahawa jaringan neural berlingkar ini mengubah cara jaringan neural memproses imej. Sebelum itu jaringan neural memproses imej mengikut piksel. Tapi hakikatnya manusia dan mana-mana makhluk hidup tidak memproses imej mengikut piksel. Makhluk hidup dan juga manusia memproses imej mengikut corak yang dikenali dari yang mudah sehinggalah ke yang kompleks. Pemprosesan itu lantas digabungkan lalu membentuk imej yang lengkap.
Proses ini diterjemahkan oleh penyelidik AI ke dalam proses perlingkaran (convolutional). Ia merupakan satu operasi matematik yang membolehkan garis luar (outline) dan juga corak sesuatu imej dikenalpasti dan diterjemahkan dalam bentuk matriks nombor. Ia kemudiannya digabungkan (pooling) menggunakan proses pooling bagi mengurangkan beban ketika proses latihan kelak.
Untuk bahagian yang kedua ini, kita akan cuba untuk mengganbungkan kedua-dua proses perlingkaran (convolutional) dan juga pooling ke dalam jaringan neural yang sedia ada. Kemudian kita akan melatih jaringan neural tersebut menggunakan data yang telahpun diproses melalui proses perlingkaran dan juga pooling. Dengan itu, maka lengkaplah jaringan neural berlingkar kita.
Mari kita sediakan dulu data kita
Sebelum kita mula melatih, perlulah kita persiapkan dulu data kita. Sekali lagi kita akan menggunakan data dari MNIST, data digit dengan tulisan tangan yang sudah dipecahkan mengikut piksel bersaiz (28 x 28). Data itu yang kita akan masukkan ke dalam jaringan neural berlingkar.
import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from tqdm import tqdm #untuk buat progress bar, baru nampak gempak sikitBentuk data yang diproses oleh Jaringan Neural Berlingkar
Jaringan Neural Berlingkar ataupun CNN mengambil data dalam bentuk yang tertentu. Ini bagi memastikan proses perlingkaran boleh berlaku dengan baik. Bentuk matriks yang diambil adalah seperti di bawah,
Saiz kumpulan menentukan jumlah imej yang akan diproses oleh jaringan neural, tinggi dan lebar adalah resolusi imej tersebut, dan saluran adalah kedalaman imej tersebut. (contohnya 1 untuk hitam putih dan 3 untuk yang berwarna mewakili RGB).
#kemudian mari kita muat turun data dan proseskan data itu menjadi input yang sesuai
# kita muat turun data mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# kita normalizekan image tersebut, bahagikan data itu kepada 255, kerana setiap data yang ada itu mewakil skala RGB bagi hitam dan putih( 0 hitam dan 255 putih)
train_images = train_images / 255.0
test_images = test_images / 255.0
# kita tukarkan bentuk imej agar sesuai untuk diproses dalam CNN. -1 diletakkan jika kita tak tahu saiz kumpulan kita
train_images = train_images.reshape(-1, 28, 28, 1)
test_images = test_images.reshape(-1, 28, 28, 1)Mari kita bina jaringan neural berlingkar kita
Seperti mana dalam gambar di bawah, kita perlu membina satu jaringan neural yang lengkap yang terdiri daripada beberapa lapisan.
Lapisan perlingkaran (convolution) + Fungsi Pengaktifan ReLU
Lapisan Pooling + flatten (penyekkan)
Lapisan tersembunyi pertama + Fungsi Pengaktifan ReLU
Lapisan tersembunyi kedua + Fungsi pengaktifan softmax
Sebagai ringkasan, apa yang berlaku dalam jaringan neural berlingkar adalah seperti berikut. Imej tulisan tangan MNIST yang sudahpun ditukarkan ke dalam matriks nombor dilalukan pada lapisan perlingkaran bagi membolehkan ia mengesan garis tepi (edges) dan corak imej tersebut. Proses ini dioptimisasikan lagi dengan fungsi pengaktifan ReLU. Jika kita ingat lagi, ReLU hanya mengambil nilai yang positif dan mengabaikan nilai yang negatif. Kemudian, bagi mengurangkan lagi jumlah parameter, ia melalui proses pooling dan dipenyekkan bagi menghasilkan tatasusunan data 1-dimensi. Ini kerana tatasusunan data yang terhasil dari proses perlingkaran dan pooling menghasilkan tatasusunan data multi-dimensi, dan ini tidak boleh dimasukkan dalam lapisan tersembunyi jaringan neural.
Setelah tatasusunan data menjadi 1-dimensi, ia dimasukkan dalam lapisan tersembunyi pertama dalam jaringan neural, dan diikuti oleh fungsi pengaktifan ReLU. Kemudian ia melalui lapisan tersembunyi kedua dan juga fungsi pengaktifan softmax. Tujuan fungsi pengaktifan softmax adalah bagi menukarkan nilai dalam matriks kepada nilai kebarangkalian (dari 0 ke 1). Ini bagi memudahkan proses evaluasi untuk mengenalpasti label yang diramalkan nanti.
Kita mulakan dengan meletakkan fungsi untuk perlingkaran dan juga pooling
# fungsi perlingkaran
def convolve2d(image, kernel, stride=1):
"""
Ini adalah fungsi untuk melakukan proses perlingkaran (convolutional). Proses perlingkaran memastikan
berlakunya darab antara matriks imej dan juga penapis (kernel) yang digunakan. Proses ini dibuat bagi mengesan
garis tepi (edges) dalam matriks imej yang dilakukan. Stride (langkah) merujuk pada jumlah piksel yang dilangkah ketika
melakukan proses perlingkaran
"""
kernel_height, kernel_width = kernel.shape #kernel (penapis) akan kita wujudkan nanti
image_height, image_width, image_channels = image.shape
output_height = (image_height - kernel_height) // stride + 1 #proses perlingkaran mengikut tinggi
output_width = (image_width - kernel_width) // stride + 1 #proses perlingkaran mengikut lebar
output = np.zeros((output_height, output_width, image_channels)) #output yang dihasilkan
#iterasi proses perlingkaran
for c in range(image_channels):
for i in range(0, image_height - kernel_height + 1, stride):
for j in range(0, image_width - kernel_width + 1, stride):
output[i // stride, j // stride, c] = np.sum(
image[i:i+kernel_height, j:j+kernel_width, c] * kernel
)
return output
# fungsi max pooling
def max_pooling(image, size=2, stride=2):
"""
Fungsi untuk melakukan pooling.Pooling bermaksud kesemua input dikumpulkan menjadi satu matriks yang lebih kecil. Dalam fungsi ini
kita menukarkan semua matriks imej yang dimasukkan ke dalam menjadi separuh dari saiz asal. Fungsi max pooling ini mengambil nombor tertinggi
dalam jendela langkah yang diambil.
"""
image_height, image_width, image_channels = image.shape
output_height = (image_height - size) // stride + 1
output_width = (image_width - size) // stride + 1
output = np.zeros((output_height, output_width, image_channels))
for c in range(image_channels):
for i in range(0, image_height - size + 1, stride):
for j in range(0, image_width - size + 1, stride):
output[i // stride, j // stride, c] = np.max(
image[i:i + size, j:j + size, c]
)
return outputSeterusnya, mari kita cuba pula untuk mendefinisikan semua fungsi pembantu seperti softmax, relu dan juga lapisan tersembunyi.
# Melaksanakan operasi matriks darab pada semua pemberat dan bias di lapisan tersembunyi pertama dan kedua
def fully_connected(inputs, weights, biases):
return np.dot(inputs, weights) + biases
#fungsi pengaktifan ReLU yang mengambil hanya nilai positif
def relu(x):
return np.maximum(0, x)
#fungsi pengaktifan softmax yang menukar nilai output kepada nilai kebarangkalian
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / exp_x.sum(axis=1, keepdims=True)Sebelum kita bina jaringan neural, mari kita letakkan nilai awal (initialisasikan) pada semua parameter kita. Untuk penapis (kernel), kita membina matriks dengan saiz (3 X 3). Untuk lapisan pertama, kita perlu bina matriks pemberat dan bias. Untuk pemberat, kita bina matriks dengan bentuk (169 X 128). 128 adalah jumlah node yang ada dalam jaringan neural kita.
# Inisialisasi berat dan bias
conv_kernel = np.random.randn(3, 3) #penapis atau kernel
fc_weights = np.random.randn(169, 128) #pemberat lapisan tersembunyi pertama
fc_biases = np.random.randn(128) #bias lapisan tersembunyi pertama
output_weights = np.random.randn(128, 10) #pemberat lapisan tersembunyi kedua
output_biases = np.random.randn(10) #bias lapisan tersembunyi keduaSelepas kita sudah initialisasikan semua parameter, kita sudah bersedia untuk membentuk jaringan neural berlingkar yang lengkap menggunakan fungsi forward pass.
# Melaksanakan operasi forward pass pada imej menggunakan berat dan bias yang diberikan
def forward_pass(image):
"""
Fungsi ini membina satu jaringan neural berlingkar yang lengkap bermula dengan proses perlingkaran, relu, max pooling, flatten, lapisan pertama,
lapisan kedua dan diakhiri dengan softmax. Fungsi ini menghasilkan output kebarangkalian (probabilities) dan semua nilai awal parameter.
"""
conv_output = convolve2d(image, conv_kernel)
relu_output = relu(conv_output)
pooled_output, pool_indices = max_pooling(relu_output)
flattened_output = pooled_output.reshape(1, -1)
fc_output = fully_connected(flattened_output, fc_weights, fc_biases)
relu_fc_output = relu(fc_output)
output = fully_connected(relu_fc_output, output_weights, output_biases)
probabilities = softmax(output)
return probabilities, (conv_output, relu_output, pooled_output, pool_indices, flattened_output, fc_output, relu_fc_output, output)Proses forward pass hanya akan menghasilkan keputusan yang tidak kerana ia masih lagi belum belajar. Seperti yang kita belajar dalam siri-siri yang lepas, proses pembelajaran hanya berlaku melalui proses backward pass (backpropagation). Dalam backward pass, nilai pemberat, bias dan semua parameter akakn dikemaskini bagi mendapatkan hasil yang paling dekat.
def backward_pass(image, label, learning_rate):
"""
Fungsi backward pass yang membolehkan jaringan neural kita belajar. Ini membolehkan kita memperkemaskinikan nilai parameter kita bagi
mengurangkan cerun kesilapan (gradient error)
"""
global conv_kernel, fc_weights, fc_biases, output_weights, output_biases
probabilities, (conv_output, relu_output, pooled_output, pool_indices, flattened_output, fc_output, relu_fc_output, output) = forward_pass(image)
# Kira kecerunan
output_error = probabilities.copy()
output_error[0, label] -= 1
grad_output_weights = np.dot(relu_fc_output.T, output_error)
grad_output_biases = output_error.sum(axis=0)
relu_fc_error = np.dot(output_error, output_weights.T)
relu_fc_error[relu_fc_output <= 0] = 0
grad_fc_weights = np.dot(flattened_output.T, relu_fc_error)
grad_fc_biases = relu_fc_error.sum(axis=0)
pooled_error = np.dot(relu_fc_error, fc_weights.T).reshape(pooled_output.shape)
relu_error = np.zeros_like(relu_output)
for c in range(pooled_output.shape[2]):
for i in range(pooled_output.shape[0]):
for j in range(pooled_output.shape[1]):
max_index = np.unravel_index(pool_indices[i, j, c], (2, 2))
relu_error[i*2 + max_index[0], j*2 + max_index[1], c] = pooled_error[i, j, c]
grad_conv_kernel = np.zeros(conv_kernel.shape)
for c in range(image.shape[2]):
for i in range(conv_kernel.shape[0]):
for j in range(conv_kernel.shape[1]):
grad_conv_kernel[i, j] += np.sum(image[i:i+conv_output.shape[0], j:j+conv_output.shape[1], c] * relu_error[:, :, c])
# Kemas kini berat dan bias
conv_kernel -= learning_rate * grad_conv_kernel
fc_weights -= learning_rate * grad_fc_weights
fc_biases -= learning_rate * grad_fc_biases.reshape(fc_biases.shape)
output_weights -= learning_rate * grad_output_weights
output_biases -= learning_rate * grad_output_biases.reshape(output_biases.shape)Ok, sekarang semua komponen sudah tersedia, mari kita gabungkan semua dalam satu fungsi latihan seperti di bawah.
# fungsi untuk melakukan latihan
def train_model(train_images, train_labels, epochs, learning_rate):
"""
Fungsi untuk melatih jaringan neural berlingkar tadi, menggunakpakai fungsi backward pass bila diberi nilai epochs dan
juga kadar pembelajaran (learning rate)
"""
for epoch in range(epochs):
print(f"Epoch {epoch+1}/{epochs}")
for i in tqdm(range(len(train_images)), desc="Training"):
image = train_images[i]
label = train_labels[i]
backward_pass(image, label, learning_rate)Dengan fungsi latihan yang sudah tersedia, mari kita mula melatih jaringan neural berlingkar kita. Untuk latihan kali ini, kita memilih 10 epoch dengan kadar pembelajaran pada 0.01. Perlu diingatkan, kerana jumlah parameter yang banyak, maka masa latihan pun akan menjadi panjang. Jika ingin mengurangkan masa latihan, anda boleh mengurangkan jumlah nod yang ada pada lapisan pertama dan lapisan kedua jaringan neural.
# Tetapan latihan
epochs = 10
learning_rate = 0.01
# Latih model
train_model(train_images, train_labels, epochs, learning_rate)Setelah latihan selesai, kita boleh uji model jaringan neural berlingkar yang sudah kita bina. Mari buat satu fungsi yang boleh membdandingkan dan menguji sama ada jaringan neural berlingkar kita mampu untuk mengenal tulisan tangan.
def evaluate_model(test_images, test_labels):
"""
Fungsi untuk menilai kemampuan jaringan neural berlingkar yang kita sudah latih. Ia membandingkan nilai ramalan dengan label yang sebenar
dan pada masa yang sama ia juga menunjukkan imej tersebut untuk memudahkan kefahaman pembaca.
"""
correct_predictions = 0
num_visualizations = 3 # Jumlah imej yang ingin divisualisasikan
for i in tqdm(range(len(test_images)), desc="Evaluating"):
test_image = test_images[i]
test_label = test_labels[i]
probabilities, _ = forward_pass(test_image)
predicted_label = np.argmax(probabilities, axis=1)[0]
correct_predictions += (predicted_label == test_label)
# Visualisasikan beberapa imej ujian
if i < num_visualizations:
plt.figure(figsize=(2, 2))
plt.title(f"True: {test_label}, Predicted: {predicted_label}")
plt.imshow(test_image.squeeze(), cmap='gray')
plt.axis('off')
plt.show()
accuracy = correct_predictions / len(test_images)
print(f"Accuracy: {accuracy:.2f}")Seusai itu, kita boleh menggunakan fungsi evaluate_model untuk menguji model kita, seperti di bawah:
# Nilai model
evaluate_model(test_images, test_labels)Jika semuanya berjalan dengan lancar, maka kita akan mendapat hasilnya seperti di bawah:
Ya, ada yang tepat, ada yang tak. Tapi model kita berjaya dengan ketepat sehingga 95% dalam mengenalpasti tulisan tangan.
Alhamdulillah. Sekarang semua dah faham kan tentang jaringan neural berlingkar? Kita jumpa lagi di siri yang akan datang.



