尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

旷视科技算法研究员春招笔试复盘:机器学习、数据结构与编程题全解析

旷视科技算法研究员春招笔试复盘:机器学习、数据结构与编程题全解析 写这篇文章的时候距离我参加旷视科技2019年实习生春招算法研究员在线笔试已经过去一段时间了。那次笔试给我留下的印象很深不只是因为“旷视”这个招牌更因为题目出得确实有水平——它不像很多公司那样只考八股文式的刷题而是把算法原理、工程实现和数学功底揉在一起考。如果你正准备算法岗实习或校招或者只是好奇AI公司的笔试到底考什么这篇复盘应该能给你一些参考。先交代一下背景。当时我还在读研方向是计算机视觉实验室的项目偏图像识别平时主要用Python和C对PyTorch还算熟悉。旷视科技MegviiFace在视觉领域的名气不用多说算法研究员这个岗位是我很早就关注的。春招刚开始我就从招聘渠道提交了简历没几天就收到了在线笔试通知。这里有个细节值得提醒大家投实习岗位一定要尽早下手越早投递笔试时间越充裕后面岗位也越充足。我身边有同学拖到春招尾巴才投结果很多厂的HC已经缩了。整个笔试是在线进行的用的牛客网平台限时两个半小时左右。题型分选择题、填空题和编程题三大部分。选择题以机器学习和深度学习为主穿插概率论、线性代数、信息论填空题里有一些手推公式的题比如softmax的梯度推导编程题一共三道覆盖字符串、动态规划和图论不限制语言可以Python也可以用C。整体的体量不算小时间其实挺紧张的。接下来我按模块详细拆解重点聊聊那些让我印象深刻的题目和背后考察的底层能力。1. 线上笔试的整体节奏和“软性”准备1.1 笔试环境与时间分配策略先说环境和流程这部分容易被低估。线上笔试不像现场笔试有监考但其实平台会全程监控浏览器切换和代码编辑行为。我当时的做法是提前一天睡好笔试当天找一个安静、网络稳定的房间准备了草稿纸和笔虽然在线笔试数学推导和数据结构设计在纸上先过一遍非常有用提前半小时登录平台测试摄像头、麦克风和浏览器插件是否正常把常见的代码模板快排、二分、并查集、KMP、二叉树的遍历在编辑器里快速过了一遍确保肌肉记忆还在。笔试开始后我先用五分钟通读所有题目然后按照“先做熟悉的、分值大的”原则安排顺序。我的策略是选择题和填空题争取在40~50分钟内完成剩下时间全部投入编程题。因为编程题每题的分值很高而且写不出来就是零分多选题至少还有概率蒙对一部分。1.2 选择题里的知识覆盖面旷视这份笔试的选择题覆盖面相当广涵盖的范围大致如下机器学习基础偏差与方差、过拟合、正则化常用模型逻辑回归、SVM、决策树、随机森林、GBDT深度学习基础卷积操作、感受野、BN、Dropout、激活函数损失函数与优化器交叉熵、hinge loss、SGD、Adam数学基础概率分布、期望方差、矩阵特征值和奇异值、向量求导信息论熵、交叉熵、KL散度、互信息数据结构与算法排序复杂度、字符串匹配、树和图的遍历。其中有一些题看似简单实际上有两个坑。第一道是关于逻辑回归的题目问的是“逻辑回归的损失函数是否是凸函数”。这题如果没搞明白原理会犹豫回答是凸也没错但更准确的表述是逻辑回归的负对数似然损失是关于参数的凸函数。第二道和KL散度相关的题目要求计算两个离散分布之间的KL散度这里需要记住公式内部是P乘以log(P/Q)的后验求和分母Q不能为零条件里一般会给平滑处理过的分布。这里多说一句备考算法研究员岗位不要只去刷LeetCode机器学习、深度学习的概念题往往是拉分的关键。因为编程题大家都会一些但基础概念的准确度并不是每个候选人都能保证。2. 编程题复盘从字符串到动态规划2.1 字符串题KMP的next数组到底怎么推编程题第一道就是字符串匹配问题给定一个模式串要求输出它在某个主串中出现的所有起始位置。看到这题我第一反应是直接用Python的str.find()但仔细一想要考的就是手写匹配算法如果调库写几行就交卷面试官那边肯定过不了。而且题目的数据范围给得很大主串长度10^6模式串长度10^5暴力O(n*m)直接超时所以必须用KMP或者更高效的字符串匹配算法。关于KMP我平时写代码时其实是直接调用正则表达式或者库函数但笔试让我把核心细节重新过了一遍。KMP的核心在于next数组。很多资料把next[i]定义为模式串前i个字符构成的子串中“最长相等前后缀的长度”。不过不同教材实现里next数组可能会偏移一位笔试的时候最好先在草稿纸上算一次再写进代码免得和标准模板对不上。以模式串p abacaba为例先算最长相等前后缀长度也就是前缀函数记为pi[i]表示p[0:i]这个子串的最长相等前后缀长度i0字符api0i1子串ab前缀a后缀b不相等pi0i2子串aba前缀a后缀a前缀ab≠后缀ba所以最长相等前后缀长度为1pi1i3子串abac依次比较前缀a和后缀c不等前缀ab和后缀ac不等前缀aba和后缀bac不等pi0i4子串abaca前缀a后缀a前缀ab≠后缀ca前缀aba≠后缀aca前缀abac≠后缀baca所以pi1i5子串abacab前缀a≠后缀b前缀ab后缀ab最长是2pi2i6子串abacaba前缀a后缀a前缀ab后缀ba不对ab≠ba再往前前缀aba后缀aba相同所以pi3。所以pi数组是[0,0,1,0,1,2,3]。而很多KMP实现中使用的next数组是这样的next[i]表示当模式串第i位失配时模式串回退到的位置。这就和pi数组有一个下标错位。我当时写KMP时直接用pi数组然后失配时j pi[j-1]代码简洁不容易出错。主串匹配时如果当前字符匹配成功i和j都加1如果失配j回退到pi[j-1]如果j为0则i加1。这样写提交后基本一次通过。这道题给我的经验是不要背模板要理解前缀函数的意义。如果你只会背某一种写法一旦面试官问你next[i]和pi[i]的区别或者让你推导某一个具体串的next数组可能就会卡住。而理解本质的话换任何下标体系都能迅速写出来。2.2 动态规划从“爬楼梯”到三维DP第二道编程题是一道典型的动态规划和路径计数有关。给定一个网格从左上角走到右下角只能向右或向下走计算有多少条不同路径。这题如果直接写一个二维DP状态转移方程大概是dp[i][j] dp[i-1][j] dp[i][j-1]边界条件是第一行和第一列都为1。这看起来很简单但旷视出题几乎不会这么直白。题目加了一个条件网格中有一些障碍物障碍物所在的位置不能走问路径数。这时候第一行和第一列也要根据障碍物的位置特判。如果网格第一个格子和最后一个格子有障碍物答案直接是0。我在这题上花了比较多时间不是因为难而是在于初始化的时候忽略了一个细节第一行如果某个位置有障碍物那么它右边的所有位置都是不可达的同理第一列如果某个位置有障碍物那么它下方的所有位置也都不可达。很多人在初始化时会用逐格判断但容易写成只把当前格子标记为0而忽略了后续格子的连带的不可达性。再往后我发现这道题背后还有优化的空间。因为每个格子的状态只依赖左边和上边的格子所以可以把二维dp压缩成一维数组空间复杂度从O(mn)降到O(n)。虽然笔试中不要求最优空间复杂度但这种优化思维在面试中是一个加分项。代码写完后我额外在注释里写了状态转移方程和边界条件这也是在线笔试的一个技巧——即使代码有Bug清晰的注释也能让面试官看到你的思路。2.3 图论题并查集与最小生成树第三道编程题和图相关给定一个有n个节点和m条边的无向图问最少添加多少条边可以让整个图连通。这道题的本质是求图中连通分量的个数。如果图中有k个连通分量那么最少需要k-1条边就能把整个图连通。我直接用了并查集来实现。初始化每个节点的父节点是自己然后遍历所有边对每条边上的两个节点做join操作最后统计不同根节点的个数。这里要注意路径压缩和按秩合并否则在极端情况下查询效率会退化到O(n)。我当时是这么写的class UnionFind { public: vectorint parent, rank; UnionFind(int n) : parent(n), rank(n, 0) { for (int i 0; i n; i) { parent[i] i; } } int find(int x) { if (parent[x] ! x) { parent[x] find(parent[x]); } return parent[x]; } void unite(int x, int y) { int rx find(x); int ry find(y); if (rx ry) return; if (rank[rx] rank[ry]) { parent[rx] ry; } else if (rank[rx] rank[ry]) { parent[ry] rx; } else { parent[ry] rx; rank[rx]; } } };然后主程序里读入边把所有节点unite最后用set统计根节点数量结果减一输出。这道题其实不是难题关键是看有没有掌握并查集这个数据结构的精髓以及能不能处理更大的数据范围。我当时在想如果n达到10^6m达到2*10^6递归find可能会导致栈溢出所以代码里用了迭代路径压缩这是一次比较明智的处理。另外C的输入输出建议用ios::sync_with_stdio(false); cin.tie(nullptr);优化速度Python选手也可以用sys.stdin.buffer来减少I/O时间。3. 机器学习与深度学习题的“隐藏考察点”3.1 从推导看理解深度笔试的填空和简答部分有一题对我后来的面试帮助特别大题目是“写出softmax损失函数对输入logits的梯度并说明当标签是one-hot时梯度可以化简成什么形式”。这个只看过理论但没动过手的人会写得比较痛苦。我的推导过程是设交叉熵损失L对logits z_i的梯度为∂L/∂z_i。对softmax输出p_i exp(z_i)/Σexp(z_j)结合交叉熵L -Σ y_i log p_i展开求导最终结果是∂L/∂z_i p_i - y_i。这一结论非常简洁通俗地说就是“模型预测的概率减去真实标签的one-hot值”。这个梯度形式也是反向传播中最常见的表达式几乎在任意一个分类任务里都会用到。我当时能把这一步推导做出来不是因为记忆力好而是研究生期间手动推过几次。这里有个心得笔试中凡是能直接写公式推导的题目都值得花点时间做完整。因为在线笔试的答案会被归档面试官是会翻看的。哪怕你代码题没有完全AC但推导题写得好面试时很容易拿这个作为切入点。顺带说一句这道题还延伸出一个常考概念——softmax的数值稳定性问题。当logit非常大时exp容易溢出所以实际实现要减去最大值再做exp。这个点如果能在注释或答案里提到会显得你确实有工程经验。3.2 几个高频“陷阱”选择题选择题里有很多看似不起眼但容易出错的知识点。我列举我认为最典型的三个SVM的核函数选择题目问RBF核函数对应的特征空间维度是多少。很多教材会说RBF核将数据映射到无穷维空间所以答案是无穷维。这里的理解关键是RBF核对应的隐式映射是无限维的但实际计算中不需要显式展开而是通过核技巧直接计算内积。BN层的作用某题问“Batch Normalization在训练和推理时使用相同的统计量吗”正确回答是训练时用当前batch的均值和方差推理时使用训练期间统计的全局均值和方差。我见过不少人在面试时把这个讲反导致后面整个网络流程理解出现偏差。Dropout之后的缩放问题Dropout在训练时随机丢弃神经元测试时需要乘上保留概率或者说在训练时对保留神经元的输出除以保留概率inverted dropout。旷视这道题的选项就在这些细节上做文章考察你有没有真正关心过实现细节。针对这类题目我的备考建议是不仅看经典书籍或课程还要从框架源码实现角度去理解。比如PyTorch中nn.BatchNorm2d有一个training参数这就是控制训练和推理不同行为的开关。搞清楚源码里的实际逻辑比死记硬背理论更有用。3.3 机器学习优化算法从SGD到Adam有一道题考察优化算法问SGD、Momentum、RMSProp和Adam之间的区别。选项里有一个对Adam的描述非常具有迷惑性说“Adam结合了动量方法的一阶矩估计和RMSProp的二阶矩估计并且直接使用当前梯度的平方来缩放学习率”。这看起来像是对的但Adam实际使用的是梯度平方的指数移动平均而不是当前梯度的平方。如果不了解Adam的更新公式很容易被这个选项坑到。这里我想多说一句在准备笔试时不要只记“Adam Momentum RMSProp”这个小结论。你应该自己在草稿纸上推一遍Momentumv_t β1 * v_{t-1} (1-β1) * g_t参数更新用v_tRMSProps_t β2 * s_{t-1} (1-β2) * g_t^2参数更新用g_t / (√s_t ε)Adam同时维护m_t和v_t然后再做偏差修正最终更新为m_t_hat / (√v_t_hat ε)。我当时在笔试前两周遭着花书和PyTorch源码把优化器部分过了一遍考试的时候看这类题基本十拿九稳。4. 数学基础题概率、线代和信息论的实际应用4.1 概率题先验、后验和贝叶斯有一道概率题让我记忆犹新假设某种罕见病在人群中的患病率是0.1%有一种检测手段患者检测结果为阳性的概率是99%健康人误检为阳性的概率是1%。如果一个人检测结果为阳性问其实际患病的概率是多少。这题考察经典的贝叶斯公式。设患病的先验概率P(D)0.001P(阳性|D)0.99P(阳性|健康)0.01。那么后验概率P(D|阳性) P(阳性|D) * P(D) / P(阳性) 0.99 * 0.001 / (0.99 * 0.001 0.01 * 0.999) ≈ 0.0902。也就是即使检测结果是阳性真正患病的概率也只有大约9%。这个数字反直觉但恰恰说明先验概率在贝叶斯推断中的分量有多重。笔试中类似的题型还有“已知P(A)P(B|A)P(B|¬A)求P(A|B)”本质上都是一个套路。这一块我的建议是熟练掌握贝叶斯公式、全概率公式、条件期望和一阶二阶矩的计算尤其是离散型随机变量和连续型随机变量的方差公式。另外关于极大似然估计和无偏性的小题也要会算。比如给定一组正态分布样本N(μ,σ²)求μ的极大似然估计很多熟悉机器学习的朋友都知道是样本均值但如果题中加了一个约束条件可能就要用拉格朗日乘数法了这也是常见的扩展方向。4.2 线代题特征值、特征向量和矩阵分解旷视的笔试里线性代数占比不小有一道题是关于矩阵特征值的已知一个3x3矩阵的特征值为1、2、3问这个矩阵的迹和行列式。迹等于特征值之和即1236行列式等于特征值之积即1×2×36。这道题很简单但如果矩阵不是对角化矩阵或者题目换成“已知矩阵的迹和行列式反求特征值”就需要额外条件了这是一些后续变体的方向。还有一道涉及SVD的题目问矩阵A的SVD分解中哪些量是唯一的。正确答案是奇异值是唯一的而左右奇异向量在对应奇异值相等时可能不唯一。这道题考察的是对SVD本质的理解。SVD在PCA、数据降维和许多推荐算法里都有很多应用。比如在PCA中我们可以对数据矩阵做SVD分解然后用右奇异向量作为主方向这样在数值上比直接计算协方差矩阵的特征分解更稳定。这里推荐大家反复推导PCA和SVD的关系PCA本质上是对协方差矩阵做特征值分解而SVD是对数据矩阵本身做分解。两者之间差了一个尺度因子。理解这个关系不仅笔试能用后面看很多论文里的降维方法也会顺畅很多。4.3 信息论从熵到互信息信息论的题在算法研究员笔试里很常见因为做模型训练时损失函数和信息论的关系太紧密了。有一道填空题给定两个离散随机变量X和Y的联合分布求H(X)、H(Y)、H(X|Y)和I(X;Y)。这道题计算量不大算是送分题但如果你公式不熟就很容易栽。我当时是先列出联合分布表格然后边缘化得到P(X)和P(Y)再套公式算H(X) -Σ P(x) log P(x)H(X|Y) -Σ P(x,y) log P(x|y)I(X;Y) H(X) - H(X|Y)。KL散度也是常客题目给两个离散分布P和Q让你算D_KL(P||Q)。需要关注一个细节KL散度是不对称的D_KL(P||Q) ≠ D_KL(Q||P)所以题目问哪个方向就要严格按题目给定的形式去算。另外如果Q的某个取值为0而P的对应取值不为0D_KL(P||Q)会趋于无穷实际中要对分布做平滑这也是文本分类和生成模型里常用的技巧。5. 一道让我印象深刻的算法设计题5.1 题面还原笔试过程中有一道题我印象特别深因为它看起来和“算法研究员”这个岗位关系不大其实考得很高级。题目大意是系统收到一个持续的数据流数据流元素是整数要求随时能够返回当前所有数据的中位数。数据量非常大单次插入和查询的时间复杂度都尽量要低。第一眼看上去这就像LeetCode上的“数据流中的中位数”常见的解法是维护一个最大堆和一个最小堆。最大堆存较小的一半数据最小堆存较大的一半数据。插入时先把数放入最大堆然后调整两个堆的大小保证最大堆的大小要么等于最小堆要么比最小堆大1。这样中位数可以直接从堆顶拿到。但旷视这题加了一个限制数据流的规模非常大内存有限不能把所有数据都存在堆里。这就逼着你思考近似算法和分治策略。我当时尝试了第二版解法用分桶计数的方式把数据按值域分到多个桶里同时记录每个桶的计数找中位数时先累加桶计数定位到目标桶再在桶内精确计数。这种方法的内存占用和数据范围相关但在一定范围内有效。再往后我还想到可以用分位数估计算法比如P²算法动态计算分位数的在线算法或者使用Count-Min Sketch这类概率数据结构来估计中位数。虽然笔试时间有限我没有把概率算法完整写出来但在代码注释里写了思路后来面试官聊到这道题时对讨论解法本身更为感兴趣。5.2 这类题背后的工程思维为什么算法研究员笔试要考数据流中位数这类题因为实际训练样本和线上推理过程中会碰到大量“数据太大内存装不下”的场景。比如训练一个点击率预估模型时特征数据的批量统计就需要流式方法不能把全量数据读入内存再算。同样在评估模型的AUC或者校准误差时如果样本规模到达亿级别也要考虑流式计算统计量。那道题让我体会到笔试不只是考“会不会背堆的解法”而是考“碰到新的约束条件能不能调整方案”。如果你平时刷题习惯了“默认内存无限”遇到这种题目就会比较被动。所以后来我复习的时候会有意识地自己给题目增加限制条件比如改成“内存只有多少MB”“数据流和查询请求交替随机到达”然后重新想解法。这种方式对思维训练很有帮助。6. 笔试之外这份岗位真正看重什么6.1 笔试题目只是门槛面试才是真正的分水岭从整体看旷视这次在线笔试的题目难度是中等偏上但它的意义在于筛选不过筛掉的是那些只会背答案、不深入理解原理的人。我在笔试结束后做了一件事把每一道题都复盘了一遍包括做错的、蒙对的、和编程题里写得不太干脆的。回顾后发现笔试里那些知识点几乎都在面试中被追问了。比如笔试中我推导了softmax的梯度面试时面试官直接问“如果标签是平滑labelsmooth label梯度会有什么变化”如果当时笔试没有动手推导过面试时大概率答不完整。又比如笔试里考了BN面试官就追着问“BN在推理阶段如果batch size为1怎么实现”这些都是真实会出现的情况。我的建议是参加任何一家公司的算法岗笔试都要把它当作面试复习的提纲。考完之后立刻逐题复盘会做的题想一想有没有更优解法或扩展不会做的题马上查资料弄懂。这种收益远比单纯追求“过笔试”要大得多。6.2 备考资料与方法论针对准备这类算法研究员在线笔试我的经验可以浓缩成下面几条数据结构与算法推荐《算法导论》前三部分和《剑指Offer》外加LeetCode的中等难度题。重点是数组、链表、树、图、字符串匹配、动态规划、贪心、并查集和堆。刷题时要用纸笔写思路再在编辑器里实现因为在线笔试没有IDE的智能提示代码习惯很重要。机器学习与深度学习需要把线性模型、SVM、决策树、集成学习、聚类、降维、神经网络和CNN/RNN的基础原理全部过一遍。最好是看完理论之后亲自用NumPy实现一下前向和反向传播这样才能留下真正的理解。数学基础高数、线性代数、概率论、信息论。很多内容看起来用不到但面试中做个简单推导就能露出马脚。推荐把《深度学习》花书的前四章和“数学基础”部分反复翻。动手能力参加一些Kaggle或者天池比赛哪怕只做一个项目也要走完完整的流程。写代码时注意代码风格、边界条件和异常处理这些都会在实际笔试中体现出来。6.3 在线笔试的答题技巧最后整理一些在线笔试的实操技巧都是我踩过坑之后总结的做题顺序先易后难。不要卡在一道题上超过20分钟合理时间是单选题每题1~2分钟填空题每题5分钟左右编程题每题30分钟左右。我认识的一个同学就是因为在一道选择题上纠结太久导致最后编程题根本没时间写。代码一定要跑通再交。线上平台判题严格编译错误、运行时错误、超时都有明确的反馈。写完代码先用自己构造的样例测一遍再检查边界值比如空数组、只有一个元素、已经有序、全是相同元素等情况。不要把代码写死注意输入输出的格式。有的平台要求额外的换行或空格提前看样例输出格式否则会白白丢分。善用注释来“说话”。如果编程题某个部分想了很久才写出来我会在注释里简要写下思路比如“这里用前缀函数而不是next数组是为了避免下标错位的混乱”。面试官看答卷时很可能关注到这些细节。心态上不要把笔试当成纯考试它更像一次合作解题的过程。遇到读了几遍都不懂的题先跳过最后有时间再回来看。在线笔试的容错率其实不低关键是把自己会做的题目都拿到分。现在回头看那场笔试让我收获最大的不是通过与否而是逼着自己把以前“感觉会”但“说不清”的知识点彻底搞清楚。算法研究员这个岗位的特殊之处在于笔试不是终点它只是给你接下来面试的一份自检清单。如果你正在准备类似的笔试建议把所有不确定的选项都标记下来考完不管结果如何把这些漏洞一一补上。这样即使这次没进下一家公司的机会也会因为你基础更扎实而变大。最后分享一个小技巧我在笔试前专门做了一份“易错点清单”把容易混淆的概念用一句话写下来比如“SGD用当前梯度Momentum用历史梯度的指数平均Adam用历史梯度及其平方的指数平均并做偏差修正”“BN训练时用batch统计量推理时用全局统计量”“Dropout训练时有缩放测试时不做缩放”。考前快速过一遍这份清单比临时翻书要高效得多。后来我把这个方法也用到了其他公司的笔试准备里效果都很不错。
返回列表