Clustering-Based Speech Emotion Recognition by Incorporating Learned Features and Deep BiLSTM
复现日志
2021/11/22
阅读文章后初步构思复现内容:
- 数据集的下载和管理(注意speaker dependent和speaker independent数据集处理方式是不同的,需要设置一个超参获取数据信息,并存储到numpy数组当中)
- speaker dependent是将所有文件混合后,随机选取80%数据作为测试和20%数据作为验证和测试。
- speaker dependent是按照speaker进行五折交叉验证,80%的人用来训练,20%的人用来验证和测试。
初步考虑将所有文件按照speaker划分文件夹,但是经过观察之后发现对于IEMOCAP数据集来说,session中的男女数据是混杂在一起的,带有“M”和“F”字样,考虑后续获得原始数据后,在训练前转化为语谱图阶段再进行划分,划分后每个session文件夹包含两个speaker,命名为M和F,每个性别文件夹内是两个npy文件,命名为data和label,分别存放聚类后的片段和标签。
- 将数据分为片段,窗长为500ms,有25%交叠,每个句子分别处理,存储到numpy数组当中作为处理后的数据,数据的形状大致为(样本数,片段数目,每个片段的数据长度)。
- 将上面处理好的数据进行聚类,距离度量使用RBF而不是欧拉距离矩阵,首先确定K值,阈值threshold动态计算[32],具体操作方式看一下该文章,K值确定后,进行K-Means聚类,将每个语段中距离每类最近的一个片段作为key segment,将所有key segment按照speaker存储到npy文件当中备用(上述为特征提取部分,参数一旦改变需要重新运行上面的程序),聚类结果最好做一个可视化,只展示部分数据(考虑4*4窗格)【这里先不加,后面有需要的话,可以添加可视化功能】。
- 构建后面的训练模型,CNN与LSTM部分是在一起训练的,可以同时写在一个模型里,但是CNN部分是预训练的,而且是一部分网络,可以在训练之前初始化参数时把预训练参数赋值好,然后LSTM进行空的初始化。这里注意双向LSTM的写法,是两层LSTM,可参考网络资料。LSTM最后一个时间步长的输出加log softmax进行结果预测,这里不要忘记对阈值确定的系数 β \beta β进行寻优。
- 训练过程参数初步考虑:
- epoch = 100
- learning rate = 0.001
- optimizer = Adam
- loss = 对数交叉熵
- equipment = RTX 3060 GPU
五折交叉验证需要根据数据集不同进行调整,同样需要设置if选择超参数决定
- 最后的结果需要speaker dependent和speaker independent分别的准确率、召回率、F1分数共2 * 3 * 3 (18个)数据,有必要可以加上WA,还有二者对应的混淆矩阵3 * 2(6个),这里只用IEMOCAP的话,需要SD、SI的三个数据3*2共6个数据和两个混淆矩阵。
数据预处理 2021/11/24
首先采用IEMOCAP数据集作为搭建,后来的数据集还未下载,所以暂时先不考虑数据集接口的更换问题。
IEMOCAP数据集的结构如下所示:
共有5个session,其中共有10种情感,这里文中只用了4种:anger、hapiness、neutral和sadness,所以将其他没用的数据删掉,保留这四种情感即可。首先要进行的是分帧,即将所有的语音数据读取后分成窗长500ms,25%重叠的(0.25*500=125 overlapping)片段,供后续使用,片段不足500ms的部分进行补零。该数据集采样率为16000Hz,那么每个片段
这里考虑几个函数:
- 音频读取函数:输入文件路径,输出音频读取的numpy数组,每个数是每个采样点的值;(经过查找可以直接使用scipy中的read函数读取到numpy数组当中)
- 片段切割函数:输入为数据、采样率、窗长和overlap的百分比,输出每个数据的切割结果,放在一个数组当中;
- 主函数:按文件夹读取音频,切割后将音频存入对应新的文件夹中,最后得到的numpy文件的文件夹组织要和上面一样。
import os # 用于文件处理
import numpy as np
import math
from scipy.io.wavfile import read
def process(data, sp_r, win_len=500, overlap=0.25):
"""分割语段函数,输入为数据、采样率、窗长和overlap的百分比,输出每个数据的切割结果,放在一个数组当中"""
num = len(data) # 语句所有样本点数
win_num = int(sp_r * (win_len / 1000)) # 计算每个窗中的样本点数目
gap = math.floor(win_num * (1 - overlap)) # 非重叠部分的长度,需要跳过
start = 0 # 窗口起始点
result = [] # 结果列表
while start < num:
if start + win_num >= num: # 如果最后一个窗长超过了原长度
seg = np.zeros([win_num]) # 新建窗长大小全零数据
seg[:len(data[start:])] = data[start:] # 将尾部放入数组
else:
seg = data[start: start + win_num] # 取出窗口内样本点
result.append(seg)
start += gap # 后移gap大小
return np.array(result)
if __name__ == '__main__':
data_base = 'IEMOCAP' # 设置数据库
sample_rate = 16000 # 数据库对应采样率
target_name = 'IEMOCAP-preprocess' # 预处理好的npy文件存放位置
for dir_path, dir_names, filenames in os.walk(data_base): # walk是一个游走遍历器,用于遍历该路径下所有文件
if not os.path.exists(target_name + dir_path[len(data_base):]):
os.mkdir(target_name + dir_path[len(data_base):]) # 创建新的存放路径
for name in filenames: # 对于每一个文件(string类型)
path = os.path.join(dir_path, name) # 获得相对地址
wave_data = read(path)[1] # 读取音频到numpy数组当中
processed = process(wave_data, sample_rate) # 对当前语段进行切割处理并返回切割结果
new_dir = target_name + path[len(data_base): - (len(name) + 1)] # 新的保存路径
if not os.path.exists(new_dir):
os.mkdir(new_dir) # 创建新的存放路径
np.save(new_dir + '\\' + name[:-3] + 'npy', processed)
更改数据库只需要更改data_base、sample_rate和target_name三个参数即可。
聚类 2021/11/25
上面经过预处理后,每个语段被分为若干大小相同的片段,我们而后需要对每个语段进行聚类以找到key segements,并将结果的npy文件保存在另外结构同源数据相同的文件夹中。需要注意的是,聚类需要一个参数K,即每个语段聚类类数,这个参数是通过一个阈值动态确定的,在每个语段中是不同的。
其中文献[32]给出了文中所使用方法Shot boundary detection的具体细节,文章连接:镜头边界检测,文中并没有具体说明是怎么计算距离的,所以这里暂时采用欧式距离矩阵计算两个片段间的距离。直接使用scipy中自带的cdist函数计算即可。根据文献[32]中的描述,阈值的设置公式如下,首先计算出所有相邻片段间的距离,对这些距离求均值,前面乘以一个权重作为阈值:
上面的权重自定义,一般 β \beta