发布于 2026-01-06 2 阅读
0

使用 Python 中的神经网络从语音中检测情绪

使用 Python 中的神经网络从语音中检测情绪

在一次数据科学训练营中,我构建了一个机器学习模型,可以从语音(预录文件和现场录音)中检测情绪。代码已上传至我的 GitHub

这绝对是我参与过的最具挑战性的项目之一,同时也是最令人兴奋的项目之一。在这篇文章中,我将带你了解我的项目:从规划和选择数据集,到构建机器学习模型并评估其性能。

项目规划

首先,在简要浏览数据集后,我制定了一个项目计划。根据我的工作经验以及过去三个月完成的任务,我了解到这一步对于编码项目的成功至关重要。计划能够帮助我(以及团队)理清思路,将大项目分解成更小的任务,发现问题,并跟踪进度——这样就不会因为要在短时间内完成大量工作而感到沮丧。

为此,我直接在项目的 GitHub 存储库中创建了一个简单的看板,这样我就可以将代码和任务放在一个地方。

GitHub 上的项目看板
GitHub 上的项目看板

要创建与 GitHub 代码库关联的项目看板:

  1. 在您想要的存储库中,单击选项卡Projects,然后单击Create project
  2. 输入Project board name
  3. (可选)输入Description项目名称并选择一个Project template
  4. 点击Create project

数据集

我使用了RAVDESS 数据集,其中包含 1440 个音频文件。这些录音由 24 位演员(12 位男性,12 位女性)录制​​,他们分别以两种不同的强度(正常和强烈)以及八种不同的语调朗读两句话,这些语调分别表达了平静、快乐、悲伤、愤怒、恐惧、惊讶、厌恶和中性等不同的情绪。除中性情绪外,每种情绪都有 192 个录音,因为中性情绪没有强烈强度的录音。

综上所述,原始 RAVDESS 数据集包括:

  • 1440 条录音
  • 24位发言人
  • 12名男性,12名女性
  • 2句话
  • 2 种强度
  • 8 种语调/情绪
  • 192 条记录,涵盖 7 种情绪
  • 96 条记录,对应 1 种情绪

RAVDESS 数据集分布
RAVDESS 数据集分布

过采样

由于数据集不平衡,所以我使用该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))
Enter fullscreen mode Exit fullscreen mode

过采样增加了 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."
Enter fullscreen mode Exit fullscreen mode

MFCC的图形表示如下:

MFCC图
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 的相关矩阵中,我注意到这两个模型都会错误分类一些听起来相似或含义模糊(即使对人类而言)的情绪,例如悲伤-平静或愤怒-快乐。

LSTM的混淆矩阵
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.")
Enter fullscreen mode Exit fullscreen mode

然后,我用预先录制和现场录制的音频文件测试了 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)
Enter fullscreen mode Exit fullscreen mode

两种模型都能从录音中识别出正确或合理的情绪!

下一步

参与这个项目真的非常令人兴奋,我已经开始考虑在某些方面改进和扩展它了:

  • 尝试其他模型(不一定是神经网络)。
  • 提取其他音频特征,看看它们是否比 MFCC 具有更好的预测能力。
  • 需要使用更大的数据集进行训练,因为 1500 个文件,每种情绪只有 200 个样本是不够的。
  • 使用自然数据进行训练,即使用人们在非摆拍情境中说话的录音,这样情感表达听起来会更真实。
  • 使用更多样化的数据进行训练,例如不同文化和语言背景的人的录音。这一点很重要,因为情感表达会因文化而异,并且也会受到个人经历的影响。
  • 将语音与面部表情和文本(语音转文本)相结合,进行多模态情感分析。
文章来源:https://dev.to/lorena/detecting-emotions-from-speech-with-neural-networks-in-python-3ioe