如何訓練自制數據集?
首先需要在 mmsegmentation/mmseg/datasets 目錄下創建一個自制數據集的配置文件,以我的蘋果葉片病害分割數據集為例,創建了mmsegmentation/mmseg/datasets/appleleafseg.py
可以看到,這個配置文件主要定義了自制數據集中的 METAINFO , 包括標簽的類別,以及對應的 palette 調色板色彩數值,還定義了原始圖像和標簽圖像的文件后綴,分別是 jpg 和 png,以及設置 reduce_zero_label 屬性 (是否忽略背景)
from mmseg.registry import DATASETS
from .basesegdataset import BaseSegDataset@DATASETS.register_module()
class AppleLeafSegDataset(BaseSegDataset):METAINFO = dict(classes=('background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic', 'Powdery_mildew', 'Rust', 'Scab', 'Health'),palette=[[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]])def __init__(self,img_suffix='.jpg',seg_map_suffix='.png',reduce_zero_label=False,# 因為上面METAINFO已經將背景0作為一種類別并且設置掩碼色彩為0,0,0所以這里的reduce_zero_label需要設置為false**kwargs) -> None:super().__init__(img_suffix=img_suffix,seg_map_suffix=seg_map_suffix,reduce_zero_label=reduce_zero_label,**kwargs)
然后將 AppleLeafSegDataset 添加到 mmseg/datasets/__init__.py
中的__all__
里
__all__ = ['BaseSegDataset', 'BioMedical3DRandomCrop', 'BioMedical3DRandomFlip','CityscapesDataset', 'PascalVOCDataset', 'ADE20KDataset','PascalContextDataset', 'PascalContextDataset59', 'ChaseDB1Dataset','DRIVEDataset', 'HRFDataset', 'STAREDataset', 'DarkZurichDataset','NightDrivingDataset', 'COCOStuffDataset', 'LoveDADataset','MultiImageMixDataset', 'iSAIDDataset', 'ISPRSDataset', 'PotsdamDataset','LoadAnnotations', 'RandomCrop', 'SegRescale', 'PhotoMetricDistortion','RandomRotate', 'AdjustGamma', 'CLAHE', 'Rerange', 'RGB2Gray','RandomCutOut', 'RandomMosaic', 'PackSegInputs', 'ResizeToMultiple','LoadImageFromNDArray', 'LoadBiomedicalImageFromFile','LoadBiomedicalAnnotation', 'LoadBiomedicalData', 'GenerateEdge','DecathlonDataset', 'LIPDataset', 'ResizeShortestEdge','BioMedicalGaussianNoise', 'BioMedicalGaussianBlur','BioMedicalRandomGamma', 'BioMedical3DPad', 'RandomRotFlip','SynapseDataset', 'REFUGEDataset', 'MapillaryDataset_v1','MapillaryDataset_v2', 'Albu', 'LEVIRCDDataset','LoadMultipleRSImageFromFile', 'LoadSingleRSImageFromFile','ConcatCDInput', 'BaseCDDataset', 'DSDLSegDataset', 'BDD100KDataset','NYUDataset', 'HSIDrive20Dataset', 'AppleLeafSegDataset'
]
接下來,需要在 mmsegmentation/mmseg/utils/class_names.py 中補充數據集元信息
我的蘋果樹葉病害數據集相關片段如下:
def appleleafdiseases_classes():"""BDD100K class names for external use(the class name is compatible withCityscapes )."""return ['background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic','Powdery_mildew', 'Rust', 'Scab', 'Health']def appleleafdiseases_palette():"""bdd100k palette for external use(same with cityscapes)"""return [[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]]dataset_aliases = {'cityscapes': ['cityscapes'],'ade': ['ade', 'ade20k'],'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'],'pcontext': ['pcontext', 'pascal_context', 'voc2010'],'loveda': ['loveda'],'potsdam': ['potsdam'],'vaihingen': ['vaihingen'],'cocostuff': ['cocostuff', 'cocostuff10k', 'cocostuff164k', 'coco-stuff','coco-stuff10k', 'coco-stuff164k', 'coco_stuff', 'coco_stuff10k','coco_stuff164k'],'isaid': ['isaid', 'iSAID'],'stare': ['stare', 'STARE'],'lip': ['LIP', 'lip'],'mapillary_v1': ['mapillary_v1'],'mapillary_v2': ['mapillary_v2'],'bdd100k': ['bdd100k'],'hsidrive': ['hsidrive', 'HSIDrive', 'HSI-Drive', 'hsidrive20', 'HSIDrive20','HSI-Drive20'],'appleleafdiseases': ['appleleafdiseases']
}
然后,需要在mmsegmentation/configs/_base_/datasets/
目錄下創建一個新的數據集配置文件 mmsegmentation/configs/_base_/datasets/apple.py
這個數據集配置文件代碼如下,可以看到,主要是告訴模型訓練和測試的一些配置信息,包括數據集類和數據集路徑,訓練,測試的pipiline數據增強,不同的dataloader(訓練集,驗證集,測試集),驗證集測試集的評價指標計算。
# dataset settings
dataset_type = 'AppleLeafSegDataset'
data_root = 'AppleLeafSegDataset/' # 自己數據集所在位置
img_scale = (320, 640) # img_scale是指圖像在處理管道中將被調整到的尺寸
crop_size = (160, 320)
train_pipeline = [dict(type='LoadImageFromFile'),dict(type='LoadAnnotations', reduce_zero_label=False), # 不忽略背景dict(type='RandomResize',scale=img_scale,ratio_range=(0.5, 2.0),keep_ratio=True),dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),dict(type='RandomFlip', prob=0.5),dict(type='PhotoMetricDistortion'),dict(type='PackSegInputs')
]
test_pipeline = [dict(type='LoadImageFromFile'),dict(type='Resize', scale=img_scale, keep_ratio=True),# add loading annotation after ``Resize`` because ground truth# does not need to do resize data transformdict(type='LoadAnnotations', reduce_zero_label=False),dict(type='PackSegInputs')
]
img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75]
# 測試時增強 (TTA) 是一種在測試階段使用的數據增強策略。它對同一張圖片應用不同的增強,例如翻轉和縮放,用于模型推理,然后將每個增強后的圖像的預測結果合并,以獲得更準確的預測結果。
tta_pipeline = [dict(type='LoadImageFromFile', backend_args=None),dict(type='TestTimeAug',transforms=[[dict(type='Resize', scale_factor=r, keep_ratio=True)for r in img_ratios],[dict(type='RandomFlip', prob=0., direction='horizontal'),dict(type='RandomFlip', prob=1., direction='horizontal')], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')]])
]
train_dataloader = dict(batch_size=4,num_workers=4,persistent_workers=True,sampler=dict(type='InfiniteSampler', shuffle=True),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='images/training', seg_map_path='annotations/training'),pipeline=train_pipeline))
val_dataloader = dict(batch_size=1,num_workers=4,persistent_workers=True,sampler=dict(type='DefaultSampler', shuffle=False),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='images/validation',seg_map_path='annotations/validation'),pipeline=test_pipeline))
test_dataloader = val_dataloaderval_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'])
test_evaluator = val_evaluator
最后,我們需要創建一個總的配置文件,mmsegmentation/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_appleleafdiseases-320×640.py
這里可以選擇mmsegmentation/configs/目錄下的不同模型進行實驗,這里以unet為例,我創建的這個文件代碼如下:
可以看到,_base_
定義了模型配置,數據集配置,調度策略配置,運行時配置。
然后也定義了裁剪大小,數據預處理。
_base_ = ['../_base_/models/apple_deeplabv3_unet_s5-d16.py', '../_base_/datasets/apple.py','../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py'
]
crop_size = (160, 320)
data_preprocessor = dict(size=crop_size)
model = dict(data_preprocessor=data_preprocessor,test_cfg=dict(crop_size=(160, 320), stride=(85, 85)))
然后,創建一個mmsegmentation/configs/_base_/models/apple_deeplabv3_unet_s5-d16.py
代碼如下, 可以看到定義了數據預處理,模型結構,backbone類型,解碼器頭和輔助解碼器頭:
# model settings
norm_cfg = dict(type='BN', requires_grad=True)
data_preprocessor = dict(type='SegDataPreProcessor',mean=[123.675, 116.28, 103.53],std=[58.395, 57.12, 57.375],bgr_to_rgb=True,pad_val=0,seg_pad_val=255)
model = dict(type='EncoderDecoder',data_preprocessor=data_preprocessor,pretrained=None,backbone=dict(type='UNet',in_channels=3,base_channels=64,num_stages=5,strides=(1, 1, 1, 1, 1),enc_num_convs=(2, 2, 2, 2, 2),dec_num_convs=(2, 2, 2, 2),downsamples=(True, True, True, True),enc_dilations=(1, 1, 1, 1, 1),dec_dilations=(1, 1, 1, 1),with_cp=False,conv_cfg=None,norm_cfg=norm_cfg,act_cfg=dict(type='ReLU'),upsample_cfg=dict(type='InterpConv'),norm_eval=False),decode_head=dict(type='ASPPHead',in_channels=64,in_index=4,channels=16,dilations=(1, 12, 24, 36),dropout_ratio=0.1,num_classes=10,norm_cfg=norm_cfg,align_corners=False,loss_decode=dict(type='LovaszLoss', reduction='none', loss_weight=1.0)),auxiliary_head=dict(type='FCNHead',in_channels=128,in_index=3,channels=64,num_convs=1,concat_input=False,dropout_ratio=0.1,num_classes=10,norm_cfg=norm_cfg,align_corners=False,loss_decode=dict(type='LovaszLoss', reduction='none', loss_weight=0.4)),# model training and testing settingstrain_cfg=dict(),test_cfg=dict(mode='slide', crop_size=128, stride=85))
然后,重新啟動
python setup.py install
pip install -v -e .
開始訓練
python tools/train.py configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_appleleafdiseases-320×640.py --work-dir mmseg_log
如何添加訓練過程中日志所打印的評價指標?
默認的只打印 IoU ,Acc,mIoU,mAcc,aAcc,如果還想打印 Fscore ,Precision, Recall,mFscore ,mPrecision, mRecall,只需要在數據集配置文件中mmsegmentation/configs/_base_/datasets/apple.py
,在這行代碼中添加 mFscore
val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mFscore'])
下面的打印信息如何解讀?
+-------------------+-------+-------+--------+-----------+--------+
| Class | IoU | Acc | Fscore | Precision | Recall |
+-------------------+-------+-------+--------+-----------+--------+
| background | 94.07 | 96.16 | 96.95 | 97.74 | 96.16 |
| Alternaria_Boltch | 58.0 | 72.75 | 73.42 | 74.1 | 72.75 |
| Brown_spot | 64.19 | 82.51 | 78.19 | 74.29 | 82.51 |
| Frogeye_leaf_spot | 44.46 | 55.28 | 61.55 | 69.43 | 55.28 |
| Grey_spot | 53.24 | 67.83 | 69.49 | 71.23 | 67.83 |
| Mosaic | 50.3 | 81.06 | 66.93 | 56.99 | 81.06 |
| Powdery_mildew | 59.52 | 93.42 | 74.63 | 62.13 | 93.42 |
| Rust | 61.07 | 71.43 | 75.83 | 80.82 | 71.43 |
| Scab | 28.49 | 50.39 | 44.35 | 39.61 | 50.39 |
| Health | 84.84 | 91.07 | 91.8 | 92.55 | 91.07 |
+-------------------+-------+-------+--------+-----------+--------+
07/11 18:55:19 - mmengine - INFO - Iter(val) [169/169] aAcc: 94.1500 mIoU: 59.8200 mAcc: 76.1900 mFscore: 73.3100 mPrecision: 71.8900 mRecall: 76.1900 data_time: 0.0196 time: 0.0292
在這個表格中,每個類別的 Acc
表示的是該類別的分類準確率(Accuracy),也稱為像素準確率(PA)。具體來說,Acc
是每個類別中被正確分類的像素數占該類別總像素數的比例。公式如下: Acc = TP TP + FN \text{Acc} = \frac{\text{TP}}{\text{TP} + \text{FN}} Acc=TP+FNTP?其中:
- TP \text{TP} TP 是該類別的真陽性像素數(正確分類為該類別的像素數)。
- FN \text{FN} FN 是該類別的假陰性像素數(實際為該類別但被錯誤分類為其他類別的像素數)。
這個準確率反映了模型在特定類別上的預測準確度,表示模型有多少比例的該類別像素被正確識別。每個類別都有一個單獨的 Acc
值,用于衡量該類別的分類性能。
總結:
- 表格中的
Acc
表示的是每個類別的分類準確率,衡量模型在該類別上的預測準確度。
在 mmsegmentation 訓練模型時,aAcc 和 mAcc 分別表示:
- aAcc(Overall Accuracy or Pixel Accuracy): 表示所有像素分類正確的比例。它是通過將所有類別的像素預測正確的數量除以總的像素數量來計算的。公式如下:
aAcc = ∑ i = 1 N TP i ∑ i = 1 N ( TP i + FP i + FN i + TN i ) \text{aAcc} = \frac{\sum_{i=1}^{N} \text{TP}_i}{\sum_{i=1}^{N} (\text{TP}_i + \text{FP}_i + \text{FN}_i + \text{TN}_i)} aAcc=∑i=1N?(TPi?+FPi?+FNi?+TNi?)∑i=1N?TPi??
其中, TP \text{TP} TP 是真陽性, FP \text{FP} FP 是假陽性, FN \text{FN} FN 是假陰性, TN \text{TN} TN 是真陰性, N N N 是類別數量。
- mAcc(Mean Accuracy): 表示每個類別的平均準確率。它是對每個類別的準確率的平均值。公式如下:
mAcc = 1 N ∑ i = 1 N TP i TP i + FN i \text{mAcc} = \frac{1}{N} \sum_{i=1}^{N} \frac{\text{TP}_i}{\text{TP}_i + \text{FN}_i} mAcc=N1?i=1∑N?TPi?+FNi?TPi??
其中, TP \text{TP} TP 和 FN \text{FN} FN 是每個類別的真陽性和假陰性, N N N 是類別數量。
總結:
- aAcc 表示所有像素分類正確的總體準確率。
- mAcc 表示每個類別的平均準確率。
怎么使用mmsegmentation的tool/test.py
首先看一下命令行參數部分的代碼
def parse_args():parser = argparse.ArgumentParser(description='MMSeg test (and eval) a model')parser.add_argument('config', help='train config file path')parser.add_argument('checkpoint', help='checkpoint file')parser.add_argument('--work-dir', help=('if specified, the evaluation metric results will be dumped into the directory as json'))parser.add_argument('--out', type=str, help='The directory to save output prediction for offline evaluation')parser.add_argument('--show', action='store_true', help='show prediction results')parser.add_argument('--show-dir', help='directory where painted images will be saved. If specified, it will be automatically saved to the work_dir/timestamp/show_dir')parser.add_argument('--wait-time', type=float, default=2, help='the interval of show (s)')parser.add_argument('--cfg-options', nargs='+', action=DictAction, help='override some settings in the used config, the key-value pair in xxx=yyy format will be merged into config file. If the value to be overwritten is a list, it should be like key="[a,b]" or key=a,b It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" Note that the quotation marks are necessary and that no white space is allowed.')parser.add_argument('--launcher', choices=['none', 'pytorch', 'slurm', 'mpi'], default='none', help='job launcher')parser.add_argument('--tta', action='store_true', help='Test time augmentation')parser.add_argument('--local_rank', '--local-rank', type=int, default=0)args = parser.parse_args()if 'LOCAL_RANK' not in os.environ:os.environ['LOCAL_RANK'] = str(args.local_rank)return args
- config:訓練配置文件路徑。
- checkpoint:模型檢查點文件路徑。
- work-dir:指定工作目錄,評估結果會保存為 JSON。
- out:保存預測結果的目錄,用于離線評估。
- show:顯示預測結果。
- show-dir:保存繪制圖像的目錄。
- wait-time:顯示間隔時間(秒)。
- cfg-options:覆蓋配置文件中的一些設置。
- launcher:選擇作業啟動器。
- tta:啟用測試時增強。
- local_rank:本地排名。
這里的 config 是指模型的整體配置文件,而不是訓練結束后保存到 --work-dir 目錄下的配置文件。
最終我執行測試的命令是
python tools/test.py
configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
mmseg_log/iter_20000.pth
--work-dir mmseg_log --out outputs --show-dir outputs
第一個參數是模型配置文件,第二個參數是訓練過程保存的權重文件,這里以最后一次iter保存的為例,–work-dir 會將測試集的評估結果和日志保存到我指定的mmseg_log目錄下,–out和–show-dir我都指定為outputs目錄下,測試結束后,會在outputs目錄下得到測試所得到的結果圖像,一種是灰度圖像,看起來是純黑色的,另一種是可視化的彩色對比圖像,如下圖:
左邊是Ground Truth,右邊是模型預測的結果。
將outputs目錄下模型測試所得到的灰度圖像轉換為偽彩色圖像
下面這段代碼保存為convert_to_pseudo_color.py
,然后執行python convert_to_pseudo_color.py
后就會將outputs目錄下模型測試所得到的灰度圖像轉換為偽彩色圖像并保存到 outputs/Pseudo/
目錄下了
import os
from PIL import Image
import numpy as np# METAINFO
METAINFO = dict(classes=('background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic', 'Powdery_mildew', 'Rust', 'Scab', 'Health'),palette=[[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]]
)# Ensure output directory exists
output_dir = 'outputs/Pseudo/'
os.makedirs(output_dir, exist_ok=True)# Get the palette from METAINFO
palette = METAINFO['palette']
palette = np.array(palette, dtype=np.uint8)def convert_to_pseudo_color(image_path, save_path):"""Convert a grayscale image to pseudo color using the given palette."""gray_image = Image.open(image_path).convert('L')gray_array = np.array(gray_image)color_image = np.zeros((gray_array.shape[0], gray_array.shape[1], 3), dtype=np.uint8)for i in range(len(palette)):color_image[gray_array == i] = palette[i]color_image = Image.fromarray(color_image)color_image.save(save_path)# Process all .png files in the outputs directory
for file_name in os.listdir('outputs'):if file_name.endswith('.png'):file_path = os.path.join('outputs', file_name)save_path = os.path.join(output_dir, file_name)convert_to_pseudo_color(file_path, save_path)print("Pseudo color images have been saved to", output_dir)
怎么打印模型 flops 和參數量?
執行這個命令,第一個參數是模型配置文件,第二個參數–shape是可選項,表示測試的圖像大小
python tools/analysis_tools/get_flops.py
configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
--shape 1024 512
打印輸出的結果如下:
==============================
Compute type: direct: randomly generate a picture
Input shape: (1024, 512)
Flops: 17.917G
Params: 3.718M
==============================
怎么打印模型 fps?
怎么按照epoch訓練,而非iter?
首先我重新編寫了一個../_base_/schedules/schedule_20k_by_epoch.py
可以看到需要將 train_cfg 中的 type 設置為 EpochBasedTrainLoop,下面的默認hooks也需要調整為按epoch方式訓練的
# optimizer
optimizer = dict(type='SGD', lr=0.001, momentum=0.9, weight_decay=0.0005)
optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None)# learning policy
param_scheduler = [dict(type='PolyLR',eta_min=1e-4,power=0.9,begin=0,end=200,by_epoch=True)
]# training schedule for 20 epochs
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=200, val_interval=1)
val_cfg = dict(type='ValLoop')
test_cfg = dict(type='TestLoop')default_hooks = dict(timer=dict(type='IterTimerHook'),logger=dict(type='LoggerHook', interval=1, log_metric_by_epoch=True),param_scheduler=dict(type='ParamSchedulerHook'),checkpoint=dict(type='CheckpointHook', by_epoch=True, interval=20),sampler_seed=dict(type='DistSamplerSeedHook'),visualization=dict(type='SegVisualizationHook'))
然后需要修改整體配置文件mmsegmentation/configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
_base_ = ['../_base_/models/apple_segformer_mit-b0.py', '../_base_/datasets/apple.py','../_base_/default_runtime.py', '../_base_/schedules/schedule_20k_by_epoch.py'
]
我將原先的../_base_/schedules/schedule_20k.py
替換成了重新編寫的../_base_/schedules/schedule_20k_by_epoch.py
然后還需要將整體配置文件中的param_scheduler進行修改,如下所示,前5個epoch按照LinearLR進行學習率調整,5-200按照PolyLR
param_scheduler = [dict(type='LinearLR', start_factor=0.1, by_epoch=True, begin=0, end=5),dict(type='PolyLR',eta_min=0.0,power=1.0,begin=5,end=200,by_epoch=True,)
]
然后我執行訓練命令,發現訓練期間,log所打印的仍然是按 iter 訓練的信息,問題是../_base_/default_runtime.py
的代碼有覆蓋,
需要將下面這行代碼修改
log_processor = dict(by_epoch=True) # 修改為按 epoch 打印日志
此時遇到了第二個問題,雖然按照epoch打印了,但是在第一個 epoch 陷入了無限循環,解決辦法是需要修改數據集配置文件../_base_/datasets/apple.py
,需要將下面這一行代碼修改為
sampler=dict(type='DefaultSampler', shuffle=True), # 更改為 DefaultSampler
原先的 InfiniteSampler 導致無限循環
運行segmenter遇到的問題
需要升級 pytorch版本到 1.13.1 cuda117,然后我在虛擬環境升級之后,遇到mmcv版本不匹配的問題,解決辦法是卸載 mmcv,然后安裝mmcv 次最新版,問題得到解決。