前面的文章(PyTorch安装环境与基础知识 )介绍了PyTorch的基本接口,已经掌握了 如何定义Net, forword参数,loss,参数更新。在这篇文章里,我们来看如何制作训练集,并且实现完整的训练和测试。和前一篇一样,我们还是从官方的一小时手册中的cifar10入手。
1.别人家的CIFAR10
由于torchvision中有函数可以自动获得几个常见的数据集(minist,cifar10,imagenet),因此可以直接获得trainset.然后transforms是对数据的一些预处理。transforms.Compose()是把几个transform语句合并到一起。ToTensor()是数据类型的转换,nomalize传入三通道的两个参数是:mean,std.
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
[transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
2.你的数据集
Ref[1]模仿cifar10的训练集做了自己训练集,用于数字的分类。首先是定义了一个训练集图片train存放的地址path。然后使用ImageFolder()函数读取,并执行transforms预处理。其中Resize()可以传入两类参数。一类是(h,w)这将把图片缩放到(h,w)大小(长宽比会改变)。另一类是传入单个参数,这将把图片经过缩放后(保持长宽比不变),将最短的边缩放到传入的参数。
def loadtraindata():
path = r"/home/********/folder/train" # 路径
trainset = torchvision.datasets.ImageFolder(path,
transform=transforms.Compose([ transforms.Resize((32, 32)),
# 将图片缩放到指定大小(h,w)或者保持长宽比并缩放最短的边到int大小
transforms.CenterCrop(32),
transforms.ToTensor()]) )
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
return trainloader
如何训练你的数据集?该项目的作者命名一个trainandsave函数,主要是把训练和网络的参数保存放在一起。作者写了详细的注释,我这里就原版引用。
def trainandsave():
trainloader = loadtraindata() # 神经网络结构
net = Net()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 学习率为0.001 criterion = nn.CrossEntropyLoss() # 损失函数也可以自己定义,我们这里用的交叉熵损失函数 # 训练部分
for epoch in range(5): # 训练的数据量为5个epoch,每个epoch为一个循环
# 每个epoch要训练所有的图片,每训练完成200张便打印一下训练的效果(loss值)
running_loss = 0.0 # 定义一个变量方便我们对loss进行输出
for i, data in enumerate(trainloader, 0): # 这里我们遇到了第一步中出现的trailoader,代码传入数据
# enumerate是python的内置函数,既获得索引也获得数据
# get the inputs
inputs, labels = data # data是从enumerate返回的data,包含数据和标签信息,分别赋值给inputs和labels
# wrap them in Variable
inputs, labels = Variable(inputs), Variable(labels) # 转换数据格式用Variable
optimizer.zero_grad() # 梯度置零,因为反向传播过程中梯度会累加上一次循环的梯度
# forward + backward + optimize
outputs = net(inputs) # 把数据输进CNN网络net
loss = criterion(outputs, labels) # 计算损失值
loss.backward() # loss反向传播
optimizer.step() # 反向传播后参数更新
running_loss += loss.data[0] # loss累加
if i % 200 == 199:
print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) # 然后再除以200,就得到这两百次的平均损失值
running_loss = 0.0 # 这一个200次结束后,就把running_loss归零,下一个200次继续使用
print('Finished Training') # 保存神经网络
torch.save(net, 'net.pkl') # 保存整个神经网络的结构和模型参数
torch.save(net.state_dict(), 'net_params.pkl') # 只保存神经网络的模型参数
可以看到enumerate把训练集的inputs和labels分开。input作为网络的输入,并使用lossfunction计算output 和label。
由于之后的项目会比较大,ref[2]给了不同文档执行不同功能的建议,我觉得这种做法有利于在一个整洁的环境中,完成项目,一并推荐给各位客官。
Ref