Update validate_onnx_parity.py
Build and Deploy / build (map[dockerfile:apps/api/Dockerfile name:api]) (push) Failing after 3m54s
Build and Deploy / build (map[dockerfile:apps/ml-service/Dockerfile name:ml]) (push) Failing after 41s
Build and Deploy / build (map[dockerfile:apps/web/Dockerfile name:web]) (push) Failing after 21s
Build and Deploy / deploy (push) Skipped

update code & comment
This commit is contained in:
Taufik Pathurrohman
2026-06-18 21:31:03 +07:00
committed by GitHub
parent 7877892f9b
commit bd5aa81988
+18 -2
View File
@@ -10,10 +10,10 @@ import onnxruntime as ort
import tensorflow as tf import tensorflow as tf
from PIL import Image, UnidentifiedImageError from PIL import Image, UnidentifiedImageError
# Definisi label kelas sesuai urutan output model klasifikasi
LABELS = ["Bercak Daun", "Daun Sehat", "Karat Daun", "Hawar Daun"] LABELS = ["Bercak Daun", "Daun Sehat", "Karat Daun", "Hawar Daun"]
# Kelas eksepsi kustom untuk menangani ketidaksesuaian akurasi prediksi
class ParityError(RuntimeError): class ParityError(RuntimeError):
"""Raised when Keras and ONNX predictions do not match.""" """Raised when Keras and ONNX predictions do not match."""
pass pass
@@ -33,16 +33,19 @@ def preprocess_image(image_path, input_size):
Raises: Raises:
ParityError: If image cannot be loaded or processed. ParityError: If image cannot be loaded or processed.
""" """
# Penanganan error secara aman saat memuat gambar ke format RGB
try: try:
img = Image.open(image_path).convert("RGB") img = Image.open(image_path).convert("RGB")
except (FileNotFoundError, UnidentifiedImageError, OSError) as e: except (FileNotFoundError, UnidentifiedImageError, OSError) as e:
raise ParityError(f"Failed to load image {image_path}: {e}") raise ParityError(f"Failed to load image {image_path}: {e}")
# Penyesuaian resolusi gambar menggunakan metode interpolasi Bilinear
try: try:
img = img.resize((input_size, input_size), Image.Resampling.BILINEAR) img = img.resize((input_size, input_size), Image.Resampling.BILINEAR)
except Exception as e: except Exception as e:
raise ParityError(f"Failed to resize image {image_path}: {e}") raise ParityError(f"Failed to resize image {image_path}: {e}")
# Konversi ke matriks float32 dan penambahan dimensi batch (1, H, W, C)
img_array = np.array(img, dtype=np.float32) img_array = np.array(img, dtype=np.float32)
img_batch = np.expand_dims(img_array, axis=0) img_batch = np.expand_dims(img_array, axis=0)
@@ -60,6 +63,7 @@ def predict_keras(model, image_batch):
Returns: Returns:
Predictions array (1, num_classes). Predictions array (1, num_classes).
""" """
# Eksekusi inferensi pada model TensorFlow/Keras tanpa log proses
predictions = model.predict(image_batch, verbose=0) predictions = model.predict(image_batch, verbose=0)
return predictions return predictions
@@ -75,6 +79,7 @@ def predict_onnx(session, image_batch):
Returns: Returns:
Predictions array (1, num_classes). Predictions array (1, num_classes).
""" """
# Eksekusi inferensi secara dinamis pada model ONNX menggunakan sesi runtime
input_name = session.get_inputs()[0].name input_name = session.get_inputs()[0].name
predictions = session.run(None, {input_name: image_batch}) predictions = session.run(None, {input_name: image_batch})
return predictions[0] return predictions[0]
@@ -94,14 +99,18 @@ def validate_image(image_path, keras_model, onnx_session, input_size, atol):
Raises: Raises:
ParityError: If predictions do not match or image cannot be processed. ParityError: If predictions do not match or image cannot be processed.
""" """
# Menyiapkan tensor gambar untuk pengujian
img_batch = preprocess_image(image_path, input_size) img_batch = preprocess_image(image_path, input_size)
# Mengekstrak matriks probabilitas dari kedua format model
keras_pred = predict_keras(keras_model, img_batch) keras_pred = predict_keras(keras_model, img_batch)
onnx_pred = predict_onnx(onnx_session, img_batch) onnx_pred = predict_onnx(onnx_session, img_batch)
# Mendapatkan indeks kelas dengan probabilitas tertinggi (Top-1)
keras_label_idx = np.argmax(keras_pred[0]) keras_label_idx = np.argmax(keras_pred[0])
onnx_label_idx = np.argmax(onnx_pred[0]) onnx_label_idx = np.argmax(onnx_pred[0])
# Validasi keselarasan keputusan klasifikasi utama
if keras_label_idx != onnx_label_idx: if keras_label_idx != onnx_label_idx:
keras_label = LABELS[keras_label_idx] keras_label = LABELS[keras_label_idx]
onnx_label = LABELS[onnx_label_idx] onnx_label = LABELS[onnx_label_idx]
@@ -110,6 +119,7 @@ def validate_image(image_path, keras_model, onnx_session, input_size, atol):
f"Keras={keras_label}, ONNX={onnx_label}" f"Keras={keras_label}, ONNX={onnx_label}"
) )
# Validasi selisih nilai desimal probabilitas menggunakan toleransi absolut
if not np.allclose(keras_pred, onnx_pred, atol=atol): if not np.allclose(keras_pred, onnx_pred, atol=atol):
max_diff = np.max(np.abs(keras_pred - onnx_pred)) max_diff = np.max(np.abs(keras_pred - onnx_pred))
raise ParityError( raise ParityError(
@@ -117,6 +127,7 @@ def validate_image(image_path, keras_model, onnx_session, input_size, atol):
f"max difference={max_diff:.6e} (atol={atol})" f"max difference={max_diff:.6e} (atol={atol})"
) )
# Pencatatan log sistem jika kedua model presisi 100%
label = LABELS[keras_label_idx] label = LABELS[keras_label_idx]
logging.info(f"PASS: {image_path} -> {label}") logging.info(f"PASS: {image_path} -> {label}")
@@ -125,6 +136,7 @@ def main():
"""Validate parity between Keras and ONNX models.""" """Validate parity between Keras and ONNX models."""
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
# Inisialisasi parser argumen untuk antarmuka CLI (Command Line Interface)
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Validate parity between Keras and ONNX models" description="Validate parity between Keras and ONNX models"
) )
@@ -161,6 +173,7 @@ def main():
args = parser.parse_args() args = parser.parse_args()
# Pengecekan eksistensi berkas model sebelum memuat memori
if not args.keras_model.exists(): if not args.keras_model.exists():
msg = f"Keras model not found at {args.keras_model}" msg = f"Keras model not found at {args.keras_model}"
logging.error(msg) logging.error(msg)
@@ -171,15 +184,18 @@ def main():
logging.error(msg) logging.error(msg)
raise FileNotFoundError(msg) raise FileNotFoundError(msg)
# Memuat model Keras (tanpa kompilasi agar lebih hemat beban komputasi)
logging.info(f"Loading Keras model from {args.keras_model}...") logging.info(f"Loading Keras model from {args.keras_model}...")
keras_model = tf.keras.models.load_model(args.keras_model, compile=False) keras_model = tf.keras.models.load_model(args.keras_model, compile=False)
# Memuat sesi ONNX dengan penyedia eksekusi CPU murni
logging.info(f"Loading ONNX model from {args.onnx_model}...") logging.info(f"Loading ONNX model from {args.onnx_model}...")
onnx_session = ort.InferenceSession( onnx_session = ort.InferenceSession(
str(args.onnx_model), str(args.onnx_model),
providers=["CPUExecutionProvider"], providers=["CPUExecutionProvider"],
) )
# Iterasi pengujian paritas (kesetaraan performa) untuk setiap gambar
logging.info(f"Validating {len(args.images)} image(s)...") logging.info(f"Validating {len(args.images)} image(s)...")
for image_path in args.images: for image_path in args.images:
try: try: