AI面试速记
🔹 1. PyTorch 中 Tensor 和 NumPy 数组有什么区别?
回答:
| 特性 | PyTorch Tensor | NumPy ndarray |
|---|---|---|
| 设备支持 | 支持 CPU 和 GPU(通过 .to('cuda')) | 仅支持 CPU |
| 自动求导 | 支持自动微分(设置 requires_grad=True) | 不支持 |
| 计算图 | 动态构建计算图(用于反向传播) | 无计算图 |
| 互操作性 | 可与 NumPy 零拷贝互转(.numpy() / torch.from_numpy()) | — |
| 性能 | GPU 加速,适合深度学习 | CPU 计算,通用科学计算 |
✅ 关键点:Tensor 是为深度学习设计的,核心优势是 GPU 加速 + 自动求导。
🔹 2. 如何将模型放到 GPU 上?如何检查是否在 GPU 上运行?
回答:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) data = data.to(device)
检查方式:
next(model.parameters()).device:查看模型参数所在设备tensor.device:查看某个张量的设备
⚠️ 注意:模型和数据必须在同一设备上,否则会报错。
✅ “模型”指的是什么?
- 在 PyTorch 中,模型是一个
nn.Module的实例,比如:PYTHON
model = torch.nn.Linear(10, 2) - 模型内部包含可学习的参数(parameters),如权重(
weight)和偏置(bias)。 - 这些参数是
torch.Tensor,每个张量都有一个.device属性(如cpu或cuda:0)。
所以,“模型在 GPU 上” 实际是指:模型的所有参数张量都存储在 GPU 显存中。
✅ “数据”指的是什么?
- “数据”通常指:
- 输入特征(input features):如图像张量
x(shape:[B, C, H, W]) - 标签(labels/targets):如分类标签
y(shape:[B])
- 输入特征(input features):如图像张量
- 它们也是
torch.Tensor,同样有.device属性。
例如:
x = x.to('cuda')就是把输入数据移到 GPU。
❗ 为什么必须在同一设备?
PyTorch 要求参与同一运算的张量必须在相同设备上。
当你执行 output = model(x) 时,本质是:
PYTHON
output = weight @ x + bias # 张量运算
如果 weight 在 GPU,而 x 在 CPU,就会报错:
TEXT
RuntimeError: Expected all tensors to be on the same device...
🔹 3. model.train() 和 model.eval() 有什么区别?为什么需要切换?
回答:
model.train():启用 训练模式Dropout层会随机丢弃神经元BatchNorm使用当前 batch 的均值和方差,并更新其统计量(running_mean/running_var)
model.eval():启用 评估/推理模式Dropout被禁用(所有神经元保留)BatchNorm使用训练阶段累积的全局统计量(不更新)
❗ 常见错误:在验证时不调用
model.eval(),导致结果不稳定或偏低。
🔹 4. 为什么 BatchNorm 在训练和推理时行为不同?
回答:
- 训练时:每个 batch 数据分布可能不同,BN 用当前 batch 的均值/方差归一化,并更新全局统计量(指数移动平均)。
- 推理时:没有 batch 概念,且希望输出确定。因此使用训练阶段学到的全局均值和方差,保证结果稳定。
💡 这也是为什么必须调用
model.eval()——否则 BN 会继续用单个样本计算统计量,导致数值异常。
作用:加速训练 + 提高稳定性 + 一定程度正则化
具体做了什么?
对一个 batch 中的每个通道(channel),进行如下操作:
x_norm = (x - mean_batch) / sqrt(var_batch + eps)
x_out = gamma * x_norm + beta
mean_batch,var_batch:当前 batch 在该通道上的均值和方差gamma,beta:可学习的缩放和平移参数(也是模型参数!)
📌 注意:BatchNorm 是按通道(channel)独立计算的。例如 CNN 中 shape
(B, C, H, W),会对每个C单独算均值/方差。
BatchNorm 的作用对象是:对每个通道 c ∈ [0, C),独立地对该通道在所有样本(B)和空间位置(H×W)上做归一化。
也就是说,每个通道有自己的 mean、var、gamma、beta。
❓1. 改变的是什么值?
- 改变的是 输入张量
x的数值(逐元素)。 - 具体来说:对每个通道,先减去该通道在当前 batch 的均值,再除以其标准差,最后乘以可学习的缩放因子并加上偏移。
- 输出
x_out的形状和输入x完全相同(仍是(B, C, H, W)),但数值已被重新缩放和平移。
❓2. x_out 是什么?
x_out是 经过 BatchNorm 处理后的激活值(activations),将作为下一层的输入。- 它具有以下特性:
- 数值范围更稳定(避免梯度爆炸/消失)
- 分布接近标准正态(训练初期)
- 包含可学习的仿射变换,保留表达能力
简单说:
x_out是“更好训练”的中间特征表示。
🔹 5. 解释 requires_grad=True 的作用。
回答:
- 当
requires_grad=True时,PyTorch 会追踪对该张量的所有操作,构建动态计算图。 - 在调用
.backward()时,自动计算该张量对 loss 的梯度,并存储在.grad属性中。 - 默认只有模型参数(如
nn.Linear.weight)的requires_grad=True,输入数据通常为False。
✅ 示例:
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward() # dy/dx = 2x = 4
print(x.grad) # 输出: tensor(4.)
🔹 6. 什么情况下需要用 with torch.no_grad():?
回答:
在不需要计算梯度的场景下使用,例如:
- 模型推理(inference)
- 验证/测试阶段
- 固定部分网络参数进行前向计算
好处:
- 节省内存(不保存中间变量用于反向传播)
- 加快计算速度
✅ 正确做法:
model.eval()
with torch.no_grad():
outputs = model(inputs)
🔹 7. 如果对一个 tensor 执行了 .detach(),还能反向传播吗?
回答:
.detach()会切断该张量与计算图的连接,返回一个不参与梯度计算的新张量。- 从
.detach()后的张量继续的操作不会被追踪,因此无法通过它反向传播到原始张量。
🌰 例子:
x = torch.tensor(1.0, requires_grad=True)
y = x ** 2
z = y.detach() # z 与 x/y 无梯度关系
loss = z ** 2
loss.backward() # x.grad 仍是 None!因为梯度没传回 x
🔹 8. 为什么不能对叶子节点(leaf tensor)的 grad 手动赋值?
回答:
- 叶子节点:用户直接创建的张量(如
torch.randn(3, requires_grad=True)),不是由其他张量运算得到的。 - PyTorch 要求叶子节点的梯度由
.backward()自动累积,不允许直接覆盖(防止破坏梯度一致性)。 - 若需清空梯度,应使用
optimizer.zero_grad()或tensor.grad = None(PyTorch ≥ 1.7 支持)。
❌ 错误:
x = torch.tensor(1.0, requires_grad=True) x.grad = torch.tensor(2.0) # RuntimeError!✅ 正确清零:
optimizer.zero_grad() # 或 x.grad = None
🔹 9. 如何冻结模型的部分层(比如只训练最后几层)?
回答:
方法一:设置 requires_grad=False
PYTHON
for param in model.features.parameters(): # 假设 features 是预训练部分 param.requires_grad = False # 只训练 classifier optimizer = torch.optim.Adam(model.classifier.parameters(), lr=1e-3)
方法二:在优化器中只传入需要更新的参数(推荐)
💡 应用场景:迁移学习(fine-tuning)
🔹 10. 如何实现自定义损失函数?需要注意什么?
回答:
继承 nn.Module 或直接写函数:
PYTHON
class CustomLoss(nn.Module):
def forward(self, pred, target):
return torch.mean((pred - target) ** 2)
注意事项:
- 返回标量(scalar)loss(
backward()要求) - 确保所有操作可导(避免
if、item()等非张量操作) - 若含超参数,建议作为
__init__参数传入
🔹 11. CrossEntropyLoss 输入的 shape 是什么?它内部做了 softmax 吗?
回答:
- 输入 logits shape:
(N, C),其中 N 是 batch size,C 是类别数 - target shape:
(N,),元素是类别索引(0 到 C-1) - 内部操作:先对 logits 做 log-softmax,再计算负对数似然(NLLLoss)
✅ 所以:不要提前对输出做 softmax! 直接传 logits 即可。
它是分类任务中最常用的损失函数
🔥 关键:输入是 logits,不是概率!不要提前做 softmax!
🔹 12. 为什么 Adam 优化器通常不需要手动调整学习率?
回答:
Adam 结合了:
- 动量(Momentum):加速收敛
- RMSProp:对每个参数自适应学习率(根据历史梯度平方调整)
因此它能自动调节不同参数的学习步长,对初始学习率不敏感(常用默认值 1e-3)。
⚠️ 但并非完全不用调:极端任务(如 GAN、大模型)仍需调整。
🔹 13. 以下代码有什么问题?
for epoch in range(10):
for x, y in dataloader:
pred = model(x)
loss = loss_fn(pred, y)
loss.backward()
optimizer.step()
回答:
缺少 optimizer.zero_grad()!
- 每次
loss.backward()会累加梯度到.grad中 - 如果不清零,梯度会越积越大,导致训练发散
✅ 正确写法:
for x, y in dataloader:
optimizer.zero_grad() # ← 关键!
pred = model(x)
loss = loss_fn(pred, y)
loss.backward()
optimizer.step()
🔹 14. 什么是“梯度消失”?PyTorch 中如何缓解?
回答:
- 梯度消失:深层网络中,反向传播时梯度逐层变小(接近 0),导致浅层参数几乎不更新。
- 原因:激活函数(如 sigmoid)导数 < 1,连乘后趋近于 0。
PyTorch 中的缓解方法:
- 使用 ReLU 等非饱和激活函数
- 残差连接(ResNet):
x + F(x)保证梯度直通 - 归一化层:BatchNorm / LayerNorm 稳定分布
- 权重初始化:Xavier / Kaiming 初始化
🔹 15. 如何在不增加显存的情况下训练更大的 batch size?
回答:
两种主流方法:
梯度累积(Gradient Accumulation)
- 小 batch 多次前向/反向,累积梯度,最后统一更新
- 等效于大 batch,但显存占用
accumulation_steps = 4 for i, (x, y) in enumerate(dataloader): loss = loss_fn(model(x), y) / accumulation_steps loss.backward() if (i + 1) accumulation_steps == 0: optimizer.step() optimizer.zero_grad()梯度检查点(Gradient Checkpointing)
- 用时间换空间:前向时不保存中间激活,反向时重新计算
- 使用
torch.utils.checkpoint.checkpoint
🔹 16. DataParallel 和 DistributedDataParallel 有什么区别?哪个更推荐?
回答:
| 特性 | DataParallel (DP) | DistributedDataParallel (DDP) |
|---|---|---|
| 并行方式 | 单进程多线程 | 多进程(每个 GPU 一个进程) |
| 通信效率 | 低(主线程瓶颈) | 高(NCCL 通信,异步) |
| 内存占用 | 主卡内存更高 | 各卡均衡 |
| 适用场景 | 快速原型 | 生产/多机训练 |
| 是否推荐 | ❌ 不推荐 | ✅ 强烈推荐 |
📌 官方文档明确建议:优先使用 DDP。
