深度学习基本单元结构与输入输出维度解析
深度学习基本单元结构与输入输出维度解析
在深度学习领域,模型的设计和结构是理解其性能和应用的关键。本文将介绍深度学习中的基本单元结构,包括卷积神经网络(CNN)、反卷积(转置卷积)、循环神经网络(RNN)、门控循环单元(GRU)和长短期记忆网络(LSTM),并详细讨论每个单元的输入和输出维度。我们将以 MNIST 数据集为例,展示这些基本单元如何组合在一起构建复杂的模型。
之前的博客:
深入理解 RNN、LSTM 和 GRU:结构、参数与应用
理解 Conv2d 和 ConvTranspose2d 的输入输出特征形状计算
1. 模型结构概述
我们构建的模型包含以下主要部分:
- 卷积神经网络(CNN)
- 反卷积(转置卷积)
- 循环神经网络(RNN)
- 门控循环单元(GRU)
- 长短期记忆网络(LSTM)
- 全连接层
2. 模型代码
以下是实现综合模型的代码:
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
from torchvision import datasets
from torch.utils.data import DataLoader
# 定义模型
class CombinedModel(nn.Module):
def __init__(self):
super(CombinedModel, self).__init__()
# CNN 部分
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) # 输入: (1, 28, 28) -> 输出: (32, 28, 28)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) # 输入: (32, 28, 28) -> 输出: (64, 28, 28)
# 反卷积部分
self.deconv = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2) # 输入: (64, 14, 14) -> 输出: (32, 28, 28)
# RNN 部分
self.rnn_input_size = 32 * 14 * 14 # 输入到 RNN 的特征数
self.rnn = nn.RNN(input_size=self.rnn_input_size, hidden_size=128, num_layers=1,
batch_first=True) # 输入: (batch_size, seq_len, input_size)
# GRU 部分
self.gru = nn.GRU(input_size=128, hidden_size=64, num_layers=1,
batch_first=True) # 输入: (batch_size, seq_len, input_size)
# LSTM 部分
self.lstm = nn.LSTM(input_size=64, hidden_size=32, num_layers=1,
batch_first=True) # 输入: (batch_size, seq_len, input_size)
# 全连接层
self.fc = nn.Linear(32, 10) # 输出: (batch_size, 10)
def forward(self, x):
# CNN 部分
print(f'Input shape: {x.shape}') # 输入形状: (batch_size, 1, 28, 28)
x = self.pool(torch.relu(self.conv1(x))) # 输出: (batch_size, 32, 28, 28)
print(f'After conv1 and pool: {x.shape}')
x = self.pool(torch.relu(self.conv2(x))) # 输出: (batch_size, 64, 14, 14)
print(f'After conv2 and pool: {x.shape}')
# 反卷积部分
x = self.deconv(x) # 输出: (batch_size, 32, 14, 14)
print(f'After deconv: {x.shape}')
# 将数据展平并调整形状以输入到 RNN
x = x.view(x.size(0), -1) # 展平为 (batch_size, 32 * 14 * 14)
print(f'After flattening: {x.shape}')
x = x.unsqueeze(1) # 添加序列长度维度,变为 (batch_size, 1, 32 * 14 * 14)
print(f'After unsqueeze for RNN: {x.shape}')
# RNN 部分
x, _ = self.rnn(x) # 输出: (batch_size, 1, 128)
print(f'After RNN: {x.shape}')
# GRU 部分
x, _ = self.gru(x) # 输出: (batch_size, 1, 64)
print(f'After GRU: {x.shape}')
# LSTM 部分
x, _ = self.lstm(x) # 输出: (batch_size, 1, 32)
print(f'After LSTM: {x.shape}')
# 取最后一个时间步的输出
x = x[:, -1, :] # 输出: (batch_size, 32)
print(f'After selecting last time step: {x.shape}')
# 全连接层
x = self.fc(x) # 输出: (batch_size, 10)
print(f'Output shape: {x.shape}')
return x
# 3. 数据加载
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)) # MNIST 数据集的均值和标准差
])
# 下载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
# 4. 训练模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = CombinedModel().to(device)
criterion = nn.CrossEntropyLoss() # 损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # 优化器
# 训练过程
num_epochs = 5
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device) # 将数据移动到设备
optimizer.zero_grad() # 清空梯度
outputs = model(images) # 前向传播
loss = criterion(outputs, labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1) # 获取预测结果
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_loss = running_loss / len(train_loader)
accuracy = 100 * correct / total
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}, Accuracy: {accuracy:.2f}%')
# 5. 评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device) # 将数据移动到设备
outputs = model(images)
_, predicted = torch.max(outputs.data, 1) # 获取预测结果
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy of the model on the test images: {100 * correct / total:.2f}%')
3. 每个基本单元的输入输出维度
3.1 CNN 部分
-
输入:
(batch_size, 1, 28, 28)
- 这是 MNIST 数据集的输入形状,其中
1
表示单通道(灰度图像),28x28
是图像的高度和宽度。
- 这是 MNIST 数据集的输入形状,其中
-
卷积层 1:
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
- 输入形状:
(batch_size, 1, 28, 28)
- 输出形状:
(batch_size, 32, 28, 28)
- 32 个特征图,空间维度保持不变。
-
最大池化层 1:
- 输入形状:
(batch_size, 32, 28, 28)
- 输出形状:
(batch_size, 32, 14, 14)
- 高度和宽度减半。
- 输入形状:
-
卷积层 2:
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
- 输入形状:
(batch_size, 32, 14, 14)
- 输出形状:
(batch_size, 64, 14, 14)
- 64 个特征图,空间维度保持不变。
-
最大池化层 2:
- 输入形状:
(batch_size, 64, 14, 14)
- 输出形状:
(batch_size, 64, 7, 7)
- 高度和宽度再次减半。
- 输入形状:
3.2 反卷积部分
- 反卷积层:
self.deconv = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2)
- 输入形状:
(batch_size, 64, 7, 7)
- 输出形状:
(batch_size, 32, 14, 14)
- 高度和宽度翻倍。
3.3 RNN 部分
-
展平:
x = x.view(x.size(0), -1)
- 输入形状:
(batch_size, 32, 14, 14)
- 输出形状:
(batch_size, 6272)
# 这里的 6272 是32 * 14 * 14
- 将特征图展平为一个向量。
-
添加序列长度维度:
x = x.unsqueeze(1)
- 输入形状:
(batch_size, 6272)
- 输出形状:
(batch_size, 1, 6272)
- 添加序列长度维度,表示只有一个时间步。
-
RNN:
- 输入形状:
(batch_size, 1, 6272)
- 输出形状:
(batch_size, 1, 128)
- RNN 输出的隐藏状态,隐藏层大小为 128。
- 输入形状:
3.4 GRU 和 LSTM 部分
-
GRU:
- 输入形状:
(batch_size, 1, 128)
- 输出形状:
(batch_size, 1, 64)
- GRU 输出的隐藏状态,隐藏层大小为 64。
- 输入形状:
-
LSTM:
- 输入形状:
(batch_size, 1, 64)
- 输出形状:
(batch_size, 1, 32)
- LSTM 输出的隐藏状态,隐藏层大小为 32。
- 输入形状:
3.5 全连接层
- 全连接层:
self.fc = nn.Linear(32, 10)
- 输入形状:
(batch_size, 32)
- 输出形状:
(batch_size, 10)
- 最终输出的类别数(10 类,表示 MNIST 的数字 0-9)。
4. 可视化模型结构
from torchinfo import summary
model = CombinedModel()
summary(model, input_size=(64,1, 28, 28))
或者
import torch
import torch.nn as nn
from torchviz import make_dot
model = CombinedModel()
dummy_input = torch.randn(1, 1, 28, 28) # (batch_size, channels, height, width)
output = model(dummy_input)
dot = make_dot(output, params=dict(model.named_parameters()))
dot.render("model_structure", format="png") # 生成 model_structure.png