(39条消息) 用pytorch构建Alexnet模型(train模块)(个人笔记)_train make_grid_韩式春秋的博客-CSDN博客
(39条消息) Pytorch 带你一行一行分析训练脚本_len(train_loader)_Rosinante.的博客-CSDN博客
训练集在送入网络前,需要被读取加载,并划分为训练集和验证集,并指定图像增强方法,然后以batch size为单位分批送入网络。
(1)transforms
torchvision.transfroms实现了丰富的图像增强方法,可以对PIL Image 和 Tensor进行转化.
注意,有些transforms只能对PIL或tensor类型之一使用,而有些transforms即能对PIL进行变换也可以对tensor进行变换。具体参见:https://pytorch.org/vision/stable/transforms.html
很多情况下,我们往往会使用多种增强方法,那么便可以使用transforms.Compose将多种变换方法串联组装起来,图片会依次经过transforms.Compose中的变换。
为了方便对训练集和数据集采用不同的增强方法,我们可以使用一个字典来保存我们要使用的增强方法,如下代码所示,定义一个data_transform字典,该字典有两个key,分别为“train”和“val”,其value为图像增强的方法。
(2)dataset
对于一个图片分类任务,我们用两种方式读取数据集,一种是使用pytorch的dataset类(这个类往往需要根据自己的任务进行重写),另一种是直接使用ImageFolder。
(3)DataLoader
dataset类是pytorch中表示数据集的抽象类,那么DataLoader作为一个迭代器,每次会产生一个batch size大小的数据
在对数据集的处理完成后,需要引入网络模型、定义损失函数和优化器等。
net.train()
net.eval()
import torch.optim as optim
import torch
import torch.nn as nn
from torchvision import transforms,datasets
import os
import json
import sys
from tqdm import tqdm
from 自己手敲VGG import vgg
"""训练集在送入网络前,需要被读取加载,并划分为训练集和验证集,并指定图像增强方法,然后以batch size为单位分批送入网络。
(1)transforms
torchvision.transfroms实现了丰富的图像增强方法,可以对PIL Image 和 Tensor进行转化
transforms.RandomResizedCrop(224) # 将图片随机裁剪,并resize到224*224
transforms.RandomHorizontalFlip(p=0.5) # 将图片以0.5的概率水平翻转
transforms.CenterCrop(224) # 从图片中心位置,以224为尺寸进行裁剪
transforms.RandomRotation() # 按角度旋转图片
transforms.ToTensor() # 将图片转化为tensor,图片将会被归一化到[0, 1],且其维度会从(H x W x C)转为(C x H x W)
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 按mean和std归一化到[-1, 1] [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]是从ImageNet数据集上得到mean和std
transforms.RandomErasing() # 在张量图像中随机选择一个矩形区域并擦除其像素
注意,有些transforms只能对PIL或tensor类型之一使用,而有些transforms即能对PIL进行变换也可以对tensor进行变换。具体参见:https://pytorch.org/vision/stable/transforms.html
很多情况下,我们往往会使用多种增强方法,那么便可以使用transforms.Compose将多种变换方法串联组装起来,图片会依次经过transforms.Compose中的变换:
transforms.Compose([transforms.Grayscale(1),
transforms.Resize([224, 224]),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485], [0.229])
])
为了方便对训练集和数据集采用不同的增强方法,我们可以使用一个字典来保存我们要使用的增强方法,如下代码所示,定义一个data_transform字典,该字典有两个key,分别为“train”和“val”,其value为图像增强的方法。
data_transform = {
"train": transforms.Compose([transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),
"val": transforms.Compose([transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])}
(2)dataset
对于一个图片分类任务,我们用两种方式读取数据集,一种是使用pytorch的dataset类(这个类往往需要根据自己的任务进行重写),另一种是直接使用ImageFolder
# 方式一:使用重写的Dataset类
dataset = MyData(label_path,
image_root_path,
transform=data_transform["train"])
train_size = int(len(dataset) * 0.7)
val_size = len(dataset) - train_size
train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size]) # 按比例划分成训练集和测试集
# 方式二:使用ImageFolder
import torchvision.datasets
train_dataset = datasets.ImageFolder(root=train_root_path,
transform=data_transform["train"])
val_dataset = datasets.ImageFolder(root=val_root_path,
transform=data_transform["val"])
(3)DataLoader
dataset类是pytorch中表示数据集的抽象类,那么DataLoader作为一个迭代器,每次会产生一个batch size大小的数据。
nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers
print('Using {} dataloader workers every process'.format(nw))
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size, shuffle=True,
num_workers=nw)
validate_loader = torch.utils.data.DataLoader(val_dataset,
batch_size=batch_size, shuffle=False,
num_workers=nw)
print("using {} images for training, {} images for validation.".format(train_num,
val_num))
2.引入网络模型、损失函数、优化器
在对数据集的处理完成后,需要引入网络模型、定义损失函数和优化器等。
对于优化器,我们必须要传入两个参数,一个是需要更新梯度的网络参数,另一个是学习率。
我们可以直接更新网络中的所有参数:
optimiter = torch.optim.Adam(net.parameters(), lr=0.01)
"""
##################################此训练使用的是批梯度下降,不是随机梯度下降,1个epoch就是所有训练或者测试样本
def main():
device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#如果有Gpu,就用Gpu,没有就用cpu
print("using {} device.".format(device))#using cuda:0 device.
data_transform={
"train":transforms.Compose([transforms.RandomResizedCrop(224),#随机裁剪(裁剪到224*224)
transforms.RandomHorizontalFlip(),# 随机翻转
transforms.ToTensor(),#转化为一个tensor
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]),# 标准化处理
"val":transforms.Compose([transforms.Resize((224,224)),
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
}
cwd=os.getcwd()#C:\Users\86131\PycharmProjects\pythonProject\手敲CV经典模型\VGG
dd="./"
data_root=os.path.abspath(os.path.join(cwd,dd))#C:\Users\86131\PycharmProjects\pythonProject\ # get data root path(获取目录)
image_path=os.path.join(data_root,"data_set","flower_data")#花数据集路径
assert os.path.exists(image_path),"{} path does not exist.".format(image_path)
# 通过定义的data_transform这个字典,传入"train"这个key,他就会返回训练集对应的数据预处理(字典还能这么用?)
train_dataset=datasets.ImageFolder(root=os.path.join(image_path,"train"),#通过atasets.ImageFolder加载数据集
transform=data_transform['train']# transform训练数据集预处理,
)
"""
代码中root=train_dir,是一个你所放数据集的绝对路径.
train = datasets.ImageFolder(‘train’),它生成了一个对象
1)类别 列表形式
2)种类对应数字标签 字典形式
3)每一个图像及其对应的标签 列表形式
"""
train_num=len(train_dataset)
flower_list=train_dataset.class_to_idx#通过此获取分类类别的名称和其所对应的索引 # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
cla_dict=dict((val,key) for key,val in flower_list.items())
json_str=json.dumps(cla_dict,indent=4)
with open("class_indices.json","w") as json_file:
json_file.write(json_str)
batch_size=32#train_num=3306 batch_size=32 epochs=30 iteration=4
epochs=30
www=os.cpu_count()
nw=min([os.cpu_count() ,batch_size if batch_size >1 else 0,8])#就是加载数据时快一些
"""num_workers是Dataloader的概念,默认值是0。是告诉DataLoader实例要使用多少个子进程进行数据加载(和CPU有关,和GPU无关)
如果num_worker设为0,意味着每一轮迭代时,dataloader不再有自主加载数据到RAM这一步骤(因为没有worker了),而是在RAM中找batch,找不到时再加载相应的batch。缺点当然是速度慢。
当num_worker不为0时,每轮到dataloader加载数据时,dataloader一次性创建num_worker个worker,并用batch_sampler将指定batch分配给指定worker,worker将它负责的batch加载进RAM。
num_worker设置得大,好处是寻batch速度快,因为下一轮迭代的batch很可能在上一轮/上上一轮…迭代时已经加载好了。坏处是内存开销大,也加重了CPU负担(worker加载数据到RAM的进程是CPU复制的嘛)。num_workers的经验设置值是自己电脑/服务器的CPU核心数,如果CPU很强、RAM也很充足,就可以设置得更大些。
num_worker小了的情况,主进程采集完最后一个worker的batch。此时需要回去采集第一个worker产生的第二个batch。如果该worker此时没有采集完,主线程会卡在这里等。(这种情况出现在,num_works数量少或者batchsize
比较小,显卡很快就计算完了,CPU对GPU供不应求。)
即,num_workers的值和模型训练快慢有关,和训练出的模型的performance无关
Detectron2的num_workers默认是4
————————————————
"""
print("using {} dataloader workers every process".format(nw))
train_loader=torch.utils.data.DataLoader(train_dataset,#通过dataloader加载train_dataset
batch_size=batch_size,shuffle=True,#通过给定的batch_size与随机参数,随机的从一个epoch样本中回去一批一批的数据
num_workers=nw)#windows系统不能设为0值
validate_dataset=datasets.ImageFolder(root=os.path.join(image_path,"val"),
transforms=data_transform["val"]#transforms测试数据集预处理
)
val_num=len(validate_dataset)
validate_loader=torch.utils.data.DataLoader(validate_dataset,
batch_size=batch_size,
shuffle=False,
num_workers=nw
)
print("using {} image for training,{} images for validation.".format(train_num,val_num))
model_name='vgg13'
net=vgg(model_name=model_name,num_class=5,init_weights=True)#传入类别5,初始化权重设为True
net.to(device)#将网络指认到指定刚刚设置的设备上(cpu或者Gpu)
loss_function=nn.CrossEntropyLoss()#定义损失函数,使用的是多类别交叉损失函数
pata=list(net.paramters())#查看模型参数
optimizer=optim.Adam(net.parameters(),lr=0.0001)#优化器时给负反馈做后勤工作 定义的优化器,优化对象是所有的可训练参数(net.parameters()),
#学习率lr=0.0002(已经经过调试,变大变小都会影响准确率
"""优化器的作用:----->使用损失函数时,调用损失函数的backward得到反向传播,反向传播求出需要调节参数对应的梯度,梯度可以使用优化器,
对梯度的参数进行调整。达到整体降低误差的目的。
用来更新和计算影响模型训练和模型输出的网络参数,使其逼近或达到最优值,从而最小化(或最大化)损失函数。
常见的优化器:
梯度下降法GD(Gradient Descent)
批量梯度下降法 BGD(Batch Gradient Descent)
随机梯度下降法SGD(Stochastic Gradient Descent)
小批量梯度下降法 MBGD(Mini-Batch Gradient Descent)
"""
"""running_loss += loss.item() 由 loss_function(logits, labels.to(device))
返回的loss类型为张量,所以需要item()取到其数值。前面已经讲过loss_function(logits, labels.to(device))
返回的是每一个mini batch的平均损失,running_loss += loss.item() 就是将每个mini batch数量的样本的平均损失累加起来
,方便后面计算每一个样本的平均损失。可能有点拗口,但看到后面就明白了。
"""
"""rate = (step+1)/len(train_loader) 这行代码是在计算当前epoch完成了百分之多少。DataLoader按batch来投喂数据,
那么假设训练集有1000张图片,batch_size=32,所以train_loader需要投喂1000/32=32次,也就是len(train_loader) 的值。step是
通过enumerate获得的,它表示当前是第step个batch。因为enumerate的start=0,所以需要step+1。假如当前是第10个batch,
那么当前epoch进行了百分之rate = 10/32。"""
best_acc=0.0#定义最佳准确率,保存最高准确率
save_path='./{}.pth'.format(model_name)#保存权重路径
train_steps=len(train_loader)#104 train_num(3306)/batch_size(32) 就是iteration
for epoch in range(epochs):#开始训练
net.train()#通过net.train()与net.eval()方法来管理Drouout()方法,也可以管理BN层
#使用net.train()开启Dropout()方法,使用net.eval()关闭Dropout()方法
running_loss=0.0#统计训练过程中的实际损失
train_bar=tqdm(train_loader,file=sys.stdout)#train_loader是迭代器 sys.stdout的形式就是print的一种默认输出格式 以上两个输出是相同的 print 默认换行
#tqdm是 Python 进度条库,可以在 Python长循环中添加一个进度提示信息。用户只需要封装任意的迭代器,是一个快速、扩展性强的进度条工具库。
for step,data in enumerate(train_bar):#遍历数据集
images,labels=data#将数据分为图片和标签
optimizer.zero_grad()#清楚历史梯度信息 这里的梯度信息不要和参数搅浑 w-=lr*梯度 清楚的只是梯度,下一轮算出新的梯度,w继续更新
outputs=net(images.to(device))#将图片数据集
loss=loss_function(outputs,labels.to(device))#通过loss_function计算预测值和真实值之间的损失,这里同样将labels指认到设备中
loss.backward()#将损失反向传播到每一个节点中
optimizer.step()#通过optimizer更新每一个节点的参数值
running_loss+=loss.item()#将得到的损失值累加到running_loss上,后期会求平均
train_bar.desc="train epoch[{}/{}] loss:{:.3f}".format(epoch+1,epochs,loss)#打印训练进度 desc('str'): 传入进度条的前缀
# train epoch[1/30] loss:1.610
# 1%| | 1/104 [00:19<33:29, 19.51s/it]
#################validation
net.veal()
acc=0.0
"""with torch.no_grad() python的上下文管理器with就不多介绍了,
这句代码意思简单粗暴一句话就是:with wrap下的所有代码都不更新梯度 """
with torch.no_grad():
val_bar=tqdm(validate_loader,file=sys.stdout)
for val_data in val_bar:
val_images,val_labels=val_data
outputs=net(val_images.to(device))
predict_y=torch.max(outputs,dim=1)[1]
"""predict_y = torch.max(outputs, dim=1)[1] 这代码的意思是获得这个batch中网络的预测标签 。
torch.max(outputs, dim=1)返回两个值,分别是最大值和其对应的索引,dim=1时按行返回最大索引,
dim=0时按列返回最大索引。outputs是网络的输出,是一个尺寸为的张量。 1行--》dim=1"""
acc+=torch.eq(predict_y,val_labels.to(device)).sum().item()
"""torch.eq()是对两个张量进行逐元素的比较,若相同位置的两个元素相同,则返回True;若不同,返回False。例如网络的预测值为predict_y=[2,0,0],实际的标签值为y=[2,1,0],那么torch.eq(predict_y, y)会返回[True, False, True],注意,在python中式可以对True和False进行数学运算的,True等价于1,False等价于0。那么通过.sum()对其求和,就是预测对的值,此时还是一个张量,所以需要.item()取出对应的值。注意此时的acc是当前batch的预测正确的个数,所以需要+=来得到所有batch的预测正确的个数。
val_accurate = acc / val_num 此时的acc是验证集上的所有预测正确的个数,val_num是验证集的样本个数,val_cacurate是验证集上的准确率。
"""
val_accurate=acc/val_num
# 100%|██████████| 1/1 [00:07<00:00, 7.73s/it]验证集进度条
print("[epoch %d] train_loss:%.3f val_accuracy:%.3f"%
(epoch+1,running_loss/train_steps,val_accurate))#[epoch 1] train_loss: 1.615 val_accuracy: 0.270 验证就直接验证就完了,没有轮数一说
if val_accurate >best_acc:
best_acc=val_accurate
torch.save(net.state_dict(),save_path)#保存当前权重
print("Finished Training")
dd=4
if __name__ == '__main__':
main()