一樣承襲前幾日的主題,繼續討論資料前處理優化的方式。同樣是Cached到記憶體內,今天會利用比較土法煉鋼的方式來進一步優化效能。
實際上有跑前一篇利用Monai來進行Cached的朋友應該有發現,每次的訓練前,都需要花費一段時間進行Cached。於是就會發生「80M的東西要讀取好幾分鐘」這樣十分不合理的事情。於是我本身在實驗的時候,對於前一篇所提到的「monai.data.CacheDataset
與直接把mednist的.npz
讀進來的效率差不多」這件事情開始懷疑,經過測試以後發現果然還有優化的空間,因此意外誕生了這一篇。
.npz
直接讀取.npz
其實會發現,不過就是幾秒鐘的事情,而且已經直接把三個切分全部讀進來了。
>>> npz_files = np.load('data/chestmnist.npz')
>>> for key in npz_files.files:
>>> print(key, f'shape {npz_files[key].shape}')
train_images shape (78468, 28, 28)
val_images shape (11219, 28, 28)
test_images shape (22433, 28, 28)
train_labels shape (78468, 14)
val_labels shape (11219, 14)
test_labels shape (22433, 14)
上一篇情況中,會比較久,主要是因為需要把10多萬張png分別進行讀取,再整成檔案花費了許多時間。而.npz
的狀況,則是因為是單一個連續的檔案,因此可以只進行一次的I/O直接全部讀取進來。
所以若是原始比較大型的檔案,如果前處理以後的影像不大(例如把X光壓成224x224),再壓成像.npz
這樣的array實際上應該是可行的。
為了讓先前dict-based的Transforms以及Dataloader可以延續使用,首先我們要把資料點從整個大矩陣切個出來,像是train set就是把78468x28x28
切成 78,468個28x28
,可以利用np.split
直接進行快速的分割:
for key in npz_files.files:
sliced_data[key] = np.split(npz_files[key], len(npz_files[key]))
[npz_files[key][i] for i in range(len(npz_files[key]))]
很慢,可以體驗看看...),會受到python慢的原罪影響,要跑非常久。矩陣運算的任務可以的話一定要交給優化過效能的numpy來執行。接著把slice後的array製程dict就完成了!
datasets = {
split : [{
'img' : sliced_data[f'{split_mapping[split]}_images'][i].transpose(0,2,1),
'labels' : sliced_data[f'{split_mapping[split]}_labels'][i][0]}
for i in range(len(sliced_data[f'{split_mapping[split]}_images']))]
具體可以參考preprocess.py,執行後可以得到下圖,基本上與先前無異。
在修改了前處理以後,可以實際跑一次train.py看看。這邊大概要注意幾點是:
一樣只訓練5個epoch,大概跑個幾分鐘就可以得到結果了:
monai.data.CacheDataset
有進行更多一些meta data的I/O,或是讀取Cached的機制沒有這麼直接,造成實際上產生資料的速度會花上更多的時間。