比賽目的
之前一直用的tensorflow框架,想上手一下pytorch框架,然後一番搜索之後發現了基於pytorch的fastai高級庫,fastai對於pytorch的關係和keras對於tensorflow的關係非常相似。正好拿這個比賽學習一下fastai,擴展一下pytorch方面的知識。最後成績天氣識別56/1054,雲狀識別31//1318。
因爲是練手,所以實現的功能非常簡單,基本就是baseline級別的東西。不得不說fastai使用起來非常簡便,從開始寫到出答案只需要幾個小時的時間,並且想法迭代起來也非常快速。
與其說這是一個比賽的總結,不如說這是一個安利fastai的廣告,fastai賽高。
題目簡介
兩個賽題都是分類問題,輸入爲尺寸不一的圖片,輸出爲圖片的類別。
例如天氣的類別爲:
天氣現象 | 編號 |
---|---|
雨凇 | 1 |
霧凇 | 2 |
霧霾 | 3 |
霜 | 4 |
露 | 5 |
結冰 | 6 |
降雨 | 7 |
降雪 | 8 |
冰雹 | 9 |
雲狀的類別和天氣差不多,分爲1~29個類別,並且包含一張圖片對應多個分類的情況,難度比天氣大。
數據分析
類別比例
總的數據數量爲10665,多標籤的數據爲454條,佔比較少,由於只實現了單標籤的網絡,因此數據統計的時候去掉了多標籤的數據。
數據非常不平衡,多的種類數量有1500+,少的種類數量只有個位數,這裏不禁要吐槽一下數據的質量。
雖然做了數據類別的可視化,類別非常不平衡,但是方案中並沒有實現,改進一下的話應該是可以提分的。
感覺在強大的數據平衡手段都拯救不了個位數和1500+的差距,深度學習,數據還是第一重要的。
圖片尺寸
由於網絡在訓練是要resize到一個固定的尺寸,因此,統計一下數據圖片的尺寸十分有必要。
下圖所示,X軸爲圖片的寬,Y軸爲圖片的高。
統計之後可以發現圖片大多數分佈在0~2000之間,在幾次控制變量試驗後,選定了resize爲512*512的訓練尺寸。
方案實現
- 網絡:Densenet201
- 損失函數:FocalLoss
- 在線數據增強
- 左右翻轉
- ~ 的旋轉
- ~倍的縮放
- 亮度調節
- 其他Trick:
- mixup
- 使用fp_16訓練,減少顯存的消耗增大BatchSize
- onecycle學習率策略
代碼
代碼非常簡潔,基本上能想到的常見的trick,fastai都已經給你封裝好了。
from fastai.vision import *
import pandas as pd
import numpy as np
import os
from fastai.callbacks import *
os.environ["CUDA_VISIBLE_DEVICES"] = "1,0"
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
data_path = '/home/sjw/Desktop/cloud/data/Train'
test_data_path = '/home/sjw/Desktop/cloud/data/Test'
df = pd.read_csv('/home/sjw/Desktop/cloud/data/Train_label.csv')
# 去除多標籤的數據
classes_list = [str(i) for i in range(1,30)]
df_single = df[df.Code.isin(classes_list)]
# 重新排列索引
df_single = df_single.reset_index(drop=True)
# 洗牌
df_single = df_single.sample(frac=1).reset_index(drop=True)
for i in range(len(df_single)):
df_single.Code[i] = int(df_single.Code[i])
test_df = pd.read_csv('/home/sjw/Desktop/cloud/data/submit_example.csv')
save_dir = '/home/sjw/Desktop/cloud/fastai/densenet201'
data_num = len(df_single)
for part in range(1,5):
val_list = list(range(data_num//5 * part + 1, data_num//5 * (part + 1) + 1))
part_save_dir = os.path.join(save_dir, 'part_{}'.format(part))
# 準備數據
data_set = (ImageList.from_df(df_single, data_path) # 從csv中讀取dilenames
.split_by_idx(val_list) # 驗證集百分之20
.label_from_df()
.add_test(ImageList.from_df(test_df, test_data_path))
.transform(get_transforms(do_flip=True, flip_vert=False,
max_rotate=10.0, max_zoom=1.1,
max_lighting=0.2, max_warp=0.2,
p_affine=0.75, p_lighting=0.75,
xtra_tfms=[*rand_resize_crop(512)]),
size=(512, 512), resize_method=ResizeMethod.SQUISH)
.databunch(bs=32, num_workers=6)
.normalize())
class FocalLoss(nn.Module):
def __init__(self, alpha=1., gamma=1.):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, inputs, targets, **kwargs):
CE_loss = nn.CrossEntropyLoss(reduction='none')(inputs, targets)
pt = torch.exp(-CE_loss)
F_loss = self.alpha * ((1 - pt) ** self.gamma) * CE_loss
return F_loss.mean()
# learn = cnn_learner(data_set, models.densenet201,
# metrics=[accuracy],
# loss_func=LabelSmoothingCrossEntropy(),
# model_dir=part_save_dir)
learn = cnn_learner(data_set, models.densenet201,
metrics=[accuracy],
loss_func=FocalLoss(),
model_dir=part_save_dir)
learn = learn.mixup().to_fp16()
learn.model = nn.DataParallel(learn.model, device_ids=[0,1])
# learn.lr_find(stop_div=True, num_it=250)
# learn.recorder.plot(suggestion=True)
step_name = 'best_' + 'part' + str(part)
learn.freeze()
learn.fit(2, 1e-3)
learn.unfreeze()
unfreeze_min_grad_lr = 1e-3
learn.fit_one_cycle(20, [unfreeze_min_grad_lr / 100, unfreeze_min_grad_lr / 10, unfreeze_min_grad_lr],
callbacks=[SaveModelCallback(learn, every='improvement', monitor='accuracy', name=step_name)])
learn.load(step_name)
learn = learn.to_fp32()
x, y = learn.TTA(ds_type=DatasetType.Test)
write_csv = test_df
for i in range(len(x)):
write_csv.iloc[i, 1] = np.argmax(x[i]).tolist() + 1
write_csv.to_csv(os.path.join(part_save_dir,'part{}_newlabel.csv'.format(part)), index=False)
learn.destroy()