【机器学习】案例2.2——GMM实现声音识别

跟随虾哥项目实践,硬件选小智就对了

xiaozhi 开源方案官方适配,二次开发文档齐全

一、项目整体背景

该项目属于基于高斯混合模型(GMM)的说话人识别(含性别识别) 领域,是语音信号处理和生物特征识别的典型应用。说话人识别的核心目标是通过语音信号的声学特征区分不同说话人(或识别性别),广泛应用于身份验证、语音助手、安防监控等场景。

传统说话人识别的关键挑战在于:

  1. 原始语音时域信号冗余度高、噪声敏感,无法直接建模;
  2. 不同说话人的语音特征服从复杂的概率分布,需选择合适的模型拟合;
  3. 需兼顾特征的静态表征(如音色)和动态变化(如语调)。

二、整体解决方案

  1. 特征层:提取梅尔频率倒谱系数(MFCC)作为静态特征,计算其差分(Delta)特征捕捉动态变化,组合成40维特征向量,并通过归一化消除信道/环境差异;
  2. 模型层:使用高斯混合模型(GMM)拟合每个说话人/性别的特征概率分布(GMM适合处理语音特征的多峰分布特性);
  3. 识别层:对测试音频提取相同特征,计算特征在各GMM模型下的对数似然值,取似然值最大的模型对应说话人/性别作为识别结果。

三、各文件详细解析(背景+解决方案+详细注释)

1. speakerfeatures.py — 语音特征提取核心模块
(1)文件背景

语音信号的原始时域数据无法直接用于建模,需转换为能表征声学特性的特征。MFCC(梅尔频率倒谱系数)是语音识别的经典特征,能模拟人耳对频率的非线性感知;Delta(差分)特征则补充特征的动态变化信息(如语速、语调),两者结合可提升特征的区分性。该文件实现了MFCC+Delta特征的提取、归一化,最终输出40维特征向量。

(2)解决的核心问题
  • 原始音频时域信号冗余、噪声敏感,转换为频域特征(MFCC)降低维度并保留核心信息;
  • 静态MFCC无法反映特征的动态变化,通过Delta特征捕捉时间维度的特征变化;
  • 不同音频的特征尺度差异大,通过归一化(CMS)消除信道/环境干扰。
(3)详细注释代码
# -*- coding: utf-8 -*-
"""
Created on Mon Sep 14 19:26:59 2015
@author: Abhijeet Kumar
@code :  实现音频的MFCC+Delta特征提取,输出40维特征向量
@Note :  20维MFCC(19个MFCC系数 + 1帧对数能量) + 20维Delta特征 = 40维特征
"""

import numpy as np
from sklearn import preprocessing
import python_speech_features as mfcc  # 专业的语音特征提取库

def calculate_delta(array):
    """
    计算特征向量矩阵的Delta(差分)特征,捕捉特征的动态变化
    Delta特征反映了MFCC特征在时间维度的变化趋势,提升特征区分性
    
    参数:
        array: 20维MFCC特征矩阵,shape=(帧数, 20)
    返回:
        deltas: 20维Delta特征矩阵,shape=(帧数, 20)
    """
    rows, cols = array.shape  # rows=帧数,cols=MFCC特征维度(20)
    deltas = np.zeros((rows, 20))  # 初始化Delta特征矩阵
    N = 2  # 计算Delta的邻域窗口大小(前后各2帧)
    for i in range(rows):  # 遍历每一帧的MFCC特征
        index = []  # 存储邻域帧的索引(后帧,前帧)
        j = 1
        while j <= N:
            # 处理边界情况:若i-j<0则取第0帧,若i+j>最后一帧则取最后一帧
            if i - j < 0:
                first = 0
            else:
                first = i - j
            if i + j > rows - 1:
                second = rows - 1
            else:
                second = i + j
            index.append((second, first))  # 存储(后j帧,前j帧)的索引
            j += 1
        # 计算Delta特征:加权平均邻域帧的差值,权重为2(j=2时)和1(j=1时),分母10是归一化系数
        deltas[i] = (array[index[0][0]] - array[index[0][1]] + (2 * (array[index[1][0]] - array[index[1][1]]))) / 10
    return deltas

def extract_features(audio, rate):
    """
    提取音频的40维特征向量:20维MFCC(含能量)+ 20维Delta特征
    步骤:1.提取MFCC;2.归一化(CMS);3.计算Delta;4.拼接特征
    
    参数:
        audio: 原始音频信号的时域数据(一维数组)
        rate: 音频的采样率(如16000Hz)
    返回:
        combined: 40维特征矩阵,shape=(帧数, 40)
    """
    # 提取MFCC特征:
    # - rate: 采样率
    # - 0.025: 帧长(25ms,语音处理标准值)
    # - 0.01: 帧移(10ms,语音处理标准值)
    # - 20: MFCC特征维度
    # - appendEnergy=True: 最后一维添加帧的对数能量(替代第0阶MFCC)
    mfcc_feat = mfcc.mfcc(audio, rate, 0.025, 0.01, 20, appendEnergy=True)

    # 归一化(CMS,倒谱均值减):消除信道/环境差异,提升特征鲁棒性
    mfcc_feat = preprocessing.scale(mfcc_feat)
    
    # 计算Delta特征(动态特征)
    delta = calculate_delta(mfcc_feat)
    
    # 拼接MFCC和Delta特征,得到40维特征向量
    combined = np.hstack((mfcc_feat, delta))
    return combined

# 主函数:测试/说明调用方式
if __name__ == "__main__":
    print("主函数说明:调用extract_features(audio, signal_rate)传入音频和采样率即可提取特征")
2. train_models.py — GMM模型训练模块
(1)文件背景

说话人的语音特征服从多峰高斯分布,高斯混合模型(GMM)能精准拟合这种分布。该文件读取训练集音频路径,为每个说话人提取5个音频的特征,训练16分量的GMM模型,并将模型保存到指定路径,供后续测试使用。

(2)解决的核心问题
  • 如何批量处理多个说话人的训练音频,聚合同一说话人的特征;
  • 如何选择GMM超参数(分量数、迭代次数等)保证拟合效果;
  • 如何将训练好的模型持久化,供测试阶段加载使用。
(3)详细注释代码
import pickle  # 模型持久化(保存/加载)
import numpy as np
from scipy.io.wavfile import read  # 读取WAV音频文件
from sklearn.mixture import GaussianMixture  # 高斯混合模型
from speakerfeatures import extract_features  # 导入自定义的特征提取函数
import warnings

# 忽略警告(如GMM收敛警告,不影响模型效果)
warnings.filterwarnings("ignore")

# 训练音频数据路径(存放各说话人的训练音频)
source = "./development_set/"

# 训练好的GMM模型保存路径
dest = "./speaker_models/"

# 训练集音频路径列表文件(每行是一个音频文件路径,格式如:speaker1-1.wav)
train_file = "development_set_enroll.txt"

# 打开训练文件,读取音频路径
file_paths = open(train_file, 'r')

count = 1  # 计数器:统计当前说话人的音频数量(每个说话人取5个音频)

# 初始化特征数组:存储单个说话人的所有音频特征
features = np.asarray(())

# 遍历每个训练音频路径
for path in file_paths:
    path = path.strip()  # 去除路径两端的空格/换行符
    print(f"正在处理音频:{path}")

    # 读取音频文件:返回采样率(sr)和音频时域数据(audio)
    sr, audio = read(source + path)

    # 提取40维MFCC+Delta特征
    vector = extract_features(audio, sr)

    # 聚合当前说话人的特征:
    # - 若特征数组为空,直接赋值
    # - 否则垂直拼接(按帧维度)
    if features.size == 0:
        features = vector
    else:
        features = np.vstack((features, vector))
    
    # 每个说话人使用5个音频训练模型(计数器到5时训练并保存模型)
    if count == 5:
        # 初始化GMM模型:
        # - n_components=16: 高斯分量数(经验值,平衡拟合效果和计算量)
        # - max_iter=200: 最大迭代次数(保证模型收敛)
        # - covariance_type='diag': 对角协方差矩阵(计算高效,适合语音特征)
        # - n_init=3: 多次初始化取最优结果,避免局部最优
        gmm = GaussianMixture(n_components=16, max_iter=200, covariance_type='diag', n_init=3)
        
        # 用当前说话人的特征训练GMM模型
        gmm.fit(features)

        # 生成模型文件名:从音频路径中提取说话人名称(如speaker1-1.wav → speaker1.gmm)
        picklefile = path.split("-")[0] + ".gmm"
        
        # 保存训练好的GMM模型(二进制写入)
        pickle.dump(gmm, open(dest + picklefile, 'wb'))
        
        # 打印训练完成信息:说话人、特征维度
        print(f'+ 说话人 {picklefile} 建模完成,特征数据量 = {features.shape}')
        
        # 重置特征数组,准备下一个说话人的特征聚合
        features = np.asarray(())
        count = 0  # 重置计数器
    count = count + 1  # 计数器递增
3. test_gender.py — 说话人/性别识别测试模块
(1)文件背景

测试阶段需加载训练好的GMM模型,对测试音频提取特征后,计算特征在每个模型下的对数似然值(似然值越高,说明特征属于该模型的概率越大),最终选择似然值最大的模型作为识别结果。

(2)解决的核心问题
  • 如何批量加载所有训练好的GMM模型,并映射到对应的说话人/性别;
  • 如何计算测试特征在各模型下的对数似然值,量化匹配程度;
  • 如何根据似然值确定最终识别结果,并输出。
(3)详细注释代码
import os  # 文件路径处理
import pickle  # 加载训练好的GMM模型
import numpy as np
from scipy.io.wavfile import read  # 读取测试音频
from speakerfeatures import extract_features  # 导入特征提取函数
import warnings
import time  # 延迟输出(提升可读性)

# 忽略警告
warnings.filterwarnings("ignore")

# 测试音频数据路径
source = "./development_set/"

# 训练好的GMM模型路径
modelpath = "./speaker_models/"

# 测试集音频路径列表文件
test_file = "development_set_test.txt"

# 打开测试文件,读取测试音频路径
file_paths = open(test_file, 'r')

# 获取所有GMM模型文件路径(过滤.gmm后缀)
gmm_files = [os.path.join(modelpath, fname) for fname in
             os.listdir(modelpath) if fname.endswith('.gmm')]

# 加载所有训练好的GMM模型(列表形式)
models = [pickle.load(open(fname, 'rb')) for fname in gmm_files]

# 提取每个模型对应的说话人/性别名称(从文件名中解析:speaker1.gmm → speaker1)
speakers = [fname.split("/")[-1].split(".gmm")[0] for fname
            in gmm_files]

# 遍历每个测试音频,进行识别
for path in file_paths:
    path = path.strip()  # 去除路径两端的空格/换行符
    print(f"\n正在测试音频:{path}")
    
    # 读取测试音频的采样率和时域数据
    sr, audio = read(source + path)
    
    # 提取测试音频的40维MFCC+Delta特征
    vector = extract_features(audio, sr)

    # 初始化对数似然值数组:长度=模型数量(说话人/性别数量)
    log_likelihood = np.zeros(len(models))

    # 遍历每个GMM模型,计算测试特征的对数似然值
    for i in range(len(models)):
        gmm = models[i]  # 获取当前模型
        # 计算特征在模型下的对数似然值(score返回每帧的对数概率)
        scores = np.array(gmm.score(vector))
        # 求和得到整个音频的总对数似然值(量化匹配程度)
        log_likelihood[i] = scores.sum()

    # 找到对数似然值最大的模型索引(似然值越大,匹配度越高)
    winner = np.argmax(log_likelihood)
    
    # 输出识别结果:最大似然值对应的说话人/性别
    print(f"\t识别结果 - {speakers[winner]}")
    
    # 延迟1秒(避免输出过快,提升可读性)
    time.sleep(1.0)

四、关键补充说明

  1. 数据文件格式要求

    • development_set_enroll.txt:每行是训练音频路径(如male1-1.wavfemale2-3.wav),每个说话人/性别需包含5个音频;
    • development_set_test.txt:每行是测试音频路径,格式与训练文件一致;
    • development_set/:存放所有训练/测试WAV音频;
    • speaker_models/:自动生成,存放训练好的.gmm模型文件。
  2. 模型超参数调整

    • GMM的n_components(分量数)可根据数据量调整(如8/16/32),分量数越多拟合效果越好,但计算量越大;
    • 帧长(0.025s)、帧移(0.01s)是语音处理的标准值,无需轻易修改;
    • MFCC维度(20)可调整为13(更常用),需同步修改Delta特征的维度。
  3. 扩展方向

    • 加入噪声鲁棒性处理(如预加重、端点检测);
    • 改用i-vector/PLDA模型提升识别精度;
    • 增加性别分类的专用标签,实现纯性别识别(而非通用说话人识别)。

跟随虾哥项目实践,硬件选小智就对了

xiaozhi 开源方案官方适配,二次开发文档齐全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值