用Python处理IEMOCAP情感标签:从原始TXT文件到可用的数据集(附完整代码)
第一次接触IEMOCAP数据集的研究者,往往会被其复杂的目录结构和分散的标注文件搞得晕头转向。这个包含12小时音频的多模态情感数据库,虽然质量极高,但原始数据就像一座迷宫——标注信息分散在多个Session文件夹的TXT文件中,情感标签使用缩写表示,还有各种需要特殊处理的异常情况。本文将带你用Python一步步解开这个谜团,把零散的标注信息转化为结构清晰的DataFrame,为后续的模型训练铺平道路。
1. IEMOCAP数据集结构解析
IEMOCAP数据集由5个会话(Session)组成,每个会话包含两位说话者(F/M)的即兴对话(impro)和剧本对话(script)。情感标注信息存储在EmoEvaluation文件夹的TXT文件中,而对应的音频片段则位于sentences/wav目录下。
典型的文件结构如下:
IEMOCAP/ ├── Session1/ │ ├── dialog/ │ │ ├── EmoEvaluation/ │ │ │ ├── Ses01F_impro01.txt │ │ │ └── ... │ ├── sentences/ │ │ ├── wav/ │ │ │ ├── Ses01F_impro01/ │ │ │ │ ├── Ses01F_impro01_F000.wav │ │ │ │ └── ... ├── Session2/ └── ...每个TXT标注文件包含多行记录,格式示例:
[3.5000 - 4.5000] Ses01F_impro01_F000 neu [0.0, 0.0, 0.0, 0.0, 0.0]其中各字段含义为:
- 时间区间
- 音频片段ID
- 情感标签(缩写)
- 其他评估数据(可忽略)
2. 构建自动化处理流程
2.1 递归查找所有标注文件
首先需要找到所有Session中的TXT标注文件。这里使用os.walk递归搜索:
import os import pandas as pd def find_all_txt_files(root_dir): """递归查找所有EmoEvaluation目录下的TXT文件""" txt_files = [] for session in [f"Session{i}" for i in range(1, 6)]: eval_dir = os.path.join(root_dir, session, "dialog", "EmoEvaluation") if not os.path.exists(eval_dir): continue for root, _, files in os.walk(eval_dir): for file in files: if file.endswith('.txt'): txt_files.append(os.path.join(root, file)) return txt_files2.2 解析单个TXT文件内容
每个TXT文件包含多行标注记录,我们需要提取音频片段ID和对应的情感标签:
def parse_txt_file(txt_path): """解析单个TXT文件,返回(音频ID, 情感标签)列表""" records = [] with open(txt_path, 'r', encoding='utf-8') as f: for line in f: if line.startswith('['): # 有效数据行 parts = line.strip().split('\t') if len(parts) >= 3: audio_id = parts[1] emotion = parts[2] records.append((audio_id, emotion)) return records2.3 构建完整音频路径
原始标注只包含片段ID,我们需要构建完整的音频文件路径:
def build_audio_path(audio_id, root_dir): """根据音频ID构建完整的wav文件路径""" session = f"Session{audio_id[3]}" # 从ID中提取Session号 base_name = audio_id.split('_')[0] + '_' + audio_id.split('_')[1] return os.path.join( root_dir, session, "sentences", "wav", base_name, f"{audio_id}.wav" )3. 情感标签标准化处理
IEMOCAP包含9种基础情感标签,但研究中常用的是4类简化版本。我们需要实现两种映射方案:
3.1 九类情感映射
原始标签到数字编码的完整映射:
EMOTION_9_MAP = { 'ang': 0, # 愤怒 'hap': 1, # 快乐 'exc': 2, # 兴奋 'sad': 3, # 悲伤 'fru': 4, # 沮丧 'fea': 5, # 恐惧 'sur': 6, # 惊讶 'neu': 7, # 中性 'oth': 8, # 其他 'dis': 8, # 厌恶(归为其他) 'xxx': 8 # 未知(归为其他) }3.2 四类情感映射
更常用的简化版本,将相似情感合并:
EMOTION_4_MAP = { 'ang': 0, # 愤怒 'hap': 1, # 快乐(包含exc) 'exc': 1, # 兴奋→快乐 'sad': 2, # 悲伤 'neu': 3, # 中性 # 其他情感设为None将被过滤 }3.3 标签转换函数
实现灵活的标签转换函数,支持两种映射方案:
def convert_emotion_label(label, mode='4class'): """转换情感标签为数字编码""" if mode == '4class': mapping = EMOTION_4_MAP else: mapping = EMOTION_9_MAP return mapping.get(label, None) # 未知标签返回None4. 构建结构化数据集
将所有组件整合,生成最终的DataFrame:
def build_iemocap_dataframe(root_dir, emotion_mode='4class'): """构建IEMOCAP数据集DataFrame""" all_records = [] # 查找并解析所有TXT文件 txt_files = find_all_txt_files(root_dir) for txt_file in txt_files: records = parse_txt_file(txt_file) for audio_id, emotion in records: # 转换情感标签 label = convert_emotion_label(emotion, emotion_mode) if label is None: # 过滤不支持的情感 continue # 构建完整音频路径 audio_path = build_audio_path(audio_id, root_dir) # 添加到结果集 all_records.append({ 'audio_id': audio_id, 'audio_path': audio_path, 'emotion': emotion, 'label': label }) return pd.DataFrame(all_records)使用示例:
df = build_iemocap_dataframe('/path/to/IEMOCAP', emotion_mode='4class') print(f"数据集大小: {len(df)}") print(df.head())5. 异常处理与实用技巧
5.1 常见问题解决方案
路径问题:
- Windows路径使用反斜杠,建议使用
os.path.join构建路径 - 确保数据集路径正确,各Session文件夹完整
- Windows路径使用反斜杠,建议使用
版本兼容性:
- 推荐使用pandas 1.0+版本
- 如果遇到编码问题,尝试指定
encoding='utf-8'
缺失文件处理:
if not os.path.exists(audio_path): print(f"警告: 音频文件不存在 {audio_path}") continue
5.2 性能优化建议
对于大型处理任务:
- 使用多进程加速文件解析:
from multiprocessing import Pool with Pool(4) as p: # 使用4个进程 records = p.map(parse_txt_file, txt_files) - 将最终DataFrame保存为Parquet格式,节省空间并提高后续加载速度:
df.to_parquet('iemocap_labels.parquet')
5.3 数据增强思路
获得基础数据集后,可以考虑:
- 添加说话者性别信息(从文件名提取F/M)
- 合并文本转录(如果有)
- 添加音频时长信息(使用librosa获取)
import librosa def get_audio_duration(path): try: return librosa.get_duration(filename=path) except: return 0.0 df['duration'] = df['audio_path'].apply(get_audio_duration)6. 完整代码整合
以下是整合所有功能的完整脚本:
import os import pandas as pd from multiprocessing import Pool import librosa # 常量定义 EMOTION_9_MAP = {...} # 如前文定义 EMOTION_4_MAP = {...} # 如前文定义 class IEMOCAPProcessor: def __init__(self, root_dir, emotion_mode='4class'): self.root_dir = root_dir self.emotion_mode = emotion_mode def process(self): txt_files = self.find_all_txt_files() with Pool(4) as p: records = p.map(self.process_single_file, txt_files) flat_records = [item for sublist in records for item in sublist] df = pd.DataFrame(flat_records) # 添加音频时长 df['duration'] = df['audio_path'].apply( lambda x: librosa.get_duration(filename=x) if os.path.exists(x) else 0.0 ) return df def find_all_txt_files(self): """查找所有TXT标注文件""" txt_files = [] for session in [f"Session{i}" for i in range(1, 6)]: eval_dir = os.path.join(self.root_dir, session, "dialog", "EmoEvaluation") if os.path.exists(eval_dir): for root, _, files in os.walk(eval_dir): for file in files: if file.endswith('.txt'): txt_files.append(os.path.join(root, file)) return txt_files def process_single_file(self, txt_path): """处理单个TXT文件""" records = [] with open(txt_path, 'r', encoding='utf-8') as f: for line in f: if line.startswith('['): parts = line.strip().split('\t') if len(parts) >= 3: audio_id = parts[1] emotion = parts[2] label = self.convert_emotion_label(emotion) if label is not None: audio_path = self.build_audio_path(audio_id) if os.path.exists(audio_path): records.append({ 'audio_id': audio_id, 'audio_path': audio_path, 'emotion': emotion, 'label': label }) return records def convert_emotion_label(self, label): """转换情感标签""" mapping = EMOTION_4_MAP if self.emotion_mode == '4class' else EMOTION_9_MAP return mapping.get(label, None) def build_audio_path(self, audio_id): """构建音频文件路径""" session = f"Session{audio_id[3]}" base_name = audio_id.split('_')[0] + '_' + audio_id.split('_')[1] return os.path.join( self.root_dir, session, "sentences", "wav", base_name, f"{audio_id}.wav" ) # 使用示例 if __name__ == '__main__': processor = IEMOCAPProcessor('/path/to/IEMOCAP', emotion_mode='4class') df = processor.process() df.to_csv('iemocap_4class.csv', index=False) print("数据集处理完成,保存为iemocap_4class.csv")这个脚本采用了面向对象的设计,将主要功能封装在IEMOCAPProcessor类中,支持:
- 多进程加速处理
- 两种情感分类模式
- 自动验证音频文件存在性
- 添加音频时长信息
- 结果导出为CSV
7. 后续应用建议
获得结构化数据集后,可以方便地用于各种深度学习框架。例如PyTorch数据加载示例:
from torch.utils.data import Dataset import torchaudio class IEMOCAPDataset(Dataset): def __init__(self, csv_path, transform=None): self.df = pd.read_csv(csv_path) self.transform = transform def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] waveform, sr = torchaudio.load(row['audio_path']) if self.transform: waveform = self.transform(waveform) return waveform, row['label']对于实际项目,还需要考虑:
- 音频预处理(降噪、归一化)
- 特征提取(MFCC、Mel频谱等)
- 数据平衡(某些情感样本可能较少)
- 数据分割(训练/验证/测试集)