前回記事の過去のkaggleコンペの「Cdiscountの画像分類チャレンジ」のデータ概要と環境準備を話しました。今回の記事はCdiscountの1位の解析モデル作成と解説します。
目次
1. 「Cdiscountの画像分類チャレンジ」のコンペの概要
___1.1 コンペの概要
___1.2 データセットの概要
___1.3 データの理解
2. 1位の解説の環境準備とデータ処理
___2.1 特徴量生成
___2.2 解析方法のサマリー
___2.3 大きなデータセットの準備
3. 1位のモデルの解説
___3.1 学習済みモデルの調整
___3.2複数枚の画像データセットを利用
___3.3 OCRデータの追加
___3.4そのたの方法
___3.5 restnetモデルのコード
3.1 学習済みモデルの調整
Resnet34で実験を開始しました。
実験の結果:
1.ほとんどすべての学習済みモデルのネットワーク構造は、1000ラベルのイメージネット用ですが、今回のコンペは5270ラベルがあります。それを直接使用すると、ネットワークのボトルネックが発生します。
2. SGD(Stochastic Gradient Descent)よりADAM Optimizerはエポックの学習が速いと変わりました。
restnet34に1×1カーネルコンボリューションレイヤーを追加しました。チャネルが512から5270になり、FC(完全接続)が5270 * 5270になります。
Adamを追加ました。エポックを増やしたから、Learning rateを小さいくなります。
lr = 0.0003
if epoch > 7:
lr = 0.0001
if epoch > 9:
lr = 0.00005
if epoch > 11:
lr = 0.00001
180 * 180の画像からランダムした1画像のパッチで11.5エポックをトレーニングすると、パブリックのLeader boardでスコアが0.72を超えることになりました。
FCレイヤーを5270×5270ノードに追加し、結果が0.5以上改善されました。
1080Ti x 4 マシンは2.5時間で1エポックをトレーニングできるので、多くの実験を行うことができました。最初の1-2エポックで結果があまり良くない場合、結果は最後には良くなく、この実験を止まります。
試しましたが、次の段階では使用しませんでした実験:
1.Multi-level categories as multi-task.
2.Hard Example Focal Loss
3.Dilation
4.Dropout
そして、restnet50を実験して、public LBのスコアは0.756になりました。
Resnet101, resnet152, inceptionresnetv2 and inceptionV4を実験しました。Inceptionv4はこの段階では少し弱く、その他学習済みモデルはパブリックLBで0.755を超えるスコアに取得できます。
3.2 複数枚の画像データセットを利用
トレーニングデータセットは、1、2、3、4イメージの製品の4つの部分に分割されます。4イメージの画像データを微調整する場合、4イメージを1つのイメージに連結します。 たとえば、3枚の画像のデータを微調整する場合は、3枚の画像を1枚の画像に連結します。
良くない画像を見つけました。モデルにとってはノイズになるため、最初の2〜3エポックで削除します。
このステップで、resnet50のモデルのクラスター、すべての画像でトレーニングされたモデル、1、2、3、4の画像製品の4つのモデルを得ました。0.77に近いaccスコアを取得できます。
いくつ回サブミットを行いました。resnet50モデルとinception-resnet-v2モデルを組み合わせれば、プライベートLBとパブリックLBで0.782 / 0.781を取得できることがわかりました。
3.4 OCRデータの追加
CDとBOOKを分類するのは非常に難しいことがわかりました。モデルが表紙を理解しないといけません。
OCTについて1週間以上かけて、CTPNを使用してテキストを含むボックスを抽出しましたが、結果は非常に良好でした。次に、CRNNを使用してボックスからテキストを抽出しました。しかし、テキストがあまり良くないことがわかりました。 画像は小さく、CRNNは英語用にトレーニングされており、再トレーニングするためのフランス語のデータセットはありません。最後の手段は、CRNNから表面の特徴量を抽出し、マルチ入力CNNにフィードします。最初の入力はResnet50 FCネットワークとCRNNmpネットワークになりました。
過剰適しないために、複数回のサブミットを行いました。
モデルでOCRを使用すると、スコアは0.782になって、0.35%向上しました。
そのための方法:
densenet161、densenet169、dpn92を試してみましたが、resnet50よりもはるかに悪いので、それらをトレーニングしたため、モデルにdenseをついかして、アンサンブルしました。モデルをアンサンブルした後、パブリックLBは0.79に達します。
最終スコアを達成するために、それに応じて224/299サイズのイメージで1イメージ製品モデルを微調整し、さらに多様性を追加するために、VGGネットに続く2つの4096 * 4096 FCレイヤーにヘッドを変更し、そして、アンサンブルしました。
最終のモデルパイプラインは下記になります。
3.5 Restnetモデルのコード
下記のコードは一位のResnetのサンプルコードになります。Resnet34, Resnet50, Resnet101などの学習済みモデルを選択することができます。
resnet.py
https://github.com/bestfitting/kaggle/blob/master/cdiscount/resnet.py
# ライブラリのインポート
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
import os
import torch.optim as optim
import torch
from torch.nn import DataParallel
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152']
import torch.nn.functional as F
#学習済みモデルのパス
model_urls = {
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
}
def make_conv_bn_relu(in_channels, out_channels, kernel_size=3, stride=1, padding=1):
return [
nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
]
# 3x3のネットワークの設定
def conv3x3(in_planes, out_planes, stride=1):
"3x3 convolution with padding"
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
#学習済みモデルの設定
def load_pretrain_pytorch_file(net,pytorch_file, skip=[], pretrained=True):
pytorch_state_dict = torch.load(pytorch_file)
if not pretrained:
pytorch_state_dict=pytorch_state_dict['state_dict']
if type(net) == DataParallel:
state_dict = net.module.state_dict()
else:
state_dict = net.state_dict()
for key in pytorch_state_dict.keys():
if key in skip or key not in state_dict.keys():
# print('not in', key)
continue
if pytorch_state_dict[key].size() != state_dict[key].size():
# print('size not the same', key)
continue
state_dict[key] = pytorch_state_dict[key]
if type(net) == DataParallel:
net.module.load_state_dict(state_dict)
else:
net.load_state_dict(state_dict)
# ネットワークの設定
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
#追加ネットワーク設定
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
#Restnetの設定
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
self.inplanes = 64
super(ResNet, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
conv_out_channels=512 * block.expansion
self.fc_in_features=conv_out_channels
self.fc_in_features = num_classes
self.layer5 = nn.Sequential(
*make_conv_bn_relu(conv_out_channels,self.fc_in_features,kernel_size=1, padding=0)
)
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.fc_p1=nn.Linear(self.fc_in_features,self.fc_in_features)
self.fc_p1_bn = nn.BatchNorm1d(self.fc_in_features)
self.fc = nn.Linear(self.fc_in_features, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
n = m.weight.size(1)
m.weight.data.normal_(0, 0.01)
m.bias.data.zero_()
self.pretrained_params=[self.conv1,self.bn1,self.layer1,self.layer2,self.layer3,self.layer4]
self.new_params=[self.fc, self.layer5, self.fc_p1, self.fc_p1_bn]
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x_l3 = self.layer5(x)
x_l3 = self.avgpool(x_l3)
x_l3 = x_l3.view(x_l3.size(0), -1)
x_l3 = self.fc_p1(x_l3)
x_l3 = self.fc_p1_bn(x_l3)
x_l3 = F.relu(x_l3)
x_l3 = self.fc(x_l3)
return x_l3
def load_pretrain_pytorch_file(self, pytorch_file):
skip=['fc.weight', 'fc.bias']
load_pretrain_pytorch_file(self,pytorch_file, skip)
print('load pretrained %s' % pytorch_file)
def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
#全体のネットワークの設定
def resnet50_kxv3a(pretrained=False, **kwargs):
model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
return model
if __name__ == "__main__":
net = eval("resnet50_kxv3a")(pretrained=False, num_classes=5270)
checkpoint = torch.load("model.pth")
net.load_state_dict(checkpoint['state_dict'])
print("succes")

Pingback: kaggle1位の解析手法 「Cdiscountの画像分類チャレンジ」2 解説の環境準備とデータ処理 - S-Analysis
Pingback: kaggle1位の解析手法 「Cdiscountの画像分類チャレンジ」1コンペの概要 - S-Analysis