• 手把手教你用pytorch实现k折交叉验证,解决类别不平衡


    在用深度学习做分类的时候,常常需要进行交叉验证,目前pytorch没有通用的一套代码来实现这个功能。可以借助 sklearn中的 StratifiedKFold,KFold来实现,其中StratifiedKFold可以根据类别的样本量,进行数据划分。以5折为例,它可以实现每个类别的样本都是4:1划分。

    代码简单的示例如下:

    1. from sklearn.model_selection import StratifiedKFold
    2. skf = StratifiedKFold(n_splits=5)
    3. for i, (train_idx, val_idx) in enumerate(skf.split(imgs, labels)):
    4. trainset, valset = np.array(imgs)[[train_idx]],np.array(imgs)[[val_idx]]
    5. traintag, valtag = np.array(labels)[[train_idx]],np.array(labels)[[val_idx]]

    以上示例是将所有imgs列表与对应的labels列表进行split,得到train_idx代表训练集的下标,val_idx代表验证集的下标。后续代码只需要将split完成的trainset与valset输入dataset即可。

    接下来用我自己数据集的实例来完整地实现整个过程,即从读取数据,到开始训练。如果你的数据集存储方式和我不同,改一下数据读取代码即可。关键是如何获取到imgs和对应的labels。

    我的数据存储方式是这样的(类别为文件夹名,属于该类别的图像在该文件夹下):

    """A generic data loader where the images are arranged in this way: ::
    
        root/dog/xxx.png
        root/dog/xxy.png
        root/dog/xxz.png
    
        root/cat/123.png
        root/cat/nsdf3.png
        root/cat/asd932_.png

     以下代码是获取imgs与labels的过程:

    1. import os
    2. import numpy as np
    3. IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png')
    4. def is_image_file(filename):
    5. return filename.lower().endswith(IMG_EXTENSIONS)
    6. def find_classes(dir):
    7. classes = [d.name for d in os.scandir(dir) if d.is_dir()]
    8. classes.sort()
    9. class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}
    10. return classes, class_to_idx
    11. if __name__ == "__main__":
    12. dir = 'your root path'
    13. classes, class_to_idx = find_classes(dir)
    14. imgs = []
    15. labels = []
    16. for target_class in sorted(class_to_idx.keys()):
    17. class_index = class_to_idx[target_class]
    18. target_dir = os.path.join(dir, target_class)
    19. if not os.path.isdir(target_dir):
    20. continue
    21. for root, _, fnames in sorted(os.walk(target_dir, followlinks=True)):
    22. for fname in sorted(fnames):
    23. path = os.path.join(root, fname)
    24. if is_image_file(path):
    25. imgs.append(path)
    26. labels.append(class_index)

    上述代码只需要把dir改为自己的root路径即可。接下来对所有数据进行5折split。其中我自己写了MyDataset类,可以直接照搬用。

    1. from sklearn.model_selection import StratifiedKFold
    2. skf = StratifiedKFold(n_splits=5) #5折
    3. for i, (train_idx, val_idx) in enumerate(skf.split(imgs, labels)):
    4. trainset, valset = np.array(imgs)[[train_idx]],np.array(imgs)[[val_idx]]
    5. traintag, valtag = np.array(labels)[[train_idx]],np.array(labels)[[val_idx]]
    6. train_dataset = MyDataset(trainset, traintag, data_transforms['train'] )
    7. val_dataset = MyDataset(valset, valtag, data_transforms['val'])
    1. from PIL import Image
    2. import torch
    3. from torch.utils.data import Dataset, DataLoader
    4. class MyDataset(Dataset):
    5. def __init__(self, imgs, labels, transform=None,target_transform=None):
    6. self.imgs = imgs
    7. self.labels = labels
    8. self.transform = transform
    9. self.target_transform = target_transform
    10. def __len__(self):
    11. return len(self.imgs)
    12. def __getitem__(self, idx):
    13. if torch.is_tensor(idx):
    14. idx = idx.tolist()
    15. path = self.imgs[idx]
    16. target = self.labels[idx]
    17. with open(path, 'rb') as f:
    18. img = Image.open(f)
    19. img = img.convert('RGB')
    20. if self.transform:
    21. img = self.transform(img)
    22. if self.target_transform is not None:
    23. target = self.target_transform(target)
    24. return img, target

    有了数据集之后,就可以创建dataloader了,后面就是正常的训练代码:

    1. from sklearn.model_selection import StratifiedKFold
    2. skf = StratifiedKFold(n_splits=5) #5折
    3. for i, (train_idx, val_idx) in enumerate(skf.split(imgs, labels)):
    4. trainset, valset = np.array(imgs)[[train_idx]],np.array(imgs)[[val_idx]]
    5. traintag, valtag = np.array(labels)[[train_idx]],np.array(labels)[[val_idx]]
    6. train_dataset = MyDataset(trainset, traintag, data_transforms['train'] )
    7. val_dataset = MyDataset(valset, valtag, data_transforms['val'])
    8. train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=args.batch_size,
    9. shuffle=True, num_workers=args.workers)
    10. test_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=args.batch_size,
    11. shuffle=True, num_workers=args.workers)
    12. # define model
    13. model = resnet18().cuda()
    14. # define criterion
    15. criterion = torch.nn.CrossEntropyLoss()
    16. # Observe that all parameters are being optimized.
    17. optimizer = optim.SGD(model.parameters(),
    18. lr=args.lr,
    19. momentum=args.momentum,
    20. weight_decay=args.weight_decay)
    21. for epoch in range(args.epoch):
    22. train_acc, train_loss = train(train_dataloader, model, criterion, args)
    23. test_acc, tect_acc_top5, test_loss = validate(test_dataloader, model, criterion, args)

    为了保证每次跑的时候分的数据都是一致的,注意shuffle=False(默认)

    StratifiedKFold(n_splits=5,shuffle=False)

    以上就是实现的基本代码,之所以在代码层面实现k折而不是在数据层面做,比如预先把数据等分为5份。是因为这个代码可以支持数据样本的随意增减,不需要人为地再去分数据,十分方便。 

  • 相关阅读:
    多角度解读新兴公链Sui:团队、架构、代币、生态等
    牛客训练3
    【Bug记录与解决】Python报错 AttributeError: ‘DataFrame‘ object has no attribute ‘concat‘ 该怎么解决(解决方案汇总)
    python之shutil模块
    Cadence OrCAD Capture ERC检查ERC Matrix详细说明
    【20年扬大真题】删除字符串s中的所有空格
    java计算机毕业设计基于ssm的邮票收藏鉴赏系统
    tomcat官网下载配置全部详细步骤(包含各种报错解决办法)
    使用 Fluent Bit 实现云边统一可观测性
    java MINio 操作工具类
  • 原文地址:https://blog.csdn.net/u013685264/article/details/126488633