为了巩固阶段性学习成果,尝试应用MLP进行实践。本次使用MNIST数据集,通过一个使用MLP训练一个手写体数字识别模型。
获取数据
本次仍然采用MNIST数据集中的数据,数据获取方式与softmax一节中相似。不同之处在于,这里从训练集中划分了20%作为验证集,用于在最终测试前对模型进行调整。
1 2 3 4 5 6 7 8
| transform = transforms.ToTensor() 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_size = int(0.8*len(train_dataset)) val_size = len(train_dataset)-train_size train_dataset,val_dataset=torch.utils.data.random_split(train_dataset,[train_size,val_size])
|
接着将训练集、验证集、测试集分别打乱并按64的批量存入迭代器中。
1 2 3 4
| batch_size = 64 train_loader=DataLoader(train_dataset, batch_size, shuffle=True) val_loader=DataLoader(val_dataset, batch_size, shuffle=True) test_loader=DataLoader(test_dataset, batch_size, shuffle=True)
|
定义模型
由于数据集较简单,事实上很简单的模型就能取得较高的精确度。一开始仅仅使用三个全连接层并直接使用ReLU激活函数就能在验证集上达到97%的精确度。后续引入了批量归一化层,并尝试着加大深度、调整宽度,但最终也只能达到98%的精确度。一者因为精确度已经较高,二者因为MLP的局限,再做过多调整意义已经不大。以下是经过调整的模型代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(28*28,1024) self.bn1 = nn.BatchNorm1d(1024) self.fc2 = nn.Linear(1024,512) self.bn2 = nn.BatchNorm1d(512) self.fc3 = nn.Linear(512,256) self.bn3 = nn.BatchNorm1d(256) self.fc4 = nn.Linear(256,10) self.relu=nn.ReLU() def forward(self,x): x = x.view(x.shape[0],-1) x = self.relu(self.bn1(self.fc1(x))) x = self.relu(self.bn2(self.fc2(x))) x = self.relu(self.bn3(self.fc3(x))) return self.fc4(x)
model = MLP()
criterion = nn.CrossEntropyLoss() optimzer = optim.Adam(model.parameters(),lr=0.001)
|
训练模型
共进行10轮训练,每轮训练完成后使用验证集对模型进行检验,看是否过拟合,从而对超参数进行调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| num_epoches = 10 epoch_losses = [] val_losses = [] for epoch in range(num_epoches): model.train() epoch_loss=0.0 for features, labels in train_loader: optimzer.zero_grad() outputs = model(features) loss = criterion(outputs,labels) loss.backward() optimzer.step() epoch_loss += loss.item() avg_epoch_loss = epoch_loss/len(train_loader) epoch_losses.append(avg_epoch_loss) model.eval() val_loss=0.0 with torch.no_grad(): for features,labels in val_loader: outputs = model(features) val_loss += criterion(outputs,labels).item() avg_val_loss=val_loss/len(val_loader) val_losses.append(avg_val_loss)
|
将各轮损失进行可视化:
损失变化
可以看到第四轮开始就出现过拟合现象了,可以根据损失的变化调整训练轮数。由于模型拟合已较好,经过调整实际变化不大。
测试
确定模型后,就可以使用测试集对模型效果进行测试。调整后的模型在测试集上精确度达到了98.08%。
1 2 3 4 5 6 7 8 9 10 11
| model.eval() with torch.no_grad(): correct = 0 total = 0 for features, labels in test_loader: outputs = model(features) _,predicted = torch.max(outputs.data,1) total += labels.size(0) correct += (predicted==labels).sum().item() accuracy=correct/total print(f"{accuracy*100:.2f}%")
|
我们还可以输出几个样本来观察预测是否正确。
部分预测结果