尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

旷视算法研究员笔试复盘:KMP、批归一化与优化算法的核心考点

旷视算法研究员笔试复盘:KMP、批归一化与优化算法的核心考点 2019年春天那会儿我正在准备找实习。简历投了一圈之后收到了旷视科技算法研究员岗的线下笔试通知。说实话看到线下笔试四个字的时候我是有点意外的因为那个年头大多数公司为了省事都已经改成线上笔试了旷视还坚持线下发卷子而且是在学校里面租教室考说明他们对候选人筛选这件事还是相当认真的。整个笔试过程给我留下的印象挺深不光是题目本身还包括考试的组织方式、题目的出题思路以及考完之后的复盘收获。这篇文章不打算写成真题大全毕竟具体题目我也记不全了而且笔试题目本身每年都在变照搬意义不大。我更想分享的是旷视算法研究员笔试到底考什么、为什么考这些、线下笔试有什么容易被忽略的细节以及如果你也想投这家公司的算法岗该怎么准备才不至于踩坑。1. 笔试概况与准备节奏2019年春招线下的真实流程1.1 投递与笔试通知旷视2019年的实习生春招比我想象中启动得早。我记得是在2月底3月初的时候公众号和牛客网上就开始挂出招聘信息网申通道开了一段时间。我投的是算法研究员岗位投完之后大概过了一周多收到了笔试通知邮件。这里有个细节值得说邮件里明确标注了线下笔试和具体教室还要求带身份证和学生证以及一份纸质简历。我当时看到要求带纸质简历就觉得这家公司挺传统的后来想想线下笔试附带简历可能意味着笔试成绩好的话简历会直接递到对应部门面试官手里流程会比纯线上筛选更高效。线下的另一个好处是考场纪律严明不太可能出现线上那种你做完了帮我交一下的操作空间。但代价就是你必须亲自跑一趟而且时间完全被锁死不能像线上笔试那样挑自己状态最好的时间段去开考。1.2 考前复习重点取舍说实话我收到通知到考试之间大概只有一周时间能准备的东西有限。我当时做了个判断旷视是做计算机视觉起家的算法研究员岗笔试大概率会涉及机器学习、深度学习和基础算法三块数学特别是概率统计和线性代数也不会少。于是我把复习重点放在了四个方面数据结构与算法排序、字符串匹配、树、动态规划这些是最常考的基础。机器学习基础损失函数、正则化、过拟合、经典分类器原理。深度学习基础卷积神经网络、批归一化、激活函数、梯度消失问题。数学基础概率分布、极大似然估计、矩阵特征值、最优化方法。后来考完回头看这个复习策略大方向没错但有个盲区——对优化算法相关的概念准备不足。那是后话后面细说。另外一个准备动作是提前问了一下已经入职旷视的学长笔试的风格。他给的反馈是旷视笔试不玩偏题怪题题目覆盖面广但都比较基础重点考察是不是真的懂而不是背了多少。他说的一句话让我印象很深他们不喜欢那种刷了几百道LeetCode但问个BatchNorm原理就懵的候选人。这句话直接影响了我后面几天的复习分配我不再死磕hard题而是花大量时间重新梳理机器学习深度学习的底层原理。2. 算法与数据结构真题从KMP到排序的实战拆解2.1 KMP next数组一道题暴露的细节功底笔试第一道大题就是字符串匹配相关具体题目是给一个模式串abacaba要求写出它的 next 数组。这道题出现在试卷比较靠前的位置分值不算特别大但我印象极深因为它考察的是最容易被忽略的细节。KMP 算法的 next 数组有多种定义方式这是这道题真正的坑。如果教材用的是最长相等前后缀长度的定义那么 next 数组是记录到当前位置为止前缀子串的最长相等前后缀长度如果用的是失配跳转位置的定义通常会对长度做减一操作或者整体移位。不同的教材、不同的老师习惯不同答案就会不一样。我考场上是按最长相等前后缀长度来算的那abacaba的前缀函数计算过程如下子串a最长相等前后缀长度为0。子串ab前缀a后缀b不相等长度为0。子串aba前缀a等于后缀a长度1再看前缀ab和后缀ba不相等所以最长长度为1。子串abac依次比较前缀a和后缀c不相等长度为0。子串abaca前缀a等于后缀a长度1所以最长长度为1。子串abacab前缀ab等于后缀ab长度2因此最长长度为2。子串abacaba前缀aba等于后缀aba长度3所以最长长度为3。所以按这个定义next 数组是{0, 0, 1, 0, 1, 2, 3}。备考的时候有个技巧KMP 的 next 数组题目千万不要只背代码模板一定要自己动手写一遍求值过程而且要同时掌握前缀函数值和失配跳转位置两种写法。考场上一旦你用哪种定义做题就在卷面上把定义写清楚这样即使跟标准答案不一样阅卷人也能看出你是真的理解而不是瞎蒙。2.2 排序算法对比笔试中反复出现的送命题排序在算法岗笔试里的地位就像是食堂的西红柿炒蛋——不一定在最显眼的位置但基本顿顿都有。旷视这套卷子里也考了排序不过问法比较有讲究不是单纯让写快排代码而是给了几段排序过程描述要求判断分别属于哪种排序算法并说明时间复杂度和空间复杂度。这种题考察的是对排序过程形态的熟悉度。比如冒泡排序每一轮相邻比较交换最大的元素像气泡一样浮到末尾。如果描述中出现相邻元素两两比较多半是冒泡。快速排序选定pivot然后把数组分成小于pivot和大于pivot两部分。如果描述中出现分治基准元素划分那就是快排。堆排序基于堆数据结构反复把堆顶元素取出放到末尾。如果描述中出现完全二叉树堆调整那就是堆排序。归并排序先把数组不断二分然后两两合并有序数组。如果描述中出现先拆后合合并有序序列那就是归并。这里我整理了一下当年复习用的对比表现在看依然很实用排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n^2)O(n^2)O(1)稳定快速排序O(n log n)O(n^2)O(log n)不稳定堆排序O(n log n)O(n log n)O(1)不稳定归并排序O(n log n)O(n log n)O(n)稳定一个容易忽略的点是快速排序最坏情况。很多人只记得平均复杂度 O(n log n)忽略了当输入已经有序且每次选择的pivot都是最值时快排会退化到 O(n^2)。旷视笔试里就有一道相关的小题问待排序数组基本有序时以下哪个排序算法效率最低选项里有快排和插入排序。答案是快排因为基本有序时插入排序效率高而快速排序如果选pivot的策略不好会频繁出现不平衡划分递归深度接近n。因此准备笔试时别只背复杂度要理解为什么复杂度是这样。比如快排的空间复杂度 O(log n) 来自递归栈的深度归并排序的空间 O(n) 来自合并过程中借用临时数组堆排序的 O(1) 空间是因为在原数组上做交换。理解了这些不管题目怎么变你都能答得上来。2.3 手撕代码题的边界处理笔试里还有一道手撕代码题具体题目我记得是给一个整数数组和一个目标值要求找出所有和为某个数的连续子数组。这类题目在LeetCode上很常见但笔试的时候有一个特殊情况容易踩坑——数组里可能有负数和零。如果数组全是正数用双指针滑动窗口就行右指针向右扩展和超过目标值时收缩左指针整个过程 O(n)。但一旦有负数这个滑动窗口的单调性假设就不成立了双指针会漏掉很多解必须换成前缀和加哈希表的做法先算前缀和数组 prefix[i] 表示前 i 个元素的和然后遍历前缀和用哈希表记录每个前缀和出现的次数对于当前位置 i 的目标就是找到之前有多少个前缀和等于 prefix[i] - target累加即可时间复杂度 O(n)。这个题本身不难但考的是你能不能识别出该用什么算法而不只是把代码写出来。我当时先把边界情况列了一遍数组为空、目标值为0、结果可能溢出、包含负数等等然后再开始写代码。写代码的时候有个小细节帮我避免了不少麻烦——变量名不用 a、b、c 这种无意义的命名而是用 prefix_sum、target、count 这种一眼能看懂的命名。阅卷人不一定会执行你的代码但一定会读你的代码写清楚比写简洁更重要。3. 机器学习与深度学习考察旷视作为CV公司的出题偏好3.1 正则化与过拟合原理题的高频考法笔试的机器学习部分有道题问的是 L1 和 L2 正则化的区别。这题在机器学习面试里几乎就是打招呼级别的题但旷视的考法多了一层——它给了一个场景某个模型在训练集上准确率99.5%在验证集上只有83%问应该优先采取什么措施。这里的核心是识别出过拟合然后正则化就是顺理成章的答案。它接着追问L1 和 L2 各有什么特点在什么场景下选哪个。L1 正则化是在损失函数上加权重的绝对值之和L2 加的是权重的平方和。两者都能抑制过拟合但原理和效果不同。L1 正则化会把部分权重推到0产生稀疏解相当于帮我们做了一次特征选择适合特征维度很高且很多特征不重要的情况。L2 正则化则是把权重整体往小里压但不会压成0适合特征维度适中、认为所有特征都有点用的情况。从优化角度理解会更清楚L1 的惩罚项在0点不可导所以梯度下降迭代时一旦权重靠近0就会被推到精确的0上L2 的梯度是线性的权重小的时候梯度也小很难精确到达0。我当时在卷子上把这个区别从几何角度解释了一遍在二维权重空间中L1 的约束区域是菱形L2 的约束区域是圆形。损失函数等值线和约束区域相切的位置菱形更容易在坐标轴上相切因此产生稀疏解圆形几乎不可能相切在坐标轴上所以权重通常不是0。用图形解释不仅简洁而且比光背结论更能体现理解深度。3.2 批归一化细节决定成败卷子里有一道关于 Batch Normalization 的题考察的点非常细致训练时和推理时BN 层用的均值和方差分别来自哪里这道题看似基础但确实能刷掉一批只背过答案没深究过的人。训练阶段BN 对每个 mini-batch 计算当前批次的均值和方差然后用它归一化当前批次的数据推理阶段没有固定的 mini-batch 概念用的是训练过程中累积的全局统计量——通常用滑动平均的方式维护的 running mean 和 running variance。难点在于为什么推理时必须用滑动平均而不是直接用当前输入算出来的统计量。我当时的理解是推理时输入往往只有一条样本单条样本的均值和方差没有任何统计意义用它做归一化会引入很大的噪声。滑动平均在训练过程中不断累积反映的是整个训练数据集的分布特征这样推理时才有稳定的参照。而且 BN 在训练时还引入了两个可学习参数 γ 和 β推理时这两个参数也是固定的。再往深一步BN 为什么能加速训练有几种解释最被广泛接受的是它缓解了内部协变量偏移让每层输入分布更稳定因此可以使用更大的学习率收敛更快。此外 BN 还有一点隐性的正则化效果因为每个 batch 的统计量有随机波动相当于给网络加了点噪声这会让训练过程不那么容易过拟合。答这种题的时候我的经验是不要只写结论要把训练和推理不一致的原因写出来。面试官看的是你有没有真正理解设计动机而不是单纯记住结论。3.3 损失函数的设计逻辑机器学习部分还有一道题考了交叉熵损失。题目给了几个常见的损失函数表达式要求识别哪个是交叉熵并解释它为什么适合分类任务。交叉熵损失的标准形式是L -Σ y_i * log(p_i)其中 y_i 是真实标签的one-hot编码p_i 是模型预测的概率分布。用它来做分类任务的核心原因是它直接衡量真实分布和预测分布之间的差异并且和 softmax 配合得非常好——softmax 先保证输出归一化成概率交叉熵再衡量概率分布的匹配程度两者结合能让梯度的计算变得非常简洁。如果从最大似然的角度看最小化交叉熵等价于最大化训练数据的对数似然也就是说交叉熵损失的背后有一个很自然的概率解释我们希望模型给真实类别赋予的概率尽可能高。还要留意一个细节多分类任务中如果真实标签是 one-hot 形式交叉熵损失其实只关心真实类别对应的那个概率值因为其他位置 y_i0相乘后是0。所以表达式可以简化为 L -log(p_c)其中 c 是真实类别。这意味着交叉熵损失实际上是在惩罚模型对正确类别的不自信预测概率越接近1损失越小越接近0损失越大而且当 p_c 接近0时损失是趋向无穷大的——这对训练来说是一种强烈的信号。3.4 深度学习基础题CNN参数量计算的陷阱笔试中出现了一个卷积神经网络参数量计算的题目看起来不算难但很容易算错。给的条件大致是输入是三通道的 224×224 图像第一层卷积用的是 64 个 7×7 的卷积核stride2padding3要求计算该层的参数量。很多人会直接算 64 × 7 × 7 3136但这只算了二维卷积核的权重漏掉了输入通道数。正确的计算方式是每个卷积核的权重是 输入通道数 × 7 × 7再加上1个偏置然后乘以输出通道数。也就是(3 × 7 × 7 1) × 64 148 × 64 9472输出特征图的尺寸其实不影响参数量因为它只是权重在空间上的共享。这个参数共享是CNN的核心思想之一也是CNN参数量远小于全连接网络的根本原因。无论输入是 224×224 还是 1024×1024只要输入通道数和卷积核尺寸不变卷积层的参数量就不变。笔试里容易坑人的点就在这里题目故意给了很多输入尺寸信息暗示你可能需要算特征图尺寸但实际上参数量的计算根本用不到。反过来如果题目问的是计算量和FLOPs那特征图尺寸就有用了因为计算量等于参数量乘以输出特征图的尺寸。这种情况下需要根据输入尺寸、stride、padding算出输出特征图的高宽output_size (input_size 2×padding - kernel_size) / stride 1代入数字(224 2×3 - 7) / 2 1 223 / 2 1 112.5。这不是整数说明实际网络里在 stride2 的卷积之前通常会先做下采样或者调整尺寸。这类题出的数字往往故意不是整数目的就是考察你算完之后能不能发现输入输出尺寸不匹配的问题从而想到实际设计时可能需要额外的填充或裁剪操作。4. 数学与优化算法粒子群这类冷门考点背后的逻辑4.1 概率统计题从贝叶斯到极大似然概率统计在算法研究员笔试里的地位很稳固几乎每套卷子都会涉及。旷视这张卷子里也有几道相关的题包括一道贝叶斯公式的简单应用题以及一道要求写出极大似然估计推导过程的题。贝叶斯公式本身不难核心公式就是P(A|B) P(B|A) × P(A) / P(B)笔试关键是理解每个量的含义以及题目给的条件该往哪里套。贝叶斯公式在机器学习里的体现就是后验概率 ∝ 似然 × 先验概率。这个视角在面试中经常会被引申比如问你为什么贝叶斯估计比极大似然估计更稳健答案就在于它引入了先验当数据量少的时候先验能起到约束作用避免过拟合。极大似然估计那道题我记得是要求对一组服从正态分布的样本做参数估计。推导过程并不复杂写出似然函数取对数对均值求导令其为0解出样本均值对方差求导令其为0解出样本方差。但有个细节是极大似然估计得到的方差是有偏的分母是 n 而不是 n-1。这个点如果能在卷子上主动指出来说明你懂的东西比题目要求的多在阅卷时会有额外的加分效果。4.2 粒子群算法为何出现在算法研究员笔试里笔试里有一道题我准备时完全没料到就是粒子群算法的原理。题目给了一段粒子群算法的迭代描述要求补充速度和位置更新的公式。粒子群算法Particle Swarm Optimization, PSO是一种群体智能优化算法灵感来自鸟群觅食行为。每个粒子代表解空间中的一个候选解粒子有位置和速度位置就是候选解各维度的取值速度决定了它下一步怎么移动。迭代过程中每个粒子记住自己历史最优位置 pbest整个群体共享全局最优位置 gbest然后根据这两个信息更新速度v w×v c1×r1×(pbest - x) c2×r2×(gbest - x)x x v其中 w 是惯性权重控制粒子保持原来运动趋势的程度c1 和 c2 分别是自我认知和社会认知的学习因子通常取2左右r1 和 r2 是 [0,1] 之间的随机数用来引入随机性。我当时在考场上看到这道题的第一反应是这不是进化计算的内容吗怎么算法研究员笔试也考这个后来复盘才想明白这其实代表了旷视对算法研究员的一种期望——他们希望你不只会调深度学习框架还要对优化这件事本身有广泛的理解。深度学习训练本质上也依赖优化算法SGD、Adam粒子群作为一种不需要梯度的全局优化方法在超参数搜索、网络结构搜索等场景里也有应用。一个合格的算法研究员不该只把自己局限在纯梯度优化的框架里。这道题给了我一个特别重要的教训准备算法研究员笔试别只看深度学习那点内容传统的启发式优化算法、概率图模型、基础数值优化方法都值得扫一遍。考的概率可能不高但一旦考到对别人来说是盲区对你是送分题这就是差距。4.3 线性代数与矩阵运算线性代数考了一次矩阵特征值的计算还有一个矩阵求导的基本题目。特征值计算本身不难都是基础操作但一旦矩阵维度变大计算就很容易出错我的建议是拿到题先看一下矩阵的结构如果能写成对角块矩阵或者三角矩阵特征值直接读出来就行少走很多弯路。矩阵求导那道题跟深度学习的联系很紧密因为反向传播本质上是链式法则加上矩阵求导。比如给定损失函数 L ||Wx - y||^2要求对 W 求梯度。展开之后就是L (Wx - y)^T (Wx - y)对 W 求导得到 2(Wx - y)x^T。这个结果在最小二乘问题里非常常见其形状是 d×d和 W 的维度一致。笔试里考这种题一方面考矩阵求导基本功另一方面也是直接为后续的神经网络梯度推导做铺垫。一个提升矩阵求导能力的小方法是记熟几个常见规律二次型对向量求导、线性变换对矩阵求导、以及链式法则在矩阵层面的应用。尤其是最后一点反向传播的数学本质就是链式法则如果能用矩阵形式把梯度表达清楚面试手推梯度的时候会非常有优势。5. 线下笔试的节奏把控与踩坑经验5.1 时间分配策略旷视这套笔试题量不算小我印象中选择题、填空题大概有30多道后面还有几道大题总考试时间是90分钟。拿到的第一件事肯定是快速翻一遍整张卷子对题目难度有个整体判断。我当时的策略是先把有把握的题一次性做掉难题用笔做上记号最后再回来啃。选择题和填空题快的5分钟内能过完遇到犹豫超过2分钟的题果断跳过——因为前面的基础题错过才是真的亏后面的大题做不出来不丢人。时间分配上我给自己定的目标是选择题加填空题控制在35到40分钟以内剩下的时间全部留给大题。这样做的好处是就算后面的大题来不及完全写完至少前面拿分的部分保住了。不过这里要提醒一个线下笔试特有的心理因素卷子放在面前你会发现旁边的人翻页很快这时候很容易产生焦虑感。我的经验是压根不要观察别人因为人家翻页快可能只是跳过了不会做的题不代表他拿的分比你多。专注在自己的卷子上才是唯一正确的事。5.2 机试与纸质卷的差异线下笔试并不都是纸质卷子旷视这次是先在机房做了一轮机试再发的纸质卷。机试环节用的是类似牛客网的在线评测系统代码题提交后立刻判分。机试的题目我记得有两道一道是数组相关的模拟题另一道是图论相关的最短路径问题。前一道比较轻松把题意搞清楚、注意一下边界条件就能过。后一道最短路径如果直接用 Dijkstra 算法就能解但数据范围给得比较大用邻接矩阵存图会超内存必须用邻接表加优先队列优化。这个环节其实很考验代码熟练度因为在线评测系统不会给你写个思路就行的机会要么通过要么不通过。备考时一定要多练手写代码尤其是直接在空白编辑器里写代码的能力——没有IDE的自动补全、没有编译提示所有东西都要靠自己。平时在LeetCode、牛客网上刷题时尽量别依赖自动补全养成在无辅助环境下写代码的习惯等到考场上就不会慌。5.3 复盘哪些题不该错考完之后我给自己做了一轮复盘客观来说有几道题是不该错的。第一道是KMP的next数组。题目考的其实就是定义和计算但我当时因为纠结题目里的next数组到底按哪种定义浪费了不少时间。现在回头看与其纠结不如直接在卷面上写明自己的定义再往下算。这算是考场心态问题平时做模拟题的时候就应该有意识地练习面对歧义题目时怎么处理。第二道是正则化那题L1和L2的区别我背得很熟但题目给了具体的过拟合场景我一开始差点只顾着写定义差点忘了结合场景分析。笔试做题和面试答题有个共同点不要太快动笔先花半分钟把题目读完把需求拆清楚再组织答案。第三道是粒子群算法完全没想到会考。这道题只能说是准备范围的疏漏不算技术问题。吃一堑长一智从那以后我准备任何算法岗笔试都会把启发式优化算法、传统机器学习、概率图模型这些非深度学习的内容也过一遍。5.4 给后来人的备考清单结合这次笔试经历我整理了一份算法研究员笔试备考清单如果目标是旷视这类以计算机视觉为核心业务的AI公司以下内容值得逐一过一遍数据结构与算法排序全部字符串匹配至少掌握KMP和BM二叉树遍历、最近公共祖先、树的直径等常见树问题动态规划从背包到区间DP都要熟悉图的最短路径Dijkstra、Floyd、Bellman-Ford要会手写。机器学习线性回归的闭式解和梯度下降推导、逻辑回归损失函数推导、SVM的拉格朗日对偶、决策树的信息增益和基尼系数、随机森林与GBDT的区别、正则化原理、过拟合的处理方法、模型评估指标准确率、精确率、召回率、F1、AUC。深度学习反向传播的矩阵形式推导、常见激活函数的优缺点、BatchNorm和LayerNorm的区别、常见损失函数交叉熵、MSE、Hinge Loss的适用场景、CNN各层参数量和计算量的计算、RNN和LSTM的梯度问题。数学基础贝叶斯公式、极大似然估计、常见分布正态、伯努利、泊松及其性质、特征值和特征向量、正定矩阵、矩阵求导、拉格朗日乘子法、梯度下降的各种变体原理。优化算法SGD、Momentum、RMSProp、Adam的原理和区别粒子群、模拟退火、遗传算法等启发式优化方法的思想凸优化的一些基本概念。这份清单不一定覆盖所有公司所有方向的考点但覆盖面已经足够广。算法研究员的笔试本质上是考察数学基础、机器学习功底、算法工程能力三者的综合水平任何一块短板都可能成为被筛掉的理由。笔试结束差不多两周后我收到了面试通知。回看整个准备过程最大的体会是笔试题目看起来杂但核心就是在检验你是不是一个有广度也有深度的算法工程师——广度是指基础知识的覆盖面深度是指对每个知识点是否真正理解到为什么。如果只刷题不思考选择题或许能蒙对但大题的推导和场景分析一定会露馅。保持这个标准去准备不管考的是旷视还是其他AI公司都能拿出有底气的表现。
返回列表