語義分割代碼實現流程

語義分割的整體實現代碼大致思路很簡單,但是具體到細節,就有很多可說的東西。

之前寫過一篇文章,可能有些地方現在又有了新的思路或者感受,或者說之前沒有突出重點。

作爲一個小白,這裏把自己知道的知識寫一下,事無鉅細,希望看到的人能有所收穫。

一、文件思路

總的來說,語義分割代碼可以分爲如下幾個部分:

  • data:圖像數據
  • data/train:訓練集數據
  • data/train/img:訓練集原始圖像img
  • data/train/label:訓練集原始圖像label
  • data/val:驗證集數據
  • data/val/img:驗證集原始圖像img
  • data/val/label:驗證集原始圖像label
  • dataset:將本地數據轉化成pytorch對應的DataSet的文件
  • model:網絡模型
  • utils:工具文件
  • utils/args:參數類
  • utils/utils:通用方法類
  • train.py:訓練網絡代碼

當然,這只是一種劃分文件的思路,還有很多不錯的思路,大家選擇一種即可。

二、代碼實現思路

代碼實現思路其實就是對上面文件的詮釋了。

1、圖像數據

沒有圖像數據啥也做不了,所以我們首先要從數據說起。

針對數據來講,有哪些需要注意的事項呢?

  1. 圖像數據是否過大
  2. 圖像數據是否需要增強預處理
  3. 圖像數據是否需要提前切分爲測試集和驗證集

1、圖像數據過大

當圖像數據過大時,很容易造成內存滿的問題,導致我們訓練失敗。

方法:A、採用cv2.resize將圖像縮小。B、將圖像split爲小圖像。

2、圖像提前預處理

圖像提前預處理是爲了讓圖像更好的去訓練,如果原始圖像存在過於模糊等問題,那麼我們就需要做一些預處理操作。

方法:採用各種數據增強庫,如:albumentations庫,對圖像亮度、對比度、銳度等進行增強。

3、圖像數據是否提前切分爲測試集和驗證集

一般來說,我們在代碼實現階段可以將圖像進行切分,當然,如果圖像數據表示很明顯簡單,我們完全可以手動將數據分爲測試集和驗證集,這就免了在代碼中實現對圖像讀取切分等操作了,自願而爲。

2、將本地圖像數據集轉化爲pytorch的DataSet

本地圖像數據執行完第一步之後,我們便來到了這一步。

爲什麼要將本地圖像數據集轉化爲pytorch的DataSet呢?

這是因爲我們要使用pytorch中的DataLoader類,DataSet作爲DataLoader類的參數,必須滿足pytorch的要求。

具體怎麼實現呢?很簡單,大家可以上網搜一下:如何將數據轉化爲pytorch的數據集。這裏簡單說一下。

class DataSet(torch.utils.data.Dataset):
    def __init__(self):
        super().__init__()

    def __len__(self):
        return len(img_list)

    def __getitem__(self, idx):
        return img, label

如上所示,我們只需寫一個類,繼承torch.utils.data.Dataset類,然後重寫它的__len__()方法和__getitem__()方法即可。

其中__len__()方法是返回數據集大小,__getitem__()方法是返回對應idx的img和label。

這裏又要說一個重點了!!!

  1. 圖像數據增強
  2. 圖像數據對應矩陣數據格式
  3. img和label的處理
  4. 數據集切分

1、圖像數據增強

這裏的增強不同於之前的圖像數據離線預處理,圖像數據預處理是爲了讓圖像變得更好,讓模型更容易訓練。

而這裏的圖像在線增強是爲了讓圖像變壞,增大訓練難度,比如反轉等。

一般使用:

class torchvision.transforms.Compose(transforms)

不過這裏也有兩個重要的操作,比如:(一般我們是要對img進行如下處理)

class torchvision.transforms.Normalize(mean, std)

給定均值:(R,G,B) 方差:(R,G,B),將會把Tensor正則化。即:Normalized_image=(image-mean)/std。

class torchvision.transforms.ToTensor

把一個取值範圍是[0,255]的PIL.Image或者shape爲(H,W,C)的numpy.ndarray,轉換成形狀爲[C,H,W],取值範圍是[0,1.0]的torch.FloadTensor

2、圖像數據對應矩陣數據格式

爲什麼說這個問題呢,因爲這個對應了你使用什麼損失函數。如果你使用了交叉熵損失,你就要將label轉化爲long形式,如果你使用MSE損失,那麼你就要將label轉化爲float形式,這個可以在報錯的時候再改正。

3、img和label的處理

一個重點!!!

img和label的處理主要是其維度的處理,當然,這個東西我也不是太理解具體細節。

但是,我知道的是,維度取決於你採用什麼損失函數

如果你採用MSE,那麼你就要將label處理成(分類數,label.shape[0], label.shape[1])三維,這樣,在計算的時候,label的維度就變成了(batch_size, NUM_CLASSES, label.shape[0], label.shape[1])四維,然後和模型輸出output四維進行計算。

如果你採用交叉熵,那麼你不用對label維度進行處理,這樣label計算時候的維度就是(batch_size, label.shape[0], label.shape[1])三維,因爲交叉熵計算的(output, label)固定label比output少一維。

一個重點!!!

label歸一化後,處理成mask形式,也就是對每個像素打了標籤。

如果是二分類,則將label處理成0、1矩陣,如果三分類,則將label處理成0、1、2矩陣。

4、數據集切分

當你處理完數據後,可以用代碼劃分數據集,例如:

採用這個方法:from torch.utils.data import random_split
dataset = BagDataset(transform)

train_size = int(0.9 * len(dataset))  # 整個訓練集中,百分之90爲訓練集
test_size = len(dataset) - train_size
train_dataset, test_dataset = random_split(dataset, [train_size, test_size])  # 劃分訓練集和測試集

3、網絡模型

實現了第1步和第2步後,基本上我們的數據處理就差不多了,簡述一下我們之前做了什麼:

  • 數據預處理、切分
  • 數據歸一化、維度變換
  • 數據集切分

然後我們的數據階段基本就結束了,然後我們就開始寫模型了,當然,這部分不做多闡述,因爲模型基本上一搜一堆,這不是我們講本文章的重點。具體怎麼寫一個網絡結構需要大家自己去學習了。

4、args和utils

args主要是一些參數的設置,比如:

import os
import torch

class Args():
    def __init__(self):
        super().__init__()

        # 輸入通道數
        self.in_channel = 1
        # 幾分類問題
        self.NUM_CLASSES = 2

        # 設置太大會內存溢出
        self.batch_size = 3
        # 線程數
        self.num_workers = 4
        # 學習率
        self.lr = 0.001

        # 數據集根目錄
        self.path = 'data'

        # 訓練集地址
        self.train_mode = 'train'
        # 驗證集地址
        self.val_mode = 'val'

        # 設置多gpu
        self.gpu_ids = [0, 1, 2]
        os.environ['CUDA_VISIBLE_DEVICES'] = '0, 1, 2'
        self.cuda = torch.cuda.is_available()

utils主要是一些方法,具體你需要哪些方法,都可以寫進去,比如切分數據集,合併數據集,判斷路徑是否存在,圖像轉化等等。

5、train.py

DataSet有了、Model有了,接下來到了最重要的部分,就是講data----->model了。

先講訓練trainer的部分。

  1. 設置DataLoader,將數據集傳入
  2. 獲得模型,設置多GPU並行
  3. 設置優化器
  4. 設置損失標準
  5. 從dataloader中獲取數據
  6. 優化器梯度設置0
  7. 將img傳入net獲得output
  8. 計算output和label的損失
  9. 損失反向傳播
  10. 優化器執行下一步
  11. 執行一段時間,保存net模型

1、設置DataLoader,將數據集傳入,如:

self.train_dataset = DataSet(path=self.args.path, mode=self.args.train_mode)

self.train_img_loader = DataLoader(dataset=self.train_dataset, batch_size=self.args.batch_size, shuffle=False,
                                num_workers=self.args.num_workers)

2、獲得模型,設置多GPU並行,如:

self.net = UNet(self.args.in_channel, self.args.NUM_CLASSES).cuda()
self.net = nn.DataParallel(self.net, self.args.gpu_ids)

3、設置優化器

4、設置損失標準,如:

self.optimizer = torch.optim.Adam(self.net.parameters())
self.criterion = torch.nn.CrossEntropyLoss().cuda()

5、從dataloader中獲取數據

6、優化器梯度設置0

7、將img傳入net獲得output

8、計算output和label的損失

9、損失反向傳播

10、優化器執行下一步,如:

        self.net.train()
        for i,[img, label] in enumerate(self.train_img_loader):
            self.optimizer.zero_grad()
            label = label.long()
            if self.args.cuda:
                img, label = img.cuda(), label.cuda()
            output = self.net(img)
            loss = self.criterion(output, label)
            loss.backward()
            self.optimizer.step()

11、執行一段時間,保存net模型

這裏有哪些需要注意的呢?

  1. 多GPU時,.cuda()寫在model、criterion、img、label的後面。
  2. 可是使用一些輸出的控件進行顯示。

再說valid驗證的部分(當然,這部分可有可無)

這裏只說注意事項!!!

  1. 驗證的時候我們的模型是固定參數的了,所以這裏不能寫net.train()了,要寫net.eval()
  2. 驗證的時候因爲模型參數不用變化,所以沒有優化器的設置,不需要損失的反向傳播

6、測試test

這裏又多加了一個test用來測試,爲什麼要說這個呢,這是因爲這裏有很多細節的東西需要說一下。

之前的操作分別對img、label、output做了哪些操作呢?

舉個例子:

img的操作基本爲:

輸入灰度圖(二維[W, H])-->Resize成[1, H, W](爲什麼要將其resize成3維呢,這是因爲net的輸入必須是4維的,在DataLoader中加上BatchSize變成了[B, 1, W, H],正好滿足輸入要求)-->標準化/歸一化(0~1之間)-->在DataLoader中變爲四維[B,1,W,H](其實到這裏就完成了,但是如果想要再次輸出的話)-->Resize成三維[B, W, H]-->*std-->+mean-->然後取batchsize中每一張圖二維[W, H]即可。

label的操作基本爲(如果採用CrossEntropy損失函數):

輸入灰度圖(二維[W, H])-->將灰度圖encode成segmap(如果是像素二分類,則變爲0-1矩陣,分別對應不同的分類)-->在DataLoader中變成了三維[B, W, H](這樣就可以和output四維計算交叉熵損失了,交叉熵損失兩個參數的維度分別是:[n,n-1], 其實到這裏就完了,如果想要再次輸出的話)-->取每一張圖二維[W, H]Decode成0~255的矩陣即可。

output的操作基本爲:

輸出爲4位矩陣[B, NUM_CLASSES, W, H]-->採用torch.argmax(output, dim=1).cpu().numpy()將output各個channel圖片融合,並降維爲[B, W, H]-->取每個batchsize的輸出二維[W, H]decode成0~255的矩陣即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章