最終決賽排名:第五名(和興路天團)
參賽背景(“觀雲識天”人機對抗大賽)
根據大賽組織方提供的圖片數據訓練算法,能夠區分降雨、降雪、冰雹、露、霜、霧(霾)、霧凇、雨凇、電線積冰9種天氣現象。
賽題分析
數據存在的問題:
- 數據量小
- 樣本不均衡
- 數據集噪聲大
- 對天氣知識的先驗知識不足
解決方案
- 數據清洗
- 數據增強:幾何變換、CutMix等
- 數據不均衡
- 標籤平滑
- 雙損失函數
- 優化器:RAdam LookAhead
- 深度模型ResNeXt101系列,EfficientNet系列。
- 注意力機制
- SVM替換softmax層
- 模型融合:VotingClassifier
- TTA:測試時增強
1.數據清洗
藉助弱監督方式引入外部數據集中的高質量數據——解決了自行擴展數據集帶來的測試偏移。步驟如下:
- 使用訓練數據建立模型
- 預測爬取的數據的標籤,對外部數據進行僞標籤標註。
- 結合樣本分佈和混淆矩陣的結果,設置了多級閾值,選擇可信度高的數據,組合成新的數據集
- 重複1,2,3。
2.數據增強
- 幾何變換——只進行水平翻轉和平移0.05
transforms.RandomAffine(degrees=0, translate=(0.05, 0.05)),
transforms.RandomHorizontalFlip()
- CutMix
3.數據不均衡
在數據層面和算法層面同時測試選取—— 上採樣和class_wight相結合
- 上採樣——通過混淆矩陣和驗證集的隨機化設置,提取模型預測錯誤的數據,然後按照一定的權重進行數據複製擴充,爲了減少上採樣可能帶來的過擬合問題,我們對擴充的數據進行了區域裁剪,使數據更傾向於需要關注的部分。
- class_weight——將不同的類別映射爲不同的權值,該參數用來在訓練過程中調整損失函數(只能用於訓練)。該參數在處理非平衡的訓練數據(某些類的訓練樣本數很少)時,可以使得損失函數對樣本數不足的數據更加關注。
4.數據標籤平滑
目的:減小過擬合問題
平滑過後的樣本交叉熵損失就不僅考慮到了訓練樣本中正確的標籤位置的損失,也稍微考慮到其他錯誤標籤位置的損失,導致最後的損失增大,導致模型的學習能力提高,即要下降到原來的損失,就得學習的更好,也就是迫使模型往增大正確分類概率並且同時減小錯誤分類概率的方向前進。
#!/usr/bin/python
# -*- encoding: utf-8 -*-
import torch
import torch.nn as nn
class LabelSmoothSoftmaxCE(nn.Module):
def __init__(self,
lb_pos=0.9,
lb_neg=0.005,
reduction='mean',
lb_ignore=255,
):
super(LabelSmoothSoftmaxCE, self).__init__()
self.lb_pos = lb_pos
self.lb_neg = lb_neg
self.reduction = reduction
self.lb_ignore = lb_ignore
self.log_softmax = nn.LogSoftmax(1)
def forward(self, logits, label):
logs = self.log_softmax(logits)
ignore = label.data.cpu() == self.lb_ignore
n_valid = (ignore == 0).sum()
label = label.clone()
label[ignore] = 0
lb_one_hot = logits.data.clone().zero_().scatter_(1, label.unsqueeze(1), 1)
label = self.lb_pos * lb_one_hot + self.lb_neg * (1-lb_one_hot)
ignore = ignore.nonzero()
_, M = ignore.size()
a, *b = ignore.chunk(M, dim=1)
label[[a, torch.arange(label.size(1)), *b]] = 0
if self.reduction == 'mean':
loss = -torch.sum(torch.sum(logs*label, dim=1)) / n_valid
elif self.reduction == 'none':
loss = -torch.sum(logs*label, dim=1)
return loss
if __name__ == '__main__':
torch.manual_seed(15)
criteria = LabelSmoothSoftmaxCE(lb_pos=0.9, lb_neg=5e-3)
net1 = nn.Sequential(
nn.Conv2d(3, 3, kernel_size=3, stride=2, padding=1),
)
net1.cuda()
net1.train()
net2 = nn.Sequential(
nn.Conv2d(3, 3, kernel_size=3, stride=2, padding=1),
)
net2.cuda()
net2.train()
with torch.no_grad():
inten = torch.randn(2, 3, 5, 5).cuda()
lbs = torch.randint(0, 3, [2, 5, 5]).cuda()
lbs[1, 3, 4] = 255
lbs[1, 2, 3] = 255
print(lbs)
import torch.nn.functional as F
logits1 = net1(inten)
logits1 = F.interpolate(logits1, inten.size()[2:], mode='bilinear')
logits2 = net2(inten)
logits2 = F.interpolate(logits2, inten.size()[2:], mode='bilinear')
# loss1 = criteria1(logits1, lbs)
loss = criteria(logits1, lbs)
# print(loss.detach().cpu())
loss.backward()
5.雙損失函數
categorical_crossentropy 和 Label Smoothing Regularization :在對原始標籤進行平滑的過程中,可能存在某些數據對標籤變化特別敏感,導致損失函數的異常增大,使模型變得不穩定,爲了增加模型的穩定性所以使用雙損失函數——categorical_crossentropy 和 Label Smoothing Regularization,即保證了模型的泛化能力,又保證了數據不會對標籤過於敏感,增加了模型的穩定性。
criterion = L.JointLoss(first=nn.crossentropyloss(), second=LabelSmoothSoftmaxCE(),
first_weight=0.5, second_weight=0.5)
6.優化器
RAdam LookAhead:兼具Adam和SGD兩者的優化器RAdam,收斂速度快,魯棒性好LookAhead對SGD進行改進,在各種深度學習任務上實現了更快的收斂 。
將RAdam和LookAhead結合在了一起,形成名爲Ranger的新優化器。在ImageNet上進行了測試,在128px,20epoch的測試中,Ranger的訓練精度達到了93%,比目前FastAI排行榜榜首提高了1%。
7.選擇的模型
ResNeXt101系列,EfficientNet系列。
- resnext101_32x16d_wsl
- resnext101_32x8d_wsl
- efficientnet-b4
- efficientnet-b5
8.注意力機制
在模型中加入SE&CBAM注意力機制——提升網絡模型的特徵提取能力。
- channel attention的過程。比SE多了一個 global max pooling(池化本身是提取高層次特徵,不同的池化意味着提取的高層次特徵更加豐富)。其第2個池化之後的處理過程和SE一樣,都是先降維再升維,不同的是將2個池化後相加再sigmod和原 feature map相乘輸出結果。
class ChannelAttention(nn.Module):
def __init__(self, in_planes, ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
out = avg_out + max_out
return self.sigmoid(out)
- spatial attention 的過程。將做完 channel attention 的feature map 作爲輸入,之後作2個大小爲列通道的維度池化,每一次池化得到的 feature map 大小就爲 h * w * 1 ,再將兩次池化的 feature map 作基於通道的連接變成了大小爲 h * w * 2 的 feature map ,再對這個feature map 進行核大小爲 7*7 ,卷積核個數爲1的卷積操作(通道壓縮)再sigmod,最後就是熟悉的矩陣全乘。
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
9.SVM替換softmax層
抽取出模型的最後一層,將其接入SVM,用訓練數據動態訓練SVM分類器,再使用訓練好的SVM分類器進行預測。
深度學習模型有支持向量機無法比擬的非線性函數逼近能力,能夠很好地提取並表達數據的特徵,深度學習模型的本質是特徵學習器。然而,深度模型往往在獨立處理分類、迴歸等問題上難以取得理想的效果。對於 SVM 來說,可以利用核函數的思想將非線性樣本映射到高維空間,使其線性可分,再通過使數據集之間的分類間隔最大化來尋找最優分割超平面,在分類問題上表現出許多特有優勢。但實質上,SVM 只含有一個隱層,數據表徵能力並不理想。因此將深度學習方法與 SVM 相結合,構造用於分類的深層模型。利用深度學習的無監督方式分層提取樣本高級特徵,然後將這些高級特徵輸入 SVM 模型進行分類,從而達到最優分類精度。
10.模型融合
多模型融合的策略,Stacking,VotingClassifier—— 提升分類準確率
Stacking方法: Stacking 先從初始數據集訓練出初級學習器,然後”生成”一個新數據集用於訓練次級學習器。在這個新數據集中,初級學習器的輸出被當作樣例輸入特徵,而初始樣本的標記仍被當作樣例標記。stacking使用交叉驗證的方式,初始訓練集 D 被隨機劃分爲 k 個大小相似的集合 D1 , D2 , … , Dk,每次用k-1 個部分訓練 T 個模型,對另個一個部分產生 T 個預測值作爲特徵,遍歷每一折後,也就得到了新的特徵集合,標記還是源數據的標記,用新的特徵集合訓練一個集合模型。
TTA:增強時測試(保證數據增強的幾何變換和tta一致)
可將準確率提高若干個百分點,它就是測試時增強(test time augmentation, TTA)。這裏會爲原始圖像造出多個不同版本,包括不同區域裁剪和更改縮放程度等,並將它們輸入到模型中;然後對多個版本進行計算得到平均輸出,作爲圖像的最終輸出分數。
tta_model = tta.TTAWrapper(model,tta.fliplr_image2label)