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)) + '\n'
            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)) + '\n'
            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('\n')
            line = line.rstrip('\n')
            #刪除本行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

 

 

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM