pytorch:EDSR 生成訓練資料的方法
Pytorch:EDSR 生成訓練資料的方法
引言
Winter is coming
正文
pytorch
提供的DataLoader
是用來包裝你的資料的工具. 所以你要將自己的 (numpy array 或其他)
資料形式裝換成 Tensor
, 然後再放進這個包裝器中. 使用 DataLoader
有什麼好處呢?
就是他們幫你有效地迭代資料, 舉例:
import torch
import torch.utils.data as Data #utils是torch中的一個模組,Data是進行小批訓練的途徑或模組
x = torch.linspace(1, 10, 10) # x data (torch tensor): 初始的資料
y = torch.linspace(10, 1, 10) # y data (torch tensor): 目標的資料
- 先轉換成
torch
能識別的Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)
torch_dataset
為用 torch
定義的一個數據庫,然後將要訓練的資料放到資料庫中。
x為用來訓練的資料,y為用於算誤差的資料
- 把
dataset
放入DataLoader
BATCH_SIZE = 5 # 批訓練大小為五,即每次抽取五個資料進行訓練 loader = Data.DataLoader( dataset=torch_dataset, # torch TensorDataset format batch_size=BATCH_SIZE, # mini batch size shuffle=True, # 要不要打亂資料 (打亂比較好) num_workers=2, # 多執行緒來讀資料,更有效率 )
我們使用 DataLoader()
來使我們的訓練過程變成一批一批,shuffle
是 Bool
型變數,為真時隨機打亂資料後進行抽樣
for epoch in range(3): # 訓練整套資料 3 次 for step, (batch_x, batch_y) in enumerate(loader): # 每一步loader釋放一小批資料用來學習,因為一組總共有10個data,batch_size又為5,所以訓練一次資料有2個step # 假設這裡就是你訓練的地方... # 打出來一些資料 print('Epoch: ', epoch, '| Step: ', step, '| batch x: ', batch_x.numpy(), '| batch y: ', batch_y.numpy())
結果如下:
Epoch: 0 | Step: 0 | batch x: [ 6. 7. 2. 3. 1.] | batch y: [ 5. 4. 9. 8. 10.]
Epoch: 0 | Step: 1 | batch x: [ 9. 10. 4. 8. 5.] | batch y: [ 2. 1. 7. 3. 6.]
Epoch: 1 | Step: 0 | batch x: [ 3. 4. 2. 9. 10.] | batch y: [ 8. 7. 9. 2. 1.]
Epoch: 1 | Step: 1 | batch x: [ 1. 7. 8. 5. 6.] | batch y: [ 10. 4. 3. 6. 5.]
Epoch: 2 | Step: 0 | batch x: [ 3. 9. 2. 6. 7.] | batch y: [ 8. 2. 9. 5. 4.]
Epoch: 2 | Step: 1 | batch x: [ 10. 4. 8. 1. 5.] | batch y: [ 1. 7. 3. 10. 6.]
可以看出, 每步都匯出了5個數據進行學習. 然後每個 epoch
的匯出資料都是先打亂了以後再匯出.
真正方便的還不是這點. 如果我們改變一下令 BATCH_SIZE = 8
, 這樣我們就知道, step=0 會匯出8個數據, 但是, step=1
時資料庫中的資料不夠 8個, 這時怎麼辦呢:
BATCH_SIZE = 8 # 批訓練的資料個數
for ...:
for ...:
...
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())
結果如下:
Epoch: 0 | Step: 0 | batch x: [ 6. 7. 2. 3. 1. 9. 10. 4.] | batch y: [ 5. 4. 9. 8. 10. 2. 1. 7.]
Epoch: 0 | Step: 1 | batch x: [ 8. 5.] | batch y: [ 3. 6.]
Epoch: 1 | Step: 0 | batch x: [ 3. 4. 2. 9. 10. 1. 7. 8.] | batch y: [ 8. 7. 9. 2. 1. 10. 4. 3.]
Epoch: 1 | Step: 1 | batch x: [ 5. 6.] | batch y: [ 6. 5.]
Epoch: 2 | Step: 0 | batch x: [ 3. 9. 2. 6. 7. 10. 4. 8.] | batch y: [ 8. 2. 9. 5. 4. 1. 7. 3.]
Epoch: 2 | Step: 1 | batch x: [ 1. 5.] | batch y: [ 10. 6.]
這時, 在 step=1
就只給你返回這個 epoch 中剩下的data.
在閱讀edsr
的原始碼時發現了下面這段程式碼:
opt.seed = random.randint(1,10000)
print("Random Seed: ",opt.seed)
torch.manual_seed(opt.seed) # 為當前cpu設定隨機種子,值為範圍在1到10000裡的一個隨機數
if cuda:
torch.cuda.manual_seed(opt.seed) # 為當前gpu設定隨機種子
cudnn.banchmark = Ture
在訓練開始時,引數的初始化為隨機的,為了讓每次的結果都一致,我們要設定隨機種子。
而cudnn.banchmark
這個方法可以讓CuDNN
的auto-tuner
自動尋找最適合當前配置的高效演算法,如果每次迭代輸入不變,可以增加,如果輸入會產生變化,則會降低計算的效率。
接下來是edsr
中使用 DataLoader
的方法。
print("===> Loading datasets")
train_set = DatasetFromHdf5("path_to_dataset.h5")
training_data_loader = DataLoader(dataset=train_set, num_workers=opt.threads, batch_size=opt.batchSize, shuffle=True)
我仔細研究了兩天發現,執行到這步時,圖片似乎已經處理好了,因為這只是對圖片進行打亂後再輸入網路進行訓練。
重新閱讀 master
中的 ReadMe
後發現了這兩行文字:
Prepare Training dataset
- Please refer Code for Data Generation for creating training files.
- Data augmentations including flipping, rotation, downsizing are adopted.
通過連結找到了具體的生成測試資料的方法,下面是edsr
的生成訓練資料的方式:
clear;
close all;
folder = 'path/to/train/folder';
savepath = 'edsr_x4.h5'; % 將模型儲存為edsr_x4.h5檔案,x4為該模型的放大倍數
%% scale factors
scale = 4; % 放大倍數
size_label = 192; % 最終經過調整後的圖片的大小
size_input = size_label/scale; % 輸入大小 = 最終大小/放大倍數 = 48
stride = 96; % 步長大小為96
%% downsizing
downsizes = [1,0.7,0.5]; % 調整大小的三維向量
data = zeros(size_input, size_input, 3, 1); % init一個名為data的大小為48x48x3的零矩陣
label = zeros(size_label, size_label, 3, 1); % init一個名為label的大小為192x192x3的零矩陣
count = 0;
margain = 0; % 應為邊緣資訊一類的變數值
%% generate data 準備資料
filepaths = []; % 宣告一個讀取檔案的目錄
filepaths = [filepaths; dir(fullfile(folder, '*.png'))]; % 得到目錄中所有圖片的列表
length(filepaths) % 圖片的個數
for i = 1 : length(filepaths) % 遍歷所有圖片
for flip = 1: 3 % 每張圖片翻轉三次
for degree = 1 : 4 % 從4個角度?
for downsize = 1 : length(downsizes)
image = imread(fullfile(folder,filepaths(i).name)); % 讀取第i張圖片
if flip == 1 % 當flip為1時,對圖片進行上下翻轉
image = flipd(image ,1);
end
if flip == 2 % 當flip為2時,對圖片進行左右翻轉
image = flipd(image ,2);
end
image = imrotate(image, 90 * (degree - 1)); % 逆時針方向旋轉圖片0-90-180-270度(角度為正則逆時針旋轉,為負則順時針)
image = imresize(image,downsizes(downsize),'bicubic'); % 通過雙三次插值的方法將影象調整為之前的1-0.7-0.5的大小
if size(image,3)==3 % 當圖片為三通道RGB影象時,進行以下的操作
%image = rgb2ycbcr(image);
image = im2double(image);
im_label = modcrop(image, scale); % 這個函式將取模後的圖片賦給im_label
作者定義了一個對影象進行處理的函式(在Matlab Doc中是找不到滴):modcrop.m
,在同個資料夾下可以找到
function imgs = modcrop(imgs, modulo)
if size(imgs,3)==1 % 灰度圖,或者可以理解為僅有一個y通道的影象
sz = size(imgs);
sz = sz - mod(sz, modulo);
imgs = imgs(1:sz(1), 1:sz(2));
else
tmpsz = size(imgs); % 獲取圖片尺寸
sz = tmpsz(1:2); % 把圖片的height和width賦給sz
sz = sz - mod(sz, modulo); % height和width對modulo取模,並減去這個值,使得sz的大小正好可以整除modulo
imgs = imgs(1:sz(1), 1:sz(2),:); % 得到新的尺寸的圖片
end
現在繼續對取模後的三通道圖片進行操作:
[hei,wid, c] = size(im_label); % 得到這張圖片的現在的大小
% 使用大小為size_label*size_label的卷積核在圖片上進行卷積,步長為stride
% subim_input 作為輸入的圖片,存入到data陣列中
% subim_label 為放大4倍後的圖片,存入到label陣列中
filepaths(i).name
for x = 1 + margain : stride : hei-size_label+1 - margain
for y = 1 + margain :stride : wid-size_label+1 - margain
subim_label = im_label(x : x+size_label-1, y : y+size_label-1, :);
subim_input = imresize(subim_label,1/scale,'bicubic');
% figure;
% imshow(subim_input);
% figure;
% imshow(subim_label);
count=count+1;
data(:, :, :, count) = subim_input; % 第count組資料
label(:, :, :, count) = subim_label;
end
end
end
end
end
end
end
order = randperm(count); % 生成一行從1到count的整數,打亂後返回
data = data(:, :, :, order); % 將打亂後的樣本順序返回給 data 和 label 陣列
label = label(:, :, :, order);
%% writing to HDF5
chunksz = 64; % 每次寫入的資料個數
created_flag = false;
totalct = 0;
for batchno = 1:floor(count/chunksz)
batchno
last_read=(batchno-1)*chunksz;
batchdata = data(:,:,:,last_read+1:last_read+chunksz);
batchlabs = label(:,:,:,last_read+1:last_read+chunksz);
startloc = struct('dat',[1,1,1,totalct+1], 'lab', [1,1,1,totalct+1]);
curr_dat_sz = store2hdf5(savepath, batchdata, batchlabs, ~created_flag, startloc, chunksz);
created_flag = true;
totalct = curr_dat_sz(end);
end
h5disp(savepath);
現在我已經大致明白了這個.m檔案要做什麼了:
- 初始化各種引數
- 使用
flipping
,rotation
,downsizing
方法對圖片調整大小 - 對圖片使用大小為
size_label*size_label
的卷積核進行卷積得到標籤圖,縮小後得到輸入圖 - 將資料打亂後寫入HDF5檔案中
我在總結到這一步時恍然大悟,原來main_edsr.py
檔案頭部的引用中那段
form dataset import DatasetFromHdf5
程式碼的意思是:從data資料夾中引用格式為Hdf5的,你已經生成好的訓練檔案(剛才訓練好的edsr_x4.h5
檔案)!!!
好了,看了這麼就,不實現一下就說不過去了。可是我一點執行,matlab就報錯說我沒有一下函式。這問題也不是一兩次了,我覺得有可能是我沒有訓練資料(原始圖片)的問題,於是我仿照我之前學習caffe框架下SRCNN的方法對程式碼的一下部分進行了修改。
% 第三行
folder = 'train';
% 55到58行去掉註釋,我想看具體的圖片長什麼樣子
figure;
imshow(subim_input);
figure;
imshow(subim_label);
然後報錯:未定義函式或變數 'flipd'
,其實我之前在Matlab Doc中查詢有關flip
函式資訊時,就發現沒有filpd
這個函數了。將d去掉後發現會報下標必須為整形的錯誤。可是flip
的用法沒錯啊。索性我直接將dim=1
時的函式替換為flipud(image)
即上下翻轉圖片,將dim=2
時的函式替換為fliplr(image)
左右翻轉圖片。這次執行沒報錯了,但是圖片顯示很是鬼畜,然後
我的電腦就藍屏了,我*************************,啊啊啊啊啊啊
算了,我脾氣超好!
憑著剛才的印象發現,編號為偶數的圖片大於編號為奇數的圖片,也就是說,我們的subim_label
的大小大於subim_input
這種最基本的問題沒出錯,還好還好。重啟電腦後發現,部落格還在,就是網聯不上了,又重啟一次後一切正常了。行吧,原諒你了,誰讓我上午心情好呢。
寫在後面
- 到這一步來說,應該算是學習結束了,基本弄清楚了訓練檔案是怎麼產生的。昨天晚上和今天早上真是收穫頗豐呢!弄清楚了很多東西,nice!我真bang(苦笑.jpg)
- 我在舍友的呼嚕聲中把這篇學習部落格完成了,剛開始還想,我打字的聲音會不會吵醒他們?,後來發現完全是多慮了,他們呼嚕聲超大,影響我學習?!,沒有沒有,開玩笑開玩笑,把刀放下好好說話……鬼知道為什麼我昨晚最晚睡(3:00 am),卻是最早起床的(8:30 am),orz
- Happy Birthday To nado, my dear idol
結語
真心喜歡過的人沒法做朋友 因為看多幾眼 都還是想擁有