语音算法实习生笔试复盘:MFCC到CTC考点全解析
那年我还在读研一实验室师兄转了一条网易2018实习生招聘的链接我一眼就盯上了“语音算法实习生”这个岗位。当时语音算法正处在从传统混合模型往端到端模型切换的节点大厂语音团队都在招人笔试题出得也很有区分度。后来我顺利进了面试虽然最终没去网易但那套笔试题对我的帮助非常大它几乎帮我重新梳理了整个语音算法知识体系。这篇文章就按我的复盘经验来写语音算法实习岗笔试到底考什么、每类题背后的知识点怎么复习、以及我在实际答题过程中踩过的坑。1. 语音算法实习生笔试到底考什么题型分布与考察目标1.1 一套典型的笔试题型结构网易这类大厂的语音算法实习生笔试一般线上进行时长大概两小时。题型构成比较固定基本是三类客观选择题和填空题、简答或手动计算题、两道编程题。客观题覆盖的范围很广信号处理、概率统计、机器学习基础、语音识别常识都会涉及。比如给你一段音频的参数让你算采样点数给你一个GMM的似然函数让你判断它是凸函数还是非凸函数或者问MFCC和Fbank的区别。这类题分值比例不大但淘汰率很高因为覆盖面太宽临时抱佛脚很难覆盖全。简答和手动计算题是语音算法岗的特色。比如让你写出维特比算法的递推公式画出MFCC特征提取的流程框图或者给出一个CTC的例子手算路径合并。这类题考察的是你有没有真正理解算法的推导过程而不是背结论。编程题两道一道偏字符串处理或数组操作一道偏动态规划或搜索难度大约是LeetCode中等偏上一点但更强调边界条件和工程细节。表格整理一下我当时遇到的典型题型占比题型题量建议用时核心考察点选择题/填空题10-15题30分钟知识广度、概念辨析简答/手算题3-4题40分钟算法原理、推导能力编程题2题50分钟编码能力、边界处理1.2 笔试背后的筛选逻辑他们要的不是只会调包的人很多同学以为语音算法岗笔试会直接考深度学习框架比如TensorFlow怎么训练一个语音识别模型但实际不是。笔试更看重三件事。第一是数学功底。语音算法大量使用概率统计和线性代数GMM、HMM、卡尔曼滤波、矩阵分解这些都会涉及笔试不会直接考你公式默写但会通过一道看似简单的计算题考察你对贝叶斯公式、期望计算这些基础概念的熟练度。我记得有一道选择题大概是这样已知先验概率和似然求后验概率最大值对应的类别其实就是最小错误率贝叶斯决策但很多人连贝叶斯公式都写不对这就很说明问题。第二是编程基本功。语音特征处理涉及大量数组和矩阵运算更别说声学模型训练的数据pipeline了。笔试编程题通常不会直接出语音相关题目但会考你动态规划、字符串匹配、滑动窗口这些基本功。原因很简单你在实际工作中要自己写数据处理脚本、自己实现论文里的模块如果连基础的边界条件都处理不好没人敢让你碰核心代码。第三才是语音领域的专业感觉。你有没有真正理解语音信号为什么要分帧加窗为什么特征要做倒谱均值减除CTC和Attention的区别是什么。这些问题的答案不在任何一本书的某一个章节里需要你动手跑过实验、读过论文、甚至踩过坑才能答得有深度。笔试里能拉开差距的恰恰是这些“看似基础但需要动手理解”的题目。2. 信号处理与特征提取考点解析从波形到特征向量2.1 采样定理与音频参数计算一道送分题里的坑语音算法笔试几乎必考一道音频参数计算题。题目常常这样出某音频采样率为16kHz单声道时长5秒。若采用帧长25ms、帧移10ms分帧请问一共可以得到多少帧这道题看似简单但坑不少。首先要算出一帧对应的采样点数16kHz乘以25ms等于400个采样点帧移10ms对应160个采样点。总采样点数是16kHz乘以5秒等于8000个点。分帧公式是从第0个采样点开始每次移动160个采样点取400个点一直到超出总长度。帧数等于floor((8000 - 400) / 160) 1也就是floor(7600 / 160) 1 47 1 48帧。等等我重新算一下7600除以160等于47.5向下取整是47加1等于48帧。很多人会直接拿总采样点数除以帧移算出50帧但这样忽略了最后一帧可能不完整以及第一帧的偏移方式。真正的工程实现里分帧有两种模式一种是每帧都要求完整不足部分丢弃另一种是允许最后一帧补零。考试时你最好在答案里注明自己用的是哪种假设这样即便结果和标准答案有出入阅卷人也能看到你的思路。与采样率相关的还有一个必考问法为什么采样率必须大于信号最高频率的两倍这就是奈奎斯特定理。语音信号的频率范围一般在300Hz到3400Hz所以传统的电话语音采样率用8kHz。但现代语音识别系统为了保证更高的识别率常用16kHz采样覆盖到7kHz左右的频率因为高频部分包含齿音、摩擦音等对识别有帮助的细节信息。笔试如果问你“16kHz采样能表示的最高频率是多少”答案就是8kHz。2.2 MFCC特征提取全过程每个步骤都不能答错MFCC是语音算法岗位笔试出现频率最高的知识点没有之一。它全称是Mel频率倒谱系数当年是语音识别的标配特征现在很多端到端系统还在用FbankFilter Bank而Fbank就是MFCC去掉最后一步DCT的结果。所以MFCC的完整流程必须烂熟于心。标准流程是预加重、分帧、加窗、FFT、计算功率谱、通过Mel滤波器组、取对数、DCT、动态特征扩展。需要理解每个步骤为什么存在。预加重用一阶高通滤波器典型系数是0.97。语音信号的高频部分能量衰减很快预加重是为了提升高频成分让频谱在全局范围内更均衡。这个操作在频域上等价于一个高通滤波器在时域就是一个简单的差分运算y[n] x[n] - 0.97 * x[n-1]。分帧和加窗是绑在一起的。分帧之后每帧信号首尾不连续直接做FFT会产生频谱泄漏也就是本来集中在某个频率的能量扩散到旁边的频带上。解决办法就是加窗让帧边缘的幅度平滑地衰减到0。语音处理最常用汉明窗它比矩形窗的主瓣略宽但旁瓣衰减大很多能有效抑制频谱泄漏。这个知识点笔试常考问法一般是“为什么加窗”或“汉明窗和矩形窗的区别”。做完FFT之后得到复数频谱取模的平方就是功率谱然后过一组Mel滤波器。Mel刻度模拟人耳对频率的非线性感知低频分辨率高高频分辨率低。所以Mel滤波器组在低频处排布得密高频处排布得疏。这一步做完每个滤波器输出一个能量值。取自然对数是为了压缩动态范围同时把乘性噪声变成加性噪声方便后续处理。最后做DCT离散余弦变换是因为滤波器组输出的各维之间存在相关性DCT可以近似去相关得到更紧凑的表示。一般取前13维作为静态特征再算一阶差分和二阶差分总共39维这是当年GMM-HMM时代最标准的特征配置。Fbank和MFCC的区别也经常出现在选择题里。Fbank就是做到log Mel谱就停不做DCT所以各维之间相关性强不适合GMM这种对角协方差模型但适合DNN输入因为DNN能自己学习特征之间的相关性。2018年前后随着DNN成为主流声学模型Fbank的使用率明显上升。2.3 端点检测与降噪笔试中的隐藏考点除了MFCC端点检测VAD和降噪也是语音算法岗笔试题中的常客。VAD的目标是从一段音频中找出语音开始和结束的位置核心特征是短时能量和短时过零率。短时能量区分清音和浊音以及静音过零率则对清音和噪声比较敏感。笔试可能会给你一段能量序列问你怎么设置阈值判断语音起点考察你是否理解自适应阈值的思想。我的建议是答题时不要只写公式而是把工程上的做法也写出来。比如先取前100ms作为噪声段估计噪声能量均值再乘以一个系数作为阈值。语音段的起点就是能量连续超过阈值N帧的第一个点终点是连续低于阈值M帧的点。这些细节在Kaldi的VAD工具里都能找到对应实现笔试写出来会显得你确实跑过实验。降噪方面笔试常考谱减法和维纳滤波的基本思想。谱减法的核心假设是噪声加性且短时平稳用带噪语音的幅度谱减去噪声幅度谱估计得到干净语音的幅度谱再结合带噪语音的相位重构信号。维纳滤波则是从最小均方误差角度估计一个滤波器对带噪语音滤波。考这个知识点时重点不是公式本身而是理解语音增强的基本假设和局限比如音乐噪声是怎么产生的为什么相位信息通常不估计而是直接沿用带噪语音的相位。3. 机器学习与声学模型核心题目从GMM到DNN的时代过渡3.1 GMM-HMM语音识别框架当年的主流架构必考2018年参加笔试时工业界语音识别的主流工具链还是KaldiGMM-HMM虽然已经在逐步被DNN-HMM取代但作为语音算法的基础知识笔试仍然会考。HMM建模的是语音信号的时序变化GMM建模的是每个HMM状态下的观测概率分布。整个框架的流程是音频特征序列作为观测HMM状态序列作为隐变量通过维特比算法找到最可能的状态序列再通过Baum-Welch算法估计模型参数。笔试常见考法有两种。一种是概念题比如“HMM的三个基本问题分别是什么”。标准答案是评估问题用前向算法解码问题用维特比算法学习问题用Baum-Welch算法。另一种是手算题比如给定一个只有两个状态、两种观测的HMM已知初始概率、转移概率、发射概率手动走一步前向算法求观测序列的概率。说实话这类题现在看难度不大但当时很多人被“维特比算法的递推公式到底怎么写”卡住了。我的经验是把维特比当成动态规划来记。它维护两个量一个是在时刻t到达状态j的最大概率另一个是这个最大概率对应的前一时刻状态。递推公式就是取上一时刻所有状态概率乘以转移概率的最大值再乘以当前观测的发射概率。笔试时写出这个递推公式再把初始化和回溯步骤补上基本就能拿满分。3.2 CTC与端到端模型2018年最热的新考点2018年正好是端到端语音识别快速崛起的时期CTCConnectionist Temporal Classification就成了笔试中的加分项。CTC解决的核心问题是序列对齐语音特征序列的长度和文本标签的长度往往不一样CTC引入一个blank符号通过动态规划穷举所有可能的对齐路径并将它们合并映射到同一个标签序列。笔试如果考CTC常见问法有两种。一种问CTC相比传统交叉熵损失的优势答案是它不需要预先对齐音频和文本可以直接在序列级别训练。另一种是手算题比如给定输入长度T等于10标签序列是单个字符“n”符号集为{n, blank}问有多少条路径可以映射到“n”。答案是怎么算的CTC的路径长度必须等于输入长度10每个位置上要么是“n”要么是blank。映射到“n”的条件是序列中至少出现一个“n”因为只有一个标签字符且不能出现其他非blank字符。所以每个位置有两种选择总路径数是2的10次方减1等于1023减去的1是全部为blank的那条路径。这类手算题考察的是你是否真正理解CTC的合并规则而不是只会调接口。这道题当年我就写错了我直接写成了2的10次方。后来复盘才意识到全blank序列映射到空标签不是“n”。这个错误在面试时还被面试官拿出来追问好在我当时把思路讲清楚了说明自己确实理解了机制只是计算时粗心面试官也就没有深究。这个经历告诉我笔试的简答题一定要把推导过程写完整哪怕结果错了过程也能帮你保住分数。3.3 编程题常用算法动态规划在语音算法笔试中的N种考法语音算法岗笔试的编程题特别偏爱动态规划。原因很简单语音识别本身的核心算法——前向算法、维特比算法、CTC训练——本质都是DP。笔试不会直接考维特比的完整实现但会考一个同样需要DP思维但又不过分依赖领域背景的题比如编辑距离、最长公共子序列、最长递增子序列。编辑距离是语音算法岗位笔试编程题的经典。它在语音领域的直接应用是文本后处理比如识别结果和参考文本的对齐计算。题目通常是给定两个字符串允许插入、删除、替换三种操作每次操作代价为1求最小编辑距离。实现思路是维护一个二维DP表dp[i][j]表示字符串A前i个字符到字符串B前j个字符的最小编辑距离。初始化时dp[i][0]等于idp[0][j]等于j因为从一个字符串变成空串只能不断删除从空串变成另一个串只能不断插入。递推公式分两种情况如果A[i-1]等于B[j-1]dp[i][j]等于dp[i-1][j-1]否则取插入、删除、替换三种操作中的最小值加1。最后返回dp[m][n]。笔试时要注意的两点是字符串下标从0开始所以DP表格要多开一行一列边界条件要初始化正确否则后续所有计算都会出错。我当时用的Python处理二维列表初始化时用了[[0] * (n1) for _ in range(m1)]一开始写成了[[0] * (n1)] * (m1)结果每一行是同一个对象的引用改一个值所有行都跟着变排查了十分钟才发现。这种细节在笔试的紧张氛围下非常容易翻车需要平时就养成好习惯。4. 典型笔试题回顾与解答思路手把手演算一遍4.1 题目一音频参数计算完整手算题目某语音文件采样率16kHz量化位数16bit单声道时长3秒。分帧参数为帧长25ms、帧移10ms加汉明窗做FFT。问一帧FFT的点数至少是多少特征维数通常是多少第一步算一帧采样点数16kHz乘以25ms等于400。FFT通常取2的整数次幂所以400个点要做512点FFT补112个零。当然也可以直接做400点FFT但工程上更常见的是补零到512点因为FFT算法对2的幂次最友好。补零不会增加频率分辨率只会让频谱采样更密这个细节如果笔试问“补零能否提高频率分辨率”答案是不能频率分辨率取决于窗长而不是FFT点数这个知识点经常作为陷阱出现。第二步算特征维数。512点FFT得到对称频谱正频率部分有257个频点。经过Mel滤波器组压缩后一般得到40维Fbank特征或13维MFCC再扩展一阶和二阶差分MFCC就变成39维。所以最终特征维度是39维如果用MFCC或40维如果用Fbank。这个结果要配合前面说的“哪些维对应静态、哪些对应动态”来解释能让答案更有说服力。4.2 题目二MFCC的DCT系数计算再出一道手动小题假设Mel滤波器组输出是25个值做DCT后取前13个系数请问这13个系数分别代表什么答案是第一维是能量的近似表现反映频谱整体幅度后面的维度依次反映频谱包络在不同频率范围内的变化趋势频率越高变化越快。这个知识点常被忽略但面试官可能顺着问“为什么取前13维而不是全取”标准的回答是DCT的能量集中特性高维系数幅度很小主要包含细节和噪声去掉它们可以在不损失太多信息的前提下降低维度。4.3 题目三最长公共子序列LCS的完整代码编程题里LCS是另一个高频题因为它在语义上更贴近语音识别中的序列对齐。给定两个字符串求它们最长公共子序列的长度。def longest_common_subsequence(a: str, b: str) - int: m, n len(a), len(b) dp [[0] * (n 1) for _ in range(m 1)] for i in range(1, m 1): for j in range(1, n 1): if a[i - 1] b[j - 1]: dp[i][j] dp[i - 1][j - 1] 1 else: dp[i][j] max(dp[i - 1][j], dp[i][j - 1]) return dp[m][n]时间复杂度是O(m*n)空间复杂度可以优化到O(min(m,n))。笔试时能写出基础版本已经够了但如果你能顺手写上空间优化的版本会给面试官留下好印象。优化思路是只需要保留上一行和当前行两个数组因为dp[i][j]只依赖于dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]这三个位置。实际语音识别里的序列对齐问题也常用类似的动态规划思路只不过状态转移更复杂。4.4 题目四CTC的路径合并手算题展开我们再展开一下CTC的路径计算。设输入帧数T等于4标签序列是“ab”符号集为{a, b, blank}问所有能映射到“ab”的路径有多少条。这个问题比单字符的情况复杂得多。CTC的映射规则是两步第一步合并重复字符第二步去除blank。所以“a-b-”和“a--b”都能映射到“ab”但“aa-b”因为先合并重复的a变成“ab”也能映射到“ab”。手动枚举T等于4的所有路径是不现实的必须用DP的思想。设每个时刻的状态包括“已经匹配到a”和“已经匹配到ab”两种每个状态还可以选择输出blank或者对应字符。笔试不要求你必须算出精确数字但要求你写出DP状态转移的表格。当时我把这个DP表格画出来面试官就认可了因为这说明我理解了CTC的训练过程本质上是前向算法在路径空间上的应用。5. 备考路线与答题技巧时间分配与踩坑记录5.1 笔试现场的时间分配建议两小时看起来不算短但真实做题时会发现时间很紧张。我第一次做模拟题时在选择题上花了45分钟导致编程题最后只剩20分钟代码都没写完。后来总结出的时间分配方案是选择题最多30分钟不会的题先蒙一个标记起来不要恋战简答和手算题控制在40分钟以内每道题的答案控制在300字以内用公式、画图、分点三种方式表达比写一大段文字更高效编程题留足50分钟先审题5分钟想清楚边界情况和测试用例再动手写写完留10分钟自测。编程题有一个很重要的策略如果动态规划的状态转移一时想不出来先写一个暴力搜索版本拿部分分再在暴力版本基础上加记忆化数组优化。笔试的判分通常按测试用例通过率算部分分能拿多少拿多少不要死磕最优解法导致最后交白卷。5.2 我踩过的坑输入输出、边界条件和编译器差异笔试踩坑有几个非常典型的情况。第一个是输入输出格式问题。有些平台要求多组输入有些是单组输入读错了格式整道题得0分。我当时的习惯是先读样例输入确认读入方式再开始写算法逻辑这样可以避免在第一步就翻车。用Python的同学特别注意input()会默认去除末尾换行符而sys.stdin.readline()会保留处理字符串时容易踩坑。第二个是动态规划数组的边界条件。二维数组的初始化写错是最高频的bug。写DP之前先在草稿纸上画一个小规模的表格手动推一遍确认递推公式和边界条件一致再写代码。这样看着慢实际上比写完再调试要快得多。第三个是线上编译器的C版本问题。网易笔试平台当时用的是C11如果你的代码里用了C17才有的特性比如结构化绑定编译直接报错。考试时尽量用最基本的语法避免依赖新特性。另外getline和cin混用会导致缓冲区问题如果你需要读包含空格的字符串最好统一用getline或者统一用cin再单独处理换行这个细节在字符串题里特别容易出问题。5.3 笔试复盘的价值错题变成面试话题笔试结束不等于整个流程结束复盘比笔试本身更重要。我当时把每道错题的知识点整理成一份笔记比如CTC路径计算、分帧公式、DP边界初始化。面试时面试官真的会顺着笔试题目往下追问。他问我“分帧时如果最后一帧不完整应该补零还是直接丢弃”我当时补了零但没想过这两种做法的优劣。后来查资料才明白补零会让模型在训练时见到大量全零帧可能导致特征分布偏移直接丢弃则会丢失尾部语音信息。工程上更稳妥的做法是调整起始偏移量让所有帧都尽量完整覆盖有效语音段。这个细节我在复盘时写了整整两页后来聊到这个问题时面试官明显比较满意。笔试后的复盘笔记后续还能用在好几个场合简历上写“熟悉语音特征提取和序列建模”时用得上面试讲项目时用得上甚至入职后真的要写特征处理代码时也用得上。所以我的建议是每道错题都按“错误原因、正确解法、知识延伸、工程联系”四个维度来整理。这样一来笔试的每一分都不会白丢它变成你长期技术积累的一部分。最后再分享一个我自己体会很深的小技巧准备笔试时把每一道简答题都当成面试题来答多写几步推导过程把你认为理所当然的假设也写出来。这样即使笔试没通过这份笔记也一定会成为你后续面试中最实在的谈资。笔试只是筛选的第一步真正决定你能不能拿到offer的是你在笔试过程中展现出来的思维方式和知识深度。语音算法这个方向入门容易精通难每一次认真复盘都是在为后面更复杂的模型和更刁钻的面试题做积累。