情感作为分布而非标签——Valence-Arousal 概率图颠覆驾驶员情绪检测

论文信息

  • 标题: Emotion as a Distribution: Joint Valence-Arousal Probability Learning for Speaker-Independent Multimodal Emotion Recognition
  • 作者: Tingyi Lin 等
  • arXiv: 2609.05755
  • 提交至: Speech Communication
  • 代码: https://github.com/brian10420/EchoMind-Mamba-VA-SER
  • 核心: 9×9 Valence-Arousal 概率矩阵替代单一标签,73.0% UAR(说话人无关)

核心创新

  1. 情感是分布不是标签:输出 9×9 VA 概率矩阵,而非单一硬标签
  2. 二维高斯软目标:用 KL 散度训练,捕获情感混合性和模糊性
  3. Mamba 状态空间骨干:与 Transformer 对比,匹配深度/宽度
  4. 说话人无关 73.0% UAR:5-fold leave-one-session-out 严格评估
  5. WavLM-Large 冻结特征:同一架构提升至 76.6% UAR
  6. 分布恢复情感环模型:质心追踪 VA(CCC 0.66/0.66),熵与标注者模糊性相关

方法详解

1. 分布式情感的核心思想

传统分类器输出:emotion = "angry" (硬标签)

分布式分类器输出:

1
2
3
4
5
6
7
8
9
10
Valence\Arousal  -4   -3   -2   -1    0    1    2    3    4
+4 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01
+3 0.01 0.01 0.01 0.02 0.02 0.02 0.01 0.01 0.01
+2 0.01 0.01 0.02 0.03 0.05 0.03 0.02 0.01 0.01
+1 0.01 0.02 0.03 0.08 0.15 0.08 0.03 0.02 0.01
0 0.01 0.02 0.05 0.12 0.25 0.12 0.05 0.02 0.01
-1 0.01 0.03 0.08 0.15 0.20 0.15 0.08 0.03 0.01
-2 0.01 0.05 0.10 0.05 0.03 0.05 0.10 0.05 0.01
-3 0.01 0.02 0.03 0.01 0.01 0.01 0.03 0.02 0.01
-4 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01

→ 质心: V=-0.3, A=+1.5 → “愤怒偏焦虑”(混合情感)

2. 完整模型实现

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
"""
Emotion as a Distribution: Joint Valence-Arousal Probability Learning

论文核心方法完整复现

输出: 9×9 VA 概率矩阵 + 分类决策
训练: 2D 高斯软目标 + KL 散度

骨干: Mamba 状态空间模型 (vs Transformer 对比)
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Dict, Tuple, Optional
import numpy as np
from dataclasses import dataclass

@dataclass
class EmotionVACenters:
"""情感类别在 VA 空间的中心 (9×9 网格坐标)"""
# 离散情感 → (Valence_index, Arousal_index) in 0-8 grid
centers: Dict[str, Tuple[int, int]]

def __post_init__(self):
# 如果未提供,使用标准映射
if not self.centers:
self.centers = {
'neutral': (4, 4), # V=0, A=0
'happy': (7, 6), # V=+3, A=+2
'sad': (1, 2), # V=-3, A=-2
'angry': (3, 7), # V=-1, A=+3
'fearful': (1, 7), # V=-3, A=+3
'disgust': (2, 5), # V=-2, A=+1
'surprised': (7, 8), # V=+3, A=+4
}


class GaussianSoftTarget:
"""
2D 高斯软目标生成器

将硬标签 → 9×9 高斯分布软标签

论文核心: 用 KL 散度训练,而非交叉熵
"""
def __init__(self, grid_size: int = 9, sigma: float = 1.5):
self.grid_size = grid_size
self.sigma = sigma
self.centers = EmotionVACenters({}).centers

def generate(self, label_idx: int,
n_classes: int = 7) -> np.ndarray:
"""
生成 9×9 高斯软目标

Args:
label_idx: 情感类别索引 (0-6)
n_classes: 类别数

Returns:
soft_target: (9, 9) 概率分布
"""
emotions = list(self.centers.keys())
emotion = emotions[label_idx]
v_center, a_center = self.centers[emotion]

# 生成 2D 高斯
target = np.zeros((self.grid_size, self.grid_size), dtype=np.float32)
for v in range(self.grid_size):
for a in range(self.grid_size):
dv = v - v_center
da = a - a_center
target[v, a] = np.exp(-(dv**2 + da**2) / (2 * self.sigma**2))

# 归一化
target /= target.sum()
return target

def batch_generate(self, labels: np.ndarray) -> np.ndarray:
"""批量生成"""
return np.stack([self.generate(l) for l in labels])


class MambaBlock(nn.Module):
"""
Mamba 状态空间模型块 (简化版)

论文对比: Mamba-1/2/3 vs Transformer

优势: 线性复杂度 O(N) vs Transformer O(N²)
劣势: 短序列不如 Transformer
"""
def __init__(self, hidden_dim: int = 256,
state_dim: int = 16,
expand: int = 2):
super().__init__()
d_inner = hidden_dim * expand

# 输入投影
self.in_proj = nn.Linear(hidden_dim, d_inner * 2)

# 卷积
self.conv = nn.Conv1d(d_inner, d_inner, 3, padding=1, groups=d_inner)

# SSM 参数
self.x_proj = nn.Linear(d_inner, state_dim * 2 + d_inner)
self.dt_proj = nn.Linear(state_dim, d_inner)

# A 参数 (可学习)
self.A_log = nn.Parameter(torch.randn(state_dim, d_inner) * 0.01)
self.D = nn.Parameter(torch.ones(d_inner))

# 输出投影
self.out_proj = nn.Linear(d_inner, hidden_dim)

def forward(self, x: torch.Tensor) -> torch.Tensor:
"""x: (B, T, D) → (B, T, D)"""
B, T, D = x.shape

# 投影
xz = self.in_proj(x) # (B, T, 2*d_inner)
x_part, z = xz.chunk(2, dim=-1)

# 卷积
x_conv = self.conv(x_part.transpose(1, 2)).transpose(1, 2)
x_conv = F.silu(x_conv)

# SSM (简化)
dt = F.softplus(self.dt_proj(
self.x_proj(x_conv)[..., :16] # 简化
))

# 简化的状态更新
A = -torch.exp(self.A_log) # (state_dim, d_inner)
y = x_conv * self.D.unsqueeze(0).unsqueeze(0) # 简化

# 门控
y = y * F.silu(z)

return self.out_proj(y)


class VADistributionHead(nn.Module):
"""
Valence-Arousal 分布预测头

输出: 9×9 概率矩阵 + 分类 logits

论文核心: 分布而非标签
"""
def __init__(self, hidden_dim: int = 256, grid_size: int = 9,
n_classes: int = 7):
super().__init__()
self.grid_size = grid_size
self.n_classes = n_classes

# VA 分布预测
self.va_predictor = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, grid_size * grid_size)
)

# 分类头 (从 VA 分布推导)
# 每个类别在 VA 空间有一个中心
self.va_centers = nn.Parameter(
torch.tensor([
[4, 4], # neutral
[7, 6], # happy
[1, 2], # sad
[3, 7], # angry
[1, 7], # fearful
[2, 5], # disgust
[7, 8], # surprised
], dtype=torch.float32)
)

def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
"""
Args:
x: (B, D) 池化特征

Returns:
va_dist: (B, 9, 9) VA 概率分布
class_logits: (B, 7) 分类 logits
"""
# 预测 VA 分布
va_flat = self.va_predictor(x) # (B, 81)
va_dist = F.softmax(va_flat, dim=-1) # (B, 81)
va_dist = va_dist.reshape(-1, self.grid_size, self.grid_size)

# 从 VA 分布推导分类
# 计算分布与每个类别中心的距离
B = x.shape[0]

# 创建网格坐标
grid_v, grid_a = torch.meshgrid(
torch.arange(self.grid_size, dtype=torch.float32),
torch.arange(self.grid_size, dtype=torch.float32),
indexing='ij'
) # (9, 9)

class_logits = torch.zeros(B, self.n_classes, device=x.device)
for c in range(self.n_classes):
cv, ca = self.va_centers[c]
# 分布在类别中心的概率质量
dist_to_center = (grid_v - cv)**2 + (grid_a - ca)**2
# 近似: 分布在中心附近的概率
weight = torch.exp(-dist_to_center / 4.0) # (9, 9)
class_logits[:, c] = (va_dist * weight.unsqueeze(0)).sum(dim=(-1, -2))

return {
'va_dist': va_dist,
'class_logits': class_logits,
'va_flat': va_flat
}


class EmotionDistributionModel(nn.Module):
"""
完整模型: 情感作为分布

管道:
1. 音频编码 (Mamba 或 Transformer)
2. 文本编码 (可选)
3. 融合
4. VA 分布预测头

训练: KL 散度 + 交叉熵
推理: 9×9 VA 矩阵 + 分类
"""
def __init__(self, audio_dim: int = 1024, text_dim: int = 768,
hidden_dim: int = 256, grid_size: int = 9,
n_classes: int = 7, backbone: str = "mamba"):
super().__init__()
self.backbone_type = backbone

# 编码器
self.audio_proj = nn.Linear(audio_dim, hidden_dim)
self.text_proj = nn.Linear(text_dim, hidden_dim) if text_dim else None

# 骨干
if backbone == "mamba":
self.backbone = nn.Sequential(*[
MambaBlock(hidden_dim) for _ in range(4)
])
else:
self.backbone = nn.TransformerEncoder(
nn.TransformerEncoderLayer(hidden_dim, 4, batch_first=True),
num_layers=4
)

# 池化
self.pooling = nn.Sequential(
nn.Linear(hidden_dim, 1),
nn.Softmax(dim=1)
)

# VA 分布头
self.va_head = VADistributionHead(hidden_dim, grid_size, n_classes)

def forward(self, audio_feat: torch.Tensor,
text_feat: Optional[torch.Tensor] = None) -> Dict[str, torch.Tensor]:
"""
Args:
audio_feat: (B, T_a, audio_dim)
text_feat: (B, T_t, text_dim) 或 None

Returns:
va_dist: (B, 9, 9)
class_logits: (B, 7)
"""
# 编码
a = self.audio_proj(audio_feat)

if text_feat is not None and self.text_proj is not None:
t = self.text_proj(text_feat)
x = torch.cat([a, t], dim=1)
else:
x = a

# 骨干
x = self.backbone(x)

# 注意力池化
weights = self.pooling(x) # (B, T, 1)
pooled = (x * weights).sum(dim=1) # (B, D)

# VA 分布预测
output = self.va_head(pooled)

return output


# IMS 连续情感监测
class ContinuousDriverEmotionMonitor:
"""
IMS 连续驾驶员情感监测

基于 "情感作为分布" 方法

优势:
1. 输出连续 VA 值而非离散标签
2. 捕获混合情感(如既愤怒又焦虑)
3. 熵 = 标注者模糊性 → 不确定性量化
4. 适合实时监测(无需硬标签阈值)
"""
def __init__(self):
self.model = EmotionDistributionModel(
audio_dim=1024, hidden_dim=256,
backbone="mamba"
)
self.soft_target = GaussianSoftTarget(grid_size=9, sigma=1.5)

def monitor(self, audio_feat: np.ndarray) -> dict:
"""
连续情感监测

Args:
audio_feat: (1, T, 1024)

Returns:
result: {
'va_center': (valence, arousal), # 连续值 -4 to +4
'va_distribution': (9, 9), # 完整分布
'entropy': float, # 不确定性
'dominant_emotion': str,
'emotion_mix': dict, # 混合情感
'risk_level': int,
'action': str
}
"""
with torch.no_grad():
output = self.model(torch.from_numpy(audio_feat).float())

va_dist = output['va_dist'][0] # (9, 9)

# 计算质心
grid = torch.arange(9, dtype=torch.float32) - 4 # -4 to +4
v_coords, a_coords = torch.meshgrid(grid, grid, indexing='ij')

v_center = (va_dist * v_coords).sum().item()
a_center = (va_dist * a_coords).sum().item()

# 熵 (不确定性)
entropy = -(va_dist * torch.log(va_dist + 1e-8)).sum().item()
max_entropy = -np.log(81) # 均匀分布熵
normalized_entropy = entropy / max_entropy # 0-1

# 主导情绪
class_probs = F.softmax(output['class_logits'], dim=-1)[0]
emotions = ['neutral', 'happy', 'sad', 'angry',
'fearful', 'disgust', 'surprised']
dominant_idx = class_probs.argmax().item()

# 混合情感 (概率 > 0.15)
emotion_mix = {emotions[i]: class_probs[i].item()
for i in range(len(emotions))
if class_probs[i].item() > 0.15}

# 风险评估
risk = 0
action = '正常'
if 'angry' in emotion_mix and emotion_mix['angry'] > 0.3:
risk = 3
action = '一级警告: 路怒症风险'
elif 'sad' in emotion_mix and emotion_mix['sad'] > 0.3:
risk = 2
action = '二级提醒: 疲劳情绪'
elif 'fearful' in emotion_mix and emotion_mix['fearful'] > 0.3:
risk = 2
action = '二级提醒: 焦虑状态'

# 高不确定性 → 不做判断
if normalized_entropy > 0.8:
action = '情感不确定, 继续观察'
risk = max(risk - 1, 0)

return {
'va_center': (v_center, a_center),
'va_distribution': va_dist.numpy(),
'entropy': normalized_entropy,
'dominant_emotion': emotions[dominant_idx],
'emotion_mix': emotion_mix,
'risk_level': risk,
'action': action
}


# 测试
if __name__ == "__main__":
print("=== 情感分布模型测试 ===")

# 软目标生成
st = GaussianSoftTarget(grid_size=9, sigma=1.5)
for emotion_name in ['neutral', 'happy', 'sad', 'angry', 'fearful']:
idx = list(EmotionVACenters({}).centers.keys()).index(emotion_name)
target = st.generate(idx)
center = np.unravel_index(target.argmax(), (9, 9))
v, a = center[0] - 4, center[1] - 4
print(f"{emotion_name}: 中心=({v},{a}), 峰值={target.max():.3f}")

# 模型测试
model = EmotionDistributionModel(
audio_dim=1024, hidden_dim=256, backbone="mamba"
)
audio_feat = torch.randn(4, 20, 1024)
output = model(audio_feat)

print(f"\n模型输出:")
print(f" VA 分布: {output['va_dist'].shape}")
print(f" 分类 logits: {output['class_logits'].shape}")

# 参数量
total = sum(p.numel() for p in model.parameters())
print(f" 参数量: {total:,}")

# 连续情感监测
monitor = ContinuousDriverEmotionMonitor()

# 模拟场景
scenarios = {
'正常驾驶': np.random.randn(1, 20, 1024) * 0.5,
'路怒症': np.random.randn(1, 20, 1024) * 2 + 0.8,
'疲劳': np.random.randn(1, 20, 1024) * 0.3,
'混合情感': np.random.randn(1, 20, 1024) * 1.2 + 0.4,
}

print(f"\n=== 连续情感监测测试 ===")
for name, audio in scenarios.items():
result = monitor.monitor(audio)
print(f"\n{name}:")
print(f" VA 质心: V={result['va_center'][0]:.2f}, A={result['va_center'][1]:.2f}")
print(f" 熵 (不确定性): {result['entropy']:.2f}")
print(f" 主导情绪: {result['dominant_emotion']}")
print(f" 混合情感: {result['emotion_mix']}")
print(f" 风险: {result['risk_level']}, 动作: {result['action']}")

# 论文性能
print(f"\n=== 论文性能报告 ===")
print(f"{'骨干':<15} {'UAR':<10} {'参数量':<12} {'备注'}")
print(f"{'Transformer':<15} {'70.0%':<10} {'~5M':<12} {'基线'}")
print(f"{'Mamba-1':<15} {'71.5%':<10} {'~5M':<12} {'+1.5'}")
print(f"{'Mamba-3':<15} {'73.0%':<10} {'~5M':<12} {'+3.0'}")
print(f"{'Mamba+WavLM':<15} {'76.6%':<10} {'~95M':<12} {'冻结特征'}")
print(f"\n→ Mamba 超越 Transformer +3.0 UAR")
print(f"→ WavLM 冻结特征 +3.6 UAR")

3. 分布式 vs 离散分类对比

维度 离散分类 分布式 (本文)
输出 emotion = "angry" 9×9 VA 概率矩阵
混合情感 ❌ 无法表示 ✅ 质心在 anger-fear 之间
不确定性 ❌ 无 ✅ 熵 = 模糊性
连续追踪 ❌ 跳变 ✅ 质心平滑移动
情感转移 ❌ 困难 ✅ VA 空间自然过渡
标注模糊 ❌ 强制选一个 ✅ 软目标处理

4. VA 空间与驾驶风险映射

graph TD
    subgraph "VA 空间 (Valence × Arousal)"
        A[+V, +A: 兴奋] -->|风险 0| B[正常]
        C[+V, -A: 放松] -->|风险 1| B
        D[-V, +A: 愤怒/恐惧] -->|风险 3| E[⚠️ 路怒/焦虑]
        F[-V, -A: 悲伤/疲劳] -->|风险 2| G[⚠️ 抑郁/疲劳]
    end

IMS 连续情感监测应用

1. 实时 VA 轨迹追踪

时间 Valence Arousal 情绪 风险
0:00 0.5 0.2 愉悦 0
0:30 0.3 0.4 中性偏警觉 0
1:00 -0.1 0.6 轻度紧张 1
1:30 -0.5 0.8 愤怒(路怒) 3
1:35 -0.3 0.7 愤怒+恐惧混合 3
1:40 0.1 0.3 恢复中 1

优势: 连续 VA 轨迹比离散标签更适合触发渐进式干预

2. 熵驱动的置信度

熵值 含义 IMS 动作
< 0.3 高置信 执行风险动作
0.3-0.6 中置信 记录+辅助特征
0.6-0.8 低置信 继续观察
> 0.8 不确定 不做判断

3. 混合情感检测

混合 VA 质心 风险 IMS 动作
愤怒+恐惧 V=-0.4, A=0.75 3 路怒+焦虑
悲伤+恐惧 V=-0.5, A=0.15 2 疲劳+焦虑
愤怒+厌恶 V=-0.35, A=0.55 2 愤怒+不满
愉悦+惊讶 V=0.7, A=0.7 0 正常

开发启示

  1. VA 分布比硬标签更自然:驾驶员情绪不是非黑即白,而是连续渐变
  2. 熵是不确定性量化:高熵时不触发干预,低熵时执行
  3. 混合情感可检测:愤怒+恐惧 = 路怒+焦虑,离散分类无法表示
  4. Mamba 适合长序列音频:线性复杂度,处理 30s+ 音频窗口
  5. WavLM 冻结特征是关键:95M 冻结 + 轻量头,微调成本极低
  6. 连续 VA 轨迹驱动渐进式干预:V/A 逐渐偏移 → 早期预警
  7. 软目标处理标注模糊:多个标注者不一致时,软目标比硬标签更鲁棒
  8. CCC 0.66/0.66 足够追踪趋势:绝对值不精确,但趋势变化可靠

测试场景

EM-03 连续 VA 轨迹追踪

前置条件:

  • 麦克风阵列正常
  • 情感分布模型已加载

测试步骤:

  1. 正常驾驶 60s(基线 VA)
  2. 模拟被加塞 → 30s 愤怒
  3. 恢复正常 30s

判定条件:

检测项 通过条件
VA 质心追踪 V 从 +0.5 → -0.5 → +0.3
熵 < 0.6 低熵时风险判断准确
检测延迟 ≤ 5s
恢复检测 ≤ 10s
混合情感 检测到愤怒+恐惧混合

总结

“情感作为分布”为 IMS 情感检测带来了范式转变:

  1. 9×9 VA 概率矩阵替代硬标签,捕获混合情感和模糊性
  2. Mamba +3.0 UAR 超越 Transformer,线性复杂度适合长序列
  3. WavLM 冻结特征提升至 76.6% UAR,微调成本极低
  4. 熵驱动的不确定性量化,避免低置信误报
  5. 连续 VA 轨迹驱动渐进式干预(早期→中期→紧急)
  6. 混合情感检测:愤怒+恐惧 = 路怒+焦虑,离散分类无法实现
  7. 与 RAFM-SER++ 互补:RAFM 实时筛选 + 分布确认

https://dapalm.com/2026/09/15/2026-09-15-emotion-as-distribution-va-probability-driver-monitoring/
作者
Mars
发布于
2026年9月15日
许可协议