首发于小哲AI

pytorch训练自己图像分类数据集

2020年6月更新,调整代码的组成结构,使其更加通用,方便使用,更加整洁,添加模型蒸馏的方法。

希望对初学者能有些帮助,欢迎star


2020年4月26更新:cnn svm knn pytorch

最近实现了利用cnn提取特征,然后利用svm或者knn,随机森林等分类器实现分类,在一些分类任务中效果会更好,代码已经在github仓库中更新。

代码实现主要功能是,cnn训练完成后,去掉全连接层,然后将提取到的训练集的特征保存为pkl文件,然后利用这些特征训练svm或者knn等分类器,保存分类器,并读取预测


2020年4月更新

之前利用pytorch写的代码,是自己刚开始使用pytorch学习时,自己练手写的,最近慢慢完善了一个pytorch实现图像分类的代码。

实现主要功能:

  • 基础功能利用pytorch实现图像分类
  • 包含带有warmup的cosine学习率优化
  • 多模型融合预测,加权与投票融合
  • 利用flask实现模型云端api部署

查看完整代码,请移步github,如果有用的话,不要忘记star



近期无意间看到华为云的垃圾分类大赛,看了官方的baseline代码,发现是使用keras写的,自己没有用过keras,看不懂代码,因而就用pytorch写了代码,利用densenet模型进行训练,完整的走了一遍这种图像分类的过程,从数据集的格式到模型的训练、测试与单张图片的预测,到最后部署在华为云的modelarts上,整个图像分类工作的流程

下载完整代码,请移步github,如果有用的话,不要忘了‘star’

这里记录一下自己的工作,同时也给刚入门深度学习、刚开始学习pytorch的同学一个参考,给大家一个相对简单的实现过程,简单的代码实现,其中也介绍了很多注意的要点

例如:1、采用ImageFolder与dataloader制作提取数据集的提取器;

2、pytorch 0.4之后的版本中Tensor与Variable合并,Variable已经不再使用,Volatile = True已经不再使用,由torch.no_grad()代替

3、由于在分类任务中,一般需要读取预训练权重进行预处理,加快网络的收敛。但是,我们一般的分类的类别数目不尽相同,因此许啊哟读入前边几层,最后一层的参数利用初始化函数进行初始化,这里介绍了读取部分预训练模型参数的方法,可以很好的理解pytorch保存的模型为字典格式

4、介绍多gpu并行训练的方式

5、介绍多gpu训练的模型保存参数与单gpu训练保存的模型参数的不同点,以及加载多gpu模型时需要对模型的键进行处理,去掉‘module’

6、在进行预测评估时,使用eval()函数

这些在这篇文章中都能找到答案,相信读完这篇文章你也可以写一个多gpu并行运算的图像分类算法

1、准备自己的数据集

对于图像分类任务,将每一类的照片放置在同一个文件夹下,然后将所有包含各种图像的文件夹放置在同一个文件夹下,例如:

我们有10类图像,分别放在10个文件夹下,命名为1,2,3,4,5,6,7,8,9,10。然后将这十个文件夹放置在同一个文件夹下,这个文件夹命名为train_data,

得到数据集之后,利用github中代码文件load_data.py来创建数据提取器,这返回一个迭代器

利用torchvision.datasets中的ImageFolder类直接进行封装,这个类直接将每一个文件夹下的图片与类别对应,返回结果为迭代器。 然后将这个迭代器传入dataloader,按每个batch提取数据

主要代码段理解:

##
train_transforms = transforms.Compose([    transforms.RandomRotation(10),    transforms.RandomResizedCrop(224),    transforms.RandomHorizontalFlip(),    transforms.ToTensor(),    transforms.Normalize((.5, .5, .5), (.5, .5, .5))])
train_dir = cfg.TRAIN_DATASET_DIR
train_datasets = datasets.ImageFolder(train_dir, transform=train_transforms)
train_dataloader = torch.utils.data.DataLoader(train_datasets, batch_size=batch_size, shuffle=True, num_workers=2)

2、densenet模型文件

参考torchvision中的models

3、训练代码

github代码中的train.py

##读入下载的预训练的权重字典
state_dict = torch.load(pretrained_path)

###去掉全链接层的权重,
#由于我们一般不会直接使用,imagenet的1000类,因此,我们需要更换网络最后的全链接层
#因此我们需要将前边几层的参数保存,最后一层重新初始化
#定义一个新的字典,将原始的参数字典,对应保存与更改
from collections import OrderedDict
new_state_dict = OrderedDict()

for k,v in state_dict.items():
    # print(k)  #打印预训练模型的键,发现与网络定义的键有一定的差别,因而需要将键值进行对应的更改,将键值分别对应打印出来就可以看出不同,根据不同进行修改
    #torchvision中的网络定义,采用了正则表达式,来更改键值,因为这里简单,没有再去构建正则表达式
    # 直接利用if语句筛选不一致的键
    ###修正键值的不对应
    if k.split('.')[0] == 'features' and (len(k.split('.')))>4:
        k = k.split('.')[0]+'.'+k.split('.')[1]+'.'+k.split('.')[2]+'.'+k.split('.')[-3] + k.split('.')[-2] +'.'+k.split('.')[-1]
    # print(k)
    else:
        pass
    ##最后一层的全连接层,进行初始化
    if k.split('.')[0] == 'classifier':
        if k.split('.')[-1] == 'weights':
            v = nn.init.kaiming_normal(model.state_dict()[k], mode='fan_out')
        else:
            model.state_dict()[k][...] = 0.0
            v = model.state_dict()[k][...]
    else:
        pass
    ##得到新的与定义网络对应的预训练参数
    new_state_dict[k] = v
##导入网络参数
model.load_state_dict(new_state_dict)

##进行多gpu的并行计算
if args.ngpu:
    model = nn.DataParallel(model,device_ids=list(range(args.ngpu)))
print("initialize the network done")

##定义优化器与损失函数optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)
loss_func = nn.CrossEntropyLoss()

训练过程

##训练max——epoch个epoch
for epoch in range(args.max_epoch):
    model.train()  ##在进行训练时加上train(),测试时加上eval()
    ##在测试时加上eval()会将BN与Dropout的进行固定
    batch = 0

    for batch_images, batch_labels in train_dataloader:
        # print(batch_labels)
        # print(torch.cuda.is_available())
        average_loss = 0
        train_acc = 0

        ##在pytorch0.4之后将Variable 与tensor进行合并,所以这里不需要进行Variable封装
        if torch.cuda.is_available():
            batch_images, batch_labels = batch_images.cuda(),batch_labels.cuda()
        out = model(batch_images)
        loss = loss_func(out,batch_labels)

    #    print(loss)
        average_loss = loss
        prediction = torch.max(out,1)[1]
        # print(prediction)

        train_correct = (prediction == batch_labels).sum()
        ##这里得到的train_correct是一个longtensor型,需要转换为float
        # print(train_correct.type())
        train_acc = (train_correct.float()) / args.batch_size
 #       print(train_acc.type())

        optimizer.zero_grad() #清空梯度信息,否则在每次进行反向传播时都会累加
        loss.backward()  #loss反向传播
        optimizer.step()  ##梯度更新

        batch+=1
        print("Epoch: %d/%d || batch:%d/%d average_loss: %.3f || train_acc: %.2f"
              %(epoch, args.max_epoch, batch, max_batch, average_loss, train_acc))

##每10epoch保存一次模型
    if epoch%10 ==0 and epoch>0:
        torch.save(model.state_dict(), args.save_folder+'/'+'dense169'+'_'+str(epoch)+'.pth')

4、测试网络模型效果

github代码中eval.py

###读取网络模型的键值对
trained_model = cfg.TRAINED_MODEL
state_dict = torch.load(trained_model)


# create new OrderedDict that does not contain `module.`
##由于之前的模型是在多gpu上训练的,因而保存的模型参数,键前边有‘module’,需要去掉,和训练模型一样构建新的字典
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
    head = k[:7]
    if head == 'module.':
        name = k[7:] # remove `module.`
    else:
        name = k
    new_state_dict[name] = v
model.load_state_dict(new_state_dict)

print('Finished loading model!')



##进行模型测试时,eval()会固定下BN与Dropout的参数
model.eval()
eval_acc = 0.0

for batch_images, batch_labels in val_dataloader:
    # print(batch_labels)
    with torch.no_grad():
        if torch.cuda.is_available():
            batch_images, batch_labels = batch_images.cuda(), batch_labels.cuda()
##在pytorch0.4的版本之前,使用Variable封装,里边采用volatile=True放置进行反传训练
#在0.4之后,官方推荐torch.no_grad(),Variable API已经被弃用

    out = model(batch_images)


    prediction = torch.max(out, 1)[1]
    num_correct = (prediction == batch_labels).sum()
    eval_acc += num_correct
    print(eval_acc)
print(' Accuracy of batch : {:.6f}'.format((eval_acc.float()) / (len(val_datasets))))

5、单张图片结果预测

github代码中的predict.py

##labels_to_classes为与输出结果对应的字典,例如:{'0','flower'}
labels2classes = cfg.labels_to_classes


###读取网络模型的键值对
trained_model = cfg.TRAINED_MODEL
state_dict = torch.load(trained_model)


# create new OrderedDict that does not contain `module.`
##由于之前的模型是在多gpu上训练的,因而保存的模型参数,键前边有‘module’,需要去掉,和训练模型一样构建新的字典
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
    head = k[:7]
    if head == 'module.':
        name = k[7:] # remove `module.`
    else:
        name = k
    new_state_dict[name] = v
model.load_state_dict(new_state_dict)

print('Finished loading model!')



##进行模型测试时,eval()会固定下BN与Dropout的参数
model.eval()


with torch.no_grad():
    if torch.cuda.is_available():
        batch_images, batch_labels = batch_images.cuda(), batch_labels.cuda()
##在pytorch0.4的版本之前,使用Variable封装,里边采用volatile=True放置进行反传训练
#在0.4之后,官方推荐torch.no_grad(),Variable PI已经被弃用

out = model(batch_images)


prediction = torch.max(out, 1)[1]
print(labels2classes[str(prediction)]

6、华为云modelarts部署

deployment中的代码主要参考这个,主要应用于将模型部署到华为云modelarts上,这里就先不叙述了

编辑于 2020-06-28 16:55