一、首先需要配置对应的环境(anconda+GPU/CPU)
二、下载手写数字数据集
数据集在资源里
三、搭建2D卷积神经网络
2D卷积是由基本卷积层+激活函数+最大池化层构成。
-
基本概念
- 卷积层:2D卷积神经网络的核心是卷积层,它通过在输入图像上滑动一个小窗口(即卷积核或滤波器)来提取特征。这个小窗口会在图像的高度和宽度两个方向上移动,因此被称为二维卷积。
- 互相关运算:尽管名字中有“卷积”二字,但在深度学习中,2D卷积神经网络通常使用的是互相关运算,而不是真正的数学卷积运算。互相关运算与卷积运算类似,但卷积核不进行翻转。
- 感受野:卷积层输出的每个元素都是输入图像中一个区域(即感受野)的特征表示。随着网络层次的加深,感受野会逐渐变大,从而能够捕捉到更大范围的特征。
-
工作原理
- 单通道计算:对于单通道图像(如灰度图),卷积核直接在图像上滑动,并在每个位置进行内积运算,得到输出矩阵的一个元素。这个过程会在整个图像上重复进行,直到生成完整的输出矩阵。
- 多通道计算:对于多通道图像(如RGB图像),需要使用多个卷积核分别对每个通道进行卷积运算,然后将所有通道的结果相加得到最终的输出。
- 卷积核参数:卷积核的大小、数量以及是否使用填充(padding)和步长(stride)等参数都会影响卷积层的输出。
-
结构特点
- 局部连接性:卷积层中的神经元只与输入图像中的局部区域相连,这种局部连接性有助于减少参数数量并提高计算效率。
- 权值共享:同一个卷积核在所有位置上的权重是相同的,这种权值共享机制进一步减少了参数数量,并使得网络能够学习到更加泛化的特征。
- 层级结构:2D卷积神经网络通常由多个卷积层、池化层和全连接层组成,形成一种层级结构。这种结构有助于逐层提取和抽象图像特征。
-
代码如下:
-
其中通道数、池化层最好为偶数
-
super(FalconNet, self).__init__()
# 定义特征提取部分
self.features = nn.Sequential(
# 第一个 FalconConv2d 层,输入通道数为 1,输出通道数为 16
FalconConv2d(1, 16, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True), # 激活函数
nn.MaxPool2d(kernel_size=2, stride=2), # 最大池化层
# 第二个 FalconConv2d 层,输入通道数为 16,输出通道数为 32
FalconConv2d(16, 32, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True), # 激活函数
nn.MaxPool2d(kernel_size=2, stride=2), # 最大池化层
# 第三个 FalconConv2d 层,输入通道数为 32,输出通道数为 64
FalconConv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True), # 激活函数
nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层
)
四、定义分类器
对输出特征进行分类
self.classifier = nn.Sequential(
nn.Linear(64 * 4 * 4, 128), # 全连接层,输入维度为 64 * 4 * 4,输出维度为 128
nn.ReLU(inplace=True), # 激活函数
nn.Linear(128, num_classes), # 全连接层,输入维度为 128,输出维度为 num_classes
)
五 、对数据集进行训练
criterion = nn.CrossEntropyLoss()
# 定义优化器,使用 Adam优化算法
optimizer = optim.Adam(model.parameters(), lr=0.0001)
# model.parameters():获取模型的所有可训练参数
# lr=0.0001:设置学习率为 0.0001
# 定义训练模型的函数
def train_model(model, dataloader, criterion, optimizer, num_epochs=10):
for epoch in range(num_epochs):
# 设置模型为训练模式
model.train()
# 初始化累积损失
running_loss = 0.0
# 遍历数据加载器中的每个批次
for inputs, labels in dataloader:
# 将输入和标签移动到指定设备(GPU 或 CPU)
inputs, labels = inputs.to(device), labels.to(device)
# 清零梯度,防止梯度累积
optimizer.zero_grad()
# 前向传播,计算模型输出
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播,计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
# 累积损失
running_loss += loss.item() * inputs.size(0)
epoch_loss = running_loss / len(dataloader.dataset)
print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss:.4f}')
train_model(model, dataloader, criterion, optimizer, num_epochs=5)
torch.save(model.state_dict(), 'model.pth')
六、对模型进行评估
def evaluate_model(model, dataloader, device='cpu'):
model.eval()
correct = 0
total = 0
with torch.no_grad():
# 遍历数据加载器中的每个批次
for inputs, labels in dataloader:
# 将输入和标签移动到指定设备
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播,计算模型输出
outputs = model(inputs)
# 获取预测的类别标签
_, predicted = torch.max(outputs, 1)
# 将 one-hot 编码的标签转换回类别标签
_, true_labels = torch.max(labels, 1)
# 更新总样本数量
total += true_labels.size(0)
# 更新正确预测的数量
correct += (predicted == true_labels).sum().item()
# 计算准确率
accuracy = correct / total
# 打印模型的准确率
print(f'Accuracy of the model: {accuracy:.4f}')
evaluate_model(model, dataloader, device=device)
# 测试数据文件夹的位置
test_images_folder = 'written_digits/test_no_label'
class CustomTransform:
def __init__(self):
# 定义一系列数据变换操作
self.transform = transforms.Compose([
transforms.ToTensor(), # 将 numpy.ndarray 转换为 torch.Tensor
transforms.Resize([32, 32]), # 将输入图片 resize 成统一尺寸 32x32
transforms.Normalize(mean=[0.485], std=[0.229]) # 对图像进行标准化处理
])
def __call__(self, image):
# 应用定义的变换操作
image = self.transform(image)
return image
# 创建自定义转换对象
transform = CustomTransform()
test_image_files = [f for f in os.listdir(test_images_folder) if f.endswith('.txt')]
model.eval()
results = []
softmax = nn.Softmax(dim=1)
# 关闭梯度计算,因为评估阶段不需要计算梯度
with torch.no_grad():
for image_file in test_image_files:
# 构建图像文件路径
image_path = os.path.join(test_images_folder, image_file)
# 读取图像数据
with open(image_path, 'r') as file:
lines = file.readlines()
image = np.array([[int(char) for char in line.strip()] for line in lines], dtype=np.float32)
image = transform(image)
image = image.to(device)
output = model(image.unsqueeze(0)) # 添加批次维度 (1, 1, 32, 32)
probabilities = softmax(output)
_, predicted = torch.max(probabilities, 1)
results.append([image_file.split('.tx')[0], predicted.item()])
results_df = pd.DataFrame(results, columns=['Test_txt_name', 'Digit'])
results_df.to_csv('predictions.csv', index=False)