Basic Deep Learning/Dive into Deep Learning 리뷰

[D2L] 7.1 Modern CNN

needmorecaffeine 2022. 7. 26. 10:51

 이전 장에서 소개한 LeNet에 대해 간략히 소개하였는데 LeNet은 CV필드에서 활발하게 사용되지 못했다. LeNet은 작은 크기의 데이터셋에서는 좋은 성능을 보였으나 좀 더 현실적이고 큰 데이터셋에서는 성능이 좋지 않았기 때문이다.

 

 Neural Network에 관해서는 1990년대부터 활발히 연구되었지만 multi channel과 multi layer를 충분히 다룰 만한 computing resource가 없었고 parameter initialization, optimization, regularization 과 같은 스킬에 대한 연구는 당시 많이 이뤄지지 않았다. 그래서 이 때는 end to end(pixel to classification)이 아닌 적은 데이터로 feature extractor를 통해 얻은 feature를 classifier에 dump하는 방식으로 training을 진행했다.

 

 위에서 말한 과거의 한계점들이 개선되고 현재의 연구 진행 방향이 확립되면서 여러 CNN 계열의 모델들이 등장하였고 엄청난 성능 향상을 보여줬다. 새로 등장했던 모델들에 대해 다뤄보겠다.

 

1. AlexNet

 AlexNet은 8-layer CNN 모델로 2012년에 등장해 기존 대비 뚜렷한 성능향상을 보여줬다. 특히 위에서 말했던 이전의 학습 방식, 즉 manually designed feature로 학습하던 관습을 깨고 데이터를 통해 학습 과정에서 feature를 학습하는 선구적인 모델이었다고 평가받는다. LeNet과 AlexNet을 비교하면 다음과 같다.

 

 구조는 매우 비슷해보이지만 AlexNet이 가지는 분명한 차이점이 있다.

1) AlexNet이 훨씬 깊다.

2) AlexNet에서는 ReLU activation을 사용하였다. 이로 인해 연산이 더 간단해졌고 다른 parameter initialization이 가능해 gradient vanishing 문제를 방지할 수 있다.

3) 더 큰 단위의 object 파악을 위해 첫 conv layer의 window가 11*11이고 이후 conv layer window 사이즈는 3 또는 5이다.

4) Maxpooling을 사용하였다.

5) 열배 이상의 convolution channel을 가지고 있다.

6) fcn이 추가되었고 그 output 크기도 많이 커졌다. 이에 따라 GPU 연산량이 더 많아졌다.

7) 모델의 복잡도를 dropout을 통해 다뤘으며 data augmentation 기법이 사용되었다.

 

 코드로 구현하면 다음과 같다.

net = nn.Sequential(
    # Here, we use a larger 11 x 11 window to capture objects. At the same
    # time, we use a stride of 4 to greatly reduce the height and width of the
    # output. Here, the number of output channels is much larger than that in
    # LeNet
    nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # Make the convolution window smaller, set padding to 2 for consistent
    # height and width across the input and output, and increase the number of
    # output channels
    nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # Use three successive convolutional layers and a smaller convolution
    # window. Except for the final convolutional layer, the number of output
    # channels is further increased. Pooling layers are not used to reduce the
    # height and width of input after the first two convolutional layers
    nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Flatten(),
    # Here, the number of outputs of the fully-connected layer is several
    # times larger than that in LeNet. Use the dropout layer to mitigate
    # overfitting
    nn.Linear(6400, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    nn.Linear(4096, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    # Output layer. Since we are using Fashion-MNIST, the number of classes is
    # 10, instead of 1000 as in the paper
    nn.Linear(4096, 10))

 위 구조로 연산되는 output shape을 살펴보면 다음과 같다.

# Fashion MNIST >> images have lower resolution(28*28) >> upsample (224*224)

batch_size = 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)

lr, num_epochs = 0.01, 10
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

 training 결과이다.


2. Network Using Blocks (VGG)

  Block을 사용한다는 것은 layer의 pattern을 반복한다는 것을 의미한다. block은 연속된 conv layer와 down sampling을 위한 maxpooling layer로 구성되어 있다. block을 함수로 구현하면 다음과 같다.

def vgg_block(num_convs, in_channels, out_channels):
	layers = []
    for _ in range(num_convs):
    	layers.append(nn.Conv2d(in_channels, out_channels, kernel_size = 3, padding = 1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size = 2, stride = 2))
    return nn.Sequential(*layers)

VGG의 구조이다.

 VGG block들이 연속해서 쌓여있으며 아래의 conv_arch 튜플형으로 block별 conv layer, output channel을 명시한다.

원조 VGG는 5개의 convolution block을 가지고 있고 첫 번째 두개의 block은 하나의 conv layer를, 나머지 세개의 block은 두개의 conv layer를 가지고 있어 총 8개의 conv layer와 이후 3개의 fcn 구조로 이루어져 VGG-11이라고 불린다.

conv_arch = ((1,64), (1,128), (2,256), (2,512), (2,512))

def vgg(conv_arch):
  conv_blks = []
  in_channels = 1

  # Conv part
  for (num_convs, out_channels) in conv_arch:
    conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
    in_channels = out_channels
  
  return nn.Sequential(
      *conv_blks, nn.Flatten(),
      # FCN
      nn.Linear(out_channels*7*7, 4096), nn.ReLU(), nn.Dropout(0.5),
      nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
      nn.Linear(4096, 10)
  )

net = vgg(conv_arch)

 training을 할 때 VGG는 특히 현재 학습할 fashion mnist에 대해 위대로 동일하게 진행할 필요가 없어 더 적은 channel을 설정하여 진행한다.

ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)

lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


3. NiN

 이전까지 다룬 LeNet, AlexNet, VGG 모두 공통의 패턴이 있는데, 연속된 conv layer, pooling layer를 지나고 마지막으로 fc layer를 거쳐 spatial structure를 가져간다. 세 모델은 얼마나 깊고 넓게 구성했냐의 차이가 있지, 이런 구조를 가진다는 것은 공통적이다.

 

 위 구조와 반대로 fc layer을 먼저 통과하는 것도 고려해볼 수 있는데 이 때 dense layer를 초기에 사용하게 되면 spatial structure를 유지하는 것은 어려워질 수 있다. 이에 대한 대한으로 등장한 것이 Network in Network, NiN으로 각각의 pixel에 대해 channel에 MLP 연산을 하는 것이다. 정확히 말하면 conv layer는 4차원 텐서(example, channel, height, width), fc layer는 2차원 텐서(example, feature)였고 NiN은 이 때 각각의 픽셀 위치(height, width)에 대해 fc layer를 적용하는 것이다. 이는 각 요소의 spatial dimension(height, width)을 exampl로, channel을 feature로 생각하는 것이다.

 

 다음 도식에서 확인할 수 있듯이, NiN block은 하나의 conv layer와 두개의 1x1 conv layer를 연속해서 가진다. 이는 per-pixel fully connected layer with ReLU activation 기능을 하는 것이다. 여기서 conv window의 크기는 사용자에 따라 설정하고 이후 1x1 conv연산을 한다. 이렇게 하면 convolution stack 내에서 per-pixel nonlinearity를 가지게 된다.

 NiN Block을 구현한 함수는 다음과 같다.

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
  return nn.Sequential(
      nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
      nn.ReLU(),
      nn.Conv2d(out_channels, out_channels, kernel_size = 1), nn.ReLU(),
      nn.Conv2d(out_channels, out_channels, kernel_size = 1), nn.ReLU())

 구조를 보면 NiN은 AlexNet에 착안하여 만들어졌기에 conv window의 size가 AlexNet과 그 쌍이 같음을 알 수 있다. 

하지만 둘 사이의 큰 차이가 존재하는데 NiN은 fc layer을 사용하지 않았고 대신에 NiN block에서 output channel의 갯수가 label class의 갯수와 같게 설정하였고 global average pooling layer가 존재한다. 이러한 NiN 구조의 장점은 오버피팅의 가능성이 줄고 모델 파라미터 갯수가 훨씬 줄어들었다는 점이다.

net = nn.Sequential(
    nin_block(1, 96, kernel_size=11, strides=4, padding=0),
    nn.MaxPool2d(3, stride=2),
    nin_block(96, 256, kernel_size=5, strides=1, padding=2),
    nn.MaxPool2d(3, stride=2),
    nin_block(256, 384, kernel_size=3, strides=1, padding=1),
    nn.MaxPool2d(3, stride=2),
    nn.Dropout(0.5),
    # There are 10 label classes
    nin_block(384, 10, kernel_size=3, strides=1, padding=1),
    nn.AdaptiveAvgPool2d((1, 1)),
    # Transform the four-dimensional output into two-dimensional output with a
    # shape of (batch size, 10)
    nn.Flatten())

 학습을 진행한 결과이다.

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


4. GoogleNet (Networks with Parallel Concatenations)

  GoogleNet은 NiN의 강점과 반복되는 block 구조의 강점을 결합한 모델이다. GoogleNet의 핵심적인 인사이트는 다양한 크기의 kernel들의 조합의 이점을 가져가는 것이다.

 

GoogleNet의 conv block은 Inception block이라고 불리는데 영화 인셉션의 "We need to go deeper"의 대사에 착안한 것이다.

 가운데 두 개의 path에서 1x1 conv를 하는데 이때 channel의 갯수를 바꾸기 위함이고 모든 네개의 path가 output의 height, width를 일치시키기 위해 적잘한 padding을 포함하고 있다. 각각의 path의 output이 마지막에 concatenate 된다.

class Inception(nn.Module):
    # `c1`--`c4` are the number of output channels for each path
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # Path 1 is a single 1 x 1 convolutional layer
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3
        # convolutional layer
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5
        # convolutional layer
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1
        # convolutional layer
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # Concatenate the outputs on the channel dimension
        return torch.cat((p1, p2, p3, p4), dim=1)

  이렇게 구현한 모델의 성능이 좋은 이유는 다양한 사이즈의 filter들로 조합되어 있어 단일 사이즈일 때 놓칠 수 있는 detail을 수합하기 때문이다.

 

 GoogleNet의 9개의 inception block과 global average pooling을 사용한다. 첫 module은 AlexNet, LeNet과 유사하며 block의 stack은 VGG에서 착안하였다.

# First Module

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
                   
# Sencond Module

b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                   nn.ReLU(),
                   nn.Conv2d(64, 192, kernel_size=3, padding=1),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

# Third module : 2 Inception Blocks

b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                   Inception(256, 128, (128, 192), (32, 96), 64),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
                   
# Fourth module : 5 Inception Blocks

b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                   Inception(512, 160, (112, 224), (24, 64), 64),
                   Inception(512, 128, (128, 256), (24, 64), 64),
                   Inception(512, 112, (144, 288), (32, 64), 64),
                   Inception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

# Fifth module : 2 Inception Blocks

b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                   Inception(512, 160, (112, 224), (24, 64), 64),
                   Inception(512, 128, (128, 256), (24, 64), 64),
                   Inception(512, 112, (144, 288), (32, 64), 64),
                   Inception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

# FC
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

 

 VGG와 마찬가지로 이번 경우의 학습을 위해 원래의 input크기를 224*224에서 96*96으로 바꿔 진행한다.

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


5. Residual Network(ResNet)

점점 더 모델들이 깊어지는 경향을 갖는데 깊어지더라도 network는 더욱 expressive해져야 layer를 더하는 것이 의미가 있다. 그 의미와 의미를 가지기 위한 개념을 다시 잡아보면 다음과 같다.

 

 F라는 특정 모델 구조가 도달할 수 있는 class of function이 있다고 하자. F에 속하는 특정 function f에는 weight과 bias가 포함되어 있는 parameter set을 가지고 있는데 이는 dataset을 통해 학습이 가능하다. 이 때 f*를 실제 찾고 싶었던 것을 찾는 function이라고 하고 이 f*가 F에 포함되어 있다면 좋은 것이고 옳은 방향이다. 하지만 이는 매우 운이 좋은 경우이다. 그래서  f*F를 F에서 best bet이라고 하면 다음과 같은 optimization problem을 설정할 수 있다.

 이 때 더 나은 모델 구조 F'를 다시 디자인하게 된다면 f*F보다는 f*F'가 더 나은 function이라고 기대되지만 F가 F'에 속한다는 보장은 없고 F' 구조가 더 좋은 결과를 갖더라도 f*F가 f*F'가 더 나은 function일 수도 있다. 이를 그림으로 묘사해보자.

 왼쪽의 non nested function을 보면 F6가 F1 보다 더 큰 set이지만 f*에 더 멀어지고 있고 오히려 F3이 f*에 더 가깝기에 더 큰 function class가 f*를 향한다는 보장은 없다. 반면 작은 F를 계속해서 포함하는 nested function은 unested의 이런 문제점을 가지지 않는다.

 

 따라서 더 큰 function이 더 작은 function을 포함한다는 가정만 성립한다면 모델의 expressive power는 더 증가한다고 할 수 있다. 특히 새로 추가된 layer가 항등함수로 학습이 된다면 layer가 추가되어도 여전히 이전 모델과 같은 power를 가지게 된다.

 

 이런 사고를 바탕으로 ResNet이 등장하였고 모든 추가 layer가 항등항수를 하나의 요소로 포함해야 power가 커진다는 점에서 residual block을 고안하게 되었다. 다시말해 desired underlying mapping이 함등함수이고 이 항등함수가 가장 위에서 activation function의 input이 되는 것이 이상적이다.

 왼쪽 regular block은 이전까지의 일반적인 f(x)가 activation function의 input으로 묘사되고 있는데 residual block은 이와 다르게 residual mapping f(x) - x를 하나의 function으로 포함하고 있다. f(x) = x 항등함수를 mapping 하는 것이다. input x를 그대로 들고와 top의 마지막 연산에 반영하는 것을 residual connection, shortcut connection이라 한다. 이럴 경우 당연히 input은 중간 layer를 거치지 않고 더 빨리 forward 할 수 있다.

 

 ResNet은 VGG의 3*3 conv layer를 그대로 따르며 residual block도 3*3 conv layer를 같은 수의 output channel를 가지고 있다. 각각의 conv layer는 batch normalizaiton layer와 ReLU activation을 가진다.

 이 때 1x1 conv layer 연산은 input에 대해 마지막 addition operation을 위해 그 채널을 맞춰주는 기능을 한다. 이것은 아래 코드의 use_1x1conv를 통해 선택할 수 있고 이로 인해 두개의 network type을 가지게 된다.

    """The Residual block of ResNet."""

class Residual(nn.Module):
    def __init__(self, input_channels, num_channels,
                 use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                               kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=1, stride=strides)
        else:
        	self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)
    
    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

 

 

 전체 모델 구조를 보면 다음과 같다.

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.BatchNorm2d(64), nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

def resnet_block(input_channels, num_channels, num_residuals,
                 first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
	return blk
    
# Add all modules to ResNet
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))

 ResNet은 네개의 residual block module로 구성되어 있고 이 중 첫번째 module이 다른 module과 다른 점은 채널의 수가 input의 채널의 수와 같아 1x1 conv연산을 수행하지 않는다.

 

lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


6. Densely Connected Networks(DenseNet)

 DenseNet은 ResNet의 확장 버전으로 이해하면 된다. 그 확장 방식을 살펴보면 다음과 같다.

ResNet에 대해 다시 살펴보면 아래 구조와 같이 liinear한 x와 nonlinear한 g(x)를 더한, f(x) = x + g(x) 로 하나의 block의 연산이 이뤄진다. 이 때 DenseNet은 두 term을 더하는 것이 아닌 concatenate한다는 것에서 차이점이 있다.

 이런 DenseNet은 sequence of function이 매우 복잡하게 증가하는데 다음과 같다.

 마지막 layer는 이전까지의 모든 layer와 densely하게 연결되는데 이러한 이유로 DenseNet이라 불리게 되었다.

DenseNet을 구성하는 두가지 주요소는 dense block과 trasition layer로 dense block은 input과 output을 concatenate하고 transition layer는 채널의 수가 너무 커지지 않도록 control 하는 역할을 한다.

 

def conv_block(input_channels, num_channels):
  return nn.Sequential(
      nn.BatchNorm2d(input_channels), nn.ReLU(),
      nn.Conv2d(input_channels, num_channels, kernel_size = 3, padding = 1))
class DenseBlock(nn.Module):
  def __init__(self, num_convs, input_channels, num_channels):
    super(DenseBlock, self).__init__()
    layer = []
    # each conv block use the same # of output channels
    for i in range(num_convs):
      layer.append(conv_blocks(num_channels * i + input_channels, num_channels))
    self.net = nn.Sequential(*layer)

  def forward(self, X):
    for blk in self.net:
      Y = blk(X)
      # Concatenate the input and output of each block on the channel
      X = torch.cat((X, Y), dim = 1)
    return X

 위 연산을 확인해보면 다음과 같다.

23 = 2(conv block) x 10(output channels) + 3(input channels)로 연산이 되며 이 때 conv block의 채널(output channel)은 input channel에 비해 전체 output channel의 수에 더 큰 영해응ㄹ 주는데 이 때문에 conv block의 채널(output channel)은 growth rate이라 불린다.

 

각각의 dense block을 더하는 것은 채널 수를 증가시키기에 너무 많이 더하는 것은 모델을 매우 복잡하게 만든다. 이를 조절하기 위한 것이 transition layer이고 1x1 conv layer를 통해 채널의 수를 줄이고 average pooling layer를 stride 2로 설정해 height과 width를 절반으로 줄인다.

def trainsition_block(input_channels, num_channels):
  return nn.Sequential(
      nn.BatchNorm2d(input_channels), nn.ReLU(),
      nn.Conv2d(input_channels, num_channels, kernel_size = 1),
      nn.AvgPool2d(kernel_size = 2, stride = 2))

 예시 연산을 살펴보면 이전 Y의 channel을 10으로 줄이고 height과 width를 절반으로 줄여 4가 되었다.

 

 DenseNet 전체 구조를 보면 네개의 dense block으로 구성되어 있다.

# `num_channels`: the current number of channels
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
blks = []
for i, num_convs in enumerate(num_convs_in_dense_blocks):
    blks.append(DenseBlock(num_convs, num_channels, growth_rate))
    # This is the number of output channels in the previous dense block
    num_channels += num_convs * growth_rate
    # A transition layer that halves the number of channels is added between
    # the dense blocks
    if i != len(num_convs_in_dense_blocks) - 1:
        blks.append(transition_block(num_channels, num_channels // 2))
        num_channels = num_channels // 2
        
net = nn.Sequential(
    b1, *blks,
    nn.BatchNorm2d(num_channels), nn.ReLU(),
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(num_channels, 10))
lr, num_epochs, batch_size = 0.1, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

 training 결과이다.

'Basic Deep Learning > Dive into Deep Learning 리뷰' 카테고리의 다른 글

[D2L] 8. RNN  (0) 2022.08.08
[D2L] 9. Modern RNN  (0) 2022.08.08
[D2L] 6. CNN  (0) 2022.07.25
[D2L] 11.1 Optimization and Deep Learning  (0) 2022.07.13
[D2L] 4.8 Numerical Stability and Initialization  (0) 2022.07.11