一、導(dǎo)讀
比賽名稱:Google Research - Identify Contrails to Reduce Global Warming
https://www.kaggle.com/competitions/google-research-identify-contrails-reduce-global-warming
訓(xùn)練 ML 模型以識別衛(wèi)星圖像中的尾跡
比賽類型:計算機(jī)視覺、語義分割
二、比賽背景
Contrails 是“凝結(jié)軌跡”的縮寫,是在飛機(jī)發(fā)動機(jī)排氣中形成的線狀冰晶云,由飛機(jī)飛過大氣中的超潮濕區(qū)域時產(chǎn)生。持續(xù)的尾跡對全球變暖的貢獻(xiàn)與它們?yōu)轱w行所燃燒的燃料一樣多。
凝結(jié)尾跡占人類造成的全球變暖的大約 1%,使用衛(wèi)星圖像的目的是確認(rèn)已有的模型的預(yù)測效果。凝結(jié)尾跡是飛機(jī)發(fā)動機(jī)排氣中形成的冰晶云。它們可以通過在大氣中吸收熱量來促進(jìn)全球變暖。研究人員已經(jīng)開發(fā)出模型來預(yù)測凝結(jié)尾跡何時形成以及它們將導(dǎo)致多少變暖。但是,他們需要使用衛(wèi)星圖像來驗證這些模型。
三、比賽任務(wù)
在本次比賽中,您將使用地球靜止衛(wèi)星圖像來識別航空軌跡。原始衛(wèi)星圖像是從GOES-16 Advanced Baseline Imager (ABI)獲得的,它在Google Cloud Storage上公開可用。
- 軌跡必須包含至少 10 個像素
- 軌跡必須至少比寬度長 3 倍
- 軌跡應(yīng)至少在兩個圖像中可見
四、比賽數(shù)據(jù)
- train/ - 訓(xùn)練集;每個文件夾代表一個record_id
- validation/ 與訓(xùn)練集相同,沒有單獨(dú)的標(biāo)簽注釋
- test/ - 測試集
- sample_submission.csv - 格式正確的樣本提交文件
五、評價指標(biāo)
為了減小提交文件的大小,我們的指標(biāo)對像素值使用游程編碼。評價指標(biāo)為 Dice coefficient:
2
?
∣
X
∩
Y
∣
∣
X
∣
+
∣
Y
∣
\frac{2 * |X \cap Y|}{|X| + |Y|}
∣X∣+∣Y∣2?∣X∩Y∣?
賽題是一個典型語義分割比賽,需要構(gòu)建語義分割的模型。相比與常規(guī)的語義分割比賽,本次比賽有兩個難點(diǎn):
- 比賽數(shù)據(jù)集比較大,450GB
- 包含時序圖片,并且標(biāo)簽和時序相關(guān)
六、Baseline
6.1 Training part
import sys
sys.path.append("../input/pretrained-models-pytorch")
sys.path.append("../input/efficientnet-pytorch")
sys.path.append("/kaggle/input/smp-github/segmentation_models.pytorch-master")
sys.path.append("/kaggle/input/timm-pretrained-resnest/resnest/")
import segmentation_models_pytorch as smp
具體來說,代碼做了以下幾個操作:
- 導(dǎo)入 sys 模塊,用于添加新的路徑到 Python 搜索路徑中。
- 使用
sys.path.append
將 “…/input/pretrained-models-pytorch”、“…/input/efficientnet-pytorch”、“/kaggle/input/smp-github/segmentation_models.pytorch-master” 和 “/kaggle/input/timm-pretrained-resnest/resnest/” 這四個路徑添加到 Python 搜索路徑中。 - 導(dǎo)入了
segmentation_models_pytorch
模塊,并使用別名smp
。
通過以上導(dǎo)入操作,你可以使用 smp
這個別名來調(diào)用 segmentation_models_pytorch 庫中的函數(shù)和類,例如圖像分割模型。
這樣做的目的是為了方便在 Kaggle 環(huán)境中使用預(yù)訓(xùn)練的 PyTorch 模型和相關(guān)的圖像分割工具,以便更輕松地進(jìn)行圖像分割任務(wù)的開發(fā)和實驗。
%%writefile config.yaml
data_path: "/kaggle/input/contrails-images-ash-color"
output_dir: "models"
folds:
n_splits: 4
random_state: 42
train_folds: [0, 1, 2, 3]
seed: 42
train_bs: 48
valid_bs: 128
workers: 2
progress_bar_refresh_rate: 1
early_stop:
monitor: "val_loss"
mode: "min"
patience: 999
verbose: 1
trainer:
max_epochs: 20
min_epochs: 20
enable_progress_bar: True
precision: "16-mixed"
devices: 2
model:
seg_model: "Unet"
encoder_name: "timm-resnest26d"
loss_smooth: 1.0
image_size: 384
optimizer_params:
lr: 0.0005
weight_decay: 0.0
scheduler:
name: "cosine_with_hard_restarts_schedule_with_warmup"
params:
cosine_with_hard_restarts_schedule_with_warmup:
num_warmup_steps: 350
num_training_steps: 3150
num_cycles: 1
這段代碼是一個 YAML 格式的配置文件,用于配置一個圖像分割任務(wù)的參數(shù)。YAML 是一種簡單的數(shù)據(jù)序列化語言,用于配置和存儲數(shù)據(jù)。
這份配置文件中包含了以下內(nèi)容:
- 數(shù)據(jù)路徑和輸出目錄:定義了數(shù)據(jù)集的路徑和輸出模型的目錄。
- 交叉驗證的折數(shù):
folds
部分指定了交叉驗證的折數(shù)和隨機(jī)種子,以便將數(shù)據(jù)集劃分為訓(xùn)練集和驗證集。 - 訓(xùn)練和驗證的批次大?。?code>train_bs 和
valid_bs
分別指定了訓(xùn)練和驗證時的批次大小。 - 訓(xùn)練的其他參數(shù):包括隨機(jī)種子
seed
、工作線程數(shù)量workers
、進(jìn)度條刷新率progress_bar_refresh_rate
等。 - 提前停止策略:
early_stop
部分指定了提前停止的相關(guān)參數(shù),例如監(jiān)測的指標(biāo)、模式(最小化或最大化)、耐心值等。 - 訓(xùn)練器(Trainer)參數(shù):包括最大訓(xùn)練周期數(shù)
max_epochs
、最小訓(xùn)練周期數(shù)min_epochs
、是否啟用進(jìn)度條等。 - 模型參數(shù):
model
部分定義了圖像分割模型的相關(guān)參數(shù),如分割模型的類型seg_model
、編碼器的名稱encoder_name
、圖像大小image_size
、優(yōu)化器參數(shù)等。
這樣的配置文件可以讓你在運(yùn)行圖像分割任務(wù)時輕松地修改參數(shù)和配置,以便快速嘗試不同的設(shè)置和調(diào)整超參數(shù),提高模型性能和訓(xùn)練效率。
# Dataset
import torch
import numpy as np
import torchvision.transforms as T
class ContrailsDataset(torch.utils.data.Dataset):
def __init__(self, df, image_size=256, train=True):
self.df = df
self.trn = train
self.normalize_image = T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
self.image_size = image_size
if image_size != 256:
self.resize_image = T.transforms.Resize(image_size)
def __getitem__(self, index):
row = self.df.iloc[index]
con_path = row.path
con = np.load(str(con_path))
img = con[..., :-1]
label = con[..., -1]
label = torch.tensor(label)
img = torch.tensor(np.reshape(img, (256, 256, 3))).to(torch.float32).permute(2, 0, 1)
if self.image_size != 256:
img = self.resize_image(img)
img = self.normalize_image(img)
return img.float(), label.float()
def __len__(self):
return len(self.df)
這段代碼定義了一個 PyTorch 數(shù)據(jù)集類 ContrailsDataset
,用于加載和處理圖像分割任務(wù)的數(shù)據(jù)。
數(shù)據(jù)集類的主要功能包括:
- 初始化:在初始化過程中,數(shù)據(jù)集類接收一個數(shù)據(jù)幀(DataFrame)
df
,以及一個布爾值train
,用于標(biāo)識數(shù)據(jù)集是用于訓(xùn)練還是驗證。同時,它也接收一個整數(shù)image_size
,表示圖像的大小,若該值不等于 256,則會使用T.transforms.Resize
將圖像調(diào)整為指定大小。 -
__getitem__
方法:這是數(shù)據(jù)集類的核心方法,在使用索引來獲取數(shù)據(jù)樣本時會調(diào)用。在這個方法中,根據(jù)索引獲取數(shù)據(jù)幀中的一行,從中提取出圖像和標(biāo)簽,并對其進(jìn)行處理。具體地,它會將讀取的 numpy 數(shù)組轉(zhuǎn)換為 PyTorch 張量,并進(jìn)行大小和通道維度的調(diào)整,最后返回處理后的圖像和標(biāo)簽。 -
__len__
方法:這個方法返回數(shù)據(jù)集的樣本數(shù)量,以便在訓(xùn)練和驗證時知道數(shù)據(jù)集的總樣本數(shù)。
該數(shù)據(jù)集類適用于加載存儲在 numpy 格式中的圖像和標(biāo)簽數(shù)據(jù),并將其轉(zhuǎn)換為 PyTorch 張量,供神經(jīng)網(wǎng)絡(luò)模型使用。注意,在使用該數(shù)據(jù)集類之前,你需要根據(jù)實際數(shù)據(jù)的存儲方式和結(jié)構(gòu)來適配數(shù)據(jù)幀 df
,以確保正確讀取圖像和標(biāo)簽數(shù)據(jù)。
self.normalize_image
是一個 torchvision 的數(shù)據(jù)轉(zhuǎn)換(transform),用于對圖像數(shù)據(jù)進(jìn)行歸一化操作。在深度學(xué)習(xí)中,歸一化是一個重要的預(yù)處理步驟,可以將圖像的像素值縮放到特定的范圍,以便更好地訓(xùn)練模型并提高模型的收斂性和穩(wěn)定性。
在 torchvision 中,T.Normalize(mean, std)
是一個常用的數(shù)據(jù)轉(zhuǎn)換,它將輸入的圖像數(shù)據(jù)進(jìn)行歸一化。它接受兩個參數(shù):
-
mean
:這是一個包含三個元素的元組或列表,表示圖像數(shù)據(jù)在每個通道上的均值。通常,這些均值是在大規(guī)模圖像數(shù)據(jù)集上計算得到的。在這里,(0.485, 0.456, 0.406) 是對應(yīng)于 ImageNet 數(shù)據(jù)集的 RGB 通道均值。 -
std
:這也是一個包含三個元素的元組或列表,表示圖像數(shù)據(jù)在每個通道上的標(biāo)準(zhǔn)差。同樣,這些標(biāo)準(zhǔn)差也是在大規(guī)模圖像數(shù)據(jù)集上計算得到的。在這里,(0.229, 0.224, 0.225) 是對應(yīng)于 ImageNet 數(shù)據(jù)集的 RGB 通道標(biāo)準(zhǔn)差。
T.Normalize
的作用是將圖像數(shù)據(jù)的每個通道減去均值,然后除以標(biāo)準(zhǔn)差,這樣處理后的圖像數(shù)據(jù)會具有零均值和單位方差,從而使數(shù)據(jù)的分布更穩(wěn)定。
在 ContrailsDataset
類中,self.normalize_image
這個數(shù)據(jù)轉(zhuǎn)換被用于對圖像數(shù)據(jù)進(jìn)行歸一化處理。在 __getitem__
方法中,將加載的圖像數(shù)據(jù)轉(zhuǎn)換為 PyTorch 張量后,會應(yīng)用 self.normalize_image
來進(jìn)行歸一化處理,以便更好地輸入神經(jīng)網(wǎng)絡(luò)模型。這樣做可以有效地將數(shù)據(jù)縮放到合適的范圍,以加快訓(xùn)練速度和提高模型性能。
__getitem__
方法的完整實現(xiàn)。該方法用于獲取數(shù)據(jù)集中的一個樣本。
-
row = self.df.iloc[index]
:從數(shù)據(jù)幀df
中根據(jù)索引index
獲取相應(yīng)行的數(shù)據(jù)。 -
con_path = row.path
:從該行數(shù)據(jù)中獲取路徑信息,該路徑指向一個數(shù)據(jù)文件,其中包含圖像數(shù)據(jù)和標(biāo)簽數(shù)據(jù)。 -
con = np.load(str(con_path))
:使用 NumPy 的np.load()
方法加載數(shù)據(jù)文件,將其讀取為一個 NumPy 數(shù)組con
。 -
img = con[..., :-1]
:從con
數(shù)組中獲取圖像部分,...
表示所有維度的索引,:-1
表示除了最后一個維度之外的所有維度。 -
label = con[..., -1]
:從con
數(shù)組中獲取標(biāo)簽部分,...
表示所有維度的索引,-1
表示最后一個維度。 -
label = torch.tensor(label)
:將標(biāo)簽數(shù)據(jù)轉(zhuǎn)換為 PyTorch 張量。 -
img = torch.tensor(np.reshape(img, (256, 256, 3))).to(torch.float32).permute(2, 0, 1)
:將圖像數(shù)據(jù)轉(zhuǎn)換為 PyTorch 張量,并進(jìn)行一系列預(yù)處理操作。np.reshape(img, (256, 256, 3))
將圖像的通道維度移到最后,然后使用torch.tensor()
將其轉(zhuǎn)換為張量,to(torch.float32)
將數(shù)據(jù)類型轉(zhuǎn)換為 float32,最后使用permute(2, 0, 1)
將通道維度移到最前面,使其符合 PyTorch 的張量格式要求。 -
if self.image_size != 256:
:檢查圖像的尺寸是否需要進(jìn)行調(diào)整。 -
img = self.resize_image(img)
:如果需要,將圖像大小調(diào)整為self.image_size
。 -
img = self.normalize_image(img)
:對圖像數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化處理,將像素值縮放到固定的范圍內(nèi),以適應(yīng)模型的輸入要求。 -
return img.float(), label.float()
:返回處理后的圖像和標(biāo)簽作為元組,并將它們轉(zhuǎn)換為 float 類型的張量。
在 PyTorch 中,permute()
是一個張量的操作函數(shù),用于重新排列張量的維度順序。它的作用是改變張量的維度排列,不改變張量中的元素值。
permute()
函數(shù)的輸入?yún)?shù)是一個表示新維度順序的整數(shù)元組。例如,對于一個四維張量 tensor
,可以使用 tensor.permute(0, 2, 3, 1)
來將原先的維度排列 [0, 1, 2, 3]
調(diào)整為 [0, 2, 3, 1]
。
下面是一個示例:
import torch
# 創(chuàng)建一個四維張量
tensor = torch.randn(2, 3, 4, 5)
# 打印原始維度排列
print("Original tensor shape:", tensor.shape) # Output: (2, 3, 4, 5)
# 使用 permute() 調(diào)整維度排列
tensor_permuted = tensor.permute(0, 2, 3, 1)
# 打印調(diào)整后的維度排列
print("Permuted tensor shape:", tensor_permuted.shape) # Output: (2, 4, 5, 3)
在上面的示例中,原始張量的維度排列是 [2, 3, 4, 5]
,使用 tensor.permute(0, 2, 3, 1)
調(diào)整為 [2, 4, 5, 3]
。可以看到,張量的維度順序被重新排列,但張量中的元素值保持不變。permute()
函數(shù)是一種非常便捷的方式來進(jìn)行維度轉(zhuǎn)換,特別是在神經(jīng)網(wǎng)絡(luò)的數(shù)據(jù)處理過程中,經(jīng)常需要調(diào)整張量的維度以適應(yīng)模型的輸入要求。
# Lightning module
import torch
import pytorch_lightning as pl
import segmentation_models_pytorch as smp
from torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau
from torch.optim import AdamW
import torch.nn as nn
from torchmetrics.functional import dice
from transformers import get_cosine_with_hard_restarts_schedule_with_warmup
seg_models = {
"Unet": smp.Unet,
"Unet++": smp.UnetPlusPlus,
"MAnet": smp.MAnet,
"Linknet": smp.Linknet,
"FPN": smp.FPN,
"PSPNet": smp.PSPNet,
"PAN": smp.PAN,
"DeepLabV3": smp.DeepLabV3,
"DeepLabV3+": smp.DeepLabV3Plus,
}
class LightningModule(pl.LightningModule):
def __init__(self, config):
super().__init__()
self.config = config
self.model = model = seg_models[config["seg_model"]](
encoder_name = config["encoder_name"],
encoder_weights = "imagenet",
in_channels = 3,
classes = 1,
activation = None,
)
self.loss_module = smp.losses.DiceLoss(mode="binary", smooth=config["loss_smooth"])
self.val_step_outputs = []
self.val_step_labels = []
def forward(self, batch):
imgs = batch
preds = self.model(imgs)
return preds
def configure_optimizers(self):
optimizer = AdamW(self.parameters(), **self.config["optimizer_params"])
if self.config["scheduler"]["name"] == "CosineAnnealingLR":
scheduler = CosineAnnealingLR(
optimizer,
**self.config["scheduler"]["params"]["CosineAnnealingLR"],
)
lr_scheduler_dict = {"scheduler": scheduler, "interval": "step"}
return {"optimizer": optimizer, "lr_scheduler": lr_scheduler_dict}
elif self.config["scheduler"]["name"] == "ReduceLROnPlateau":
scheduler = ReduceLROnPlateau(
optimizer,
**self.config["scheduler"]["params"]["ReduceLROnPlateau"],
)
lr_scheduler = {"scheduler": scheduler, "monitor": "val_loss"}
return {"optimizer": optimizer, "lr_scheduler": lr_scheduler}
elif self.config["scheduler"]["name"] == "cosine_with_hard_restarts_schedule_with_warmup":
scheduler = get_cosine_with_hard_restarts_schedule_with_warmup(
optimizer,
**self.config["scheduler"]["params"][self.config["scheduler"]["name"]],
)
lr_scheduler_dict = {"scheduler": scheduler, "interval": "step"}
return {"optimizer": optimizer, "lr_scheduler": lr_scheduler_dict}
def training_step(self, batch, batch_idx):
imgs, labels = batch
preds = self.model(imgs)
if self.config["image_size"] != 256:
preds = torch.nn.functional.interpolate(preds, size=256, mode='bilinear')
loss = self.loss_module(preds, labels)
self.log("train_loss", loss, on_step=True, on_epoch=True, prog_bar=True, batch_size=16)
for param_group in self.trainer.optimizers[0].param_groups:
lr = param_group["lr"]
self.log("lr", lr, on_step=True, on_epoch=False, prog_bar=True)
return loss
def validation_step(self, batch, batch_idx):
imgs, labels = batch
preds = self.model(imgs)
if self.config["image_size"] != 256:
preds = torch.nn.functional.interpolate(preds, size=256, mode='bilinear')
loss = self.loss_module(preds, labels)
self.log("val_loss", loss, on_step=False, on_epoch=True, prog_bar=True)
self.val_step_outputs.append(preds)
self.val_step_labels.append(labels)
def on_validation_epoch_end(self):
all_preds = torch.cat(self.val_step_outputs)
all_labels = torch.cat(self.val_step_labels)
all_preds = torch.sigmoid(all_preds)
self.val_step_outputs.clear()
self.val_step_labels.clear()
val_dice = dice(all_preds, all_labels.long())
self.log("val_dice", val_dice, on_step=False, on_epoch=True, prog_bar=True)
if self.trainer.global_rank == 0:
print(f"\nEpoch: {self.current_epoch}", flush=True)
這段代碼定義了一個 PyTorch Lightning 模塊 LightningModule
,用于訓(xùn)練圖像分割模型。
主要功能包括:
- 初始化:在初始化過程中,接收一個配置參數(shù)
config
,用于配置模型的參數(shù)和優(yōu)化器。 - 構(gòu)建圖像分割模型:根據(jù)配置中的
seg_model
和encoder_name
,從seg_models
字典中選擇合適的圖像分割模型,并初始化該模型。 - 定義損失函數(shù):使用
smp.losses.DiceLoss
作為損失函數(shù),并根據(jù)配置中的loss_smooth
參數(shù)初始化 Dice Loss。 - 前向傳播:在
forward
方法中,接收一個批次的圖像數(shù)據(jù)batch
,將其輸入模型中進(jìn)行前向傳播,并返回預(yù)測結(jié)果preds
。 - 配置優(yōu)化器和學(xué)習(xí)率調(diào)度器:通過
configure_optimizers
方法配置優(yōu)化器和學(xué)習(xí)率調(diào)度器。根據(jù)配置中的scheduler
,選擇對應(yīng)的學(xué)習(xí)率調(diào)度器,例如CosineAnnealingLR
、ReduceLROnPlateau
或cosine_with_hard_restarts_schedule_with_warmup
。 - 訓(xùn)練步驟:在
training_step
方法中,接收一個批次的圖像數(shù)據(jù)batch
和批次索引batch_idx
,執(zhí)行模型的訓(xùn)練步驟。計算模型的預(yù)測結(jié)果preds
和損失函數(shù)的值loss
,并輸出訓(xùn)練的損失值和學(xué)習(xí)率。 - 驗證步驟:在
validation_step
方法中,接收一個批次的圖像數(shù)據(jù)batch
和批次索引batch_idx
,執(zhí)行模型的驗證步驟。計算模型的預(yù)測結(jié)果preds
和損失函數(shù)的值loss
,并輸出驗證的損失值。 - 驗證輪結(jié)束時操作:在
on_validation_epoch_end
方法中,進(jìn)行每個驗證輪結(jié)束后的操作。計算 Dice 指標(biāo),并打印當(dāng)前的訓(xùn)練輪數(shù)。
該 LightningModule
類為圖像分割任務(wù)提供了整體的訓(xùn)練和驗證流程,包括模型的初始化、損失函數(shù)的定義、前向傳播、優(yōu)化器和學(xué)習(xí)率調(diào)度器的配置,以及訓(xùn)練和驗證的具體步驟。它是 PyTorch Lightning 框架中的一個核心組件,可以大大簡化訓(xùn)練過程,并提供了豐富的功能和回調(diào)函數(shù)來定制化訓(xùn)練過程。
初始化的步驟:
-
__init__
方法:初始化函數(shù),在創(chuàng)建類實例時被調(diào)用,用于定義模型的結(jié)構(gòu)和其他初始化操作。-
config
: 是一個字典,包含了模型的配置參數(shù)。 -
model
: 初始化語義分割模型,通過seg_models
字典中指定的seg_model
和encoder_name
來選擇特定的語義分割模型。 -
loss_module
: 定義了用于計算損失的 DiceLoss,參數(shù)mode="binary"
表示計算二值分割的 Dice Loss。 -
val_step_outputs
和val_step_labels
: 這是用于保存驗證步驟中的模型輸出和真實標(biāo)簽的列表,以便在validation_step
方法中使用和跟蹤驗證指標(biāo)。
-
接下來,這個 LightningModule
類還包含其他幾個方法,用于實現(xiàn)模型的前向傳播、優(yōu)化器和學(xué)習(xí)率調(diào)度器的配置,以及訓(xùn)練和驗證步驟的定義。
前向傳播過程:
forward
方法定義了模型的前向傳播過程。它接收一個批次的輸入數(shù)據(jù) batch
,其中 batch
是一個包含圖像數(shù)據(jù)的張量。在這里,imgs
表示輸入的圖像數(shù)據(jù)。
然后,self.model
表示定義的語義分割模型,根據(jù) config["seg_model"]
和 config["encoder_name"]
來選擇相應(yīng)的模型結(jié)構(gòu)。self.model
接收 imgs
作為輸入,進(jìn)行前向傳播,得到預(yù)測的語義分割結(jié)果 preds
。
最后,forward
方法返回預(yù)測結(jié)果 preds
,這個結(jié)果將在訓(xùn)練過程中用于計算損失和優(yōu)化模型。
配置優(yōu)化器和學(xué)習(xí)率調(diào)度器:
在這個方法中,首先根據(jù)配置參數(shù) self.config["optimizer_params"]
創(chuàng)建一個 AdamW 優(yōu)化器對象 optimizer
,其中使用了模型的參數(shù) self.parameters()
。
然后,根據(jù)配置參數(shù) self.config["scheduler"]["name"]
來選擇相應(yīng)的學(xué)習(xí)率調(diào)度器。
- 如果選擇的調(diào)度器是
CosineAnnealingLR
,則創(chuàng)建一個 CosineAnnealingLR 調(diào)度器對象scheduler
,并使用self.config["scheduler"]["params"]["CosineAnnealingLR"]
中的參數(shù)來配置調(diào)度器。 - 如果選擇的調(diào)度器是
ReduceLROnPlateau
,則創(chuàng)建一個 ReduceLROnPlateau 調(diào)度器對象scheduler
,并使用self.config["scheduler"]["params"]["ReduceLROnPlateau"]
中的參數(shù)來配置調(diào)度器。 - 如果選擇的調(diào)度器是
cosine_with_hard_restarts_schedule_with_warmup
,則創(chuàng)建一個使用get_cosine_with_hard_restarts_schedule_with_warmup
函數(shù)生成的調(diào)度器對象scheduler
,并使用self.config["scheduler"]["params"][self.config["scheduler"]["name"]]
中的參數(shù)來配置調(diào)度器。
最后,根據(jù)選擇的調(diào)度器返回一個字典,其中包含了優(yōu)化器和學(xué)習(xí)率調(diào)度器的配置信息。這樣,在訓(xùn)練過程中,Lightning 就會自動地根據(jù)這些配置來進(jìn)行優(yōu)化和學(xué)習(xí)率調(diào)整。
在訓(xùn)練集上的一個前向傳播和損失計算的步驟:
這個方法接收一個批次的輸入數(shù)據(jù) batch
和批次的索引 batch_idx
。
首先,從輸入批次 batch
中解包得到圖像數(shù)據(jù) imgs
和對應(yīng)的標(biāo)簽 labels
。然后,通過 self.model
對圖像數(shù)據(jù)進(jìn)行前向傳播,得到預(yù)測的語義分割結(jié)果 preds
。
如果配置中的 image_size
不等于 256,那么會對預(yù)測結(jié)果 preds
進(jìn)行插值,將其調(diào)整為大小為 256x256 的分辨率。
接著,使用 self.loss_module
計算預(yù)測結(jié)果 preds
和真實標(biāo)簽 labels
之間的 Dice Loss。這里使用 Dice Loss 作為損失函數(shù)來度量預(yù)測結(jié)果和真實標(biāo)簽之間的相似度。
然后,通過 self.log
方法記錄訓(xùn)練損失 train_loss
,并設(shè)置 on_step=True
和 on_epoch=True
,這樣在訓(xùn)練過程中會每個步驟和每個 epoch 都打印損失,并顯示在進(jìn)度條中。
接下來,獲取當(dāng)前優(yōu)化器的學(xué)習(xí)率 lr
,并使用 self.log
方法記錄學(xué)習(xí)率 lr
,設(shè)置 on_step=True
和 on_epoch=False
,這樣在訓(xùn)練過程中會每個步驟打印學(xué)習(xí)率,并顯示在進(jìn)度條中。
最后,返回計算得到的損失值 loss
,這個值將用于進(jìn)行反向傳播和模型的優(yōu)化。
模型在驗證集上的一個前向傳播和損失計算的步驟:
這個方法接收一個批次的輸入數(shù)據(jù) batch
和批次的索引 batch_idx
。
首先,從輸入批次 batch
中解包得到圖像數(shù)據(jù) imgs
和對應(yīng)的標(biāo)簽 labels
。然后,通過 self.model
對圖像數(shù)據(jù)進(jìn)行前向傳播,得到預(yù)測的語義分割結(jié)果 preds
。
如果配置中的 image_size
不等于 256,那么會對預(yù)測結(jié)果 preds
進(jìn)行插值,將其調(diào)整為大小為 256x256 的分辨率。
接著,使用 self.loss_module
計算預(yù)測結(jié)果 preds
和真實標(biāo)簽 labels
之間的 Dice Loss。這里使用 Dice Loss 作為損失函數(shù)來度量預(yù)測結(jié)果和真實標(biāo)簽之間的相似度。
然后,通過 self.log
方法記錄驗證損失 val_loss
,設(shè)置 on_step=False
和 on_epoch=True
,這樣在每個 epoch 結(jié)束時打印驗證損失,并顯示在進(jìn)度條中。
接下來,將預(yù)測結(jié)果 preds
和標(biāo)簽 labels
添加到列表 self.val_step_outputs
和 self.val_step_labels
中,這樣在每個 epoch 結(jié)束時可以使用這些數(shù)據(jù)來計算整個驗證集上的評估指標(biāo)。
注意,這里沒有返回任何值,因為在 Lightning 中,在驗證階段只需要計算驗證指標(biāo),不需要進(jìn)行反向傳播和優(yōu)化,因此不需要返回?fù)p失值。
完成了一個完整的驗證階段后進(jìn)行的操作:
該方法在每個 epoch 結(jié)束時被調(diào)用,用于對整個驗證集的預(yù)測結(jié)果進(jìn)行評估和記錄。
首先,將所有驗證步驟中得到的預(yù)測結(jié)果 self.val_step_outputs
和標(biāo)簽 self.val_step_labels
拼接起來,形成一個完整的預(yù)測結(jié)果和對應(yīng)的標(biāo)簽,分別存儲在 all_preds
和 all_labels
中。
然后,對預(yù)測結(jié)果 all_preds
進(jìn)行 sigmoid 函數(shù)的轉(zhuǎn)換,將其轉(zhuǎn)換為概率值在 0 到 1 之間。
接著,使用 torchmetrics.functional.dice
函數(shù)計算預(yù)測結(jié)果 all_preds
和真實標(biāo)簽 all_labels
之間的 Dice 系數(shù)。Dice 系數(shù)是用于評估語義分割任務(wù)的一種指標(biāo),用于衡量預(yù)測結(jié)果與真實標(biāo)簽之間的相似度。
接下來,通過 self.log
方法記錄驗證集上的 Dice 系數(shù) val_dice
,設(shè)置 on_step=False
和 on_epoch=True
,這樣在每個 epoch 結(jié)束時打印驗證集上的 Dice 系數(shù),并顯示在進(jìn)度條中。
最后,如果當(dāng)前進(jìn)程是全局排名為 0 的進(jìn)程(通常是主進(jìn)程),則打印當(dāng)前 epoch 的信息,例如顯示當(dāng)前 epoch 的編號,這里使用 self.current_epoch
來獲取當(dāng)前的 epoch 編號,并使用 print
函數(shù)打印該信息。
# Actual training
import warnings
warnings.filterwarnings("ignore")
import gc
import os
import torch
import yaml
import pandas as pd
import pytorch_lightning as pl
from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping, TQDMProgressBar
from torch.utils.data import DataLoader
from sklearn.model_selection import KFold
from pytorch_lightning.loggers import CSVLogger
torch.set_float32_matmul_precision("medium")
with open("config.yaml", "r") as file_obj:
config = yaml.safe_load(file_obj)
pl.seed_everything(config["seed"])
gc.enable()
contrails = os.path.join(config["data_path"], "contrails/")
train_path = os.path.join(config["data_path"], "train_df.csv")
valid_path = os.path.join(config["data_path"], "valid_df.csv")
train_df = pd.read_csv(train_path)
valid_df = pd.read_csv(valid_path)
train_df["path"] = contrails + train_df["record_id"].astype(str) + ".npy"
valid_df["path"] = contrails + valid_df["record_id"].astype(str) + ".npy"
df = pd.concat([train_df, valid_df]).reset_index()
Fold = KFold(shuffle=True, **config["folds"])
for n, (trn_index, val_index) in enumerate(Fold.split(df)):
df.loc[val_index, "kfold"] = int(n)
df["kfold"] = df["kfold"].astype(int)
for fold in config["train_folds"]:
print(f"\n###### Fold {fold}")
trn_df = df[df.kfold != fold].reset_index(drop=True)
vld_df = df[df.kfold == fold].reset_index(drop=True)
dataset_train = ContrailsDataset(trn_df, config["model"]["image_size"], train=True)
dataset_validation = ContrailsDataset(vld_df, config["model"]["image_size"], train=False)
data_loader_train = DataLoader(
dataset_train,
batch_size=config["train_bs"],
shuffle=True,
num_workers=config["workers"],
)
data_loader_validation = DataLoader(
dataset_validation,
batch_size=config["valid_bs"],
shuffle=False,
num_workers=config["workers"],
)
checkpoint_callback = ModelCheckpoint(
save_weights_only=True,
monitor="val_dice",
dirpath=config["output_dir"],
mode="max",
filename=f"model-f{fold}-{{val_dice:.4f}}",
save_top_k=1,
verbose=1,
)
progress_bar_callback = TQDMProgressBar(
refresh_rate=config["progress_bar_refresh_rate"]
)
early_stop_callback = EarlyStopping(**config["early_stop"])
trainer = pl.Trainer(
callbacks=[checkpoint_callback, early_stop_callback, progress_bar_callback],
logger=CSVLogger(save_dir=f'logs_f{fold}/'),
**config["trainer"],
)
model = LightningModule(config["model"])
trainer.fit(model, data_loader_train, data_loader_validation)
del (
dataset_train,
dataset_validation,
data_loader_train,
data_loader_validation,
model,
trainer,
checkpoint_callback,
progress_bar_callback,
early_stop_callback,
)
torch.cuda.empty_cache()
gc.collect()
這段代碼實際上是執(zhí)行圖像分割模型的訓(xùn)練過程。它使用了 PyTorch Lightning 框架來簡化訓(xùn)練過程,并采用 K 折交叉驗證的方式來訓(xùn)練多個模型。
主要步驟如下:
- 讀取配置文件:首先,代碼通過讀取 “config.yaml” 配置文件來加載訓(xùn)練的參數(shù)設(shè)置。
- 數(shù)據(jù)準(zhǔn)備:代碼讀取數(shù)據(jù)集文件并構(gòu)建訓(xùn)練集和驗證集的數(shù)據(jù)幀(DataFrame)。然后,根據(jù) K 折交叉驗證的要求,將數(shù)據(jù)劃分為 K 份,其中 (K-1) 份作為訓(xùn)練集,1 份作為驗證集。
- 開始訓(xùn)練:通過
for
循環(huán),對每個折(fold)進(jìn)行訓(xùn)練。 - 構(gòu)建數(shù)據(jù)集和數(shù)據(jù)加載器:對于每個折,代碼通過構(gòu)建
ContrailsDataset
數(shù)據(jù)集類和數(shù)據(jù)加載器來加載訓(xùn)練和驗證數(shù)據(jù)。將數(shù)據(jù)集傳遞給數(shù)據(jù)加載器,以便進(jìn)行批量數(shù)據(jù)加載。 - 配置回調(diào)函數(shù):為訓(xùn)練過程配置回調(diào)函數(shù),包括模型保存回調(diào)、早?;卣{(diào)和進(jìn)度條回調(diào)。這些回調(diào)函數(shù)在訓(xùn)練過程中會根據(jù)設(shè)定的條件執(zhí)行相應(yīng)的操作。
- 配置
pl.Trainer
:通過配置pl.Trainer
類,指定訓(xùn)練過程中的一些設(shè)置,例如使用的 GPU 數(shù)量、最大訓(xùn)練周期數(shù)、最小訓(xùn)練周期數(shù)等。 - 創(chuàng)建
LightningModule
模型:創(chuàng)建一個LightningModule
模型,將配置文件中的參數(shù)傳遞給模型。 - 訓(xùn)練模型:使用
trainer.fit
方法進(jìn)行模型的訓(xùn)練。在訓(xùn)練過程中,模型會自動執(zhí)行前向傳播、反向傳播、優(yōu)化器更新等操作。 - 清理資源:每完成一個折的訓(xùn)練后,代碼會釋放一些資源,如數(shù)據(jù)集、數(shù)據(jù)加載器、模型、回調(diào)函數(shù)等,以便于下一個折的訓(xùn)練。
整個訓(xùn)練過程會持續(xù)多個周期,每個周期(epoch)會對訓(xùn)練集進(jìn)行迭代訓(xùn)練,然后在驗證集上進(jìn)行驗證,并根據(jù)驗證結(jié)果選擇是否早?;虮4婺P汀W罱K,通過多次 K 折交叉驗證,可以得到多個訓(xùn)練好的模型,并從中選擇最好的模型進(jìn)行后續(xù)使用。
import seaborn as sn
import matplotlib.pyplot as plt
for fold in config["train_folds"]:
metrics = pd.read_csv(f"/kaggle/working/logs_f{fold}/lightning_logs/version_0/metrics.csv")
del metrics["step"]
del metrics["lr"]
del metrics["train_loss_step"]
metrics.set_index("epoch", inplace=True)
g = sn.relplot(data=metrics, kind="line")
plt.title(f"Fold {fold}")
plt.gcf().set_size_inches(15, 5)
plt.grid()
plt.show()
這段代碼用于繪制圖像分割模型訓(xùn)練過程中的一些指標(biāo)隨著訓(xùn)練周期的變化情況。它通過讀取每個折(fold)的訓(xùn)練日志文件,提取相應(yīng)的指標(biāo)數(shù)據(jù),并使用 seaborn 和 matplotlib 庫進(jìn)行可視化。
主要步驟如下:
- 通過
for
循環(huán)遍歷每個折(fold)。 - 讀取訓(xùn)練日志:使用
pd.read_csv
讀取每個折訓(xùn)練的日志文件,該日志文件保存了訓(xùn)練過程中的指標(biāo)數(shù)據(jù)。 - 數(shù)據(jù)預(yù)處理:刪除不需要的列,并將 “epoch” 列設(shè)置為數(shù)據(jù)幀的索引,以便后續(xù)繪圖。
- 繪制折(fold)的指標(biāo)曲線:使用
sn.relplot
繪制每個折的指標(biāo)隨著訓(xùn)練周期的變化情況。這里使用了 seaborn 中的relplot
函數(shù)來繪制折線圖。 - 設(shè)置圖像屬性:設(shè)置圖像的標(biāo)題、尺寸和網(wǎng)格等屬性。
- 顯示圖像:使用
plt.show()
顯示繪制好的圖像。
通過以上步驟,代碼將繪制每個折的訓(xùn)練過程中指標(biāo)的變化曲線,以便觀察模型的訓(xùn)練情況、收斂性和性能。這樣的可視化有助于了解訓(xùn)練的進(jìn)展情況,并可以發(fā)現(xiàn)模型是否過擬合或欠擬合,以及在哪些周期達(dá)到了最佳性能等信息。
6.2 Submission part
import warnings
warnings.filterwarnings("ignore")
import gc
import os
import glob
import numpy as np
import pandas as pd
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
import pytorch_lightning as pl
import torchvision.transforms as T
import yaml
這段代碼導(dǎo)入了一系列的 Python 庫和模塊,用于進(jìn)行圖像分割任務(wù)的實驗和開發(fā)。
具體的導(dǎo)入內(nèi)容包括:
-
warnings
:用于忽略警告信息,以便在實驗過程中不顯示警告。 -
gc
:Python 的垃圾回收模塊,用于處理內(nèi)存管理和垃圾回收。 -
os
:用于與操作系統(tǒng)進(jìn)行交互,比如文件路徑的操作和系統(tǒng)命令的執(zhí)行。 -
glob
:用于查找符合特定規(guī)則的文件路徑。 -
numpy
:用于處理數(shù)值計算和數(shù)組操作。 -
pandas
:用于數(shù)據(jù)處理和分析,特別是用于處理結(jié)構(gòu)化數(shù)據(jù),如 DataFrame。 -
torch
:PyTorch 深度學(xué)習(xí)框架的核心模塊。 -
torch.nn
:PyTorch 中的神經(jīng)網(wǎng)絡(luò)模塊,包含各種層和損失函數(shù)。 -
Dataset
和DataLoader
:PyTorch 中用于處理數(shù)據(jù)的模塊,用于加載數(shù)據(jù)集并構(gòu)建數(shù)據(jù)加載器。 -
pytorch_lightning
:PyTorch Lightning 是一個輕量級的 PyTorch 框架擴(kuò)展,用于簡化深度學(xué)習(xí)的訓(xùn)練和開發(fā)流程。 -
torchvision.transforms
:用于定義圖像數(shù)據(jù)的預(yù)處理和數(shù)據(jù)增強(qiáng)的模塊。 -
yaml
:用于讀取和解析 YAML 格式的配置文件。
這些導(dǎo)入語句為后續(xù)的圖像分割任務(wù)實驗提供了必要的基礎(chǔ)庫和模塊,可以方便地進(jìn)行數(shù)據(jù)處理、模型定義、訓(xùn)練和驗證等操作。同時,通過 PyTorch Lightning 的使用,還能進(jìn)一步簡化訓(xùn)練流程,并提供豐富的功能和回調(diào)函數(shù)來進(jìn)行定制化的實驗和調(diào)試。
batch_size = 32
num_workers = 1
THR = 0.5
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
data = '/kaggle/input/google-research-identify-contrails-reduce-global-warming'
data_root = '/kaggle/input/google-research-identify-contrails-reduce-global-warming/test/'
submission = pd.read_csv(os.path.join(data, 'sample_submission.csv'), index_col='record_id')
這段代碼設(shè)置了一些變量和路徑,為進(jìn)行圖像分割任務(wù)的預(yù)測和提交結(jié)果做準(zhǔn)備。
具體的設(shè)置包括:
-
batch_size = 32
:定義每個批次中的樣本數(shù)量。 -
num_workers = 1
:定義數(shù)據(jù)加載器的工作線程數(shù)量。 -
THR = 0.5
:定義一個閾值(threshold),用于在進(jìn)行預(yù)測時對模型輸出進(jìn)行二值化,以得到最終的分割結(jié)果。 -
device
:定義計算設(shè)備,如果可用,則使用 CUDA 加速,否則使用 CPU 進(jìn)行計算。 -
data
:設(shè)置數(shù)據(jù)集的根路徑,此處指向 “/kaggle/input/google-research-identify-contrails-reduce-global-warming”,該路徑可能包含訓(xùn)練集和測試集等數(shù)據(jù)。 -
data_root
:設(shè)置測試集數(shù)據(jù)的路徑,指向 “/kaggle/input/google-research-identify-contrails-reduce-global-warming/test/”,該路徑是測試集數(shù)據(jù)存放的目錄。 -
submission
:讀取測試集的樣本提交文件 “sample_submission.csv”,并將 “record_id” 列作為數(shù)據(jù)幀的索引,該文件用于提交最終的預(yù)測結(jié)果。
通過上述設(shè)置,代碼為后續(xù)的測試數(shù)據(jù)加載、模型預(yù)測和結(jié)果提交做好了準(zhǔn)備。具體的預(yù)測和提交過程可能會在后續(xù)的代碼中進(jìn)行。
filenames = os.listdir(data_root)
test_df = pd.DataFrame(filenames, columns = ['record_id'])
test_df['path'] = data_root + test_df['record_id'].astype(str)
這段代碼用于構(gòu)建測試集的數(shù)據(jù)幀(DataFrame),以便在進(jìn)行圖像分割模型的預(yù)測時使用。
具體步驟如下:
- 使用
os.listdir(data_root)
獲取測試集數(shù)據(jù)目錄data_root
中的所有文件名列表filenames
。os.listdir()
函數(shù)會返回指定目錄下的所有文件和子目錄的名稱。 - 創(chuàng)建一個新的數(shù)據(jù)幀
test_df
,并將filenames
列表作為一列 “record_id” 加入數(shù)據(jù)幀。 - 構(gòu)建 “path” 列:將 “record_id” 列中的每個文件名轉(zhuǎn)換為完整的文件路徑,并添加為 “path” 列。這樣,“path” 列中保存了測試集數(shù)據(jù)文件的完整路徑。
通過以上步驟,代碼將測試集數(shù)據(jù)的文件名和完整路徑保存在數(shù)據(jù)幀 test_df
中,方便后續(xù)加載數(shù)據(jù)和進(jìn)行模型的預(yù)測。每行數(shù)據(jù)表示測試集中的一個樣本,其中 “record_id” 列保存了樣本的文件名,“path” 列保存了樣本的完整文件路徑。
class ContrailsDataset(torch.utils.data.Dataset):
def __init__(self, df, image_size=256, train=True):
self.df = df
self.trn = train
self.df_idx: pd.DataFrame = pd.DataFrame({'idx': os.listdir(f'/kaggle/input/google-research-identify-contrails-reduce-global-warming/test')})
self.normalize_image = T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
self.image_size = image_size
if image_size != 256:
self.resize_image = T.transforms.Resize(image_size)
def read_record(self, directory):
record_data = {}
for x in [
"band_11",
"band_14",
"band_15"
]:
record_data[x] = np.load(os.path.join(directory, x + ".npy"))
return record_data
def normalize_range(self, data, bounds):
"""Maps data to the range [0, 1]."""
return (data - bounds[0]) / (bounds[1] - bounds[0])
def get_false_color(self, record_data):
_T11_BOUNDS = (243, 303)
_CLOUD_TOP_TDIFF_BOUNDS = (-4, 5)
_TDIFF_BOUNDS = (-4, 2)
N_TIMES_BEFORE = 4
r = self.normalize_range(record_data["band_15"] - record_data["band_14"], _TDIFF_BOUNDS)
g = self.normalize_range(record_data["band_14"] - record_data["band_11"], _CLOUD_TOP_TDIFF_BOUNDS)
b = self.normalize_range(record_data["band_14"], _T11_BOUNDS)
false_color = np.clip(np.stack([r, g, b], axis=2), 0, 1)
img = false_color[..., N_TIMES_BEFORE]
return img
def __getitem__(self, index):
row = self.df.iloc[index]
con_path = row.path
data = self.read_record(con_path)
img = self.get_false_color(data)
img = torch.tensor(np.reshape(img, (256, 256, 3))).to(torch.float32).permute(2, 0, 1)
if self.image_size != 256:
img = self.resize_image(img)
img = self.normalize_image(img)
image_id = int(self.df_idx.iloc[index]['idx'])
return img.float(), torch.tensor(image_id)
def __len__(self):
return len(self.df)
這是一個名為 ContrailsDataset
的 PyTorch 數(shù)據(jù)集類,用于處理圖像分割任務(wù)中的數(shù)據(jù)加載和預(yù)處理。
類中包含以下方法:
-
__init__(self, df, image_size=256, train=True)
:初始化方法,用于指定數(shù)據(jù)集的相關(guān)設(shè)置和參數(shù)。df
是包含樣本信息的數(shù)據(jù)幀,image_size
是圖像的尺寸,默認(rèn)為 256,train
是一個布爾值,用于標(biāo)識是否是訓(xùn)練集,如果為 True,則表示是訓(xùn)練集,否則表示是測試集。 -
read_record(self, directory)
:用于從指定目錄directory
讀取記錄數(shù)據(jù)。這個方法從不同文件中加載 “band_11”、“band_14” 和 “band_15” 數(shù)據(jù),然后返回這些數(shù)據(jù)組成的字典。 -
normalize_range(self, data, bounds)
:用于將數(shù)據(jù)映射到指定范圍 [0, 1] 的方法。data
是要映射的數(shù)據(jù),bounds
是目標(biāo)范圍的上下界。 -
get_false_color(self, record_data)
:用于生成偽彩色圖像的方法。該方法從記錄數(shù)據(jù)字典record_data
中獲取 “band_11”、“band_14” 和 “band_15” 數(shù)據(jù),并通過歸一化和組合生成偽彩色圖像。 -
__getitem__(self, index)
:用于獲取數(shù)據(jù)集中特定索引處的數(shù)據(jù)樣本。根據(jù)索引index
,讀取數(shù)據(jù)幀df
中對應(yīng)的樣本信息,調(diào)用read_record
方法讀取記錄數(shù)據(jù),并通過get_false_color
方法生成偽彩色圖像。然后,對圖像進(jìn)行大小調(diào)整、標(biāo)準(zhǔn)化處理,并返回圖像和樣本的 ID。 -
__len__(self)
:用于獲取數(shù)據(jù)集的長度,即樣本的數(shù)量。
通過這個自定義的數(shù)據(jù)集類,可以方便地加載數(shù)據(jù)、對圖像進(jìn)行預(yù)處理,并在訓(xùn)練和預(yù)測過程中使用 PyTorch 的數(shù)據(jù)加載器來加載批次數(shù)據(jù)。這樣,可以方便地將數(shù)據(jù)送入模型進(jìn)行訓(xùn)練和推理。
get_false_color
方法用于生成偽彩色圖像,主要是通過計算不同波段之間的像元值差異來構(gòu)造一張偽彩色圖像。在這里,該方法接受一個包含多個波段數(shù)據(jù)的字典 record_data
作為輸入,然后根據(jù)不同波段之間的范圍進(jìn)行歸一化,構(gòu)造偽彩色圖像并返回。
具體的步驟如下:
-
_T11_BOUNDS
、_CLOUD_TOP_TDIFF_BOUNDS
和_TDIFF_BOUNDS
是用來指定不同波段的范圍。這些值用于將不同波段數(shù)據(jù)映射到 [0, 1] 范圍內(nèi)。 -
N_TIMES_BEFORE
是一個常量,用于指定取偽彩色圖像的哪個時間點(diǎn)。在這里,根據(jù)img = false_color[..., N_TIMES_BEFORE]
選擇取第 N_TIMES_BEFORE 個時間點(diǎn)的偽彩色圖像。 -
r
、g
和b
是分別對應(yīng)于紅、綠、藍(lán)通道的像素值。計算這些通道的方法是通過對不同波段之間的像元值進(jìn)行差異計算,然后將差異值映射到指定的范圍內(nèi)。 - 使用
np.clip
函數(shù)將歸一化后的像素值限制在 [0, 1] 范圍內(nèi),然后通過np.stack
函數(shù)將三個通道的像素值堆疊在一起,構(gòu)成一張偽彩色圖像false_color
。 - 最后,根據(jù)選定的時間點(diǎn)
N_TIMES_BEFORE
,從false_color
中提取出對應(yīng)時間點(diǎn)的偽彩色圖像img
并返回。
這樣,get_false_color
方法可以根據(jù)給定的波段數(shù)據(jù)構(gòu)造一張偽彩色圖像,用于在深度學(xué)習(xí)模型中進(jìn)行處理和訓(xùn)練。
def rle_encode(x, fg_val=1):
"""
Args:
x: numpy array of shape (height, width), 1 - mask, 0 - background
Returns: run length encoding as list
"""
dots = np.where(
x.T.flatten() == fg_val)[0] # .T sets Fortran order down-then-right
run_lengths = []
prev = -2
for b in dots:
if b > prev + 1:
run_lengths.extend((b + 1, 0))
run_lengths[-1] += 1
prev = b
return run_lengths
def list_to_string(x):
"""
Converts list to a string representation
Empty list returns '-'
"""
if x: # non-empty list
s = str(x).replace("[", "").replace("]", "").replace(",", "")
else:
s = '-'
return s
這段代碼定義了兩個輔助函數(shù) rle_encode
和 list_to_string
,用于對圖像分割結(jié)果進(jìn)行 Run Length Encoding (RLE) 編碼和字符串轉(zhuǎn)換的操作。
-
rle_encode(x, fg_val=1)
:該函數(shù)用于對圖像進(jìn)行 RLE 編碼。輸入?yún)?shù)x
是一個 NumPy 數(shù)組,表示一個二值化的圖像掩碼(mask),其中 1 表示目標(biāo)區(qū)域(前景),0 表示背景區(qū)域。函數(shù)會將前景區(qū)域的像素位置編碼成一串 RLE 格式的列表,返回的是一個包含像素位置和長度的列表。這個編碼方法常用于圖像分割任務(wù)的結(jié)果提交,以便減少提交文件的大小和計算量。 -
list_to_string(x)
:該函數(shù)用于將列表轉(zhuǎn)換為字符串表示。輸入?yún)?shù)x
是一個列表,函數(shù)會將列表轉(zhuǎn)換為一個不包含方括號和逗號的字符串表示。如果列表為空,則返回'-'
字符串。這個函數(shù)在對 RLE 編碼結(jié)果進(jìn)行字符串表示時很有用,方便保存到提交文件或其他輸出中。
通過使用這兩個輔助函數(shù),可以將圖像分割結(jié)果進(jìn)行編碼和轉(zhuǎn)換為指定格式的字符串表示,方便提交預(yù)測結(jié)果或保存到文件中。這在進(jìn)行圖像分割任務(wù)的評估和結(jié)果輸出時非常有用。
class LightningModule(pl.LightningModule):
def __init__(self, config):
super().__init__()
self.model = smp.Unet(encoder_name=config["encoder_name"],
encoder_weights=None,
in_channels=3,
classes=1,
activation=None,
)
def forward(self, batch):
return self.model(batch)
這是一個 PyTorch Lightning 的子類 LightningModule
,它定義了一個簡單的圖像分割模型。
該類包含以下方法:
-
__init__(self, config)
:初始化方法,接受一個配置字典config
,并使用該配置創(chuàng)建 Unet 模型。encoder_name
指定了使用的編碼器名稱,encoder_weights
為 None 表示不使用預(yù)訓(xùn)練權(quán)重,in_channels=3
表示輸入圖像的通道數(shù)為 3(RGB 彩色圖像),classes=1
表示輸出的通道數(shù)為 1(二值化的分割掩碼),activation=None
表示不使用激活函數(shù)。 -
forward(self, batch)
:前向傳播方法,接受一個批次的圖像batch
,并將其傳遞給 Unet 模型進(jìn)行前向計算,返回模型的輸出。
該類繼承了 PyTorch Lightning 的 pl.LightningModule
,因此它具有 Lightning 模型所需的必要功能,如 training_step
、validation_step
、configure_optimizers
等方法。在實際的訓(xùn)練和驗證過程中,可以使用此 Lightning 模型類,以更簡潔的方式定義和管理模型,并進(jìn)行訓(xùn)練和推理。
MODEL_PATH = "/kaggle/working/models/"
#with open(os.path.join(MODEL_PATH, "config.yaml"), "r") as file_obj:
# config = yaml.safe_load(file_obj)
test_ds = ContrailsDataset(
test_df,
config["model"]["image_size"],
train = False
)
test_dl = DataLoader(test_ds, batch_size=batch_size, num_workers = num_workers)
這部分代碼用于創(chuàng)建測試集的數(shù)據(jù)加載器(DataLoader),以便在模型推理(預(yù)測)階段使用。
-
ContrailsDataset
是之前定義的用于加載測試數(shù)據(jù)的自定義數(shù)據(jù)集類,通過傳入測試數(shù)據(jù)的信息test_df
和其他相關(guān)參數(shù),創(chuàng)建了test_ds
對象。 -
test_ds
:是通過ContrailsDataset
類創(chuàng)建的測試數(shù)據(jù)集對象,用于加載測試集的圖像數(shù)據(jù)并進(jìn)行預(yù)處理。 -
config["model"]["image_size"]
:通過訪問配置字典config
中的 “model” 部分,并獲取 “image_size” 參數(shù)的值,即測試數(shù)據(jù)的圖像尺寸。 -
train=False
:將train
參數(shù)設(shè)為 False,表示test_ds
是測試數(shù)據(jù)集,以便在數(shù)據(jù)集類中進(jìn)行相應(yīng)處理。 -
DataLoader
是 PyTorch 提供的數(shù)據(jù)加載器,用于批量加載數(shù)據(jù)。通過傳入test_ds
數(shù)據(jù)集對象、batch_size
和num_workers
參數(shù),創(chuàng)建了test_dl
數(shù)據(jù)加載器。 -
batch_size=batch_size
:指定每個批次中的樣本數(shù)量,這里使用之前設(shè)定的batch_size
值。 -
num_workers=num_workers
:指定數(shù)據(jù)加載器的工作線程數(shù)量,這里使用之前設(shè)定的num_workers
值。
通過創(chuàng)建測試數(shù)據(jù)加載器 test_dl
,我們可以方便地批量加載測試數(shù)據(jù),然后將數(shù)據(jù)輸入到模型進(jìn)行預(yù)測,最終得到測試集的分割結(jié)果。
gc.enable()
all_preds = {}
for i, model_path in enumerate(glob.glob(MODEL_PATH + '*.ckpt')):
print(model_path)
model = LightningModule(config["model"]).load_from_checkpoint(model_path, config=config["model"])
model.to(device)
model.eval()
model_preds = {}
for _, data in enumerate(test_dl):
images, image_id = data
images = images.to(device)
with torch.no_grad():
predicted_mask = model(images[:, :, :, :])
if config["model"]["image_size"] != 256:
predicted_mask = torch.nn.functional.interpolate(predicted_mask, size=256, mode='bilinear')
predicted_mask = torch.sigmoid(predicted_mask).cpu().detach().numpy()
for img_num in range(0, images.shape[0]):
current_mask = predicted_mask[img_num, :, :, :]
current_image_id = image_id[img_num].item()
model_preds[current_image_id] = current_mask
all_preds[f"f{i}"] = model_preds
del model
torch.cuda.empty_cache()
gc.collect()
這段代碼使用已經(jīng)訓(xùn)練好的多個模型對測試集進(jìn)行預(yù)測,并將預(yù)測結(jié)果保存在 all_preds
字典中。
-
gc.enable()
:啟用 Python 的垃圾回收,這有助于及時釋放不再使用的內(nèi)存。 -
all_preds = {}
:創(chuàng)建一個空字典all_preds
,用于存儲所有模型的預(yù)測結(jié)果。 -
for i, model_path in enumerate(glob.glob(MODEL_PATH + '*.ckpt')):
:使用glob.glob()
函數(shù)獲取所有以 “.ckpt” 結(jié)尾的文件路徑,即訓(xùn)練好的模型的路徑。然后,通過循環(huán)遍歷所有的模型文件。 -
model = LightningModule(config["model"]).load_from_checkpoint(model_path, config=config["model"])
:加載指定路徑model_path
的訓(xùn)練好的模型,并根據(jù)配置config["model"]
創(chuàng)建 Lightning 模型。這里使用.load_from_checkpoint()
方法來加載模型。 -
model.to(device)
:將模型移動到指定的計算設(shè)備device
(GPU 或 CPU)上。 -
model.eval()
:將模型設(shè)置為評估模式,即關(guān)閉 BatchNormalization 和 Dropout 層,以便在推理階段保持一致的行為。 -
model_preds = {}
:創(chuàng)建一個空字典model_preds
,用于存儲當(dāng)前模型的預(yù)測結(jié)果。 -
for _, data in enumerate(test_dl):
:使用test_dl
數(shù)據(jù)加載器循環(huán)遍歷測試集的數(shù)據(jù)。 -
images, image_id = data
:從test_dl
中獲取當(dāng)前批次的圖像數(shù)據(jù)images
和圖像 IDimage_id
。 -
images = images.to(device)
:將圖像數(shù)據(jù)移動到指定的計算設(shè)備上。 -
with torch.no_grad():
:使用torch.no_grad()
上下文管理器,關(guān)閉梯度計算,加速推理過程。 -
predicted_mask = model(images[:, :, :, :])
:對圖像進(jìn)行預(yù)測,得到模型輸出predicted_mask
。 -
if config["model"]["image_size"] != 256:
:根據(jù)配置中的圖像尺寸,對模型輸出進(jìn)行大小調(diào)整。 -
predicted_mask = torch.sigmoid(predicted_mask).cpu().detach().numpy()
:將模型輸出進(jìn)行sigmoid激活,并將結(jié)果轉(zhuǎn)換為NumPy數(shù)組。這里得到了每個圖像的預(yù)測掩碼。 -
for img_num in range(0, images.shape[0]):
:遍歷當(dāng)前批次中的每張圖像。 -
current_mask = predicted_mask[img_num, :, :, :]
:獲取當(dāng)前圖像的預(yù)測掩碼。 -
current_image_id = image_id[img_num].item()
:獲取當(dāng)前圖像的圖像 ID。 -
model_preds[current_image_id] = current_mask
:將當(dāng)前圖像的預(yù)測掩碼加入model_preds
字典中,以圖像 ID 為鍵,掩碼為值。 -
all_preds[f"f{i}"] = model_preds
:將當(dāng)前模型的預(yù)測結(jié)果加入all_preds
字典中,以模型編號f{i}
為鍵,當(dāng)前模型的預(yù)測結(jié)果為值。 -
del model
、torch.cuda.empty_cache()
和gc.collect()
:釋放模型占用的內(nèi)存并進(jìn)行垃圾回收,以便在下一次循環(huán)中使用新的模型。
通過上述循環(huán),代碼對測試集中的所有圖像使用多個訓(xùn)練好的模型進(jìn)行預(yù)測,并將每個模型的預(yù)測結(jié)果保存在 all_preds
字典中。每個模型的預(yù)測結(jié)果都是一個字典,其中每個圖像 ID 對應(yīng)一個預(yù)測掩碼,即每張圖像的分割預(yù)測結(jié)果。
for index in submission.index.tolist():
for i in range(len(glob.glob(MODEL_PATH + '*.ckpt'))):
if i == 0:
predicted_mask = all_preds[f"f{i}"][index]
else:
predicted_mask += all_preds[f"f{i}"][index]
predicted_mask = predicted_mask / len(glob.glob(MODEL_PATH + '*.ckpt'))
predicted_mask_with_threshold = np.zeros((256, 256))
predicted_mask_with_threshold[predicted_mask[0, :, :] < THR] = 0
predicted_mask_with_threshold[predicted_mask[0, :, :] > THR] = 1
submission.loc[int(index), 'encoded_pixels'] = list_to_string(rle_encode(predicted_mask_with_threshold))
這段代碼使用多個模型的預(yù)測結(jié)果來生成提交文件的 Run Length Encoding (RLE) 編碼。文章來源:http://www.zghlxwxcb.cn/news/detail-630002.html
-
for index in submission.index.tolist():
:對提交文件中的每個圖像 ID 進(jìn)行遍歷。 -
for i in range(len(glob.glob(MODEL_PATH + '*.ckpt'))):
:遍歷之前訓(xùn)練好的多個模型的索引i
。 -
if i == 0:
:如果是第一個模型的索引,將預(yù)測掩碼初始化為all_preds[f"f{i}"][index]
。 -
else:
:對于其他模型的索引,將預(yù)測掩碼累加上all_preds[f"f{i}"][index]
,以獲得多個模型的預(yù)測結(jié)果之和。 -
predicted_mask = predicted_mask / len(glob.glob(MODEL_PATH + '*.ckpt'))
:將預(yù)測掩碼除以模型的總數(shù),以得到平均預(yù)測結(jié)果。 -
predicted_mask_with_threshold = np.zeros((256, 256))
:創(chuàng)建一個大小為 (256, 256) 的全零數(shù)組,用于存儲閾值化后的預(yù)測結(jié)果。 -
predicted_mask_with_threshold[predicted_mask[0, :, :] < THR] = 0
和predicted_mask_with_threshold[predicted_mask[0, :, :] > THR] = 1
:根據(jù)閾值THR
,將預(yù)測掩碼中小于閾值的像素設(shè)置為0,大于閾值的像素設(shè)置為1,得到二值化的分割結(jié)果。 -
submission.loc[int(index), 'encoded_pixels'] = list_to_string(rle_encode(predicted_mask_with_threshold))
:使用 RLE 編碼函數(shù)對二值化的分割結(jié)果進(jìn)行編碼,并將編碼后的結(jié)果保存在提交文件的相應(yīng)行中。
通過上述步驟,代碼將使用多個模型的預(yù)測結(jié)果進(jìn)行投票或平均,然后根據(jù)閾值 THR
進(jìn)行二值化處理,并最終將結(jié)果保存在提交文件中,用于在 Kaggle 上提交圖像分割任務(wù)的預(yù)測結(jié)果。文章來源地址http://www.zghlxwxcb.cn/news/detail-630002.html
到了這里,關(guān)于【計算機(jī)視覺 | Kaggle】飛機(jī)凝結(jié)軌跡識別 Baseline 分享和解讀(含源代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!