Pytorch定义并训练自己的数字数据集

Posted hikseeker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch定义并训练自己的数字数据集相关的知识,希望对你有一定的参考价值。

这一篇主要讲解Pytorch搭建一个卷积神经网络识别自己的数字数据集基本流程。

注:一开始接触很多教程都是直接加载datasets已有的MNIST等,如果想要训练自己的数据就可以采用这个方法。

基本步骤:获取并读取数据-->定义网络模型和损失函数-->使用优化算法训练模型-->利用验证数据集求取网络识别准确度

1、首先是获取并读取数据,其中最关键的就是Datasets这个类即torchvision.datasets,类里自带很多数据集(包括mnist/coco/cifar10等)。就拿mnist手写识别数据集来说,我们所需要提取的信息主要有两个:一个是手写图片的所有像素,另一个就是这张图片的标签(即这个数字的大小是多少)。torchvision.datasets.MNIST()该函数所返回的就是这些信息。

datasets这个类是图像数据集中非常重要的一个类,当我们需要定义自己的数据集进行加载训练的时候,应该要继承datasets这个父类,其中父类中的两个私有成员函数必须被重载:

def __getitem__(self, index) 
def __len__(self)  

len返回的是这个数据集的大小,而getitem主要用来编写支持数据集索引的函数。

getitem()会接收一个index,然后返回图片数据和标签。这个index通常是指一个list的index,这个list的每个元素就包含了图像所在的系统路径和标签信息。

2、那么,如果我们需要定义一个自己的数据集,就需要有一个这样的list列表。方法就是将图片的系统路径和标签信息放在一个txt文件当中,然后再从这个txt中读取信息。所以,定义并读取自己数据的基本流程如下:

(1)制作存储了图片系统路径和标签的txt文件

(2)将这些信息转换成一个list,这个list的每一个元素对应一个样本

(3)通过getitem函数读取数据像素信息和标签

3、比如我本来有这样的10个数字信息,每一个数字取700张作为训练集,200张作为验证集,如图所示

技术图片

 

 

 技术图片

 

 

 4、接下来需要编写一个python脚本对这些图像进行信息提取(提取图片路径和标签)

import os
b = 0
dir = ‘F:/ele/data/‘
#os.listdir的结果就是一个list集合
#可以使用一个list的sort方法进行排序,有数字就用数字排序
files = os.listdir(dir)
files.sort()
#print("files:", files)
train = open(‘F:/ele/data/train.txt‘, ‘a‘)
test = open(‘F:/ele/data/test.txt‘, ‘a‘)
a = 0
a1 = 0
while(b < 20):#20是因为10个train文件夹+10个valid的文件夹
    #这里采用的是判断文件名的方式进行处理
    if ‘train‘ in files[b]:#如果文件名有train
        label = a #设置要标记的标签,比如sample001_train里面都是0的图片,标签就是0
        ss = ‘F:/ele/data/‘ + str(files[b]) + ‘/‘ #700张训练图片
        pics = os.listdir(ss) #得到sample001_train文件夹下的700个文件名
        i = 1
        while i < 701:#一共有700张
            name = str(dir) + str(files[b]) + ‘/‘ + pics[i-1] + ‘ ‘ + str(int(label)) + ‘
‘
            train.write(name)
            i = i + 1
        a = a + 1
    if ‘valid‘ in files[b]:
        label = a1
        ss = ‘F:/ele/data/‘ + str(files[b]) + ‘/‘ #200张验证图片
        pics = os.listdir(ss)
        j = 1
        while j < 201:
            name = str(dir) + str(files[b]) + ‘/‘ + pics[j-1] + ‘ ‘ + str(int(label)) + ‘
‘
            test.write(name)
            j = j + 1
        a1 = a1 + 1
    b = b + 1

5、过上面的python文本处理就可以得到train和test两个txt文件如图所示。

技术图片

6、接下来就要定义数据集的类MyDataset如下所示:

class MyDataset(Dataset):
    #初始化一些需要传入的参数和数据集的调用
    def __init__(self, txt, transform=None,
                 target_transform=None, loader=default_loader):
        super(MyDataset, self).__init__()
        imgs = []
        fh = open(txt, ‘r‘)
        #按照传入的路径和txt文本参数,以只读的形式打开这个文本
        for line in fh:
            #迭代该列表,按行循环txt文本
            line = line.strip(‘
‘)
            line = line.rstrip(‘
‘)
            #删除本行string字符串末尾的指定字符,
            words = line.split()
            #用split的方式将该行分割成列表
            #split的默认参数是空格,所以不传递任何参数时分割空格
            imgs.append((words[0], int(words[1])))
            #把txt的内容读入imgs列表保存
            #word[0]是图片信息,words[1]是label
        self.imgs = imgs
        self.transform = transform
        self.target_transform = target_transform
        self.loader = loader
    #对数据进行预处理并返回想要的信息
    #这个方法是必须要有的,用于按照索引读取每个元素的具体内容
    def __getitem__(self, index):
        fn, label = self.imgs[index]
        img = self.loader(fn) #按照标签里面的地址读取图片的RGB像素
        if self.transform is not None:
            img = self.transform(img)
        return img, label
        #return哪些内容,那么我们在训练时循环读取每个batch时,就能获取哪些内容
    #初始化一些需要传入的参数和数据集的调用
    #这个函数必须写,返回数据集长度,也就是多少张图片,和Loader长度区分
    def __len__(self):
        return len(self.imgs)

7、其中比较关键的一个就是读取图像信息的方式default_loader,这个是定义的一个函数:

#定义读取文件的格式
def default_loader(path):
    return Image.open(path).convert(‘L‘)

L表示读取灰度信息,只返回一个通道的数据

RGB表示返回三个通道的数据

接下来就是正式读取信息返回到列表里面了

因为我们所读取到的图片的尺度是图片原本的尺度,在设计神经网络的过程中,输入尺度往往有一定的要求,比如我文章使用的这个LeNet输入必须是28*28,因此可以自己定义一个train_transform进行尺度变换如下:

train_transforms = transforms.Compose(
    [transforms.RandomResizedCrop((28, 28)),
     transforms.ToTensor()]
)
test_transforms = transforms.Compose(
    [transforms.RandomResizedCrop((28, 28)),
     transforms.ToTensor()]
)

然后加载MyDataset这个类:

train_data = MyDataset(txt=root+‘train.txt‘, transform=train_transforms) 
test_data = MyDataset(txt=root+‘test.txt‘, transform=test_transforms)

8、当我们把所有的数字数据打包好了之后就是要放到DataLoader里面进行批量打包,batch_size可以一次读取多张图片的信息。

train_loader = DataLoader(dataset=train_data, batch_size=10, shuffle=True, num_workers=1) 
test_loader = DataLoader(dataset=test_data, batch_size=10, shuffle=False, num_workers=1)

这样一个自己的数字数据集就定义好了。

9、接下来就是要定义自己的网络模型,这里举例最简单的LeNet网络结构的类定义,其他网络只需更改类即可。

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

__init__用于对网络元素进行初始化,forward用于定义网络前向传播的规则。

10、接下来创建一个这样的网络,定义学习率,学习epoch次数,optimizer优化器类型:

net = LeNet()
lr, num_epoch = 0.001, 20
optimizer = torch.optim.Adam(net.parameters(), lr=lr)

11、接下来编写训练代码,在训练过程中

def train(net, train_iter, test_iter, start_epoch, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on:", device)
    loss = torch.nn.CrossEntropyLoss()#定义损失函数
    batch_count = 0 #第几个batch,如果7000张图片的batch_size是10,那么共有700个batch
    nb = len(train_iter)#训练数据一共有多少
    for epoch in range(start_epoch, num_epochs):
        #这里之所以会有start_epoch是为了后面直接加载上次未训练完的信息
        train_l_sum = 0.0#训练损失值
        train_acc_sum = 0.0#训练精度
        n, start = 0, time.time()
        pbar = tqdm(enumerate(train_iter), total=nb)
        #tqmd可以更直观地观察训练集加载的过程
        for i, (imgs, targets) in pbar:
            imgs = imgs.to(device)
            targets = targets.to(device)
            y_hat = net(imgs)#把像素信息传入网络得出预测结果
            l = loss(y_hat, targets)#计算预测结果和标签的损失值
            optimizer.zero_grad()#梯度清零
            l.backward()#反向传播
            optimizer.step()#优化器作用
            train_l_sum += l.cpu().item()
            #这里使用y_hat.argmax(dim=1)是因为该网络返回的是一个包含10个结果的向量
            # 这10个结果分别是所属类别的概率
            train_acc_sum += (y_hat.argmax(dim=1) == targets).sum().cpu().item()
            #10个类别里面取出最大值的索引作为结果
            n += targets.shape[0]
            batch_count += 1
            s = ‘%g/%g  %g‘ % (epoch, num_epochs - 1, len(targets))
            pbar.set_description(s)  # 这个就是进度条显示

        mean_loss = train_l_sum/batch_count
        train_acc = train_acc_sum/n
        test_acc = test(net, test_iter, device)
        #下面这三个列表作为全局变量用于后面的绘图
        mean_loss_list.append(mean_loss)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(‘loss %.4f, train_acc %.3f, test_acc %.3f‘ % (mean_loss, train_acc, test_acc))
        #在所有的epoch训练完之后创建节点列表保存到.pt文件里面
        #这样创建的好处是可以把当前未训练完的epoch也保存进去
        chkpt = {‘epoch‘: epoch,
                 ‘model‘: net.state_dict(),
                 ‘optimizer‘: optimizer.state_dict()}
        torch.save(chkpt, PATH)
        del chkpt

12、训练过程中的test是表示采用验证集计算精度,考察模型泛化能力

def test(net, test_iter, device):
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for imgs, targets in test_iter:
            net.eval()
            y_hat = net(imgs.to(device)).argmax(dim=1)
            acc_sum += (y_hat == targets.to(device)).float().sum().cpu().item()
            net.train()
            n += targets.shape[0]
        return acc_sum/n

13、训练完成后将结果使用PLT库画出来

train(net, train_loader, test_loader, start_epoch=start_epoch, optimizer=optimizer, device=device, num_epochs=num_epoch)
plot_use_plt(mean_loss_list, train_acc_list, test_acc_list, num_epoch)

14、首先要加载import matplotlib.pyplot as plt

def plot_use_plt(mean_loss_list, train_acc_list, test_acc_list, num_epoch):
    x1 = range(0, num_epoch)
    x2 = range(0, num_epoch)
    x3 = range(0, num_epoch)
    plt.subplot(1, 3, 1) #一行三列的第一列
    plt.plot(x1, mean_loss_list, ‘o-‘)
    plt.title(‘Train_loss vs.epochs‘)
    plt.ylabel(‘Train loss‘)
    plt.subplot(1, 3, 2)
    plt.plot(x2, train_acc_list, ‘.-‘)
    plt.title(‘Train_acc vs.epochs‘)
    plt.ylabel(‘Train acc‘)
    plt.subplot(1, 3, 3)
    plt.plot(x3, test_acc_list, ‘.-‘)
    plt.title(‘Test_acc vs.epochs‘)
    plt.ylabel(‘Test acc‘)
    plt.savefig("F:/ele/show.jpg")#这一句话一定要放在plt.show()前面
    plt.show()

15、训练结果如图所示

技术图片

 

 本项目完整代码已上传至githubhttps://github.com/logic03/Digital-recognition-with-ConvNet

 

 

  

 

 

以上是关于Pytorch定义并训练自己的数字数据集的主要内容,如果未能解决你的问题,请参考以下文章

3.2使用PyTorch搭建AlexNet并训练花分类数据集

使用faster-rcnn.pytorch训练自己数据集

PyTorch 和 Albumentations 实现图像分类(猫狗大战)

Pytorch自定义数据集模型训练流程

图像分类---利用pytorch搭建AlexNet网络模型训练自己的数据集(猫狗分类)

图像分类基于PyTorch搭建LSTM实现MNIST手写数字体识别(单向LSTM,附完整代码和数据集)