#สร้าง Deep Learning API ด้วย PyTorch และ FastAPI

การพัฒนาโมเดล Deep Learning ไม่ได้จบเพียงแค่การ Train โมเดลให้ได้ Accuracy ที่ดีเท่านั้น หากต้องการนำโมเดลไปใช้งานจริง เราต้องมีวิธีให้ Web Application, Mobile Application หรือระบบอื่นสามารถเรียกใช้งานโมเดลได้

วิธีที่นิยมคือการนำโมเดลมาสร้างเป็น REST API โดยในบทความนี้จะใช้

  • PyTorch สำหรับสร้างและ Train โมเดล Deep Learning
  • Torchvision สำหรับจัดการ Dataset และ Image Transform
  • FastAPI สำหรับสร้าง REST API
  • Pillow สำหรับอ่านไฟล์ภาพ
  • Uvicorn สำหรับรัน ASGI Server
  • Docker สำหรับ Containerize API

ตัวอย่างจะใช้โมเดล CNN สำหรับจำแนกตัวเลขลายมือจากชุดข้อมูล MNIST


#ภาพรวม Architecture

                    ┌─────────────────────┐
                    │   Client / Web App  │
                    └──────────┬──────────┘
                               │
                               │ POST /predict
                               │ multipart/form-data
                               ▼
                    ┌─────────────────────┐
                    │       FastAPI       │
                    │      REST API       │
                    └──────────┬──────────┘
                               │
                               │ Image preprocessing
                               ▼
                    ┌─────────────────────┐
                    │       PyTorch       │
                    │      CNN Model      │
                    └──────────┬──────────┘
                               │
                               │ logits / probability
                               ▼
                    ┌─────────────────────┐
                    │    JSON Response    │
                    │ digit / confidence  │
                    └─────────────────────┘

Pipeline หลักคือ

Image
  ↓
Preprocessing
  ↓
Tensor
  ↓
PyTorch Model
  ↓
Logits
  ↓
Softmax
  ↓
Predicted Class
  ↓
REST API Response

#1. สร้าง Project

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

pytorch-dl-api/
├── app.py
├── model.py
├── train.py
├── model.pth
├── Dockerfile
└── pyproject.toml

สร้างโครงการด้วย uv

mkdir pytorch-dl-api
cd pytorch-dl-api

uv init

ติดตั้ง Package

uv add torch torchvision fastapi uvicorn pillow python-multipart

หรือหากใช้ pip

pip install torch torchvision fastapi uvicorn pillow python-multipart

python-multipart จำเป็นสำหรับ FastAPI เมื่อรับไฟล์แบบ multipart/form-data


#2. สร้างโมเดล CNN ด้วย PyTorch

สร้างไฟล์

model.py
import torch
from torch import nn


class CNN(nn.Module):
    def __init__(self):
        super().__init__()

        self.features = nn.Sequential(
            nn.Conv2d(1, 32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2),

            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )

        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(64 * 7 * 7, 128),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(128, 10),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

Input ของ MNIST มีขนาด

1 × 28 × 28

หมายถึง

Channel = 1
Width   = 28
Height  = 28

โมเดลประกอบด้วย

Conv2D
  ↓
ReLU
  ↓
MaxPool
  ↓
Conv2D
  ↓
ReLU
  ↓
MaxPool
  ↓
Flatten
  ↓
Linear
  ↓
Output 10 Classes

Output มีทั้งหมด 10 class คือเลข

0 1 2 3 4 5 6 7 8 9

#3. Train โมเดล

สร้างไฟล์

train.py
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

from model import CNN


device = torch.device(
    "cuda"
    if torch.cuda.is_available()
    else "cpu"
)

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST(
    root="data",
    train=True,
    download=True,
    transform=transform,
)

test_dataset = datasets.MNIST(
    root="data",
    train=False,
    download=True,
    transform=transform,
)

train_loader = DataLoader(
    train_dataset,
    batch_size=64,
    shuffle=True,
)

test_loader = DataLoader(
    test_dataset,
    batch_size=1000,
    shuffle=False,
)

model = CNN().to(device)

loss_fn = nn.CrossEntropyLoss()

optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.001,
)

epochs = 5

for epoch in range(epochs):

    model.train()

    total_loss = 0

    for images, labels in train_loader:

        images = images.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()

        outputs = model(images)

        loss = loss_fn(outputs, labels)

        loss.backward()

        optimizer.step()

        total_loss += loss.item()

    print(
        f"Epoch {epoch + 1}/{epochs} "
        f"Loss: {total_loss / len(train_loader):.4f}"
    )


model.eval()

correct = 0
total = 0

with torch.no_grad():

    for images, labels in test_loader:

        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)

        predictions = outputs.argmax(dim=1)

        total += labels.size(0)

        correct += (
            predictions == labels
        ).sum().item()


accuracy = correct / total

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


torch.save(
    model.state_dict(),
    "model.pth"
)

print("Model saved to model.pth")

รัน

uv run python train.py

หรือ

python train.py

เมื่อ Train เสร็จจะได้

model.pth

แนวทางที่เหมาะสำหรับ inference คือบันทึก parameter ของโมเดลผ่าน

torch.save(
    model.state_dict(),
    "model.pth"
)

แทนการ serialize object ของโมเดลทั้งก้อน เพราะสามารถควบคุมโครงสร้างโมเดลและการโหลดได้ชัดเจนกว่า


#4. สร้าง Deep Learning API

สร้างไฟล์

app.py
from io import BytesIO

import torch
from fastapi import FastAPI, File, HTTPException, UploadFile
from PIL import Image
from pydantic import BaseModel
from torchvision import transforms

from model import CNN


app = FastAPI(
    title="PyTorch MNIST API",
    version="1.0.0",
    description="Deep Learning API with PyTorch and FastAPI",
)


device = torch.device(
    "cuda"
    if torch.cuda.is_available()
    else "cpu"
)


model = CNN()

state_dict = torch.load(
    "model.pth",
    map_location=device,
    weights_only=True,
)

model.load_state_dict(state_dict)

model.to(device)

model.eval()


transform = transforms.Compose([
    transforms.Grayscale(num_output_channels=1),
    transforms.Resize((28, 28)),
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])


class PredictionResponse(BaseModel):
    predicted_digit: int
    confidence: float


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


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


@app.post(
    "/predict",
    response_model=PredictionResponse,
)
async def predict(
    file: UploadFile = File(...)
):
    if not file.content_type:
        raise HTTPException(
            status_code=400,
            detail="Missing content type",
        )

    if not file.content_type.startswith("image/"):
        raise HTTPException(
            status_code=400,
            detail="File must be an image",
        )

    try:

        image_bytes = await file.read()

        image = Image.open(
            BytesIO(image_bytes)
        ).convert("L")

    except Exception as exc:

        raise HTTPException(
            status_code=400,
            detail="Invalid image file",
        ) from exc


    tensor = transform(image)

    tensor = tensor.unsqueeze(0)

    tensor = tensor.to(device)


    with torch.inference_mode():

        logits = model(tensor)

        probabilities = torch.softmax(
            logits,
            dim=1,
        )

        confidence, predicted = (
            probabilities.max(dim=1)
        )


    return PredictionResponse(
        predicted_digit=int(
            predicted.item()
        ),
        confidence=float(
            confidence.item()
        ),
    )

จุดสำคัญคือ

model.eval()

เพื่อเปลี่ยนโมเดลเข้าสู่ evaluation mode โดย layer เช่น Dropout และ Batch Normalization จะทำงานในรูปแบบ inference

และ

with torch.inference_mode():

ช่วยปิดการทำงานที่เกี่ยวข้องกับ gradient ระหว่าง inference เพราะ API ไม่ได้ทำ training


#5. รัน API

ใช้ Uvicorn

uv run uvicorn app:app --reload

หรือ

uvicorn app:app --reload

API จะทำงานที่

http://127.0.0.1:8000

#6. ทดสอบ Swagger UI

เปิด Browser

http://127.0.0.1:8000/docs

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

เลือก

POST /predict

จากนั้น Upload รูปภาพตัวเลข แล้วกด

Execute

ตัวอย่าง Response

{
  "predicted_digit": 7,
  "confidence": 0.9982
}

#7. ทดสอบด้วย cURL

สมมติว่ามีไฟล์

digit.png

รัน

curl -X POST \
  "http://127.0.0.1:8000/predict" \
  -H "accept: application/json" \
  -H "Content-Type: multipart/form-data" \
  -F "file=@digit.png"

Response

{
  "predicted_digit": 7,
  "confidence": 0.9982
}

#8. ทดสอบด้วย Python

import requests


url = "http://127.0.0.1:8000/predict"


with open("digit.png", "rb") as image:

    response = requests.post(
        url,
        files={
            "file": image
        },
    )


print(response.json())

#9. ทำไมต้องใช้ Softmax

Output จากโมเดลก่อน Softmax เป็นค่าที่เรียกว่า

logits

ตัวอย่าง

[
    -1.23,
     0.12,
     4.85,
     0.54,
    -0.23,
     0.14,
     1.02,
     8.92,
    -1.56,
     0.22
]

เราสามารถแปลงเป็น probability ด้วย

probabilities = torch.softmax(
    logits,
    dim=1
)

จากนั้นเลือก class ที่มี probability สูงสุด

confidence, predicted = (
    probabilities.max(dim=1)
)

#10. การเลือก CPU หรือ GPU

สามารถเลือก Device อัตโนมัติ

device = torch.device(
    "cuda"
    if torch.cuda.is_available()
    else "cpu"
)

จากนั้นย้ายทั้ง Model และ Tensor ไปยัง Device เดียวกัน

model.to(device)

tensor = tensor.to(device)

หาก Train ด้วย GPU แต่ต้องการ Deploy บน CPU สามารถใช้

state_dict = torch.load(
    "model.pth",
    map_location="cpu",
    weights_only=True,
)

#11. เพิ่ม Endpoint สำหรับ Model Information

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

    return {
        "framework": "PyTorch",
        "model": "CNN",
        "dataset": "MNIST",
        "classes": 10,
        "device": str(device),
    }

Response

{
  "framework": "PyTorch",
  "model": "CNN",
  "dataset": "MNIST",
  "classes": 10,
  "device": "cpu"
}

#12. เพิ่ม Top-K Prediction

บางระบบอาจไม่ต้องการเพียง class ที่มีคะแนนสูงที่สุด แต่อาจต้องการ Top 3

values, indices = torch.topk(
    probabilities,
    k=3,
    dim=1,
)

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

{
  "predictions": [
    {
      "digit": 7,
      "confidence": 0.91
    },
    {
      "digit": 1,
      "confidence": 0.05
    },
    {
      "digit": 9,
      "confidence": 0.02
    }
  ]
}

แนวคิดนี้สามารถนำไปใช้กับ Image Classification ที่มีหลาย class เช่น

Cat
Dog
Bird
Car
Truck
Plane

#13. สร้าง Docker Image

สร้าง

Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY . .

RUN pip install --no-cache-dir \
    torch \
    torchvision \
    fastapi \
    uvicorn \
    pillow \
    python-multipart

EXPOSE 8000

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

Build

docker build -t pytorch-api .

Run

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

เปิด

http://localhost:8000/docs

#14. Docker Compose

สร้างไฟล์

compose.yaml
services:

  api:

    build: .

    container_name: pytorch-api

    ports:
      - "8000:8000"

    restart: unless-stopped

รัน

docker compose up -d --build

ตรวจสอบ

docker compose ps

ดู Log

docker compose logs -f

#15. Production Architecture

เมื่อนำไปใช้จริง อาจมี Architecture เช่น

Internet
   │
   ▼
Nginx / Caddy
   │
   ▼
FastAPI
   │
   ▼
Preprocessing
   │
   ▼
PyTorch Model
   │
   ├── CPU
   │
   └── GPU
   │
   ▼
Prediction
   │
   ▼
JSON Response

หากมีผู้ใช้จำนวนมากอาจเพิ่ม

Load Balancer
Redis
Message Queue
Monitoring
Tracing
GPU Worker
Model Registry

#16. แนวทางสำหรับ Production

#โหลดโมเดลเพียงครั้งเดียว

ไม่ควรเขียนแบบนี้ภายใน endpoint

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

    model = CNN()

    model.load_state_dict(...)

เพราะจะโหลดโมเดลใหม่ทุก Request

ควรโหลดเมื่อ Application Start

model = CNN()

model.load_state_dict(...)

model.eval()

แล้วใช้โมเดลเดิมในการรับ Request


#ตรวจสอบชนิดไฟล์

ควรตรวจสอบ

file.content_type

และควรจำกัด

  • MIME type
  • File size
  • Image dimension
  • รูปแบบไฟล์ที่อนุญาต

เช่น

image/png
image/jpeg

#จำกัดขนาดไฟล์

ไม่ควรให้ Client Upload ไฟล์ขนาดไม่จำกัด

ตัวอย่าง policy

Maximum image size = 5 MB

เพื่อป้องกัน

Memory exhaustion
Denial of Service
Oversized request

#แยก Training กับ Serving

ระบบ Production ควรแยกออกเป็น

Training Pipeline
        │
        ▼
   model.pth
        │
        ▼
Model Registry / Artifact Storage
        │
        ▼
Inference API

ไม่ควร Train โมเดลทุกครั้งที่ API เริ่มทำงาน


#17. เพิ่ม Model Version

Response ควรระบุ Version ของโมเดล

MODEL_VERSION = "1.0.0"

เช่น

{
  "predicted_digit": 7,
  "confidence": 0.9982,
  "model_version": "1.0.0"
}

ช่วยให้สามารถตรวจสอบได้ว่า Prediction มาจากโมเดลเวอร์ชันใด


#18. API Versioning

Production API อาจใช้

/api/v1/predict

แทน

/predict

ตัวอย่าง

@app.post("/api/v1/predict")
async def predict(...):
    ...

หากมีโมเดลใหม่

/api/v2/predict

ทำให้ Client เดิมยังสามารถใช้ API Version เก่าได้


#19. เพิ่ม Health Check

ควรมี Endpoint

GET /health

ตัวอย่าง

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

    return {
        "status": "ok",
        "model_loaded": True,
        "device": str(device),
    }

สามารถนำไปใช้กับ

Docker Healthcheck
Kubernetes Liveness Probe
Kubernetes Readiness Probe
Load Balancer
Monitoring

#20. Security ที่ควรเพิ่ม

เมื่อ API เปิดให้ผู้ใช้งานจริง ควรพิจารณา

Authentication
Authorization
API Key
JWT
Rate Limiting
HTTPS
Input Validation
Request Size Limit
CORS
Logging
Monitoring

ตัวอย่าง Architecture

Client
  │
  ▼
API Gateway
  │
  ├── Authentication
  ├── Rate Limit
  └── Logging
  │
  ▼
FastAPI
  │
  ▼
PyTorch Model

#21. Logging Prediction

ตัวอย่างข้อมูลที่ควรเก็บ

request_id
timestamp
model_version
prediction
confidence
latency
device

ตัวอย่าง Log

{
  "request_id": "a91d...",
  "model_version": "1.0.0",
  "prediction": 7,
  "confidence": 0.9982,
  "latency_ms": 24.3,
  "device": "cuda"
}

ข้อมูลเหล่านี้ช่วยในการทำ

Monitoring
Debugging
Model Performance Analysis
Model Drift Detection

#22. วัด Inference Latency

from time import perf_counter


start = perf_counter()

with torch.inference_mode():
    logits = model(tensor)

latency_ms = (
    perf_counter() - start
) * 1000

Response อาจเพิ่ม

{
  "predicted_digit": 7,
  "confidence": 0.9982,
  "latency_ms": 12.4
}

#23. Batch Prediction

หาก Client ต้องการส่งหลายภาพพร้อมกัน การทำ Batch สามารถลด overhead ของการเรียก API หลายครั้ง

Image 1
Image 2
Image 3
Image 4
   │
   ▼
Tensor Batch
   │
   ▼
PyTorch
   │
   ▼
Predictions

PyTorch Tensor จะมีรูปแบบ

Batch × Channel × Height × Width

เช่น

32 × 1 × 28 × 28

#24. ตัวอย่าง Flow ของระบบจริง

User uploads image
        │
        ▼
POST /api/v1/predict
        │
        ▼
Validate file
        │
        ▼
Read image
        │
        ▼
Resize / Normalize
        │
        ▼
Convert to Tensor
        │
        ▼
Move Tensor to Device
        │
        ▼
PyTorch Inference
        │
        ▼
Softmax
        │
        ▼
Top Prediction
        │
        ▼
JSON Response

#25. PyTorch API กับ ML API ต่างกันอย่างไร

หลักการของ API ไม่ต่างกันมาก

Machine Learning แบบดั้งเดิมอาจเป็น

Request
  ↓
Feature Vector
  ↓
scikit-learn
  ↓
Prediction

Deep Learning มักเป็น

Request
  ↓
Image / Text / Audio
  ↓
Preprocessing
  ↓
Tensor
  ↓
PyTorch Model
  ↓
Prediction

จุดสำคัญของ DL API คือการจัดการ

Tensor
Device
GPU
Batch
Model State
Preprocessing
Inference Memory
Latency

#26. สามารถนำแนวทางนี้ไปใช้กับอะไรได้บ้าง

โครงสร้างเดียวกันสามารถนำไปใช้กับโมเดลหลายประเภท

#Image Classification

Image
↓
CNN / ResNet / ViT
↓
Class

เช่น

Cat vs Dog
Plant Disease
Medical Image Classification
Defect Detection
Food Classification

#Object Detection

Image
↓
YOLO / Faster R-CNN
↓
Bounding Boxes

Response อาจเป็น

{
  "objects": [
    {
      "label": "car",
      "confidence": 0.97,
      "box": [120, 80, 330, 250]
    }
  ]
}

#NLP

Text
↓
Tokenizer
↓
Transformer
↓
Classification

เช่น

Sentiment Analysis
Text Classification
Spam Detection
Intent Classification

#Time Series

Historical Data
↓
LSTM / GRU / Transformer
↓
Forecast

เช่น

Stock Forecast
Energy Forecast
Demand Forecast
Exchange Rate Forecast

#27. Production Stack ที่แนะนำ

Client
   │
   ▼
Caddy / Nginx
   │
   ▼
FastAPI
   │
   ▼
PyTorch
   │
   ├── CPU
   └── NVIDIA GPU
   │
   ▼
Redis / Database

และเพิ่ม Observability

Prometheus
Grafana
OpenTelemetry

รวมถึง CI/CD

GitHub
   │
   ▼
GitHub Actions
   │
   ▼
Docker Registry
   │
   ▼
Server / Kubernetes

#28. สิ่งที่ควรเพิ่มเมื่อพัฒนาเป็นระบบจริง

รายการสำคัญ ได้แก่

  • Model versioning
  • Input schema validation
  • File size limit
  • Authentication
  • Rate limiting
  • HTTPS
  • Structured logging
  • Metrics
  • Distributed tracing
  • Automated testing
  • Model monitoring
  • Drift monitoring
  • GPU monitoring
  • CI/CD
  • Container security
  • Model registry

#สรุป

การนำ Deep Learning Model ไปใช้งานผ่าน API สามารถแบ่งออกเป็น 4 ส่วนหลัก

1. Train Model
2. Save Model
3. Load Model
4. Serve Prediction API

ใน PyTorch เราสามารถบันทึกโมเดลด้วย

torch.save(
    model.state_dict(),
    "model.pth"
)

จากนั้นโหลดกลับมา

model = CNN()

model.load_state_dict(
    torch.load(
        "model.pth",
        weights_only=True,
    )
)

model.eval()

และทำ inference ผ่าน FastAPI

POST /predict

Architecture สุดท้ายคือ

Client
  ↓
FastAPI
  ↓
Preprocessing
  ↓
PyTorch
  ↓
Prediction
  ↓
JSON

แนวทางนี้เป็นพื้นฐานสำคัญของการพัฒนา AI Application, Model Serving, MLOps และ Production Deep Learning System และสามารถต่อยอดจากโมเดล CNN ขนาดเล็กไปสู่ ResNet, Vision Transformer, LSTM, Transformer หรือโมเดล Deep Learning อื่นได้


#เอกสารอ้างอิง