我们来看一下这个函数unsqueeze的名字,大概意思就是解压,实际上就是在指定的维度上进行展开。在不考虑其他的参数的时候,这个函数大概长下面这个样子:
unsqueeze(dim)这个函数的实际意义是,在指定的维度dim的位置,增加一个 ‘1’,使得整个tensor被提高一个维度。对于我们的训练部分中的img.unsqueeze(0),含义就是在第0维的位置前添加上一个 '1',这样我们的图片的维度就被强制转换为[1, channel = 1, height = 16, width = 16]的形式了,正好1就代表我们batch_size = 1,就是只有一张图片。与这个函数对应的还有一个函数squeeze(dim),这个函数的功能就刚好相反了,暂时我们还用不到,等到之后用到了再说咯(我记得好像NLP作业里面就用到了)
放下学习率变化的那部分代码不谈,我们的训练函数部分基本结束······那是不可能的,我们辛辛苦苦训练的模型,还没保存呢。那就保存一下呗┓( ´∀` )┏
torch.save(model.state_dict(), 'F:\\Code_Set\\Python\\PaperExp\\LeNet-1989\\epoch-{:d}_loss-{:.6f}_error-{:.2%}.pth'.format(epochs, lossList[-1], testError[-1])) #路径自己指定哟,我这个只是我自己的路径接下来我们要做的,就是把代码全都放在一起,并且添加一些可以做的简单的可视化工作咯:
import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets from torchvision import transforms as T from torch.utils.data import DataLoader import matplotlib.pyplot as plt ''' 定义数据的初始化方法: 1. 将图片的尺寸强制转化为 16 * 16 2. 将数据转化成tensor 3. 将数据的灰度值范围从[0, 1]转化为[-1, 1] ''' picProcessor = T.Compose([ T.Resize((16, 16)), T.ToTensor(), T.Normalize( mean = [0.5], std = [0.5] ), ]) ''' 数据的读取和处理: 1. 从官网下载太慢了,所以先重新指定路径,并且在mnist.py文件里把url改掉,这部分可以百度,很容易找到的 2. 使用上面的处理器进行MNIST数据的处理,并加载 ''' dataPath = "F:\\Code_Set\\Python\\PaperExp\\DataSetForPaper\\" #在使用的时候请改成自己实际的MNIST数据集路径 mnistTrain = datasets.MNIST(dataPath, train = True, download = False, transform = picProcessor) #首次使用的时候,把download改成True,之后再改成False mnistTest = datasets.MNIST(dataPath, train = False, download = False, transform = picProcessor) # 因为如果在CPU上,模型的训练速度还是相对来说较慢的,所以如果有条件的话就在GPU上跑吧(一般的N卡基本都支持) device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu") ''' 神经网络类的定义 1. 输入卷积: in_channel = 1, out_channel = 12, kernel_size = (5, 5), stride = (2, 2), padding = 2 2. 激活函数: 1.7159Tanh(2/3*x) 3. 第二层卷积: in_channel = 12, out_channel = 12, kernel_size = (5, 5), stride = (2, 2), padding = 2 4. 激活函数同上 5. 全连接层: 192 * 30 6. 激活函数同上 7. 全连接层:30 * 10 8. 激活函数同上 按照论文的说明,需要对网络的权重进行一个[-2.4/F_in, 2.4/F_in]的均匀分布的初始化 ''' class LeNet1989(nn.Module): def __init__(self): super(LeNet1989, self).__init__() self.conv1 = nn.Conv2d(1, 12, 5, stride = 2, padding = 2) self.act1 = nn.Tanh() self.conv2 = nn.Conv2d(12, 12, 5, stride = 2, padding = 2) self.act2 = nn.Tanh() self.fc1 = nn.Linear(192, 30) self.act3 = nn.Tanh() self.fc2 = nn.Linear(30, 10) self.act4 = nn.Tanh() for m in self.modules(): if isinstance(m, nn.Conv2d): F_in = m.kernel_size[0] * m.kernel_size[1] * m.in_channels m.weight.data = torch.rand(m.weight.data.size()) * 4.8 / F_in - 2.4 / F_in if isinstance(m, nn.Linear): F_in = m.in_features m.weight.data = torch.rand(m.weight.data.size()) * 4.8 / F_in - 2.4 / F_in def forward(self, x): x = self.conv1(x) x = 1.7159 * self.act1(2.0 * x / 3.0) x = self.conv2(x) x = 1.7159 * self.act2(2.0 * x / 3.0) x = x.view(-1, 192) x = self.fc1(x) x = 1.7159 * self.act3(2.0 * x / 3.0) x = self.fc2(x) out = 1.7159 * self.act4(2.0 * x / 3.0) return out lossList = [] testError = [] def train(epochs, model, optimizer, scheduler: bool, loss_fn, trainSet, testSet): trainNum = len(trainSet) testNum = len(testSet) for epoch in range(epochs): lossSum = 0.0 print("epoch: {:02d} / {:d}".format(epoch+1, epochs)) #这段主要是显示点东西,免得因为硬件问题训练半天没动静还以为电脑死机了┓( ´∀` )┏ #训练部分 for idx, (img, label) in enumerate(trainSet): x = img.unsqueeze(0).to(device) #将标签转化为one-hot向量 y = torch.zeros(1, 10) y[0][label] = 1.0 y = y.to(device) #梯度下降与参数更新 out = model(x) optimizer.zero_grad() loss = loss_fn(out, y) loss.backward() optimizer.step() lossSum += loss.item() if (idx + 1) % 5000 == 0: print("sample: {:05d} / {:d} --> loss: {:.4f}".format(idx+1, trainNum, loss.item())) #同样的,免得你以为电脑死了,顺便看看损失函数,看看是不是在下降,如果电脑性能比较差,可以改成100,这样每隔几秒就有个显示,起码心里踏实┓( ´∀` )┏ lossList.append(lossSum / trainNum) #测试部分,每训练一个epoch就在测试集上进行错误率的求解与保存 with torch.no_grad(): errorNum = 0 for img, label in testSet: x = img.unsqueeze(0).to(device) out = model(x) _, pred_y = out.max(dim = 1) if(pred_y != label): errorNum += 1 testError.append(errorNum / testNum) #这段代码是用来改变学习率的,现在先不用看,如果觉得影响性能,可以全都注释掉 if scheduler == True: if epoch < 2: for param_group in optimizer.param_groups: param_group['lr'] = 5.0e-4 elif epoch < 5: for param_group in optimizer.param_groups: param_group['lr'] = 2.0e-4 elif epoch < 8: for param_group in optimizer.param_groups: param_group['lr'] = 1.0e-4 elif epoch < 12: for param_group in optimizer.param_groups: param_group['lr'] = 5.0e-5 else: for param_group in optimizer.param_groups: param_group['lr'] = 1.0e-5 torch.save(model.state_dict(), 'F:\\Code_Set\\Python\\PaperExp\\LeNet-1989\\epoch-{:d}_loss-{:.6f}_error-{:.2%}.pth'.format(epochs, lossList[-1], testError[-1])) #模型的保存路径记得改成自己想要的哟 if __name__ == '__main__': model = LeNet1989().to(device) loss_fn = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr = 1.0e-3) scheduler = False #因为我们还不用设置学习率改变,所以设置False #当然啦,如果想要试试效果也可以改一下,但是我测试过要训练很多代才能达到论文中的提到的结果 epochs = 40 train(epochs, model, optimizer, scheduler, loss_fn, mnistTrain, mnistTest) plt.subplot(1, 2, 1) plt.plot(lossList) plt.subplot(1, 2, 2) testError = [num * 100 for num in testError] plt.plot(testError) plt.show() 训练及结果