【OUCディープラーニング入門】第5週学習記録:ShuffleNet&EfficientNet&Migration学習

Part1 論文読解とビデオ学習

1 シャッフルネット V1&V2

1.1 ネットワーク構造

ハイライト: GConv と DWConv で構成され、モバイル デバイスでの計算時間が短縮されるチャネル シャッフルのアイデアが提案されています。

チャンネル シャッフル: チャンネル間の情報交換を強化します。

シャッフルネットV1

 ShuffleNet 構造テーブル:

 純粋な理論的な計算のみを考慮すると、ShuffleNet では必要な計算が少なくなります。

 V1 に基づいて、V2 では計算の複雑さは FLOP だけを見ることができないことを指摘し、効率的なネットワークを設計するための 4 つのガイドラインを提案し、新しいブロック設計を提案しました。

FLOP は計算量を測定する直接的な指標ではありませんが、メモリ アクセス、並列レベル、メモリ、サイズ、コストによって生じる時間コストも計算量に影響します。

 効率的なネットワークを設計するための 4 つのガイドライン:

  • FLOP が変化しない場合、畳み込み層の入力特徴行列が出力特徴行列と等しいときに MAC が最小になります (MAC はメモリ アクセス コスト)。

  • FLOP が変化しない場合、GConv のグループが増加すると MAC も増加します。
  • ネットワーク設計が細分化されると、速度が遅くなります

  • 要素ごとの操作の影響は元に戻せません (要素ごととは、活性化関数 ReLU、加算操作 AddTensor、Addbias などを含む各要素で実行される操作を指します。これらの操作には FLOP は小さいですが、MAC は大きくなります)

概要:

  1. よりバランスのとれたコンボリューションを使用します (入力と出力の比率はできるだけ 1 に近づける必要があります)。
  2. グループ畳み込みの演算コストに注意
  3. ネットワークの断片化を軽減する
  4. 要素ごとの操作の使用を減らす

shuffleNet の構造。最初の 2 つは V1、最後の 2 つは V2 です。

(c) フラグメンテーション演算を軽減する構造となっており、左ブランチにはユニットがなく、右ブランチでは ReLU のみが実行され、入力チャネルと出力チャネルは一致しており、グループ畳み込みは破棄されます。

(d) 構造上、左右の分岐の出力チャンネルは入力チャンネルと一致しており、結合後の最終的な出力チャンネルは入力チャンネルの 2 倍になります。

1.2 PyTorch に基づいて ShuffleNet V2 を構築する

コードリンク: (colab) ShuffleNetV2

# ShuffleNet V2

# 划分并组合
def channel_shuffle(x:Tensor,groups:int)->Tensor:
  batch_size,num_channels,height,width = x.size()
  channels_per_group = num_channels//groups

  # [batch_size,num_channels,height,width]->[batch_size,groups,channels_per_group,height,width]
  x = x.view(batch_size,groups,channels_per_group,height,width)

  # 转换成在内存中连续的数据
  x = torch.transpose(x,1,2).contiguous()

  # flatten
  x = x.view(batch_size,-1,height,width)

  return x


class InvertedResidual(nn.Module):
  def __init__(self,input_c:int,output_c:int,stride:int):
    super(InvertedResidual,self).__init__()

    if stride not in [1,2]:
      raise ValueError("illegal stride value.")
    self.stride = stride

    assert output_c%2==0
    branch_features = output_c//2
    # 当stride为1时,input_channel应该是branch_features的两倍
    # <<是位运算
    assert (self.stride!=1) or (input_c==branch_features<<1)

    # 右分支
    if self.stride == 2:
      self.branch1 = nn.Sequential(
        self.depthwise_conv(input_c,input_c,kernel_s=3,stride=self.stride,padding=1),
        nn.BatchNorm2d(input_c),
        nn.Conv2d(input_c,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
        nn.BatchNorm2d(branch_features),
        nn.ReLU(inplace=True)
      )
    # 左分支没有操作
    else:
      self.branch1 = nn.Sequential()

    self.branch2 = nn.Sequential(
      nn.Conv2d(input_c if self.stride>1 else branch_features,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
      nn.BatchNorm2d(branch_features),
      nn.ReLU(inplace=True),
      self.depthwise_conv(branch_features,branch_features,kernel_s=3,stride=self.stride,padding=1),
      nn.BatchNorm2d(branch_features),
      nn.Conv2d(branch_features,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
      nn.BatchNorm2d(branch_features),
      nn.ReLU(inplace=True)
    )

  @staticmethod
  def depthwise_conv(input_c:int,output_c:int,kernel_s:int,stride:int=1,padding:int=0,bias:bool=False)->nn.Conv2d:
    return nn.Conv2d(in_channels=input_c,out_channels=output_c,kernel_size=kernel_s,
            stride=stride,padding=padding,bias=bias,groups=input_c)

  def forward(self,x:Tensor)->Tensor:
    if self.stride==1:
      x1,x2 = x.chunk(2,dim=1)
      out = torch.cat((x1,self.branch2(x2)),dim=1)
    else:
      out = torch.cat((self.branch1(x),self.branch2(x)),dim=1)

    out = channel_shuffle(out,2)

    return out


class ShuffleNetV2(nn.Module):
  def __init__(self,
        stages_repeats:List[int],
        stages_out_channels:List[int],
        num_classes:int=1000,
        inverted_residual:Callable[...,nn.Module]=InvertedResidual):
    super(ShuffleNetV2,self).__init__()

    if len(stages_repeats)!=3:
        raise ValueError("expected stages_repeats as list of 3 positive ints")
    if len(stages_out_channels)!=5:
        raise ValueError("expected stages_out_channels as list of 5 positive ints")
    self._stage_out_channels = stages_out_channels

    # input RGB image
    input_channels = 3
    output_channels = self._stage_out_channels[0]

    self.conv1 = nn.Sequential(
      nn.Conv2d(input_channels,output_channels,kernel_size=3,stride=2,padding=1,bias=False),
      nn.BatchNorm2d(output_channels),
      nn.ReLU(inplace=True)
    )
    input_channels = output_channels

    self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)

    # Static annotations for mypy
    self.stage2: nn.Sequential
    self.stage3: nn.Sequential
    self.stage4: nn.Sequential

    stage_names = ["stage{}".format(i) for i in [2,3,4]]
    for name, repeats, output_channels in zip(stage_names,stages_repeats,self._stage_out_channels[1:]):
      seq = [inverted_residual(input_channels,output_channels,2)]
      for i in range(repeats-1):
        seq.append(inverted_residual(output_channels,output_channels,1))
      setattr(self,name,nn.Sequential(*seq))
      input_channels = output_channels

    output_channels = self._stage_out_channels[-1]
    self.conv5 = nn.Sequential(
      nn.Conv2d(input_channels,output_channels,kernel_size=1,stride=1,padding=0,bias=False),
      nn.BatchNorm2d(output_channels),
      nn.ReLU(inplace=True)
    )

    self.fc = nn.Linear(output_channels,num_classes)

  def _forward_impl(self,x:Tensor)->Tensor:
    x = self.conv1(x)
    x = self.maxpool(x)
    x = self.stage2(x)
    x = self.stage3(x)
    x = self.stage4(x)
    x = self.conv5(x)
    x = x.mean([2,3])
    x = self.fc(x)
    return x

  def forward(self,x:Tensor)->Tensor:
    return self._forward_impl(x)


def shufflenet_v2_x0_5(num_classes=1000):
  model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,48,96,192,1024],num_classes=num_classes)
  return model


def shufflenet_v2_x1_0(num_classes=1000):
  model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,116,232,464,1024],num_classes=num_classes)
  return model


def shufflenet_v2_x1_5(num_classes=1000):
  model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,176,352,704,1024],num_classes=num_classes)
  return model


def shufflenet_v2_x2_0(num_classes=1000):
  model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,244,488,976,2048],=num_classes)
  return model

2 EfficientNet V3

2.1 ネットワーク構造

ハイライト: 入力解像度、ネットワークの深さ、幅の影響を同時に調査します。

  • 深さを増やすと、より複雑な特徴を抽出できますが、勾配の消失とトレーニングの困難という問題に直面します。
  • 幅を大きくすると、よりきめ細かい特徴が得られるため、トレーニングが容易になりますが、より深い特徴を学習するのは困難になります。
  • ネットワークに入力される画像の解像度を上げると、より詳細な特徴を取得できる可能性がありますが、精度の向上が低下し、画像の解像度が大きいと計算量が増加します。
  • 入力画像の深さ、幅、解像度を同時に高めると、より良い結果が得られる可能性があります

EfficientNet ネットワークの構造は、Web 検索技術を通じて取得されます。

MBConv :

 

  • 最初に 1*1 コンボリューションを使用して次元を増やします。コンボリューション カーネルの数は入力チャネルの n 倍になります。
  • n=1 の場合、1*1 畳み込みは使用されません。つまり、stage2 の MBConv 構造には次元拡張のための 1*1 畳み込みがありません。
  • ショートカット接続は、入力特徴行列が出力特徴行列と同じ形状である場合にのみ存在します。

SEモジュール

SE モジュールは、グローバル平均プーリングと 2 つの完全接続層で構成されます。最初の完全接続層のノード数は、MBConv 特徴行列の 1/4 であり、Swish 活性化関数が使用されます。2 番目の完全接続層のノードは、MBConv 特徴行列の 1/4 です。層 数字は DW 畳み込み層が出力する特徴行列のチャネル数であり、シグモイド活性化関数が使用されます。 

B0~B7のネットワークパラメータ設定:

 効果:

実際の経験: 高精度、パラメータは少ないが、より多くの GPU メモリを消費します

2.2 PyTorch に基づいて EfficientNet V3 を構築する

コードリンク: (colab) EfficientNet V3

# EfficientNet V3

def _make_divisible(ch,divisor=8,min_ch=None):
  if min_ch is None:
    min_ch = divisor
  new_ch = max(min_ch,int(ch+divisor/2)//divisor*divisor)
  if new_ch<0.9*ch:
    new_ch += divisor
  return new_ch


def drop_path(x, drop_prob: float = 0., training: bool = False):
  if drop_prob==0. or not training:
    return x
  keep_prob = 1-drop_prob
  shape = (x.shape[0],)+(1,)*(x.ndim-1)  # 适用于多种维度
  random_tensor = keep_prob+torch.rand(shape,dtype=x.dtype,device=x.device)
  random_tensor.floor_()
  output = x.div(keep_prob)*random_tensor
  return output


class DropPath(nn.Module):
  def __init__(self,drop_prob=None):
    super(DropPath,self).__init__()
    self.drop_prob = drop_prob

  def forward(self,x):
    return drop_path(x,self.drop_prob,self.training)


class ConvBNActivation(nn.Sequential):
  def __init__(self,
        in_planes:int,
        out_planes:int,
        kernel_size:int=3,
        stride:int=1,
        groups:int=1,
        norm_layer:Optional[Callable[..., nn.Module]]=None,
        activation_layer:Optional[Callable[...,nn.Module]]=None):
    padding=(kernel_size-1)//2
    if norm_layer is None:
      norm_layer = nn.BatchNorm2d
    if activation_layer is None:
      activation_layer = nn.SiLU

    super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes,
                            out_channels=out_planes,
                            kernel_size=kernel_size,
                            stride=stride,
                            padding=padding,
                            groups=groups,
                            bias=False),
                        norm_layer(out_planes),
                        activation_layer())


class SqueezeExcitation(nn.Module):
  def __init__(self,
        input_c:int,
        expand_c:int,
        squeeze_factor:int=4):
    super(SqueezeExcitation,self).__init__()
    squeeze_c = input_c//squeeze_factor
    self.fc1 = nn.Conv2d(expand_c,squeeze_c, 1)
    self.ac1 = nn.SiLU()
    self.fc2 = nn.Conv2d(squeeze_c,expand_c,1)
    self.ac2 = nn.Sigmoid()

  def forward(self,x:Tensor)->Tensor:
    scale = F.adaptive_avg_pool2d(x,output_size=(1,1))
    scale = self.fc1(scale)
    scale = self.ac1(scale)
    scale = self.fc2(scale)
    scale = self.ac2(scale)
    return scale*x


class InvertedResidualConfig:
  def __init__(self,
        kernel:int, # 3or5
        input_c:int,
        out_c:int,
        expanded_ratio:int, # 1or6
        stride:int, # 1or2
        use_se:bool,  # True
        drop_rate:float,
        index:str,  # 1a,2a,2b...
        width_coefficient:float):
    self.input_c = self.adjust_channels(input_c,width_coefficient)
    self.kernel = kernel
    self.expanded_c = self.input_c*expanded_ratio
    self.out_c = self.adjust_channels(out_c,width_coefficient)
    self.use_se = use_se
    self.stride = stride
    self.drop_rate = drop_rate
    self.index = index

  @staticmethod
  def adjust_channels(channels:int,width_coefficient:float):
    return _make_divisible(channels*width_coefficient,8)


class InvertedResidual(nn.Module):
  def __init__(self,cnf:InvertedResidualConfig,norm_layer:Callable[...,nn.Module]):
    super(InvertedResidual,self).__init__()

    if cnf.stride not in [1,2]:
      raise ValueError("illegal stride value.")

    self.use_res_connect = (cnf.stride==1andcnf.input_c==cnf.out_c)

    layers = OrderedDict()
    activation_layer = nn.SiLU

    # expand
    if cnf.expanded_c!=cnf.input_c:
      layers.update({"expand_conv":ConvBNActivation(cnf.input_c,
                              cnf.expanded_c,
                              kernel_size=1,
                              norm_layer=norm_layer,
                              activation_layer=activation_layer)})

    # depthwise
    layers.update({"dwconv":ConvBNActivation(cnf.expanded_c,
                        cnf.expanded_c,
                        kernel_size=cnf.kernel,
                        stride=cnf.stride,
                        groups=cnf.expanded_c,
                        norm_layer=norm_layer,
                        activation_layer=activation_layer)})

    if cnf.use_se:
        layers.update({"se":SqueezeExcitation(cnf.input_c,cnf.expanded_c)})

    # project
    layers.update({"project_conv":ConvBNActivation(cnf.expanded_c,
                          cnf.out_c,
                          kernel_size=1,
                          norm_layer=norm_layer,
                          activation_layer=nn.Identity)})

    self.block = nn.Sequential(layers)
    self.out_channels = cnf.out_c
    self.is_strided = cnf.stride>1

    # 只有在使用shortcut连接时才使用dropout层
    if self.use_res_connect and cnf.drop_rate>0:
      self.dropout = DropPath(cnf.drop_rate)
    else:
      self.dropout = nn.Identity()

  def forward(self,x:Tensor)->Tensor:
    result = self.block(x)
    result = self.dropout(result)
    if self.use_res_connect:
      result += x

    return result


class EfficientNet(nn.Module):
  def __init__(self,
        width_coefficient:float,
        depth_coefficient:float,
        num_classes:int=1000,
        dropout_rate:float=0.2,
        drop_connect_rate:float=0.2,
        block:Optional[Callable[...,nn.Module]]=None,
        norm_layer:Optional[Callable[...,nn.Module]]=None):
    super(EfficientNet,self).__init__()

    # kernel_size,in_channel,out_channel,exp_ratio,strides,use_SE,drop_connect_rate,repeats
    default_cnf = [[3,32,16,1,1,True,drop_connect_rate,1],
            [3,16,24,6,2,True,drop_connect_rate,2],
            [5,24,40,6,2,True,drop_connect_rate,2],
            [3,40,80,6,2,True,drop_connect_rate,3],
            [5,80,112,6,1,True,drop_connect_rate,3],
            [5,112,192,6,2,True,drop_connect_rate,4],
            [3,192,320,6,1,True,drop_connect_rate,1]]

    def round_repeats(repeats):
      return int(math.ceil(depth_coefficient*repeats))

    if block is None:
      block = InvertedResidual

    if norm_layer is None:
      norm_layer = partial(nn.BatchNorm2d,eps=1e-3,momentum=0.1)

    adjust_channels = partial(InvertedResidualConfig.adjust_channels,width_coefficient=width_coefficient)

    # build inverted_residual_setting
    bneck_conf = partial(InvertedResidualConfig,width_coefficient=width_coefficient)

    b = 0
    num_blocks = float(sum(round_repeats(i[-1]) for i in default_cnf))
    inverted_residual_setting = []
    for stage,args in enumerate(default_cnf):
      cnf = copy.copy(args)
      for i in range(round_repeats(cnf.pop(-1))):
        if i>0:
          cnf[-3] = 1
          cnf[1] = cnf[2]

        cnf[-1] = args[-2]*b/num_blocks  #更新dropout
        index = str(stage+1)+chr(i+97)  #1a,2a,2b...
        inverted_residual_setting.append(bneck_conf(*cnf,index))
        b += 1

    # create layers
    layers = OrderedDict()

    # first conv
    layers.update({"stem_conv":ConvBNActivation(in_planes=3,
                          out_planes=adjust_channels(32),
                          kernel_size=3,
                          stride=2,
                          norm_layer=norm_layer)})

    # building inverted residual blocks
    for cnf in inverted_residual_setting:
      layers.update({cnf.index: =block(cnf,norm_layer)})

    # build top
    last_conv_input_c = inverted_residual_setting[-1].out_c
    last_conv_output_c = adjust_channels(1280)
    layers.update({"top":ConvBNActivation(in_planes=last_conv_input_c,
                      out_planes=last_conv_output_c,
                      kernel_size=1,
                      norm_layer=norm_layer)})

    self.features = nn.Sequential(layers)
    self.avgpool = nn.AdaptiveAvgPool2d(1)

    classifier = []
    if dropout_rate>0:
      classifier.append(nn.Dropout(p=dropout_rate,inplace=True))
    classifier.append(nn.Linear(last_conv_output_c,num_classes))
    self.classifier = nn.Sequential(*classifier)

    # 权重初始化
    for m in self.modules():
      if isinstance(m,nn.Conv2d):
        nn.init.kaiming_normal_(m.weight,mode="fan_out")
        if m.bias is not None:
          nn.init.zeros_(m.bias)
      elif isinstance(m,nn.BatchNorm2d):
        nn.init.ones_(m.weight)
        nn.init.zeros_(m.bias)
      elif isinstance(m,nn.Linear):
        nn.init.normal_(m.weight,0,0.01)
        nn.init.zeros_(m.bias)

  def _forward_impl(self,x:Tensor)->Tensor:
    x = self.features(x)
    x = self.avgpool(x)
    x = torch.flatten(x,1)
    x = self.classifier(x)

    return x

  def forward(self,x:Tensor)->Tensor:
    return self._forward_impl(x)


def efficientnet_b0(num_classes=1000):
  # 224x224
  return EfficientNet(width_coefficient=1.0,depth_coefficient=1.0,dropout_rate=0.2,num_classes=num_classes)


def efficientnet_b1(num_classes=1000):
  # 240x240
  return EfficientNet(width_coefficient=1.0,depth_coefficient=1.1,dropout_rate=0.2,num_classes=num_classes)


def efficientnet_b2(num_classes=1000):
  # 260x260
  return EfficientNet(width_coefficient=1.1,depth_coefficient=1.2,dropout_rate=0.3,num_classes=num_classes)


def efficientnet_b3(num_classes=1000):
  # 300x300
  return EfficientNet(width_coefficient=1.2,depth_coefficient=1.4,dropout_rate=0.3,num_classes=num_classes)


def efficientnet_b4(num_classes=1000):
  # 380x380
  return EfficientNet(width_coefficient=1.4,depth_coefficient=1.8,dropout_rate=0.4,num_classes=num_classes)


def efficientnet_b5(num_classes=1000):
  # 456x456
  return EfficientNet(width_coefficient=1.6,depth_coefficient=2.2,dropout_rate=0.4,num_classes=num_classes)


def efficientnet_b6(num_classes=1000):
  # 528x528
  return EfficientNet(width_coefficient=1.8,depth_coefficient=2.6,dropout_rate=0.5,num_classes=num_classes)


def efficientnet_b7(num_classes=1000):
  # 600x600
  return EfficientNet(width_coefficient=2.0,depth_coefficient=3.1,dropout_rate=0.5,num_classes=num_classes)

3 トランス里の多頭自意識

トランスフォーマー: 元々は NLP 分野向けに提案され、並列化できずメモリ長が短かった以前の逐次ネットワークを置き換えるものでした。ハードウェアの制限がない場合、Transformer は無制限の長さのメモリを実現できます。

点乗算で得られる値は非常に大きく、ソフトマックス後の勾配は非常に小さくなります。

自意識:

マルチヘッドセルフアテンション:

 

 

 ヘッドの接合:

 その後、入力ベクトルと出力ベクトルの長さが変わらないように、結合された行列を処理する必要があります。マルチヘッド アテンション メカニズムは、さまざまなヘッド パーツから学習した情報を組み合わせて、より意味のある特徴を生成することができます。マルチヘッドの本質は、複数の独立したアテンション計算であり、統合機能があり、過学習を防ぐことができます。

Part2 コード演習

1 犬猫の戦いに VGG モデルを使用する

1.1 ライブラリを呼び出す

import numpy as np
import matplotlib.pyplot as plt
import os
import torch
import torch.nn as nn
import torchvision
from torchvision import models,transforms,datasets
import time
import json


# 判断是否存在GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s '%torch.cuda.is_available())

1.2 データをダウンロードして解凍する

# 下载数据
! wget http://fenggao-image.stor.sinaapp.com/dogscats.zip
! unzip dogscats.zip

1.3 データ処理

# 数据处理
normalize = transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])

vgg_format = transforms.Compose([transforms.CenterCrop(224),transforms.ToTensor(),normalize])

data_dir = './dogscats'

dsets = {x:datasets.ImageFolder(os.path.join(data_dir,x),vgg_format) for x in ['train','valid']}
dset_sizes = {x:len(dsets[x]) for x in ['train','valid']}
dset_classes = dsets['train'].classes

# 查看dsets的一些属性
print(dsets['train'].classes)
print(dsets['train'].class_to_idx)
print(dsets['train'].imgs[:5])
print('dset_sizes: ', dset_sizes)

loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['valid'], batch_size=5, shuffle=False, num_workers=6)

count = 1
for data in loader_valid:
  print(count,end='\n')
  if count==1:
    inputs_try,labels_try = data
  count+=1

print(labels_try)
print(inputs_try.shape)

# 显示图片
def imshow(inp, title=None):
  inp = inp.numpy().transpose((1,2,0))
  mean = np.array([0.485,0.456,0.406])
  std = np.array([0.229,0.224,0.225])
  inp = np.clip(std*inp+mean,0,1)
  plt.imshow(inp)
  if title is not None:
    plt.title(title)
  plt.pause(0.001)

# 显示labels_try的5张图片,即valid里第一个batch的5张图片
out = torchvision.utils.make_grid(inputs_try)
imshow(out,title=[dset_classes[x] for x in labels_try])

1.4 モデルの作成

# 创建模型

model_vgg = models.vgg16(pretrained=True)

with open('./imagenet_class_index.json') as f:
  class_dict = json.load(f)
dic_imagenet = [class_dict[str(i)][1] for i in range(len(class_dict))]

inputs_try,labels_try = inputs_try.to(device),labels_try.to(device)
model_vgg = model_vgg.to(device)

outputs_try = model_vgg(inputs_try)

print(outputs_try)
print(outputs_try.shape)

m_softm = nn.Softmax(dim=1)
probs = m_softm(outputs_try)
vals_try,pred_try = torch.max(probs,dim=1)

print('prob sum: ',torch.sum(probs,1))
print('vals_try: ',vals_try)
print('pred_try: ',pred_try)

print([dic_imagenet[i] for i in pred_try.data])
imshow(torchvision.utils.make_grid(inputs_try.data.cpu()), 
  title=[dset_classes[x] for x in labels_try.data.cpu()])

# 修改最后一层,冻结前面层的参数

print(model_vgg)

model_vgg_new = model_vgg;

for param in model_vgg_new.parameters():
  param.requires_grad = False
model_vgg_new.classifier._modules['6'] = nn.Linear(4096,2)
model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim=1)

model_vgg_new = model_vgg_new.to(device)

print(model_vgg_new.classifier)

モデルを表示:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)
Sequential(
  (0): Linear(in_features=25088, out_features=4096, bias=True)
  (1): ReLU(inplace=True)
  (2): Dropout(p=0.5, inplace=False)
  (3): Linear(in_features=4096, out_features=4096, bias=True)
  (4): ReLU(inplace=True)
  (5): Dropout(p=0.5, inplace=False)
  (6): Linear(in_features=4096, out_features=2, bias=True)
  (7): LogSoftmax(dim=1)
)

1.5 トレーニングとテスト

# 训练并测试全连接层

criterion = nn.NLLLoss()

# 学习率
lr = 0.001

# 随机梯度下降
optimizer_vgg = torch.optim.SGD(model_vgg_new.classifier[6].parameters(),lr=lr)

def train_model(model,dataloader,size,epochs=1,optimizer=None):
  model.train()
  
  for epoch in range(epochs):
    running_loss = 0.0
    running_corrects = 0
    count = 0
    for inputs,classes in dataloader:
      inputs = inputs.to(device)
      classes = classes.to(device)
      outputs = model(inputs)
      loss = criterion(outputs,classes)           
      optimizer = optimizer
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()
      _,preds = torch.max(outputs.data,1)
      # statistics
      running_loss += loss.data.item()
      running_corrects += torch.sum(preds==classes.data)
      count += len(inputs)
      print('Training: No. ',count,' process ... total: ',size)
    epoch_loss = running_loss/size
    epoch_acc = running_corrects.data.item()/size
    print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss,epoch_acc))
        
        
# 模型训练
train_model(model_vgg_new,loader_train,size=dset_sizes['train'],epochs=1,optimizer=optimizer_vgg)  

# 测试

def test_model(model,dataloader,size):
  model.eval()
  predictions = np.zeros(size)
  all_classes = np.zeros(size)
  all_proba = np.zeros((size,2))
  i = 0
  running_loss = 0.0
  running_corrects = 0
  for inputs,classes in dataloader:
    inputs = inputs.to(device)
    classes = classes.to(device)
    outputs = model(inputs)
    loss = criterion(outputs,classes)           
    _,preds = torch.max(outputs.data,1)
    # statistics
    running_loss += loss.data.item()
    running_corrects += torch.sum(preds==classes.data)
    predictions[i:i+len(classes)] = preds.to('cpu').numpy()
    all_classes[i:i+len(classes)] = classes.to('cpu').numpy()
    all_proba[i:i+len(classes),:] = outputs.data.to('cpu').numpy()
    i += len(classes)
    print('Testing: No. ',i,' process ... total: ',size)        
  epoch_loss = running_loss/size
  epoch_acc = running_corrects.data.item()/size
  print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss,epoch_acc))
  return predictions,all_proba,all_classes
  
predictions,all_proba,all_classes = test_model(model_vgg_new,loader_valid,size=dset_sizes['valid'])
  

1.6 視覚化

AI 芸術鑑賞チャレンジ 2 問

コードリンク:

ここで resnet を使用してみます。メインコードは次のとおりです。

num_workers = 2
batch_size = 32

net = models.resnet50(pretrained=True)
net.fc = nn.Linear(net.fc.in_features,49,bias=True)
net = net.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(),lr=0.001)

train_data = PDataset(train_dir,transform=transform)
train_dataloader = DataLoader(dataset=train_data,shuffle=True,num_workers=num_workers,batch_size=batch_size,pin_memory=True)

for epoch in range(30):  # 重复多轮训练
    for i,(img,label) in enumerate(train_dataloader):
      img = img.to(device)
      labels = label.to(device)
      # 优化器梯度归零
      optimizer.zero_grad()
      # 正向传播+反向传播+优化 
      outputs = net(img)
      loss = criterion(outputs,labels)
      loss.backward()
      optimizer.step()
    # 输出统计信息  
    print('Epoch: %d loss: %.3f' %(epoch+1,loss.item()))

print('Finished Training')

import pandas as pd

test_imgs = os.listdir(test_dir)

id_list = []
label_list = []

for img in test_imgs:
  id = img.split('.')[0]
  img = Image.open(test_dir+img).convert('RGB')
  img = transform(img).to(device)
  img = img.unsqueeze(0)
  label = net(img)
  i = 0
  # print(label[0])
  max = label[0][0]
  max_id = 0
  for value in label[0]:
    if value > max:
      max = value
      max_id = i
    i+=1
  print(max_id)
  id_list.append(id)
  label_list.append(max_id)

dataframe = pd.DataFrame(
        {'id': id_list, 'nameid': label_list})
dataframe.to_csv(r"results.csv", sep=',')

resnet50のトレーニング結果:

 csvを生成して送信した後の判定結果:

このうちresnet50トレーニングを15回行った結果がresult1、resnet50トレーニングを50回行った結果がresult2であり、特に結果が悪い。これは、トレーニング ラウンドが少なすぎることに関連している可能性があります。この競技会には 49 のカテゴリーがあり、これは複数のカテゴリーの問題であり、多くの場合、望ましい効果を達成するには、より優れたネットワーク構造とより少ない損失が必要です。さらに、提供された資料によると、トレーニング セットとテスト セットの両方がロングテールになる傾向があり、これによりトレーニング モデルがすべての画像を同じカテゴリに分類することになりやすく、csv ファイルを観察すると次のことがわかります。ほとんどの写真はすべて No. 3 または No. 16 に分類されており、No. 3 と No. 16 には最大のサンプルサイズが含まれており、モデルご都合主義の現象が実際に起こったことを示しています。

次に、最優秀賞受賞者が提供したコード スキームを研究しました。ここで使用したバックボーン ネットワークは EfficientNet B3 で、さまざまな損失の下で 5 セットのテスト結果を記録し、どの画像が属するかを投票によって決定しました。クラス、私はこれだと思います投票メカニズムはこのコードスキームのハイライトであり、5つのトレーニング結果の利点を組み合わせることができ、互いに学習でき、非常に賢明であり、この種のバックボーンネットワークパラメータは小さく、主な計算効率は評価されません5結果的に時間がかかってしまいます。

 

 2 位と 3 位のコード スキームを観察すると、バックボーン ネットワークとして resnet200、3 位はefficientnet b3 と resnext50 をそれぞれ使用しており、この投票メカニズムも使用されていることがわかりました。個人的には、基幹ネットワークの構造が単純で計算効率が高い場合には、この投票機構を利用することで認識精度を向上できるのではないかと考えています。

おすすめ

転載: blog.csdn.net/qq_55708326/article/details/126226317