前回記事の過去の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