别只刷题了!用Python和PyTorch复现那些‘经典’的深度学习期末考题(附代码)
别只刷题了用Python和PyTorch复现那些‘经典’的深度学习期末考题附代码深度学习理论考试总让人头疼——公式推导、参数计算、概念辨析稍不留神就会陷入纸上谈兵的困境。但换个角度想这些试题本质上是检验我们对核心算法的理解程度。与其死记硬背不如打开Jupyter Notebook用代码将这些抽象问题具象化。本文将带你用PyTorch重新演绎五类经典考题从反向传播实现到LSTM结构拆解让理论在代码中活起来。1. 反向传播的代码解剖考试要求推导三层网络梯度PyTorch的自动微分机制能让我们直观验证计算结果。先构建一个无激活函数的简易网络import torch # 试题参数初始化 x torch.tensor([1.0], requires_gradTrue) w1 torch.tensor([0.5], requires_gradTrue) w2 torch.tensor([0.3], requires_gradTrue) w3 torch.tensor([0.2], requires_gradTrue) # 前向传播 y x * w1 * w2 * w3 print(f前向输出值: {y.item()}) # 输出: 0.03现在模拟考题要求计算梯度# 设置损失梯度为1.0 y.backward(torch.tensor([1.0])) print(fdL/dw1: {w1.grad.item()}) # 0.06 (w2*w3*x) print(fdL/dw2: {w2.grad.item()}) # 0.1 (w1*w3*x) print(fdL/dw3: {w3.grad.item()}) # 0.15 (w1*w2*x)对比手工计算$\frac{\partial L}{\partial w_1} \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial w_1} 1.0 \times (w_2 w_3 x) 0.3 \times 0.2 \times 1 0.06$关键发现通过.grad属性可以直接验证梯度计算是否正确。实践中建议用torch.autograd.grad()函数更灵活地获取特定梯度dy_dw1 torch.autograd.grad(outputsy, inputsw1, retain_graphTrue)[0]2. 卷积网络参数实战考试常见的卷积参数计算题用PyTorch的nn.Conv2d可以直观验证。题目给出输入尺寸32×32×3卷积核10个5×5stride1, padding0import torch.nn as nn conv nn.Conv2d(in_channels3, out_channels10, kernel_size5, stride1, padding0) input torch.randn(1, 3, 32, 32) # batch1 output conv(input) print(f输出特征图尺寸: {output.shape[2:]}) # torch.Size([1, 10, 28, 28])参数数量计算每个5×5卷积核有$5 \times 5 \times 3 75$个权重10个卷积核共$75 \times 10 750$权重参数每个卷积核1个偏置共$10$个偏置参数总计$750 10 760$个可训练参数提示使用sum(p.numel() for p in conv.parameters())可自动统计参数总量添加池化层验证pool nn.MaxPool2d(kernel_size2, stride2) pool_output pool(output) print(f池化后尺寸: {pool_output.shape[2:]}) # torch.Size([1, 10, 14, 14])3. Word2Vec模型对比实现CBOW和Skip-gram的结构差异常出现在简答题中。下面用PyTorch实现两者的核心区别class CBOW(nn.Module): def __init__(self, vocab_size, embedding_dim): super().__init__() self.embeddings nn.Embedding(vocab_size, embedding_dim) self.linear nn.Linear(embedding_dim, vocab_size) def forward(self, context): # context: [batch, 2*window_size] embeds self.embeddings(context).mean(dim1) # 上下文词向量平均 return self.linear(embeds) class SkipGram(nn.Module): def __init__(self, vocab_size, embedding_dim): super().__init__() self.embeddings nn.Embedding(vocab_size, embedding_dim) self.linear nn.Linear(embedding_dim, vocab_size) def forward(self, target): # target: [batch, 1] embeds self.embeddings(target).squeeze(1) return self.linear(embeds)架构对比表特性CBOWSkip-gram输入上下文词索引目标词索引输出目标词概率分布上下文词概率分布计算效率适合高频词适合低频词数学本质上下文词向量的均值预测目标词目标词向量预测上下文词分布负采样实现示例# 负采样损失函数 neg_loss -torch.log(torch.sigmoid(pos_score)) - \ torch.sum(torch.log(torch.sigmoid(-neg_scores)))4. LSTM长期依赖解决方案用代码揭示LSTM如何解决RNN的梯度消失问题。关键在门控机制class CustomLSTMCell(nn.Module): def __init__(self, input_size, hidden_size): super().__init__() # 输入门、遗忘门、输出门、候选记忆 self.gates nn.Linear(input_size hidden_size, 4*hidden_size) def forward(self, x, hc): h, c hc gates self.gates(torch.cat([x, h], dim1)) i, f, o, g gates.chunk(4, 1) # 拆分为四个部分 i, f, o torch.sigmoid(i), torch.sigmoid(f), torch.sigmoid(o) g torch.tanh(g) new_c f * c i * g # 记忆更新公式 new_h o * torch.tanh(new_c) return new_h, new_c门控机制解析遗忘门($f_t$)控制历史记忆保留量输入门($i_t$)调节新记忆的写入比例候选记忆($\tilde{C}_t$)存储当前时间步的新信息输出门($o_t$)决定隐藏状态的输出比例可视化门控信号变化plt.plot(forget_gate_history, labelForget Gate) plt.plot(input_gate_history, labelInput Gate) plt.legend()5. Attention机制代码演绎Seq2Seq的注意力改进方案通过代码展示其工作原理class Attention(nn.Module): def __init__(self, enc_dim, dec_dim): super().__init__() self.attn nn.Linear(enc_dim dec_dim, dec_dim) self.v nn.Parameter(torch.rand(dec_dim)) def forward(self, hidden, encoder_outputs): # hidden: [1, batch, dec_dim] # encoder_outputs: [seq_len, batch, enc_dim] seq_len encoder_outputs.shape[0] hidden hidden.repeat(seq_len, 1, 1) # 沿序列维度复制 energy torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim2))) energy energy.permute(1, 2, 0) # [batch, dec_dim, seq_len] v self.v.repeat(encoder_outputs.size(1), 1).unsqueeze(1) attention torch.bmm(v, energy).squeeze(1) # [batch, seq_len] return torch.softmax(attention, dim1)注意力计算流程将解码器隐藏状态与所有编码器输出拼接通过全连接层和tanh激活计算能量值使用可学习参数$v$计算注意力分数应用softmax归一化得到注意力权重对比传统Seq2Seq与Attention的效果差异# 传统Seq2Seq output, hidden decoder(input, hidden) # 加入Attention后 attn_weights attention(hidden, encoder_outputs) context torch.bmm(attn_weights.unsqueeze(1), encoder_outputs.transpose(0, 1)) output, hidden decoder(input, torch.cat((context, hidden), dim2))