การสกัดคุณลักษณะ: การตรึงแกนหลัก
ผู้เรียนจะตรึงทุกชั้นยกเว้นส่วนหัวการจำแนกขั้นสุดท้าย ฝึกเฉพาะชั้นใหม่บนชุดข้อมูลกำหนดเองขนาดเล็ก และยืนยันว่าเวลาในการฝึกลดลงอย่างมาก
การสกัดคุณลักษณะ: การตรึงแกนหลัก เป็นบทเรียน Machine Learning Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Machine Learning Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
Feature Extraction vs Fine-Tuning
Transfer learning has two main strategies. In feature extraction, the pre-trained backbone is completely frozen — its weights do not change during training. Only the new classification head, which you add on top, learns from your data. In fine-tuning, the entire network or at least some backbone layers are also updated.
Feature extraction is the right choice when your dataset is small (less than a few thousand images) or when your images are similar to ImageNet (natural photographs of everyday objects). It is much faster since gradients do not flow through the backbone, and it avoids destroying carefully learned features with noisy updates from too little data.
Freezing Parameters in PyTorch
In PyTorch, each parameter tensor has a requires_grad attribute. Setting it to False prevents gradient computation for that tensor, effectively freezing it. The simplest way to freeze all backbone parameters is to iterate over model.parameters() and set requires_grad = False, then replace the classification head (which starts with new random weights, so requires_grad=True by default).
This is efficient: PyTorch's autograd skips frozen parameters during the backward pass, reducing memory usage and speeding up training significantly compared to fine-tuning the whole network.
import torchvision.models as models
import torch.nn as nn
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
# Freeze ALL backbone parameters
for param in model.parameters():
param.requires_grad = False
# Replace the classification head (creates new trainable parameters)
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)
# model.fc.parameters() have requires_grad=True by default
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f'Trainable: {trainable:,} / Total: {total:,} ({trainable/total:.1%})')Why Frozen Features Work Well
ImageNet pre-trained models learn a hierarchy of features: early layers detect low-level edges and colours, middle layers detect textures and parts, and later layers detect abstract objects. These features are general visual features that transfer broadly across image domains.
When your task involves natural images — flowers, animals, medical scans, satellite photos — these pre-learned features are far more informative than anything a randomly initialised network could extract from a small dataset. Feature extraction leverages this by treating the frozen backbone as a fixed feature transformer and only training a small linear classifier on top of the extracted features.
import torchvision.models as models
import torch
# Pre-extract features for all images (faster than forward-passing every epoch)
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
model.fc = torch.nn.Identity() # Remove classification head
model.eval()
# Extract 2048-dim features for all training images once
all_features, all_labels = [], []
with torch.no_grad():
for images, labels in train_loader:
features = model(images) # Shape: (batch, 2048)
all_features.append(features)
all_labels.append(labels)
X_train = torch.cat(all_features) # (N, 2048)
y_train = torch.cat(all_labels) # (N,)Training Only the Classification Head
Once features are frozen, training becomes fast. The optimiser only updates the new head's weights. You can either train a simple nn.Linear layer in PyTorch, or even pass the pre-extracted feature vectors to scikit-learn's LogisticRegression or SVC — both approaches work well for linear classification on top of rich pre-trained features.
Using a single linear layer is equivalent to training a logistic regression on the extracted features. For more complex tasks or when your classes require non-linear decision boundaries, you can use a small multi-layer head with ReLU activations and dropout between the frozen backbone and the output.
import torch.nn as nn
import torch.optim as optim
# Option 1: Simple linear head (logistic regression on features)
classifier = nn.Linear(2048, num_classes)
optimizer = optim.Adam(classifier.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
# Option 2: Small MLP head for more complex tasks
mlp_head = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, num_classes)
)
# scikit-learn option (useful for small datasets)
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(max_iter=1000, C=1.0)
clf.fit(X_train.numpy(), y_train.numpy())DataLoader and Transform Setup
During feature extraction, image preprocessing must match the transforms used when training the original model. For ImageNet pre-trained models this means: resize to 256, centre-crop to 224×224, convert to tensor, and normalise with ImageNet mean and std.
When you use weights.transforms(), PyTorch automatically provides the correct preprocessing pipeline associated with those specific weights. This eliminates a common source of subtle bugs where you accidentally use wrong normalisation constants, which can reduce transfer learning accuracy by several percentage points.
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import torchvision.models as models
weights = models.ResNet50_Weights.IMAGENET1K_V1
preprocess = weights.transforms() # Includes correct resize, crop, normalize
train_dataset = datasets.ImageFolder('data/train', transform=preprocess)
val_dataset = datasets.ImageFolder('data/val', transform=preprocess)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)
print('Classes:', train_dataset.classes)
print('Training samples:', len(train_dataset))Training Speed Comparison
Feature extraction is dramatically faster than full fine-tuning. When all backbone parameters have requires_grad=False, PyTorch does not compute gradients through them during loss.backward(), saving memory and computation proportional to the number of frozen layers.
For ResNet-50 (25M parameters, 2048 frozen features, only ~2K parameters in the head), a training epoch on a 5000-image dataset completes in seconds on CPU, compared to minutes for full fine-tuning. Pre-extracting features offline and then training only the head as a standard sklearn classifier is even faster, since the backbone forward pass only runs once per image.
import time
# Measuring training time difference
# With frozen backbone (feature extraction):
start = time.time()
for batch in train_loader:
images, labels = batch
with torch.no_grad():
features = backbone(images) # Fast: no grad tracking
loss = criterion(classifier(features), labels)
loss.backward() # Gradients only through tiny classifier
optimizer.step()
optimizer.zero_grad()
print(f'Feature extraction epoch: {time.time()-start:.1f}s')
# Full fine-tuning trains 25M params instead of ~2KAdapting EfficientNet for Feature Extraction
The approach is the same for EfficientNet: freeze all parameters, then replace the final classification layer. For EfficientNet the classifier is a Sequential block accessed via model.classifier, not model.fc. Always check the model architecture to find the correct attribute name.
EfficientNet-B0's classifier expects 1280-dimensional input features. When writing code that works with multiple architectures, introspect the final layer with in_features instead of hard-coding the dimension, making your code more reusable across different backbone choices.
import torchvision.models as models
import torch.nn as nn
model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1)
# Freeze backbone
for param in model.parameters():
param.requires_grad = False
# EfficientNet uses model.classifier, not model.fc
print('Old classifier:', model.classifier)
in_features = model.classifier[1].in_features # Access Linear inside Sequential
model.classifier = nn.Linear(in_features, num_classes)
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f'Trainable parameters: {trainable:,}')Adapting ViT for Feature Extraction
For ViT, the classification head is accessed via model.heads. The feature dimension from ViT-B/16's backbone is 768 (the transformer embedding dimension). After freezing all parameters, replace model.heads with a new nn.Linear(768, num_classes).
ViT benefits less from feature extraction than CNNs because its attention layers are more task-specific — they learn to attend to different image regions than what your task requires. If you use a ViT backbone, at minimum unfreeze the last few transformer encoder blocks for best performance. However, for a quick experiment, full feature extraction is still a valid starting point.
import torchvision.models as models
import torch.nn as nn
vit = models.vit_b_16(weights=models.ViT_B_16_Weights.IMAGENET1K_V1)
# Freeze all parameters
for param in vit.parameters():
param.requires_grad = False
# Replace the classification head
print('Old head:', vit.heads) # Sequential with Linear(768, 1000)
vit.heads = nn.Linear(768, num_classes)
print('ViT trainable params:', sum(p.numel() for p in vit.parameters() if p.requires_grad))Evaluating Feature Extraction Results
After training with frozen features, evaluate on the validation set using standard classification metrics. A well-tuned feature extractor often achieves 90%+ accuracy on small custom datasets (500-5000 images per class) where training from scratch would overfit completely.
If accuracy is lower than expected, consider: (1) adding data augmentation during training, (2) using a dropout layer before the linear head, (3) using a slightly more expressive head (two linear layers with ReLU), or (4) moving to partial fine-tuning by unfreezing the last residual block. Monitor both train and validation accuracy to distinguish overfitting from underfitting.
from sklearn.metrics import classification_report
import torch
def evaluate(model, loader, device):
model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for images, labels in loader:
images = images.to(device)
logits = model(images)
preds = logits.argmax(dim=1).cpu()
all_preds.extend(preds.numpy())
all_labels.extend(labels.numpy())
print(classification_report(all_labels, all_preds,
target_names=class_names))
evaluate(model, val_loader, device)When Feature Extraction Is Not Enough
Feature extraction works best when the source domain (ImageNet: natural photographs) and target domain are similar. When your data is very different — X-ray images, satellite imagery, microscopy, infrared thermal images — the low-level features still transfer (edge detectors are universal), but higher-level ImageNet features may be less useful.
Signs that feature extraction is underperforming: validation accuracy plateaus well below expectations, or train accuracy is much higher than validation (indicating the head is overfitting to too few features). In these cases, move to fine-tuning: unfreeze some backbone layers and train with a much lower learning rate to gradually adapt the pre-learned features to your domain.
Quick Check
Test your understanding of feature extraction with frozen backbones from this lesson.
Lesson Recap
In this lesson you learned: feature extraction freezes all backbone parameters and trains only a new classification head, giving fast training and strong results when your data resembles ImageNet, freezing is done by setting requires_grad=False on backbone parameters before replacing the final layer, and pre-extracting features offline is even faster by running the backbone once and caching the feature vectors. Next up we learn fine-tuning — how to carefully unfreeze backbone layers with low learning rates for even better accuracy.
คำถามที่พบบ่อย
บทเรียน “การสกัดคุณลักษณะ: การตรึงแกนหลัก” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การสกัดคุณลักษณะ: การตรึงแกนหลัก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Machine Learning Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การสกัดคุณลักษณะ: การตรึงแกนหลัก”
ผู้เรียนจะตรึงทุกชั้นยกเว้นส่วนหัวการจำแนกขั้นสุดท้าย ฝึกเฉพาะชั้นใหม่บนชุดข้อมูลกำหนดเองขนาดเล็ก และยืนยันว่าเวลาในการฝึกลดลงอย่างมาก คุณปฏิบัติ Machine Learning Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Machine Learning Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Machine Learning Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “การสกัดคุณลักษณะ: การตรึงแกนหลัก” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Machine Learning Academy นี้ได้ไหม
ได้ บทเรียน Machine Learning Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- แบบจำลองที่ฝึกไว้ล่วงหน้าใน torchvision: ResNet, EfficientNet และ ViT
- การสกัดคุณลักษณะ: การตรึงแกนหลัก
- การปรับแต่งละเอียด: การปลดตรึงและอัตราการเรียนรู้ต่ำ
- การปรับใช้ข้ามโดเมน: ภาพทางการแพทย์ที่มีป้ายกำกับจำกัด