VGGNet Implementation

  • ÄÁº¼·ç¼Ç ¿¬»ê¸¸ ¹è¿î »óÅ¿¡¼­ VGG¸¦ ¹Ù·Î ÀÌÇØÇÏ°í Â¥±â¿¡´Â ¹«¸®°¡ ÀÖ½À´Ï´Ù.
  • ¿¬»êµéÀÇ µ¿ÀÛ ¿ø¸®¸¦ ÃæºÐÈ÷ ÀÌÇØÇÑÈÄ ´Ù½Ã º¸¼Åµµ ´ÊÁö ¾Ê½À´Ï´Ù.

  • 2014 ILSVRC 2nd place

  • VGG-16
  • Convolution layer
  • Maxpooling layer
  • Fully connected layer

´ëü ÅؽºÆ®

In [1]:
# ·±Å¸ÀÓ À¯Çü GPU ¸ðµå·Î º¯°æ
!pip install torch torchvision
Requirement already satisfied: torch in /usr/local/lib/python3.6/dist-packages (1.1.0)
Requirement already satisfied: torchvision in /usr/local/lib/python3.6/dist-packages (0.3.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from torch) (1.16.4)
Requirement already satisfied: pillow>=4.1.1 in /usr/local/lib/python3.6/dist-packages (from torchvision) (4.3.0)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from torchvision) (1.12.0)
Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow>=4.1.1->torchvision) (0.46)

Prepare Data

  • ¸ðµ¨ÀÌ ÇнÀÀÌ µÇ´ÂÁö¸¸ È®ÀÎÇÒ ¼ö ÀÖ°Ô °£´ÜÇÑ µ¥ÀÌÅ͸¦ ´Ù¿î·Îµå ÇÕ´Ï´Ù.
In [2]:
!rm -r images
import os 

# À̹ÌÁö ÆÄÀÏÀ» ÀúÀåÇÒ Æú´õ¸¦ »ý¼ºÇÕ´Ï´Ù.
try:
  os.mkdir("images")
  os.mkdir("images/dogs")
  os.mkdir("images/cats")
except:
  pass

# À̹ÌÁöµéÀ» ÁöÁ¤ÇÑ À§Ä¡¿¡ ´Ù¿î·ÎµåÇÕ´Ï´Ù.
# images/dogs ¹Ø¿¡ 2°³
!wget https://i.kinja-img.com/gawker-media/image/upload/s--WFkXeene--/c_scale,f_auto,fl_progressive,q_80,w_800/ol9ceoqxidudap8owlwn.jpg -P images/dogs
!wget https://www.rspcansw.org.au/wp-content/uploads/2017/08/50_a-feature_dogs-and-puppies_mobile.jpg -P images/dogs

# images/cats ¹Ø¿¡ 2°³
!wget https://www.catster.com/wp-content/uploads/2018/05/A-gray-cat-crying-looking-upset.jpg -P images/cats
!wget https://www.scarymommy.com/wp-content/uploads/2018/01/c1.jpg?w=700 -P images/cats
rm: cannot remove 'images': No such file or directory
--2019-08-26 07:56:09--  https://i.kinja-img.com/gawker-media/image/upload/s--WFkXeene--/c_scale,f_auto,fl_progressive,q_80,w_800/ol9ceoqxidudap8owlwn.jpg
Resolving i.kinja-img.com (i.kinja-img.com)... 151.101.194.166, 151.101.130.166, 151.101.2.166, ...
Connecting to i.kinja-img.com (i.kinja-img.com)|151.101.194.166|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 32099 (31K) [image/jpeg]
Saving to: ¡®images/dogs/ol9ceoqxidudap8owlwn.jpg¡¯

ol9ceoqxidudap8owlw 100%[===================>]  31.35K  --.-KB/s    in 0.005s  

2019-08-26 07:56:10 (5.82 MB/s) - ¡®images/dogs/ol9ceoqxidudap8owlwn.jpg¡¯ saved [32099/32099]

--2019-08-26 07:56:15--  https://www.rspcansw.org.au/wp-content/uploads/2017/08/50_a-feature_dogs-and-puppies_mobile.jpg
Resolving www.rspcansw.org.au (www.rspcansw.org.au)... 101.0.77.122
Connecting to www.rspcansw.org.au (www.rspcansw.org.au)|101.0.77.122|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 130940 (128K) [image/jpeg]
Saving to: ¡®images/dogs/50_a-feature_dogs-and-puppies_mobile.jpg¡¯

50_a-feature_dogs-a 100%[===================>] 127.87K   157KB/s    in 0.8s    

2019-08-26 07:56:17 (157 KB/s) - ¡®images/dogs/50_a-feature_dogs-and-puppies_mobile.jpg¡¯ saved [130940/130940]

--2019-08-26 07:56:19--  https://www.catster.com/wp-content/uploads/2018/05/A-gray-cat-crying-looking-upset.jpg
Resolving www.catster.com (www.catster.com)... 192.124.249.102
Connecting to www.catster.com (www.catster.com)|192.124.249.102|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 165145 (161K) [image/jpeg]
Saving to: ¡®images/cats/A-gray-cat-crying-looking-upset.jpg¡¯

A-gray-cat-crying-l 100%[===================>] 161.27K  --.-KB/s    in 0.03s   

2019-08-26 07:56:19 (5.12 MB/s) - ¡®images/cats/A-gray-cat-crying-looking-upset.jpg¡¯ saved [165145/165145]

--2019-08-26 07:56:22--  https://www.scarymommy.com/wp-content/uploads/2018/01/c1.jpg?w=700
Resolving www.scarymommy.com (www.scarymommy.com)... 104.18.166.96, 104.18.164.96, 104.18.168.96, ...
Connecting to www.scarymommy.com (www.scarymommy.com)|104.18.166.96|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2169547 (2.1M) [image/jpeg]
Saving to: ¡®images/cats/c1.jpg?w=700¡¯

c1.jpg?w=700        100%[===================>]   2.07M  --.-KB/s    in 0.03s   

2019-08-26 07:56:23 (67.4 MB/s) - ¡®images/cats/c1.jpg?w=700¡¯ saved [2169547/2169547]

1. Settings

1) Import required libraries

In [0]:
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.init as init
import torch.utils.data as data
import torchvision.datasets as dset
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

2) Hyperparameter

In [0]:
batch_size= 1
learning_rate = 0.0002
num_epoch = 100

2. Data Loader

ex)

root/dog/xxx.png

root/dog/xxy.png

root/cat/123.png

root/cat/nsdf3.png

In [0]:
# ¶óº§(ȤÀº Ŭ·¡½º) º°·Î Æú´õ°¡ ÀúÀåµÇ¾î ÀÖ´Â ·çÆ® µð·ºÅ丮¸¦ ÁöÁ¤ÇÕ´Ï´Ù.
img_dir = "./images"

# ÇØ´ç ·çÆ® µð·ºÅ丮¸¦ ImageFolder ÇÔ¼ö¿¡ Àü´ÞÇÕ´Ï´Ù.
# À̶§ À̹ÌÁöµé¿¡ ´ëÇÑ º¯Çüµµ °°ÀÌ Àü´ÞÇØÁÝ´Ï´Ù.
img_data = dset.ImageFolder(img_dir, transforms.Compose([
                                      transforms.Resize(256),                   # À̹ÌÁö Å©±â¸¦ 256x256À¸·Î ¹Ù²ãÁÝ´Ï´Ù.
                                      transforms.RandomResizedCrop(224),        # 256x256 À̹ÌÁöÀÇ ·£´ýÇÑ À§Ä¡¿¡¼­ 224x224 Å©±â¸¸Å­ »ùÇøµ ÇÕ´Ï´Ù.
                                      transforms.RandomHorizontalFlip(),        # ·£´ýÇÑ È®·ü·Î À̹ÌÁö¸¦ Á¿ì¹ÝÀü ÇÕ´Ï´Ù.
                                      transforms.ToTensor(),                    # À̹ÌÁö µ¥ÀÌÅ͸¦ ÅÙ¼­·Î º¯ÇüÇÕ´Ï´Ù.
            ]))

train_loader = data.DataLoader(img_data, batch_size=batch_size,
                            shuffle=True, num_workers=2)

3. Model

1) Basic Blocks

  • ¸ðµ¨¿¡ ¹Ýº¹µÇ´Â ºÎºÐÀÌ ¸¹±â ¶§¹®¿¡ À̸¦ ÇÔ¼ö·Î ¸¸µé¾î ´Ü¼øÈ­ ÇÕ´Ï´Ù.
  • ¸Ç À§¿¡ À̹ÌÁö¸¦ º¸¸é ÄÁº¼·ç¼Ç ¿¬»êÀÌ 2¹ø ¿¬¼ÓÇÏ´Â °æ¿ì¿Í 3¹ø ¿¬¼ÓÇÏ´Â °æ¿ì°¡ Àִµ¥ À̸¦ °¢°¢ ¸¸µé¾îÁÝ´Ï´Ù.
  • ¾Æ·¡ÀÇ ÄÚµå´Â ÃÖÀûÀÇ ¹æ¹ýÀ̶ó±â º¸´Ù´Â ±×¸²ÀÇ ±¸Á¶¸¦ ¸ð¹æÇÑ ÄÚµåÀÔ´Ï´Ù.
In [0]:
# ÄÁº¼·ç¼Ç ¿¬»êÀÌ 2¹ø ¿¬¼ÓÇÏ´Â °æ¿ì
# ÄÁº¼·ç¼Ç-È°¼ºÈ­ÇÔ¼ö-ÄÁº¼·ç¼Ç-È°¼ºÈ­ÇÔ¼ö-Ç®¸µ
def conv_2_block(in_dim,out_dim):
    model = nn.Sequential(
        nn.Conv2d(in_dim,out_dim,kernel_size=3,padding=1),
        nn.ReLU(),
        nn.Conv2d(out_dim,out_dim,kernel_size=3,padding=1),
        nn.ReLU(),
        nn.MaxPool2d(2,2)
    )
    return model

  
# ÄÁº¼·ç¼Ç ¿¬»êÀÌ 3¹ø ¿¬¼ÓÇÏ´Â °æ¿ì
# ÄÁº¼·ç¼Ç-È°¼ºÈ­ÇÔ¼ö-ÄÁº¼·ç¼Ç-È°¼ºÈ­ÇÔ¼ö-ÄÁº¼·ç¼Ç-È°¼ºÈ­ÇÔ¼ö-Ç®¸µ
def conv_3_block(in_dim,out_dim):
    model = nn.Sequential(
        nn.Conv2d(in_dim,out_dim,kernel_size=3,padding=1),
        nn.ReLU(),
        nn.Conv2d(out_dim,out_dim,kernel_size=3,padding=1),
        nn.ReLU(),
        nn.Conv2d(out_dim,out_dim,kernel_size=3,padding=1),
        nn.ReLU(),
        nn.MaxPool2d(2,2)
    )
    return model

2) VGG Model

In [0]:
# À§¿¡¼­ Á¤ÀÇÇÑ ºí·ÏµéÀ» ÀÌ¿ëÇØ VGG ³×Æ®¿öÅ©¸¦ ¸¸µé¾îº¸°Ú½À´Ï´Ù.
# ÇÊÅÍÀÇ °³¼ö°¡ 2ÀÇ n½ÂÀÇ °ªÀ» °¡Áö±â ¶§¹®¿¡ base_dimÀ̶õ º¯¼ö¸¦ Ãß°¡Çؼ­ ´Ü¼øÈ­ Çß½À´Ï´Ù.
# ÇöÀç dog, cat µÎ °¡Áö Ŭ·¡½º¸¦ ±¸ºÐÇÏ·Á°í Çϱ⠶§¹®¿¡ num_classes=2·Î ¼³Á¤Çß½À´Ï´Ù.

class VGG(nn.Module):
    def __init__(self, base_dim, num_classes=2):
        super(VGG, self).__init__()
        self.feature = nn.Sequential(
            conv_2_block(3,base_dim),
            conv_2_block(base_dim,2*base_dim),
            conv_3_block(2*base_dim,4*base_dim),
            conv_3_block(4*base_dim,8*base_dim),
            conv_3_block(8*base_dim,8*base_dim),            
        )
        self.fc_layer = nn.Sequential(
            nn.Linear(8*base_dim * 7 * 7, 100),
            nn.ReLU(True),                                                      # True ´Â inplace ¿¬»êÀ» ÇÏ°Ú´Ù´Â Àǹ̸¦ °¡Áý´Ï´Ù. inplace ¿¬»êÀº °á°ú°ªÀ» »õ·Î¿î º¯¼ö¿¡ °ªÀ» ÀúÀåÇÏ´Â ´ë½Å ±âÁ¸ÀÇ µ¥ÀÌÅ͸¦ ´ëüÇϴ°ÍÀ» ÀǹÌÇÕ´Ï´Ù.
            #nn.Dropout(),
            nn.Linear(100, 20),
            nn.ReLU(True),
            #nn.Dropout(),
            nn.Linear(20, num_classes),
        )

    def forward(self, x):
        x = self.feature(x)
        x = x.view(x.size(0), -1)                                               # x.size(0)¸¦ batch size·Î ¹Ù²ãµµ °°Àº °ªÀÔ´Ï´Ù.
        x = self.fc_layer(x)
        return x
    

4. Optimizer & Loss

In [8]:
# gpu°¡ »ç¿ë °¡´ÉÇÑ °æ¿ì¿¡´Â device¸¦ 0¹ø gpu·Î ¼³Á¤ÇÏ°í ºÒ°¡´ÉÇϸé cpu·Î ¼³Á¤ÇÕ´Ï´Ù.
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

# ¾Õ¼­ Á¤ÀÇÇÑ´ë·Î vGG Ŭ·¡½º¸¦ ÀνºÅϽºÈ­ ÇÏ°í ÁöÁ¤ÇÑ ÀåÄ¡¿¡ ¿Ã¸³´Ï´Ù.
model = VGG(base_dim=16).to(device)

# ¼Õ½ÇÇÔ¼ö ¹× ÃÖÀûÈ­ÇÔ¼ö¸¦ ¼³Á¤ÇÕ´Ï´Ù.
loss_func = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# ¸ðµ¨ ÀÚ³à ³ëµåÀÇ À̸§°ú ¸ðµâÀ» Ãâ·ÂÇÕ´Ï´Ù.
for i in model.named_children():
    print(i)
cuda:0
('feature', Sequential(
  (0): Sequential(
    (0): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (1): Sequential(
    (0): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (2): Sequential(
    (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (3): Sequential(
    (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (4): Sequential(
    (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
))
('fc_layer', Sequential(
  (0): Linear(in_features=6272, out_features=100, bias=True)
  (1): ReLU(inplace)
  (2): Linear(in_features=100, out_features=20, bias=True)
  (3): ReLU(inplace)
  (4): Linear(in_features=20, out_features=2, bias=True)
))

5. Train

In [9]:
for i in range(num_epoch):
    for j,[image,label] in enumerate(train_loader):
        x = image.to(device)
        y_= label.to(device)
        
        optimizer.zero_grad()
        output = model.forward(x)
        loss = loss_func(output,y_)
        loss.backward()
        optimizer.step()

    if i % 10 ==0:
        print(loss)
tensor(0.6225, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.6045, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.5143, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.4301, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.4345, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.4101, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.4734, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.4204, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(0.6140, device='cuda:0', grad_fn=<NllLossBackward>)
tensor(1.0608, device='cuda:0', grad_fn=<NllLossBackward>)
In [0]: