soft-computing-ai / inference.py
SamuelTheo's picture
update model, dockerfile
51bece2
Raw History Blame Contribute Delete
7.7 kB
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from typing import Any
import cv2
import numpy as np
import skfuzzy as fuzz
import tensorflow as tf
from skfuzzy import control as ctrl
IMAGE_SIZE = (256, 256)
LABEL_TOKENS = ("neg", "neutral", "pos")
LABEL_MAP = {label: index for index, label in enumerate(LABEL_TOKENS)}
LABEL_NAMES = {
LABEL_MAP["neg"]: "Negative",
LABEL_MAP["neutral"]: "Neutral",
LABEL_MAP["pos"]: "Positive",
}
NUM_CLASSES = len(LABEL_TOKENS)
MODEL_PATH = (
Path(__file__).resolve().parent / "artifacts" / "cnn_fis_classifier.keras"
)
def get_model_path() -> Path:
return MODEL_PATH.resolve()
@lru_cache(maxsize=1)
def load_model():
model_path = get_model_path()
if not model_path.exists():
raise FileNotFoundError(
f"Keras model tidak ditemukan di: {model_path}. "
"Pastikan file artefak ada di folder ai_engine/artifacts."
)
return tf.keras.models.load_model(model_path)
def preprocess_image_bytes(image_bytes: bytes) -> np.ndarray:
image_array = np.frombuffer(image_bytes, dtype=np.uint8)
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
if image is None:
raise ValueError("Gambar tidak bisa dibaca. Pastikan file upload adalah gambar yang valid.")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, IMAGE_SIZE, interpolation=cv2.INTER_AREA)
image = image.astype(np.float32) / 255.0
return image
def normalize_probability_vector(model_output: np.ndarray) -> np.ndarray:
probabilities = np.asarray(model_output, dtype=np.float32).reshape(-1)
if probabilities.shape[0] == NUM_CLASSES:
return probabilities
raise ValueError(
f"Expected {NUM_CLASSES} class probabilities for "
f"{', '.join(LABEL_TOKENS)}, received shape {probabilities.shape}."
)
def probability_dict_from_vector(probability_vector: np.ndarray) -> dict[str, float]:
probabilities = normalize_probability_vector(probability_vector)
if probabilities.shape[0] != NUM_CLASSES:
raise ValueError(
f"Expected {NUM_CLASSES} class probabilities, received shape {probabilities.shape}."
)
return {
"negative": float(probabilities[LABEL_MAP["neg"]]),
"neutral": float(probabilities[LABEL_MAP["neutral"]]),
"positive": float(probabilities[LABEL_MAP["pos"]]),
}
def build_fuzzy_system() -> ctrl.ControlSystemSimulation:
positive_confidence = ctrl.Antecedent(np.linspace(0, 1, 101), "positive_confidence")
neutral_confidence = ctrl.Antecedent(np.linspace(0, 1, 101), "neutral_confidence")
negative_confidence = ctrl.Antecedent(np.linspace(0, 1, 101), "negative_confidence")
final_decision = ctrl.Consequent(np.linspace(0, 100, 101), "final_decision")
for variable in (positive_confidence, neutral_confidence, negative_confidence):
variable["low"] = fuzz.trimf(variable.universe, [0.0, 0.0, 0.45])
variable["medium"] = fuzz.trimf(variable.universe, [0.2, 0.5, 0.8])
variable["high"] = fuzz.trimf(variable.universe, [0.55, 1.0, 1.0])
final_decision["negative"] = fuzz.trimf(final_decision.universe, [0, 0, 35])
final_decision["neutral"] = fuzz.trimf(final_decision.universe, [30, 50, 70])
final_decision["positive"] = fuzz.trimf(final_decision.universe, [65, 100, 100])
rules = [
ctrl.Rule(
positive_confidence["high"]
& neutral_confidence["low"]
& negative_confidence["low"],
final_decision["positive"],
),
ctrl.Rule(
positive_confidence["high"]
& neutral_confidence["medium"]
& negative_confidence["low"],
final_decision["positive"],
),
ctrl.Rule(
positive_confidence["medium"]
& neutral_confidence["low"]
& negative_confidence["low"],
final_decision["positive"],
),
ctrl.Rule(
negative_confidence["high"]
& positive_confidence["low"]
& neutral_confidence["low"],
final_decision["negative"],
),
ctrl.Rule(
negative_confidence["high"]
& positive_confidence["low"]
& neutral_confidence["medium"],
final_decision["negative"],
),
ctrl.Rule(
negative_confidence["medium"]
& positive_confidence["low"]
& neutral_confidence["low"],
final_decision["negative"],
),
ctrl.Rule(
neutral_confidence["high"]
& positive_confidence["low"]
& negative_confidence["low"],
final_decision["neutral"],
),
ctrl.Rule(
neutral_confidence["high"]
& positive_confidence["medium"]
& negative_confidence["low"],
final_decision["neutral"],
),
ctrl.Rule(
neutral_confidence["high"]
& negative_confidence["medium"]
& positive_confidence["low"],
final_decision["neutral"],
),
ctrl.Rule(
positive_confidence["medium"] & negative_confidence["medium"],
final_decision["neutral"],
),
ctrl.Rule(
positive_confidence["medium"] & neutral_confidence["medium"],
final_decision["neutral"],
),
ctrl.Rule(
negative_confidence["medium"] & neutral_confidence["medium"],
final_decision["neutral"],
),
ctrl.Rule(
positive_confidence["high"] & negative_confidence["high"],
final_decision["neutral"],
),
ctrl.Rule(
positive_confidence["low"]
& negative_confidence["low"]
& neutral_confidence["low"],
final_decision["neutral"],
),
]
control_system = ctrl.ControlSystem(rules)
return ctrl.ControlSystemSimulation(control_system)
def fuzzy_label_from_probabilities(probability_vector: np.ndarray) -> tuple[str, float]:
probabilities = probability_dict_from_vector(probability_vector)
fuzzy_system = build_fuzzy_system()
fuzzy_system.input["positive_confidence"] = probabilities["positive"]
fuzzy_system.input["neutral_confidence"] = probabilities["neutral"]
fuzzy_system.input["negative_confidence"] = probabilities["negative"]
fuzzy_system.compute()
decision_score = float(fuzzy_system.output["final_decision"])
if decision_score >= 65:
label = "Positive"
elif decision_score <= 35:
label = "Negative"
else:
label = "Neutral"
return label, decision_score
def predict_image_bytes(image_bytes: bytes) -> dict[str, Any]:
image = preprocess_image_bytes(image_bytes)
model = load_model()
batch = np.expand_dims(image, axis=0).astype(np.float32)
model_output = np.asarray(model.predict(batch, verbose=0)[0], dtype=np.float32)
probability_vector = normalize_probability_vector(model_output)
probabilities = probability_dict_from_vector(probability_vector)
fuzzy_label, fuzzy_score = fuzzy_label_from_probabilities(probability_vector)
cnn_prediction_name = LABEL_NAMES[int(np.argmax(probability_vector))]
return {
"label": fuzzy_label,
"fis_score": fuzzy_score,
"cnn_prediction_name": cnn_prediction_name,
"probabilities": probabilities,
}