0
0

【PyTorch】Global Average Pooling(GAP)を使用した画像分類の実装方法

Posted at

はじめに

Global Average Pooling(GAP)はディープラーニングにおいて使用されるテクニックの一つです。このブログではGAPの実装方法について説明します。

GAPとは

GAPは特にCNNの最終段階で用いられて、入力画像の特徴マップの空間的な次元を削減することで過学習を防ぐ効果があります。
GAPは各特徴マップに対してすべての値の平均を計算します。

例)チャネル数が100、画像サイズが7×7の場合
①画像サイズが7×7から49個の値の平均を計算します。
②同じ処理を100チャネル分行います。
③最終的に100個の値からなる1次元のベクトルになります。

GAPを使う利点

・パラメータ数が大幅に減少するため、学習速度が向上します。
・モデルがシンプルになるため過学習を防げます。
・画像中のオブジェクトの位置に依存しない特徴抽出が可能です。

使用するデータ

今回使用するデータはSIGNATEに公開されている「モノクロ画像の感情分類」です。こちらテーブルと画像の2種類のデータがあります。
train.zip、test.zip、train_master.tsvをそれぞれダウンロードします。
画像は学習用と評価用がそれぞれ312枚あります。

#実装方法
今回はGoogle Colabでコードを書いていきます。
以下全体コード

from google.colab import drive
drive.mount("/content/drive/")
path = "/content/drive/data" #画像とtrain_master.tsvがあるフォルダのパス

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import glob
from PIL import Image
from sklearn.preprocessing import LabelEncoder
import torch
from torch import optim,nn
from torchvision import transforms
from torch.utils.data import DataLoader,Dataset

df = pd.read_csv(f"{path}/train_master.tsv",sep = "\t")
le = LabelEncoder()
label = le.fit_transform(df["expression"])

file_path = sorted(glob.glob(f"{path}/train/*jpg"))

class NewDataset(Dataset):
  def __init__(self,file_path,label,transform = None):
    self.file_path = file_path
    self.label = label
    self.transform = transform

  def __len__(self):
    return len(self.file_path)

  def __getitem__(self,idx):
    file_path = self.file_path[idx]
    image = Image.open(file_path)
    label = self.label[idx]
    if self.transform:
      image = self.transform(image)
    return image,label

transform = transforms.Compose([
    transforms.Resize((128,128)),
    transforms.ToTensor(),
    transforms.Normalize(0.5,0.5)
])

dataset = NewDataset(file_path,label,transform)

train,test = train_test_split(dataset,test_size = 0.2)

train_batch = DataLoader(
    train,
    batch_size = 16,
    shuffle = True,
    num_workers = 2
)
test_batch = DataLoader(
    test,
    batch_size = 16,
    shuffle = False,
    num_workers = 2
)

class Model(nn.Module):
  def __init__(self):
    super(Model,self).__init__()
    self.conv1 = nn.Conv2d(1,64,kernel_size = 3,stride = 1,padding = 1)
    self.conv2 = nn.Conv2d(64,64,kernel_size = 3,stride = 1,padding = 1)
    self.conv3 = nn.Conv2d(64,128,kernel_size = 3,stride = 1,padding = 1)
    self.conv4 = nn.Conv2d(128,128,kernel_size = 3,stride = 1,padding = 1)
    self.conv5 = nn.Conv2d(128,256,kernel_size = 3,stride = 1,padding = 1)
    self.conv6 = nn.Conv2d(256,256,kernel_size = 3,stride = 1,padding = 1)
    self.pool = nn.MaxPool2d(2)
    self.relu = nn.ReLU()
    self.drop = nn.Dropout2d(0.3)
    self.gap = nn.AdaptiveAvgPool2d(1)
    self.fc1 = nn.Linear(256,4)

  def forward(self,x):
    x = self.relu(self.conv1(x))
    x = self.relu(self.conv2(x))
    x = self.drop(x)
    x = self.pool(x)
    x = self.relu(self.conv3(x))
    x = self.relu(self.conv4(x))
    x = self.pool(x)
    x = self.drop(x)
    x = self.relu(self.conv5(x))
    x = self.relu(self.conv6(x))
    x = self.pool(x)
    x = self.drop(x)
    x = self.gap(x)
    x = x.view(x.size(0), -1)  
    x = self.fc1(x)
    return x

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = Model().to(device)

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

train_loss_list = []
test_loss_list = []
train_acc_list = []
test_acc_list = []

epochs = 50
for epoch in range(1,epochs+1):
  print("----------------------")
  print(f"{epoch}/{epochs}")
  
  train_loss = 0
  test_loss = 0
  train_acc = 0
  test_acc = 0

  model.train()
  for data,label in train_batch:
    data = data.to(device)
    label = label.to(device)
    
    optimizer.zero_grad()
    ypred = model(data)
    loss = criterion(ypred,label)
    loss.backward()
    optimizer.step()
    train_loss += loss.item()
    ypred_label = torch.max(ypred,1)[1]
    train_acc += torch.sum(ypred_label == label).item() / len(label)

  epoch_train_loss = train_loss / len(train_batch)
  epoch_train_acc = train_acc / len(train_batch)

  model.eval()
  with torch.no_grad():
    for data,label in test_batch:
      data = data.to(device)
      label = label.to(device)

      ypred = model(data)
      loss = criterion(ypred,label)
      test_loss += loss.item()
      ypred_label = torch.max(ypred,1)[1]
      test_acc += torch.sum(ypred_label == label).item() /len(label)

    epoch_test_loss = test_loss / len(test_batch) 
    epoch_test_acc = test_acc / len(test_batch)

    print(f"train_loss:{epoch_train_loss}") 
    print(f"train_acc:{epoch_train_acc}") 
    print(f"test_loss:{epoch_test_loss}")
    print(f"test_acc:{epoch_test_acc}")

    train_loss_list.append(epoch_train_loss)
    train_acc_list.append(epoch_train_acc)
    test_loss_list.append(epoch_test_loss)
    test_acc_list.append(epoch_test_acc)

GAP使用部分

self.gap = nn.AdaptiveAvgPool2d(1)
x = x.view(x.size(0), -1) 

nn.AdaptiveAvgPool2d()の引数に指定した高さと幅にプーリングします。
nn.AdaptiveAvgPool2d(1)とすることで、各チャネルを1×1にプーリングするので、各チャネルの平均値を得ることが可能です。

GAP層の出力は(バッチサイズ、チャネル数、1、1)の形状を持ちます。
これを全結合層に渡すため(バッチサイズ、チャネル数)という形にしてあげます。
x = x.view(x.size(0), -1) はそのための処理です。

まとめ

今回はGAPを使用した画像分類の実装方法をブログにしました。
GAPは様々な利点があるので画像分類をする際は是非使ってみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0