说明¶
说明
tiny_activation 提供 7 种常用激活函数的前向 / 反向 / 原地实现,全部在 tiny::Tensor 上工作。Softmax 沿最后一维做数值稳定的归一化,可直接用于分类网络的输出层。
Activation — 给神经网络注入非线性
没有激活函数的神经网络只是线性变换的堆叠——再多层也等价于一层。
算法直觉¶
为什么需要非线性?¶
- 线性层就是 \(y = xW + b\),两层线性层堆叠:\(y = (xW_1 + b_1)W_2 + b_2 = x(W_1W_2) + (b_1W_2 + b_2)\)→ 仍然是一个线性变换!
- 非线性激活函数(ReLU、Sigmoid 等)在每层之间"打断"线性,让网络能学到复杂的模式
常见激活函数¶
| 函数 | 公式 | 输出范围 | 特点 | 适用场景 |
|---|---|---|---|---|
| ReLU | \(\max(0, x)\) | \([0, \infty)\) | 计算快、缓解梯度消失 | 隐藏层默认 |
| Leaky ReLU | \(x > 0 ? x : \alpha x\) | \((-\infty, \infty)\) | 解决 ReLU 死亡问题 | 某些深层网络 |
| Sigmoid | \(1/(1+e^{-x})\) | \((0, 1)\) | 输出可解释为概率 | 二分类输出层 |
| Tanh | \((e^x-e^{-x})/(e^x+e^{-x})\) | \((-1, 1)\) | 零中心化 | 某些 RNN 变体 |
| Softmax | \(e^{x_i}/\sum e^{x_j}\) | \((0, 1)\) 和为 1 | 输出概率分布 | 多分类输出层 |
选型建议
- 隐藏层:ReLU,没有之一。又快又好
- 二分类输出:Sigmoid,输出 0~1 的概率
- 多分类输出:Softmax,所有类概率和为 1
- 避免:隐藏层用 Sigmoid/Tanh,梯度容易消失,深层训练不动
Sigmoid 的饱和区
输入过大或过小时梯度接近 0,反向传播时梯度很难传回前面的层。这是早期神经网络难以训练的主要原因之一。
ActType 枚举¶
enum class ActType
{
RELU = 0, // max(0, x)
LEAKY_RELU, // x > 0 ? x : alpha*x
SIGMOID, // 1 / (1 + exp(-x))
TANH, // tanh(x)
SOFTMAX, // exp(xi) / sum(exp(xj)),沿最后一维
GELU, // 0.5 * x * (1 + tanh(sqrt(2/pi)*(x + 0.044715*x^3)))
LINEAR // 恒等
};
数学定义¶
| 激活 | 前向 | 反向 |
|---|---|---|
| ReLU | \( y = \max(0, x) \) | \( \frac{dL}{dx} = \frac{dL}{dy} \cdot \mathbb{1}[x > 0] \) |
| Leaky ReLU | \( y = x \cdot (x > 0) + \alpha x \cdot (x \le 0) \) | \( \frac{dL}{dx} = \frac{dL}{dy} \cdot (\mathbb{1}[x>0] + \alpha \mathbb{1}[x \le 0]) \) |
| Sigmoid | \( y = \frac{1}{1 + e^{-x}} \) | \( \frac{dL}{dx} = \frac{dL}{dy} \cdot y(1-y) \) |
| Tanh | \( y = \tanh(x) \) | \( \frac{dL}{dx} = \frac{dL}{dy} \cdot (1 - y^2) \) |
| Softmax | \( y_i = \frac{e^{x_i - \max}}{\sum_j e^{x_j - \max}} \) | \( \frac{dL}{dx_i} = y_i \left(\frac{dL}{dy_i} - \sum_j \frac{dL}{dy_j} y_j\right) \) |
| GELU | \( y = 0.5 x \left(1 + \tanh\left(\sqrt{\frac{2}{\pi}}(x + 0.044715 x^3)\right)\right) \) | 数值微分(实现见 gelu_backward) |
Softmax 数值稳定性
实现先对每行求最大值并相减,再做 exp 与归一化,等价于 \(\operatorname{softmax}(x)\) 但避免溢出。归一化时分母加 TINY_MATH_MIN_DENOMINATOR 防止除零。
API 概览¶
前向(返回新张量)¶
Tensor relu_forward (const Tensor &x);
Tensor leaky_relu_forward (const Tensor &x, float alpha = 0.01f);
Tensor sigmoid_forward (const Tensor &x);
Tensor tanh_forward (const Tensor &x);
Tensor softmax_forward (const Tensor &x);
Tensor gelu_forward (const Tensor &x);
每个 *_forward 内部都 clone() 输入再调用对应的 *_inplace。
原地版本(直接修改 x)¶
void relu_inplace (Tensor &x);
void leaky_relu_inplace (Tensor &x, float alpha = 0.01f);
void sigmoid_inplace (Tensor &x);
void tanh_inplace (Tensor &x);
void softmax_inplace (Tensor &x);
void gelu_inplace (Tensor &x);
适合显存敏感、不需要保留输入的场景(推理流水线常用)。
反向(仅在 TINY_AI_TRAINING_ENABLED 时编译)¶
Tensor relu_backward (const Tensor &x, const Tensor &grad_out);
Tensor leaky_relu_backward (const Tensor &x, const Tensor &grad_out, float alpha = 0.01f);
Tensor sigmoid_backward (const Tensor &y, const Tensor &grad_out); // 传 forward 的输出
Tensor tanh_backward (const Tensor &y, const Tensor &grad_out); // 传 forward 的输出
Tensor softmax_backward (const Tensor &y, const Tensor &grad_out); // 传 forward 的输出
Tensor gelu_backward (const Tensor &x, const Tensor &grad_out);
反向函数的 cache
- ReLU / LeakyReLU / GELU:传入
x(forward 的输入)。 - Sigmoid / Tanh / Softmax:传入
y(forward 的输出),避免重新求 sigmoid。ActivationLayer::forward()会自动按这个规则cache_正确的张量。
Dispatch 助手¶
Tensor act_forward (const Tensor &x, ActType type, float alpha = 0.01f);
void act_inplace (Tensor &x, ActType type, float alpha = 0.01f);
Tensor act_backward(const Tensor &cache, const Tensor &grad_out,
ActType type, float alpha = 0.01f);
按枚举值 dispatch,便于从配置文件 / 模型构造参数中传入。
常见用法¶
// 直接使用函数式 API
Tensor h = tanh_forward(x);
// 用 ActType + dispatch 实现可插拔
ActType act = ActType::GELU;
Tensor y = act_forward(x, act);
// 与 ActivationLayer 配合(Sequential 网络中)
Sequential m;
m.add(new Dense(in, hid));
m.add(new ActivationLayer(ActType::RELU));
适用场景¶
- 隐藏层激活:ReLU / LeakyReLU / GELU。
- 概率输出:Sigmoid(二分类)、Softmax(多分类)。
- Transformer 风格 MLP:GELU。
- 饱和归一:Tanh。
- 跳过激活:LINEAR(恒等),常用于
LossType::CROSS_ENTROPY接收 raw logits 的回归 / 分类。