《DeepSeek大模型高性能核心技术与多模态融合开发(人工智能技术丛书)》(王晓华)【摘要 书评 试读】- 京东图书
随着信息技术的不断发展,如何让机器识别人类情绪,这个问题受到了学术界和工业界的广泛关注。目前,情绪识别有两种方式,一种是检测生理信号,如呼吸、心率和体温等;另一种是检测情感行为,如人脸微表情识别、语音情绪识别和姿态识别。语音情绪识别(Speech Emotion Recognition,SER)是一种生物特征属性的识别方法,可通过一段语音的声学特征(与语音内容和语种无关)来识别说话人的情绪状态。语音情绪示例如图6-3所示。
图6-3 语音情绪示例
在上一节中,我们完成了基于文本的评论情感分类模型,并完成了一个具有示范意义的模型训练。本节将继续完成一项语音实战任务,即基于MLA的人类语音情感分类。
6.2.1 情绪数据的获取与标签的说明
首先是语音情绪数据集的下载,这里我们使用瑞尔森情感语音和歌曲视听数据库RAVDESS。RAVDESS语音数据集部分包含1440个文件:每个演员60次试验×24名演员=1440。RAVDESS包含24名专业演员(12名女性,12名男性),用中性的北美口音说出两个词汇匹配的陈述。言语情绪包括平静、快乐、悲伤、愤怒、恐惧、惊讶和厌恶的表情。每种表情都在两种情绪强度(正常、强烈)和一种额外的中性表情下产生。
读者可以自行下载对应的数据集,这里我们使用Audio_Speech_Actors_01-24.zip这个子数据集做情感分类。下载后的数据集结构如图6-4所示。
图6-4 左图是Audio文件夹,右图是单个文件夹数据
下面需要讲解一下情绪文件的标签问题。这个数据包含中性、平静、快乐、悲伤、愤怒、恐惧、厌恶、惊讶八种情感。请读者注意一下,本项目只使用里面的Audio_Speech_Actors_01-24.zip数据集,说话的语句只有Kids are talking by the door和Dogs are sitting by the door。
在这个数据集中,每个音频文件都拥有一个独一无二的文件名,例如图6-4所示的“03-01-01-01-01-01-01.wav”。这些文件名由七部分数字标识符构成,并非随意命名。这些数字标识符实际上赋予了文件名特定的标签意义,通过文件名就能够了解音频文件的某些属性或特征:
通过对比,03-01-02-01-01-01-01.wav这个文件对应的信息是:
另外需要注意,在这个数据集中,音频的采样率为22050,这一点可以设定或者采用在上一章介绍的librosa进行读取。
6.2.2 情绪数据集的读取
下面是需要对情绪数据集的读取,在读取数据之前需要注意,数据集中每个文件都存放在不同的文件夹中,而每个文件夹都包含若干个不同的情绪文件。因此,在读取数据时首先需要实现文件夹的读取函数:
import numpy as np
import torch
import os
import librosa as lb
import soundfile
# 这个是列出所有目录下文件夹的函数
def list_folders(path):
"""
列出指定路径下的所有文件夹名
"""
folders = []
for root, dirs, files in os.walk(path):
for dir in dirs:
folders.append(os.path.join(root, dir))
return folders
def list_files(path):
files = []
for item in os.listdir(path):
file = os.path.join(path, item)
if os.path.isfile(file):
files.append(file)
return files
由于这里读取的是音频数据,我们在上一章对音频数据降维的时候完成了基于librosa库包的音频读取和转化,可以把相应的代码用在这个示例中,代码如下所示。
#注意采样率的变更
def audio_features(wav_file_path, mfcc = True, chroma = False, mel = False,sample_rate = 22050):
audio,sample_rate = lb.load(wav_file_path,sr=sample_rate)
if len(audio.shape) != 1:
return None
result = np.array([])
if mfcc:
mfccs = np.mean(lb.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40).T, axis=0)
result = np.hstack((result, mfccs))
if chroma:
stft = np.abs(lb.stft(audio))
chroma = np.mean(lb.feature.chroma_stft(S=stft, sr=sample_rate).T, axis=0)
result = np.hstack((result, chroma))
if mel:
mel = np.mean(lb.feature.melspectrogram(y=audio, sr=sample_rate, n_mels=40, fmin=0, fmax=sample_rate//2).T, axis=0)
result = np.hstack((result, mel))
# print("file_title: {}, result.shape: {}".format(file_title, result.shape))
return result
这里需要注意,由于读取的是不同数据集,而采样率会随着数据集的不同而变化,因此这里的采样率为22050。
下面展示了完整的数据读取代码。为了便于理解,我们对每种情绪进行了文字定义,并将这些定义与相应的情绪序号进行了关联。需要注意,在前面的讲解中,情绪序号是排在文件名中第三个位置的数据。因此,我们可以通过对文件名进行文本分割,提取出情绪序号,并根据序号与情绪的对应关系,读取并理解相应情绪的标签,代码如下:
ravdess_label_dict = {"01": "neutral", "02": "calm", "03": "happy", "04": "sad", "05": "angry", "06": "fear", "07": "disgust", "08": "surprise"}
folders = list_folders("./dataset")
label_dataset = []
train_dataset = []
for folder in folders:
files = list_files(folder)
for _file in files:
label = _file.split("\\")[-1].replace(".wav","").split("-")[2]
ravdess_label = ravdess_label_dict[label]
label_num = int(label) - 1 #这里减1是由于初始位置是1,而一般列表的初始位置是0
result = audio_features(_file)
train_dataset.append(result)
label_dataset.append(label_num)
train_dataset = torch.tensor(train_dataset,dtype=torch.float)
label_dataset = torch.tensor(label_dataset,dtype=torch.long)
print(train_dataset.shape)
print(label_dataset.shape)
最终的打印结果是将训练数据和label数据转化为torch的向量,如下所示。
torch.Size([1440,40])
torch.Size([1440])
这里我们仅仅使用了MFCC的特征作为音频特征,对于其他特征,读者可以自行尝试。特别需要注意,这个示例中MFCC的维度是40,这与后续模型的输入维度相同;如果修改了输入特征长度后,后续的模型维度也要修改。
6.2.3 语音情感分类模型的设计和训练
接下来,我们需要完成情感分类模型的设计与训练。首先是模型部分,根据我们在上一节获取到的输入数据与标签信息,完成模型的设计,代码如下所示。
import torch
import torch.nn as nn
import torch.optim as optim
from moudle import attention_moudle,feedforward_layer
class EncoderBlock(torch.nn.Module):
def __init__(self,d_model = 128,attention_head_num = 4,hidden_dropout = 0.1):
super().__init__()
self.hidden_dropout = hidden_dropout
assert d_model%attention_head_num == 0,print("d_model % n_head must be Zero")
self.d_model = d_model
self.attention_head_num = attention_head_num
self.per_head_num = d_model//attention_head_num
self.input_layernorm = torch.nn.RMSNorm(d_model)
self.mha = attention_moudle.MultiHeadAttention_MLA(d_model,attention_head_num)
self.post_attention_layernorm = torch.nn.RMSNorm(d_model)
self.mlp = feedforward_layer.Swiglu(hidden_size=d_model)
def forward(self,x, past_length = 0):
residual = x
embedding = self.input_layernorm(x)
embedding = self.mha(embedding,past_length=past_length)
embedding = torch.nn.functional.dropout(embedding,p = self.hidden_dropout)
layernorm_output = self.post_attention_layernorm(embedding + residual)
mlp_output = self.mlp(layernorm_output)
output = torch.nn.functional.dropout(mlp_output, p=self.hidden_dropout)
output = residual + output
return output
# 定义模型
class DNN(torch.nn.Module):
def __init__(self, input_size = 40, hidden_size = 128, output_size = 8):
super(DNN, self).__init__()
self.hidden = torch.nn.Linear(input_size, hidden_size)
self.encoder_block = EncoderBlock(d_model = hidden_size, attention_head_num = 4)
self.relu = torch.nn.ReLU()
self.output = torch.nn.Linear(hidden_size, output_size)
def forward(self, x):
x = torch.unsqueeze(x, 1)
x = self.hidden(x)
x = self.encoder_block(x, past_length=0)
x = torch.nn.Flatten()(x)
x = self.relu(x)
x = self.output(x)
return x
上面这段代码定义了一个深度神经网络(DNN)模型,它包含一个线性层、一个编码块(EncoderBlock)、一个ReLU激活函数层和一个输出层。编码块内部使用了多头注意力机制(MultiHeadAttention_MLA)和前馈网络(Swiglu),并在注意力和前馈网络后应用了残差连接和层归一化(RMSNorm)。模型的前向传播首先将输入数据通过隐藏层,然后通过编码块,接着应用ReLU激活函数,最后通过输出层得到预测结果。请读者运行代码验证结果。
本文节选自《DeepSeek大模型高性能核心技术与多模态融合开发》,获出版社和作者授权发布。