使用 Python 中的神经网络从语音中检测情绪
在一次数据科学训练营中,我构建了一个机器学习模型,可以从语音(预录文件和现场录音)中检测情绪。代码已上传至我的 GitHub。
这绝对是我参与过的最具挑战性的项目之一,同时也是最令人兴奋的项目之一。在这篇文章中,我将带你了解我的项目:从规划和选择数据集,到构建机器学习模型并评估其性能。
项目规划
首先,在简要浏览数据集后,我制定了一个项目计划。根据我的工作经验以及过去三个月完成的任务,我了解到这一步对于编码项目的成功至关重要。计划能够帮助我(以及团队)理清思路,将大项目分解成更小的任务,发现问题,并跟踪进度——这样就不会因为要在短时间内完成大量工作而感到沮丧。
为此,我直接在项目的 GitHub 存储库中创建了一个简单的看板,这样我就可以将代码和任务放在一个地方。
要创建与 GitHub 代码库关联的项目看板:
- 在您想要的存储库中,单击选项卡
Projects,然后单击Create project。 - 输入
Project board name。 - (可选)输入
Description项目名称并选择一个Project template。 - 点击
Create project。
数据集
我使用了RAVDESS 数据集,其中包含 1440 个音频文件。这些录音由 24 位演员(12 位男性,12 位女性)录制,他们分别以两种不同的强度(正常和强烈)以及八种不同的语调朗读两句话,这些语调分别表达了平静、快乐、悲伤、愤怒、恐惧、惊讶、厌恶和中性等不同的情绪。除中性情绪外,每种情绪都有 192 个录音,因为中性情绪没有强烈强度的录音。
综上所述,原始 RAVDESS 数据集包括:
- 1440 条录音
- 24位发言人
- 12名男性,12名女性
- 2句话
- 2 种强度
- 8 种语调/情绪
- 192 条记录,涵盖 7 种情绪
- 96 条记录,对应 1 种情绪
过采样
由于数据集不平衡,所以我使用该RandomOversample方法为中性类别创建新特征。
def oversample(X, y):
X = joblib.load("speech_emotion_recognition/features/X.joblib")
y = joblib.load("speech_emotion_recognition/features/y.joblib")
print(Counter(y))
oversample = RandomOverSampler(sampling_strategy="minority")
X_over, y_over = oversample.fit_resample(X, y)
X_over_save, y_over_save = "X_over.joblib", "y_over.joblib"
joblib.dump(X_over, os.path.join("speech_emotion_recognition/features/", X_over_save))
joblib.dump(y_over, os.path.join("speech_emotion_recognition/features/", y_over_save))
过采样增加了 96 个新的数据点,所以最终我有1536 个音频文件可以使用。
另一个不平衡点与性别有关:男性录音的数量略多,而且正常强度的录音数量也略多。我没有处理这个不平衡点,因为它对我的项目影响不大,我只是想预测情绪。不过,这在未来或许值得探讨。
特征提取
音频文件中可以提取出许多特征,但我决定使用梅尔频率倒谱系数(MFCC)。
梅尔频率倒谱(MFC)是对声音短期功率谱的表示,它基于对数功率谱在非线性梅尔频率尺度上的线性余弦变换。梅尔频率倒谱系数(MFCC)是构成MFC的各个系数。
倒谱和梅尔频率倒谱的区别在于,梅尔频率倒谱中,频带在梅尔尺度上是等间距分布的,这比普通频谱中使用的线性间隔频带更接近人耳听觉系统的响应。这种频率扭曲可以更好地表示声音,例如在音频压缩中。
为了从音频文件中提取 MFCC,我使用了 Python 库librosa:
def extract_features(path, save_dir):
feature_list = []
start_time = time.time()
for dir, _, files in os.walk(path):
for file in files:
y_lib, sample_rate = librosa.load(
os.path.join(dir, file), res_type="kaiser_fast"
)
mfccs = np.mean(
librosa.feature.mfcc(y=y_lib, sr=sample_rate, n_mfcc=40).T, axis=0
)
file = int(file[7:8]) - 1
arr = mfccs, file
feature_list.append(arr)
print("Data loaded in %s seconds." % (time.time() - start_time))
X, y = zip(*feature_list)
X, y = np.asarray(X), np.asarray(y)
print(X.shape, y.shape)
X_save, y_save = "X.joblib", "y.joblib"
joblib.dump(X, os.path.join(save_dir, X_save))
joblib.dump(y, os.path.join(save_dir, y_save))
return "Preprocessing completed."
MFCC的图形表示如下:
机器学习模型
我分别使用 MFCC 和情感标签训练了三个不同的神经网络模型:
-
多层感知器(MLP)
def mlp_classifier(X, y): X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) mlp_model = MLPClassifier( hidden_layer_sizes=(100,), solver="adam", alpha=0.001, shuffle=True, verbose=True, momentum=0.8, ) -
卷积神经网络(CNN)
def cnn_model(X, y): X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) x_traincnn = np.expand_dims(X_train, axis=2) x_testcnn = np.expand_dims(X_test, axis=2) model = Sequential() model.add(Conv1D(16, 5, padding="same", input_shape=(40, 1))) model.add(Activation("relu")) model.add(Conv1D(8, 5, padding="same")) model.add(Activation("relu")) model.add( Conv1D( 8, 5, padding="same", ) ) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(Activation("relu")) model.add(Flatten()) model.add(Dense(8)) model.add(Activation("softmax")) model.compile( loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"], ) cnn_history = model.fit( x_traincnn, y_train, batch_size=50, epochs=100, validation_data=(x_testcnn, y_test), ) -
长短期记忆网络(LSTM)
def lstm_model(X, y): X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) X_train_lstm = np.expand_dims(X_train, axis=2) X_test_lstm = np.expand_dims(X_test, axis=2) lstm_model = Sequential() lstm_model.add(LSTM(64, input_shape=(40, 1), return_sequences=True)) lstm_model.add(LSTM(32)) lstm_model.add(Dense(32, activation="relu")) lstm_model.add(Dropout(0.1)) lstm_model.add(Dense(8, activation="softmax")) lstm_model.compile( optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"] ) lstm_model.summary() lstm_history = lstm_model.fit(X_train_lstm, y_train, batch_size=32, epochs=100)
经过多次调整超参数后,我发现模型在较低的学习率(0.001)、adam较小的优化器和较少的层数下通常表现更好。所有模型都存在过拟合(无法泛化到未见过的数据),但这似乎是神经网络和音频数据处理中常见的问题。
正如预期的那样,MLP 的准确率最低,因为它是一个非常基础的模型(一个简单的前馈人工神经网络)。CNN 和 LSTM 的训练准确率相近(80%),但 CNN 在测试数据上的表现(60%)优于 LSTM(51%)。作为参考,目前最先进的语音分类模型准确率在 70% 到 80% 之间,所以我对我的 CNN 模型的准确率相当满意。
观察实际情绪与预测情绪的对比尤其有趣,可以发现哪些情绪被错误分类了。从 CNN 和 LSTM 的相关矩阵中,我注意到这两个模型都会错误分类一些听起来相似或含义模糊(即使对人类而言)的情绪,例如悲伤-平静或愤怒-快乐。
预测
最激动人心的部分是能够根据新数据进行预测,更具体地说,是实时预测电影音效片段sounddevice和我自己的声音。为了录制我的声音,我使用了 Python 库:
import soundfile as sf
import sounddevice as sd
from scipy.io.wavfile import write
def record_voice():
fs = 44100 # Sample rate
seconds = 3 # Duration of recording
# sd.default.device = "Built-in Audio" # Speakers full name here
print("Say something:")
myrecording = sd.rec(int(seconds * fs), samplerate=fs, channels=2)
sd.wait() # Wait until recording is finished
write("speech_emotion_recognition/recordings/myvoice.wav", fs, myrecording)
print("Voice recording saved.")
然后,我用预先录制和现场录制的音频文件测试了 CNN 和 LSTM 模型:
def make_predictions(file):
cnn_model = keras.models.load_model(
"speech_emotion_recognition/models/cnn_model.h5"
)
lstm_model = keras.models.load_model(
"speech_emotion_recognition/models/lstm_model.h5"
)
prediction_data, prediction_sr = librosa.load(
file,
res_type="kaiser_fast",
duration=3,
sr=22050,
offset=0.5,
)
mfccs = np.mean(
librosa.feature.mfcc(y=prediction_data, sr=prediction_sr, n_mfcc=40).T, axis=0
)
x = np.expand_dims(mfccs, axis=1)
x = np.expand_dims(x, axis=0)
predictions = lstm_model.predict_classes(x)
emotions_dict = {
"0": "neutral",
"1": "calm",
"2": "happy",
"3": "sad",
"4": "angry",
"5": "fearful",
"6": "disgusted",
"7": "surprised",
}
for key, value in emotions_dict.items():
if int(key) == predictions:
label = value
print("This voice sounds", predictions, label)
两种模型都能从录音中识别出正确或合理的情绪!
下一步
参与这个项目真的非常令人兴奋,我已经开始考虑在某些方面改进和扩展它了:
- 尝试其他模型(不一定是神经网络)。
- 提取其他音频特征,看看它们是否比 MFCC 具有更好的预测能力。
- 需要使用更大的数据集进行训练,因为 1500 个文件,每种情绪只有 200 个样本是不够的。
- 使用自然数据进行训练,即使用人们在非摆拍情境中说话的录音,这样情感表达听起来会更真实。
- 使用更多样化的数据进行训练,例如不同文化和语言背景的人的录音。这一点很重要,因为情感表达会因文化而异,并且也会受到个人经历的影响。
- 将语音与面部表情和文本(语音转文本)相结合,进行多模态情感分析。