Computer Vision ด้วย PyTorch ในปี 2026: CNN, Transfer Learning และคำถามสัมภาษณ์

Computer vision ด้วย PyTorch: สร้าง CNN ตั้งแต่เริ่มต้น, ประยุกต์ใช้ transfer learning กับโมเดล pretrained และเตรียมตัวสำหรับการสัมภาษณ์เทคนิค

Computer Vision ด้วย PyTorch ในปี 2026: CNN, Transfer Learning และคำถามสัมภาษณ์

Computer vision ด้วย PyTorch ได้กลายเป็นแนวทางหลักสำหรับการจำแนกภาพ การตรวจจับวัตถุ และงานจดจำภาพในปี 2026 บทความนี้ครอบคลุมการสร้าง CNN ตั้งแต่เริ่มต้น การประยุกต์ใช้ transfer learning กับโมเดล pretrained และการเตรียมตัวสำหรับคำถามสัมภาษณ์ด้านเทคนิค

ประสิทธิภาพ PyTorch 2.4

PyTorch 2.4 แนะนำ torch.compile() เป็นค่าเริ่มต้นสำหรับการดำเนินการ CNN ให้ความเร็วเพิ่มขึ้น 30-50% บน GPU สมัยใหม่โดยไม่ต้องเปลี่ยนแปลงโค้ด ตัวอย่างทั้งหมดในบทความนี้เข้ากันได้กับ PyTorch 2.4+

ทำความเข้าใจ Convolutional Neural Networks สำหรับการจำแนกภาพ

Convolutional Neural Networks สกัดคุณลักษณะแบบลำดับชั้นจากภาพผ่านตัวกรองที่เรียนรู้ได้ ต่างจากเครือข่าย fully connected ตรงที่ CNN รักษาความสัมพันธ์เชิงพื้นที่ระหว่างพิกเซล ทำให้เหมาะสำหรับงานด้านการมองเห็น สถาปัตยกรรมประกอบด้วยเลเยอร์ convolution ที่ตรวจจับขอบและรูปแบบ เลเยอร์ pooling ที่ลดมิติ และเลเยอร์ fully connected ที่ทำการจำแนก

การดำเนินการ convolution เลื่อนตัวกรองขนาดเล็ก (โดยทั่วไป 3x3 หรือ 5x5) ข้ามภาพอินพุต คำนวณ dot product ที่แต่ละตำแหน่ง กระบวนการนี้สร้าง feature map ที่เน้นรูปแบบเฉพาะ เช่น ขอบ พื้นผิว หรือรูปทรง เลเยอร์ที่ลึกกว่าจะรวมคุณลักษณะระดับต่ำเหล่านี้เข้าเป็นการแสดงที่ซับซ้อน—ใบหน้า วัตถุ หรือฉาก

python
# cnn_architecture.py
import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    """Basic CNN for CIFAR-10 classification (32x32 RGB images, 10 classes)"""
    
    def __init__(self, num_classes: int = 10):
        super().__init__()
        # First conv block: 3 input channels (RGB) -> 32 feature maps
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),  # Output: 32x32x32
            nn.BatchNorm2d(32),  # Normalize activations for stable training
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 16x16x32
        )
        # Second conv block: increase depth for richer features
        self.conv2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),  # Output: 16x16x64
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 8x8x64
        )
        # Third conv block: capture high-level patterns
        self.conv3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),  # Output: 8x8x128
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 4x4x128
        )
        # Classifier head
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),  # Prevent overfitting
            nn.Linear(256, num_classes)
        )
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        return self.classifier(x)

สถาปัตยกรรมนี้เพิ่มจำนวนตัวกรองอย่างต่อเนื่อง (32 → 64 → 128) ในขณะที่ลดมิติเชิงพื้นที่ผ่าน pooling BatchNorm ทำให้การฝึกมีเสถียรภาพด้วยการ normalize เอาต์พุตของเลเยอร์ ในขณะที่ Dropout ป้องกันไม่ให้ classifier จดจำตัวอย่างการฝึก

การฝึก CNN ด้วย PyTorch DataLoaders

การโหลดข้อมูลอย่างมีประสิทธิภาพเป็นสิ่งสำคัญสำหรับการใช้งาน GPU ในระหว่างการฝึก DataLoader ของ PyTorch จัดการ batching, shuffling และการโหลดข้อมูลแบบขนานโดยอัตโนมัติ การเพิ่มข้อมูลที่เหมาะสม—random crop, flip และ color jitter—ปรับปรุงการ generalize บนภาพที่ไม่เคยเห็นอย่างมีนัยสำคัญ

python
# train_cnn.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# Data augmentation for training (reduces overfitting)
train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomCrop(32, padding=4),  # Random crop with padding
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),  # CIFAR-10 mean
                         (0.2470, 0.2435, 0.2616))  # CIFAR-10 std
])

# No augmentation for validation (deterministic evaluation)
val_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),
                         (0.2470, 0.2435, 0.2616))
])

def train_model(model: nn.Module, epochs: int = 20) -> dict:
    """Train CNN with standard best practices"""
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = model.to(device)
    
    # Load CIFAR-10 dataset
    train_data = datasets.CIFAR10(root="./data", train=True, 
                                   download=True, transform=train_transform)
    val_data = datasets.CIFAR10(root="./data", train=False,
                                 transform=val_transform)
    
    # DataLoaders with num_workers for parallel loading
    train_loader = DataLoader(train_data, batch_size=128, shuffle=True,
                              num_workers=4, pin_memory=True)
    val_loader = DataLoader(val_data, batch_size=256, shuffle=False,
                            num_workers=4, pin_memory=True)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    
    best_acc = 0.0
    for epoch in range(epochs):
        model.train()
        for images, labels in train_loader:
            images, labels = images.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        
        scheduler.step()
        
        # Validation
        model.eval()
        correct, total = 0, 0
        with torch.no_grad():
            for images, labels in val_loader:
                images, labels = images.to(device), labels.to(device)
                outputs = model(images)
                _, predicted = outputs.max(1)
                total += labels.size(0)
                correct += predicted.eq(labels).sum().item()
        
        acc = 100. * correct / total
        best_acc = max(best_acc, acc)
        print(f"Epoch {epoch+1}/{epochs} - Val Acc: {acc:.2f}%")
    
    return {"best_accuracy": best_acc}

การเพิ่มประสิทธิภาพการฝึกที่สำคัญ ได้แก่: pin_memory=True สำหรับการถ่ายโอน CPU-ไป-GPU ที่เร็วขึ้น, num_workers สำหรับการโหลดข้อมูลแบบขนาน, optimizer AdamW พร้อม weight decay สำหรับ regularization และตารางเวลา learning rate cosine annealing สำหรับการลู่เข้าที่ราบรื่น

Transfer Learning ด้วย Pretrained ResNet และ EfficientNet

Transfer learning ใช้ประโยชน์จากโมเดลที่ฝึกมาแล้วบน ImageNet (1.2 ล้านภาพ, 1000 คลาส) และปรับให้เข้ากับชุดข้อมูลที่กำหนดเอง แนวทางนี้ให้ความแม่นยำสูงกว่าด้วยข้อมูลการฝึกและเวลาการคำนวณที่น้อยกว่า torchvision.models ของ PyTorch มีสถาปัตยกรรมที่ทันสมัยที่สุดพร้อม weights pretrained

แนวทางมาตรฐานคือการแช่แข็งเลเยอร์ convolution แรกๆ (ที่ตรวจจับคุณลักษณะสากล เช่น ขอบ) และ fine-tune เลเยอร์หลังๆ รวมถึงหัว classification ใหม่สำหรับงานเป้าหมาย

python
# transfer_learning.py
import torch
import torch.nn as nn
from torchvision import models
from torchvision.models import ResNet50_Weights, EfficientNet_B0_Weights

def create_transfer_model(
    model_name: str = "resnet50",
    num_classes: int = 10,
    freeze_backbone: bool = True
) -> nn.Module:
    """Create a pretrained model with custom classification head"""
    
    if model_name == "resnet50":
        # Load ResNet50 with ImageNet weights
        model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
        # Replace final fully connected layer
        in_features = model.fc.in_features  # 2048 for ResNet50
        model.fc = nn.Sequential(
            nn.Dropout(0.3),
            nn.Linear(in_features, num_classes)
        )
        
    elif model_name == "efficientnet_b0":
        # EfficientNet: better accuracy/compute tradeoff
        model = models.efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)
        in_features = model.classifier[1].in_features  # 1280 for B0
        model.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(in_features, num_classes)
        )
    
    if freeze_backbone:
        # Freeze all layers except classifier
        for name, param in model.named_parameters():
            if "fc" not in name and "classifier" not in name:
                param.requires_grad = False
    
    return model

def count_trainable_params(model: nn.Module) -> int:
    """Count parameters that will be updated during training"""
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

# Example usage
model = create_transfer_model("resnet50", num_classes=5, freeze_backbone=True)
print(f"Trainable parameters: {count_trainable_params(model):,}")
# Output: ~10,245 (only classifier) vs ~25 million (full ResNet50)

การแช่แข็ง backbone ลดพารามิเตอร์ที่ฝึกได้จาก 25 ล้านเหลือประมาณ 10,000 ทำให้สามารถฝึกบนชุดข้อมูลขนาดเล็กโดยไม่เกิด overfitting สำหรับชุดข้อมูลขนาดใหญ่ (10,000+ ภาพ) ค่อยๆ ปลดล็อคเลเยอร์หลังๆ โดยใช้ differential learning rates—อัตราต่ำกว่าสำหรับเลเยอร์ pretrained, อัตราสูงกว่าสำหรับ classifier ใหม่

พร้อมที่จะพิชิตการสัมภาษณ์ Data Science & ML แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

กลยุทธ์การเพิ่มข้อมูลสำหรับ Computer Vision

การเพิ่มข้อมูลขยายชุดการฝึกโดยเทียมด้วยการใช้การแปลงที่รักษาเนื้อหาเชิงความหมาย การเพิ่มข้อมูลสมัยใหม่ไปไกลกว่าการพลิกและหมุนพื้นฐาน—เทคนิคอย่าง MixUp และ CutOut สร้างตัวอย่างการฝึกสังเคราะห์ที่ปรับปรุงความทนทานของโมเดล

python
# augmentation_strategies.py
import torch
import torchvision.transforms.v2 as T
from torchvision.transforms.v2 import functional as F

# Modern augmentation pipeline using torchvision v2 transforms
advanced_augmentation = T.Compose([
    T.RandomResizedCrop(224, scale=(0.8, 1.0)),  # Random crop and resize
    T.RandomHorizontalFlip(p=0.5),
    T.RandomVerticalFlip(p=0.1),  # Less common but useful for some domains
    T.RandomRotation(degrees=15),
    T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.1),
    T.RandomAffine(degrees=0, translate=(0.1, 0.1)),  # Small translations
    T.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
    T.ToImage(),
    T.ToDtype(torch.float32, scale=True),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

class CutOut:
    """Randomly mask out square regions of the image"""
    def __init__(self, n_holes: int = 1, length: int = 16):
        self.n_holes = n_holes
        self.length = length
    
    def __call__(self, img: torch.Tensor) -> torch.Tensor:
        h, w = img.shape[1], img.shape[2]
        mask = torch.ones_like(img)
        
        for _ in range(self.n_holes):
            y = torch.randint(0, h, (1,)).item()
            x = torch.randint(0, w, (1,)).item()
            y1 = max(0, y - self.length // 2)
            y2 = min(h, y + self.length // 2)
            x1 = max(0, x - self.length // 2)
            x2 = min(w, x + self.length // 2)
            mask[:, y1:y2, x1:x2] = 0
        
        return img * mask

def mixup_data(
    x: torch.Tensor,
    y: torch.Tensor,
    alpha: float = 0.4
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, float]:
    """MixUp: blend two images and their labels"""
    lam = torch.distributions.Beta(alpha, alpha).sample().item()
    batch_size = x.size(0)
    index = torch.randperm(batch_size)
    
    mixed_x = lam * x + (1 - lam) * x[index]
    y_a, y_b = y, y[index]
    
    return mixed_x, y_a, y_b, lam

CutOut บังคับให้โมเดลพึ่งพาเบาะแสภาพหลายอย่างแทนที่จะเป็นพื้นที่แยกแยะเดียว MixUp สร้างการรวมแบบ convex ของคู่การฝึก ส่งผลให้ขอบเขตการตัดสินใจราบรื่นขึ้น เทคนิคเหล่านี้ปรับปรุงความแม่นยำอย่างสม่ำเสมอ 1-3% บน benchmark มาตรฐาน

คำถามสัมภาษณ์เกี่ยวกับ CNN และ Computer Vision

การสัมภาษณ์เทคนิคสำหรับตำแหน่ง computer vision ทดสอบทั้งความเข้าใจทางทฤษฎีและทักษะการนำไปใช้จริง เตรียมตัวสำหรับคำถามเกี่ยวกับการออกแบบสถาปัตยกรรม ความท้าทายในการเพิ่มประสิทธิภาพ และข้อพิจารณาการ deploy ในโลกจริง

หัวข้อสัมภาษณ์ทั่วไป

ผู้สัมภาษณ์มักถามเกี่ยวกับการคำนวณ receptive field, วัตถุประสงค์ของ batch normalization, เหตุใด convolution จึงทำงานได้ดีกับภาพ และวิธีวินิจฉัย overfitting ในโมเดล vision

ถาม: Receptive field คืออะไรและทำไมถึงสำคัญ?

Receptive field คือพื้นที่ของภาพอินพุตที่มีอิทธิพลต่อตำแหน่ง feature map เฉพาะ เลเยอร์ที่ลึกกว่ามี receptive field ที่ใหญ่กว่า ทำให้สามารถจับบริบทที่กว้างขึ้น สำหรับเครือข่ายที่มี n เลเยอร์ convolution โดยใช้ kernel 3x3 receptive field จะเติบโตเป็น (2n + 1) x (2n + 1) การออกแบบสถาปัตยกรรมต้องสมดุลระหว่างขนาด receptive field และต้นทุนการคำนวณ—dilated convolution ขยาย receptive field โดยไม่เพิ่มพารามิเตอร์

ถาม: ทำไมต้องใช้ batch normalization ใน CNN?

Batch normalization ทำการ normalize อินพุตของเลเยอร์ให้มี mean เป็นศูนย์และ variance เป็นหนึ่ง แก้ปัญหา internal covariate shift ประโยชน์ ได้แก่: การลู่เข้าเร็วขึ้น (learning rate ที่สูงขึ้นมีเสถียรภาพ), ผลของ regularization (ลดความต้องการ dropout) และการปรับปรุงการไหลของ gradient (ป้องกัน vanishing gradient ในเครือข่ายลึก) ในระหว่างการ inference, running statistics แทนที่ batch statistics สำหรับเอาต์พุตที่แน่นอน

ถาม: อธิบายความแตกต่างระหว่าง same และ valid padding

Same padding เพิ่มศูนย์รอบอินพุตเพื่อรักษามิติเชิงพื้นที่หลัง convolution—อินพุต 32x32 กับ kernel 3x3 สร้างเอาต์พุต 32x32 Valid padding ไม่ใช้ padding ลดมิติ—การดำเนินการเดียวกันสร้างเอาต์พุต 30x30 Same padding เป็นที่นิยมในเครือข่ายลึกเพื่อหลีกเลี่ยงการลดมิติเชิงพื้นที่อย่างรวดเร็ว

ถาม: จัดการกับความไม่สมดุลของคลาสในการจำแนกภาพอย่างไร?

กลยุทธ์ ได้แก่: weighted cross-entropy loss (น้ำหนักสูงกว่าสำหรับคลาสส่วนน้อย), oversampling คลาสส่วนน้อยในระหว่างการฝึก, การเพิ่มข้อมูลที่เน้นคลาสที่มีตัวแทนน้อย และ focal loss ที่ลดน้ำหนักตัวอย่างที่ง่าย สำหรับความไม่สมดุลรุนแรง (>100:1) รวมหลายเทคนิคและพิจารณา metrics นอกเหนือจากความแม่นยำ—F1 score, precision-recall AUC หรือการวิเคราะห์ confusion matrix

สำหรับการเตรียมสัมภาษณ์ deep learning เพิ่มเติม สำรวจโมดูลคำถาม CNN & Image Classification และ Deep Learning Fundamentals

Deploy โมเดล Vision PyTorch ด้วย TorchScript

การ deploy ใน production ต้องการการแปลงโมเดล PyTorch เป็นรูปแบบที่เพิ่มประสิทธิภาพ TorchScript คอมไพล์โมเดลเป็นการแสดงแบบพกพาที่ทำงานโดยไม่ต้องใช้ Python ทำให้สามารถ deploy ในแอปพลิเคชัน C++, อุปกรณ์มือถือ หรือสภาพแวดล้อม serverless

python
# deployment_export.py
import torch
from torchvision import models
from torchvision.models import ResNet18_Weights

def export_for_production(model: torch.nn.Module, save_path: str) -> None:
    """Export model to TorchScript for production deployment"""
    model.eval()  # Set to evaluation mode (disables dropout, uses running stats)
    
    # Create example input matching expected dimensions
    example_input = torch.randn(1, 3, 224, 224)
    
    # Method 1: Tracing (for models without control flow)
    traced_model = torch.jit.trace(model, example_input)
    traced_model.save(save_path.replace(".pt", "_traced.pt"))
    
    # Method 2: Scripting (handles if/else, loops)
    scripted_model = torch.jit.script(model)
    scripted_model.save(save_path.replace(".pt", "_scripted.pt"))
    
    # Verify outputs match
    with torch.no_grad():
        original_out = model(example_input)
        traced_out = traced_model(example_input)
        scripted_out = scripted_model(example_input)
    
    assert torch.allclose(original_out, traced_out, atol=1e-5)
    assert torch.allclose(original_out, scripted_out, atol=1e-5)
    print(f"Model exported successfully to {save_path}")

def optimize_for_inference(model_path: str) -> torch.jit.ScriptModule:
    """Load and optimize TorchScript model for inference"""
    model = torch.jit.load(model_path)
    
    # Optimize for inference (fuses operations, removes dropout)
    optimized = torch.jit.optimize_for_inference(model)
    
    return optimized

# Export ResNet18
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
export_for_production(model, "resnet18_production.pt")

สำหรับความเร็ว inference สูงสุด พิจารณาการ export ONNX สำหรับความเข้ากันได้ข้าม framework หรือ TensorRT สำหรับการ deploy บน GPU NVIDIA การเปรียบเทียบ PyTorch vs TensorFlow ครอบคลุมข้อแลกเปลี่ยนการ deploy ระหว่าง framework

สรุป

Computer vision ด้วย PyTorch รวมการ abstract ที่ทรงพลังกับเครื่องมือที่พร้อมใช้งานใน production:

  • สถาปัตยกรรม CNN สกัดคุณลักษณะแบบลำดับชั้นผ่านเลเยอร์ convolution, pooling และ normalization
  • Transfer learning กับโมเดล pretrained (ResNet, EfficientNet) ให้ความแม่นยำสูงบนข้อมูลจำกัด
  • การเพิ่มข้อมูล (MixUp, CutOut, การแปลงทางเรขาคณิต) ปรับปรุง generalization และป้องกัน overfitting
  • การสัมภาษณ์เทคนิคทดสอบ receptive field, batch normalization, กลยุทธ์ padding และการจัดการความไม่สมดุลของคลาส
  • TorchScript ทำให้สามารถ deploy ใน production โดยไม่ต้องพึ่งพา Python

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

นักพัฒนาฟูลสแตก ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 14 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง

LangChain สำหรับ Data Scientist ปี 2026: LLM, Agent และคำถามสัมภาษณ์

LangChain สำหรับ Data Scientist ปี 2026: LLM, Agent และคำถามสัมภาษณ์

เชี่ยวชาญ LangChain 0.3 สำหรับ data science: LCEL chain, รูปแบบ RAG, ReAct agent, ระบบ memory และคำถามสัมภาษณ์สำหรับตำแหน่ง ML engineering

ภาพประกอบคำถามสัมภาษณ์ MLOps แสดง MLflow model registry, pipeline การนำไปใช้งาน และแดชบอร์ดเฝ้าติดตาม drift บนพื้นหลังสีเข้ม

MLOps ในปี 2026: MLflow, Model Registry และคำถามสัมภาษณ์เชิงเทคนิค

คำถามสัมภาษณ์ MLOps ครอบคลุมวงจรชีวิต ML การติดตามการทดลองด้วย MLflow การเลื่อนระดับใน model registry รูปแบบการนำไปใช้งาน การเฝ้าติดตาม drift และ system design สำหรับปี 2026 พร้อมโค้ด Python และคำตอบ

สถาปัตยกรรมไปป์ไลน์ RAG retrieval-augmented generation พร้อมฐานข้อมูลเวกเตอร์และ LLM

RAG และ LLM ในปี 2026: Retrieval-Augmented Generation สำหรับสัมภาษณ์ Data Science

อธิบาย Retrieval-Augmented Generation (RAG) สำหรับการสัมภาษณ์ data science ในปี 2026 ครอบคลุมฐานข้อมูลเวกเตอร์ กลยุทธ์ chunking โมเดล embedding agentic RAG Graph RAG และสถาปัตยกรรมไปป์ไลน์ที่พร้อมใช้งานจริง