# 直接复制到编译器里面看就行 # 视频配套:09.多分类问题_哔哩哔哩_bilibili import torch import time start_time = time.time() # 为了计算和gpu计算时的差距,调用这个包来计算程序运行时间 # 以下三个是与数据集相关的包 from torchvision import transforms #torchvision里的transforms针对各种各样的图像进行的处理的工具 from torchvision import datasets from torch.utils.data import DataLoader import torch.nn.functional as F # for using function relu() 采用relu激活函数 import torch.optim as optim # 需要用到的一些优化器 # 1.prepare dataset batch_size = 100 #先把提取到的图像转变成pytorch里面的tensor张量 transform = transforms.Compose([ # transforms属于torchvision模块的方法,它是常见的图像预处理的方法 transforms.ToTensor(), # convert the PIL(pillow) Image to Tensor # 具体转换见:https://www.bilibili.com/video/BV1Y7411d7Ys?p=9&vd_source=181df9645c4940d2088894ef3be6cc28 # 31:00 # 重点看图像张量是怎么实现变换的 # 图像有单通道图和多通道图 用pillow或者opencv读进来的图像一般是(W(wight),H(height),C(channel)) # 在pytorch里面需要把图像转换为(C,W,H),通道需要放在前面,主要是为了在pytorch里面进行更加高效的卷积运算和图像处理 # (W,H,C) -> (C,W,H) transforms.Normalize((0.1307,),(0.3081,)) # 后面两个数字是已经计算好的minst数据集的像素平均值和标准差,如果是自己的数据集,得先计算出来 # 标准化分布的训练效果比较好(eg:标准正态分布) # 上面是在归一化,第一个数是均值,第二个数是标准差,想的是把(0~255)映射到(0~1)上 # 满足(0,1)标准化分布,上面这两个数字是算好的,以后拿到数据集先对平均值和标准差进行计算 # 数据归一化处理transforms.Normalize详情: # https://blog.csdn.net/qq_38765642/article/details/109779370 ]) # 准备训练集 这个训练集有60000张图像 train_dataset = datasets.MNIST(root='../dataset/mnist/', # 训练集的文件位置 train= True, # 是否为训练集 download=True, # 是否需要下载,如果相应位置已经有数据集,那么不需要下载,如果没有的话就得下载 transform=transform) # 训练数据集的时候需要shuffle train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, num_workers=4) # 准备测试集 这个测试集有10000张图像 test_dataset = datasets.MNIST(root='../dataset/mnist/', train= False, download=True, transform=transform) # 测试数据集的时候,不需要shuffle,这样是为了保证顺序是一样的,对观察结果比较有帮助 test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size, num_workers=4) # 以下为网络模型 class Net(torch.nn.Module): def __init__(self): super(Net,self).__init__() self.l1 = torch.nn.Linear(784, 512) # 第二个数字和下面的第一个数字对应 self.l2 = torch.nn.Linear(512, 233) self.l3 = torch.nn.Linear(233, 128) self.l4 = torch.nn.Linear(128, 64) self.l5 = torch.nn.Linear(64, 10) # 最后一个数字是类别数 # 调用model(inputs)直接进入下面的这个forward()方法 def forward(self,x): # 关于x.view() : https://blog.csdn.net/echo_gou/article/details/121035061 # x.view()就是对tensor进行reshape # 这里采用的时全连接网络,需要把每一张图像转换成一个一维的矩阵 x = x.view(-1,784) # print(x.shape) # torch.Size([64, 784]) 说明有64(和batch-size相同,batch-size可更改)张图片 """ -1表示一个不确定的数,784表示列,就是对x进行reshape,n行784列 由于每一张图片是 1*28*28 = 784的,所以这里每一行就是一张图像 """ x = F.relu(self.l1(x)) # self.l1(x)就是先调用上面的torch.nn.Linear(784, 512), # 先对x进行线性变换,然后经过relu进行非线性变换 x = F.relu(self.l2(x)) x = F.relu(self.l3(x)) x = F.relu(self.l4(x)) return self.l5(x) # 最后一层不做激活,这个是为了用交叉熵损失,把输出直接经过 CrossEntropyLoss 函数计算损失 model = Net() #建立损失函数和优化器 """ 使用CrossEntropyLoss的时候,神经网络的最后一层是不需要做激活的,正如下面代码所示 """ criterion = torch.nn.CrossEntropyLoss() # 这个损失函数结合了softmax和交叉熵损失,把这俩集成到了一个里面 optimizer = optim.SGD(model.parameters(),lr=0.01,momentum=0.9) # momentum为动量,一般采用0.9 # 4.Train and Test def train(epoch): # 这里是把一个epoch的训练封装成函数 running_loss = 0.0 #print(train_loader) for batch_idx,data in enumerate(train_loader,0): """ 上方的batch_idx就是我们所说的迭代次数,所以可以这样直接拿出来,即 batch-index ,即每一个mini-batch的索引, minst训练集有60000张图片,所以需要经过 60000 / batch_size 个循环才能完成一次epoch """ inputs, target = data # print(inputs.shape) # torch.Size([64, 1, 28, 28]) # print(target.shape) # torch.Size([64]) optimizer.zero_grad() # 优化器清零,默认梯度会累加,如果不清0,那么loss会很快变成无穷大 # forward + backward + update(前馈+反馈+更新) #前馈: """ __call__魔术方法,使得类实例对象可以像调用普通函数那样,以"对象名()"的形式使用 在Net继承的父类 torch.nn.Module 里面也有forward方法,而本代码在Net里面重写了forward方法 所以下面 model(inputs) 就是在调用类Net里面的forward方法,下面这一行代码就是网络的接口 详情见:https://blog.csdn.net/fengbingchun/article/details/122331018 """ outputs = model(inputs) # 经过所设定的网络计算输出 loss = criterion(outputs,target) # 通过输出计算交叉熵损失 #反馈: loss.backward() # 反向传播,梯度计算 #更新: optimizer.step() # 更新参数 # 为了更方便地表示loss,先让它累加三百次,然后再将这300次的累加值取平均,以便更好地表示出来, # 否则循环一次就表示一次loss,那也太蠢了 running_loss += loss.item() # 再强调一遍,.item()就是将tensor转换成值,便于python里面的计算 #每输出300次迭代,输出一次running_loss if batch_idx % 200 == 199: # %5d是C语言的格式化输出,对不足5位的整形用空格补齐5位 # print('[%d, %d] loss: %.6f' % (epoch + 1, batch_idx + 1, running_loss / 200)) print('[%d, %d] loss: %.6f' % (epoch + 1, batch_idx + 1, running_loss / 200)) running_loss = 0.0 def test(): correct = 0 total = 0 # 执行完torch.no_grad(),下面的代码就不会再去计算梯度了,不会形成计算图,不会迭代新参数,相当于拿训练好的模型来测试 # torch.no_grad()的说明:https://blog.csdn.net/Answer3664/article/details/99460175 with torch.no_grad(): # with的说明:https://blog.csdn.net/ego_bai/article/details/80873242 for data in test_loader: # 这一行和test_loader那一行差不多的意思 images, labels = data outputs = model(images) # imags相当于测试集的输入,outputs是测试集地输出 # print(outputs.data.shape) # torch.Size([100, 10]) batch-size = 100 #print(images.shape) _, predicted = torch.max(outputs.data, dim=1) # 按行取最大值,得出行上面最大值所对应地索引,这里比较特殊,得出的索引就是识别出来的数字 # print(predicted) # print(labels) # print(predicted.shape) # torch.Size([100]) # print(labels.size(0)) # 100 # 下面地total就是已经测试过的样本总数 total += labels.size(0) # 这里labels.size(0)和batch-size相等,等于每一个batch的样本数量 # 下面是对 predicted 和 labels 里面的值分别比较 # predicted == labels 输出的是一个tensor,里面是bool类型的东西,相应位置一样就是true,不一样就是false # (predicted == labels).sum() 输出的是一个tensor类型的数字,我的batch是100,输出了tensor(97) # 最后的.item()就是把tensor(97)转换成97,用来求解数字,赋值给correct # correct是目前测试的正确的总数,而total是目前测试的样本总数,当测试完成后,correct / total 就是正确率 correct += (predicted == labels).sum().item() # 张量之间的比较运算 """ 关于 .item()的笔记:https://pytorch.org/docs/stable/tensors.html?highlight=item#torch.Tensor.item x = torch.tensor([[1]]) print(x) # tensor([[ 1]]) print(x.item()) # 1 简而言之,.item()就是把tensor类型转换成数字类型,便于运算 """ # 下面的代码对正确率进行了处理,以便于输出百分数 # print(correct / total) print('accuracy on test set: %.3f %% ' % (100 * correct / total)) if __name__ == '__main__': for epoch in range(10): print("epoch: %d / %d"%(epoch+1,20)) # 下面相当于训练 1 个epoch就测试 1 次,用来查看测试准确率是否上升 train(epoch) # test() # 这样是训练一轮测试一次,写在epoch的for循环里面 test() # 这样是所有样本训练完了最后测试,写在epoch的for循环外面 end_time = time.time() print("Total running time:",end_time - start_time,"s")