27.数据集实战


数据集实战:收集、读取、预处理数据集,并且完成模型的搭建、训练工作。

Pokemon数据集

在这里插入图片描述
在这里插入图片描述

▪ 链接: https://pan.baidu.com/s/1V_ZJ7ufjUUFZwD2NHSNMFw
▪ 提取码:dsxl

总体的百分之60作train,百分之20作validation,百分之20作test。

Load data

inherit form torch.utils,data.Dataset,Dataset提供了一些通用的功能。
我们需要自己实现两个函数:_len_是数据集样本的数量,返回一个整型数字,接口_getitem_返回一个指定的x样本。
两大步骤,第一个我们需要实现怎么读取具体的样本,第二个是获取样本的数量。

Custom Dataset

class NumberDataset(Dataset):
    def __init__(self,training=True):
        if training:
            self.samples=list(range(1,1001))
        else:
            self.samples=list(range(1001,1501))

    def _len_(self):
        return len(self.samples)

    def _getitem_(self,idx):
        return self.samples[idx]

首先要继承Dataset这个基本母类,然后完成两个函数_len_和_getitem_。
在初始化中,先把数据存储起来,创建一个1到1001的list,并且把它保存到类的成员变量self.samples上。
train时,就读取1到1001,test时读取1001到1501。

预处理preprocessing

网络的输入大小往往是固定的

  • Image Resize

224×224 for ResNet18
当图片大小不符合要求时,需要Resize到固定的尺寸。

  • Data Argumentation

Rotae
Crop

  • Normalize

mean,std
把数据规范到0的附近,更有利于train

  • ToTensor

转换成tensor类型。

root:首先我们需要找到数据集的位置。
resize:不同是网络输入的图片大小不一样,resize参数是针对所有网络而设置的。
mode:我们需要作train,validation和test,需要三种结构。
然后对数据集的每一个类进行一个映射,我们需要人为的把label编码好。
首先我们需要创建一个空字典,根据类名进行映射,例如把妙蛙种子映射为0。当我们遍历目录得到五种文件名,进行随机编码,先进来的是0,1,编码后固定。
首先遍历root目录下的文件,把目录名字过滤掉。
listdir()返回的顺序是不固定的,所以我们把它排序,排序后顺序固定。
name2label字典里有几个元素数,当前键的值就是几。
在这里插入图片描述
我们希望拿到文件后,数据对:image path+image label,因为一次性加载所有图片可能会爆内存。
遍历精灵类别后,使用glob.glob同时获取所有的匹配路径。并把当前文件内所有png、jpg、jpeg和gif格式的图片合并到images中。
在这里插入图片描述

我们的label信息根据路径得到,把所有信息保存在images.csv,里面是所有图片的地址和标签。
在这里插入图片描述

如果不存在,则先创建并保存在csv,接下来再进行读取。我们进行读取时,把每张图片的路径和label分别保存在images和labels中。
接下载进行类型的分类。

__getitem__函数:
我们需要使用transform和PIL方法根据路径把图片加载进来。
在transform中,我们先使用Image.open方法把图片加载,再convert成一个可以处理的类型RGB。

我们完成了三个大步骤,首先完成初始化工作,将图片信息进行储存,先人为的对标签进行编码也就是排序,再通过load_csv函数生成images和labels的信息。再根据mode把images和labels进行裁剪。然后完成len函数获得长度。最后完成getitem函数,通过索引得到图片地址信息和标签信息,然后再通过tramsform方法加载图片,并把图片和标签转换成可处理的tensor类型,返回。

在这里插入图片描述
先在终端查看是否已经安装visdom,然后 python -m visdom.server运行。
在这里插入图片描述
在这里插入图片描述
接下来进行可视化

viz.images(x,win='sample_x',opts=dict(title='sample_x'))

在这里插入图片描述
在这里插入图片描述

得到一张大小是224,标签为1的图片。
我们还需要完成一些预处理工作,把resize的尺寸变大.

transforms.Resize((int(self.resize*1.25),int(self.resize*1.25)),interpolation=InterpolationMode.BICUBIC),

再做随机旋转,注意旋转角度过大时可能会造成网络不收敛的情况。

transforms.RandomRotation(15),

旋转后图片空白处会自动添黑,这是我们不希望有的,所以需要进行裁剪。

 transforms.CenterCrop(self.resize),

最后还需要做normalize,把数据统一到0和1附近,范围变为-1到1。

transforms.Normalize(mean=mean,std=std)

visdom接受的范围是0到1,但是normalize后,数据范围是-1到1。因此我们需要denormalize。
在这里插入图片描述
在denormalize中,x_hat是我们normalize后的,normalize的原理是x_hot=(x-mean)/std,当我们需要原来的x时,只需要x=x_hot*std+mean。我们的mean和std是长度为3的标量,我们需要把它变成3维的,因此先成为tensor类型,在进行unsqueeze。

    def denormalize(self,x_hat):
        mean = [0.485, 0.456, 0.406],
        std = [0.229, 0.224, 0.225]

        #x_hot=(x-mean)/std
        #x=x_hot*std+mean
        #x:[c,h,w]
        #mean:[3]=>[3,1,1]
        mean=torch.tensor(mean).unsqueeze(1).unsqueeze(1)
        std=torch.tensor(std).unsqueeze(1).unsqueeze(1)

        x=x_hat*std+mean

        return x

我们需要加载大量图片,采用Dataloader进行实现。

在这里插入图片描述
在这里插入图片描述
这是一个batch的图片,每10s更新一个batch。
如果你的文件夹名字对应的所有图片都在该文件夹内,这种类型的数据,可以使用Dafaloader中ImageFolder,一行可以解决。但是这种不是通用的,仅仅适应于该种类型。

    import torchvision

    viz=visdom.Visdom()

    tf = transforms.Compose([
        transforms.Resize((64,64)),
        transforms.ToTensor(),
    ])
    db=torchvision.datasets.ImageFolder(root='pokemon',transform=tf)
    loader=DataLoader(db,batch_size=32,shuffle=True)

在这里插入图片描述

Build model

23的Resnet18

Train and Test

对于train的每个epoch,train后,进行validation,保存下最好的acc和epoch,再使用这个最好状态下的信息进行test。一般不需要对val和test数据集进行shuffle,需要全部进行测试。然后把模型放在cuda上,优化器一般优先选择Adam。计算loss采用CrossEntropyLoss,这个接收的参数就是logits,是不需要经过softmax的。

optimizer=optim.Adam(model.parameters(),lr=lr)
    criteon=nn.CrossEntropyLoss()

在evaluate函数中,不需要做反向传播,所以放在torch.no_grad下。correct的值是通过前向传播计算出来的预测值pred,然后将pred和真实值y进行比较。

def evaluate(model,loader):

    for x,y in loader:
        correct=0
        total=len(loader.dataset)
        x,y=x.to(device),y.to(device)
        with torch.no_grad():
            logits=model(x)
            pred=logits.argmax(dim=1)

            correct=torch.eq(pred,y).sum().float().item()
    return correct/total

对于最好的状态,除了保存best_acc,best_epoch,还要进行torch.save(model.state_dict(),‘best.mdl’),把当前信息存储在best.mdl,后缀名随意。

Transfer Learning

在A上train好一个分类器,再transfer到B上。对于B来说,其所含有的样本可能最好训练的结果也不及使用上述分类器刚开始训练结果,效果类似于站在巨人肩膀上vs从0开始。
在这里插入图片描述
左面是已经train好的,把最后一层去掉,new classifier是随机初始化的,我们可以train这部分或者全部,效果会远远好于之前。
我们使用torchvision中带的resnet18,可以得到训练好的模型,体现在参数是非常好的状态。然后我们需要获取这些参数,假设只有18层,我们把前17层取出来,最后一层是我们需要重新学习的知识。Transfer Learning体现在这两行代码中:

 trained_model=resnet18(pretrained=True)
    model=nn.Sequential(*list(trained_model.children())[:-1],#输出为[b,512,1,1]
                        Flatten(),#[b,512,1,1]=>[b,512]
                        nn.Linear(512,5)
                         )

完整代码

load data

import torch
import os,glob
import random,csv

from torch.utils.data import Dataset,DataLoader

from torchvision import transforms
from PIL import Image
from torchvision.transforms import InterpolationMode


class Pokemon(Dataset):
    def __init__(self,root,resize,mode):
        super(Pokemon, self).__init__()
        self.root=root
        self.resize=resize

        self.name2label={}  #"sq..." :0
        for name in sorted(os.listdir(os.path.join(root))):
            if not os.path.join(os.path.join(root,name)):
                continue

            self.name2label[name]=len(self.name2label.keys())
        print(self.name2label)
        self.images,self.labels=self.load_csv('images.csv')

        if mode=='train':   #60%
            self.images=self.images[:int(0.6*len(self.images))]
            self.labels=self.labels[:int(0.6*len(self.labels))]
        elif mode=='val':   #60%=>80%
            self.images = self.images[int(0.6 * len(self.images)):int(0.8 * len(self.images))]
            self.labels = self.labels[int(0.6 * len(self.labels)):int(0.8 * len(self.labels))]
        else:               #80%=>100%
            self.images = self.images[int(0.8 * len(self.images)):]
            self.labels = self.labels[int(0.8 * len(self.labels)):]

    def load_csv(self,filename):

        if not os.path.exists(os.path.join(self.root,filename)):
            images=[]
            for name in self.name2label.keys():
                # 'pokemon\\mewto\\00001.png'
                images+=glob.glob(os.path.join(self.root,name,'*.png'))
                images+=glob.glob(os.path.join(self.root,name,'*jpg'))
                images += glob.glob(os.path.join(self.root, name, '*jpeg'))
                images += glob.glob(os.path.join(self.root, name, '*gif'))

            print(len(images),images)

            random.shuffle(images)
            with open(os.path.join(self.root,filename),mode='w',newline='') as f:
                writer=csv.writer(f)
                for img in images:# 'pokemon\\mewto\\00001.png'
                    name=img.split(os.sep)[-2]
                    label=self.name2label[name]
                    # 'pokemon\\mewto\\00001.png',2
                    writer.writerow([img,label])
                print('writen into csv file:',filename)

        images,labels=[],[]
        with open(os.path.join(self.root,filename)) as f:
            reader=csv.reader(f)
            for row in reader:
                img,label=row
                label=int(label)

                images.append(img)
                labels.append(label)

        assert len(images)==len(labels)

        return images,labels



    def __len__(self):
        return len(self.images) #此处不是1168,因为已经裁剪过

    def denormalize(self,x_hat):
        mean = [0.485, 0.456, 0.406]
        std = [0.229, 0.224, 0.225]

        #x_hot=(x-mean)/std
        #x=x_hot*std+mean
        #x:[c,h,w]
        #mean:[3]=>[3,1,1]
        mean=torch.tensor(mean).unsqueeze(1).unsqueeze(1)
        std=torch.tensor(std).unsqueeze(1).unsqueeze(1)

        x=x_hat*std+mean

        return x



    def __getitem__(self, idx):
        #idx:[0~len(self.images)]
        #self.images,self.labels
        #img:'pokemon\\mewto\\00001.png'
        #label:0/1/2/3/4
        img,label=self.images[idx],self.labels[idx]

        tf=transforms.Compose([
            lambda x:Image.open(x).convert('RGB'), #string path=>image data
            transforms.Resize((int(self.resize*1.25),int(self.resize*1.25)),interpolation=InterpolationMode.BICUBIC),
            transforms.RandomRotation(15),
            transforms.CenterCrop(self.resize),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],std = [0.229, 0.224, 0.225])
        ])

        img=tf(img)
        label=torch.tensor(label)

        return img,label

def main():


    import visdom
    import time
    import torchvision

    viz=visdom.Visdom()

    # tf = transforms.Compose([
    #     transforms.Resize((64,64)),
    #     transforms.ToTensor(),
    # ])
    # db=torchvision.datasets.ImageFolder(root='pokemon',transform=tf)
    # loader=DataLoader(db,batch_size=32,shuffle=True)
    #
    # print(db.class_to_idx)
    #
    # for x,y in loader:
    #
    #     viz.images(x,nrow=8,win='batch',opts=dict(title='batch'))
    #     viz.text(str(y.numpy()),win='label',opts=dict(title='batch-y'))
    #
    #     time.sleep(10)

    db=Pokemon('pokemon',224,'train')
    x,y=next(iter(db))
    print('sample:',x.shape,y.shape,y)
    viz.images(db.denormalize(x),win='sample_x',opts=dict(title='sample_x'))
    loader=DataLoader(db,batch_size=32,shuffle=True,num_workers=8)

    for x,y in loader:     #x,y是一个batch

        viz.images(db.denormalize(x),nrow=8,win='batch',opts=dict(title='batch'))
        viz.text(str(y.numpy()),win='label',opts=dict(title='batch-y'))

        time.sleep(10)   #每次加载完休息10s


if __name__ == '__main__':
    main()

train and test

import torch
from torch import nn,optim
import torchvision,visdom
from torch.utils.data import DataLoader

from pokemon import Pokemon
from resnet import ResNet18

batchsz=32
lr=1e-3
epochs=10

device=torch.device('cuda')
torch.manual_seed(1234)

train_db=Pokemon('pokemon',224,mode='train')
val_db=Pokemon('pokemon',224,mode='val')
test_db=Pokemon('pokemon',224,mode='test')
train_loader=DataLoader(train_db,batch_size=batchsz,shuffle=True,num_workers=4)
val_loader=DataLoader(val_db,batch_size=batchsz,num_workers=2)
test_loader=DataLoader(test_db,batch_size=batchsz,num_workers=2)

viz=visdom.Visdom()

def evaluate(model,loader):

    for x,y in loader:
        correct=0
        total=len(loader.dataset)
        x,y=x.to(device),y.to(device)
        with torch.no_grad():
            logits=model(x)
            pred=logits.argmax(dim=1)

            correct=torch.eq(pred,y).sum().float().item()
    return correct/total


def main():

    model=ResNet18(5).to(device)
    optimizer=optim.Adam(model.parameters(),lr=lr)
    criteon=nn.CrossEntropyLoss()


    best_acc,best_epoch=0,0
    global_step=0
    viz.line([0],[-1],win='loss',opts=dict(title='loss'))
    viz.line([0],[-1],win='val_acc',opts=dict('val_acc'))
    for epoch in epochs:
        for step,(x,y) in enumerate(train_loader):
            #x:[b,3,224,224],y:[b]
            x,y=x.to(device),y.to(device)

            logits=model(x)
            loss=criteon(logits,y)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            viz.line([loss.item()], [global_step], win='loss', update='append')
            global_step+=1

        if epoch%2==0:
            val_acc=evaluate(model,val_loader)
            if val_acc>best_acc:
                best_epoch=epoch
                best_acc=val_acc

                torch.save(model.state_dict(),'best.mdl')
                viz.line([val_acc], [global_step], win='val_acc', update='append')

    print('best acc:',best_acc,'best epoch:',best_epoch)

    model.load_state_dict(torch.load('best.mdl'))
    print('loaded form ckpt!')

    test_acc=evaluate(model,test_loader)
    print('test acc:',test_acc)







if __name__ == '__main__':
    main()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值