- สร้าง Deep Learning API ด้วย PyTorch และ FastAPI
- 1. สร้าง Project
- 2. สร้างโมเดล CNN ด้วย PyTorch
- 3. Train โมเดล
- 4. สร้าง Deep Learning API
- 5. รัน API
- 6. ทดสอบ Swagger UI
- 7. ทดสอบด้วย cURL
- 8. ทดสอบด้วย Python
- 9. ทำไมต้องใช้ Softmax
- 10. การเลือก CPU หรือ GPU
- 11. เพิ่ม Endpoint สำหรับ Model Information
- 12. เพิ่ม Top-K Prediction
- 13. สร้าง Docker Image
- 14. Docker Compose
- 15. Production Architecture
- 16. แนวทางสำหรับ Production
- 17. เพิ่ม Model Version
- 18. API Versioning
- 19. เพิ่ม Health Check
- 20. Security ที่ควรเพิ่ม
- 21. Logging Prediction
- 22. วัด Inference Latency
- 23. Batch Prediction
- 24. ตัวอย่าง Flow ของระบบจริง
- 25. PyTorch API กับ ML API ต่างกันอย่างไร
- 26. สามารถนำแนวทางนี้ไปใช้กับอะไรได้บ้าง
- 27. Production Stack ที่แนะนำ
- 28. สิ่งที่ควรเพิ่มเมื่อพัฒนาเป็นระบบจริง
- สรุป
- เอกสารอ้างอิง
#สร้าง 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 อื่นได้
#เอกสารอ้างอิง
-
PyTorch — Saving and Loading Models
https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html -
PyTorch — Quickstart
https://docs.pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html -
Torchvision — Transforms
https://docs.pytorch.org/vision/stable/transforms.html -
FastAPI — Request Files
https://fastapi.tiangolo.com/tutorial/request-files/ -
FastAPI — Response Model
https://fastapi.tiangolo.com/tutorial/response-model/ -
FastAPI Documentation
https://fastapi.tiangolo.com/