轻量级语音情绪识别:手工特征+深度集成模型在5个数据集上的系统对比

论文信息

  • 标题: Speech emotion recognition with light weight deep neural ensemble model using hand crafted features
  • 期刊: Scientific Reports, Vol. 15, 11824 (2025)
  • 作者: Chowdhury, J.H., Ramanna, S. & Kotecha, K.
  • 链接: https://doi.org/10.1038/s41598-025-95734-z
  • 发表日期: 2025年4月7日
  • 核心方法: 1D-CNN + CNN-BiLSTM 集成 + MFCC/ZCR/RMSE/Chroma-STFT 手工特征

核心创新

证明了轻量级手工特征+简单集成模型在5个标准数据集上超越复杂频谱图模型。这意味着在车载边缘设备上,不需要大规模预训练模型也能实现可靠的语音情绪识别。

关键数据:

  • RAVDESS准确率:89.33%
  • CREMA-D准确率:78.67%
  • 模型参数:<2M(vs Wav2Vec2的90M+)
  • 特征提取:4种经典手工特征(MFCC + ZCR + RMSE + Chroma STFT)

方法详解

1. 整体架构

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
import numpy as np
import librosa
from typing import Tuple

class SpeechFeatureExtractor:
"""
语音情绪识别手工特征提取器

论文: Scientific Reports, 2025
特征: MFCC(13) + ZCR(1) + RMSE(1) + Chroma STFT(12) = 27维

优势:
- 计算量小(vs 频谱图+CNN)
- 可在边缘设备实时运行
- 不需要预训练模型
"""

def __init__(self, sr=22050, n_mfcc=13, hop_length=512,
n_fft=2048, frame_length=2048):
self.sr = sr
self.n_mfcc = n_mfcc
self.hop_length = hop_length
self.n_fft = n_fft
self.frame_length = frame_length

def extract_mfcc(self, audio: np.ndarray) -> np.ndarray:
"""
提取MFCC特征(13维)

MFCC模拟人耳对频率的感知,是SER最常用的特征
"""
mfcc = librosa.feature.mfcc(
y=audio,
sr=self.sr,
n_mfcc=self.n_mfcc,
n_fft=self.n_fft,
hop_length=self.hop_length
)
# 取统计量:均值+标准差
mfcc_mean = np.mean(mfcc, axis=1)
mfcc_std = np.std(mfcc, axis=1)
return np.concatenate([mfcc_mean, mfcc_std]) # 26维

def extract_zcr(self, audio: np.ndarray) -> np.ndarray:
"""
提取过零率(1维)

ZCR反映语音的频率特征,可用于区分有声/无声段
"""
zcr = librosa.feature.zero_crossing_rate(
audio,
frame_length=self.frame_length,
hop_length=self.hop_length
)
return np.array([np.mean(zcr), np.std(zcr)]) # 2维

def extract_rmse(self, audio: np.ndarray) -> np.ndarray:
"""
提取均方根能量(1维)

RMSE反映语音的能量,与情绪强度相关
"""
rmse = librosa.feature.rms(
y=audio,
frame_length=self.frame_length,
hop_length=self.hop_length
)
return np.array([np.mean(rmse), np.std(rmse)]) # 2维

def extract_chroma(self, audio: np.ndarray) -> np.ndarray:
"""
提取Chroma STFT特征(12维)

Chroma反映音高类别,可捕捉语音中的音调变化
"""
chroma = librosa.feature.chroma_stft(
y=audio,
sr=self.sr,
n_fft=self.n_fft,
hop_length=self.hop_length
)
return np.concatenate([
np.mean(chroma, axis=1),
np.std(chroma, axis=1)
]) # 24维

def extract_all(self, audio: np.ndarray) -> np.ndarray:
"""
提取全部4种特征

Returns:
features: 54维特征向量 (26+2+2+24)
"""
mfcc = self.extract_mfcc(audio)
zcr = self.extract_zcr(audio)
rmse = self.extract_rmse(audio)
chroma = self.extract_chroma(audio)

return np.concatenate([mfcc, zcr, rmse, chroma])

def extract_realtime(self, audio_chunk: np.ndarray) -> np.ndarray:
"""
实时特征提取(用于车载部署)

Args:
audio_chunk: 音频块 (1-3秒)

Returns:
features: 54维特征
"""
# 确保音频长度一致
target_length = self.sr * 3 # 3秒
if len(audio_chunk) < target_length:
audio_chunk = np.pad(audio_chunk,
(0, target_length - len(audio_chunk)))
else:
audio_chunk = audio_chunk[:target_length]

return self.extract_all(audio_chunk)


# 车载噪声环境特征提取
class CabinNoiseRobustExtractor(SpeechFeatureExtractor):
"""
车舱噪声鲁棒特征提取器

扩展基础提取器,增加:
1. 谱减法降噪
2. VAD静音检测
3. 噪声自适应特征
"""

def spectral_subtraction(self, audio: np.ndarray,
noise_profile: np.ndarray = None) -> np.ndarray:
"""
谱减法降噪

Args:
audio: 带噪语音
noise_profile: 噪声样本(可选)
"""
if noise_profile is None:
# 取前10帧作为噪声估计
noise_profile = audio[:self.sr // 10]

# STFT
S_full = librosa.stft(audio, n_fft=self.n_fft,
hop_length=self.hop_length)
S_noise = librosa.stft(noise_profile, n_fft=self.n_fft,
hop_length=self.hop_length)

# 噪声功率谱估计
noise_power = np.mean(np.abs(S_noise) ** 2, axis=1, keepdims=True)

# 谱减
magnitude = np.abs(S_full) ** 2
clean_magnitude = np.maximum(magnitude - noise_power, 0)

# 重建
phase = np.angle(S_full)
clean_stft = np.sqrt(clean_magnitude) * np.exp(1j * phase)

return librosa.istft(clean_stft, hop_length=self.hop_length)


# 测试代码
if __name__ == "__main__":
extractor = CabinNoiseRobustExtractor(sr=22050)

# 模拟3秒语音
np.random.seed(42)
duration = 3
t = np.linspace(0, duration, 22050 * duration)
audio = 0.5 * np.sin(2 * np.pi * 200 * t) + 0.3 * np.random.randn(len(t))

# 提取特征
features = extractor.extract_all(audio)
print(f"特征维度: {features.shape}")
print(f"MFCC(均值+标准差): {features[:26].shape}")
print(f"ZCR: {features[26:28].shape}")
print(f"RMSE: {features[28:30].shape}")
print(f"Chroma: {features[30:].shape}")

print(f"\n总维度: {len(features)}")
print(f"模型参数量估计: ~1.8M (1D-CNN) + ~0.5M (BiLSTM) = ~2.3M")
print(f"vs Wav2Vec2: ~90M (95x更大)")

3. 集成模型代码

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
import torch
import torch.nn as nn

class CNN1D(nn.Module):
"""
1D卷积神经网络 - 模型1

输入: 54维特征向量
输出: 7类情绪概率
"""

def __init__(self, input_dim=54, num_classes=7,
hidden_dims=[128, 64, 32]):
super().__init__()

layers = []
prev_dim = input_dim
for dim in hidden_dims:
layers.extend([
nn.Conv1d(prev_dim, dim, kernel_size=3, padding=1),
nn.BatchNorm1d(dim),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Dropout(0.3)
])
prev_dim = dim

self.features = nn.Sequential(*layers)
self.classifier = nn.Linear(prev_dim, num_classes)

def forward(self, x):
# x: (B, 54, L) 其中L是时间步数
x = self.features(x)
x = x.mean(dim=2) # 全局平均池化
return self.classifier(x)


class CNNBiLSTM(nn.Module):
"""
CNN-BiLSTM 模型 - 模型2

CNN提取局部特征,BiLSTM捕捉时序依赖
"""

def __init__(self, input_dim=54, num_classes=7,
cnn_channels=[64, 128], lstm_hidden=64):
super().__init__()

# CNN层
self.cnn = nn.Sequential(
nn.Conv1d(input_dim, cnn_channels[0], kernel_size=3, padding=1),
nn.BatchNorm1d(cnn_channels[0]),
nn.ReLU(),
nn.MaxPool1d(2),

nn.Conv1d(cnn_channels[0], cnn_channels[1], kernel_size=3, padding=1),
nn.BatchNorm1d(cnn_channels[1]),
nn.ReLU(),
nn.MaxPool1d(2),
)

# BiLSTM层
self.bilstm = nn.LSTM(
input_size=cnn_channels[1],
hidden_size=lstm_hidden,
num_layers=2,
bidirectional=True,
batch_first=True,
dropout=0.3
)

# 分类头
self.classifier = nn.Sequential(
nn.Linear(lstm_hidden * 2, 64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, num_classes)
)

def forward(self, x):
# x: (B, 54, L)
x = self.cnn(x) # (B, C, L')
x = x.transpose(1, 2) # (B, L', C)

# BiLSTM
lstm_out, _ = self.bilstm(x) # (B, L', 2*H)

# 取最后时间步
out = lstm_out[:, -1, :]
return self.classifier(out)


class EnsembleSER:
"""
集成语音情绪识别

论文方法: 概率平均集成
模型1: 1D-CNN (~1.8M params)
模型2: CNN-BiLSTM (~0.5M params)
总计: ~2.3M params (vs Wav2Vec2 90M)
"""

def __init__(self, num_classes=7):
self.model1 = CNN1D(num_classes=num_classes)
self.model2 = CNNBiLSTM(num_classes=num_classes)
self.emotion_labels = [
'neutral', 'happy', 'sad', 'angry',
'fearful', 'disgust', 'surprised'
]

def predict(self, features):
"""
集成预测

Args:
features: (54, L) 特征序列

Returns:
dict: {emotion, confidence, probabilities}
"""
x = torch.FloatTensor(features).unsqueeze(0) # (1, 54, L)

with torch.no_grad():
p1 = torch.softmax(self.model1(x), dim=1)
p2 = torch.softmax(self.model2(x), dim=1)

# 概率平均
ensemble_prob = (p1 + p2) / 2
pred_idx = ensemble_prob.argmax(dim=1).item()

return {
'emotion': self.emotion_labels[pred_idx],
'confidence': ensemble_prob[0, pred_idx].item(),
'probabilities': {
self.emotion_labels[i]: ensemble_prob[0, i].item()
for i in range(len(self.emotion_labels))
}
}

4. 数据集对比

数据集 样本数 情绪类别 说话人数 准确率
RAVDESS 1,440 8 24 89.33%
TESS 2,800 7 2 95.71%
SAVEE 480 7 4 88.75%
CREMA-D 7,444 6 91 78.67%
EmoDB 535 7 10 91.59%

IMS开发启示

1. 边缘部署优先级

方案 参数量 准确率 延迟 部署平台
本文(手工特征+集成) ~2.3M 78-96% <50ms QCS8255 ✅
Wav2Vec2+分类头 ~90M 80-97% ~200ms 需GPU ❌
频谱图+ResNet ~25M 75-92% ~100ms 需NPU ⚠️
MFCC+SVM <0.1M 65-85% <10ms 任意 ✅

2. 车载情绪检测场景

场景 情绪 DMS动作 优先级
路怒症 angry + high arousal 警告+主动减速 🔴 P0
疲劳驾驶 sad + low arousal 疲劳警告 🔴 P0
焦虑/紧张 fearful + high arousal 舒缓提示 🟡 P1
开心/满意 happy + normal 无干预 🟢 P2
中性/平静 neutral 无干预 🟢 P2

3. 部署建议

优先级 功能 方案 周期
P0 基础SER(7类) MFCC+1D-CNN 3个月
P1 噪声鲁棒 谱减法+数据增强 6个月
P1 实时推理 ONNX量化部署 6个月
P2 环境感知 TG-EAT(需LLM) 12个月
P3 多模态融合 语音+面部+行为 18个月

性能对比

方法 参数量 RAVDESS CREMA-D 平均 边缘部署
本文(集成) 2.3M 89.33% 78.67% 85.0% ✅
Wav2Vec2微调 90M 91.2% 82.1% 86.6% ❌
频谱图+CNN 8M 87.5% 76.3% 81.9% ⚠️
MFCC+ SVM 0.1M 82.1% 71.5% 76.8% ✅
eGeMAPS+RF 0.05M 79.3% 68.2% 73.8% ✅

总结

轻量级手工特征+集成模型的价值在于:在车载边缘设备上,不需要90M参数的预训练模型也能实现85%准确率的语音情绪识别。

对IMS的3条可执行建议:

  1. Phase1用MFCC+1D-CNN——2.3M参数可在QCS8255上实时运行
  2. 谱减法预处理是车舱降噪第一步——简单但有效
  3. 集成概率平均比单模型提升3-5%——代价仅为2倍推理时间

论文链接: https://doi.org/10.1038/s41598-025-95734-z