形狀對不上就相加:例如把 [1, 64, 56, 56] 加到 [1, 256, 56, 56],PyTorch 會報 RuntimeError: The size of tensor a (256) must match the size of tensor b (64) at non-singleton dimension 1(實測)。這正是第 18、19 行 downsample 存在的原因。
import torch
import torch.nn as nn
from torchvision.models import resnet50
from torchvision.models.resnet import Bottleneck
torch.manual_seed(0)
# 1. 看 torchvision 的 ResNet-50:由幾個 Bottleneck 物件組成?
model = resnet50(weights=None) # 只建結構,不下載權重
blocks = [m for m in model.modules() if isinstance(m, Bottleneck)]
print('Bottleneck 物件:', len(blocks), '個')
print('有 downsample 的:', sum(b.downsample is not None for b in blocks), '個')
print('layer1[0].downsample =', model.layer1[0].downsample)
print('layer1[1].downsample =', model.layer1[1].downsample)
# 2. 自己做兩個 50 層的網路:零件一樣,只差有沒有捷徑
W, DEPTH = 128, 50
class Plain(nn.Module):
def __init__(self):
super().__init__()
self.inp = nn.Linear(3072, W)
self.layers = nn.ModuleList([nn.Linear(W, W) for _ in range(DEPTH)])
self.out = nn.Linear(W, 10)
def forward(self, x):
x = torch.relu(self.inp(x))
for fc in self.layers:
x = torch.relu(fc(x))
return self.out(x)
class Residual(Plain): # 繼承 Plain:零件一模一樣,只改 forward
def forward(self, x):
x = torch.relu(self.inp(x))
for i in range(0, DEPTH, 2):
identity = x
out = torch.relu(self.layers[i](x))
out = self.layers[i + 1](out)
out += identity # ← 捷徑:把輸入加回來
x = torch.relu(out)
return self.out(x)
# 3. 同一批假資料,比較第 1 層和第 50 層的梯度
x = torch.randn(256, 3072) # 256 張「假圖片」(3072 = 32×32×3)
y = torch.randint(0, 10, (256,)) # 256 個假答案(0~9)
for Net in (Plain, Residual):
torch.manual_seed(0) # 兩個網路的初始權重一模一樣
net = Net()
loss = nn.CrossEntropyLoss()(net(x), y)
loss.backward()
g1 = net.layers[0].weight.grad.norm().item()
g50 = net.layers[-1].weight.grad.norm().item()
n = sum(p.numel() for p in net.parameters())
print(f'{Net.__name__:8} 參數 {n:,} 第 1 層梯度 {g1:.3e} 第 50 層梯度 {g50:.3e}')