req:
如何理解嵌入向量用于构建一个微型MLP的参数(权重和偏置),一般来说MLP的权重是训练得到的
res:
你问得非常准确,这是理解CAN的关键一步。
在普通MLP里,权重矩阵 W 和偏置向量 b 确实是“可训练的张量”,它们与输入无关,只在反向传播时被更新。
CAN的做法是:把“权重/偏置”本身变成由另一个嵌入向量
即时“生成”的函数,而不再是一团独立的可训练参数。
下面分三步把这件事拆开讲清楚。
- 普通MLP vs. CAN-micro-MLP
普通MLP(以1层、输入dim=d₁、输出dim=d₂为例)
W ∈ ℝ^{d₂×d₁}, b ∈ ℝ^{d₂}
→ 训练过程中直接更新 W、b
→ 同一个 W、b 对任何样本都不变
CAN-micro-MLP
W、b不是自由参数,而是由“诱导特征”A的嵌入向量
e_A ∈ ℝ^{D} 经过一个“生成器”实时拼出来的:
[W | b] = reshape( MLP_gen(e_A) )
其中 MLP_gen 是另一套共享的小网络,它的输出长度
= d₂×d₁ + d₂,reshaped 后变成 W 和 b。
于是
- 不同的 A 会给出不同的 (W, b);
- 同一个 A 在任何样本上都给出相同的 (W, b);
- 训练时我们只更新 MLP_gen 的少量参数,而不用存
N² 个独立的 W、b。
- 为什么要这么做?
- 把“特征组合”的容量从“显式存储”变成“函数生成”,
参数空间从 O(N²D) 降到 O(ND′)。 - 生成的 (W, b) 仍然可以随着训练迭代而变——
只是它们的变化是通过更新 MLP_gen 间接实现的。 - 这样既能像笛卡尔积那样“给每对组合独立参数”,
又不需要真的存下所有组合。
- 一个极简的 PyTorch 伪代码
class CoActionUnit(nn.Module):def __init__(self, d_emb, d_hidden, d_out):super().__init__()# 生成器:把 e_A 映射成 [W|b]self.gen = nn.Sequential(nn.Linear(d_emb, 128),nn.Tanh(),nn.Linear(128, d_hidden*d_out + d_out) # W+b)self.d_hidden = d_hiddenself.d_out = d_outdef forward(self, e_a, e_b):# e_a: [B, d_emb] 诱导侧# e_b: [B, d_emb] 输入侧params = self.gen(e_a) # [B, d_h*d_o + d_o]W, b = torch.split(params, [self.d_hidden*self.d_out, self.d_out], dim=1)W = W.view(-1, self.d_out, self.d_hidden) # [B, d_o, d_h]out = torch.bmm(W, e_b.unsqueeze(2)).squeeze(2) + breturn out # [B, d_o]
- 只有
self.gen里的权重会被反向传播更新; - 对不同的诱导特征 ID,我们查表取出对应的 e_a,
然后即时生成它“私有”的 W、b,用完即弃。
一句话总结
CAN 把“为每对特征存一套 W、b”换成“用一个小网络
根据特征 A 的嵌入当场算出 W、b”,从而把参数爆炸
问题转化为“一个共享的小网络”的拟合问题,而这个小
网络的权重才是我们真正训练的对象。