#สร้าง ML API ด้วย Scikit-learn และ FastAPI ตั้งแต่ Train Model จนถึง Docker

Machine Learning Model ที่ผ่านการ Train แล้วจะมีประโยชน์มากขึ้นเมื่อสามารถนำไปใช้งานร่วมกับระบบอื่นได้ เช่น Web Application, Mobile Application, Backend Service หรือระบบภายในองค์กร

แนวทางที่นิยมคือการนำ Machine Learning Model มาเปิดให้บริการผ่าน REST API เพื่อให้ระบบอื่นส่งข้อมูลเข้ามาและรับผลการทำนายกลับไป

บทความนี้จะแสดงขั้นตอนการสร้าง ML API ด้วย Scikit-learn และ FastAPI แบบครบวงจร ตั้งแต่

  • เตรียมข้อมูล
  • Train Machine Learning Model
  • ประเมินผลโมเดล
  • บันทึกโมเดลด้วย joblib
  • โหลดโมเดลเข้าสู่ FastAPI
  • สร้าง Endpoint สำหรับ Prediction
  • ทดสอบผ่าน Swagger UI
  • ทดสอบด้วย cURL
  • สร้าง Docker Image
  • แนวทางปรับปรุงสำหรับ Production

ตัวอย่างใช้ Dataset ยอดนิยมคือ Iris Dataset และสร้างโมเดลด้วย RandomForestClassifier


#ภาพรวม Architecture

แนวคิดพื้นฐานของ ML API สามารถสรุปได้ดังนี้

Dataset
   ↓
Data Preparation
   ↓
Scikit-learn
   ↓
Train Model
   ↓
Evaluate Model
   ↓
Save Model
   ↓
model.joblib
   ↓
FastAPI
   ↓
REST API
   ↓
Client Application

เมื่อระบบพร้อมใช้งาน Client สามารถส่งข้อมูลผ่าน HTTP Request

Web / Mobile / Backend
          ↓
      POST /predict
          ↓
       FastAPI
          ↓
 Scikit-learn Model
          ↓
      Prediction
          ↓
     JSON Response

#1. เตรียม Environment

ควรแยก Python Environment ของโปรเจกต์ออกจากระบบหลักด้วย Virtual Environment

สร้างโฟลเดอร์โปรเจกต์

mkdir ml-api
cd ml-api

สร้าง Virtual Environment

python -m venv .venv

บน macOS หรือ Linux

source .venv/bin/activate

บน Windows

.venv\Scripts\activate

ติดตั้ง Library ที่ต้องใช้

pip install scikit-learn fastapi uvicorn joblib numpy

สามารถสร้างไฟล์ requirements.txt

scikit-learn
fastapi
uvicorn
joblib
numpy

จากนั้นติดตั้งด้วย

pip install -r requirements.txt

#2. โครงสร้างโปรเจกต์

โครงสร้างตัวอย่าง

ml-api/
├── app/
│   ├── __init__.py
│   └── main.py
├── model/
│   └── iris_model.joblib
├── train.py
├── requirements.txt
└── Dockerfile

แบ่งหน้าที่ออกเป็น

  • train.py สำหรับ Train และ Save Model
  • model/ สำหรับเก็บ Model Artifact
  • app/main.py สำหรับสร้าง API
  • requirements.txt สำหรับ Dependency
  • Dockerfile สำหรับ Containerization

#3. Dataset ที่ใช้ในตัวอย่าง

Scikit-learn มี Iris Dataset มาให้ใช้งานโดยตรง

Dataset มีข้อมูลดอก Iris จำนวน 3 Class ได้แก่

0 = setosa
1 = versicolor
2 = virginica

Input Feature มี 4 ค่า

sepal length
sepal width
petal length
petal width

ตัวอย่างข้อมูล

5.1, 3.5, 1.4, 0.2

เป้าหมายคือให้ Model ทำนายว่าข้อมูลดังกล่าวเป็น Iris ชนิดใด


#4. Train Machine Learning Model

สร้างไฟล์

train.py

และเพิ่มโค้ดดังนี้

from pathlib import Path

import joblib

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split


# Load dataset
iris = load_iris()

X = iris.data
y = iris.target


# Split dataset
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)


# Create model
model = RandomForestClassifier(
    n_estimators=100,
    random_state=42
)


# Train model
model.fit(X_train, y_train)


# Evaluate model
y_pred = model.predict(X_test)

accuracy = accuracy_score(
    y_test,
    y_pred
)

print(f"Accuracy: {accuracy:.4f}")


# Create model directory
Path("model").mkdir(
    exist_ok=True
)


# Save model
joblib.dump(
    model,
    "model/iris_model.joblib"
)

print("Model saved successfully")

รัน Training

python train.py

ตัวอย่างผลลัพธ์

Accuracy: 0.9667
Model saved successfully

หลังจากรันจะได้ไฟล์

model/iris_model.joblib

ไฟล์นี้คือ Machine Learning Model ที่พร้อมนำไปใช้ใน API


#5. ทำไมต้อง Save Model

ในการใช้งานจริง เราไม่ควร Train Model ใหม่ทุกครั้งที่ Client เรียก API

กระบวนการควรแยกเป็นสอง Phase

Training Phase

Dataset
   ↓
Train Model
   ↓
model.joblib

และ

Serving Phase

model.joblib
   ↓
Load Model
   ↓
API
   ↓
Prediction

ข้อดีคือ

  • ลดเวลาในการตอบสนอง
  • ไม่ต้อง Train ซ้ำ
  • Version Model ได้
  • Deploy Model ได้อิสระจาก Training Pipeline

#6. สร้าง FastAPI Application

สร้างไฟล์

app/main.py

เพิ่มโค้ด

from pathlib import Path

import joblib
import numpy as np

from fastapi import FastAPI
from pydantic import BaseModel


app = FastAPI(
    title="Iris ML API",
    description="Machine Learning API using Scikit-learn",
    version="1.0.0"
)


MODEL_PATH = Path(
    "model/iris_model.joblib"
)


model = joblib.load(
    MODEL_PATH
)


CLASS_NAMES = [
    "setosa",
    "versicolor",
    "virginica"
]


class IrisInput(BaseModel):
    sepal_length: float
    sepal_width: float
    petal_length: float
    petal_width: float


class PredictionResponse(BaseModel):
    prediction: int
    class_name: str
    probability: float


@app.get("/")
def root():
    return {
        "message": "Iris Machine Learning API"
    }


@app.get("/health")
def health():
    return {
        "status": "ok"
    }


@app.post(
    "/predict",
    response_model=PredictionResponse
)
def predict(data: IrisInput):

    features = np.array([
        [
            data.sepal_length,
            data.sepal_width,
            data.petal_length,
            data.petal_width
        ]
    ])

    prediction = model.predict(
        features
    )[0]

    probabilities = model.predict_proba(
        features
    )[0]

    probability = probabilities[
        prediction
    ]

    return {
        "prediction": int(prediction),
        "class_name": CLASS_NAMES[prediction],
        "probability": float(probability)
    }

#7. อธิบาย Input Schema

FastAPI ใช้ Pydantic ในการ Validate Request

class IrisInput(BaseModel):
    sepal_length: float
    sepal_width: float
    petal_length: float
    petal_width: float

Client ต้องส่ง JSON ที่มีโครงสร้างตรงกับ Schema

{
  "sepal_length": 5.1,
  "sepal_width": 3.5,
  "petal_length": 1.4,
  "petal_width": 0.2
}

หาก Client ส่งข้อมูลไม่ครบ FastAPI จะตอบ Validation Error โดยอัตโนมัติ


#8. Response Schema

กำหนด Response ด้วย

class PredictionResponse(BaseModel):
    prediction: int
    class_name: str
    probability: float

ตัวอย่าง Response

{
  "prediction": 0,
  "class_name": "setosa",
  "probability": 1.0
}

ทำให้ API มี Contract ที่ชัดเจน


#9. เปิด API Server

รันด้วย Uvicorn

uvicorn app.main:app --reload

Server จะทำงานที่

http://127.0.0.1:8000

ทดสอบ Root Endpoint

http://127.0.0.1:8000

ผลลัพธ์

{
  "message": "Iris Machine Learning API"
}

#10. Swagger UI

FastAPI สร้าง Interactive API Documentation ให้อัตโนมัติ

เปิด

http://127.0.0.1:8000/docs

จะพบ Swagger UI

สามารถเลือก

POST /predict

จากนั้นกด

Try it out

ใส่ข้อมูล

{
  "sepal_length": 5.1,
  "sepal_width": 3.5,
  "petal_length": 1.4,
  "petal_width": 0.2
}

กด Execute

ตัวอย่าง Response

{
  "prediction": 0,
  "class_name": "setosa",
  "probability": 1.0
}

#11. ทดสอบ API ด้วย cURL

สามารถทดสอบจาก Command Line

curl -X POST \
  http://127.0.0.1:8000/predict \
  -H "Content-Type: application/json" \
  -d '{
    "sepal_length": 5.1,
    "sepal_width": 3.5,
    "petal_length": 1.4,
    "petal_width": 0.2
  }'

ตัวอย่างผลลัพธ์

{
  "prediction": 0,
  "class_name": "setosa",
  "probability": 1.0
}

#12. Prediction Flow

เมื่อ Client เรียก

POST /predict

กระบวนการทำงานคือ

JSON Request
    ↓
Pydantic Validation
    ↓
Convert to NumPy Array
    ↓
model.predict()
    ↓
model.predict_proba()
    ↓
Build JSON Response
    ↓
Return Prediction

#13. ใช้ Scikit-learn Pipeline

สำหรับงานจริงควรใช้ Pipeline แทนการแยก Preprocessing ออกจาก Model

ตัวอย่าง Logistic Regression พร้อม StandardScaler

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler


pipeline = Pipeline([
    (
        "scaler",
        StandardScaler()
    ),
    (
        "classifier",
        LogisticRegression()
    )
])


pipeline.fit(
    X_train,
    y_train
)

บันทึก Pipeline

joblib.dump(
    pipeline,
    "model/model.joblib"
)

ข้อดีคือ Preprocessing จะติดไปกับ Model

Input
  ↓
StandardScaler
  ↓
LogisticRegression
  ↓
Prediction

ช่วยลดปัญหา

Training-Serving Skew

ซึ่งเกิดจากขั้นตอน Preprocessing ตอน Train ไม่ตรงกับตอนใช้งานจริง


#14. เพิ่ม Validation ให้ Input

สามารถกำหนด Constraint เพิ่มเติมด้วย Pydantic

from pydantic import BaseModel, Field


class IrisInput(BaseModel):

    sepal_length: float = Field(
        gt=0
    )

    sepal_width: float = Field(
        gt=0
    )

    petal_length: float = Field(
        gt=0
    )

    petal_width: float = Field(
        gt=0
    )

หากส่งค่าติดลบ เช่น

{
  "sepal_length": -5,
  "sepal_width": 3.5,
  "petal_length": 1.4,
  "petal_width": 0.2
}

FastAPI จะ Reject Request ให้อัตโนมัติ


#15. เพิ่ม Health Check

Health Check มีประโยชน์มากในระบบ Production

@app.get("/health")
def health():

    return {
        "status": "ok"
    }

ระบบภายนอกสามารถตรวจสอบ

GET /health

เช่น

  • Docker
  • Kubernetes
  • Load Balancer
  • Monitoring System

#16. เพิ่ม Model Information Endpoint

สามารถสร้าง Endpoint สำหรับดูข้อมูล Model

@app.get("/model-info")
def model_info():

    return {
        "model": "RandomForestClassifier",
        "version": "1.0.0",
        "dataset": "Iris"
    }

ช่วยให้ตรวจสอบได้ว่า API กำลังใช้ Model Version ใด


#17. Batch Prediction

บางระบบต้องการส่งหลาย Record พร้อมกัน

สามารถสร้าง Schema

from typing import List


class BatchRequest(BaseModel):
    items: List[IrisInput]

ตัวอย่าง Endpoint

@app.post("/predict/batch")
def predict_batch(
    request: BatchRequest
):

    features = [
        [
            item.sepal_length,
            item.sepal_width,
            item.petal_length,
            item.petal_width
        ]
        for item in request.items
    ]

    predictions = model.predict(
        features
    )

    return {
        "predictions": [
            {
                "prediction": int(pred),
                "class_name": CLASS_NAMES[pred]
            }
            for pred in predictions
        ]
    }

Request ตัวอย่าง

{
  "items": [
    {
      "sepal_length": 5.1,
      "sepal_width": 3.5,
      "petal_length": 1.4,
      "petal_width": 0.2
    },
    {
      "sepal_length": 6.7,
      "sepal_width": 3.1,
      "petal_length": 4.7,
      "petal_width": 1.5
    }
  ]
}

#18. Dockerize ML API

สร้างไฟล์

Dockerfile

ตัวอย่าง

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install \
    --no-cache-dir \
    -r requirements.txt

COPY . .

EXPOSE 8000

CMD [
    "uvicorn",
    "app.main:app",
    "--host",
    "0.0.0.0",
    "--port",
    "8000"
]

Build Docker Image

docker build \
  -t sklearn-api .

รัน Container

docker run \
  -p 8000:8000 \
  sklearn-api

จากนั้นเปิด

http://localhost:8000/docs

#19. เพิ่ม .dockerignore

สร้าง

.dockerignore

เนื้อหา

.venv
__pycache__
*.pyc
.git
.gitignore
README.md

ช่วยลดขนาด Docker Build Context


#20. ตัวอย่าง requirements.txt

fastapi
uvicorn
scikit-learn
joblib
numpy

สำหรับ Production ควร Pin Version เช่น

fastapi==0.116.1
uvicorn==0.35.0
scikit-learn==1.7.1
joblib==1.5.1
numpy==2.3.2

อย่างไรก็ตาม Version ควรตรวจสอบและกำหนดตาม Environment ที่ใช้จริง


#21. การเชื่อมต่อจาก JavaScript

Frontend สามารถเรียก ML API ด้วย fetch

const response = await fetch(
  "http://localhost:8000/predict",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      sepal_length: 5.1,
      sepal_width: 3.5,
      petal_length: 1.4,
      petal_width: 0.2
    })
  }
);

const result = await response.json();

console.log(result);

ตัวอย่างผลลัพธ์

{
  prediction: 0,
  class_name: "setosa",
  probability: 1.0
}

#22. Architecture เมื่อใช้ร่วมกับ Web Application

ตัวอย่าง

React / Next.js
       ↓
    REST API
       ↓
    FastAPI
       ↓
Scikit-learn Pipeline
       ↓
  Prediction

ถ้ามี Database

Frontend
   ↓
FastAPI
   ├── PostgreSQL
   └── ML Model

#23. แยก Training Service และ Prediction Service

สำหรับ Production Architecture ควรแยกหน้าที่

Training Pipeline

Dataset
   ↓
Data Cleaning
   ↓
Feature Engineering
   ↓
Model Training
   ↓
Evaluation
   ↓
Model Registry

และ

Prediction Service

Client
   ↓
API Gateway
   ↓
FastAPI
   ↓
Model Artifact
   ↓
Prediction

ข้อดีคือ Training และ Serving Scale แยกกันได้


#24. Model Versioning

ควร Version Model เช่น

model/
├── iris-v1.joblib
├── iris-v2.joblib
└── iris-v3.joblib

หรือกำหนดผ่าน Environment Variable

MODEL_PATH=model/iris-v2.joblib

ใน Python

import os

MODEL_PATH = os.getenv(
    "MODEL_PATH",
    "model/iris-v1.joblib"
)

ช่วยให้เปลี่ยน Model ได้โดยไม่แก้ Source Code


#25. อย่า Load Model ทุก Request

ตัวอย่างที่ไม่ควรทำ

@app.post("/predict")
def predict(data: IrisInput):

    model = joblib.load(
        "model/model.joblib"
    )

    prediction = model.predict(...)

    return prediction

เพราะทุก Request ต้องอ่านไฟล์ Model ใหม่

แนวทางที่เหมาะสมคือ Load ครั้งเดียวตอน Application Startup

model = joblib.load(
    "model/model.joblib"
)

จากนั้น Reuse Model ทุก Request


#26. Security Considerations

ไฟล์ joblib หรือ pickle ไม่ควรถูกโหลดจากแหล่งที่ไม่น่าเชื่อถือ

เนื่องจาก Serialized Python Object อาจมีโค้ดอันตรายฝังอยู่ได้

แนวทางคือ

โหลดเฉพาะ Model Artifact
ที่สร้างโดย Pipeline ขององค์กร
และผ่านกระบวนการตรวจสอบแล้ว

ไม่ควรรับไฟล์ Model จาก User แล้วโหลดด้วย joblib.load() โดยตรง


#27. Production Considerations

เมื่อ Deploy จริง ควรพิจารณาเรื่องต่อไปนี้

#Input Validation

ตรวจสอบ

Data Type
Range
Missing Values
Unexpected Values

#Error Handling

ควรมี Standard Error Response

{
  "error": "prediction_failed",
  "message": "Unable to generate prediction"
}

#Logging

ควร Log ข้อมูล เช่น

timestamp
request_id
model_version
latency
prediction

แต่ควรระวังไม่ให้ Log ข้อมูลส่วนบุคคลหรือ Sensitive Data โดยไม่จำเป็น

#Monitoring

ติดตาม

Request Count
Latency
Error Rate
Prediction Distribution
Model Drift
Data Drift

#28. ML API กับแนวคิด MLOps

เมื่อระบบเริ่มใหญ่ขึ้น Workflow จะเปลี่ยนจาก

Train
  ↓
Save Model
  ↓
API

ไปเป็น

Data
  ↓
Training Pipeline
  ↓
Experiment Tracking
  ↓
Model Evaluation
  ↓
Model Registry
  ↓
Deployment
  ↓
Monitoring
  ↓
Retraining

เครื่องมือที่สามารถนำมาต่อยอดได้ เช่น

MLflow
DVC
Docker
GitHub Actions
Kubernetes
Prometheus
Grafana

#29. ตัวอย่าง CI/CD

สามารถใช้ GitHub Actions

Git Push
   ↓
Run Unit Test
   ↓
Train / Validate Model
   ↓
Build Docker Image
   ↓
Push Container Registry
   ↓
Deploy

สำหรับงานจริงควรหลีกเลี่ยงการ Train Model ในทุก Commit หาก Dataset หรือ Training Process ใช้เวลานาน และควรแยก Training Pipeline ออกจาก Application Deployment Pipeline ตามความเหมาะสม


#30. แนวทางทดสอบ ML API

ควรทดสอบอย่างน้อย 3 ระดับ

Unit Test
API Test
Model Validation Test

ตัวอย่างสิ่งที่ควรตรวจสอบ

API ตอบ HTTP 200 หรือไม่
Input Validation ทำงานหรือไม่
Prediction Format ถูกต้องหรือไม่
Model สามารถ Load ได้หรือไม่
Probability อยู่ระหว่าง 0-1 หรือไม่
Health Check ทำงานหรือไม่

#31. Workflow สำหรับ Workshop

สามารถใช้บทความนี้ทำ Workshop ตามลำดับ

Create Project
      ↓
Install Dependencies
      ↓
Load Dataset
      ↓
Train Model
      ↓
Evaluate Model
      ↓
Save Model
      ↓
Create FastAPI
      ↓
Create /predict
      ↓
Test Swagger
      ↓
Test cURL
      ↓
Docker Build
      ↓
Docker Run

#32. สรุป

การสร้าง ML API ด้วย Scikit-learn ไม่ได้ซับซ้อน หากแยกขั้นตอนออกเป็น

Training
Serving
Deployment

Scikit-learn ทำหน้าที่ด้าน

Data Processing
Model Training
Prediction

FastAPI ทำหน้าที่

REST API
Input Validation
API Documentation
HTTP Communication

Docker ทำหน้าที่

Packaging
Environment Isolation
Deployment

ดังนั้น Architecture ที่เหมาะสมจะเป็น

Client Application
       ↓
     FastAPI
       ↓
Input Validation
       ↓
Scikit-learn Pipeline
       ↓
   ML Prediction
       ↓
   JSON Response

เมื่อพัฒนาต่อไปสามารถเพิ่ม

Model Versioning
Experiment Tracking
CI/CD
Monitoring
Model Registry
Data Drift Detection
Automatic Retraining

ซึ่งเป็นพื้นฐานสำคัญของการพัฒนา Machine Learning System และ MLOps ในระดับ Production