卷积神经网络新姿势:除了余弦卷积,这几招也值得认真看一眼




2019-06-07

blog_main_img

上一篇我们已经聊过 `CosineConv2D` 这种更偏卷积算子层面的新玩法,这一篇换个方向,看几个不用大改骨架、但很有意思的“小招式”。

这类东西最迷人的点不在于名字新,而在于它们通常都有一个共同特点:

  • 改动不算夸张
  • 很容易塞进现有 CNN
  • 一旦有效,替换成本往往不高

所以它们特别适合拿来做实验。你不一定非得一把梭全网替换,但很适合在自己的模型里先试一层、两层,看看味道对不对。

这篇主要在看什么

从公开摘要里可以确认,这篇“其他”主要点到了两类很值得留意的思路:

  • SinReLU
  • Swish

它们的共性是:都在想办法让传统激活函数别那么“硬”,让网络在表达能力和训练行为上多一点回旋空间。

如果用一句话概括这篇的气质,大概就是:

不是推翻 CNN,而是想把它常用的小零件再打磨一下。

1. SinReLU:给 ReLU 加一点“波动感”

ReLU 之所以流行,是因为简单、直接、好训。

但它也有自己的脾气:

  • 形状太硬
  • 负半轴信息处理得很绝
  • 对某些更细腻的模式变化不够灵活

SinReLU 这类思路,可以粗略理解成:

在 ReLU 的门控逻辑之外,再给它一点正弦式的起伏感。

你不用一上来就死背公式,先抓直觉就够了:

  • 它还是保留了 ReLU 系的核心味道
  • 但不想让激活曲线一直板着脸
  • 希望通过一点周期性或波动性,把表达能力再抬一截

这种设计为什么会让人觉得有潜力?

  • 一方面,它不像完全重新发明一个巨大模块
  • 另一方面,它又不是纯粹的参数微调,而是真的在“形状”上做文章

对于卷积网络来说,激活函数的形状一旦变得更灵活,网络对局部模式、细小变化、复杂响应的表达,就有机会跟着变活一点。

当然,这类方法是不是一定比 ReLU 强,不存在绝对答案。更准确的说法是:

它给了你一个值得试的方向。

2. Swish:让输入自己给自己做一道“软门”

如果说 SinReLU 更像是在 ReLU 的基础上加一点波动感,那么 Swish 的气质就更偏“平滑门控”。

它最常见的写法是:

f(x) = x * sigmoid(beta * x)

这个式子看起来很短,但味道很足。

它不像 ReLU 那样:

  • 负数就直接拍平
  • 正数就线性放行

而是给输入乘上一个 sigmoid(beta * x) 形成的软权重。于是:

  • 较大的正值会被更明显地保留
  • 一部分负值不会被立刻清零
  • 整体曲线比 ReLU 更平滑

从使用体验上看,Swish 最讨喜的地方就在这里:

  • 它没有复杂到吓人
  • 又比普通 ReLU 多了点“弹性”

所以很多人第一次看到它时都会有一种感觉:

这玩意儿,好像真的挺适合直接塞进现有网络里试试。

3. 为什么这种“小改动”会让人上头

很多 CNN 新招并不是在 backbone 上狠狠干一刀,而是专挑“小但关键”的位置下手。

比如:

  • 激活函数
  • 卷积核的计算方式
  • 通道之间的重新加权
  • 局部和全局信息的混合方式

这类位置有个特点:

它们虽然小,但几乎贯穿全网。

所以你改一个激活函数,影响的并不是一层两层,而是整张网络的非线性表达方式。

这也是为什么很多看起来只是“小修小补”的论文,最后却能带来挺像样的提升。

4. 这类新姿势的价值,不只是精度涨没涨

很多人看新模块时,第一反应都是:

那它到底涨了几点?

这个问题当然重要,但还不够。

SinReLUSwish 这种东西,真正有价值的地方通常还包括:

  • 是否容易接进现有模型
  • 是否容易训练
  • 是否引入太多额外参数
  • 推理成本有没有明显变重
  • 对不同网络深度是否稳定

尤其是 Swish 这一类,优点就在于它非常像一个“替换成本不高”的升级件。对工程实践来说,这种东西的吸引力其实很大。

5. 如果你是做 CNN 实验的,应该怎么试

我的建议很简单:别一上来全网换血。

先从小规模替换开始更稳:

  1. 选一个你已经跑熟的 baseline
  2. 先只替换少数 block 里的激活函数
  3. 保持学习率、batch size、训练轮数不变
  4. 看 loss 曲线、收敛速度和验证集表现

这样你能更快判断:

  • 是真的有效
  • 还是只是训练波动

6. 在 Python 里试这种新招,门槛其实没那么高

6.1 在 TensorFlow / Keras 里试一个 Swish

import tensorflow as tf
from tensorflow import keras


def swish(x, beta=1.0):
    return x * tf.sigmoid(beta * x)


inputs = keras.Input(shape=(32, 32, 3))
x = keras.layers.Conv2D(32, 3, padding="same")(inputs)
x = keras.layers.BatchNormalization()(x)
x = keras.layers.Activation(swish)(x)
x = keras.layers.GlobalAveragePooling2D()(x)
outputs = keras.layers.Dense(10, activation="softmax")(x)

model = keras.Model(inputs, outputs)
model.summary()

这类实验的核心不是代码多复杂,而是你能不能把变量控制住。

6.2 在 PyTorch 里直接写个 Swish 模块

import torch
import torch.nn as nn


class Swish(nn.Module):
    def __init__(self, beta=1.0):
        super().__init__()
        self.beta = beta

    def forward(self, x):
        return x * torch.sigmoid(self.beta * x)


x = torch.randn(4, 64, 32, 32)
act = Swish(beta=1.0)
y = act(x)
print(x.shape, y.shape)

顺手提一句,在 PyTorch 里你也可以直接看看 SiLU,它和 Swish 非常接近:

import torch.nn as nn

act = nn.SiLU()

如果你只是想快速做实验,SiLU 会很顺手。

6.3 一个简化版“SinReLU 风格”尝试

严格实现要看你采用的具体定义,但如果只是为了体验“ReLU + 周期扰动”的思路,可以先写一个玩具版本:

import torch
import torch.nn as nn


class ToySinReLU(nn.Module):
    def __init__(self, alpha=0.1):
        super().__init__()
        self.alpha = alpha

    def forward(self, x):
        return torch.relu(x) + self.alpha * torch.sin(x)


x = torch.linspace(-4, 4, 9)
layer = ToySinReLU(alpha=0.05)
print(layer(x))

这个版本不是论文标准实现,但足够帮你理解那种“在 ReLU 基础上加一点波动感”的直觉。

7. 这类方法真正适合谁

如果你属于下面这几类人,这种“新姿势”很值得看:

  • 手里已经有一个成熟 CNN baseline
  • 想做低成本结构改造
  • 不想一上来重写大网络
  • 愿意用小实验换一些潜在提升

它不一定每次都能带来压倒性收益,但很适合拿来做:

  • 模块替换实验
  • 激活函数 ablation
  • 轻量结构优化

8. 最后收个尾

这篇“其他”看起来像是在随手记几个新点子,其实很有代表性。

它传达出来的意思大概是:

CNN 不只是 backbone 在进化,连最基础的激活函数和局部计算方式,也一直有人在认真折腾。

其中:

  • SinReLU 更像是在 ReLU 的表达形状上加活力
  • Swish 更像是在平滑门控上做优化

它们未必能在所有场景里都赢,但绝对属于那种值得你放进实验清单里的东西。

说白了,很多模型提升并不是一拳把房子拆了重盖,而是把原来那些“默认值”一个个重新审视。激活函数这件事,恰好就是最典型的一项。