前馈全连接层 - 代码实现及测试
1 课程概览
本课实现前馈全连接层(Feed Forward)。包含两个线性层,先升维(512→2048)再降维(2048→512),中间用 ReLU 激活函数增加非线性。作用是强化特征,对每个位置的词向量单独强化。类比参观大厂后回到教室学习,阅历和眼界不同了,还是你但认知更清晰。
2 核心概念与定义
- Feed Forward:前馈全连接层,两个线性层。
- 升维:512 → 2048,学习更多信息。
- 降维:2048 → 512,压缩回原维度。
- ReLU:激活函数,增加非线性,保留正数,负数变 0。
- d_ff:前馈全连接维度,一般比 d_model 大(如 2048)。
- 强化特征:对每个位置的词向量单独强化。
3 模型与算法详解
前馈全连接层结构
输入 (512) → Linear1 (512→2048) → ReLU → Dropout → Linear2 (2048→512) → 输出 (512)
维度变化
| 步骤 | 维度 | 说明 |
|---|---|---|
| 输入 | 512 | 词向量维度 |
| Linear1 后 | 2048 | 升维 |
| ReLU 后 | 2048 | 增加非线性 |
| Dropout 后 | 2048 | 防止过拟合 |
| Linear2 后 | 512 | 降维,恢复原维度 |
作用
对注意力机制的结果进一步加工,强化特征。
- 考虑注意力机制可能对复杂过程的拟合程度不够
- 增加两层网络来做
- 对每个位置的词向量单独强化特征
类比:参观大厂
先升维(参观大厂,眼界提高),再降维(回到教室,还是你但认知不同)。
- 升维:去阿里、字节等大厂参观,阅历和眼界提高
- 降维:回到教室学习,还是你,但认知更清晰
- 类似"由俭入奢易,由奢入俭难"
为什么是两层?
试验试出来的,不做三层、四层。
- 两层效果最好
- 不做三层、四层
- 通过实验验证
4 数学原理与推导
前馈全连接层公式
$$\text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2$$
步骤分解
第一层线性变换(升维): $$h = xW_1 + b_1$$ 其中 $W_1 \in \mathbb{R}^{d_{model} \times d_{ff}}$,$b_1 \in \mathbb{R}^{d_{ff}}$
ReLU 激活函数: $$a = \max(0, h)$$
第二层线性变换(降维): $$\text{output} = aW_2 + b_2$$ 其中 $W_2 \in \mathbb{R}^{d_{ff} \times d_{model}}$,$b_2 \in \mathbb{R}^{d_{model}}$
参数说明
| 参数 | 含义 | 典型值 |
|---|---|---|
| d_model | 词向量维度 | 512 |
| d_ff | 前馈全连接维度 | 2048 |
| W_1 | 第一层权重 | (512, 2048) |
| b_1 | 第一层偏置 | (2048,) |
| W_2 | 第二层权重 | (2048, 512) |
| b_2 | 第二层偏置 | (512,) |
5 代码示例
import torch
import torch.nn as nn
class FeedForward(nn.Module):
"""前馈全连接层"""
def __init__(self, d_model=512, d_ff=2048, dropout=0.1):
"""
初始化前馈全连接层
:param d_model: 词向量维度,例如 512
:param d_ff: 前馈全连接维度,例如 2048(一般比 d_model 大)
:param dropout: 随机失活概率
"""
super().__init__()
# 1. 第一个全连接层(升维:512 → 2048)
self.w1 = nn.Linear(d_model, d_ff)
# 2. 第二个全连接层(降维:2048 → 512)
self.w2 = nn.Linear(d_ff, d_model)
# 3. dropout 层(防止过拟合)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""
前向传播
:param x: 输入张量 (batch, seq_len, d_model)
:return: 输出张量 (batch, seq_len, d_model)
"""
# 1. 第一层线性变换(升维):512 → 2048
x = self.w1(x)
# 2. ReLU 激活函数(增加非线性,保留正数,负数变 0)
x = torch.relu(x)
# 3. dropout(防止过拟合)
x = self.dropout(x)
# 4. 第二层线性变换(降维):2048 → 512
x = self.w2(x)
return x
# 测试代码
def dm05_test_feedforward():
"""测试前馈全连接层"""
# 1. 创建前馈全连接层对象
my_ffn = FeedForward(d_model=512, d_ff=2048, dropout=0.1)
# 2. 创建输入张量 (batch=2, seq_len=4, d_model=512)
x = torch.randn(2, 4, 512)
# 3. 前向传播
output = my_ffn(x)
# 4. 打印形状
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
return output
if __name__ == "__main__":
dm05_test_feedforward()
代码说明
| 代码 | 说明 |
|---|---|
nn.Linear(d_model, d_ff) | 第一层,升维 512→2048 |
nn.Linear(d_ff, d_model) | 第二层,降维 2048→512 |
torch.relu(x) | ReLU 激活函数 |
nn.Dropout(dropout) | 随机失活 |
d_ff=2048 | 前馈全连接维度,比 d_model 大 |
6 重难点与易错提醒
- ❗重点:前馈全连接层包含两个线性层,先升维再降维。
- ❗重点:d_ff 一般比 d_model 大(如 2048 > 512)。
- ❗重点:作用是强化特征,对每个位置的词向量单独强化。
- ⚠️易错:ReLU 在两个线性层之间。
- 💡深入理解:类比参观大厂,升维提高眼界,降维回到原点但认知不同。
- 💡深入理解:两层是试验试出来的,不做三层、四层。
7 课堂问答精选
Q1:前馈全连接层的作用是什么?
A:强化特征,对每个位置的词向量单独强化。考虑注意力机制可能对复杂过程的拟合程度不够,增加两层网络来做。
Q2:前馈全连接层的结构是什么?
A:两个线性层,先升维(512→2048),中间用 ReLU 激活函数,再降维(2048→512)。公式:FFN(x) = max(0, xW1+b1)W2+b2。
Q3:d_ff 一般选择多少?
A:d_ff 一般比 d_model 大,如 2048 > 512。如果比 d_model 小,就没必要做这个操作了。
Q4:为什么是两层而不是三层、四层?
A:试验试出来的,两层效果最好。不做三层、四层。
8 本课小结
- 前馈全连接层:两个线性层,先升维再降维。
- 结构:Linear1(512→2048) → ReLU → Dropout → Linear2(2048→512)。
- 作用:强化特征。
- d_ff 一般比 d_model 大(2048 > 512)。
- 两层是试验试出来的。
9 延伸思考
- 残差连接和层规范化如何实现?
- 前馈全连接层如何与其他组件组合?