
简介面向音频处理与机器学习初学者的MATLAB男声女声识别项目基于GUI图形界面完成性别分类。资源共五个文件包含三段分别用于训练和测试的男女声音频、一个GUI布局文件以及一份完整M源码压缩包仅1.42MB轻量易部署。项目从读取音频、预处理、傅里叶变换与频谱分析到MFCC特征提取再到SVM或神经网络分类器构建形成完整识别链路界面文件保存了布局源码实现按钮回调、特征计算与结果展示可直接运行并替换自己的音频进行验证。已有3721人学习浏览既能作为MATLAB信号处理与模式识别课程的实战练习也可作为语音性别识别竞赛或毕业设计的基础模板。整个项目在纯MATLAB环境下即可完成实验与验证适合初学者快速掌握语音特征分析与分类器训练流程。1. 男女声音识别不是玄学MATLAB里的特征与分类两条线把一段录音交给计算机判断说话人是男是女这件事在MATLAB里跑通全流程只需要几十行代码但很多人第一次做都会翻车。男声和女声的差异本质上是生理结构差异在声学上的映射声带厚薄导致基频不同声道长短导致共振峰位置不同。用MATLAB做男女声音识别核心工作其实是两条线——特征提取和分类器训练前者决定信息是否够用后者决定信息怎么被利用。这个项目非常适合语音入门者验证信号处理流程也适合有数据但缺分类思路的从业者做快速原型验证。下面从特征参数讲起逐层落到代码和坑位。2. 特征提取基频、MFCC与共振峰的参数怎么设2.1 基频男声85-180Hz、女声165-255Hz分水岭在这里基频是男女声音识别里最直观、也最稳定的特征。成年男性声带厚而长振动频率大致落在85到180Hz女性声带薄而短基频范围大约在165到255Hz。两个范围有重叠区间单靠阈值切分会留下误判但作为特征输入分类器它贡献的区分度非常高。MATLAB从R2019a开始内置了pitch函数不用自己写自相关或倒谱法输入音频和采样率就能拿到逐帧基频序列。[audioIn, fs] audioread(female_01.wav); if size(audioIn, 2) 1 audioIn mean(audioIn, 2); % 双声道混成单声道避免两个声道数据不同步 end [f0, loc] pitch(audioIn, fs, ... WindowLength, round(fs * 0.05), ... % 帧长50ms OverlapLength, round(fs * 0.025), ... % 帧移25ms Range, [70 300]); % 基频搜索范围 f0Median median(f0, omitnan); % 中位数比均值更抗个别帧的野值这里几个参数值得解释。WindowLength设50ms而不是语音识别常用的25ms是因为基频提取需要足够长的周期数才能稳定估计帧移25ms保证相邻帧有50%重叠让基频轨迹平滑。Range建议锁在[70 300]而不是默认的[60 300]能过滤掉部分低频噪声和咳嗽声。最后用median而不是mean做汇总统计是因为录音里经常会有个别帧pitch估计失败返回NaN或极端值中位数不会被这些离群点带偏。实际项目里我一般还会把f0Median和std(f0, omitnan)一起作为两个特征后者反映说话人语调起伏程度对区分朗读和口语也有帮助。2.2 MFCC提取帧长25ms、13个系数的默认值是哪里来的基频只描述了声带的振动快慢没有描述声音在频域上的形状。男女声除了基频不同声道传递特性也不同——女性声道短频谱共振峰整体往高频偏移。MFCC梅尔频率倒谱系数就是用来捕捉这个频谱包络形状的。MATLAB的mfcc函数把每帧音频变换到梅尔域再做离散余弦变换输出一组系数。第一维代表帧能量其余各维描述频谱形状。[coeffs, ~] mfcc(audioIn, fs, ... WindowLength, round(fs * 0.025), ... % 25ms语音识别事实标准 OverlapLength, round(fs * 0.0125), ... % 12.5ms帧移 NumCoeffs, 13); % 13维前13维保留绝大多数频谱形状信息 mfccMean mean(coeffs(:, 2:end), 1); % 丢掉第1维能量只留MFCC25ms帧长、10ms左右帧移是语音领域的经验参数理论上窗越短时间分辨率越高但频率分辨率越差25ms是两者平衡点。NumCoeffs用13是HTK时代的习惯低维系数描述频谱包络的整体走向高维系数描述细节对性别分类来说细节部分反而容易带进说话人个性噪声所以13够用。MFCC在帧和帧之间是短时平稳的但整个句子里不同位置的音素不同简单求平均会把音素差异抹掉。我的做法是把mfccMean和std(coeffs(:, 2:end), 0, 1)即每一维的标准差拼接起来这样既保留平均频谱形状又记录了频谱随时间的变化幅度。这一步对短音频尤其重要后面避坑章节会细说。2.3 共振峰与短时能量补特征还是补噪声共振峰是声道谐振产生的频谱峰值成年女性由于声道短前三个共振峰F1-F3平均比男性高20%左右。MATLAB没有现成的共振峰提取函数常见做法是用LPC线性预测系数求解多项式根来推算。做法不复杂但共振峰估计对噪声极敏感真实录音里一个电话信道就把高频共振峰压没了所以我不建议把它当作主力特征。[ar, ~] lpc(audioIn, 12); % 12阶LPC覆盖4个共振峰左右 rts roots(ar); % 多项式根 rts rts(imag(rts) 0); % 只保留复平面上半部分 angles atan2(imag(rts), real(rts)); formantHz sort(angles * fs / (2 * pi)); % 换算成频率这个代码块在干净录音上能得到相对合理的F1-F3估计但也仅此而已。实战中我会把共振峰特征作为可选项当数据集是麦克风近场录音时加上前三个共振峰频率能提升一到两个百分点的准确率当数据来源混杂比如一部分是手机录音、一部分是电话录音时共振峰引入的噪声远大于信息量不加。短时能量同理男声通常比女声略低但能量受录音距离影响太大同一句话远近各录一遍能量差异比性别差异还大。特征选取的逻辑是优先选物理机制明确且对录音条件不敏感的特征基频和MFCC属于前者共振峰和能量属于后者。3. 分类器选型SVM、决策树与kNN的实战对比3.1 用fitcecoc包SVMRBF核与标准化是默认动作特征提取完后男女声音识别就是一个典型的二分类问题。在样本量几百到几千这个区间支持向量机是稳定度最高的选择。MATLAB里直接调fitcecoc就可以完成多分类封装二分类时它等价于训练一个标准的SVM。选择RBF核是因为性别分类的特征边界不是线性的——基频和MFCC之间存在交互关系线性核在这个任务上表现明显差一截。svmModel fitcecoc(features, labels, ... Learners, templateSVM(KernelFunction, rbf, ... KernelScale, auto), ... Standardize, true, ... Prior, empirical);KernelScale设成auto让MATLAB根据训练数据自动估计RBF核的尺度参数省去手动调参。Standardize一定要设true因为MFCC均值在几十的量级基频中位数只有一百多标准差不一致会导致RBF核的距离计算被大数值维度主导。Prior设empirical表示按训练集中男女样本的比例自动调整先验概率这一步对处理不平衡数据特别重要后面避坑章节会展开。这套配置在我的实测里对干净语音数据集通常能拿90%以上的准确率而且基本不需要调参。3.2 决策树和kNN什么时候可以替代SVM决策树的特点是训练极快、结果可解释能直接看出模型用了哪些特征、阈值是多少。fitctree在特征维度不高时表现不错而且对数据分布没有假设男女样本重叠严重时也没问题。但它对特征缩放不敏感不需要标准化代价是决策边界是轴对齐的矩形在特征耦合较强的数据上精度上限不如SVM。kNN的思路更朴素——新样本距离谁近就归谁。treeModel fitctree(features, labels, ... MaxNumSplits, 20, ... % 限制树的复杂度防止深树过拟合 SplitCriterion, gdi); % 基尼不纯度 knnModel fitcknn(features, labels, ... NumNeighbors, 5, ... Distance, cosine); % 余弦距离在高维特征上比欧氏距离更稳定MaxNumSplits设20意味着允许最多20个分支节点对二分类问题通常够用。限制深度是一种正则化——决策树如果不限深会逐帧记住训练集里的噪声训练准确率接近100%测试集立刻掉回75%。kNN的NumNeighbors设奇数在二分类里可以避免平票5是默认起点样本少时可以降到3样本多时提到7效果差别不大。kNN在推理时要计算新样本与所有训练样本的距离当训练集超过几千条时单次预测耗时明显上升实时性要求高的场景不推荐。3.3 数据集划分与交叉验证分层抽样决定结果可信度分类器选完下一步就是验证它到底行不行。很多人在这一步犯的错误是随机划分训练集和测试集导致某个人的语音片段同时出现在两边——虽然测试集和训练集来自同一个说话人模型记住了这个人的音色测试准确率虚高到95%以上但换成一个陌生说话人立刻崩盘。正确的做法是分层抽样并按说话人去切。cv cvpartition(labels, KFold, 5, Stratify, true); for i 1:cv.NumTestSets trainIdx cv.training(i); testIdx cv.test(i); model fitcecoc(features(trainIdx, :), labels(trainIdx), ... Learners, templateSVM(KernelFunction, rbf, KernelScale, auto), ... Standardize, true); pred predict(model, features(testIdx, :)); acc(i) sum(pred labels(testIdx)) / numel(testIdx); end meanAcc mean(acc);KFold设5代表五折交叉验证每次用4/5数据训练、1/5验证循环五次取平均。Stratify必须设true它保证每一折里男女比例和全量数据一致避免某一折全是女声导致模型在这个fold上直接失分。如果你的数据集里有重复说话人记得先把同一个人的所有语音分到同一折里这需要你手动构造分组信息cvpartition本身不知道哪些文件属于同一个人。SVM在特征经过标准化后会有额外开销但五折交叉验证的整体耗时通常在几十秒量级完全可接受。4. 用MATLAB跑通“采集→特征→训练→预测”全流程4.1 audioDatastore按文件夹批量读取标签从目录名来写完整流程的第一步是把散落的音频文件组织成带标签的数据集。常见做法是建两个文件夹male目录放男声录音female目录放女声录音然后让audioDatastore自动扫描子目录用子目录名生成标签。这个方案的好处是后续只有新增数据不用改代码往文件夹里丢文件就行。ads audioDatastore(data, ... IncludeSubfolders, true, ... LabelSource, foldernames);data目录下面直接放male和female两个子文件夹。IncludeSubfolders设true才会递归扫描子目录。LabelSource设foldernames告诉MATLAB把文件所在子目录的名字作为该文件的标签生成的结果是categorical类型方便后面直接喂给分类器。录音格式不限制wav、mp3、m4a都能读但建议尽量统一成wavMP3经过有损压缩会抹平一部分高频细节这些细节对MFCC的前几维影响不大但会污染共振峰相关特征。4.2 批量特征提取与拼接把音频变成特征矩阵音频数据按batch读入后要对每条录音做相同流程的预处理和特征提取。这里有几个隐藏细节每条录音的长度不同所以MFCC帧数不同直接用全部帧拼特征会有对齐问题。我的做法是逐条提取特征后在时间维上做聚合统计把每条音频压缩成一个固定长度的行向量最后拼成一个大矩阵。allFeatures []; allLabels []; while hasdata(ads) [audioIn, info] read(ads); fs info.SampleRate; if size(audioIn, 2) 1 audioIn mean(audioIn, 2); end audioIn audioIn / max(abs(audioIn)); % 峰值归一化消除录音音量差异 f0 pitch(audioIn, fs, ... WindowLength, round(fs * 0.05), ... OverlapLength, round(fs * 0.025), ... Range, [70 300]); f0Vec [median(f0, omitnan), std(f0, omitnan), mean(f0, omitnan)]; [coeffs, ~] mfcc(audioIn, fs, ... WindowLength, round(fs * 0.025), ... OverlapLength, round(fs * 0.0125), ... NumCoeffs, 13); mfccFeat [mean(coeffs(:, 2:end), 1), std(coeffs(:, 2:end), 0, 1)]; featVec [f0Vec, mfccFeat]; % 3 24 27维特征 allFeatures [allFeatures; featVec]; %#okAGROW allLabels [allLabels; info.Label]; %#okAGROW endaudioIn / max(abs(audioIn))这一步很容易被跳过但对真实录音影响很大——不同设备录音的音量差异能到10倍以上不做归一化的话能量相关的分量会把分类器带偏。特征向量一共27维基频中位数、基频标准差、基频均值再加12维MFCC均值和12维MFCC标准差。std在MFCC上用的是0权重即除以N-1这也是MATLAB默认。4.3 训练与五折交叉验证一张混淆矩阵看透结果特征矩阵和标签都准备好了接下来进入训练阶段。我习惯先跑一遍五折交叉验证确认模型稳定再在全部数据上重新训练最终模型。features allFeatures; labels allLabels; featuresNorm zscore(features); % 全局标准化保存均值和标准差供预测复用 rng(42); % 固定随机种子结果可复现 cv cvpartition(labels, KFold, 5, Stratify, true); acc zeros(cv.NumTestSets, 1); confMat zeros(2, 2); for i 1:cv.NumTestSets trainIdx cv.training(i); testIdx cv.test(i); model fitcecoc(featuresNorm(trainIdx, :), labels(trainIdx), ... Learners, templateSVM(KernelFunction, rbf, KernelScale, auto), ... Standardize, false); % 已用zscore标准化这里关掉避免双重标准化 pred predict(model, featuresNorm(testIdx, :)); acc(i) mean(pred labels(testIdx)); confMat confMat confusionmat(labels(testIdx), pred); end fprintf(平均准确率: %.2f%%\n, mean(acc) * 100);rng(42)固定随机种子让交叉验证的结果可以复现这在调参时很重要——不固定种子的话每次跑结果都在波动你没法判断一个参数改动是真实提升还是随机波动。zscore要放在交叉验证之前做还是放在每折内部做其实有讲究。严格来说应该在训练折上计算均值和标准差再应用到测试折上避免测试集信息泄漏到标准化里但样本量大时两者结果几乎无差别这里为了简化代码用了全局标准化。如果追求严谨把标准化放进循环内部即可。最后的混淆矩阵对角线元素之和除以总样本数和平均准确率应该一致两者互为校验。4.4 单条录音的预测上线前的最后一步模型训练完成后实际使用场景是给一条新的录音输出一个性别标签。这中间要做的不是直接调用predict而是把训练阶段那套预处理和特征提取逻辑原封不动地搬过来否则特征对不上模型预测毫无意义。function label predictGender(audioPath, model, normMean, normStd) [audioIn, fs] audioread(audioPath); if size(audioIn, 2) 1 audioIn mean(audioIn, 2); end audioIn audioIn / max(abs(audioIn)); f0 pitch(audioIn, fs, ... WindowLength, round(fs * 0.05), ... OverlapLength, round(fs * 0.025), ... Range, [70 300]); f0Vec [median(f0, omitnan), std(f0, omitnan), mean(f0, omitnan)]; [coeffs, ~] mfcc(audioIn, fs, ... WindowLength, round(fs * 0.025), ... OverlapLength, round(fs * 0.0125), ... NumCoeffs, 13); mfccFeat [mean(coeffs(:, 2:end), 1), std(coeffs(:, 2:end), 0, 1)]; featVec [f0Vec, mfccFeat]; featVecNorm (featVec - normMean) ./ normStd; % 用训练集的统计量标准化 label predict(model, featVecNorm); end这个函数把流程封装成了一个可复用的入口。normMean和normStd是训练时zscore返回的均值和标准差必须在训练阶段保存下来。要注意这里处理的是单条短音频如果音频超过10秒建议切段后逐段预测再投票因为整段求出均值会把前半段说话、后半段沉默的情况抹平导致特征被拉向中间值。这个细节在真实场景出现频率很高下一章详细展开。5. 避坑五个让识别模型翻车的常见问题5.1 训练集准确率99%、测试集70%特征里混进了录音环境交叉验证结果远低于训练集这是最常见的翻车现场。男女声音识别里训练集和测试集如果来自不同设备MFCC特征会把麦克风的频响特性一起学进去。我遇到过用一套录音设备的数据训练到另一套设备上测试准确率直接从92%掉到71%的情况。原因在于MFCC描述的是频谱包络而频谱包络里既有声道形状生理特征也有录音设备的滤波特性环境特征SVM无法自动区分这两者。解决思路有两个方向。第一个是数据层面训练数据尽可能覆盖多设备、多场景让分类器学到的是声带差异而不是麦克风差异。第二个是特征层面把MFCC做倒谱均值归一化CMN减去整段音频MFCC的均值。MATLAB里手动实现就是coeffs coeffs - mean(coeffs)这能去掉大部分线性信道畸变。两个做法都做效果最好。5.2 pitch返回一堆NaN搜索范围和采样率没对齐pitch函数返回的f0里如果出现大量NaN说明你的音频可能根本不是预期采样率或者搜索范围设得太窄。我踩过一次某批wav文件标注是44.1kHz实际是16kHz的mp3转码来的audioread返回的实际Fs就是16kHz代码里写的帧长参数是按44.1kHz算的等效窗长从50ms变成了138ms基频轨迹乱七八糟。解决方法是每次audioread之后直接用返回的fs计算窗口参数不要写死。另外检查Range——如果录音里有压着嗓子的气声基频可能跌到70Hz以下被过滤掉。我的习惯是先用[50 400]跑一遍看统计分布再决定要不要收窄到[70 300]。调试时打印sum(isnan(f0)) / numel(f0)如果超过20%就要回头查数据了。5.3 所有测试样本都判成男声数据不平衡与先验纠缠模型把测试集里的女声也判成男声说明训练集里男声样本远多于女声SVM的决策边界被先验概率推向了女声一侧。fitcecoc默认假设类别先验均匀当实际比例是8:2时模型会在置信度上偏向多数类。解决方法是显式设置Prior参数。除了empirical让MATLAB按训练集比例自动估计更稳妥的方案是做类别加权给少数类更高的权重让决策边界重新居中。数据扩充是另一个方向——把女声样本做变速和加噪在特征空间里等价于女声样本周围多了合法邻居SVM的支撑向量会把这部分空间重新划分。两者可以叠加使用效果比单方面调整阈值要好。5.4 mfcc矩阵形状对不上帧长与音频时长不匹配当音频短于一个窗长时mfcc返回空矩阵或行数极少的矩阵后续mean(coeffs)直接报错。这个问题在真实场景比想象中更常见——我用手机录过长按语音消息生成的文件只有0.3秒整个音频还不够一帧MFCC使用。解决思路是设置最小音频时长门槛处理前先判断numel(audioIn) fs * 0.5就跳过该条数据或补零到0.5秒。补零会引入假的频谱形状所以我倾向直接过滤。另外帧移参数如果设成round(fs * 0.0125)短音频产生的帧数很少帧与帧之间的统计量方差会非常大这也是短音频判别结果不稳定的根源。建议少于2秒的录音不要直接进模型而是先按0.5秒一段切片取多数投票。5.5 程序跑到一半内存爆炸循环里数组不断变长批量特征提取的循环里用allFeatures [allFeatures; featVec]是可以跑的但样本量超过几千以后MATLAB每次拼接都要重新分配整块内存运行速度会以指数级下降最后直接内存溢出。这在纯技术层面是个性能问题在工程层面是数据规模增长必然遇到的瓶颈。两个常见的优化方向。一是预分配先统计ads里的文件总数numel(ads.Files)按这个数预分配一个大矩阵再用索引填入。二是改用tall数组或直接写parfor并行处理每个文件独立提取特征再合并天然适合并行。我一般会先用预分配解决90%的场景数据量到几万条再考虑并行。内存问题看起来不起眼但处理一整夜数据跑到一半崩溃的体验真的很痛。6. 进阶说话人归一化与数据增强把准确率再往上推一档6.1 说话人归一化去掉个性留性别共性基线模型能到90%左右以后再往上提就进入精调阶段。男女声音识别有一个独特优势基频的绝对值其实混合了“性别信息”和“个人音色”两个因素。一个嗓门粗的女低音和一个声线高的男声单看基频中位数可能完全重叠。说话人归一化的思路是把每条音频的基频轨迹除以该条音频的基频中位数把绝对频率变成相对变化模式。f0Norm f0 / median(f0, omitnan); f0StatNorm [std(f0Norm, omitnan), iqr(f0Norm), mean(f0Norm)];归一化后保留了语调起伏的相对模式抹掉了说话人本身的绝对音高这在跨说话人场景下能显著提升泛化能力。代价是如果测试时输入只有一句话、音高变化范围太小归一化后的特征会变得不稳定所以这种方法在长音频上效果好于短音频。前后对比可以用五折交叉验证正确率来评判提升通常在1到3个百分点之间。6.2 数据增强三件套变速、加噪、音量抖动数据集只有几十条语音时模型再调也就那样。扩大训练集最便宜的方式不是去网上找数据而是对已有数据做增强。语音领域最常用的三件套是变速、加噪和音量抖动。变速保持基频不变、时长变化模拟说话快慢差异加噪让模型见过不干净的环境音量抖动模拟不同录音距离。MATLAB里变速用shiftPitch或者audioread后重采样再插值都可以最简单的实现是把音频用resample插值后播放长度变化但音高也变了其实这已经足够破坏掉一部分设备特征达到增强目的。对MFCC类特征来说只要增强了频谱形状和基频的多样性模型的鲁棒性就会提升具体增强倍数建议每类数据翻三倍左右再多容易过拟合到噪声模式上。6.3 验证方法留一个陌生说话人出来交叉验证能防止随机波动但它测不出模型的真实泛化能力。我最后都会做一个留出性验证把所有训练文件按说话人分组随机挑出两个陌生说话人的所有音频不参与训练只做测试。这个测试模拟的是真实使用场景——来的永远是没见过的新说话人。这里有一个很容易被忽略的点同一个人的不同音频在交叉验证里会被分到不同折模型实际上已经“见过”这个人了准确率偏乐观。按说话人做分组交叉验证才接近真实水平。如果这个准确率能稳定在85%以上这个模型才算真正可以落地。我最早做这个项目时没有按说话人分组模型在见过的人上表现优秀一换陌生人立刻掉到75%。后来把说话人信息显式编码到交叉验证分组里才看到真实水平。希望这个验证方法能帮你少走一段弯路。本文还有配套的精品资源点击获取