就只是贴了一个代码,部分解释写在代码注释里面了,之后会补上文字解释:
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
import time #用于计时的
#定义一个名为Zch的神经网络模型的类 ,之所以是神经网络模型的类是因为Zch继承了nn.Module
class Zch(nn.Module):
def __init__(self):
super(Zch, self).__init__()
self.module1 = Sequential(
Conv2d(3, 32, 5, padding=2) ,
MaxPool2d(2) ,
Conv2d(32, 32, 5, padding=2) ,
MaxPool2d(2) ,
Conv2d(32, 64, 5, padding=2) ,
MaxPool2d(2) ,
Flatten() , # torch对于在nn.Module中也有专门的flatten函数,torch.nn.Flatten()
Linear(1024, 64) ,
Linear(64, 10)
)
#对于神经网络模型,虽说训练时要一来一回 : (来:)需要前向传播计算loss,grad ; (回:)需要反向传播时修正模型中的参数
#但显然,通过前想传播就可以构造出计算图了(计算的流程图了) , 所以 ,反向传播写在神经网络类的定以外就可以了(而且反响传播人家pytorch已经集装好了,两条函数调用就可以了)
def forward(self,x):
output = self.module1(x)
return output
#引入(下载)torch官方已经整理好的数据集CIFAR10
train_dataset = torchvision.datasets.CIFAR10("dataset",train=True,transform=torchvision.transforms.ToTensor(),download=True) # 第二个参数train=True意思是 这是训练数据集;
test_dataset = torchvision.datasets.CIFAR10("dataset",train=False,transform=torchvision.transforms.ToTensor(),download=True) # 第二个参数train=True意思是 这是测试数据集;
#展示train_dataset 、 test_dataset 的长度
print( len( train_dataset ) )
print( len( test_dataset ) )
#通过DataLoader类实例化训练集数据和测试集数据的Dataloader数据加载器
train_dataloader = DataLoader(train_dataset,batch_size=64)
test_dataloader = DataLoader(test_dataset,batch_size=64)
z1 = Zch() # 实例化模型,我们得把训练的数据塞到一个实例出来的模型中去
loss_fn = nn.CrossEntropyLoss() # 定义(实例化) 损失函数,由于CIFAR10是一个10分类问题,所以将损失函数实例化为一个交叉熵计算工具,用于计算预测值和真实值之间的差距loss , 并可以通过调用.backward()来根据loss计算出模型中的各个参数的梯度grad
learning_rate = 1e-2 # 1e-2 就是0.01的意思,这样更好看、直观。并且,learning_rate是一个学习是比较重要的参数,所以,但提出来,方便查找和修改
optim = torch.optim.SGD(z1.parameters() , lr=learning_rate) #定义(实例化)一个优化器,把模型的实例z1的参数与optim绑定,好让优化器知道 该通过loss_fn.backward()计算出的grad 来去修正谁
#一般会训练多个epoch (1个epoch就是一个全数据集走一遍)
epoch = 10 # 训练的轮数
train_step = 0
test_step = 0
start_time = time.time()
for idx in range(epoch):
z1.train() #对于神经网络中有Dropout层、BatchNorm层等时,在模型训练、测试的时在训练前要明确地加 .train() .eval()标注。 如果网络中没有这两类层结构,那么,.train() .eval()加不加其实没关系不影响模型训练
for data in train_dataloader :
imgs,labels = data #由于CIFAR10是一个10分类问题,所以对于每个图片他都是输出一个数值代表一个类别。由于我们设置batch_size=64 , 所以,labels就算一个长度为64的1维度tensor向量
outputs = z1(imgs)
loss = loss_fn(outputs , labels) # 计算得到loss
#接下来准备用优化器根据loss调用backward()函数 求出的 grad 来对模型参数进行更新 。
#【注意】由于我们是在一套循环里面反复调用优化器对模型参数进行更新,为了避免上一轮的干扰,每次在loss计算相应的grad前,先将优化器的内容清空
optim.zero_grad()
loss.backward() #根据计算得到的loss计算出对应的grad
optim.step() #调用优化器,根据 loss.backward()求得的grad 对模型参数进行更新
train_step += 1
tt_time = time.time()
if train_step % 100 == 0 :
print("第",train_step,"次训练迭代时,loss为:",loss.item()) #loss.item()和loss的区别在于:区别不大,略
print("到第", train_step, "次训练迭代时,耗时:", tt_time - start_time)
#如何确认训练的模型是否有效呢? -- 通过测试数据集:测试集数据 传入 刚刚在训练的模型 ,查看其准确率,毕竟loss没有准确率来的直观
#测试集:
z1.eval() # 对于神经网络中有Dropout层、BatchNorm层等时,在模型训练、测试的时在训练前要明确地加 .train() .eval()标注。 如果网络中没有这两类层结构,那么,.train() .eval()加不加其实没关系不影响模型训练
with torch.no_grad() : #在测试的时候就不需要对模型进行调优了,所以我们需要写明no_grad()
test_total_loss = 0
test_total_accuracy = 0 #在一轮epoch中可以正确预测多少个图片分类
for data in test_dataloader :
imgs , targets = data
outputs = z1(imgs)
#计算loss
loss = loss_fn(outputs , targets)
test_total_loss += loss
#计算accuracy
#labels是一个1维的64元素的tensor,我们的outputs是一个2维的64*10的tensor,64是batchsize大小,而10是因为我们的输出是经网络训练后,该张图片是10分类中的各个分类的概率
outputs = outputs.argmax(1) #找到一维array中的最大值,并返回该最大值所在的array的下标 ; 参数为0意味着一列列对比,参数为1意味着一行行对比
accuracy = (outputs == targets).sum()
test_total_accuracy += accuracy
print("第",idx,"轮测试集上的总loss是:",test_total_loss.item()) #不加.item()输出是:tensor(311.6063) 而非 311.6063 就很乱
print("第", idx, "轮测试集上的总accuracy是:", test_total_accuracy.item())
# #补充解释一下求解accuracy时的公式: accuracy = (outputs == targets).sum()
# a1 = torch.tensor([1,3,5,7])
# b1 = torch.tensor([1,36,15,7])
#
# res = (a1 == b1) #因为两个tensor相 == 就是会返回一个元素是True或者False的tensor阿
# print(res)
# print(res.sum()) #该元素是True或者False的tensor的调用sum()函数返回该tensor中所有的True值的加和