LHUC: Learning Hidden Unit Contributions for Unsupervised Acoustic Model Adaptation

基本信息

字段 内容
标题 Learning Hidden Unit Contributions for Unsupervised Acoustic Model Adaptation
作者 Pawel Swietojanski, Jinyu Li, Steve Renals
机构 University of Edinburgh; Microsoft Research
年份 2016 (IEEE/ACM TASLP’16)
方向 Hidden Unit Gating, Speaker Adaptation (原始);推荐侧 → 用户个性化特征门控
场景 声学模型说话人自适应(原始);快手 PPNet 借鉴用于多用户/多场景个性化推荐
arXiv https://arxiv.org/abs/1601.02828

源用于声学模型中提升不同人说话的语音识别效果

在推荐领域最开始被快手PPNet使用,用于多用户个性化推荐/多场景推荐

PPNet将用户特征和其它特征作为输入,通过门控机制动态控制DNN的隐藏层输出,为每个用户学习个性化偏置参数,但是参数更新时其它特征不接受门控网络的反传梯度,来减少门控对现有Embedding收敛产生的影响,

img

易混淆点

  • 左侧DNN输入:剔除 gating的bias_id外,其他剩余特征对应emb拼接后形成的input_emb_layer
  • 右侧Gate NN输入:所有特征emb(包括gating专属的bias_id特征 + 其余DNN特征)。但梯度更新仅仅更新右侧gating的bias_id特征emb,不更新左侧DNN的特征emb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import torch
import torch.nn as nn

class LHUCLayer(nn.Module):
"""
LHUC (Learning Hidden Unit Contributions) Layer
核心功能:根据用户个性化特征,学习对共享隐层单元的缩放权重。
"""
def __init__(self, hidden_dim, user_dim, scaling_func='2sigmoid'):
"""
:param hidden_dim: 共享隐层的维度(需要被缩放的维度)
:param user_dim: 用户个性化特征的维度(Gate的输入维度)
:param scaling_func: 缩放函数的类型,'2sigmoid' 是推荐系统中常用的(范围 0~2),
'exp' 或 'sigmoid' 也是可选方案。
"""
super(LHUCLayer, self).__init__()
self.scaling_func = scaling_func

# Gate 网络:将用户特征映射到隐层维度
# 在原始论文中,每个 Speaker 只有一组参数 r;
# 在推荐系统 PPNet 中,通常通过一个 Linear 层从 User Embedding 生成 r。
self.gate = nn.Linear(user_dim, hidden_dim)

# 初始化:通常希望初始时 scale 接近 1,即不改变原始网络输出
# 如果使用 2*sigmoid,bias 初始化为 0 时,输出为 1.0 (sigmoid(0)=0.5, 0.5*2=1)
nn.init.zeros_(self.gate.bias)
nn.init.xavier_uniform_(self.gate.weight)

def forward(self, hidden_input, user_feature):
"""
:param hidden_input: (Batch, Hidden_Dim) 共享网络的隐层输出
:param user_feature: (Batch, User_Dim) 用户个性化特征 (如 User ID Embedding)
"""
# 1. 计算原始的 gate 输出 r
gate_out = self.gate(user_feature)

# 2. 应用重参数化函数生成 Scaling Factor (xi)
if self.scaling_func == '2sigmoid':
# 范围 (0, 2),初始值接近 1
scale = 2.0 * torch.sigmoid(gate_out)
elif self.scaling_func == 'sigmoid':
# 范围 (0, 1)
scale = torch.sigmoid(gate_out)
elif self.scaling_func == 'exp':
# 范围 (0, +inf)
scale = torch.exp(gate_out)
else:
scale = gate_out # Identity

# 3. 逐元素相乘:re-weighting
return hidden_input * scale

class SimplePPNet(nn.Module):
"""
一个集成 LHUC 的简单推荐模型 (类似 PPNet 结构)
结构:Embedding -> MLP (with LHUC) -> Output
"""
def __init__(self, feature_dim, user_num, user_emb_dim, hidden_units=[64, 32]):
super(SimplePPNet, self).__init__()

# 模拟用户 ID Embedding,作为 LHUC 的个性化输入来源
self.user_embedding = nn.Embedding(user_num, user_emb_dim)

# 共享的主体 MLP 网络
self.fcs = nn.ModuleList()
self.lhuc_layers = nn.ModuleList()

input_dim = feature_dim
for unit in hidden_units:
# 1. 共享的全连接层
self.fcs.append(nn.Linear(input_dim, unit))
# 2. 对应的 LHUC 层
self.lhuc_layers.append(LHUCLayer(unit, user_emb_dim))
input_dim = unit

self.activation = nn.ReLU()
self.final = nn.Linear(input_dim, 1) # 输出层 (CTR 预测)
self.sigmoid = nn.Sigmoid()

def forward(self, dense_features, user_ids):
# 获取用户个性化特征
user_emb = self.user_embedding(user_ids)

curr_out = dense_features

# 逐层前向传播
for fc, lhuc in zip(self.fcs, self.lhuc_layers):
# A. 共享网络计算
curr_out = fc(curr_out)
curr_out = self.activation(curr_out)

# B. LHUC 个性化加权 (关键步骤)
# 将共享层的激活输出 与 用户Embedding 生成的权重 相乘
curr_out = lhuc(curr_out, user_emb)

logits = self.final(curr_out)
return self.sigmoid(logits)

# ==========================================
# 测试用例 (Test Case)
# ==========================================
def test_lhuc_implementation():
print("=== 开始测试 LHUC 实现 ===")

# 1. 超参数设置
BATCH_SIZE = 4
FEATURE_DIM = 10 # 输入特征维度
USER_NUM = 100 # 用户总数
USER_EMB_DIM = 8 # 用户 Embedding 维度
HIDDEN_UNITS = [16, 8] # MLP 结构

# 2. 实例化模型
model = SimplePPNet(FEATURE_DIM, USER_NUM, USER_EMB_DIM, HIDDEN_UNITS)
print(f"模型结构:\n{model}")

# 3. 构造模拟数据
# 随机生成 Dense 特征
dummy_features = torch.randn(BATCH_SIZE, FEATURE_DIM)
# 随机生成用户 ID
dummy_user_ids = torch.randint(0, USER_NUM, (BATCH_SIZE,))

print(f"\n输入数据形状: Feature {dummy_features.shape}, UserID {dummy_user_ids.shape}")

# 4. 前向传播测试
try:
output = model(dummy_features, dummy_user_ids)
print(f"前向传播输出形状: {output.shape}")
print(f"输出值示例: {output.detach().view(-1).numpy()}")
except Exception as e:
print(f"前向传播失败: {e}")
return

# 5. 反向传播测试 (验证梯度是否连通)
try:
loss = output.mean()
loss.backward()
print("反向传播成功,梯度正常计算。")

# 检查 LHUC 层参数是否有梯度
lhuc_grad = model.lhuc_layers[0].gate.weight.grad
if lhuc_grad is not None:
print(f"LHUC Gate 梯度范数: {lhuc_grad.norm().item():.4f}")
else:
print("警告: LHUC 层没有梯度!")

except Exception as e:
print(f"反向传播失败: {e}")

if __name__ == "__main__":
test_lhuc_implementation()