medium
有人可能会争辩说,最好是过拟合你的模型,然后对其进行逆向工程而不是相反。
在这个项目中,我们可以看到将Dropout正则化实现到神经网络后的准确性和验证损失的差异。 我们将使用PyTorch库从头开始构建一个顺序神经网络,以便在fashion-MNIST数据集中对10个不同的类进行分类。 这个数据集是28x28灰度图像的衣服。 我们将深入研究dropout的方法,并证明它是否能防止过度拟合。
去除正则化会避免模型过拟合吗? - 图1
该项目的灵感来自:

  1. Facebook Udacity PyTorch Challenge.

首先,我们将创建一个没有正则化实现的神经网络,我们的假设是我们可以推断,随着时间的推移,我们的模型在验证集中表现不佳,因为我们用训练集训练我们的模型越多, 通过对测试数据的特定特征进行分类则越好,从而创建不良的泛化模型去推理。

让我们导入Fashion-MNIST数据集

让我们使用torchvision下载数据集,通常我们将20%的数据集分开用于验证集。 但在这种情况下,我们将直接从torchvision下载数据集。

  1. import torch
  2. from torchvision import datasets, transforms
  3. import helper
  4. transform = transforms.Compose([transforms.ToTensor(),
  5. transforms.Normalize((0.5,0.5,0.5),
  6. (0.5,0.5,0.5))])
  7. traindataset = datasets.FashionMNIST('~/.pytorch/F_MNIST_data/', download=True,train=True,transform=transform)
  8. trainloader = torch.utils.data.DataLoader(dataset=traindataset, batch_size=64, shuffle=True)
  9. testdataset = datasets.FashionMNIST('~/.pytorch/F_MNIST_data/', download=True,train=False,transform=transform)
  10. testloader = torch.utils.data.DataLoader(dataset=testdataset, batch_size=64, shuffle=True)

我们需要导入torchvision来下载数据集和转换。 然后我们使用变换库将图像转换为张量并进行标准化。 通常批量训练和验证集以提高训练速度并且改组数据也会增加训练和测试数据的学习差异。
定义神经网络
这个模型将有2个隐藏层,输入层将有784个单元,并且在最终层将有10个输出,因为我们有10个不同的类进行分类。 我们将使用交叉熵损失,因为它具有对数性质,可以将我们的输出归一化到接近零或一。

  1. from torch import nn
  2. from torch.functional import F
  3. class FashionNeuralNetwork(nn.Module):
  4. def __init__(self):
  5. super().__init__()
  6. # Create layers here
  7. self.layer_input = nn.Linear(784,256)
  8. self.layer_hidden_one = nn.Linear(256,128)
  9. self.layer_hidden_two = nn.Linear(128,64)
  10. self.layer_output = nn.Linear(64,10)
  11. def forward(self, x):
  12. # Flattened the input to make sure it fits the layer input
  13. x = x.view(x.shape[0],-1)
  14. # Pass in the input to the layer and do forward propagation
  15. x = F.relu(self.layer_input(x))
  16. x = F.relu(self.layer_hidden_one(x))
  17. x = F.relu(self.layer_hidden_two(x))
  18. # Dimension = 1
  19. x = F.log_softmax(self.layer_output(x),dim=1)
  20. return x

该神经网络将使用ReLU作为隐藏层的非线性激活函数,并使用log-softmax激活输出和负对数似然函数用于我们的损失函数。 如果我们查看PyTorch库中的交叉熵损失的文档,该标准将nn.LogSoftmax()和nn.NLLLoss()组合在一个单独的类中。 损失可以描述为:
去除正则化会避免模型过拟合吗? - 图2

请注意,转发传播结束时的线性函数的dim = 1,这意味着输出结果的每一行的概率总和必须等于1.给单个元素的概率最高图像的概率最高 被归类为相应的类索引。
我们必须确保模型的输出形状正确性,

  1. # Instantiate the model
  2. model = FashionNeuralNetwork()
  3. # Get the images and labels from the test loader
  4. images, labels = next(iter(testloader))
  5. # Get the log probability prediction from our model
  6. log_ps = model(images)
  7. # Normalize the probability by taking the exponent of the log-prob
  8. ps = torch.exp(log_ps)
  9. # Print out the size
  10. print(ps.shape)

确保输出为:

  1. torch.Size([64, 10])

测量我们模型的准确性

由于我们想要一个类的最高概率,我们将使用ps.topk来获得top-k值和top-k索引的元组,例如,如果在4个元素中最高为kth,我们将得到3作为指数。

  1. top_p, top_class = ps.topk(1,dim=1)
  2. # Print out the most likely classes for the first 10 examples
  3. print(top_class[:10,:])

去除正则化会避免模型过拟合吗? - 图3
top_class是尺寸为64x1的2D张量,而我们的标签是尺寸为64的1D张量。为了测量标签和模型预测之间的准确度,我们必须确保张量的形状是相同的。

  1. # We have to reshape the labels to 64x1 using the view() method
  2. equals = top_class == labels.view(*top_class.shape)
  3. print(equals.shape)

比较张量的输出将是:

  1. torch.Size([64, 1])

为了计算模型的准确性,我们只需计算模型正确预测的次数。 如果我们的预测与标签相同,则上面的==运算符将逐行检查。 最终结果将是二进制0不相同,1正确预测。 我们可以使用torch.mean计算平均值,但我们需要将equals转换为FloatTensor。

  1. accuracy = torch.mean(equals.type(torch.FloatTensor))
  2. # Print the accuracy
  3. print(f'Accuracy: {accuracy.item()*100}%')

训练我们的模型

由于我们希望损失函数与Logarithm Softmax函数的行为相反,我们将使用负对数似然来计算我们的损失。

  1. from torch import optim
  2. # Instantiate the model
  3. model = FashionNeuralNetwork()
  4. # Use Negative Log Likelyhood as our loss function
  5. loss_function = nn.NLLLoss()
  6. # Use ADAM optimizer to utilize momentum
  7. optimizer = optim.Adam(model.parameters(), lr=0.003)
  8. # Train the model 30 cycles
  9. epochs = 30
  10. # Initialize two empty arrays to hold the train and test losses
  11. train_losses, test_losses = [],[]
  12. # Start the training
  13. for i in range(epochs):
  14. running_loss = 0
  15. # Loop through all of the train set forward and back propagate
  16. for images,labels in trainloader:
  17. optimizer.zero_grad()
  18. log_ps = model(images)
  19. loss = loss_function(log_ps, labels)
  20. loss.backward() # Backpropagate
  21. optimizer.step()
  22. running_loss += loss.item()
  23. # Initialize test loss and accuracy to be 0
  24. test_loss = 0
  25. accuracy = 0
  26. # Turn off the gradients
  27. with torch.no_grad():
  28. # Loop through all of the validation set
  29. for images, labels in testloader:
  30. log_ps = model(images)
  31. ps = torch.exp(log_ps)
  32. test_loss += loss_function(log_ps, labels)
  33. top_p, top_class = ps.topk(1,dim=1)
  34. equals = top_class == labels.view(*top_class.shape)
  35. accuracy += torch.mean(equals.type(torch.FloatTensor))
  36. # Append the average losses to the array for plotting
  37. train_losses.append(running_loss/len(trainloader))
  38. test_losses.append(test_loss/len(testloader))

打印我们的模型
去除正则化会避免模型过拟合吗? - 图4
这证明了我们的假设,即完全说明我们的模型将训练的很好,但不能推广训练数据集之外的图像。 我们可以看到,30个周期的训练损失显著减少,但我们的验证损失在大约36-48%之间波动。 这是过度拟合的标志,这个情况说明,模型学习训练数据集的特定特征和模式,它无法正确分类数据集之外的图像。 这通常很糟糕,因为这意味着如果我们使用推理,模型就无法正确分类。

为了清楚的说明

让我们画图看一下:

  1. # Plot the graph here
  2. %matplotlib inline
  3. %config InlineBackend.figure_format = 'retina'
  4. import matplotlib.pyplot as plt
  5. plt.plot(train_losses, label='Training Loss')
  6. plt.plot(test_losses, label='Validation Loss')
  7. plt.legend(frameon=True)

去除正则化会避免模型过拟合吗? - 图5

过度拟合

从上图中我们可以清楚地看到,我们的模型并没有很好地泛华。 这意味着该模型在对训练数据集之外的图像进行分类方面做得不好。 这真的很糟糕,这意味着我们的模型只学习我们的训练数据集的具体内容,它变得如此个别,以至于它只能识别来自训练集的图像。 如果我们从图表中看到,每个周期的训练损失都会显著减少,但是,我们可以看到验证损失却没发生什么变化。

正则

这就是正则化的用武之地,其中一种方法是进行L2正则化,也称为early-stopping,这基本上意味着我们将在验证损失最低时停止训练我们的模型。 在这种情况下,我们的验证损失在3-5个时期后达到最佳。 这意味着超过5个周期,我们的模型泛化会变得更糟。
但是,还有另一种方法可以解决这个问题。 我们可以为我们的模型进行dropout,以进行更多的泛化。 基本上,我们的模型通过在大型重量上滚雪球并使其他要训练的重量不足而贪婪地行动。 通过具有随机丢失,具有较小权重的节点将有机会在循环期间被训练,从而在结束时给出更一般化的分数。 换句话说,它迫使网络在权重之间共享信息,从而提供更好的泛化能力。
注意:
在训练期间,我们希望实行dropout,但是,在验证过程中,我们需要我们模型的全部功能,因为那时我们可以完全测量模型对这些图像进行泛化其准确性。 如果我们使用model.eval()模式,我们将停止使用dropout,并且不要忘记在训练期间使用model.train()再次使用它。

  1. ### Define our new Network with Dropouts
  2. class FashionNeuralNetworkDropout(nn.Module):
  3. def __init__(self):
  4. super().__init__()
  5. # Create layers here
  6. self.layer_input = nn.Linear(784,256)
  7. self.layer_hidden_one = nn.Linear(256,128)
  8. self.layer_hidden_two = nn.Linear(128,64)
  9. self.layer_output = nn.Linear(64,10)
  10. # 20% Dropout here
  11. self.dropout = nn.Dropout(p=0.2)
  12. def forward(self, x):
  13. # Flattened the input to make sure it fits the layer input
  14. x = x.view(x.shape[0],-1)
  15. # Pass in the input to the layer and do forward propagation
  16. x = self.dropout(F.relu(self.layer_input(x)))
  17. x = self.dropout(F.relu(self.layer_hidden_one(x)))
  18. x = self.dropout(F.relu(self.layer_hidden_two(x)))
  19. # Dimension = 1
  20. x = F.log_softmax(self.layer_output(x),dim=1)
  21. return x

这个神经网络将与第一个模型非常相似,但是,我们将增加20%的丢失。 现在让我们训练这个模型吧!

  1. from torch import optim
  2. # Instantiate the model
  3. model = FashionNeuralNetworkDropout()
  4. # Use Negative Log Likelyhood as our loss function
  5. loss_function = nn.NLLLoss()
  6. # Use ADAM optimizer to utilize momentum
  7. optimizer = optim.Adam(model.parameters(), lr=0.003)
  8. # Train the model 30 cycles
  9. epochs = 30
  10. # Initialize two empty arrays to hold the train and test losses
  11. train_losses, test_losses = [],[]
  12. # Start the training
  13. for i in range(epochs):
  14. running_loss = 0
  15. # Loop through all of the train set forward and back propagate
  16. for images,labels in trainloader:
  17. optimizer.zero_grad()
  18. log_ps = model(images)
  19. loss = loss_function(log_ps, labels)
  20. loss.backward() # Backpropagate
  21. optimizer.step()
  22. running_loss += loss.item()
  23. # Initialize test loss and accuracy to be 0
  24. test_loss = 0
  25. accuracy = 0
  26. # Turn off the gradients
  27. with torch.no_grad():
  28. # Turn on Evaluation mode
  29. model.eval()
  30. # Loop through all of the validation set
  31. for images, labels in testloader:
  32. log_ps = model(images)
  33. ps = torch.exp(log_ps)
  34. test_loss += loss_function(log_ps, labels)
  35. top_p, top_class = ps.topk(1,dim=1)
  36. equals = top_class == labels.view(*top_class.shape)
  37. accuracy += torch.mean(equals.type(torch.FloatTensor))
  38. # Turn on Training mode again
  39. model.train()
  40. # Append the average losses to the array for plotting
  41. train_losses.append(running_loss/len(trainloader))
  42. test_losses.append(test_loss/len(testloader))

输出结果:
去除正则化会避免模型过拟合吗? - 图6
这里的目标是使验证损失与我们的训练损失一样低,这意味着我们的模型相当准确。 让我们再次绘制图表,看看正则化后的差异。 即使精度水平仅整体上升0.3%,该模型也没有过度拟合,因为它保持了在训练期间训练的所有节点的平衡。 让我们绘制图表并查看差异:

  1. # Plot the graph here
  2. %matplotlib inline
  3. %config InlineBackend.figure_format = 'retina'
  4. import matplotlib.pyplot as plt
  5. plt.plot(train_losses, label='Training Loss')
  6. plt.plot(test_losses, label='Validation Loss')
  7. plt.legend(frameon=True)

去除正则化会避免模型过拟合吗? - 图7

推理

现在我们的模型可以更好地泛化,让我们用提供模型尝试用训练数据集之外的图像进行预测,并可视化模型的分类。

  1. # Make sure to make our model in the evaluation mode
  2. model.eval()
  3. # Get the next image and label
  4. images, labels = next(iter(testloader))
  5. img = images[0]
  6. # Convert 2D image to 1D vector
  7. img = img.view(1, 784)
  8. # Calculate the class probabilities (log-softmax) for img
  9. with torch.no_grad():
  10. output = model.forward(img)
  11. # Normalize the output
  12. ps = torch.exp(output)
  13. # Plot the image and probabilities
  14. helper.view_classify(img.view(1, 28, 28), ps, version='Fashion')

去除正则化会避免模型过拟合吗? - 图8

结论

这很棒! 我们可以看到培训损失和验证损失之间的显着平衡。 可以肯定地说,如果我们训练模型进行更多循环并微调我们的超参数,则验证损失将减少。 从上图中我们可以看出,我们的模型随着时间的推移更好地泛化,模型在6-8个时期之后可以获得更好的精度,并且可以肯定地说模型通过实现模型的丢失来防止过度拟合。

Thank you so much for your time, and please check out this repository for the full code!

This is my Portfolio and Linked-In profile :)