DeEAR开源模型教程:从零开始复现DeEAR训练流程(含wav2vec2微调脚本)

1. 引言:为什么需要语音情感识别?

你有没有想过,为什么有时候听一段语音,即使不看说话人的表情,也能感受到他的情绪?是开心、愤怒、悲伤还是平静?这背后就是语音中蕴含的情感信息在起作用。对于机器来说,理解语音中的情感一直是个难题,但DeEAR(Deep Emotional Expressiveness Recognition)这个开源项目,为我们提供了一个强大的工具。

简单来说,DeEAR是一个基于wav2vec2模型的深度语音情感表达分析系统。它能从一段语音中,分析出三个关键的情感维度:唤醒度(激动还是平静)、自然度(听起来自不自然)、韵律(说话的节奏感)。这有什么用呢?想象一下,智能客服能根据你的语气调整回复策略,在线教育平台能判断学生听课时的情绪状态,甚至心理辅导应用能通过语音分析用户的情绪变化。

本教程将手把手带你从零开始,复现DeEAR的完整训练流程。我会提供详细的wav2vec2微调脚本,并解释每一步背后的原理。即使你之前没有接触过语音情感识别,也能跟着这篇教程,搭建起自己的语音情感分析模型。

2. 环境准备与数据获取

2.1 搭建基础开发环境

工欲善其事,必先利其器。我们先来准备好运行DeEAR所需的环境。推荐使用Python 3.8及以上版本,并创建一个独立的虚拟环境,避免包版本冲突。

# 创建并激活虚拟环境(以conda为例)
conda create -n deear_env python=3.11
conda activate deear_env

# 安装PyTorch(请根据你的CUDA版本选择对应命令)
# 例如,CUDA 11.8版本
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 安装核心依赖库
pip install transformers==4.36.0
pip install datasets==2.16.0
pip install evaluate==0.4.1
pip install accelerate==0.25.0
pip install librosa==0.10.1
pip install scikit-learn==1.3.2
pip install gradio==4.13.0  # 用于后续的可视化界面

如果你使用的是预配置的镜像环境(比如CSDN星图镜像),通常这些依赖已经安装好了,可以直接通过启动脚本运行。

# 在镜像环境中快速启动DeEAR服务
/root/DeEAR_Base/start.sh
# 或者直接运行
python /root/DeEAR_Base/app.py

服务启动后,在浏览器中访问 http://localhost:7860 就能看到DeEAR的交互界面了。

2.2 获取情感语音数据集

模型训练离不开数据。对于语音情感识别,有几个公开可用的经典数据集:

  1. CREMA-D:包含7,442条由91位演员录制的语音片段,标注了6种基本情绪(高兴、悲伤、愤怒、恐惧、厌恶、中性)以及情感强度。
  2. RAVDESS:包含24位专业演员的语音和视频,涵盖8种情绪(平静、高兴、悲伤、愤怒、恐惧、惊讶、厌恶)。
  3. IEMOCAP:一个多模态数据集,包含视频、语音和文本,情感标注更为细致。
  4. EmoDB:德语情感语音数据库,包含7种情绪。

本教程以CREMA-D数据集为例,因为它规模适中、标注清晰,且易于获取。你可以从官方渠道申请下载,或者在一些开源数据平台找到它。

下载后,数据集的结构通常如下:

CREMA-D/
├── AudioWAV/               # 存放所有WAV格式的音频文件
│   ├── 1001_DFA_ANG_XX.wav
│   ├── 1001_DFA_DIS_XX.wav
│   └── ...
├── VideoFlash/             # 视频文件(本教程用不到)
└── processedResults/       # 可能包含一些处理结果

我们需要重点关注的是音频文件(.wav)和它们对应的情感标签。CREMA-D的文件名就包含了标签信息,例如1001_DFA_ANG_XX.wav

  • 1001: 说话人ID
  • DFA: 句子内容代码
  • ANG: 情感标签(ANG=愤怒)
  • XX: 情感强度(HI=高,LO=低,MD=中,XX=未指定/中性)

3. 理解DeEAR的核心:wav2vec2模型

在开始写代码之前,我们需要先搞明白DeEAR的“大脑”是什么。它基于一个叫做wav2vec2的模型,这是Facebook AI(现Meta AI)在2020年提出的一个里程碑式的工作。

3.1 wav2vec2是如何“听”声音的?

你可以把wav2vec2想象成一个非常聪明的“听觉学徒”。它的学习过程分为两步:

  1. 无监督预训练(自学阶段):模型被扔进海量的、没有标注的语音数据里。它的任务是玩一个“填空游戏”:随机把一段连续语音中的某些小片段(比如0.01秒)盖住,然后尝试根据上下文猜出被盖住的部分是什么。通过这个游戏,它学会了语音中最基本的构成单元(称为“语音单元”或“声学特征”),比如不同的音素、音调变化等。这就像小孩在学会认字前,先大量听大人说话,潜移默化地掌握了语言的发音规律。

  2. 有监督微调(专项培训):有了强大的“听觉基础”后,我们再给它一个有标注的任务,比如情感分类。我们在预训练好的模型后面加一个简单的分类头(可以理解为一个答题卡),然后用CREMA-D这样标注了“高兴”、“悲伤”的数据集去训练它。这时,模型会调整参数,学会把之前学到的通用语音特征,映射到具体的情感标签上。

为什么用wav2vec2做情感识别?

  • 能力强:它在语音识别等任务上已经证明了其强大的特征提取能力,这些特征同样适用于捕捉情感相关的声学线索(如音高、语速、能量)。
  • 效率高:相比于训练一个模型从零开始识别情感,基于预训练的wav2vec2进行微调,所需的数据量更少,训练时间更短,效果通常更好。
  • 通用性好:同一个预训练模型,可以通过微调适应不同的下游任务(如语音识别、语音情感识别、说话人识别等)。

3.2 DeEAR的三维情感分析

DeEAR没有简单地做“高兴/悲伤”这样的单一分类,而是采用了更细腻的三维分析法:

维度 它关心的是什么? 举个例子
唤醒度 (Arousal) 语音的“能量”水平,激动程度。 体育解说(高唤醒) vs 睡前故事(低唤醒)
自然度 (Nature) 语音听起来是否自然、流畅,像真人日常说话。 自然对话(自然) vs 机器人朗读或极度紧张的发言(不自然)
韵律 (Prosody) 语音的节奏、重音和语调变化。 朗诵诗歌(富有韵律) vs 平淡地念说明书(平淡)

这种分析方法的好处是,它能更全面、更连续地描述情感状态,而不是非此即彼的标签。例如,一段语音可能“高度唤醒但不自然”(比如激动的结巴),或者“自然但缺乏韵律”(比如平淡的叙述)。

4. 从零开始:数据预处理与特征提取

现在,我们开始动手。第一步是把原始的音频文件,处理成模型能“吃”的格式。

4.1 加载并解析数据集

我们需要写一个脚本来读取CREMA-D的音频文件,并根据文件名解析出情感标签,同时为DeEAR任务构造三维标签。

import os
import pandas as pd
import librosa
import torchaudio
from pathlib import Path

# 情感标签映射字典 (根据CREMA-D文件名)
EMOTION_MAP = {
    'ANG': 'anger',
    'DIS': 'disgust', 
    'FEA': 'fear',
    'HAP': 'happy',
    'NEU': 'neutral',
    'SAD': 'sadness'
}

# 为DeEAR任务构造三维标签的简单规则(示例,实际应根据你的标注或启发式规则)
def construct_deear_labels(emotion, intensity):
    """
    根据基本情感和强度,构造唤醒度、自然度、韵律的伪标签。
    这是一个简化示例,真实项目需要更精细的规则或人工标注。
    """
    label_map = {
        'arousal': 0.0,  # 0-1之间的值,表示低到高
        'nature': 0.0,   # 0-1之间的值,表示不自然到自然
        'prosody': 0.0   # 0-1之间的值,表示平淡到富有韵律
    }
    
    # 示例规则:愤怒、高兴、恐惧通常唤醒度高
    if emotion in ['anger', 'happy', 'fear']:
        label_map['arousal'] = 0.8
    elif emotion == 'neutral':
        label_map['arousal'] = 0.3
    else:
        label_map['arousal'] = 0.5
        
    # 示例规则:中性、高兴的语音通常更自然
    if emotion in ['neutral', 'happy']:
        label_map['nature'] = 0.9
    elif emotion in ['disgust', 'fear']: # 厌恶、恐惧可能不自然
        label_map['nature'] = 0.4
    else:
        label_map['nature'] = 0.7
        
    # 示例规则:悲伤、愤怒的语音可能更有韵律变化
    if emotion in ['sadness', 'anger']:
        label_map['prosody'] = 0.8
    else:
        label_map['prosody'] = 0.6
        
    # 根据强度微调
    if intensity == 'HI':
        label_map['arousal'] = min(1.0, label_map['arousal'] + 0.15)
    elif intensity == 'LO':
        label_map['arousal'] = max(0.0, label_map['arousal'] - 0.15)
        
    return label_map

def load_cremad_dataset(data_dir):
    """
    加载CREMA-D数据集,并构建DataFrame。
    """
    audio_dir = Path(data_dir) / "AudioWAV"
    data = []
    
    for audio_path in audio_dir.glob("*.wav"):
        filename = audio_path.stem
        parts = filename.split('_')
        
        if len(parts) >= 4:
            speaker_id = parts[0]
            sentence_code = parts[1]
            emotion_code = parts[2]
            intensity_code = parts[3]
            
            emotion = EMOTION_MAP.get(emotion_code, 'unknown')
            
            # 构造DeEAR三维标签(这里用启发式规则,实际应用需调整)
            deear_labels = construct_deear_labels(emotion, intensity_code)
            
            data.append({
                'file_path': str(audio_path),
                'speaker_id': speaker_id,
                'emotion': emotion,
                'intensity': intensity_code,
                'arousal': deear_labels['arousal'],
                'nature': deear_labels['nature'],
                'prosody': deear_labels['prosody']
            })
    
    df = pd.DataFrame(data)
    print(f"成功加载 {len(df)} 条音频样本。")
    print(df[['emotion', 'arousal', 'nature', 'prosody']].head())
    return df

# 使用示例
data_dir = "/path/to/your/CREMA-D"  # 替换为你的数据集路径
df = load_cremad_dataset(data_dir)

4.2 音频预处理与特征标准化

wav2vec2模型对输入音频有特定的要求:单声道、16kHz采样率。我们需要将音频统一处理成这个格式。

from transformers import Wav2Vec2FeatureExtractor
import torch
from datasets import Dataset, Audio

# 加载wav2vec2的特征提取器(与预训练模型配套)
model_name = "facebook/wav2vec2-base-960h"  # 也可以使用其他变体,如"facebook/wav2vec2-large-960h-lv60"
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_name)

def preprocess_function(examples):
    """
    批处理函数:加载音频并提取特征。
    """
    # 使用datasets库的Audio功能加载并重采样音频
    audio_arrays = [x["array"] for x in examples["audio"]]
    
    # 特征提取器会自动处理padding和转换为模型需要的格式
    inputs = feature_extractor(
        audio_arrays, 
        sampling_rate=feature_extractor.sampling_rate, 
        padding=True, 
        return_tensors="pt",
        max_length=16000 * 10,  # 假设最长10秒,可根据数据调整
        truncation=True
    )
    
    # 添加标签
    inputs["labels_arousal"] = examples["arousal"]
    inputs["labels_nature"] = examples["nature"] 
    inputs["labels_prosody"] = examples["prosody"]
    
    return inputs

# 将Pandas DataFrame转换为Hugging Face Dataset格式
from datasets import Dataset
hf_dataset = Dataset.from_pandas(df)

# 添加音频列(datasets库会自动加载和缓存)
hf_dataset = hf_dataset.cast_column("file_path", Audio(sampling_rate=16000))

# 应用预处理函数
encoded_dataset = hf_dataset.map(
    preprocess_function,
    remove_columns=hf_dataset.column_names,  # 移除原始列,只保留处理后的特征
    batched=True,
    batch_size=32
)

print(encoded_dataset[0])  # 查看一条处理后的样本

5. 核心实战:wav2vec2模型微调脚本

这是本教程最核心的部分。我们将编写一个完整的脚本,来微调wav2vec2模型,使其能够预测唤醒度、自然度和韵律这三个维度。

5.1 定义多任务回归模型

DeEAR需要同时预测三个连续值(回归任务),所以我们将在wav2vec2的基座模型上,添加一个自定义的回归头。

import torch
import torch.nn as nn
from transformers import Wav2Vec2Model, Wav2Vec2PreTrainedModel
from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss

class Wav2Vec2ForMultiTaskRegression(Wav2Vec2PreTrainedModel):
    """
    自定义模型:基于wav2vec2,输出三个回归值(唤醒度、自然度、韵律)。
    """
    def __init__(self, config):
        super().__init__(config)
        self.wav2vec2 = Wav2Vec2Model(config)
        # 回归头:将wav2vec2的输出映射到三个维度
        # wav2vec2-base的隐藏层大小是768
        self.regressor = nn.Sequential(
            nn.Dropout(config.final_dropout if hasattr(config, 'final_dropout') else 0.1),
            nn.Linear(config.hidden_size, config.hidden_size),
            nn.GELU(),  # 激活函数
            nn.Dropout(0.1),
            nn.Linear(config.hidden_size, 3)  # 输出3个值
        )
        # 初始化权重
        self.post_init()
        
    def forward(self, input_values, attention_mask=None, labels_arousal=None, labels_nature=None, labels_prosody=None):
        # 1. 通过wav2vec2基座模型提取特征
        outputs = self.wav2vec2(input_values, attention_mask=attention_mask)
        # 取最后一层隐藏状态的平均值作为整个语音片段的表示
        hidden_states = outputs.last_hidden_state  # [batch_size, seq_len, hidden_size]
        
        # 2. 应用注意力掩码,并计算有效部分的均值
        if attention_mask is not None:
            input_lengths = attention_mask.sum(-1)  # 每个样本的有效长度
            # 将padding部分置零
            hidden_states = hidden_states * attention_mask.unsqueeze(-1).float()
            # 对非padding部分求平均
            pooled_output = hidden_states.sum(dim=1) / input_lengths.unsqueeze(-1).float()
        else:
            # 如果没有掩码,直接求全局平均
            pooled_output = hidden_states.mean(dim=1)
            
        # 3. 通过回归头得到预测值
        logits = self.regressor(pooled_output)  # [batch_size, 3]
        arousal_pred, nature_pred, prosody_pred = logits[:, 0], logits[:, 1], logits[:, 2]
        
        loss = None
        # 4. 如果有标签,计算损失(均方误差损失,适用于回归任务)
        if labels_arousal is not None and labels_nature is not None and labels_prosody is not None:
            loss_fct = MSELoss()
            loss_arousal = loss_fct(arousal_pred.squeeze(), labels_arousal.float())
            loss_nature = loss_fct(nature_pred.squeeze(), labels_nature.float())
            loss_prosody = loss_fct(prosody_pred.squeeze(), labels_prosody.float())
            # 总损失为三个任务损失的加权和,这里简单相加
            loss = loss_arousal + loss_nature + loss_prosody
            
        # 返回结果
        return {
            'loss': loss,
            'logits': logits,
            'arousal_pred': arousal_pred,
            'nature_pred': nature_pred,
            'prosody_pred': prosody_pred,
            'hidden_states': outputs.hidden_states,
            'attentions': outputs.attentions
        }

5.2 完整的训练脚本

现在,我们将数据加载、模型定义、训练循环整合在一起。

#!/usr/bin/env python3
"""
DeEAR模型训练脚本:微调wav2vec2进行三维语音情感回归。
"""

import os
import torch
import numpy as np
from datasets import Dataset, load_from_disk
from transformers import (
    Wav2Vec2FeatureExtractor,
    TrainingArguments,
    Trainer,
    EarlyStoppingCallback
)
from sklearn.model_selection import train_test_split
import evaluate

# 1. 加载预处理好的数据集
print("加载数据集...")
# 假设你已经运行了第4步的预处理,并保存了数据集
# encoded_dataset = load_from_disk("/path/to/your/processed_dataset")

# 这里我们创建一个模拟数据集用于演示(实际使用时请替换为真实数据)
def create_dummy_dataset(num_samples=1000):
    data = {
        'input_values': [np.random.randn(16000).astype(np.float32) for _ in range(num_samples)], # 模拟1秒音频
        'attention_mask': [np.ones(16000, dtype=np.int64) for _ in range(num_samples)],
        'labels_arousal': np.random.rand(num_samples).astype(np.float32),
        'labels_nature': np.random.rand(num_samples).astype(np.float32),
        'labels_prosody': np.random.rand(num_samples).astype(np.float32)
    }
    return Dataset.from_dict(data)

encoded_dataset = create_dummy_dataset(200)
# 划分训练集和验证集
train_testvalid = encoded_dataset.train_test_split(test_size=0.2, seed=42)
train_dataset = train_testvalid['train']
eval_dataset = train_testvalid['test']

print(f"训练集大小: {len(train_dataset)}")
print(f"验证集大小: {len(eval_dataset)}")

# 2. 加载特征提取器和模型
print("加载模型和特征提取器...")
model_name = "facebook/wav2vec2-base-960h"
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_name)

# 加载我们自定义的模型
from transformers import Wav2Vec2Config
config = Wav2Vec2Config.from_pretrained(model_name)
# 可以在这里修改配置,例如增加dropout防止过拟合
config.hidden_dropout = 0.1
config.attention_dropout = 0.1
config.final_dropout = 0.1

model = Wav2Vec2ForMultiTaskRegression.from_pretrained(
    model_name, 
    config=config,
    ignore_mismatched_sizes=True  # 忽略回归头尺寸不匹配的警告
)

# 3. 定义评估指标(回归任务常用均方根误差RMSE和皮尔逊相关系数)
def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    # predictions是模型输出,labels是真实标签
    arousal_pred, nature_pred, prosody_pred = predictions[:, 0], predictions[:, 1], predictions[:, 2]
    arousal_true, nature_true, prosody_true = labels[:, 0], labels[:, 1], labels[:, 2]
    
    metrics = {}
    from scipy.stats import pearsonr
    import numpy as np
    
    for name, pred, true in [('arousal', arousal_pred, arousal_true), 
                             ('nature', nature_pred, nature_true), 
                             ('prosody', prosody_pred, prosody_true)]:
        # 计算均方误差 (MSE) 和均方根误差 (RMSE)
        mse = np.mean((pred - true) ** 2)
        rmse = np.sqrt(mse)
        metrics[f'{name}_mse'] = float(mse)
        metrics[f'{name}_rmse'] = float(rmse)
        
        # 计算皮尔逊相关系数
        if len(pred) > 1:  # 需要至少两个点计算相关系数
            corr, _ = pearsonr(pred, true)
            metrics[f'{name}_pearsonr'] = float(corr)
        else:
            metrics[f'{name}_pearsonr'] = 0.0
            
        # 计算平均绝对误差 (MAE)
        mae = np.mean(np.abs(pred - true))
        metrics[f'{name}_mae'] = float(mae)
    
    # 三个维度的平均RMSE
    avg_rmse = (metrics['arousal_rmse'] + metrics['nature_rmse'] + metrics['prosody_rmse']) / 3
    metrics['avg_rmse'] = float(avg_rmse)
    
    return metrics

# 4. 配置训练参数
training_args = TrainingArguments(
    output_dir="./deear_wav2vec2_checkpoints",  # 模型保存路径
    evaluation_strategy="epoch",                 # 每个epoch后在验证集上评估
    save_strategy="epoch",                       # 每个epoch后保存模型
    learning_rate=1e-4,                          # 学习率,微调通常较小
    per_device_train_batch_size=8,               # 每个GPU的批次大小
    per_device_eval_batch_size=8,
    num_train_epochs=20,                         # 训练轮数
    weight_decay=0.01,                           # 权重衰减,防止过拟合
    logging_dir="./logs",                        # 日志目录
    logging_steps=50,
    load_best_model_at_end=True,                 # 训练结束后加载最佳模型
    metric_for_best_model="avg_rmse",            # 根据平均RMSE选择最佳模型
    greater_is_better=False,                     # RMSE越小越好
    report_to="tensorboard",                     # 可选:使用TensorBoard记录
    push_to_hub=False,                           # 是否上传到Hugging Face Hub
    seed=42,
)

# 5. 创建Trainer并开始训练
print("开始训练...")
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_metrics,
    callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],  # 早停,防止过拟合
)

train_result = trainer.train()

# 6. 保存最终模型
print("训练完成,保存模型...")
trainer.save_model("./deear_wav2vec2_final")
feature_extractor.save_pretrained("./deear_wav2vec2_final")
print("模型已保存至 ./deear_wav2vec2_final")

# 7. 在测试集上评估最终性能(如果有独立的测试集)
# eval_results = trainer.evaluate(test_dataset)
# print(f"测试集性能: {eval_results}")

5.3 关键训练技巧与参数解读

  • 学习率:微调预训练模型时,学习率通常设置得比较小(如1e-5到1e-4),以免破坏预训练阶段学到的宝贵知识。
  • 批次大小:受限于GPU内存,语音模型的批次大小通常较小。可以尝试梯度累积来模拟更大的批次。
  • 早停(Early Stopping):当验证集指标在连续几个epoch不再提升时,自动停止训练,这是防止过拟合的有效手段。
  • 损失函数:对于回归任务,我们使用均方误差(MSE)。你也可以尝试平滑L1损失(Smooth L1 Loss),它对异常值不那么敏感。
  • 评估指标:我们同时使用了RMSE(衡量预测误差大小)和皮尔逊相关系数(衡量预测趋势与真实值的一致性)。一个好的模型应该两者都表现良好。

6. 模型使用与结果解读

训练完成后,我们就可以用这个模型来分析新的语音了。

6.1 加载模型并进行预测

import torch
import librosa
import numpy as np
from transformers import Wav2Vec2FeatureExtractor

# 加载训练好的模型和特征提取器
model_path = "./deear_wav2vec2_final"
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_path)
model = Wav2Vec2ForMultiTaskRegression.from_pretrained(model_path)
model.eval()  # 设置为评估模式

def predict_emotion(audio_path):
    """
    对单条语音进行三维情感预测。
    """
    # 1. 加载和预处理音频
    speech, sr = librosa.load(audio_path, sr=16000, mono=True)
    
    # 2. 提取特征
    inputs = feature_extractor(
        speech, 
        sampling_rate=16000, 
        return_tensors="pt",
        padding=True,
        max_length=16000 * 15,  # 最长15秒
        truncation=True
    )
    
    # 3. 模型预测
    with torch.no_grad():
        outputs = model(**inputs)
    
    # 4. 获取预测结果并后处理
    arousal = outputs.arousal_pred.item()
    nature = outputs.nature_pred.item()
    prosody = outputs.prosody_pred.item()
    
    # 将输出限制在[0,1]范围内(假设你的标签也在0-1之间)
    arousal = max(0.0, min(1.0, arousal))
    nature = max(0.0, min(1.0, nature))
    prosody = max(0.0, min(1.0, prosody))
    
    # 5. 将连续值转换为类别描述(可选,更易理解)
    def get_category(value, threshold=0.5):
        return "高" if value > threshold else "低"
    
    arousal_cat = get_category(arousal, 0.5)
    nature_cat = get_category(nature, 0.5)
    prosody_cat = get_category(prosody, 0.5)
    
    results = {
        'arousal': {'value': round(arousal, 3), 'category': arousal_cat},
        'nature': {'value': round(nature, 3), 'category': nature_cat},
        'prosody': {'value': round(prosody, 3), 'category': prosody_cat},
        'interpretation': f"这段语音听起来{arousal_cat}唤醒、{nature_cat}自然、韵律{prosody_cat}。"
    }
    
    return results

# 使用示例
audio_file = "path/to/your/test_audio.wav"
result = predict_emotion(audio_file)
print("预测结果:")
for dim, info in result.items():
    if dim != 'interpretation':
        print(f"  {dim}: {info['value']} ({info['category']})")
print(result['interpretation'])

6.2 结果可视化与解释

为了让结果更直观,我们可以用简单的图表来展示:

import matplotlib.pyplot as plt

def plot_deear_results(results_dict, title="DeEAR情感分析结果"):
    """
    绘制三维情感分析雷达图。
    """
    dimensions = ['唤醒度', '自然度', '韵律']
    values = [results_dict['arousal']['value'], 
              results_dict['nature']['value'], 
              results_dict['prosody']['value']]
    
    # 使雷达图闭合
    values += values[:1]
    angles = np.linspace(0, 2 * np.pi, len(dimensions), endpoint=False).tolist()
    angles += angles[:1]
    
    fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(projection='polar'))
    ax.plot(angles, values, 'o-', linewidth=2)
    ax.fill(angles, values, alpha=0.25)
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(dimensions)
    ax.set_ylim(0, 1)
    ax.set_title(title, size=16, y=1.1)
    
    # 添加网格和值标签
    ax.grid(True)
    for angle, value in zip(angles[:-1], values[:-1]):
        ax.text(angle, value+0.05, f'{value:.2f}', ha='center')
    
    plt.tight_layout()
    plt.show()

# 使用示例
plot_deear_results(result, title="测试语音情感分析")

这个雷达图能直观展示一段语音在三个维度上的“情感轮廓”。例如,一段激昂的演讲可能在“唤醒度”和“韵律”上得分很高,但在“自然度”上得分较低。

7. 总结与进阶方向

通过这篇教程,我们完成了DeEAR语音情感识别模型从零开始的复现。我们学习了如何:

  1. 准备环境与数据:搭建Python环境,获取并预处理CREMA-D等情感语音数据集。
  2. 理解核心模型:了解了wav2vec2的工作原理及其在情感识别任务中的优势。
  3. 处理数据:将原始音频转换为模型输入,并构造三维情感标签。
  4. 构建与训练模型:编写了自定义的wav2vec2多任务回归模型,并完成了完整的训练流程。
  5. 使用与解读模型:加载训练好的模型进行预测,并可视化分析结果。

如果你想进一步探索,这里有一些进阶方向:

  • 使用更高质量的数据集:尝试IEMOCAP等包含更丰富上下文和更细致标注的数据集。
  • 改进标签构造:本教程使用了启发式规则构造三维标签。更可靠的方法是进行人工标注,或使用其他已标注了维度情感的数据集(如MSP-Podcast)。
  • 尝试不同的模型架构:除了在wav2vec2后加简单回归头,可以尝试更复杂的结构,如LSTM、注意力机制来建模时序信息。
  • 多模态融合:结合文本转写内容(可用wav2vec2的语音识别能力)或面部表情(如果有多模态数据),进行多模态情感分析。
  • 部署与优化:将模型转换为ONNX或使用TorchScript进行优化,以便在移动端或边缘设备上高效运行。

语音情感识别是一个充满挑战和机遇的领域。DeEAR项目为我们提供了一个优秀的起点。希望这篇教程能帮助你快速上手,并在此基础上构建出更强大、更实用的语音情感分析应用。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

更多推荐