手写数字集MNIST经常被用来给深度学习的初学者学习使用,它是由0到9的数字图像构成的。训练图像有60000张,测试图像有10000张,其中的每一张图片都有对应的标签数据,这些手写图片都是28*28像素,灰度图像。
不过它们并不是作为图像文件存储的,而是作为28*28的二维数组保存起来的,数组中的每个元素对应图片中的每个像素。
MNIST数据集包含四个文件,如下:
MNIST数据集官方网站:http://yann.lecun.com/exdb/mnist/
在这个实训要求中,我们需要使用卷积神经网络来完成数据集的训练和测试,由于本人就是个小白,所以也是查阅了很多资料才知道了卷积神经网络的工作原理。可以看一下深度学习的数学第五章,感兴趣的还可以来这看一下:https://github.com/vdumoulin/conv_arithmetic.git
开始我们的代码部分:首先配置环境并读取数据集,读取数据集的方法有很多,也可以去找一下其他的方法。因为我的还没配置gpu,所以是用cpu跑的。
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import random
import torchvision
USEGPU = False
device = "cuda" if USEGPU else "cpu"
def read(lab_path, img_path):
lab_file = open(lab_path, 'rb')
img_file = open(img_path, 'rb')
labels = np.fromfile(lab_file, offset=8, dtype=np.uint8)
images = np.fromfile(img_file, offset=16, dtype=np.uint8).reshape(len(labels), 784)
lab_file.close()
img_file.close()
return labels, images
train_labs, train_imgs = read("./train_labs.idx1-ubyte", "./train_imgs.idx3-ubyte")
test_labs, test_imgs = read("./test_labs.idx1-ubyte", "test_imgs.idx3-ubyte")
test_imgs = test_imgs.reshape(10000,1,28,28)
train_imgs = train_imgs.reshape(60000,1,28,28)
X = torch.from_numpy(train_imgs).type(torch.FloatTensor)
Y = torch.from_numpy(train_labs).type(torch.LongTensor)
X_test = torch.from_numpy(test_imgs).type(torch.FloatTensor)
Y_test = torch.from_numpy(test_labs).type(torch.LongTensor)
X = X.to(device)
Y = Y.to(device)
X_test = X_test.to(device)
Y_test = Y_test.to(device)
如图,我们实训要求的是用两个卷积层和两个池化层来搭建我们神经网络系统。(也可以多弄几个卷积层,这样训练和测试效果要好一些)
class neutral_net(torch.nn.Module):
def __init__(self):
super(neutral_net, self).__init__()
# 卷积层
self.conv = nn.Sequential(
# 第一个卷积层
nn.Conv2d(1, 32, 3, padding=1), # 输入图像尺寸为28x28,输出32*28*28
nn.ReLU(),
# 第一个池化层
nn.MaxPool2d(kernel_size=2), # 输出32*14*14
# 第二个卷积层
nn.Conv2d(32, 64, 3, padding=1), # 输出64*14*14
nn.ReLU(),
# 第二个池化层
nn.MaxPool2d(kernel_size=2) # 输出64*7*7
)
# 输出的全连接层
# 卷积层后的图像特征维度需要与全连接层对应
self.fc = nn.Sequential(
nn.Linear(64 * 7 * 7, 100), # 先通过一个较大的全连接层进行特征整合
nn.ReLU(),
nn.Linear(100, 10) # 输出层,10个类别
)
def forward(self, x):
x = self.conv(x)
# 展平特征图以输入到全连接层
x = x.view(x.size(0), -1) # 自适应尺寸
x = self.fc(x)
return x
net = neutral_net().to(device)
我们还需要导入MNIST数据集的训练子集进行训练,输出训练过程中的Loss变化曲线图以及测试集正确率变化曲线图。
def get_max(a): # 获取数组最大值下标
max = a.max();
max_i = 0
for i in range(len(a)):
if a[i] == max:
return i
optimizer = torch.optim.SGD(net.parameters(), lr =0.001)
loss_func = torch.nn.CrossEntropyLoss()
# loss_func是损失函数,optimizer是优化器
# 初始化列表来存储loss和准确率
train_losses = []
accuracies = []
loss = 0
BATCH_SIZE = 128 # 一次训练批次,可以自行修改
EPOCH = 500 # 训练周期
for i in range(6000):# 循环次数6000,也可以自己调整看看
offset = random.randint(0, 60000 - BATCH_SIZE)
X_batch = X[offset:offset + BATCH_SIZE]
Y_batch = Y[offset:offset + BATCH_SIZE]
Y_hat = net(X_batch)
loss = loss_func(Y_hat, Y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录训练loss
train_losses.append(loss.item())
if i % EPOCH == 0:
offset = random.randint(0, 10000 - BATCH_SIZE)
X_test_b = X_test[offset:offset + BATCH_SIZE]
Y_test_b = Y_test[offset:offset + BATCH_SIZE]
Y_test_hat = net(X_test_b) # 通常不需要调用.forward(),因为net(x)已经包含了forward过程
test_loss = loss_func(Y_test_hat, Y_test_b)
# 计算测试集准确率
correct = 0
total = 0
for j in range(BATCH_SIZE):
p_value = get_max(Y_test_hat[j]) # 假设get_max返回概率最高的类别的索引
r_value = test_labs[offset + j]
if p_value == r_value:
correct += 1
total += 1
accuracy = correct / total
accuracies.append(accuracy)
print("step=", i, "train_loss=", loss.item(), "test_loss=", test_loss.item(), "accuracy=", accuracy)
# 绘制loss和准确率图
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.plot(train_losses, label='Train Loss')
plt.title('Loss Over Time')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(accuracies, label='Accuracy')
plt.title('Accuracy Over Time')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.tight_layout()
plt.show()
print("in the end loss=", loss.data.item())
上面就是代码的全部了,在完成这个代码的过程中遇到了很多问题,各个层之间的关系老搞不清楚,全连接层的输入老匹配不了池化层的输出,滤波器究竟如何从原始图中得到卷积层等等。还是得多去学习,多去发现。
下面附上输出: