Segmentasi gambar adalah proses fundamental dalam computer vision, khususnya dalam analisis citra medis. Proses ini melibatkan pemisahan gambar digital menjadi beberapa segmen atau region, memungkinkan identifikasi dan isolasi objek yang diminati. Dalam konteks medis, segmentasi sangat krusial untuk mendeteksi, mengukur, dan menganalisis struktur biologis seperti sel kanker pada citra mikroskopik atau histopatologi. Salah satu arsitektur deep learning yang sangat sukses untuk tugas segmentasi gambar medis adalah U-Net. Artikel ini akan memandu Anda langkah demi langkah dalam tutorial segmentasi sel kanker u-net menggunakan library Keras dan TensorFlow di Python, memanfaatkan dataset publik.
Pentingnya Segmentasi Sel Kanker & Keunggulan U-Net Keras
Segmentasi sel kanker memungkinkan analisis kuantitatif dan kualitatif yang lebih akurat terhadap karakteristik sel, seperti ukuran, bentuk, dan jumlahnya, yang penting untuk diagnosis dan penelitian. Arsitektur U-Net, yang diperkenalkan oleh Olaf Ronneberger dkk. pada tahun 2015, dirancang khusus untuk segmentasi citra biomedis. Keunikannya terletak pada kemampuannya untuk bekerja efektif bahkan dengan dataset yang relatif kecil dan kemampuannya menangkap konteks global serta detail lokal melalui jalur encoder-decoder dengan skip connections. Mari kita mulai membangun model u-net keras segmentasi gambar medis kita.
Persiapan Sebelum Memulai Tutorial U-Net
Sebelum terjun ke dalam kode, ada beberapa persiapan yang perlu dilakukan, baik dari segi pengetahuan maupun perangkat lunak.
Pengetahuan Dasar yang Dibutuhkan
- Pemahaman dasar bahasa pemrograman Python.
- Pemahaman konsep dasar Deep Learning, terutama Convolutional Neural Networks (CNN), proses training, dan fungsi loss.
- Familiaritas dengan konsep dasar computer vision akan sangat membantu.
Software dan Library
- Python (disarankan versi 3.7 ke atas).
- TensorFlow (versi 2.x direkomendasikan). Keras biasanya sudah terintegrasi.
- Numpy: Untuk operasi numerik dan manipulasi array.
- Matplotlib: Untuk visualisasi data dan hasil.
- OpenCV (
opencv-python
): Untuk pemrosesan gambar dasar seperti membaca dan mengubah ukuran. - Scikit-image: Berguna untuk beberapa operasi pemrosesan gambar tambahan.
- Tqdm: Untuk menampilkan progress bar yang informatif selama proses yang panjang.
Langkah 1: Menyiapkan Lingkungan Python & TensorFlow
Langkah pertama adalah memastikan semua library yang dibutuhkan terinstal di lingkungan Python Anda. Anda bisa menggunakan pip (package installer for Python) untuk menginstalnya. Buka terminal atau command prompt Anda dan jalankan perintah berikut:
pip install tensorflow numpy matplotlib opencv-python scikit-image tqdm
Setelah instalasi selesai, Anda dapat memverifikasinya dengan mencoba mengimpor library tersebut di Python shell atau dalam script Python untuk implementasi u-net tensorflow python:
import tensorflow as tf
import numpy as np
import cv2
import matplotlib.pyplot as plt
print(f"TensorFlow Version: {tf.__version__}")
Jika tidak ada pesan error, berarti lingkungan Anda sudah siap untuk melanjutkan ke langkah berikutnya.
Langkah 2: Memilih & Mempersiapkan Dataset Sel Kanker
Dataset adalah jantung dari setiap proyek machine learning, terutama dalam domain medis.
Pentingnya Dataset Berkualitas untuk Segmentasi Medis
Kualitas dan kuantitas dataset adalah faktor penentu utama keberhasilan model deep learning segmentasi gambar medis. Dataset harus representatif terhadap variasi data yang mungkin ditemui di dunia nyata dan memiliki anotasi (mask segmentasi) yang akurat.
Memilih Dataset Publik Segmentasi Sel Kanker
Ada beberapa segmentasi sel kanker dataset publik yang bisa digunakan untuk tugas ini. Salah satu yang paling populer adalah Data Science Bowl 2018 Nuclei dataset. Dataset ini berisi koleksi besar gambar mikroskopik dengan anotasi nukleus sel yang beragam. Anda bisa mencarinya dan mengunduhnya dari platform seperti Kaggle.
Catatan: Pastikan Anda membaca dan mematuhi lisensi penggunaan dataset yang Anda pilih.
Setelah mengunduh, ekstrak file dataset. Anda perlu memahami struktur direktorinya untuk memuat data dengan benar.
Memahami Struktur Dataset
Dataset seperti Data Science Bowl 2018 biasanya memiliki struktur direktori yang terorganisir. Umumnya, strukturnya adalah sebagai berikut:
- Folder untuk data training:
- Subfolder berisi gambar asli (misalnya, dalam format
.png
). - Subfolder terpisah berisi mask segmentasi (ground truth), seringkali sebagai gambar biner (hitam-putih) di mana piksel nukleus diberi nilai tertentu (misal, 255 atau 1) dan background bernilai 0. Mask ini bisa berupa satu file per gambar atau multiple file jika instance segmentation diperlukan (namun untuk U-Net standar, kita fokus pada semantic segmentation – satu mask gabungan).
- Subfolder berisi gambar asli (misalnya, dalam format
- Folder untuk data testing (jika disediakan terpisah).
Penting untuk memastikan Anda mengetahui path (lokasi file) ke gambar input dan mask segmentasi yang bersesuaian.
Langkah 3: Preprocessing Gambar Medis dengan Python
Preprocessing gambar medis python adalah tahap krusial untuk menyiapkan data agar sesuai dengan input model U-Net dan meningkatkan performa training. Langkah ini melibatkan pemuatan, pengubahan ukuran, normalisasi, dan augmentasi data.
Memuat Gambar dan Mask
Kita perlu menulis fungsi untuk membaca file gambar dan mask dari direktori. Library seperti `os` atau `glob` bisa digunakan untuk mendapatkan daftar path file, dan `OpenCV` (cv2) atau `PIL`/`Pillow` (melalui `skimage.io` misalnya) untuk membaca file gambar.
import os
import glob
from skimage.io import imread # Alternatif lain selain cv2
# Contoh path (sesuaikan dengan struktur dataset Anda)
TRAIN_IMG_PATH = 'path/to/your/dataset/stage1_train/'
# Asumsi: tiap gambar ada di folder sendiri, dengan subfolder 'images' dan 'masks'
# Contoh: stage1_train/ID1/images/ID1.png, stage1_train/ID1/masks/*.png
def load_data_paths(base_path):
image_paths = []
mask_paths_list = [] # Menggunakan nama yang lebih deskriptif
# Dapatkan semua ID unik dari folder training
ids = next(os.walk(base_path))[1]
for img_id in ids:
img_path = os.path.join(base_path, img_id, 'images', img_id + '.png')
# Mengambil semua path file mask untuk gambar ini
mask_files = glob.glob(os.path.join(base_path, img_id, 'masks', '*.png'))
# Hanya tambahkan jika gambar dan mask ditemukan
if os.path.exists(img_path) and mask_files:
image_paths.append(img_path)
mask_paths_list.append(mask_files) # Simpan list path mask per gambar
return image_paths, mask_paths_list
# Contoh pemanggilan (gunakan path yang benar)
# image_paths, mask_paths_list = load_data_paths(TRAIN_IMG_PATH)
# print(f"Total images found: {len(image_paths)}")
Fungsi berikut membaca gambar dan menggabungkan multiple mask (jika ada) menjadi satu mask biner.
import numpy as np
import cv2
IMG_HEIGHT = 128
IMG_WIDTH = 128
IMG_CHANNELS = 3 # Atau 1 jika grayscale
def read_and_process_image(img_path):
img = cv2.imread(img_path, cv2.IMREAD_COLOR) # Baca sebagai BGR
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Konversi ke RGB
img = cv2.resize(img, (IMG_WIDTH, IMG_HEIGHT), interpolation=cv2.INTER_AREA)
return img
def read_and_combine_masks(mask_files, height=IMG_HEIGHT, width=IMG_WIDTH):
# Inisialisasi mask gabungan (single channel)
combined_mask = np.zeros((height, width, 1), dtype=bool)
for mask_file in mask_files:
mask = cv2.imread(mask_file, cv2.IMREAD_GRAYSCALE)
# Pastikan mask berhasil dibaca
if mask is not None:
mask = cv2.resize(mask, (width, height), interpolation=cv2.INTER_NEAREST) # Interpolasi Nearest untuk mask
mask = np.expand_dims(mask, axis=-1) # Tambah channel dimension
combined_mask = np.maximum(combined_mask, mask > 0) # Gabungkan mask (pixel > 0 adalah foreground)
# else:
# print(f"Warning: Could not read mask file {mask_file}") # Opsional: tambahkan warning
return combined_mask.astype(np.float32) # Konversi ke float untuk input model
# Contoh penggunaan (perlu loop untuk semua data):
# if image_paths:
# single_image = read_and_process_image(image_paths[0])
# single_mask = read_and_combine_masks(mask_paths_list[0])
# print(single_image.shape, single_mask.shape)
Kode di atas menunjukkan cara memuat path dan fungsi dasar untuk membaca serta menggabungkan mask. Anda perlu mengadaptasinya sesuai struktur dataset spesifik Anda.
Resizing dan Normalisasi
Model U-Net memerlukan input dengan dimensi yang konsisten. Kita perlu mengubah ukuran semua gambar dan mask ke ukuran yang ditentukan (misal, 128×128 atau 256×256), seperti yang sudah dilakukan dalam fungsi di atas. Selain itu, nilai piksel gambar biasanya dinormalisasi ke rentang [0, 1] untuk membantu stabilitas training. Mask harus dipastikan dalam format biner (0.0 atau 1.0) dan tipe data float32.
# Resizing sudah termasuk dalam fungsi read_and_process_image dan read_and_combine_masks di atas
def normalize_image(img):
# Pastikan input adalah float sebelum dibagi
return img.astype(np.float32) / 255.0
# Dalam loop pemrosesan data:
# processed_image = normalize_image(read_and_process_image(img_path))
# processed_mask = read_and_combine_masks(mask_files)
# Pastikan mask sudah dalam bentuk [0.0, 1.0]
# print(np.unique(processed_mask)) # Harusnya hanya 0. dan 1.
Augmentasi Data untuk Performa Lebih Baik
Augmentasi data secara artifisial memperbanyak data training dengan menerapkan transformasi acak (seperti rotasi, flip horizontal/vertikal, zoom, perubahan kecerahan) pada gambar dan mask secara bersamaan. Ini membantu model menjadi lebih robust terhadap variasi dan mengurangi risiko overfitting, terutama pada dataset medis yang seringkali terbatas.
Anda bisa menggunakan `ImageDataGenerator` dari Keras (kurang fleksibel untuk segmentasi dengan mask) atau library khusus augmentasi seperti `albumentations`. Penggunaan `tf.data` pipeline dengan fungsi augmentasi `tf.image` juga merupakan pilihan yang efisien.
# Contoh Konseptual Augmentasi dengan tf.image (diintegrasikan dalam tf.data pipeline)
# def augment_data(image, mask):
# if tf.random.uniform(()) > 0.5:
# image = tf.image.flip_left_right(image)
# mask = tf.image.flip_left_right(mask)
# if tf.random.uniform(()) > 0.5:
# image = tf.image.flip_up_down(image)
# mask = tf.image.flip_up_down(mask)
# # Tambahkan transformasi lain jika diperlukan (rotasi, zoom, etc.)
# return image, mask
Membuat Pipeline Input Data (tf.data)
Untuk dataset besar, memuat seluruhnya ke memori tidak efisien atau bahkan tidak mungkin. Menggunakan API `tf.data` memungkinkan pembuatan pipeline data yang efisien untuk memuat, memproses (termasuk augmentasi), mengacak (shuffle), dan membuat batch data secara on-the-fly.
# Menggunakan tf.data (lebih direkomendasikan)
def create_tf_dataset(image_paths, mask_paths_list, batch_size, augment=False):
# Buat dataset dari path
# Perlu memastikan mask_paths_list adalah format yang bisa di-slice oleh tf.data
# Jika mask_paths_list berisi list (untuk multiple files per image), perlu penanganan khusus
# Solusi: Proses mask menjadi satu file per image terlebih dahulu, atau gunakan tf.py_function
# Menggunakan RaggedTensor jika jumlah mask per gambar bervariasi
mask_paths_ragged = tf.ragged.constant(mask_paths_list)
path_ds = tf.data.Dataset.from_tensor_slices((image_paths, mask_paths_ragged))
# Fungsi wrapper untuk memuat dan memproses dalam tf.data
def load_and_process(img_path, mask_files_tensor):
# Gunakan tf.py_function untuk menjalankan fungsi Python (cv2, numpy)
# Ini lebih mudah untuk menangani list path mask yang bervariasi
img = tf.py_function(func=read_and_process_image,
inp=[img_path],
Tout=tf.float32)
# Perlu mengonversi tensor ragged/list path kembali ke list python untuk fungsi numpy
mask = tf.py_function(func=lambda files_tensor: read_and_combine_masks([p.decode('utf-8') for p in files_tensor.numpy()], IMG_HEIGHT, IMG_WIDTH),
inp=[mask_files_tensor],
Tout=tf.float32)
# Normalisasi gambar
img = normalize_image(img) # Panggil fungsi normalisasi
# Pastikan shape di-set secara eksplisit setelah py_function
img.set_shape([IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS])
mask.set_shape([IMG_HEIGHT, IMG_WIDTH, 1])
return img, mask
# Map fungsi load_and_process ke dataset path
image_label_ds = path_ds.map(load_and_process, num_parallel_calls=tf.data.AUTOTUNE)
# Terapkan augmentasi jika diminta
if augment:
# image_label_ds = image_label_ds.map(augment_data, num_parallel_calls=tf.data.AUTOTUNE)
pass # Implementasi fungsi augment_data di sini atau panggil library
# Shuffle, batch, dan prefetch
dataset = image_label_ds.shuffle(buffer_size=len(image_paths))
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(buffer_size=tf.data.AUTOTUNE)
return dataset
# BATCH_SIZE = 16
# Perlu split data paths menjadi train dan validation sets terlebih dahulu
# train_image_paths, val_image_paths, train_mask_paths, val_mask_paths = train_test_split(image_paths, mask_paths_list, test_size=0.1)
# train_dataset = create_tf_dataset(train_image_paths, train_mask_paths, BATCH_SIZE, augment=True)
# val_dataset = create_tf_dataset(val_image_paths, val_mask_paths, BATCH_SIZE, augment=False)
Kode `tf.data` di atas adalah contoh yang lebih detail, menggunakan `tf.py_function` untuk mengakomodasi fungsi preprocessing yang ada. Perhatikan konversi path dari tensor ke string di dalam lambda `py_function`. Anda mungkin perlu menyesuaikan ini lebih lanjut.
Langkah 4: Membangun Arsitektur U-Net dengan Keras
Setelah data siap, langkah selanjutnya adalah mendefinisikan arsitektur model U-Net.
Memahami Arsitektur U-Net Lebih Dalam
Arsitektur U-Net penjelasan singkatnya memiliki bentuk seperti huruf ‘U’ dan terdiri dari dua jalur utama:
- Encoder Path (Contracting Path): Jalur ini secara bertahap mengurangi resolusi spasial sambil meningkatkan jumlah fitur (channel). Ini berfungsi seperti CNN pada umumnya untuk mengekstraksi fitur kontekstual dari gambar. Terdiri dari blok-blok berulang: biasanya dua lapisan konvolusi 3×3 (dengan aktivasi ReLU), diikuti oleh operasi max-pooling 2×2 untuk downsampling.
- Bottleneck: Lapisan di bagian terdalam (dasar ‘U’), menghubungkan encoder dan decoder. Biasanya terdiri dari beberapa lapisan konvolusi tanpa pooling.
- Decoder Path (Expansive Path): Jalur ini secara bertahap meningkatkan resolusi spasial dan melokalisasi fitur untuk menghasilkan peta segmentasi. Setiap langkah biasanya terdiri dari:
- Lapisan ‘up-convolution’ atau ‘transposed convolution’ 2×2 yang menaikkan resolusi dan mengurangi separuh jumlah channel fitur.
- Konkatenasi (penggabungan) dengan peta fitur dari encoder path yang bersesuaian (dari level resolusi yang sama). Ini adalah skip connection yang krusial, memungkinkan decoder menggunakan kembali informasi fitur spasial resolusi tinggi dari encoder.
- Dua lapisan konvolusi 3×3 (dengan ReLU), mirip dengan encoder, untuk memproses fitur gabungan.
- Output Layer: Lapisan konvolusi 1×1 terakhir yang memetakan vektor fitur dari lapisan decoder terakhir ke jumlah kelas segmentasi yang diinginkan. Untuk segmentasi biner (foreground/background), layer ini memiliki 1 filter dengan fungsi aktivasi Sigmoid.
Skip connections adalah inovasi kunci U-Net, memungkinkan kombinasi informasi semantik tingkat tinggi (dari decoder) dengan informasi spasial detail (dari encoder) untuk menghasilkan batas segmentasi yang presisi.
Implementasi Kode U-Net dengan Keras TensorFlow
Berikut adalah implementasi arsitektur U-Net menggunakan Keras Functional API, sering disebut sebagai kode python u-net medical imaging:
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Dropout, Conv2DTranspose, concatenate
from tensorflow.keras.models import Model
def build_unet_model(input_shape):
inputs = Input(input_shape)
# -- Encoder Path --
# Block 1
c1 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(inputs)
c1 = Dropout(0.1)(c1)
c1 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c1)
p1 = MaxPooling2D((2, 2))(c1)
# Block 2
c2 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p1)
c2 = Dropout(0.1)(c2)
c2 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c2)
p2 = MaxPooling2D((2, 2))(c2)
# Block 3
c3 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p2)
c3 = Dropout(0.2)(c3)
c3 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c3)
p3 = MaxPooling2D((2, 2))(c3)
# Block 4
c4 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p3)
c4 = Dropout(0.2)(c4)
c4 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c4)
p4 = MaxPooling2D(pool_size=(2, 2))(c4)
# -- Bottleneck --
c5 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p4)
c5 = Dropout(0.3)(c5)
c5 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c5)
# -- Decoder Path --
# Block 6
u6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c5)
u6 = concatenate([u6, c4]) # Skip connection
c6 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u6)
c6 = Dropout(0.2)(c6)
c6 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c6)
# Block 7
u7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c6)
u7 = concatenate([u7, c3]) # Skip connection
c7 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u7)
c7 = Dropout(0.2)(c7)
c7 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c7)
# Block 8
u8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(c7)
u8 = concatenate([u8, c2]) # Skip connection
c8 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u8)
c8 = Dropout(0.1)(c8)
c8 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c8)
# Block 9
u9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(c8)
u9 = concatenate([u9, c1], axis=3) # Skip connection
c9 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u9)
c9 = Dropout(0.1)(c9)
c9 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c9)
# -- Output Layer --
outputs = Conv2D(1, (1, 1), activation='sigmoid')(c9) # Sigmoid untuk segmentasi biner
model = Model(inputs=[inputs], outputs=[outputs])
return model
# Tentukan input shape sesuai preprocessing
input_shape = (IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)
model = build_unet_model(input_shape)
model.summary() # Cetak ringkasan arsitektur model
Kode ini mendefinisikan fungsi `build_unet_model` yang membuat instance model U-Net dengan Keras. Jumlah filter (misal, 16, 32, …) dan laju dropout dapat disesuaikan sebagai hyperparameter.
Langkah 5: Kompilasi Model U-Net (Optimizer, Loss, Metrik)
Sebelum model dapat dilatih, ia perlu dikompilasi. Proses kompilasi mengonfigurasi model untuk training dengan menentukan optimizer, fungsi loss (kerugian), dan metrik evaluasi.
Memilih Optimizer
Optimizer adalah algoritma yang digunakan untuk memperbarui bobot model berdasarkan data dan fungsi loss. Adam adalah optimizer yang sangat populer dan seringkali memberikan hasil yang baik untuk berbagai masalah deep learning, termasuk segmentasi. Learning rate (laju pembelajaran) awal, misalnya 1e-4 atau 1e-3, mungkin perlu disesuaikan (tuning).
Memilih Fungsi Loss untuk Segmentasi
Fungsi loss mengukur seberapa baik prediksi model dibandingkan dengan target sebenarnya (ground truth mask). Untuk segmentasi biner (foreground vs background), `BinaryCrossentropy` adalah fungsi loss standar. Namun, pada gambar medis, sering terjadi ketidakseimbangan kelas (misalnya, area sel jauh lebih kecil dari area background). Dalam kasus seperti itu, fungsi loss yang lebih fokus pada overlap antar region, seperti Dice Loss atau IoU Loss (sering disebut juga Jaccard Loss), atau kombinasi keduanya dengan Binary Crossentropy, bisa lebih efektif.
# Contoh implementasi Dice Loss sebagai fungsi kustom
# import tensorflow.keras.backend as K
# def dice_loss(y_true, y_pred, smooth=1e-6):
# y_true_f = K.flatten(y_true)
# y_pred_f = K.flatten(y_pred)
# intersection = K.sum(y_true_f * y_pred_f)
# score = (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
# return 1 - score
# def bce_dice_loss(y_true, y_pred):
# # Memberikan bobot yang sama untuk BCE dan Dice Loss
# return tf.keras.losses.binary_crossentropy(y_true, y_pred) + dice_loss(y_true, y_pred)
Metrik Evaluasi Penting untuk Segmentasi Gambar
Metrik digunakan untuk memonitor dan mengevaluasi performa model selama dan setelah training. Berbeda dengan loss, metrik tidak digunakan untuk mengoptimalkan model tetapi untuk mengukur kinerjanya dalam satuan yang lebih mudah diinterpretasikan. Metrik evaluasi segmentasi gambar yang umum digunakan adalah:
- Intersection over Union (IoU) atau Jaccard Index: Mengukur rasio antara area persimpangan (intersection) dan area gabungan (union) antara prediksi dan ground truth.
- Dice Coefficient (F1 Score): Mirip dengan IoU, tetapi dihitung sebagai 2 * (area persimpangan) / (total area prediksi + total area ground truth). Sering digunakan bersama Dice Loss.
- Akurasi Piksel: Persentase piksel yang diklasifikasikan dengan benar. Namun, metrik ini bisa menyesatkan pada dataset yang tidak seimbang.
# Implementasi Dice Coefficient sebagai metrik kustom
import tensorflow.keras.backend as K
def dice_coefficient(y_true, y_pred, smooth=1e-6):
y_true_f = K.flatten(y_true)
y_pred_f = K.flatten(y_pred)
intersection = K.sum(y_true_f * y_pred_f)
return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
# Alternatif: Menggunakan metrik bawaan Keras jika sesuai
# iou_metric = tf.keras.metrics.MeanIoU(num_classes=2)
# Perhatian: MeanIoU Keras mungkin memerlukan input y_pred dalam bentuk label kelas (bukan probabilitas)
# atau perlu thresholding sebelum dihitung.
Kode Kompilasi Model (TensorFlow Keras)
Sekarang, kita dapat mengompilasi model dengan pilihan optimizer, loss, dan metrik dalam tensorflow keras tutorial segmentasi ini.
# Pilih loss function: 'binary_crossentropy' atau loss kustom seperti bce_dice_loss
loss_function = 'binary_crossentropy'
# Atau jika menggunakan loss kustom:
# loss_function = bce_dice_loss
# Pilih metrik: Metrik kustom dice_coefficient atau metrik Keras lainnya
# Pastikan metrik sesuai dengan output sigmoid model (probabilitas)
metrics_list = ['accuracy', dice_coefficient]
# Atau jika menggunakan MeanIoU (hati-hati dengan format input):
# metrics_list = [tf.keras.metrics.MeanIoU(num_classes=2)]
# Kompilasi model
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer,
loss=loss_function,
metrics=metrics_list)
print("Model compiled successfully!")
Pastikan loss dan metrik yang Anda gunakan kompatibel dengan output model Anda (misalnya, output sigmoid antara 0 dan 1 cocok dengan `binary_crossentropy` dan `dice_coefficient`).
Langkah 6: Melatih Model U-Net untuk Segmentasi
Dengan model yang telah dikompilasi dan data generator (`tf.data.Dataset`) yang siap, kita bisa mulai mempelajari cara melatih u-net untuk segmentasi.
Menjalankan Proses Training
Gunakan metode `model.fit()` dari Keras untuk memulai proses training. Jika Anda menggunakan `tf.data.Dataset` untuk data training dan validasi, Keras akan secara otomatis menangani iterasi batch.
# Asumsikan train_dataset dan val_dataset (objek tf.data.Dataset) sudah dibuat dari langkah 3
# Jika Anda memuat data sebagai numpy array X_train, Y_train, X_val, Y_val:
# history = model.fit(X_train, Y_train, validation_data=(X_val, Y_val), batch_size=BATCH_SIZE, epochs=EPOCHS, callbacks=callbacks_list)
EPOCHS = 50 # Jumlah epoch bisa disesuaikan (misal: 50, 100, atau lebih)
# BATCH_SIZE = 16 # Pastikan ini sama dengan yang digunakan saat membuat dataset
# List callbacks (didefinisikan di bagian selanjutnya)
# callbacks_list = [checkpoint, early_stopping, reduce_lr]
# Melatih model menggunakan tf.data.Dataset
# history = model.fit(
# train_dataset,
# epochs=EPOCHS,
# validation_data=val_dataset,
# callbacks=callbacks_list # Sertakan callbacks di sini
# )
# print("Training finished.")
# Objek 'history' akan menyimpan nilai loss dan metrik selama training
Catatan: Training model deep learning bisa memakan waktu yang signifikan, mulai dari beberapa jam hingga beberapa hari, tergantung pada ukuran dataset, kompleksitas model, jumlah epoch, dan terutama ketersediaan hardware (GPU sangat direkomendasikan untuk mempercepat proses).
Parameter Training Utama
epochs
: Menentukan berapa kali keseluruhan dataset training akan dilewatkan melalui model.batch_size
: Jumlah sampel data yang diproses dalam satu iterasi sebelum bobot model diperbarui. Ditentukan saat membuat `tf.data.Dataset`.steps_per_epoch
: Jumlah batch yang akan diambil dari generator/dataset dalam satu epoch (biasanya dihitung otomatis jika input adalah `tf.data.Dataset` atau generator Keras standar).validation_data
: Data yang digunakan untuk mengevaluasi loss dan metrik model di akhir setiap epoch. Data ini tidak digunakan untuk training.validation_steps
: Jumlah batch yang akan diambil dari `validation_data` untuk evaluasi (juga biasanya otomatis untuk `tf.data.Dataset`).callbacks
: List fungsi yang akan dipanggil pada berbagai tahap training (dijelaskan di bawah).
Optimalkan Training dengan Callbacks Keras
Callbacks adalah alat yang sangat berguna dalam Keras untuk memodifikasi atau memantau proses training. Beberapa callbacks yang paling penting untuk segmentasi adalah:
ModelCheckpoint
: Menyimpan bobot model (atau seluruh model) secara berkala selama training. Opsi penting adalah `save_best_only=True`, yang hanya akan menyimpan model jika performanya pada data validasi (berdasarkan metrik yang dipantau, misal, `val_dice_coefficient`) membaik.EarlyStopping
: Menghentikan proses training secara otomatis jika performa pada data validasi tidak menunjukkan peningkatan selama beberapa epoch berturut-turut (ditentukan oleh parameter `patience`). Ini membantu mencegah overfitting dan menghemat waktu komputasi.ReduceLROnPlateau
: Mengurangi learning rate optimizer secara otomatis jika metrik pada data validasi berhenti membaik (stagnan). Penurunan learning rate dapat membantu model ‘menyempurnakan’ bobotnya di sekitar solusi optimal.
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau
# Callback untuk menyimpan model terbaik berdasarkan val_dice_coefficient
checkpoint = ModelCheckpoint('unet_cancer_segmentation_best.h5', # Nama file untuk menyimpan model
monitor='val_dice_coefficient', # Metrik yang dipantau pada data validasi
verbose=1, # Tampilkan pesan saat model disimpan
save_best_only=True, # Hanya simpan model terbaik
mode='max') # 'max' karena Dice Coefficient lebih tinggi lebih baik ('min' untuk loss)
# Callback untuk menghentikan training jika tidak ada peningkatan performa validasi
early_stopping = EarlyStopping(monitor='val_dice_coefficient', # Metrik yang dipantau
patience=10, # Jumlah epoch tanpa peningkatan sebelum berhenti
verbose=1,
mode='max',
restore_best_weights=True) # Kembalikan bobot dari epoch terbaik saat berhenti
# Callback untuk mengurangi learning rate jika performa validasi stagnan
reduce_lr = ReduceLROnPlateau(monitor='val_dice_coefficient', # Metrik yang dipantau
factor=0.1, # Faktor pengurangan LR (new_lr = lr * factor)
patience=5, # Jumlah epoch tanpa peningkatan sebelum LR dikurangi
verbose=1,
mode='max',
min_lr=1e-6) # Batas bawah learning rate
# Gabungkan semua callbacks ke dalam satu list
callbacks_list = [checkpoint, early_stopping, reduce_lr]
# Gunakan list ini dalam pemanggilan model.fit():
# history = model.fit(..., callbacks=callbacks_list)
Menyimpan Model yang Telah Dilatih
Setelah proses training selesai (atau dihentikan oleh `EarlyStopping`), model terbaik biasanya sudah disimpan secara otomatis oleh `ModelCheckpoint` (misalnya, sebagai `unet_cancer_segmentation_best.h5`). Jika Anda menggunakan `restore_best_weights=True` pada `EarlyStopping`, objek `model` di memori juga akan memiliki bobot terbaik. Anda juga bisa menyimpan model final secara manual jika diperlukan.
# Model terbaik seharusnya sudah disimpan oleh ModelCheckpoint.
# Jika Anda ingin menyimpan model keadaan terakhir secara manual (misalnya, setelah training selesai tanpa EarlyStopping):
# model.save('unet_cancer_segmentation_final_epoch.h5')
# print("Final epoch model saved.")
# Memuat kembali model terbaik yang disimpan:
# custom_objects = {'dice_coefficient': dice_coefficient, 'dice_loss': dice_loss} # Sertakan loss/metrik kustom
# loaded_model = tf.keras.models.load_model('unet_cancer_segmentation_best.h5',
# custom_objects=custom_objects)
Langkah 7: Mengevaluasi Performa Model U-Net
Setelah training, sangat penting untuk mengevaluasi performa model pada data test (data yang sama sekali tidak pernah dilihat oleh model selama proses training atau pemilihan hyperparameter). Ini memberikan estimasi yang tidak bias tentang seberapa baik model akan berkinerja pada data baru di dunia nyata.
Pertama, siapkan data test menggunakan pipeline preprocessing yang sama dengan data training (resizing, normalisasi) dan buat `tf.data.Dataset` untuk test set.
# Muat model terbaik yang disimpan (pastikan menyertakan custom objects jika ada)
custom_objects = {'dice_coefficient': dice_coefficient} # Tambahkan loss kustom jika digunakan saat kompilasi
# Pastikan path file model sudah benar
loaded_model = tf.keras.models.load_model('unet_cancer_segmentation_best.h5',
custom_objects=custom_objects)
print("Best model loaded successfully for evaluation.")
# Asumsikan test_dataset (objek tf.data.Dataset) sudah dibuat untuk data test
# test_loss, test_accuracy, test_dice = loaded_model.evaluate(test_dataset)
# print(f"Test Loss: {test_loss:.4f}")
# print(f"Test Accuracy: {test_accuracy:.4f}")
# print(f"Test Dice Coefficient: {test_dice:.4f}")
# Jika test set berupa numpy array (X_test, Y_test)
# results = loaded_model.evaluate(X_test, Y_test, batch_size=BATCH_SIZE)
# print(f"Test Loss: {results[0]:.4f}")
# print(f"Test Accuracy: {results[1]:.4f}")
# print(f"Test Dice Coefficient: {results[2]:.4f}") # Sesuaikan indeks berdasarkan metrik saat kompilasi
Nilai metrik pada data test, terutama Dice Coefficient atau IoU, memberikan indikasi kunci tentang kemampuan generalisasi model.
Langkah 8: Visualisasi Hasil Prediksi Segmentasi
Selain metrik kuantitatif, melihat hasil prediksi secara visual sangat membantu untuk memahami kekuatan dan kelemahan model. Kita bisa membandingkan gambar asli, mask ground truth, dan mask hasil prediksi model.
import matplotlib.pyplot as plt
import random
import numpy as np
# Muat kembali model jika belum
# custom_objects = {'dice_coefficient': dice_coefficient}
# loaded_model = tf.keras.models.load_model('unet_cancer_segmentation_best.h5', custom_objects=custom_objects)
# Asumsikan Anda memiliki path ke gambar dan mask test
# test_image_paths, test_mask_paths_list = load_data_paths('path/to/your/test/data/')
# Jika menggunakan tf.data.Dataset untuk test:
# test_dataset_unbatched = test_dataset.unbatch().take(5) # Ambil beberapa sampel
# for img, true_mask in test_dataset_unbatched:
# # Lakukan prediksi (img sudah dinormalisasi dari dataset)
# input_image = tf.expand_dims(img, axis=0)
# pred_mask_prob = loaded_model.predict(input_image)[0]
# pred_mask = (pred_mask_prob > 0.5).astype(np.uint8)
#
# # Tampilkan (perlu mengembalikan gambar ke skala 0-255 jika dinormalisasi)
# plt.figure(figsize=(12, 4))
# plt.subplot(1, 3, 1)
# plt.imshow((img.numpy() * 255).astype(np.uint8))
# plt.title("Original Image")
# plt.axis('off')
# # ... (lanjutkan subplot untuk true_mask dan pred_mask)
# Jika memuat manual dari path:
num_samples_to_show = 5
# sample_indices = random.sample(range(len(test_image_paths)), num_samples_to_show)
plt.figure(figsize=(15, 5 * num_samples_to_show))
# for i, index in enumerate(sample_indices):
# Muat gambar asli dan mask ground truth
# img_path = test_image_paths[index]
# mask_files = test_mask_paths_list[index]
# original_image_vis = read_and_process_image(img_path) # Untuk visualisasi (0-255)
# true_mask = read_and_combine_masks(mask_files)
# Lakukan prediksi
# Normalisasi gambar sebelum prediksi
# normalized_image_pred = normalize_image(original_image_vis)
# Tambahkan batch dimension
# input_image = np.expand_dims(normalized_image_pred, axis=0)
# predicted_mask_prob = loaded_model.predict(input_image)[0] # Hapus batch dimension
# Thresholding untuk mendapatkan mask biner
# threshold = 0.5
# predicted_mask = (predicted_mask_prob > threshold).astype(np.uint8)
# Tampilkan
# plt.subplot(num_samples_to_show, 3, i * 3 + 1)
# plt.imshow(original_image_vis)
# plt.title(f"Original Image {index}")
# plt.axis('off')
#
# plt.subplot(num_samples_to_show, 3, i * 3 + 2)
# plt.imshow(true_mask.squeeze(), cmap='gray') # Hapus channel dim untuk grayscale
# plt.title("Ground Truth Mask")
# plt.axis('off')
#
# plt.subplot(num_samples_to_show, 3, i * 3 + 3)
# plt.imshow(predicted_mask.squeeze(), cmap='gray') # Hapus channel dim
# plt.title("Predicted Mask")
# plt.axis('off')
# plt.tight_layout()
# plt.show()
# Catatan: Kode visualisasi di atas perlu dijalankan dengan data dan model yang sudah dimuat.
# Komentar dihapus agar lebih ringkas, tetapi logikanya tetap sama.
Visualisasi ini memungkinkan perbandingan langsung antara gambar input, anotasi sebenarnya (ground truth), dan hasil segmentasi model Anda. Perhatikan area di mana prediksi akurat dan di mana terjadi kesalahan (false positives: area diprediksi sebagai sel padahal bukan, atau false negatives: area sel yang terlewatkan).
Kesimpulan & Langkah Berikutnya Belajar Deep Learning
Selamat! Anda telah mengikuti panduan langkah demi langkah untuk membangun, melatih, dan mengevaluasi model U-Net untuk segmentasi sel kanker menggunakan Keras dan TensorFlow. Tutorial ini mencakup aspek-aspek penting mulai dari persiapan data menggunakan dataset publik, implementasi arsitektur U-Net, kompilasi model dengan loss dan metrik yang sesuai, proses training dengan callbacks penting, evaluasi menggunakan metrik evaluasi segmentasi gambar yang relevan, hingga visualisasi hasil prediksi.
Pencapaian ini merupakan fondasi yang kuat dalam perjalanan Anda belajar deep learning untuk gambar medis. Beberapa langkah selanjutnya yang bisa Anda eksplorasi untuk memperdalam pemahaman dan meningkatkan hasil meliputi:
- Optimasi Hyperparameter: Bereksperimen lebih lanjut dengan parameter seperti learning rate optimizer, ukuran batch (batch size), jumlah filter di setiap lapisan U-Net, fungsi aktivasi alternatif, atau bahkan modifikasi kecil pada arsitektur U-Net (misalnya, menggunakan Attention U-Net).
- Dataset Berbeda: Mencoba melatih dan menguji model pada dataset sel kanker atau gambar medis lainnya untuk melihat generalisasi model atau menyesuaikannya dengan domain spesifik.
- Eksplorasi Arsitektur Lain: Mempelajari dan mengimplementasikan arsitektur segmentasi lain seperti Fully Convolutional Network (FCN), SegNet, DeepLab, atau bahkan model berbasis Transformer yang lebih baru untuk segmentasi. Perbandingan seperti U-Net vs Mask R-CNN segmentasi bisa menjadi topik menarik, meskipun perlu dicatat bahwa Mask R-CNN dirancang untuk instance segmentation (membedakan setiap objek individual), sementara U-Net standar fokus pada semantic segmentation (membedakan kelas piksel).
- Transfer Learning & Fine-tuning: Menggunakan bobot model yang telah dilatih sebelumnya (pre-trained weights) pada dataset gambar besar (jika tersedia model yang relevan, misal dari dataset medis besar) sebagai titik awal untuk training pada dataset Anda. Ini seringkali dapat mempercepat konvergensi dan meningkatkan performa, terutama pada dataset yang lebih kecil.
- Post-processing: Menerapkan teknik pemrosesan tambahan pada output prediksi model (mask probabilitas atau biner) untuk menghaluskan batas segmentasi atau menghilangkan artefak kecil, misalnya menggunakan algoritma seperti Conditional Random Fields (CRF) atau operasi morfologi.
Proyek yang telah Anda selesaikan ini merupakan contoh proyek segmentasi gambar medis yang solid dan dapat menjadi tambahan berharga untuk portofolio Anda di bidang AI dan computer vision.
Kembangkan Solusi Computer Vision Anda Bersama Kirim.ai
Menerapkan AI dan computer vision, seperti yang ditunjukkan dalam tutorial segmentasi gambar medis ini, dapat membuka potensi besar dalam berbagai aplikasi, mulai dari percepatan diagnosis medis hingga otomatisasi proses di berbagai industri. Jika Anda atau organisasi Anda tertarik untuk mengembangkan atau mengintegrasikan solusi computer vision canggih yang disesuaikan dengan kebutuhan spesifik bisnis atau penelitian Anda, tim ahli di Kirim.ai siap membantu.
Kirim.ai menawarkan layanan pengembangan solusi AI yang komprehensif. Kami menyediakan platform SaaS dengan berbagai alat AI siap pakai, serta pengembangan platform kustom (aplikasi mobile, aplikasi web, sistem backend) yang dirancang khusus untuk memanfaatkan kekuatan kecerdasan buatan sesuai kebutuhan Anda. Tertarik menerapkan solusi Computer Vision canggih seperti segmentasi gambar medis atau aplikasi AI lainnya? Hubungi Kirim.ai hari ini untuk konsultasi solusi AI Anda.
Jelajahi bagaimana AI dapat mentransformasi alur kerja dan memberikan keunggulan kompetitif. Pelajari lebih lanjut tentang kapabilitas dan layanan AI kami di website Kirim.ai.
Tanggapan (0 )