추론 함수 작성
추론 함수의 구조와 핵심 컴포넌트를 이해하고 작성합니다.
추론 함수란
추론 함수는 사용자가 업로드한 이미지를 처리하여 예측 결과를 반환하는 Python 함수입니다.
처리 과정:
- 전처리: 사용자 이미지 → model 입력 형식 변환
- 추론: Triton Inference Server로 model 호출
- 후처리: model 출력 → 시각화된 결과 이미지 생성
기본 구조
from keynet_inference import keynet_function, UserInput, TritonPlugin, Storage
@keynet_function(
name="my-inference",
description="Custom inference function",
base_image="openwhisk/action-python-v3.12:latest",
)
def main(args: dict):
# 클라이언트 초기화
storage = Storage()
triton = TritonPlugin()
# 1. 사용자 입력
image_url = UserInput.get("image_url")
# 2. 전처리
image_buffer = storage.get(image_url)
input_tensor = preprocess(image_buffer)
# 3. 추론
outputs = triton.predict(df={"input_0": input_tensor})
# 4. 후처리
result_buffer = postprocess(outputs["output_0"])
# 5. 결과 업로드 및 반환
result_url = storage.put(result_buffer)
return {"url": result_url}
핵심 컴포넌트
@keynet_function
함수의 진입점을 정의하는 데코레이터입니다.
@keynet_function(
name="function-name",
description="Function description",
base_image="openwhisk/action-python-v3.12:latest",
)
def main(args: dict):
pass
파라미터:
name(필수): 함수의 고유 이름description(필수): 함수 설명base_image(권장): OpenWhisk base image (기본값:openwhisk/action-python-v3.12:latest)
함수 시그니처:
args: dict: Platform에서 전달하는 파라미터 (내부적으로 사용)- 반환값:
{"url": <결과 이미지 URL>}(필수)
반환값 형식 (중요)
추론 함수는 반드시 다음 형식으로 반환해야 합니다:
return {"url": result_url}
url키는 필수입니다- 값은
storage.put()으로 업로드한 결과 이미지 URL이어야 합니다 - 다른 형식으로 반환하면 Platform에서 결과를 표시할 수 없습니다
UserInput
Platform에서 사용자가 입력한 값을 가져옵니다.
image_url = UserInput.get("image_url")
threshold = UserInput.get("threshold", 0.5) # 기본값 지정
중요
UserInput.get()의 key는 Platform Input 설정과 정확히 일치해야 합니다.
Storage
S3 스토리지에서 이미지를 다운로드하고 결과를 업로드합니다.
storage = Storage()
# 이미지 다운로드 (BytesIO 반환)
image_buffer = storage.get(image_url)
# 결과 업로드 (URL 반환)
result_url = storage.put(result_buffer)
# 반환값으로 사용 (필수)
return {"url": result_url}
storage.put() 반환값
storage.put()은 업로드된 이미지의 S3 URL을 반환합니다. 이 URL을 그대로 {"url": ...} 형식으로 반환해야 Platform에서 결과를 표시할 수 있습니다.
TritonPlugin
Triton Inference Server로 model을 호출합니다.
triton = TritonPlugin()
# 추론 실행
outputs = triton.predict(df={"input_0": input_tensor})
# outputs는 dict 형태: {"output_0": numpy.ndarray}
Model 이름은 Platform이 자동으로 지정하므로 별도 설정이 불필요합니다.
완전한 예제: MNIST
손글씨 숫자 인식 추론 함수입니다.
mnist_inference.py
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
from keynet_inference import keynet_function, UserInput, TritonPlugin, Storage
@keynet_function(
name="mnist-inference",
description="MNIST handwritten digit recognition",
base_image="openwhisk/action-python-v3.12:latest",
)
def main(args: dict):
"""
MNIST 손글씨 숫자 인식 추론 함수
입력: 손글씨 숫자 이미지
출력: 예측된 숫자와 시각화된 결과 이미지
"""
storage = Storage()
triton = TritonPlugin()
# 사용자 입력
image_url = UserInput.get("input_0")
# 1. 전처리: 이미지 다운로드 및 변환
image_buffer = storage.get(image_url)
image_buffer.seek(0)
image_data = image_buffer.read()
# 이미지 로드 및 grayscale 변환
from io import BytesIO as BIO
preprocess_buffer = BIO(image_data)
image = Image.open(preprocess_buffer).convert("L")
# MNIST 입력 형식으로 resize (28x28)
image_resized = image.resize((28, 28))
array = np.array(image_resized, dtype=np.float32)
# 색상 반전 처리 (흰 배경 → 검은 배경)
mean_brightness = array.mean()
if mean_brightness > 127:
array = 255.0 - array
# 정규화
array = array / 255.0
array = (array - 0.1307) / 0.3081
# Shape 변환: (28, 28) → (1, 1, 28, 28)
input_tensor = np.expand_dims(array, axis=(0, 1))
# 2. 추론
outputs = triton.predict(df={"input_0": input_tensor})
# 3. 후처리: 예측 결과 파싱
predictions = outputs["output_0"]
predicted_digit = int(np.argmax(predictions))
# log_softmax → 확률 변환
exp_preds = np.exp(predictions[0] - predictions[0].max())
probabilities = exp_preds / exp_preds.sum()
confidence = float(probabilities[predicted_digit])
# 4. 결과 시각화
canvas_size = 400
result_image = Image.new("RGB", (canvas_size, canvas_size), "white")
draw = ImageDraw.Draw(result_image)
# 폰트 설정
try:
digit_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 200
)
conf_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40
)
except:
digit_font = ImageFont.load_default()
conf_font = ImageFont.load_default()
# 숫자 표시 (중앙)
digit_text = str(predicted_digit)
try:
bbox = draw.textbbox((0, 0), digit_text, font=digit_font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
except:
text_width = 150
text_height = 200
digit_x = (canvas_size - text_width) // 2
digit_y = (canvas_size - text_height) // 2 - 30
draw.text((digit_x, digit_y), digit_text, fill="#E53935", font=digit_font)
# Confidence 표시 (하단)
conf_text = f"{confidence:.1%}"
try:
conf_bbox = draw.textbbox((0, 0), conf_text, font=conf_font)
conf_width = conf_bbox[2] - conf_bbox[0]
except:
conf_width = 100
conf_x = (canvas_size - conf_width) // 2
conf_y = canvas_size - 60
draw.text((conf_x, conf_y), conf_text, fill="#1976D2", font=conf_font)
# 5. 결과 업로드
result_buffer = BytesIO()
result_image.save(result_buffer, format="PNG")
result_buffer.seek(0)
result_url = storage.put(result_buffer)
return {"url": result_url}
의존성 관리
함수에서 사용하는 Python 패키지를 requirements.txt에 명시합니다.
requirements.txt
keynet-inference>=0.8.5
Pillow==10.4.0
numpy==1.26.4
버전 고정 권장
재현 가능한 환경을 위해 패키지 버전을 명시하는 것을 권장합니다.
다음 단계
함수 작성이 완료되었다면 배포 방법을 선택하세요.
CLI 배포 (권장)
간편한 자동 배포를 원한다면:
CLI로 배포하기 - keynet-inference push 명령어로 자동 빌드 및 배포
수동 배포 (레거시)
Dockerfile을 직접 제어하거나 복잡한 의존성이 필요하다면:
수동으로 배포하기 - Dockerfile 작성 → Harbor push → Platform 등록