国科大深度学习期末考了啥?我用Python复现了2020-2023年所有计算题(附完整代码)
·
国科大深度学习期末计算题实战:用Python复现卷积、损失函数与模型设计
备考深度学习课程时,单纯记忆理论概念往往事倍功半。本文将以2020-2023年国科大深度学习期末考题中的计算题为核心,通过Python代码完整复现解题过程,帮助读者在实战中掌握卷积运算、损失函数计算等核心技能。我们将使用TensorFlow和PyTorch双框架实现,并附赠调试技巧和可视化方法。
1. 卷积运算的三重境界:Full/Same/Valid实战
卷积神经网络(CNN)的核心操作在不同填充方式下表现迥异。让我们以2021年考题为例,实现三种卷积模式:
import tensorflow as tf
import numpy as np
# 输入矩阵和卷积核(与2021年考题一致)
input_matrix = np.array([
[5, 6, 0, 1, 8, 2],
[0, 9, 8, 4, 6, 5],
[2, 6, 5, 3, 8, 4],
[6, 3, 4, 9, 1, 0],
[7, 5, 9, 1, 6, 7],
[2, 5, 9, 2, 3, 7]
], dtype=np.float32)
kernel = np.array([
[0, -1, 1],
[1, 0, 0],
[0, -1, 1]
], dtype=np.float32)
# 转换为TensorFlow需要的4D格式 (batch, height, width, channels)
input_tensor = tf.reshape(input_matrix, [1, 6, 6, 1])
kernel_tensor = tf.reshape(kernel, [3, 3, 1, 1])
# Valid卷积实现
valid_conv = tf.nn.conv2d(input_tensor, kernel_tensor,
strides=1, padding='VALID')
valid_relu = tf.nn.relu(valid_conv)
print("Valid卷积结果:\n", valid_conv.numpy().squeeze())
print("ReLU激活后:\n", valid_relu.numpy().squeeze())
# Same卷积实现
same_conv = tf.nn.conv2d(input_tensor, kernel_tensor,
strides=1, padding='SAME')
same_relu = tf.nn.relu(same_conv)
print("\nSame卷积结果:\n", same_conv.numpy().squeeze())
print("ReLU激活后:\n", same_relu.numpy().squeeze())
# Full卷积模拟(通过手动补零)
padded_input = tf.pad(input_tensor, [[0,0], [2,2], [2,2], [0,0]])
full_conv = tf.nn.conv2d(padded_input, kernel_tensor,
strides=1, padding='VALID')
full_relu = tf.nn.relu(full_conv)
print("\nFull卷积结果:\n", full_conv.numpy().squeeze())
print("ReLU激活后:\n", full_relu.numpy().squeeze())
调试提示:当卷积结果与预期不符时,检查输入数据的维度顺序是否正确。TensorFlow使用NHWC格式,而PyTorch默认使用NCHW格式。
三种卷积方式的差异对比如下:
| 卷积类型 | 输出尺寸 | 边界处理 | 适用场景 |
|---|---|---|---|
| Valid | (n-f+1)×(n-f+1) | 不填充 | 希望输出尺寸缩减时 |
| Same | n×n | 自动填充保持尺寸 | 需要保持空间分辨率 |
| Full | (n+f-1)×(n+f-1) | 充分填充 | 需要完整覆盖所有可能重叠 |
2. 损失函数计算:从交叉熵到Focal Loss
深度学习模型的优化核心在于损失函数计算。我们复现2020年和2021年考题中的损失函数计算场景。
2.1 多分类交叉熵实战
import torch
import torch.nn as nn
import numpy as np
# 2020年考题数据
def softmax(x):
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
# 模型A输出
logits_A = np.array([np.log(20), np.log(40), np.log(60), np.log(80)])
probs_A = softmax(logits_A)
target = np.array([0, 0, 0, 1]) # one-hot编码
# 手动计算交叉熵
def cross_entropy(probs, target):
return -np.sum(target * np.log(probs))
loss_A = cross_entropy(probs_A, target)
print(f"模型A交叉熵: {loss_A:.4f}")
# 模型B输出
logits_B = np.array([np.log(10), np.log(30), np.log(50), np.log(90)])
probs_B = softmax(logits_B)
loss_B = cross_entropy(probs_B, target)
print(f"模型B交叉熵: {loss_B:.4f}")
# 使用PyTorch验证
criterion = nn.CrossEntropyLoss()
loss_A_torch = criterion(torch.tensor([logits_A]), torch.tensor([3])) # 类别索引3
loss_B_torch = criterion(torch.tensor([logits_B]), torch.tensor([3]))
print(f"PyTorch验证 - 模型A: {loss_A_torch.item():.4f}, 模型B: {loss_B_torch.item():.4f}")
2.2 Focal Loss实现(2021年考题)
class FocalLoss(nn.Module):
def __init__(self, alpha=0.4, gamma=2, reduction='mean'):
super(FocalLoss, self).__init__()
self.alpha = alpha
self.gamma = gamma
self.reduction = reduction
def forward(self, inputs, targets):
BCE_loss = nn.functional.binary_cross_entropy_with_logits(
inputs, targets, reduction='none')
pt = torch.exp(-BCE_loss) # 计算p_t
focal_loss = self.alpha * (1-pt)**self.gamma * BCE_loss
if self.reduction == 'mean':
return focal_loss.mean()
elif self.reduction == 'sum':
return focal_loss.sum()
return focal_loss
# 2021年考题数据
targets = torch.tensor([
[1, 0], [0, 1], [1, 0], [0, 1], [1, 0]
], dtype=torch.float32)
predictions = torch.tensor([
[0.2, 0.8], [0.3, 0.7], [0.4, 0.6], [0.1, 0.9], [0.5, 0.5]
], dtype=torch.float32)
# 计算常规交叉熵
criterion = nn.BCELoss()
ce_loss = criterion(predictions, targets)
print(f"常规交叉熵损失: {ce_loss.item():.4f}")
# 计算Focal Loss
focal_loss = FocalLoss(alpha=0.4, gamma=2)
fl_loss = focal_loss(predictions, targets)
print(f"Focal Loss: {fl_loss.item():.4f}")
注意:Focal Loss中的alpha参数需要根据类别不平衡比例调整,gamma参数控制难易样本的权重。
3. 模型参数量计算与感受野分析
2022年考题要求计算CNN各层的参数量和感受野。我们通过代码实现自动化计算:
def calculate_parameters(input_shape, kernel_size, filters, stride=1):
"""计算卷积层参数量"""
# 公式: (kernel_height * kernel_width * input_channels + 1) * filters
# 假设不考虑偏置时去掉+1
return kernel_size[0] * kernel_size[1] * input_shape[-1] * filters
def calculate_output_shape(input_shape, kernel_size, stride, padding='valid'):
"""计算输出特征图尺寸"""
h, w = input_shape[1], input_shape[2]
kh, kw = kernel_size
if padding.lower() == 'same':
return (h // stride, w // stride)
elif padding.lower() == 'valid':
return ((h - kh) // stride + 1, (w - kw) // stride + 1)
else:
raise ValueError("Unsupported padding type")
# 2022年考题网络结构
input_shape = (1, 32, 32, 3) # 假设输入为32x32 RGB图像
# C1层: 3x3卷积,深度5,stride=1
c1_params = calculate_parameters(input_shape, (3,3), 5)
c1_output = calculate_output_shape(input_shape, (3,3), 1, 'valid')
print(f"C1层 - 参数量: {c1_params}, 输出尺寸: {c1_output[0]}x{c1_output[1]}x5")
# P1层: 2x2均值池化,stride=2
p1_output = (c1_output[0]//2, c1_output[1]//2)
print(f"P1层 - 输出尺寸: {p1_output[0]}x{p1_output[1]}x5")
# C2层: 5x5卷积,深度2,stride=1
c2_input_shape = (1, p1_output[0], p1_output[1], 5)
c2_params = calculate_parameters(c2_input_shape, (5,5), 2)
c2_output = calculate_output_shape(c2_input_shape, (5,5), 1, 'valid')
print(f"C2层 - 参数量: {c2_params}, 输出尺寸: {c2_output[0]}x{c2_output[1]}x2")
# 全连接层参数量计算
fc_input = c2_output[0] * c2_output[1] * 2 # 展平后的维度
fc_output = 10 # 输出10维
fc_params = fc_input * fc_output
print(f"全连接层参数量: {fc_params}")
# 感受野计算
def calculate_receptive_field(layers):
rf = 1
stride_product = 1
for layer in layers:
if layer['type'] == 'conv':
rf += (layer['kernel_size'] - 1) * stride_product
stride_product *= layer['stride']
elif layer['type'] == 'pool':
stride_product *= layer['stride']
return rf
# 定义网络结构
layers = [
{'type': 'conv', 'kernel_size': 3, 'stride': 1},
{'type': 'pool', 'stride': 2},
{'type': 'conv', 'kernel_size': 5, 'stride': 1}
]
rf = calculate_receptive_field(layers)
print(f"F3层的感受野大小: {rf}x{rf}")
4. 设计题代码化:从思路到实现
深度学习考试中的设计题往往考察创新思维和实现能力。我们以2023年图像分割题为例,展示如何将设计方案转化为代码。
4.1 U-Net改进方案实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class DoubleConv(nn.Module):
"""(卷积 => [BN] => ReLU) * 2"""
def __init__(self, in_channels, out_channels):
super().__init__()
self.double_conv = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True)
)
def forward(self, x):
return self.double_conv(x)
class AttentionBlock(nn.Module):
"""注意力机制模块"""
def __init__(self, F_g, F_l, F_int):
super(AttentionBlock, self).__init__()
self.W_g = nn.Sequential(
nn.Conv2d(F_g, F_int, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(F_int)
)
self.W_x = nn.Sequential(
nn.Conv2d(F_l, F_int, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(F_int)
)
self.psi = nn.Sequential(
nn.Conv2d(F_int, 1, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(1),
nn.Sigmoid()
)
self.relu = nn.ReLU(inplace=True)
def forward(self, g, x):
g1 = self.W_g(g)
x1 = self.W_x(x)
psi = self.relu(g1 + x1)
psi = self.psi(psi)
return x * psi
class ImprovedUNet(nn.Module):
def __init__(self, n_channels=3, n_classes=1):
super(ImprovedUNet, self).__init__()
# 下采样路径
self.inc = DoubleConv(n_channels, 64)
self.down1 = nn.MaxPool2d(2)
self.conv1 = DoubleConv(64, 128)
self.down2 = nn.MaxPool2d(2)
self.conv2 = DoubleConv(128, 256)
self.down3 = nn.MaxPool2d(2)
self.conv3 = DoubleConv(256, 512)
self.down4 = nn.MaxPool2d(2)
self.conv4 = DoubleConv(512, 1024)
# 上采样路径 + 注意力机制
self.up1 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)
self.att1 = AttentionBlock(512, 512, 256)
self.conv5 = DoubleConv(1024, 512)
self.up2 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)
self.att2 = AttentionBlock(256, 256, 128)
self.conv6 = DoubleConv(512, 256)
self.up3 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)
self.att3 = AttentionBlock(128, 128, 64)
self.conv7 = DoubleConv(256, 128)
self.up4 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
self.att4 = AttentionBlock(64, 64, 32)
self.conv8 = DoubleConv(128, 64)
self.outc = nn.Conv2d(64, n_classes, kernel_size=1)
def forward(self, x):
# 下采样
x1 = self.inc(x)
x2 = self.conv1(self.down1(x1))
x3 = self.conv2(self.down2(x2))
x4 = self.conv3(self.down3(x3))
x5 = self.conv4(self.down4(x4))
# 上采样+注意力
u1 = self.up1(x5)
a1 = self.att1(u1, x4)
u1 = torch.cat([a1, u1], dim=1)
u1 = self.conv5(u1)
u2 = self.up2(u1)
a2 = self.att2(u2, x3)
u2 = torch.cat([a2, u2], dim=1)
u2 = self.conv6(u2)
u3 = self.up3(u2)
a3 = self.att3(u3, x2)
u3 = torch.cat([a3, u3], dim=1)
u3 = self.conv7(u3)
u4 = self.up4(u3)
a4 = self.att4(u4, x1)
u4 = torch.cat([a4, u4], dim=1)
u4 = self.conv8(u4)
logits = self.outc(u4)
return torch.sigmoid(logits)
# 模型测试
model = ImprovedUNet()
input_tensor = torch.randn(1, 3, 256, 256)
output = model(input_tensor)
print(f"输入尺寸: {input_tensor.shape}")
print(f"输出尺寸: {output.shape}")
这个改进版U-Net主要创新点:
- 引入注意力机制模块,让网络更关注重要区域
- 使用双卷积块保持特征提取能力
- 增加网络深度提升特征抽象能力
4.2 模型可视化与调试技巧
import torchviz
from torchsummary import summary
# 生成模型结构图
dot = torchviz.make_dot(model(input_tensor), params=dict(model.named_parameters()))
dot.render("improved_unet", format="png")
# 打印模型摘要
summary(model, input_size=(3, 256, 256))
# 梯度检查工具
def check_gradients(model):
for name, param in model.named_parameters():
if param.grad is not None:
print(f"{name} - 梯度均值: {param.grad.mean().item():.6f}")
else:
print(f"{name} - 无梯度")
# 训练过程中可以定期调用检查
# check_gradients(model)
在实际项目中,我发现注意力机制能显著提升小目标分割效果,但会增加约15%的计算量。对于256x256的输入图像,建议batch size设置为8-16以适应大多数GPU内存。
更多推荐
所有评论(0)