Machine Learning Academy · บทเรียน

ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน

ผู้เรียนจะเพิ่ม MaxPool2d หลังชั้น卷积 คำนวณมิติผลลัพธ์ และทำความเข้าใจว่าการพูลช่วยให้ตำแหน่งคลาดเคลื่อนได้พร้อมลดการคำนวณอย่างไร

บทเรียน 2 จาก 413 ขั้นตอน

ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน เป็นบทเรียน Machine Learning Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Machine Learning Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุผลที่มีเลเยอร์พูลลิง

เลเยอร์พูลลิง จะลดมิติเชิงพื้นที่ของแผนที่คุณลักษณะ ช่วยลดการคำนวณและการใช้หน่วยความจำ พร้อมทำให้ตัวแทนข้อมูลกระชับขึ้น นอกจากนี้ยังทำให้เกิด ความไม่แปรผันเชิงพื้นที่ ในระดับหนึ่ง กล่าวคือ การเลื่อนตำแหน่งเล็กน้อยของคุณลักษณะในอินพุตจะให้เอาต์พุตหลังพูลลิงเหมือนเดิม หากไม่มีพูลลิง (หรือคอนโวลูชันที่มี stride เท่ากับ 2) มิติเชิงพื้นที่จะคงที่ตลอดทุกเลเยอร์ ทำให้เครือข่ายต้องใช้การคำนวณสูงเกินไปสำหรับภาพขนาดใหญ่

import torch
import torch.nn as nn

# Without pooling: feature map grows in channels but not reduced
conv1 = nn.Conv2d(3, 32, 3, padding=1)   # (B, 32, H, W)
conv2 = nn.Conv2d(32, 64, 3, padding=1)  # (B, 64, H, W)

# With pooling: spatial dims are halved each time
pool = nn.MaxPool2d(kernel_size=2, stride=2)

x = torch.randn(4, 3, 32, 32)
out = pool(torch.relu(conv1(x)))
print(out.shape)   # (4, 32, 16, 16) -- halved!

Max Pooling: เลือกค่าสูงสุด

MaxPool2d จะแบ่งแผนที่คุณลักษณะอินพุตออกเป็นหน้าต่างที่ไม่ซ้อนทับกัน แล้วเลือกค่าสูงสุดในแต่ละหน้าต่าง ค่าสูงสุดสอดคล้องกับการกระตุ้นที่แรงที่สุดของตัวกรองในตำแหน่งใดก็ตามภายในหน้าต่างนั้น จึงบอกได้ว่าคุณลักษณะปรากฏอยู่ที่ใดที่หนึ่งในบริเวณนั้นหรือไม่ โดยไม่ขึ้นกับตำแหน่งที่แน่นอน Max pool ขนาด 2x2 ที่มี stride เท่ากับ 2 จะลดความสูงและความกว้างลงครึ่งหนึ่ง ทำให้ขนาดเชิงพื้นที่ทั้งหมดลดลง 4 เท่า

import torch
import torch.nn as nn

pool = nn.MaxPool2d(kernel_size=2, stride=2)

# Simple 4x4 feature map
x = torch.tensor([[[[ 1., 3., 2., 4.],
                     [ 5., 6., 7., 8.],
                     [ 9., 2., 1., 3.],
                     [ 4., 5., 6., 7.]]]])

out = pool(x)
print(out.shape)   # (1, 1, 2, 2)
print(out)
# Max of top-left 2x2: max(1,3,5,6)=6
# Max of top-right 2x2: max(2,4,7,8)=8
# Max of bottom-left 2x2: max(9,2,4,5)=9
# Max of bottom-right 2x2: max(1,3,6,7)=7

Average Pooling เทียบกับ Max Pooling

AvgPool2d จะคำนวณค่าเฉลี่ยของค่าในแต่ละหน้าต่างพูลลิงแทนการเลือกค่าสูงสุด Average pooling จะทำให้แผนที่คุณลักษณะเรียบขึ้นและเก็บข้อมูลจากการกระตุ้นทั้งหมดไว้ ขณะที่ max pooling จะทิ้งการกระตุ้นที่อ่อนกว่า โดยทั่วไปเครือข่ายสำหรับการจำแนกประเภทจะเลือกใช้ max pooling เมื่อ การปรากฏอยู่ ของคุณลักษณะสำคัญกว่าความแรงโดยเฉลี่ย ส่วน average pooling มักใช้สำหรับ global pooling ที่ส่วนท้ายของ CNN เพื่อยุบมิติเชิงพื้นที่

import torch
import torch.nn as nn

x = torch.tensor([[[[ 1., 3., 2., 4.],
                     [ 5., 6., 7., 8.],
                     [ 9., 2., 1., 3.],
                     [ 4., 5., 6., 7.]]]])

max_pool = nn.MaxPool2d(2, stride=2)
avg_pool = nn.AvgPool2d(2, stride=2)

print('MaxPool:', max_pool(x))
# tensor([[[[6., 8.], [9., 7.]]]])

print('AvgPool:', avg_pool(x))
# tensor([[[[3.75, 5.25], [5.00, 4.25]]]])

Global Average Pooling

Global Average Pooling (GAP) จะยุบมิติเชิงพื้นที่ทั้งหมดให้เหลือค่าเดียวต่อช่องสัญญาณ โดยหาค่าเฉลี่ยจากทุกตำแหน่งเชิงพื้นที่ หากแผนที่คุณลักษณะมีรูปร่าง (B, C, H, W), GAP จะให้ผลลัพธ์เป็น (B, C) ซึ่งเป็นเวกเตอร์ที่มีค่า C ค่า แทนค่าการกระตุ้นเฉลี่ยของตัวกรองแต่ละตัวทั่วทั้งภาพ GAP จะเข้ามาแทนที่การแปลงเป็นเวกเตอร์แบนและชั้นเชื่อมต่อเต็มรูปแบบขนาดใหญ่ด้านบนของ CNN แบบดั้งเดิม (VGG) ช่วยลดจำนวนพารามิเตอร์ลงอย่างมากและทำหน้าที่เป็นตัวทำให้เป็นระเบียบที่มีประสิทธิภาพ โดยใช้ใน ResNet, MobileNet และ EfficientNet

import torch
import torch.nn as nn

# Global Average Pooling (AdaptiveAvgPool2d to any output size)
gap = nn.AdaptiveAvgPool2d(output_size=(1, 1))

x = torch.randn(8, 512, 7, 7)   # typical final feature map
out = gap(x)
print(out.shape)   # (8, 512, 1, 1)

# Flatten to (B, C) for the classifier
flat = out.view(out.size(0), -1)
print(flat.shape)  # (8, 512)

# Then: nn.Linear(512, num_classes)

การคำนวณมิติเอาต์พุต

การคำนวณมิติเชิงพื้นที่ของเอาต์พุตหลังพูลลิงทำได้ตรงไปตรงมา: output_size = floor((input_size - kernel_size) / stride) + 1 สำหรับพูลขนาด 2x2 ที่มี stride เท่ากับ 2 และอินพุตขนาด 28x28 เอาต์พุตจะมีขนาด 14x14 การคำนวณมิติผิดเป็นสาเหตุที่พบบ่อยของข้อผิดพลาดด้านรูปร่าง AdaptiveMaxPool2d และ AdaptiveAvgPool2d ช่วยแก้ปัญหานี้โดยรับขนาดเอาต์พุตที่ต้องการโดยตรง แล้วคำนวณ kernel และ stride ที่จำเป็นโดยอัตโนมัติ เหมาะอย่างยิ่งเมื่อต้องการเอาต์พุตขนาดคงที่ไม่ว่าอินพุตจะมีขนาดเท่าใด

import torch
import torch.nn as nn

# Classic fixed-size pooling
x = torch.randn(1, 32, 28, 28)
pool = nn.MaxPool2d(kernel_size=2, stride=2)
print(pool(x).shape)   # (1, 32, 14, 14)

# Adaptive pooling: specify desired output size
adaptive_pool = nn.AdaptiveAvgPool2d((4, 4))
print(adaptive_pool(x).shape)  # (1, 32, 4, 4) -- any input

# Works even with irregular input sizes:
x2 = torch.randn(1, 32, 17, 23)
print(adaptive_pool(x2).shape)  # (1, 32, 4, 4) -- still 4x4!

พูลลิงเพื่อความไม่แปรผันต่อการเลื่อนตำแหน่ง

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

import torch
import torch.nn as nn

pool = nn.MaxPool2d(2, 2)

# Feature map with an 'activated' pixel at position (1,1)
fm1 = torch.zeros(1, 1, 4, 4)
fm1[0, 0, 1, 1] = 1.0   # activation at (1,1)

# Shift by 1 pixel to (1,2)
fm2 = torch.zeros(1, 1, 4, 4)
fm2[0, 0, 1, 2] = 1.0

# Both fall in the same 2x2 window -> same pool output
print(torch.allclose(pool(fm1), pool(fm2)))  # True!

พูลลิงเทียบกับคอนโวลูชันที่มี Stride เท่ากับ 2

สถาปัตยกรรม CNN สมัยใหม่ (ResNet, EfficientNet) หันมาใช้ คอนโวลูชันที่มี stride เท่ากับ 2 แทนการใช้เลเยอร์ max pooling แยกต่างหากมากขึ้น ข้อดีคือคอนโวลูชันที่มี stride เท่ากับ 2 สามารถ เรียนรู้ได้ เครือข่ายจึงเป็นผู้กำหนดวิธีลดขนาด และอาจรักษาข้อมูลที่มีประโยชน์ไว้ได้มากกว่าการดำเนินการ max แบบตายตัว Max pooling ยังคงใช้ในสถาปัตยกรรมแบบดั้งเดิม (VGGNet, AlexNet) และมีข้อดีด้านความเรียบง่ายในการคำนวณ ทั้งสองวิธีมีเป้าหมายเดียวกัน คือ ลดความละเอียดเชิงพื้นที่ลง 2 เท่า

import torch
import torch.nn as nn

# Option 1: Conv + explicit MaxPool
block_maxpool = nn.Sequential(
    nn.Conv2d(32, 64, 3, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(2, 2)      # separate pooling
)

# Option 2: Stride-2 Conv (learnable downsampling)
block_stride = nn.Sequential(
    nn.Conv2d(32, 64, 3, padding=1, stride=2),  # stride=2
    nn.ReLU()
)

x = torch.randn(4, 32, 16, 16)
print(block_maxpool(x).shape)  # (4, 64, 8, 8)
print(block_stride(x).shape)   # (4, 64, 8, 8) -- same!

พูลลิงไม่มีพารามิเตอร์ที่เรียนรู้ได้

คุณสมบัติสำคัญของเลเยอร์พูลลิงคือ ไม่มีพารามิเตอร์ที่เรียนรู้ได้ Max pooling เพียงเลือกค่าสูงสุด ส่วน average pooling จะคำนวณค่าเฉลี่ย ทำให้เลเยอร์พูลลิงทำงานได้รวดเร็วมากและไม่ต้องใช้หน่วยความจำสำหรับพารามิเตอร์ โดยไม่เพิ่มจำนวนพารามิเตอร์ของโมเดล การไม่มีพารามิเตอร์ยังหมายความว่าพูลลิงไม่สามารถทำให้เกิดการเรียนรู้เกินได้ นี่คือเหตุผลที่ global average pooling ตอนท้ายของเครือข่ายเป็นตัวทำให้เป็นระเบียบที่มีประสิทธิภาพ เพราะบังคับให้เครือข่ายเข้ารหัสข้อมูลไว้ในค่าเฉลี่ยของแต่ละช่องสัญญาณ ไม่ใช่ในตำแหน่งเชิงพื้นที่

import torch.nn as nn

pool = nn.MaxPool2d(2, 2)
avg_pool = nn.AvgPool2d(2, 2)

# Count parameters
max_params = sum(p.numel() for p in pool.parameters())
avg_params = sum(p.numel() for p in avg_pool.parameters())

print('MaxPool parameters:', max_params)    # 0
print('AvgPool parameters:', avg_params)    # 0

# Compare with a Conv2d layer
conv = nn.Conv2d(32, 32, 2, stride=2)  # similar operation
conv_params = sum(p.numel() for p in conv.parameters())
print('Conv2d (stride-2) parameters:', conv_params)  # 4128

สถาปัตยกรรม CNN ทั่วไปที่มีพูลลิง

สถาปัตยกรรม CNN มาตรฐานจะสลับระหว่างบล็อกคอนโวลูชัน (Conv+BN+ReLU) และเลเยอร์พูลลิง โดยค่อย ๆ ลดมิติเชิงพื้นที่พร้อมเพิ่มจำนวนช่องสัญญาณ หลังบล็อกคอนโวลูชัน global average pooling จะยุบมิติเชิงพื้นที่ จากนั้นเลเยอร์เชิงเส้นจะแปลงผลลัพธ์เป็นคะแนนของแต่ละคลาส รูปแบบนี้ทำให้ได้เครือข่ายที่มีจำนวนพารามิเตอร์จัดการได้ง่าย มีการทำให้เป็นนัยทั่วไปที่ดี และจำแนกภาพได้อย่างมีประสิทธิภาพ

import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),                # 32->16
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),                # 16->8
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(),
        )
        self.pool = nn.AdaptiveAvgPool2d((1, 1))  # GAP
        self.classifier = nn.Linear(128, num_classes)

    def forward(self, x):
        x = self.features(x)
        x = self.pool(x).view(x.size(0), -1)
        return self.classifier(x)

model = SimpleCNN()
x = torch.randn(4, 3, 32, 32)
print(model(x).shape)  # (4, 10)

Fractional และ Adaptive Pooling

AdaptiveMaxPool2d(output_size) จะคำนวณขนาด kernel และ stride ที่จำเป็นโดยอัตโนมัติ เพื่อให้ได้มิติเอาต์พุตตามที่ระบุ ไม่ว่าอินพุตจะมีขนาดเท่าใดก็ตาม คุณสมบัตินี้มีประโยชน์อย่างยิ่งเมื่อต้องการเวกเตอร์ขนาดคงที่สำหรับตัวจำแนก แต่ภาพอินพุตมีขนาดแตกต่างกัน (เช่น การตรวจจับวัตถุและชุดข้อมูลที่มีความละเอียดหลากหลาย) FractionalMaxPool2d ใช้บริเวณพูลลิงแบบสุ่มเพื่อเพิ่มความสุ่ม ทำหน้าที่เป็นตัวทำให้เป็นระเบียบเพิ่มเติมในสถาปัตยกรรมบางประเภท

import torch
import torch.nn as nn

# Adaptive pooling: always produces 3x3 output
adaptive = nn.AdaptiveMaxPool2d((3, 3))

sizes = [(32, 32), (48, 64), (100, 100)]
for h, w in sizes:
    x = torch.randn(2, 64, h, w)
    out = adaptive(x)
    print(f'Input {h}x{w} -> Output {out.shape[2]}x{out.shape[3]}')
# Always 3x3 regardless of input!

# Fractional max pooling (randomised pooling regions)
frac = nn.FractionalMaxPool2d(kernel_size=2, output_size=(6, 6))
x = torch.randn(2, 32, 12, 12)
print('Fractional pool:', frac(x).shape)  # (2, 32, 6, 6)

การแสดงผลลัพธ์ของพูลลิง

เพื่อทำความเข้าใจผลของพูลลิงในเชิงภาพ: แผนที่คุณลักษณะก่อน max pooling จะแสดงการตอบสนองดิบของตัวกรองที่แต่ละพิกเซล หลังพูลลิง เอาต์พุตจะมีความละเอียดเชิงพื้นที่หยาบขึ้น โดยแต่ละค่าจะแทนการตอบสนองที่แรงที่สุดในบริเวณใกล้เคียง แผนที่คุณลักษณะจะมีความเป็นนามธรรมมากขึ้นและไม่ขึ้นกับตำแหน่งมากนัก เมื่ออยู่ในส่วนลึกของเครือข่าย หลังผ่านพูลลิงหลายรอบ ขอบเขตการรับข้อมูลของเซลล์ประสาทแต่ละตัวจะครอบคลุมภาพต้นฉบับเกือบทั้งหมด ทำให้สามารถจดจำรูปแบบในภาพโดยรวมได้

import torch
import torch.nn as nn

# Simulate pooling effect on a feature map
pool2 = nn.MaxPool2d(2, 2)
pool4 = nn.MaxPool2d(4, 4)

x = torch.randn(1, 1, 16, 16)
print('Before pooling:', x.shape)        # (1,1,16,16)
print('After 2x2 pool:', pool2(x).shape) # (1,1,8,8)
print('After 4x4 pool:', pool4(x).shape) # (1,1,4,4)

# After 3 rounds of 2x2 pooling on 32x32 input:
pool_3x = nn.Sequential(
    nn.MaxPool2d(2), nn.MaxPool2d(2), nn.MaxPool2d(2)
)
print('After 3x 2x2 pool:', pool_3x(torch.randn(1,1,32,32)).shape)
# (1, 1, 4, 4)

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจแนวคิด Machine Learning ด้วย Python จากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า MaxPool2d และ AvgPool2d ลดมิติเชิงพื้นที่ด้วยการเลือกค่าสูงสุดหรือค่าเฉลี่ยในแต่ละหน้าต่างพูลลิง ทำให้ได้ตัวแทนข้อมูลที่กระชับและมีความไม่แปรผันต่อการเลื่อนตำแหน่งในระดับท้องถิ่น AdaptiveAvgPool2d สร้างเอาต์พุตขนาดคงที่ไม่ว่ามิติของอินพุตจะเป็นเท่าใด (ใช้เป็น Global Average Pooling) และ พูลลิงไม่มีพารามิเตอร์ที่เรียนรู้ได้ จึงทำงานได้รวดเร็วและไม่ทำให้เกิดการเรียนรู้เกิน บทถัดไปเราจะสร้างและฝึก CNN แบบสมบูรณ์บน CIFAR-10

เริ่มต้นได้ฟรี

เรียนรู้ Python ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
30
บทเรียน
120

คำถามที่พบบ่อย

บทเรียน “ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Machine Learning Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน”

ผู้เรียนจะเพิ่ม MaxPool2d หลังชั้น卷积 คำนวณมิติผลลัพธ์ และทำความเข้าใจว่าการพูลช่วยให้ตำแหน่งคลาดเคลื่อนได้พร้อมลดการคำนวณอย่างไร คุณปฏิบัติ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การ卷积และตัวกรอง: การตรวจจับขอบและรูปแบบ
  2. ชั้นการพูล: การลดขนาดเชิงพื้นที่และความไม่แปรผัน
  3. การสร้างและฝึก CNN บน CIFAR-10
  4. การเพิ่มข้อมูล: การแปลงเพื่อความทนทาน
← กลับไปที่ Machine Learning Academy