Pytorch數據讀取與預處理該如何實現
在煉丹時,數據的讀取與預處理是關鍵一步。不同的模型所需要的數據以及預處理方式各不相同,如果每個輪子都我們自己寫的話,是很浪費時間和精力的。Pytorch幫我們實現瞭方便的數據讀取與預處理方法,下面記錄兩個DEMO,便於加快以後的代碼效率。
根據數據是否一次性讀取完,將DEMO分為:
1、串行式讀取。也就是一次性讀取完所有需要的數據到內存,模型訓練時不會再訪問外存。通常用在內存足夠的情況下使用,速度更快。
2、並行式讀取。也就是邊訓練邊讀取數據。通常用在內存不夠的情況下使用,會占用計算資源,如果分配的好的話,幾乎不損失速度。
Pytorch官方的數據提取方式盡管方便編碼,但由於它提取數據方式比較死板,會浪費資源,下面對其進行分析。
1 串行式讀取
1.1 DEMO代碼
import torch from torch.utils.data import Dataset,DataLoader class MyDataSet(Dataset):# ————1———— def __init__(self): self.data = torch.tensor(range(10)).reshape([5,2]) self.label = torch.tensor(range(5)) def __getitem__(self, index): return self.data[index], self.label[index] def __len__(self): return len(self.data) my_data_set = MyDataSet()# ————2———— my_data_loader = DataLoader( dataset=my_data_set, # ————3———— batch_size=2, # ————4———— shuffle=True, # ————5———— sampler=None, # ————6———— batch_sampler=None, # ————7———— num_workers=0 , # ————8———— collate_fn=None, # ————9———— pin_memory=True, # ————10———— drop_last=True # ————11———— ) for i in my_data_loader: # ————12———— print(i)
註釋處解釋如下:
1、重寫數據集類,用於保存數據。除瞭 __init__() 外,必須實現 __getitem__() 和 __len__() 兩個方法。前一個方法用於輸出索引對應的數據。後一個方法用於獲取數據集的長度。
2~5、 2準備好數據集後,傳入DataLoader來迭代生成數據。前三個參數分別是傳入的數據集對象、每次獲取的批量大小、是否打亂數據集輸出。
6、采樣器,如果定義這個,shuffle隻能設置為False。所謂采樣器就是用於生成數據索引的可迭代對象,比如列表。因此,定義瞭采樣器,采樣都按它來,shuffle再打亂就沒意義瞭。
7、批量采樣器,如果定義這個,batch_size、shuffle、sampler、drop_last都不能定義。實際上,如果沒有特殊的數據生成順序的要求,采樣器並沒有必要定義。torch.utils.data 中的各種 Sampler 就是采樣器類,如果需要,可以使用它們來定義。
8、用於生成數據的子進程數。默認為0,不並行。
9、拼接多個樣本的方法,默認是將每個batch的數據在第一維上進行拼接。這樣可能說不清楚,並且由於這裡可以探究一下獲取數據的速度,後面再詳細說明。
10、是否使用鎖頁內存。用的話會更快,內存不充足最好別用。
11、是否把最後小於batch的數據丟掉。
12、迭代獲取數據並輸出。
1.2 速度探索
首先看一下DEMO的輸出:
輸出瞭兩個batch的數據,每組數據中data和label都正確排列,符合我們的預期。那麼DataLoader是怎麼把數據整合起來的呢?首先,我們把collate_fn定義為直接映射(不用它默認的方法),來查看看每次DataLoader從MyDataSet中讀取瞭什麼,將上面部分代碼修改如下:
my_data_loader = DataLoader( dataset=my_data_set, batch_size=2, shuffle=True, sampler=None, batch_sampler=None, num_workers=0 , collate_fn=lambda x:x, #修改處 pin_memory=True, drop_last=True )
結果如下:
輸出還是兩個batch,然而每個batch中,單個的data和label是在一個list中的。似乎可以看出,DataLoader是一個一個讀取MyDataSet中的數據的,然後再進行相應數據的拼接。為瞭驗證這點,代碼修改如下:
import torch from torch.utils.data import Dataset,DataLoader class MyDataSet(Dataset): def __init__(self): self.data = torch.tensor(range(10)).reshape([5,2]) self.label = torch.tensor(range(5)) def __getitem__(self, index): print(index) #修改處2 return self.data[index], self.label[index] def __len__(self): return len(self.data) my_data_set = MyDataSet() my_data_loader = DataLoader( dataset=my_data_set, batch_size=2, shuffle=True, sampler=None, batch_sampler=None, num_workers=0 , collate_fn=lambda x:x, #修改處1 pin_memory=True, drop_last=True ) for i in my_data_loader: print(i)
輸出如下:
驗證瞭前面的猜想,的確是一個一個讀取的。如果數據集定義的不是格式化的數據,那還好,但是我這裡定義的是tensor,是可以直接通過列表來索引對應的tensor的。因此,DataLoader的操作比直接索引多瞭拼接這一步,肯定是會慢很多的。一兩次的讀取還好,但在訓練中,大量的讀取累加起來,就會浪費很多時間瞭。
自定義一個DataLoader可以證明這一點,代碼如下:
import torch from torch.utils.data import Dataset,DataLoader from time import time class MyDataSet(Dataset): def __init__(self): self.data = torch.tensor(range(100000)).reshape([50000,2]) self.label = torch.tensor(range(50000)) def __getitem__(self, index): return self.data[index], self.label[index] def __len__(self): return len(self.data) # 自定義DataLoader class MyDataLoader(): def __init__(self, dataset,batch_size): self.dataset = dataset self.batch_size = batch_size def __iter__(self): self.now = 0 self.shuffle_i = np.array(range(self.dataset.__len__())) np.random.shuffle(self.shuffle_i) return self def __next__(self): self.now += self.batch_size if self.now <= len(self.shuffle_i): indexes = self.shuffle_i[self.now-self.batch_size:self.now] return self.dataset.__getitem__(indexes) else: raise StopIteration # 使用官方DataLoader my_data_set = MyDataSet() my_data_loader = DataLoader( dataset=my_data_set, batch_size=256, shuffle=True, sampler=None, batch_sampler=None, num_workers=0 , collate_fn=None, pin_memory=True, drop_last=True ) start_t = time() for t in range(10): for i in my_data_loader: pass print("官方:", time() - start_t) #自定義DataLoader my_data_set = MyDataSet() my_data_loader = MyDataLoader(my_data_set,256) start_t = time() for t in range(10): for i in my_data_loader: pass print("自定義:", time() - start_t)
運行結果如下:
以上使用batch大小為256,僅各讀取10 epoch的數據,都有30多倍的時間上的差距,更大的batch差距會更明顯。另外,這裡用於測試的每個數據隻有兩個浮點數,如果是圖像,所需的時間可能會增加幾百倍。因此,如果數據量和batch都比較大,並且數據是格式化的,最好自己寫數據生成器。
2 並行式讀取
2.1 DEMO代碼
import matplotlib.pyplot as plt from torch.utils.data import DataLoader from torchvision import transforms from torchvision.datasets import ImageFolder path = r'E:\DataSets\ImageNet\ILSVRC2012_img_train\10-19\128x128' my_data_set = ImageFolder( #————1———— root = path, #————2———— transform = transforms.Compose([ #————3———— transforms.ToTensor(), transforms.CenterCrop(64) ]), loader = plt.imread #————4———— ) my_data_loader = DataLoader( dataset=my_data_set, batch_size=128, shuffle=True, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=True, drop_last=True ) for i in my_data_loader: print(i)
註釋處解釋如下:
1/2、ImageFolder類繼承自DataSet類,因此可以按索引讀取圖像。路徑必須包含文件夾,ImageFolder會給每個文件夾中的圖像添加索引,並且每張圖像會給予其所在文件夾的標簽。舉個例子,代碼中my_data_set[0] 輸出的是圖像對象和它對應的標簽組成的列表。
3、圖像到格式化數據的轉換組合。更多的轉換方法可以看 transform 模塊。
4、圖像法的讀取方式,默認是PIL.Image.open(),但我發現plt.imread()更快一些。
由於是邊訓練邊讀取,transform會占用很多時間,因此可以先將圖像轉換為需要的形式存入外存再讀取,從而避免重復操作。
其中transform.ToTensor()會把正常讀取的圖像轉換為torch.tensor,並且像素值會映射至[0,1][0,1]。由於plt.imread()讀取png圖像時,像素值在[0,1][0,1],而讀取jpg圖像時,像素值卻在[0,255][0,255],因此使用transform.ToTensor()能將圖像像素區間統一化。
以上就是Pytorch數據讀取與預處理該如何實現的詳細內容,更多關於Pytorch數據讀取與預處理的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- pytorch 實現多個Dataloader同時訓練
- 解決Pytorch dataloader時報錯每個tensor維度不一樣的問題
- Pytorch數據讀取之Dataset和DataLoader知識總結
- pytorch DataLoader的num_workers參數與設置大小詳解
- 我對PyTorch dataloader裡的shuffle=True的理解