第五章——Pytorch中常用的工具


1. 數據處理

數據加載

在Pytorch 中,數據加載可以通過自己定義的數據集對象來實現。數據集對象被抽象為Dataset類,實現自己定義的數據集需要繼承Dataset,並實現兩個Python魔法方法。

  • __getitem__: 返回一條數據或一個樣本。obj[index]等價於obj.__getitem__(index).
  • __len__: 返回樣本的數量。len(obj)等價於obj.__len__().
import torch as t from torch.utils import data import os from PIL import Image import numpy as np class DogCat(data.Dataset): def __init__(self,root): imgs=os.listdir(root) #所有圖片的絕對路徑 #這里不實際加載圖片,只是指定路徑,當調用__getitem__時才會真正讀圖片 self.imgs=[os.path.join(root, img) for img in imgs] def __getitem__(self, index): img_path=self.imgs[index] #dog->1, cat->0 label=1 if 'dog' in img_path.split("/")[-1] else 0 pil_img=Image.open(img_path) array=np.asarray(pil_img) data=t.from_numpy(array) return data,label def __len__(self): return len(self.image) dataset=DogCat('N:/百度網盤/kaggle/DogCat') img,label=dataset[0]#相當於調用dataset.__getitem__(0) for img,label in dataset: print(img.size(),img.float().mean(),label) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

結果:

torch.Size([280, 300, 3]) tensor(71.6653) 0 torch.Size([396, 312, 3]) tensor(131.8400) 0 torch.Size([414, 500, 3]) tensor(156.6921) 0 torch.Size([375, 499, 3]) tensor(96.8243) 0 torch.Size([445, 431, 3]) tensor(103.8582) 1 torch.Size([373, 302, 3]) tensor(160.0512) 1 torch.Size([240, 288, 3]) tensor(95.1983) 1 torch.Size([499, 375, 3]) tensor(90.5196) 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

問題:結果大小不一,這對於batch訓練的神經網絡來說很不友好。
返回的樣本數值交大,未歸一化至【-1,1】

針對上述問題,pytorch提供了torchvision。它是一個視覺工具包,提供了很多視覺圖像處理的工具。
其中transforms模塊提供了對PIL Image對象和Tensor對象的常用操作。

對PIL Image的常見操作如下:

  • Scale/Resize: 調整尺寸,長寬比保持不變; #Resize
  • CenterCrop、RandomCrop、RandomSizedCrop:裁剪圖片;
  • Pad: 填充;
  • ToTensor: 將PIL Image對象轉換成Tensor,會自動將【0,255】歸一化至【0,1】。

對Tensor的常見操作如下:

  • Normalize: 標准化,即減均值,除以標准差;
  • ToPILImage:將Tensor轉為PIL Image.

    如果要對圖片進行多個操作,可通過Compose將這些操作拼接起來,類似於nn.Sequential.
    這些操作定義之后是以對象的形式存在,真正使用時需要調用它的__call__方法,類似於nn.Mudule.
    例如:要將圖片調整為224*224,首先應構建操作trans=Scale((224,224)),然后調用trans(img).

import os from PIL import Image import numpy as np from torchvision import transforms as T transforms=T.Compose([ T.Resize(224), #縮放圖片(Image),保持長寬比不變,最短邊為224像素 T.CenterCrop(224), #從圖片中間裁剪出224*224的圖片 T.ToTensor(), #將圖片Image轉換成Tensor,歸一化至【0,1】 T.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5]) #標准化至【-1,1】,規定均值和方差 ]) class DogCat(data.Dataset): def __init__(self,root, transforms=None): imgs=os.listdir(root) self.imgs=[os.path.join(root, img) for img in imgs] self.transforms=transforms def __getitem__(self, index): img_path=self.imgs[index] #dog->1, cat->0 label=1 if 'dog' in img_path.split("/")[-1] else 0 data=Image.open(img_path) if self.transforms: data=self.transforms(data) return data,label def __len__(self): return len(self.imgs) dataset=DogCat('N:/百度網盤/kaggle/DogCat/', transforms=transforms) img,label=dataset[0]#相當於調用dataset.__getitem__(0) for img,label in dataset: print(img.size(),label) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

結果:

torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

除了上述操作外,transforms還可以通過Lambda封裝自定義的轉換策略.
例如相對PIL Image進行隨機旋轉,則可以寫成trans=T.Lambda(lambda img: img.rotate(random()*360)).

ImageFolder

下面介紹一個會經常使用到的Dataset——ImageFolder,它的實現和上述DogCat很相似。
ImageFolder假設所有的文件按文件夾保存,每個文件夾下存儲同一個類別的圖片,文件夾名為類名,其構造函數如下:

ImageFolder(root, transform=None, target_transform = None, loader = default_loader) 
  • 1
  • 2

它主要有四個參數:

  • root :在root指定的路徑下尋找圖片
  • transform: 對PIL Image進行轉換操作, transform的輸入是使用loader讀取圖片返回的對象;
  • target_transform :對label的轉換;
  • loader: 指定加載圖片的函數,默認操作是讀取為PIL Image對象。
    label是按照文件夾名順序排序后存成字典的,即{類名:類序號(從0開始)} ,一般來說最好直接將文件夾命名為從0開始的數字,這樣會和ImageFolder實際的label一直,如果不是這種命名規則,建議通過self.class_to_idx屬性了解label和文件夾名的映射關系。
from torchvision.datasets import ImageFolder dataset=ImageFolder('N:\\data\\') dataset.class_to_idx
  • 1
  • 2
  • 3

運行結果:

{'cat': 0, 'dog': 1}
  • 1

輸入:

#所有圖片的路徑和對應的label dataset.imgs
  • 1
  • 2

輸出:

[('N:\\data\\cat\\cat.1.jpg', 0), ('N:\\data\\cat\\cat.2.jpg', 0), ('N:\\data\\cat\\cat.3.jpg', 0), ('N:\\data\\cat\\cat.4.jpg', 0), ('N:\\data\\dog\\dog.9131.jpg', 1), ('N:\\data\\dog\\dog.9132.jpg', 1), ('N:\\data\\dog\\dog.9133.jpg', 1), ('N:\\data\\dog\\dog.9134.jpg', 1)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
#沒有任何的transform,所以返回的還是PIL Image對象 dataset[0][1] #第一維是第幾張圖,第二維為1返回label
  • 1
  • 2

輸出:0

dataset[0][0] #第一維是第幾張圖,第二維為0返回圖片數據,返回的Image對象如圖所示:
  • 1

輸出:
這里寫圖片描述

加上transform:

normilize=T.Normalize(mean=[0.4,0.4,0.4],std=[0.2,0.2,0.2]) transform=T.Compose([ T.RandomResizedCrop (224), T.RandomHorizontalFlip(), T.ToTensor(), normilize, ])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
dataset=ImageFolder('N:\\data\\',transform=transform)
#深度學習中圖片數據一般保存為CxHxWx,即通道數x圖片高x圖片寬 dataset[0][0].size()
  • 1
  • 2
  • 3

輸出:

torch.Size([3, 224, 224])
  • 1
to_img=T.ToPILImage()
#0.2和0.4是標准差和均值的近似 to_img(dataset[0][0]*0.2+0.4)
  • 1
  • 2
  • 3

輸出:
這里寫圖片描述

DataLoader加載數據

Dateset只負責數據的抽象,一次調用__getitem__只返回一個樣本。
在訓練神經網絡時,是對一個batch的數據進行操作,同時還要進行shuffle和並行加速等。
對此,pytorch提供了DataLoader幫助我們實現這些功能。
DataLoader的函數定義如下:

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)
  • 1
  • 2
  • dataset: 加載的數據集)Dataset對象;
  • batch_size: 批大小;
  • shuffle:是否將數據打亂;
  • sampler:樣本抽樣
  • num_workers:使用多進程加載的進程數,0代表不使用多進程;
  • collate_fn:如何將多個樣本數據拼接成一個batch,一般使用默認的拼接方式即可;
  • pin_memory:是否將數據保存在pin memory區,pin memory中的數據轉到GPU會快一些;
  • drop_last:dataset 中的數據個數可能不是 batch_size的整數倍,drop_last為True會將多出來不足一個batch的數據丟棄。
from torch.utils.data import DataLoader dataloader=DataLoader(dataset, batch_size=3, shuffle=True, num_workers=0, drop_last=False) dataiter=iter(dataloader) imgs,labels=next(dataiter) imgs.size()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

輸出:

torch.Size([3, 3, 224, 224])
  • 1

dataloader是一個可迭代的對象,我們可以像使用迭代器一樣使用它,例如:

for batch_datas,batch_labels in dataloader: train()
  • 1
  • 2

dataiter=iter(dataloader) batch_datas,batch_labels =next(dataiter)
  • 1
  • 2

sampler:采樣模塊

Pytorch 中還提供了一個sampler模塊,用來對數據進行采樣。
常用的有隨機采樣器RandonSampler,當dataloadershuffle參數為True時,系統會自動調用這個采樣器 ,實現打亂數據。

默認的采樣器是SequentialSampler, 它會按順序一個一個進行采樣。

這里介紹另外一個很有用的采樣方法:它會根據每個樣本的權重選取數據,在樣本比例不均衡的問題中,可用它進行重采樣。

構建WeightedRandomSampler時需提供兩個參數:每個樣本的權重weights、共選取的樣本總數num_samples,以及一個可選參數replacement。權重越大的樣本被選中的概率越大,待選取的樣本數目一般小於全部樣本數目。
replacement用於指定是否可以重復選取某一個樣本,默認為True,即允許在一個epoch中重復采樣某一個數據。
如果設為False,則當某一類樣本被全部選取完,但樣本數目仍為達到num_samples時,sampler將不會再從該類中選取數據,此時可能導致weights參數失效。
下面舉例說明:
1)

#dataset=DogCat('N:/百度網盤/kaggle/DogCat/',transforms=transforms) dataset=DogCat('N:/百度網盤/kaggle/DogCat/', transforms=transforms) #img,label=dataset[0]#相當於調用dataset.__getitem__(0) #狗的圖片取出的概率是貓的概率的兩倍 #兩類取出的概率與weights的絕對值大小無關,之和比值有關 weights=[2 if label==1 else 1 for data ,label in dataset] weights
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

輸出:

[1, 1, 1, 1, 2, 2, 2, 2]
  • 1

2)

from torch.utils.data.sampler import WeightedRandomSampler sampler=WeightedRandomSampler(weights, num_samples=9, replacement=True) dataloader=DataLoader(dataset, batch_size=3, sampler=sampler) for datas,labels in dataloader: print(labels.tolist())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

輸出:

[1, 0, 1] [1, 0, 1] [1, 0, 1]
  • 1
  • 2
  • 3

可見貓狗樣本比例約為1:2,另外一共有8個樣本,卻返回了9個樣本,說明樣本有被重復返回的,這就是replacement參數的作用。
下面我們將replacement設置為False.

from torch.utils.data.sampler import WeightedRandomSampler sampler=WeightedRandomSampler(weights,num_samples=8,replacement=False) dataloader=DataLoader(dataset,batch_size=4,sampler=sampler) for datas,labels in dataloader: print(labels.tolist())
  • 1
  • 2
  • 3
  • 4
  • 5

輸出:

[0, 0, 1, 0] [1, 0, 1, 1]
  • 1
  • 2

在這種情況下,num_samples等於dataset的樣本總數,為了 不重復選取,sampler會將每個樣本都返回,這樣就失去了weight的意義。

從上面的例子可見sampler在采樣中的作用:如果指定了samplershuffle將不再生效,並且sampler.num_smples會覆蓋dataset的實際大小,即一個epoch返回的圖片總數取決於sampler.num_samples.


總結:
完整代碼:

import os from PIL import Image from torch.utils import data #import numpy as np from torchvision import transforms as T from torch.utils.data import DataLoader from torch.utils.data.sampler import WeightedRandomSampler transforms=T.Compose([ T.Resize(224), #縮放圖片(Image),保持長寬比不變,最短邊為224像素 T.CenterCrop(224), #從圖片中間裁剪出224*224的圖片 T.ToTensor(), #將圖片Image轉換成Tensor,歸一化至【0,1】 T.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5]) #標准化至【-1,1】,規定均值和方差 ]) class DogCat(data.Dataset): def __init__(self,root, transforms=None): imgs=os.listdir(root) self.imgs=[os.path.join(root, img) for img in imgs] self.transforms=transforms def __getitem__(self, index): img_path=self.imgs[index] #dog->1, cat->0 label=1 if 'dog' in img_path.split("/")[-1] else 0 data=Image.open(img_path) if self.transforms: data=self.transforms(data) return data,label def __len__(self): return len(self.imgs) dataset=DogCat('N:/百度網盤/kaggle/DogCat/', transforms=transforms) img,label=dataset[0]#相當於調用dataset.__getitem__(0) print("******dataset*************") print("dataset") for img,label in dataset: print(img.size(),label) dataset=DogCat('N:/百度網盤/kaggle/DogCat/', transforms=transforms) #狗的圖片取出的概率是貓的概率的兩倍 #兩類取出的概率與weights的絕對值大小無關,之和比值有關 weights=[2 if label==1 else 1 for data ,label in dataset] print("******weights**************") print("weight:{}".format(weights)) print("******sampler**************") sampler=WeightedRandomSampler(weights,num_samples=8,replacement=False) dataloader=DataLoader(dataset,batch_size=4,sampler=sampler) for datas,labels in dataloader: print(labels.tolist())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

輸出:

******dataset************* dataset torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 0 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1 torch.Size([3, 224, 224]) 1 ******weights************** weight:[1, 1, 1, 1, 2, 2, 2, 2] ******sampler************** [0, 0, 1, 1] [1, 0, 1, 0]


免責聲明!

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



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