这篇文章记录我自毕业以来,为大模型算法岗位面试准备的一些知识。这篇笔记主要基于个人实际经历,希望可供后来者参考。
本人水平有限,文章内容不一定正确。本文的内容也难称完整,会持续保持更新。
算法基础知识
交叉熵、熵和KL散度之间的关系是什么?
设\(P\)代表实际数据的分布,\(Q\)是模型预测的分布,那么,
熵的定义: \[ H (P) = -\sum_x P(x) \log P(x) \] 交叉熵: \[ H_\text{CE}(P, Q) = -\sum_x P(x) \log {Q(x)} \]
KL散度的定义: \[ D_\text{KL}(P, Q) = \sum_x P(x) \log {P(x) \over Q(x)} \]
\[ H_\text{CE}(P, Q) = D_\text{KL}(P, Q) + H(P) \]
什么时候优化KL散度等于优化交叉熵?
如前所述,如果数据分布\(H(P)\)为常数(即不随优化过程改变),那么优化KL散度等价于优化交叉熵。
反过来,如果\(P\)随著优化过程变化,那么两个优化目标就不能等价了。
手写K-Means
TODO
如果特征维度很大而样本又少,应该怎么办?
TODO
MSE和MAE分别作为损失函数会带来什么区别?
特征存在共线性问题会有什么影响?应当如何应对。
TODO
在训练模型的过程中,我们经常遇到过拟合。有哪些常见的应对过拟合的方法?
- 数据层面:增加数据量、数据增强;
- 模型设计层面:Dropout、DropBlock、weight decay;
- 训练策略:迁移学习、部分参数微调、early stop、辅助任务、不确定性估计;
- 推理策略:test-time augmentation;
LLM数据工程
数据去重为什么重要?有哪些数据去重的方法?
- 提升训练效率:重复数据让模型浪费算力做无用功,拖慢收敛速度。
- 防止过拟合与记忆:模型会“背下”重复样本,而不是学习泛化规律,导致测试效果差,甚至引发隐私泄露。
- 缓解长尾问题:高频数据重复会加剧数据不平衡,让模型忽视低频但重要的知识。
常用的去重方法:
- 精确去重:直接比对字符串或字节,移除完全相同的样本。常用哈希指纹(如MD5)来加速。
- 近似去重(模糊去重):用于发现内容高度相似但不完全相同的文本。经典算法是MinHash + LSH。对于小规模数据,可以使用编辑距离去重。
偏好对齐方法为什么需要准备成对的训练数据?能不能只给正面数据,不提供负面数据?
我们从以下几点分析这个问题。
1. 数据集中能不能只包含正面数据或负面数据?如果只给正面数据,那么偏好对齐任务便退化为SFT任务。Jiwoo Hong等人(Hong, Lee, and Thorne 2024)指出,随著SFT过程的进行,虽然模型产生训练文本的概率会上升,但被拒绝文本的概率也随之上升。这表明只给出正面数据(只做SFT)是无法作偏好微调的。类似的,可以推测如果只降低负面数据的出现概率,那正面文本的出现概率也会下降。
2. 为什么PPO使用的是成对的正负样本,而不是为每一个回复给出一个准确的分值? - 标注噪声:从标注规范制定的角度看,个例的打分结果会带有噪声,而同时进行成对的数据标注,有助于减少标注噪声。 - 从reward model学习的角度看,reward model学习目标是正确地评估不同回复之间的相对优劣,而不是模型回复的绝对评分,因此标注过程中分数不是必要的。 - 评分的粒度问题:例如,有两个模型回复都打了3分(5分制),那这两个回复间就不能再分出优劣了吗?即使两个回复都打了同样的分数,它们之间的差异也可能提供有价值的信息。 以上讨论说明了为什么PPO为什么采用了成对正负样本进行训练。但是在项目实践中,我也经历过需要标注员标注具体分数的情况。在任务规范清晰、打分标准容易制定的前提下,规定具体的评分标准有好处:
- 详细、严谨的规范有助于减少数据中的噪声;
- 分值间的相对差异大小可以为训练过程提供细化的指导。例如,可参考LLaMa 2(Touvron et al., n.d.)在reward model的损失中加入一个margin component,要求正负样本相似时reward model的打分可以不用差距太大,反之则要求二者的打分要有显著差异。
- 最后,即使是在标注员已经提供了具体分值的情况下,我们仍要求标注员对所有回复进行排序,即使有些模型回复的分值是相同的。不同标注员的排序一致性是评价数据质量的一个重要指标。
3. 训练成本。 从训练成本的角度来看,成对的偏好训练数据更容易大规模收集(例如可以在用户点踩的时候立即给出另一个备选回复),标注成本也更低。
预训练/后训练/偏好对齐/强化学习
LoRA训练的时候,如何选择冻结的层?
LoRA本身只插入低秩适配器(Adapter)矩阵,原始权重是冻结的,因此问题的关键不在于「冻结哪些层」,而在于「对哪些层应用LoRA适配器」。常见的选择策略如下:
- 只对Attention的Q和V矩阵应用LoRA:这是LoRA原始论文的做法,在参数量和效果之间取得平衡。Q和V的调整对模型行为影响较大,足以适应下游任务。
- 对所有Attention层(Q/K/V/O)应用LoRA:当下游任务与预训练分布差异较大时(如代码生成、数学推理),对所有Attention投影矩阵都应用LoRA可以提升适配能力。
- 对FFN层也应用LoRA:FFN层存储了大量知识,在需要注入新知识或改变模型行为模式时(如长CoT训练),对FFN层也应用LoRA有帮助。
- 根据层深度选择:底层(靠近输入)捕获通用语法/句法特征,高层(靠近输出)更偏向语义和任务相关特征。因此:
- 若任务与原始领域差异不大,可只对高层应用LoRA;
- 若任务需要大幅改变模型行为,建议对所有层均应用LoRA。
- 根据参数重要性选择:可计算各层权重的Fisher Information或梯度幅值,选择重要性高的层应用LoRA,减少冗余参数。
实践中,最常见的配置是对所有Attention的Q和V矩阵应用LoRA(rank=8~64),再根据任务复杂度决定是否扩展到其他层。
PPO的critic model是做什么的,为什么需要critic model?
Critic model(也称value model)在PPO中负责估计状态价值函数 \(V(s)\),即给定当前状态(已生成的token序列),预测从该状态出发能获得的期望累积奖励。
为什么需要critic model?
- 降低梯度方差(Variance Reduction):PPO使用advantage函数 \(A(s,a) = Q(s,a) - V(s)\) 来衡量某个动作相对于平均水平的优势。直接使用reward作为信号,梯度方差极大,训练不稳定。减去baseline \(V(s)\) 后,方差显著降低。
- 提供逐位置的学习信号:reward model只在完整序列结束时给出一个奖励(稀疏奖励),而critic model可以在每个token位置估计未来回报,使得actor model在每个生成步骤都能获得学习信号。
- GAE(Generalized Advantage Estimation)的依赖:PPO通常使用GAE来计算advantage,GAE需要在每个时间步都有value预测,以平衡bias和variance的trade-off。
为什么不能直接用reward model代替critic model?
Reward model给出的是完整序列的奖励评分,而critic model给出的是当前状态的期望回报。二者角色不同:reward model是环境提供的评分信号,critic model是对该评分信号的拟合,用于降低方差。如果去掉critic model,PPO就退化为REINFORCE算法,方差会大幅增加,训练难以收敛。
为什么GRPO可以成功去除PPO的critic model?
核心改进在于用group内的相对优势替代critic model的value估计。
具体来说:
Group-based advantage估计:GRPO对同一个prompt采样多个回复(一个group),然后用group内各回复的reward相对于group均值的标准化分数作为advantage: \[A_i = \frac{r_i - \text{mean}(r_1,\dots,r_G)}{\text{std}(r_1,\dots,r_G)}\] 这本质上是用group内的其他样本作为baseline,替代了critic model的value估计。
为什么可以去掉critic model?
- Critic model的作用是提供baseline以降低方差。GRPO利用同一prompt下多个采样结果的reward分布来构造baseline,同样能达到降低方差的效果。
- 省去critic model意味著减少了约一半的训练参数(value model通常与policy model规模相当),显著降低了显存和计算开销。
- 避免了critic model训练不稳定或拟合不准确带来的额外噪声。
GRPO的局限:GRPO依赖于group内样本的多样性。如果group内所有回复的reward相近(方差小),则advantage信号微弱,训练效率降低。这也是后续DAPO提出Dynamic Sampling(丢弃reward方差为0的group)的原因。
GRPO方法在后续其它的工作中得到哪些改进?
DAPO:
- Clip higher:DAPO观察到训练过程中,actor model的entropy在不断降低,导致reward趋同且探索不足,故提出修改PPO-Clip策略,允许clip的下界和上界设置不同的值。通过提高上界,可以鼓励探索。
- 移除KL散度。DAPO认为在长CoT训练的过程中,模型参数显著偏离原模型是寻常的,而且移除KL散度项不会严重影响效果;
- Dynamic Sampling:GRPO需要group内有reward差异,否则当前组内数据就不会产生梯度。如果一组rollout没有reward variance,DAPO会直接将其丢弃。
- Token-Level Loss:原GRPO会进行sequence-level的loss平均,这会导致长回复的sample中,每个token的权重变小。DAPO采取token级别的平均,有利于长思维链学习。
- Overlong Reward Shaping:GRPO通常采用truncation和简单的超长惩罚。DAPO提出length-aware penaly,在超长时给予线性的惩罚(从0线性降低到-1)。
GSPO:
- 序列级重要性采样:GRPO逐token算重要性权重,长序列方差高,噪声累积;GSPO用整个序列的联合概率比(几何平均),所有token等权重;
- 序列级裁剪与奖励:裁剪和奖励分配都改为全序列级别;
详细介绍Clip higher这个技巧。
DAPO采用的目标函数具备这样的形式: \[ \begin{aligned} \mathcal J(\mathbf \theta) &= \mathbb E \left[ \frac{1}{\sum_{i=1}^G|o_i|} \sum_{i=1}^G \sum_{t=1}^{|o_i|} \min(r_{i, t}(\mathbf \theta) \hat A_{i, t}, clip( r_{i, t}(\mathbf \theta), 1 - \epsilon_\text{low}, 1 + \epsilon_\text{high} )\hat A_{i, t} ) \right]\\ \end{aligned} \] 我们分情况讨论$ (r_{i, t}() A_{i, t}, clip( r_{i, t}(), 1 - , 1 + )A_{i, t} )$这个核心算式:
- 如果\(r_{i, t}(\theta)\)处于上下截断界限之间,公式简化为\(r_{i, t}(\mathbf \theta)\hat A_{i, t}\)这样的简单情形,我们不做更多讨论;
- 如果\(r_{i, t}(\theta)\)被截断为\(1 - \epsilon_\text{low}\),此时policy model比reference model更不偏好使用当前action。公式变成\(\min(r_{i, t}(\mathbf \theta) \hat A_{i, t}, (1 - \epsilon_\text{low})\hat A_{i, t})\),此时如果\(\hat A_{i, t}<0\),\(\theta\)的梯度就被截断,阻止policy model进一步降低当前action的输出概率;
- 如果\(r_{i, t}(\theta)\)被截断为\(1 + \epsilon_\text{high}\),此时policy model比reference model更偏好使用当前action。公式变成\(\min(r_{i, t}(\mathbf \theta) \hat A_{i, t}, (1 + \epsilon_\text{high})\hat A_{i, t})\),此时如果\(\hat A_{i, t} > 0\),\(\theta\)的梯度就消失,阻止policy model进一步增大对当前action的偏好;
DAPO将\(\epsilon_\text{low}\)和\(\epsilon_\text{high}\)设置为不同的值,并适当提高\(\epsilon_\text{high}\),从而允许模型在\(\hat A_{i, t}\)为正的时候,减少对提高“exploitation token”概率的限制,从而鼓励exploitation。
有时关闭DAPO的dynamic sampling,效果反而更好。为什么?
因为DAPO的重新采样改变了数据分布。例如模型已经高概率答对了某个问题,但dynamic sampling会重新采样,直到出现负例,导致模型过度训练。
为什么要做偏好对齐?只做SFT不行吗?
只做SFT不做偏好对齐经常会导致一些问题。
1. 从训练目标来看:预训练、SFT任务本质上都是要求模型预测下一个token,而这与具体业务场景的目标——例如”以安全和有益的方式遵循人类指令”——是不一致的(Ouyang et al. 2022)。这种训练目标的不对齐导致模型容易产生不安全、不忠实或有害的信息。 2. 曝光偏差问题:在预训练、SFT阶段,模型只是在训练数据的分布上进行下一个token的预测。一些基于强化学习的偏好对齐任务提供了一种在模型自身预测分布上进行训练的方法(Ranzato et al. 2015),缓解了曝光偏差问题。
为什么SFT后的大模型会有幻觉?
模型的幻觉是指模型误认为自己能感知到一些实际感知不到的东西,或者以非常肯定的态度给出错误信息。例如,问”现在是几点钟?“,模型在不接入外部插件的情况下回答”现在是下午三点”,这便属于模型幻觉。
预训练、SFT任务本质上都是要求模型预测下一个token,而这与AI应用的目标——“以安全和有益的方式遵循人类指令”是不一致的(Ouyang et al. 2022)。这种训练目标的不对齐导致模型容易产生不安全、不忠实或有害的信息,例如导致模型幻觉。
什么是对齐税?如何减少对齐税?
在经过偏好对齐后,模型的通用能力可能下降,比如可能出现更多地生成不适当的”拒绝回答”现象,这一问题被称为”对齐税”。 那么,我们要如何减少”对齐税”,让经过RLHF的模型尽量保持RLHF之前的能力呢?
- 训练时不要偏离reference太多:在PPO算法中,除了reward model、policy model,还有一个reference model(同样由SFT后的模型初始化得到)。PPO算法会限制policy model不过分偏离reference model。这可以视为一种限制policy model能力偏移的机制。(尽管reference model的作用不止于此。)InstructGPT(Ouyang et al. 2022)指出通过在PPO更新步骤中混入增加预训练分布的对数似然可以大幅降低模型的能力衰退。
- 强化学习/偏好对齐不一定带来对齐税:尽管多数研究未能消除偏好对齐对模型性能的损害,但也存在证据表明负对齐税是可能实现的。OpenAI对大模型数学答题能力的一项研究(Lightman et al. 2023)显示以合适的reward model设计为前提,模型的数学回答正确率能够提升。尽管这项工作是针对数学任务进行的,但这一结论有可能可以推广到一般的文本生成任务。
SFT过程中会出现灾难性遗忘吗?如何应对灾难性遗忘?
一般我们在项目中会通过SFT来获得一个面向专门领域和特定任务的模型,例如心理诊疗模型、法律咨询模型、医疗建议模型、论文修改模型。SFT的过程中大模型很容易忘记对象任务以外的知识,损失一部分通用能力。就目前的技术来看,灾难性遗忘问题是不可避免的,但可以通过以下方法缓解:
限制偏离基模的程度:使用基模作为教师模型,引入KL散度约束,使模型的token分布不要过于偏离预训练模型。 数据重放:一种直接的做法是在SFT阶段随机地采样一部分预训练数据。(经验上看,预训练数据的采样比例在SFT阶段会起到比较大的影响。)
部分参数微调:与全量微调不同,可考虑采用LoRA、Soft Prompt等方法,通过调控参与训练的参数量来控制SFT的拟合程度。(个人实践中还是全量微调比较多。)
其它训练参数的调整:可以尝试调整学习率、epoch数等参数,使用early stop等技巧减少过拟合和遗忘现象。一般训练13个epoch、损失降到0.50.3以下就好。训练太多,训练集拟合得太厉害也不是好事。
除了在训练阶段尽量缓解灾难性遗忘问题,去弥补训练后已经发生的遗忘也是一种可取思路。以下是一些工程上可以选择的方法:
外部知识库/知识图谱/检索增强:实际上指望大模型记住各种各样琐碎的知识是十分困难的。相比之下,赋予大模型随时调用外部知识库是一种弥补知识缺陷的有效方法。
一些相关的学术工作:LoRAMoE(Dou et al. 2024)通过冻结主干网络,迫使LoRA利用一部分世界知识来缓解灾难性遗忘。
当前RLHF的流程有什么值得优化的地方?
这是一个开放性问题。言之有理地回答即可。
个人认为可以讨论的点:
- 更细粒度的reward:如Let’s Verify Step by Step(Lightman et al. 2023)一文指出的那样,偏好对齐可有更细粒度的标注,给出更准确的reward反馈。现在普遍常见的偏好数据只针对完整的回答给出标注,而不是针对每一个句子给出标注。相比于标注整个回答的偏好,仅标注出第一个有问题的句子/步骤不会增加更多的标注代价,但能够提升reward信号的精细程度。这样的细粒度偏好数据是目前比较少见的,有待建设;
- BT模型的优化和精细化:大多数偏好对齐方法采用了BT模型。BT模型要求reward model拉开正负样本的得分差距,但并没有考虑到正负样本间差异以及偏好程度差异的强弱问题。直觉上,对于回复相似,偏好差异不明显的情况,应该给与比较低的权重;对于回复差异大,偏好对比明显的情况,应该给与比较高的权重。一些工作(Touvron et al., n.d.; Meng, Xia, and Chen 2024)尝试了人为设置规则或调整超参来调整BT model的margin,但也许还有自适应地调整BT model的margin且取得更好对齐效果的方法存在。
旋转位置编码相对于原本的绝对位置编码有何改进?
TODO
为什么Transformer模型要使用LayerNorm而不是BatchNorm。
TODO
介绍RMSNorm。
观察到LayerNorm的成功在于缩放的稳定性,而中心化操作是可有可无的。故RMSNorm选择只进行缩放,去除了中心化操作,减少了计算开销;
import torch
import torch.nn as nn
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6):
super().__init__()
self.eps = eps
# 可学习的缩放参数 Gamma,初始化为全 1
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x 的形状通常是 (batch_size, seq_len, hidden_size)
# 1. 计算平方后的平均值,保留最后一个维度 (keepdim=True)
variance = x.pow(2).mean(-1, keepdim=True)
# 2. 计算 1 / RMS(x)
# 使用 torch.rsqrt(t) 相当于 1 / torch.sqrt(t),计算速度更快
rsqrt = torch.rsqrt(variance + self.eps)
# 3. 归一化并乘上可学习参数
return x * rsqrt * self.weight请介绍几种你熟悉的tokenization方法。
TODO
Encoder-Decoder结构、Prefix LM和Causal LM有什么不同?
T5论文的Figure 4很好地展示了三者的不同。 Encoder-Decoder结构下,Encoder的输入token间是两两互相可见的,Decoder的attention mask则是单向可见的。
Prefix LM和Causal LM的结构相近,区别是Prefix LM中,前缀文本内的token是两两互相可见的,前缀文本后的token是单向可见的;而Causal LM中所有token都是单向可见的。
Prefix LM和Encoder-Decoder相比是类似的,都允许一部分token之间两两完全可见,但Prefix LM好比是实现了Encoder和Decoder的参数共享,减少了参数量。
当下Causal LM是被大模型广泛采用的架构。虽然Encoder-Decoder和Prefix LM的双向注意力理论上有优势,但仍然被大模型领域所”淘汰”。这就引出了另一个问题,“为什么大模型都采用Causal LM结构”
为什么大模型都采用Causal LM结构?
- KV-Cache优化:Encoder-Decoder和Prefix LM都不易采用KV-Cache优化,而支持KV-Cache的Causal LM对推理加速更友好;
- 训练效率:Encoder-Decoder架构模型的Pipeline Parallel难以优化。即使模型架构能带来理论上的性能优势,但是却带来昂贵的训练代价的话,那么这种模型结构就会输给那些更能充分利用算力的模型;
- 轨迹依赖:OpenAI使用Causal LM成功实现了GPT系列大模型,这使得后来者都更倾向于采用相近的配置进行实验。
Qwen 3.5有哪些重要的优化点?
- 混合注意力机制:Gated Delta Network + 标准注意力。其中75%的层采用了混合注意力;
- 极致稀疏的MoE架构,极低的激活比。397B激活17B,35B激活3B;
- 原生多模态+early fusion,使用视觉token,而不是文本模型+外挂视觉;
- 原生多token预测支持
讲讲MOE的原理,为什么MOE的参数量大但计算量不大?
核心思想是将FFN层替换为N个专家网络+门控路由器,每个token只路由到Top-k个专家(通常K=1或者2),这就实现了控制计算量,但增大参数量。
路由的计算: \[ g(x) = \text{softmax}(W_g \cdot x) \] 根据上式的结果选择Top-K个专家,加权混合。
(DeepSeek-V3引入共享专家,共享专家一定会被激活,负责通用知识,路由专家由Top-K决定是否激活,负责领域知识。)
MOE的负载均衡怎么做?
为了避免训练时路由器总是把token分给少数专家,引入Aux Loss: \[ L_\text{aux} = \alpha \cdot N \cdot \sum_{i=1}^N f_i \cdot p_i \] 其中\(f_i\)是专家\(i\)被选中的token比例,\(p_i\)是专家的门控概率。
专家被均匀分配时,上式取得最小值,最小值为\(1\)。
为什么不是\(p_i * p_i\),而是\(f_i * p_i\)呢? 因为MOE使用Top-K选择专家,因此存在\(p_i\)比较小(只是略大于没有选中的专家),但是\(f_i\)很大的情况。因此需要结合实际频率\(f_i\)和预测的权重\(p_i\).
至于\(f_i * f_i\)肯定是不行的,因为\(f_i\)不可导。
为什么MOE使用Top-K而不是随机采样呢?
- 随机采样可能会导致专家“同质化”,因为每个专家都有概率在不应该被路由的时候被路由,不得不学习一些通用知识;
- 随机性会给并行化的训练工程带来一些麻烦。
MOE路由是batch维度的还是token维度的?
- Batch维度:同一batch内所有token走同一个专家,引入了batch内的互相干扰,不合理。
- Token维度:合理,但是实现复杂,需要All-to-All通信。例如,先将
(batch_size, seq_len, hidden_dim)的矩阵展平为(batch_size * seq_len, hidden_dim),即不考虑batch的概念,然后分别计算每个token的去向,将输入送到对应的计算设备上。
Agent设计
RAG
讲一讲NDCG(Normalized Discounted Cumulative Gain)这个reward/metric。
在介绍NDCG之前,先需要了解DCG和IDCG。
DCG:设召回模型返回n条结果, \[ DCG = \sum_{i=1}^n \frac{ 2^{\text{rel}_i} - 1 }{ \log_2(i+1) } \]
- \(\text{rel}_i\)表示第\(i\)个位置的搜索结果的相关性;我们可以用标注数据来定义相关性,如果第\(i\)个结果在标注的ground truth中,就让它的相关性为1,否则为0.
- 分子:相关性越高,贡献越大
- 分母:位置越靠后,折扣越大,按\(\log\)衰减。这是因为我们假设用户更关心排序靠前的结果。
IDCG:把所有结果按最佳排序(相关性从高到低)时的 DCG。
NDCG:\(\text{NDCG} = {\text{DCG}\over \text{IDCG}}\). 可以看出\(0 \le \text{NDCG} \le 1\). \(0\le \text{NDCG}\)是显然的。而\(\text{NDCG} \le 1\)可以这样理解:
假设存在\(i < j, g_i > g_j\),其中\(g_i, g_j\)是DCG的分子。那么交换这两项带来DCG的差值为: \[ \begin{aligned} \Delta &= {g_i \over \log_2(j+1)} + {g_j \over {\log_2(i+1)}} -({{{g_i} \over {\log_2(i+1)}} +{ g_j \over {\log_2(j+1)}}})\\ &= {g_i - g_j\over\log_2(j+1) } + {g_j - g_i\over \log_2(i+1)}\\ &= (g_j - g_i)(\frac{1}{\log_2(i+1)} - \frac{1}{\log_2(j+1)})\\ & > 0 \end{aligned} \]
因此DCG这类问题的最佳排序方案就是为高权重的位置分配高价值的结果,因此\(\text{NDCG} \le 1\).
CoT
你是如何构造CoT的?
这个要结合实际项目作答。可供参考的简单回答:
- 数据层面:
- 合成数据:利用商用模型搜集合成数据,生成符合特定规则的求解步骤;
- 规则与模板引导:对于垂直领域任务,可以在prompt中设计对应规则模板;
- 数据清洗与过滤:利用verifier或规则检查,滤除错误数据;
- 训练层面:
- SFT:基于prompt、CoT、answer三元组构造训练数据;
- RL方法:可引入过程奖励提升CoT的准确率;利用可验证的任务设计和奖励设计,获得可靠的CoT;
- 推理层面:
- 采样多条CoT,利用Self-Consistency方法选出最优解,或者利用Tree of Thoughts方法进行回溯搜索。
多模态大模型
你能列举几个常见的从视觉编码器的图像特征映射到大模型文本特征的方法吗?
各种多模态融合模块的优缺点? (注意)Q-Former容易破坏visual token之间的空间关系。
如果让你从头开始训练一个多模态大模型,你会如何设计模型结构、训练流程和项目流程?
对于多模态大模型,提高图像的分辨率是十分重要的。为什么现在的大模型往往使用如256x256这类低分辨率的图像,无法做到高分辨率呢?
你观察到GPT4-V有哪些问题?为什么会存在这些问题?
计算机视觉相关问题
为什么要对图像做预处理?
- 预处理将数据变换到统一的格式,方便模型学习;
- 通过提前提取有用的特征,裁剪出感兴趣区域来加快计算;
ViT模型中是如何对位置信息进行编码的?
TODO
请比较Transformer和CNN
TODO
算法题
接下来简单记录一些面试过程中实际遇到的或其它面试经验推荐的题。
在面试过程中要求现场手写的算法题常常不会很难,但作答过程中要思路清晰,能够有效地与面试官沟通,尽量减少试错次数,一次通过。
答题之前记得问清楚数据规模,时间复杂度和空间复杂度需求。
- ⭐最长回文子串
- ⭐二叉树的最近公共祖先
- ⭐⭐两数之和变体:设A、B是两个有序数组,找出所有\(i, j\)满足
A[i] + B[j] = k,要求常数空间复杂度,线性时间复杂度。
此题需要注意数组不是严格单调的,即可能连续出现若干个相同的数。编写双指针法时需要注意边界条件,避免漏掉一些解。 - ⭐⭐三数之和:由两数之和扩展而来的一道题。
- ⭐滑动窗口求最大值:要求\(O(n)\)时间复杂度。
- ⭐top k算法:写出\(O(n\log k)\)的基于堆的方法并不困难,但最好能进一步掌握基于快排的\(O(n)\)时间复杂度的解法。
- 🌙岛屿数量:实际上属于求连通域数量的算法,可用DFS/BFS/并查集方法解。
- 🌙反转每对括号间的子串:考察栈的用法。也可以不用栈来解。容易想到\(O(n^2)\)复杂度的算法,但实际也存在\(O(n)\)的解法。
- 🌙搜索选择排序数组:考察二分法。需要做题者注意到选择排序数组的特性。
- 🌙寻找两个正序数组的中位数(困难):更一般的,可以推广为寻找两个正序数组的第k小的数。考察二分法。需要思考应该对哪个量做二分查找。
- 🌙NER的BIO形式标注转start,end形式(例子 38):以命名实体识别(NER)任务为背景的简单算法题。
- 🌙马在棋盘上的概率:可以用动态规划的方法求解,时间复杂度为\(O(n^2k)\),这里\(n\)为棋盘大小,\(k\)为步数。还可以想到本题所执行的动态规划步骤实际上是一种卷积,且每一步的卷积核都是相同的,因此本题可以用快速幂的方式进行优化。
- 🌙整数拆分:将整数\(n\geq 2\)拆分为任意个正整数的和,使其乘积最大。容易想到基于动态规划的解法,但不容易给出\(O(1)\)时间复杂度的,基于数学分析的解法。
- 问题延伸:如果题目改为将整数\(n\geq 2\)拆分为任意个正数的和,使其乘积最大。该如何分析?参考例子 39
- ⭐给定一个字符串和一个词典,问字符串能否由词典中的单词拼接而成。用DFS可解。可考虑引入缓存等策略进行优化。
开放性问题
这一节记录一些自己被问到的开放性问题。问题具体的答案还是需要结合自身经历才行。
在个人技术成长方面,你有什么规划?
一般可以列出自己最近感兴趣的论文和想做的方向,与团队业务相结合就好。
如何平衡团队的业务目标与个人的技术成长?
面试官问出这个问题的时候,基本意味著这个团队是业务导向的,技术上不太强。
ε=(´ο`*)))
回答“优先完成团队目标,业余也会保持对技术的关注”即可。
为什么想要离职/看新机会?
- 仅仅是解释前东家的缺点是不够的。每个公司有每个公司的缺点,不能保证下一家没有相同的问题。一般可以将问题解释为当前公司平台无法提供更好的发展,而现在在看的机会能补充这方面的不足。最好不要表现出自己是当前工作上遇到困难而离职的,而应尽量表现出自己的事业当前处于上升期,但由于某些原因(工作地点、待遇、发展方向、职业规划)而不得不离开。这时可以顺带说说自己有多么适合这个新岗位。重要的是表现出自己与新公司是一个互相成就的关系。
- 说清楚想去哪里,比说为什么离开更重要。
请介绍一下过往最困难的一个项目。
你当前采用的项目方案的亮点是什么?你对项目最重要的贡献是?
是什么使你在项目中具有不可替代性?或者是什么使你具备比他人更高的价值?
拿到offer后的标准问题
拿到Offer后,记得问以下重点问题。可以不直接问,但也需要间接地通过各种渠道去了解。要了解的包括但不限于:
- 绩效的考核方式,KPI的考核情况
- 有没有末位淘汰
- 有没有轮流背D绩效
- 绩效工资占縂工资比例
- 绩效有没有强制分布
- 绩效如何影响年终奖
- 股票和期权的行权规则
- 竞业协议
- 是否需要背竞业协议
- 竞业协议是否与期权等权利挂钩
- 工作强度:可以开门见山地询问新工作的工作强度如何
- 总包、总包构成和职级
- 总包构成:
- 总回报预计多少
- 其中多少是现金,多少由股票/期权构成?
- 公积金缴纳比例
- 职级:公司的职级是如何划分的,如何与互联网的“标准职级”对应?
- 总包构成:
一些经验和感想:
- (不是绝对的)猎头告诉你某公司不加班、965……基本不太可信,还是需要额外信息佐证。
- 竞业协议是不公平的。如果你签了竞业协议,公司就有权阻止你入职同行业内的任何公司,并在你离职后提供一些微小的补偿。但相反的,假如你主动离职去读博、考公,主动希望公司启动竞业协议,公司却可以选择不启动竞业。主动权总是在公司手上。 某些公司,例如拼多多,有很大的概率启动竞业协议,并且在员工离职后派人调查其去向;所以这个问题一定要在入职之前了解清楚。
总结
笔记的重要性:只学习不总结就容易忘。不做笔记不行啊。最好每次面试过后,立即将记录下来,时不时重新过一遍。
平时多练习,多刷题,保持手感。有的公司(例如Minimax)的算法测试会期望给出最优解,面试时 即使把题目做出来了,也可以问一下需不需要继续改进,提一提能想到的改进点。
附录
智力题
本节记录我收集到的一些代码题或者智力题。
两个硬币的后验概率
有两个硬币,一个硬币的两面都是正面;另一个是正常硬币:一面是正面,另一面是反面;
随机选取其中一枚,抛掷五次,都是正面。问这枚硬币是正常硬币的概率是?
解:设\(A=\text{抛掷五次硬币都是正面}\),\(B={这是一枚正常硬币}\)
题目要求\(P(B|A)\)
\[ \begin{aligned} P(B|A) &= P(A|B) P(B) / P(A)\\ &= 0.5^5 \times 0.5 / P(A) \end{aligned} \]
其中
\[ \begin{aligned} P(A) &= P(A|B)P(B) + P(A|\neg B)P(\neg B) \\ &= 0.5^5 \times 0.5 + 1 \times 0.5 \\ \end{aligned} \]
所以
\[ \begin{aligned} P(B|A) &= 0.5^5 \times 0.5/ P(A) \\ &= \frac{0.5^5 \times 0.5} {0.5^5\times 0.5 + 1\times 0.5}\\ &= \frac{1}{1 + 32}\\ &= \frac{1}{33} \end{aligned} \]
NER标注格式转换
描述: 在命名实体识别(NER)任务中,BIO标注方式是常用的标注格式之一。但在某些应用场景下,我们可能需要将BIO格式转换为start,end形式的标注。
请你实现一个Python函数,将BIO格式的NER标注转换为start,end形式。
函数输入:
- tokens: 一个字符串列表,表示分词后的句子
- bio_tags: 一个字符串列表,表示对应的BIO标注
函数输出: 一个字典列表,每个字典包含以下键:
- ‘entity’: 实体文本
- ‘start’: 实体开始位置
- ‘end’: 实体结束位置
- ‘type’: 实体类型
示例:
输入:
tokens = ['John', 'lives', 'in', 'New', 'York', '.']
bio_tags = ['B-PER', 'O', 'O', 'B-LOC', 'I-LOC', 'O']输出:
[
{'entity': 'John', 'start': 0, 'end': 1, 'type': 'PER'},
{'entity': 'New York', 'start': 3, 'end': 5, 'type': 'LOC'}
]请实现函数bio_to_span(tokens, bio_tags)来完成这个转换任务。
实体的end索引应该是实体最后一个词的索引+1
对于单个词的实体,start和end的差值应该是1
需要正确使用连续的多个词组成的实体
def bio_to_span(tokens, bio_tags):
tokens.append('<eos>')
bio_tags.append('O')
start = 0
end = 0
entity = []
typ = None
ret = []
for i, (t, b) in enumerate(zip(tokens, bio_tags)):
if b.startswith('B-'):
start = i
entity = [t]
typ = b[2:]
elif b.startswith('I-'):
assert typ == b[2:], '{} != {}'.format(typ, b[2:])
elif b == 'O':
if len(entity):
end = i
ret.append({
'entity': ''.join(tokens[start:end]),
'start': start,
'end': end,
'type': typ
})
entity.clear()
else:
raise Exception("Invalid BIO label: {}".format(b))
return ret 接下来用一些测试用例检查给出的算法的正确性。在请求面试官检查之前,先自己编写一些测试用例是一个好习惯。
查看测试用例
test_cases = [
# case 1
{
"input": (["我", "爱", "北京", "天安门"], ["O", "O", "B-LOC", "I-LOC"]),
"output": [{'entity': '北京天安门', 'type': 'LOC', 'start': 2, 'end': 4}]
},
# case 2
{
"input": (["他", "是", "一位", "来自", "中国", "的", "医生", ",", "名叫", "张伟"],
["O", "O", "O", "O", "B-LOC", "O", "B-PROF", "O", "O", "B-PER"]),
"output": [{'entity': '中国', 'type': 'LOC', 'start': 4, 'end': 5},
{'entity': '医生', 'type': 'PROF', 'start': 6, 'end': 7},
{'entity': '张伟', 'type': 'PER', 'start': 9, 'end': 10}]
},
# case 3 (空实体)
{
"input": (["今天", "天气", "真好"], ["O", "O", "O"]),
"output": []
},
# case 4
{
'input': (
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'],
['O', 'B-PER', 'I-PER', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'O', 'B-LOC', 'O']
),
'output': [{'entity': 'bc', 'type': 'PER', 'start': 1, 'end': 3},
{'entity': 'efg', 'type': 'ORG', 'start': 4, 'end': 7},
{'entity': 'i', 'type': 'LOC', 'start': 8, 'end': 9}]
},
# case 5 (空序列)
{
'input': (
[], []
),
'output': []
},
# case 6 (只有O标签)
{
'input': (['a', 'b', 'c'], ['O', 'O', 'O']),
'output': []
},
# case 7
{
'input': (
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
['B-PER', 'I-PER', 'O', 'B-PER', 'O', 'B-PER', 'I-PER', 'I-PER']
),
'output': [{'entity': 'ab', 'type': 'PER', 'start': 0, 'end': 2},
{'entity': 'd', 'type': 'PER', 'start': 3, 'end': 4},
{'entity': 'fgh', 'type': 'PER', 'start': 5, 'end': 8}]
}
]
for test_case in test_cases:
out = bio_to_span(*test_case['input'])
assert len(out) == len(test_case['output']), 'Length inqueal! {} != {}'.format(out, test_case['output'])
for o1, o2 in zip(out, test_case['output']):
for k in o2.keys():
assert o1[k] == o2[k], '{} != {}'.format(o1, o2)
print('所有测试用例均通过。')例子 39 🌙如何将整数\(n\geq 2\)拆分为任意个正数的和,使其乘积最大?
设\(n\)被拆分为\(k\)个数,容易证明每个数都为\(n/k\)时乘积最大。(先证明\(k=2\)时,\(n\)应该被拆为\(\frac{n}{2} + \frac{n}{2}\);然后通过构造条件满足数学归纳法来证明。)
加下来,设将\(n\)拆为\(k\)份,可知其乘积应为\(f(n, k) = \left(\frac{n}{k}\right)^k\). 先不考虑\(k\)应为正整数的问题,令\(k \in \mathbb R^+\). \(f(n, k)\)是一个连续函数。
\[ \begin{aligned} f(n, k) &= e^{\ln \left(\frac{n}{k}\right)^k} \\ &= e^{k\ln n - k \ln k } \end{aligned} \]
\[ \begin{aligned} \frac{\partial f}{\partial k} &= e^{k\ln n - k \ln k }\cdot (\ln n - \ln k - 1) \end{aligned} \]
\[ \begin{aligned} \frac{\partial f}{\partial k} = 0 & \Rightarrow \ln n - \ln k - 1 = 0\\ & \Rightarrow k = \frac{n}{e} \end{aligned} \]
容易看出\(\frac{\partial f}{\partial k}\)随著\(k\)的增加而单调上升。因此\(k=\frac{n}{e}\)是函数的极大值点。但是\(k\)需要是整数,因此我们可以考虑取\(k=\lceil \frac{n}{e} \rceil\)或\(k=\lfloor \frac{n}{e}\rfloor\),取其中使乘积最大的\(k\)即可。
例子 40 求斐波那契数列1、1、2、3、5、8……的通项公式。
当\(n\geq 3\)时,有\(F_{n} = F_{n-1} + F_{n-2}\). 写成矩阵形式为
\[ \begin{pmatrix} F_n\\ F_{n-1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} F_{n-1}\\ F_{n-2} \end{pmatrix} \]
于是可得
\[ \begin{aligned} \begin{pmatrix} F_n\\ F_{n-1} \end{pmatrix} &= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} F_{n-1}\\ F_{n-2} \end{pmatrix} \\ &= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^2 \begin{pmatrix} F_{n-2}\\ F_{n-3} \end{pmatrix} \\ &= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^{n-2} \begin{pmatrix} 1\\ 1 \end{pmatrix} \\ &=\mathbf A^{n-2} \begin{pmatrix} 1\\ 1 \end{pmatrix} \end{aligned} \]
\(\mathbf A= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}\)的特征值为\(\lambda_1 = \frac{1 - \sqrt 5}{2}\), \(\lambda_2 = \frac{1 + \sqrt 5}{2}\),对应的特征向量为 \(\vec x_1 = (\frac{1 - \sqrt 5}{2}, 1)^T\), \(\vec x_2 = (\frac{1 + \sqrt 5}{2}, 1)^T\)
特征值的求解方法
设\(\lambda\)是\(\mathbf A\)的特征向量,\(\vec x\)是对应的特征向量,则有
\[ \mathbf A \vec x = \lambda \vec x \]
从而得到\(\lambda \mathbf E - \mathbf A\)的秩为0.
\[ \left| \begin{matrix} \lambda - 1 & -1 \\ -1 & \lambda \end{matrix} \right| = 0 \Rightarrow \lambda ^2 - \lambda - 1 = 0 \]
从而得到两个解:\(\lambda_1 = \frac{1 - \sqrt 5}{2}\), \(\lambda_2 = \frac{1 + \sqrt 5}{2}\)
将\(\lambda_1, \lambda_2\)的值代入到\((\lambda \mathbf E - \mathbf A)\vec x = \vec 0\)可解得两个特征向量
\[ \vec x_1 = (\frac{1 - \sqrt 5}{2}, 1)^T \]
和
\[ \vec x_2 = (\frac{1 + \sqrt 5}{2}, 1)^T \]
因此\(\mathbf A\)是可对角化的,即令
\[ \mathbf P = \begin{pmatrix} \vec x_1, \vec x_2 \end{pmatrix} ,\]
则有\(\mathbf P^{-1} \mathbf A \mathbf P = \mathbf\Lambda=\begin{pmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{pmatrix}\) 因此\(\mathbf A^{n-2}=(\mathbf P^{-1} \mathbf\Lambda \mathbf P)^{n-2}=\mathbf P \mathbf\Lambda^{n-2} \mathbf P^{-1}\)
\[ \begin{aligned} \mathbf P \mathbf\Lambda^{n-2} \mathbf P^{-1} \begin{pmatrix} 1 \\ 1 \end{pmatrix} &= \frac{1}{\lambda_1 - \lambda_2} \begin{pmatrix} \lambda_1 & \lambda_2 \\ 1 & 1 \end{pmatrix} \begin{pmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{pmatrix}^{n-2} \begin{pmatrix} 1 & -\lambda_2 \\ -1 & \lambda_1 \end{pmatrix} \begin{pmatrix} 1 \\ 1 \end{pmatrix} \\ &=\frac{1}{\lambda_1 - \lambda_2} \begin{pmatrix} \lambda_1 & \lambda_2 \\ 1 & 1 \end{pmatrix} \begin{pmatrix} \lambda_1^{n-2} & 0 \\ 0 & \lambda_2^{n-2} \end{pmatrix} \begin{pmatrix} \lambda_1 \\ \lambda_2 \end{pmatrix}\\ &=\frac{1}{\lambda_1 - \lambda_2} \begin{pmatrix} \lambda_1 & \lambda_2 \\ 1 & 1 \end{pmatrix} \begin{pmatrix} \lambda_1^{n-1} \\ \lambda_2^{n-1} \end{pmatrix}\\ &=\frac{1}{\lambda_1 - \lambda_2} \begin{pmatrix} \lambda_1^n + \lambda_2^n\\ \lambda_1^{n-1}+\lambda_2^{n-1} \end{pmatrix}\\ \end{aligned} \]
即通项公式为\(F_n=\frac{1}{\lambda_1 - \lambda_2}(\lambda_1^n + \lambda_2^n)\),代入特征值得到\(F_n=\frac{1}{\sqrt 5}((\frac{1+\sqrt 5}{2})^n - (\frac{1 - \sqrt 5}{2})^n)\)
参考资料:斐波那契数列通项公式求解