计算机保研/考研面试复习——深度学习篇
计算机保研/考研面试复习系列
专业课(PDF整理版下载):
计算机保研/考研面试复习专业课篇——数据结构
计算机保研/考研面试复习专业课篇——计算机网络
计算机保研/考研面试复习专业课篇——操作系统
计算机保研/考研面试复习专业课篇——计算机组成原理
数学(PDF整理版下载):
计算机保研/考研面试复习数学篇——高等数学
计算机保研/考研面试复习数学篇——线性代数
计算机保研/考研面试复习数学篇——概率论
计算机保研/考研面试复习数学篇——离散数学
人工智能 (PDF整理版下载):
计算机保研/考研面试复习——机器学习篇
计算机保研/考研面试复习——深度学习篇
一、DNN(深度神经网络)
1. 描述一下神经网络?推导反向传播公式?
神经网络是一种模拟生物神经系统的计算模型,由多层神经元(节点)组成,每个节点通过激活函数对输入进行变换。神经网络通过调节连接权重来学习数据中的模式。反向传播算法是一种用于训练神经网络的梯度下降法,通过链式法则计算损失函数相对于每个权重的梯度,并更新权重。

2. 讲解一下 dropout 原理?
Dropout 是一种正则化技术,通过在训练过程中随机“丢弃”一部分神经元及其连接,来防止神经网络的过拟合。它通过减少神经元之间的相互依赖性,使得每个神经元更加鲁棒,有助于提升模型的泛化能力。
3. 梯度消失和梯度膨胀的原因是什么?有什么方法可以缓解?
原因:
- 梯度不稳定:在反向传播过程中,梯度会通过乘积形式传递,如果每一层的激活函数对梯度的导数小于1,则随着层数的增加,梯度会以指数形式衰减,导致梯度消失。反之,如果激活函数的导数大于1,梯度会以指数形式增加,导致梯度爆炸。
- 激活函数的选择:激活函数的选择对梯度的稳定性有很大影响。常用的激活函数如sigmoid和tanh,它们的导数都接近于1,因此更容易导致梯度消失。而ReLU等激活函数的导数在0和1之间,相对较稳定。
方法:
- 使用适当的初始化参数:适当的初始化参数可以避免梯度消失和爆炸的问题。一种常用的方法是使用He初始化或Xavier初始化来初始化权重参数。
- 使用残差结构:残差结构可以有效地解决梯度消失问题。通过引入残差连接,可以直接将前一层的梯度传递到后一层,避免了梯度的指数衰减。在深度神经网络中,如ResNet等网络结构采用了残差连接。
- 使用长短期记忆网络(LSTM):LSTM是一种特殊的RNN结构,通过内部的“门”结构可以记住前几次训练的“残留记忆”,从而避免了梯度消失问题。LSTM经常用于处理序列数据,如自然语言处理等领域。
- 使用梯度检查和梯度裁剪:通过定期检查梯度的值,可以发现梯度爆炸的问题。一旦发现梯度值过大,可以对其进行裁剪,将其限制在一个合理的范围内。这种方法的缺点是可能会破坏原始数据的分布。
- 使用适当的优化器:选择适当的优化器也可以缓解梯度消失和爆炸的问题。例如,Adam优化器通过引入两个额外的参数来调整学习率,可以自动调整学习率以适应不同的数据集和模型结构。
4. 什么时候该用浅层神经网络,什么时候该选择深层网络
浅层神经网络 适用于数据量较小,简单的线性或低维问题,计算资源有限的场景下。 深层神经网络适用于复杂、数据量大的非线性问题,需要模型具有更强的表达能力和更高的泛化性能,需要有足够的计算资源, 任务涉及高级抽象,如图像识别、自然语言处理等。
5. Sigmoid、Relu、Tanh 激活函数都有哪些优缺点?
Sigmoid:
- 优点:平滑,输出在[0,1]之间,适合二分类问题。
- 缺点:容易造成梯度消失,训练速度慢。
ReLU:
- 优点:计算简单,不易引发梯度消失,收敛快。
- 缺点:可能导致“神经元死亡”,即输出恒为0。
Tanh:
- 优点:输出在[-1,1]之间,零均值,适合处理非正负对称数据
- 缺点:同样容易引发梯度消失。
6. 写出常用激活函数的导数


7. 训练模型的时候,是否可以把网络参数全部初始化为 0?为什么?
不可以。全部初始化为0会导致每个神经元的输出都相同,梯度相同,导致每个神经元在反向传播时更新相同的权重,无法学习到有效的特征。隐藏层中的神经元会变得完全对称,无法打破对称性。网络失去非线性变换能力,退化为线性模型。应使用随机初始化(如正态分布或均匀分布)来打破对称性。
8. Batchsize 大小会如何影响收敛速度?
- 小Batch Size:梯度噪声大,参数更新更加频繁,能够更好地避免局部最优,收敛速度可能更快。但计算开销大,训练时间长。
- 大Batch Size:计算梯度更稳定,收敛轨迹更平滑。计算效率高,能够充分利用GPU等硬件资源。但可能导致模型陷入局部最优,泛化能力下降。
二、CNN
1. 简述 CNN 的工作原理?
卷积神经网络(CNN)是一种深度学习模型,主要用于处理图像数据。其工作原理如下:
- 卷积层:通过卷积操作提取输入数据的特征。卷积核在输入图像上滑动,计算局部区域的加权和,生成特征图(feature map)。
- 激活函数:通常使用ReLU激活函数,增加模型的非线性能力。
- 池化层:通过下采样操作减少特征图的尺寸,保留重要特征,降低计算复杂度。
- 全连接层:将高维特征映射到输出类别,进行分类或回归。
- 输出层:根据任务类型(分类或回归)生成最终输出。
2. 卷积核是什么?选择大卷积核和小卷积核有什么影响?
卷积核是一个小的权重矩阵,用于对输入图像进行卷积操作。卷积核在输入图像上滑动,通过点积计算得到特征图。
- 大卷积核:感受野较大,能捕捉到全局信息,但参数较多,计算量大,可能会过拟合。
- 小卷积核:感受野较小,能捕捉到局部信息,参数较少,计算效率高,但可能无法捕捉到全局特征。
3. 你在实际应用中如何设计卷积核?
- 选择合适的大小:通常选择3×3或5×5的卷积核,通过堆叠多层来增加感受野。
- 设计不同的滤波器:在同一层中可以使用多个卷积核来提取不同类型的特征,如边缘、纹理等。
- 确定步长和填充:步长(Stride)决定了卷积核在输入数据上滑动的间隔,影响输出特征图的大小。填充(Padding)通常用于保持特征图的尺寸,避免边缘信息的损失。
- 使用不同的激活函数:用于引入非线性,帮助网络学习更复杂的特征。
- 正则化:适当加入正则化手段,如 Dropout 或 L2 正则,避免过拟合。
4. 为什么 CNN 具有平移不变性?
卷积神经网络(CNN)之所以具有平移不变性,主要得益于其结构设计中的几个关键特性。卷积层通过局部连接和权重共享,使得网络能够在图像的任何位置检测到相同的特征,并输出相应的响应。这种设计使得无论目标在图像中的位置如何变化,卷积层都能够捕捉到其特征。池化层,尤其是最大池化,通过取感受野内的最大值来进一步增强网络的平移不变性。即使特征发生平移,只要它们仍然在池化窗口内,最大池化仍然能够返回相同的最大值,从而保持特征的稳定性。随着网络深度的增加,卷积核的感受野逐渐扩大,使得网络能够捕捉到更大范围内的平移不变特征。此外,全连接层在网络末端整合了前面层提取的特征,进行最终的分类或其他任务,这些特征已经通过多层卷积和池化处理,因此网络对输入数据的平移具有不变性。
5. Pooling 操作是什么?有几种?作用是什么?
Pooling 操作 是对特征图进行降采样的操作,目的是减少数据的维度、提取主要特征,同时降低计算复杂度。
- 最大池化(Max Pooling):选择池化窗口内的最大值作为输出。
- 平均池化(Average Pooling):选择池化窗口内的平均值作为输出。
- 全局平均池化(Global Average Pooling):对整个特征图进行池化,输出每个特征图的平均值。
作用:
- 降低计算复杂度:降低了特征图尺寸,减少了计算量和内存占用。
- 保留重要特征:通过提取特征的最大值或平均值,保留重要信息。
- 防止过拟合:通过减少参数数量,降低模型的复杂性。
6. 什么是 batch normalization?它的原理是什么?在 CNN 中如何使用?
Batch Normalization(批标准化) 是一种加速神经网络训练并提高稳定性的方法。它通过对每一层的输入进行标准化处理,将其均值设为0,方差设为1,然后对标准化后的输出进行线性变换(缩放和平移)。
原理:
- 标准化:对每个 mini-batch 计算均值和方差,进行标准化。
- 线性变换:引入可学习的缩放参数 γ和平移参数 β,使得网络可以调整标准化后的分布。
使用:通常添加在卷积层和激活函数层之间,使输入的数据符合同一分布,从而使得训练更加简单、快速。
7. 卷积操作的本质特性包括稀疏交互和参数共享,具体解释这两种特性以其作用?
- 稀疏交互:卷积操作只对局部区域进行计算,而不是对整个输入进行全连接,因此减少了计算量和参数数量,提高了计算效率。
- 参数共享:同一卷积核在整个输入上滑动,提取局部特征,这意味着同一个参数在多个位置重复使用,减少了参数数量,降低了模型的复杂性。
8. 你是如何理解 fine-tuning?有什么技巧
Fine-tuning(微调)是指在一个预训练模型的基础上,针对新的任务进行微调。通过利用预训练模型的知识,减少训练时间并提高新任务的模型性能。
技巧:
- 冻结前几层:只微调后几层,减少训练参数。
- 调整学习率:对微调的层使用较小的学习率,避免破坏预训练模型的已有特征。
- 逐步解冻:逐步解冻前几层,逐层微调,保证模型稳定性。
三、RNN
1. 简述 RNN 模型原理,说说 RNN 适合解决什么类型问题?为什么?
RNN(循环神经网络) 是一种能够处理序列数据的神经网络模型。它通过在每个时间步共享权重,并将前一时间步的隐藏状态作为当前时间步的输入,使得模型具有记忆和处理时间序列的能力。
适用于时间序列预测,如股票价格、天气预报等;自然语言处理,如文本生成、语言翻译等。
原因:RNN 可以通过循环结构捕捉输入数据中的时间依赖关系,适合处理序列数据和动态变化的数据。
2. RNN 和 DNN 有何异同?
相同点:都是神经网络模型,都通过层与层之间的权重连接来进行学习,都可以使用梯度下降法进行优化。
不同点:
- 结构:RNN 有循环结构,能够处理序列数据;DNN 是前馈网络,适合处理独立样本数据。
- 记忆能力:RNN 具有记忆功能,可以处理时间相关的数据;DNN 没有记忆功能,只处理静态数据。
3. RNN 为什么有记忆功能?
RNN 通过隐藏状态(hidden state)在时间步之间传递信息,使得每个时间步的输出不仅依赖于当前的输入,还依赖于之前的输入序列,从而具备记忆能力。
4. 长短期记忆网络 LSTM 是如何实现长短期记忆功能的?
LSTM(长短期记忆网络) 通过引入记忆单元和门控机制,有效地解决了 RNN 中的梯度消失和梯度爆炸问题,能够更好地捕捉长时间依赖关系。LSTM 通过三个门(输入门、遗忘门、输出门)来控制信息的流动,从而实现长短期记忆功能。
- 遗忘门(Forget Gate):决定保留多少前一时刻的记忆。
- 输入门(Input Gate):决定当前输入信息对记忆的影响。
- 输出门(Output Gate):决定当前时刻的输出。
在一个训练好的LSTM模型中,当输入序列中没有重要信息时,遗忘门的值接近于1,输入门的值接近于0,表示过去的记忆被完整保存,而输入信息被放弃,从而实现长期记忆功能。当输入序列中存在重要信息时,LSTM应把他存入记忆中,此时输入门接近于1;当输入序列中存在重要信息且该信息意味着之前的记忆不再重要时,输入门的值接近1,遗忘门的值接近0。
5. 长短期记忆网络 LSTM 各模块都使用什么激活函数,可以使用其他激活函数么?
输入门、遗忘门、输出门:通常使用 Sigmoid 函数,将输出压缩到 [0, 1] 范围,用于控制信息的流动。
候选记忆:通常使用 Tanh 函数,压缩到 [-1, 1] 范围。
可以使用其他激活函数,但需要注意激活函数的输出范围和梯度性质对网络性能的影响。
6. GRU 和 LSTM 有何异同?
相同点:都是为了解决 RNN 中的梯度消失问题而设计的,都具有门控机制,能捕捉长时间依赖关系
不同点:
- 结构:GRU结构简单,只有两个门(更新门和重置门),无独立的记忆单元;LSTM 有三个门和独立的记忆单元。
- 计算效率:GRU 参数较少,计算效率更高;LSTM 参数较多,能够捕捉更复杂的时间依赖关系。
7. 什么是 Seq2Seq 模型?该模型能解决什么类型问题?
Seq2Seq(序列到序列)模型 是一种将输入序列转换为输出序列的模型,广泛用于机器翻译、文本摘要、问答系统等任务。它由一个编码器(encoder)和一个解码器(decoder)组成,编码器将输入序列编码为一个固定长度的向量,解码器将该向量解码为输出序列。
8. 注意力机制是什么?Seq2Seq 模型引入注意力机制主要解决什么问题?
注意力机制 是一种增强神经网络模型性能的方法,它允许模型在处理当前输入时,关注到输入序列中的某些重要部分,而不是只依赖于编码器输出的固定向量。通过加权平均输入序列的各个部分,注意力机制能够更好地捕捉输入和输出之间的依赖关系。
解决的问题: 在传统 Seq2Seq 模型中,编码器需要将整个输入序列编码成一个固定长度的向量,这会导致信息的丢失,尤其是长序列。引入注意力机制后,模型可以动态地关注输入序列中的不同部分,缓解了信息瓶颈的问题。
四、Pytorch
1. Pytorch如何微调fine tuning?
- 局部微调:加载了模型参数后,只想调节最后几层,其他层不训练,也就是不进行梯度计算,pytorch提供的requires_grad使得对训练的控制变得非常简单。
- 全局微调:对全局微调时,只不过我们希望改换过的层和其他层的学习速率不一样,这时候把其他层和新层在optimizer中单独赋予不同的学习速率。
2. Pytorch如何调用gpu 并使用多gpu?
可以将网络模型,数据,损失函数这三种变量在GPU上进行训练
gpu 调用:
- cuda():
# 将网络模型在gpu上训练
model = Model()
if torch.cuda.is_available():
model = model.cuda()
# 损失函数在gpu上训练
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
loss_fn = loss_fn.cuda()
# 数据在gpu上训练
for data in dataloader:
imgs, targets = data
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
- to(device):
#指定训练设备
device = torch.device("cpu") # 使用cpu训练
device = torch.device("cuda") # 使用gpu训练
device = torch.device("cuda:0") # 当电脑中有多张显卡时,使用第一张显卡
device = torch.device("cuda:1") # 当电脑中有多张显卡时,使用第二张显卡
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #单卡推荐
#使用 GPU 训练
model = model.to(device)
loss_fn = loss_fn.to(device)
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
使用多 gpu:model = nn . DataParallel ( model ) ,DataParallel并行的方式,是将输入一个batch的数据均分成多份,分别送到对应的GPU进行计算,各个GPU得到的梯度累加。与Module相关的所有数据也都会以对模型和相应的数据进行.cuda()处理,可以将内存中的数据复制到gpu显存中去。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = CreateModel()
model= nn.DataParallel(model)
model.to(device)
3. Pytorch数据
Pytorch导入数据主要依靠 torch.utils.data.DataLoader和 torch.utils.data.Dataset这两个类来完成。
操作顺序:
- 创建一个 Dataset 对象
- 创建一个 DataLoader 对象
- 循环这个 DataLoader 对象,将img, label加载到模型中进行训练
torch.utils.data.Dataset:这是一个抽象类,所以我们需要对其进行派生,从而使用其派生类来创建数据集。最主要的两个函数实现为__len__和__getitem__。
- __init__:可以在这里设置加载的data和label。
- __len__:获取数据集大小
- __getitem__:根据索引获取一条训练的数据和标签。
torch.utils.data.DataLoader:接收torch.utils.data.Dataset作为输入,得到DataLoader,它是一个迭代器,方便我们去多线程地读取数据,并且可以实现batch以及shuffle的读取等。
(1)构建数据集-torch.utils.data.Dataset
- 通过内置的下载功能
cf10_data = torchvision.datasets.CIFAR10('dataset/cifar/', download=True)
- 自己实现
任何自定义的数据集都要继承自torch.utils.data.Dataset,然后重写__len__(self)和__getitem__(self, idx)
import torch
from torch.utils.data import Dataset
class myDataset(Dataset):
def __init__(self, data, label):
#创建5*2的数据集
self.data = torch.tensor([[1,2],[3,4],[2,1],[3,4],[4,5]])
#5个数据的标签
self.label = torch.tensor([0,1,0,1,2])
#根据索引获取data和label
def __getitem__(self,index):
return self.data[index], self.label[index] #以元组的形式返回
#获取数据集的大小
def __len__(self):
return len(self.data)
data = myDataset()
(2)数据载入-torch.utils.data.DataLoader
torch.utils.data.Dataset通过__getitem__获取单个数据,如果希望获取批量数据、shuffle或者其它的一些操作,那么就要由torch.utils.data.DataLoader来实现
dataset = myDataset()
dataloader = DataLoader(dataset,batch_size=2,shuffle=False,num_workers=0,drop_last=True)
for step, train_data in enumerate(dataloader):
data,label = train_data
(3)把数据放入GPU中
- Dataset阶段把数据放入GPU(必须把num_workers设置为0)
def __getitem__(self, index):
data = torch.tensor(self.data[index], dtype = torch.float32)
label = torch.tensor(self.label[index], dtype = torch.long)
if torch.cuda.is_available():
data = data.cuda()
label = label.cuda()
return data, label
- DataLoader阶段把数据放入GPU(推荐使用这种实现方式)
dataset = myDataset()
dataloader = DataLoader(dataset,batch_size=2,shuffle=False,num_workers=0,drop_last=True)
for step, train_data in enumerate(dataloader):
data,label = train_data
if torch.cuda.is_available():
data = data.cuda()
label = label.cuda()
4. 自定义层的步骤
- 自定义一个类,继承自Module类,并且一定要实现两个基本函数,第一个构造函数__init__,第二个是层的逻辑运算函数,即前向计算函数forward函数。
- 在构造函数__init__中实现层的参数定义。比如Linear层的权重和偏置,Conv2d层的in_channels, out_channels, kernel_size, stride=1,padding=0, dilation=1, groups=1,bias=True, padding_mode='zeros’这一系列参数。
- 在前向传播forward函数里面实现前向运算。
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3)
def forward(self, x):
x = self.conv1(x)
return x
tudui = Tudui()
input = torch.randn(1, 10) # 创建一个随机输入张量
output = tudui(input) # 通过自定义层进行前向传播
5. nn.ModuleList和nn.Sequential的不同
- nn.ModuleList并没有定义一个网络,它只是将不同模块存储在一起,这些模块之间没有什么先后顺序可言。
- nn.Sequential实现了内部的forward函数,并且里面的模块必须是按照顺序进行排列的,所以我们必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。
6. Sequential三种实现方法
- 创建一个Sequential,不断添加模型子层,传入的第一个参数为该层的名称,第二个是该层的实例。
model1 = nn.Sequential()
model1.add_module('conv', nn.Conv2d(1, 5, 2))
model1.add_module('fc', nn.Linear(10, 2))
model1.add_module('sigmoid', nn.Sigmoid())
- 直接将所有实例化的子类按顺序传入,但是不能对每个层进行命名,默认每个层的名称是0,1,2
model2 = nn.Sequential(nn.Conv2d(1, 5, 2),
nn.Linear(10, 2),
nn.Sigmoid())
- 创建一个字典,然后将这个字典传入,这个字典和方法一致都是层的名称和该层的实例化的键值对。
from collections import OrderedDict
model3 = nn.Sequential(OrderedDict([
('conv', nn.Conv2d(1, 5, 2)),
('fc', nn.Linear(10, 2)),
('sigmoid', nn.Sigmoid())
]))
7. 计算图
计算图通常包含两种元素,一个是tensor,另一个是Function。Function指的是计算图中某个节点(node)所进行的运算,比如加减乘除卷积等等。Function内部有forward()和backward()两个方法,分别应用于正向、反向传播。对于任意一个张量来说,我们可以用 tensor.is_leaf 来判断它是否是叶子张量(leaf tensor)。在反向传播过程中,只有 is_leaf=True 的时候,需要求导的张量的导数结果才会被最后保留下来。
8. requires_grad
当我们创建一个张量 (tensor) 的时候,如果没有特殊指定的话,那么这个张量是默认是不需要求导的。我们可以通过 tensor.requires_grad 来检查一个张量是否需要求导。在张量间的计算过程中,如果在所有输入中,有一个输入需要求导,那么输出一定会需要求导;相反,只有当所有输入都不需要求导的时候,输出才会不需要 。例如虽然输入的训练数据是默认不求导的,但是model 中的所有参数默认是求导的
9. 反向传播的流程
必须有: loss.backward()反向传播;optimizer.step() 权重更新;optimizer.zero_grad() 导数清零
for data in train_dataloader:
imgs, targets = data
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
# 优化器优化模型
optimizer.zero_grad()
loss.backward()
optimizer.step()
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)