学习的过程短期目标是丰富己身,长远来看有的人为了就业财富自由;有的则为了创造一些有意义的事物,更多的是为了前者。
此文章用于记录和总结深度学习相关算法岗的各种面试问题,搜集答案并加入博主一些浅显的理解,欢迎评论区纠正、补充。
一、经典网络架构篇
1.介绍Transformer
2.什么是Self-attention
注意力是很稀缺的,万物将注意力聚集在所获得信息的一部分上(通过感官获得的信息很多,将有限的注意力集中在少部分有用的信息上有利于资源分配而进行各种生命活动)。
注意力提示有自主性和非自住性提示。非自主性提示是基于环境中物体的突出性和易见性:如黑白色物体中一个鲜艳颜色的物体;在喝完咖啡后注意力在意志的推动下注意力聚集在黑白色书本上,这就是属于自主性提示的辅助。
在注意力机制的背景下,自主性提示被称为查询(query)。 给定任何查询,注意力机制通过注意力汇聚(attention pooling) 将选择引导至感官输入(sensory inputs,例如中间特征表示)。 在注意力机制中,这些感官输入被称为值(value)。 更通俗的解释,每个值都与一个键(key)配对, 这可以想象为感官输入的非自主提示。 如 :numref:fig_qkv
所示,可以通过设计注意力汇聚的方式, 便于给定的查询(自主性提示)与键(非自主性提示)进行匹配, 这将引导得出最匹配的值(感官输入)
查询(自主提示)和键(非自主提示)之间的交互形成了注意力汇聚; 注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出
可以理解为注意力层的输出是感官输入对于自主性提示和非自主性提示的一个加权求和(映射,权重等价于query和key的相似度)成输出的过程,输出的维度和V的维度是一致的。
3.介绍Bert
训练方式、输入的token类型、适用场景
4.为什么要有位置编码,你知道哪些种类的位置编码
绝对位置编码:正余弦,bert中的
相对位置编码:旋转位置编码(rope)
1. 更好的相对位置感知能力
-
绝对位置编码:将位置信息直接加到词向量上(如
token_embedding + position_embedding
),但位置与内容之间的交互是加性关系,可能导致模型难以区分“位置信息”和“语义信息”。 -
RoPE:通过旋转矩阵对词向量进行变换,
-
将位置信息表示为复数平面上的旋转操作。对查询向量 qmqm 和键向量 knkn,通过旋转矩阵 RR 变换:
qm=Rθmq,kn=Rθnkqm=Rθmq,kn=Rθnk其中旋转矩阵 Rθ 由角度 θ 决定,m,n 为绝对位置110。
-
注意力分数:变换后点积仅依赖相对位置差 m−nm−n
-
使注意力分数的计算显式包含相对位置信息:
# 注意力分数计算(RoPE) q_m = rotate(q, m) # 对位置m的查询向量应用旋转 k_n = rotate(k, n) # 对位置n的键向量应用旋转 attention_score = q_m · k_n # 点积结果包含相对位置 (m-n)
优势:
-
点积
q_m · k_n
的结果仅依赖于相对位置差m-n
,使模型更容易学习到相对位置模式(如局部性、方向性)。
-
2. 良好的长度外推性(Length Extrapolation)
-
绝对位置编码问题:训练时最大长度固定(如512),推理时若输入长度超过该值,位置编码无意义,性能急剧下降。
-
RoPE的解决方案:
-
旋转操作是与位置无关的线性变换,可扩展到任意位置。
-
即使推理长度远超训练长度,RoPE仍能保持位置信息的连续性(如通过NTK-aware Scaling等策略进一步优化外推能力)。
优势:显著提升模型处理长文本的能力(如LLaMA、GPT-NeoX均采用RoPE支持长上下文)。
-
3. 保持向量范数不变
-
RoPE的本质是正交旋转:
rotate(q, θ) = [q_0*cosθ - q_1*sinθ, q_0*sinθ + q_1*cosθ] # 2D示例
-
旋转操作不改变向量的模长(
||rotate(q)|| = ||q||
)。
优势: -
避免因位置编码叠加导致向量范数漂移,提升数值稳定性。
-
5.介绍卷积神经网络,比较卷积神经网络和Transformer的优劣
卷积神经网络是为了处理图像数据设计而成,其需要的参数少于全连接架构的网络,而且卷积操作很容易用GPU并行计算,因此,卷积神经网络可以高效地采样从而获得精准的模型,还能够高效的计算,Resnet的主要操作就是卷积,其准确率很高,其很多变体在目前研究中还被当作模型的性能基准。
卷积神经网络具有空间不变性:(1)平移不变性,不管检测模型出现在图像的哪个位置,网络对相同的图像区域具有相似的反应。(2)局部性,神经网络的前几层只探索图像中的局部区域,不过度在意图像中的相隔较远区域的关系。最终,可以聚合这些局部特征,在整张图像上进行预测。
卷积的输出受到输入形状、卷积核形状、填充和步幅影响
填充能够解决原始图像边缘信息丢失的问题;步幅可以有效减小图像的宽度和高度(比如原始的输入分辨率冗余时)
当设置ph=kh-1.pw=kw-1时可以使得输入输出具有相同的高度和宽度,当kh是奇数时,在两侧分别填充ph/2;为偶数时在顶部填充向上取整ph/2;在底部填充向下取整ph/2。宽度同理
卷积核高度和宽度通常是奇数,这样既能保持空间维度,又能在顶部和底部、左右填充相同数量的行和列;此外,可以得出输出的像素点是以输入的对应位置像素点为中心与卷积核进行互相关运算得到。
卷积操作的输出大小{(输入张量高-卷积核高+填充行数量+垂直步幅)/垂直步幅}向下取整*{输入张量宽-卷积核宽+填充列数量+水平步幅)/水平步幅}向下取整
卷积计算函数:
def corr2d(X, K): #K为卷积核,X为输入张量
"""计算二维互相关运算"""
h, w = K.shape
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)
定义卷积层:
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size))
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
# 其中批量大小和通道数都为1
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2 # 学习率
for i in range(10):
Y_hat = conv2d(X)
l = (Y_hat - Y) ** 2
conv2d.zero_grad()
l.sum().backward()
# 迭代卷积核
conv2d.weight.data[:] -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')
可以迭代卷积层的参数
二、通用的深度学习网络层
1.BatchNormlization和LayerNormlization的区别
二者的相同处是都是根据特征的分布对样本特征进行标准正态化(均值变成0,方差变成1)的一个过程减掉均值后除以方差,在训练的时候是对于小批量的样本进行计算,而预测的时候是在全局即所有待预测的数据上计算。
但是不同的是标准正态化过程中的均值和方差两个参数的计算目标不同:
BN的均值和方差是对于处于一个批次的所有样本的每一个特征(同一列)里面计算,同时会学习两个可调节的参数;二维情况:
三维情况:
LN则是对于同一个样本特征的所有标量进行计算,输入为二维(特征仅是一维的情况)
二者在输入为三维时的对比:
为什么在时序的问题中经常使用LN而不是BN?
如上面的立方体的输入向量示意图,由于时序问题中序列特征的长度有差异,在序列不够长时会用0来填充,而BN在进行标准正态化的过程中会使用全局的输入特征的均值和方差来进行标准正态化,但是LN只要在每个输入特征中进行这个过程,所以前者可能会由于输入特征的长度的大小的差异使得效果不够好,但是后者不存在输入特征长度差异对结果造成的负面影响。
三、优化器
深度学习中优化器的作用
优化器(Optimizer)在深度学习中起着至关重要的作用,它负责调整模型的参数(如权重和偏置),以最小化损失函数(Loss Function)。优化器通过计算梯度并更新参数来实现这一目标。以下是优化器的主要作用:
-
参数更新:
- 优化器根据损失函数的梯度信息,更新模型的参数,使得损失函数逐步减小。
-
梯度计算:
- 优化器使用反向传播算法计算损失函数相对于模型参数的梯度。
-
学习率调整:
- 优化器可以动态调整学习率,以控制参数更新的步伐,避免过大或过小的更新步长。
-
收敛加速:
- 优化器通过各种技术(如动量、加权平均、学习率调度等)加速模型的收敛,提高训练效率。
常见的优化器
常见的优化器包括:
- SGD(Stochastic Gradient Descent):随机梯度下降,基本的优化方法。
- Momentum:引入动量项,缓解梯度震荡问题。
- Adagrad:自适应学习率调整,适用于稀疏数据。
- RMSprop:改进的自适应学习率方法,解决Adagrad学习率衰减问题。
- Adam:结合Momentum和RMSprop的优点,广泛应用于各种深度学习任务。
AdaW优化器
AdaW(Adaptive Weight Decay)是近年来提出的一种改进优化器,它主要结合了Adam优化器和自适应权重衰减的思想。AdaW在训练过程中动态调整权重衰减参数,以提高模型的泛化能力和训练效率。
主要特点
-
自适应权重衰减:
- AdaW引入了自适应权重衰减机制,根据参数的更新情况动态调整权重衰减系数,避免过度惩罚或不足惩罚。
-
结合Adam优化器:
- AdaW继承了Adam优化器的优点,使用动量和自适应学习率调整,提高了训练的稳定性和效率。
四、损失函数
负对数似然损失
NLL Loss,全称为负对数似然损失(Negative Log-Likelihood Loss),是一种常用的损失函数,尤其在分类任务中广泛应用。它通常用于训练分类模型,特别是与 softmax 函数结合使用时。在深度学习框架中,如 PyTorch,NLL Loss 是一个基本的损失函数。
定义
NLL Loss 是对似然函数取负对数后的结果。其目的是最大化模型对正确类别的预测概率。公式如下:
对于一个给定的样本 x 和其真实类别 y,模型预测的概率分布为 p(y∣x),负对数似然损失定义为:
NLL Loss=−log(p(y∣x))
使用场景
-
多分类问题:NLL Loss 常用于多分类问题中,与 softmax 层结合使用。在这种情况下,softmax 层将模型的输出转换为概率分布,然后 NLL Loss 计算该分布与真实标签的损失。
-
语言模型:在自然语言处理任务中,NLL Loss 常用于训练语言模型,以最大化对下一个词的预测概率。
结合 Softmax 使用
在实践中,NLL Loss 通常与 softmax 函数结合使用。softmax 函数将模型的输出(通常是未归一化的得分)转换为概率分布。然后,NLL Loss 计算这个概率分布与真实标签之间的损失。
在这种组合中,通常使用 Cross Entropy Loss(交叉熵损失),因为它在内部结合了 softmax 和 NLL Loss 的计算,简化了使用过程。
实现示例
以下是一个使用 PyTorch 的示例,展示如何使用 NLL Loss:
import torch
import torch.nn as nn
import torch.nn.functional as F
# 模拟模型输出(未归一化的得分)
output = torch.tensor([[1.5, 2.0, 0.5]])
# 真实标签
target = torch.tensor([1])
# 使用 log_softmax 将输出转换为对数概率
log_prob = F.log_softmax(output, dim=1)
# 使用 NLL Loss 计算损失
nll_loss = nn.NLLLoss()
loss = nll_loss(log_prob, target)
print(f"NLL Loss: {loss.item()}")
总结
NLL Loss 是一种用于分类任务的损失函数,适用于模型输出为概率分布的情形。通过最大化正确类别的预测概率,NLL Loss 帮助模型更好地进行分类任务。它常与 softmax 函数结合使用,以实现概率分布的计算。
五、评价指标
六、微调方法
大模型通用
lora、qlora、prefix tuning
diffusion类模型特有:
controlnet、adapter tunind
七、激活函数
1.什么是激活函数,为什么使用激活函数
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活, 它们将输入信号转换为输出的可微运算。 大多数激活函数都是非线性的。
单纯在多层感知机模型中添加隐藏层(若不加入非线性的激活函数)没有任何好处,原因很简单:上面的隐藏单元由输入的仿射函数给出, 而输出(softmax操作前)只是隐藏单元的仿射函数。 仿射函数的仿射函数本身就是仿射函数, 但是我们之前的线性模型已经能够表示任何仿射函数,可证如下:
2.知道哪些激活函数,介绍一下
当输入为负时,ReLU函数的导数为0,而当输入为正时,ReLU函数的导数为1。 注意,当输入值精确等于0时,ReLU函数不可导。 在此时,我们默认使用左侧的导数,即当输入为0时导数为0。
使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,并且ReLU减轻了困扰以往神经网络的梯度消失问题。
它是一个平滑的、可微的阈值单元近似
sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代
八、其他常见问题
1.什么是过拟合以及缓解过拟合的方法
过拟合是指模型的训练误差远小于验证误差。
过拟合和欠拟合的问题可能是由模型复杂性和可用训练数据集的大小:
①模型复杂性,当模型复杂度较低时,会欠拟合;随着模型复杂度升高到一定程度泛化损失最低,模型复杂度再升高,则会产生过拟合。
②数据集大小,训练数据集中样本越少,可以理解为模型对所学习领域数据的分布就越不典型,因此可能会倾向于学习到少量数据中不典型的特征,容易过拟合。一般来说,更多的数据不会有坏处。(当然也要保证质量)
缓解过拟合的方法:
1.加入正则化
2.暂退法:在每一层计算的同时注入噪声,具体做法是在训练过程中丢弃(drop out)一些神经元。真正好的,具有泛化性的模型应该能够适应数据小的扰动,比如有噪声的模糊图像。神经网络的过拟合与每一层都依赖前一层的激活值有关,这种情况称为“共适应性”,暂退法可以破坏共适应性。
2.前向传播、反向传播和计算图
前向传播:顺序从输入层到输出层计算和存储神经网络中的每层的结果。
计算图:用于保存运算符以及变量间的依赖关系
反向传播:指的是计算神经网络参数梯度的方法,根据链式法则,按相反顺序从输出层到输入层遍历网络。
训练神经网络时,前向传播和反向传播相互依赖,在模型权重初始化后,交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数,反向传播依赖前向传播中存储的中间值,为避免重复计算,需要保存中间值,直到反向传播完成,这也是训练比单纯的预测需要更多显存的原因之一。