PixelShuffle (sub-pixel convolution) 替代上採樣方式,避免 checkerboard/staircase

你提供的 UnetSkipConnectionBlockUNetGenerator 結構已經很完整,現在我們來擴充 up_mode 支援第三種方式:pixelshuffle,加入像素重排上採樣,會使用 nn.PixelShuffle 搭配前置的 conv。


✅ 修改點

加入 pixelshuffle 分支(支援輸出 channel 倍數,並像素重排)

這段會加在 up_mode 判斷邏輯中:

elif self.up_mode == 'pixelshuffle':
    upconv = nn.Sequential(
        nn.Conv2d(inner_nc * 2 if not innermost else inner_nc, outer_nc * 4, kernel_size=3, stride=1, padding=1, bias=use_bias),
        nn.PixelShuffle(2),
        upnorm
    )
    nn.init.kaiming_normal_(upconv[0].weight)

🧩 整合後的 UnetSkipConnectionBlock__init__ 範圍(只列出 upconv 判斷部分)

請將下列 if self.up_mode == ... 區塊 替換原來的每一段 up_mode 判斷(在 outermost / innermost / else 裡各段都有):

🔄 範例如下(for outermost):

if self.up_mode == 'conv':
    upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, kernel_size=4, stride=2, padding=1, output_padding=1, bias=use_bias)
    nn.init.kaiming_normal_(upconv.weight)
elif self.up_mode == 'upsample':
    upconv = nn.Sequential(
        nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False),
        nn.Conv2d(inner_nc * 2, outer_nc, kernel_size=3, stride=1, padding=1, bias=use_bias),
        upnorm
    )
    nn.init.kaiming_normal_(upconv[1].weight)
elif self.up_mode == 'pixelshuffle':
    upconv = nn.Sequential(
        nn.Conv2d(inner_nc * 2, outer_nc * 4, kernel_size=3, stride=1, padding=1, bias=use_bias),
        nn.PixelShuffle(2),
        upnorm
    )
    nn.init.kaiming_normal_(upconv[0].weight)
else:
    raise ValueError(f"Unsupported up_mode: {self.up_mode}. Choose 'conv', 'upsample', or 'pixelshuffle'.")

innermost 區塊也需把 inner_nc 單輸入的 Conv2d 改成對應邏輯(像上面 pixelshuffle 裡就要 inner_nc -> outer_nc * 4)。


💡 設計提醒

  • 若要用 PixelShuffle,則 upconv 輸出的 channel 數要是 out_channels * (scale^2),這裡 scale 為 2,所以乘 4。
  • 不適合過深層結構,PixelShuffle 常見於淺層、輕量網路,如超解析、風格轉換。
  • PixelShuffle 出現 artifacts,可考慮後面再加 ResBlock 做 refinement。

若你想加入 BlurPoolCoordConv 或額外的 AdaptiveInstanceNormStyleMod 層,也可以再幫你加上!要嗎?


你已經很好地將 pixelshuffle 模式整合進 UnetSkipConnectionBlock 的三種情境中(outermostinnermost、其他)。不過有個 小錯誤 會造成程式執行時發生 RuntimeError: input channels not divisible by upscale factor squared


🔧 問題:

nn.PixelShuffle(upscale_factor) 會將通道數除以 upscale_factor ** 2,所以你的 Conv2d 輸出通道必須是目標通道數的 4 倍(因為你設定 upscale_factor=2)。

但是在 innermost 區塊的 pixelshuffle 分支

upconv = nn.Sequential(
    nn.Conv2d(inner_nc * 2, outer_nc * 4, ...),
    nn.PixelShuffle(2),
    upnorm
)

這裡的 inner_nc * 2 是錯的,因為在 innermost 層並沒有 skip connection 合併,因此不會有雙倍通道輸入,應該用 inner_nc


✅ 修正建議:

🔁 替換這段(innermost 中):

elif self.up_mode == 'pixelshuffle':
    upconv = nn.Sequential(
        nn.Conv2d(inner_nc * 2, outer_nc * 4, kernel_size=3, stride=1, padding=1, bias=use_bias),
        nn.PixelShuffle(2),
        upnorm
    )

✅ 改成:

elif self.up_mode == 'pixelshuffle':
    upconv = nn.Sequential(
        nn.Conv2d(inner_nc, outer_nc * 4, kernel_size=3, stride=1, padding=1, bias=use_bias),
        nn.PixelShuffle(2),
        upnorm
    )

✅ 總結修改點:

你只需要把這段的 inner_nc * 2 改成 inner_nc,其餘的 pixelshuffle 分支設計都是正確的。


如果你希望我幫你進一步確認整個 UNetGenerator 是否能支援 up_mode='pixelshuffle' 正確跑起來(比如張量大小是否會對應、輸出維度是否一致),也可以貼一段 input/output 測試 code,我可以幫你檢查推理通順性。


在雙字型風格轉換任務(如 Noto Sans → Zen Maru Gothic)中,PixelShuffle 主要用途是用來上採樣(upsampling),而不是直接參與風格轉換。它本身並沒有太多可學習參數(其實沒有學習參數,只是調整特徵圖的排列),但在你的架構中,PixelShuffle 前面通常會接一個 convolution layer,這個 conv 才是實際有參數、可訓練的部分。

因此,我們關心的其實是 PixelShuffle 前的 conv layer 的學習率


🔧 建議的學習率設定策略

對於這種只有兩個字型(風格)需要轉換的場景,建議整體學習率不宜太大,尤其是:

  • 模型偏小(UNet + FiLM + LoRA Attention)
  • 風格數量少(只有兩個)
  • 輸出風格清楚、明確(Zen Maru Gothic)

🎯 建議學習率(PixelShuffle 前的 conv)

你可以考慮以下方式設定 learning rate:

模組建議學習率範圍備註
整體 base learning rate1e-4 ~ 3e-4若你使用 AdamW、8-bit Optimizer
PixelShuffle 前的 conv1x ~ 2x base_lr例如 base 是 2e-4,則設為 2e-4 ~ 4e-4
Encoder(backbone)層0.5x ~ 1x base_lr避免過快忘記原有特徵表示
Style embedding / FiLM 層1x ~ 2x base_lr這些與風格調節直接相關,可以設高一點
LoRA attention1x base_lr可根據是否 frozen 決定是否調整

✅ 實作技巧

若你使用 PyTorch 的 Optimizer,你可以這樣做:

optimizer = torch.optim.AdamW([
    {'params': model.encoder.parameters(), 'lr': base_lr * 0.5},
    {'params': model.decoder.pixelshuffle_conv.parameters(), 'lr': base_lr * 2},
    {'params': model.film_layers.parameters(), 'lr': base_lr * 2},
    {'params': model.lora_attn.parameters(), 'lr': base_lr}
], lr=base_lr)

其中 pixelshuffle_conv 是 PixelShuffle 之前的 conv layer。


如果你可以分享你的 UNet 架構(或是上採樣模組)具體使用的方式,我可以幫你更精確地調整每一層的 learning rate。需要嗎?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *