내 풀이 링크:https://github.com/lionkingchuchu/cs231n.git
이번 과제는 본격적으로 framwork 를 사용해서 신경망을 구현해 볼 차례이다. 우리가 framework를 사용하는 이유는 먼저 framework의 미리 구현된 함수가 간편한 것도 있지만, framework를 사용하면 GPU를 사용해 계산을 처리하는 것이 쉽게 가능하기 때문이다.
우리가 일반적으로 파이썬 코드를 실행하면 보통 CPU에서 계산을 처리하는데, CPU는 10~20개의 코어, 쓰레드를 사용해 직렬로 계산하여 코어가 적은 대신 복잡하거나 긴 수식을 빠르게 계산할 수 있는 반면, GPU는 1000개 이상 단위의 코어를 사용해 병렬적으로 계산해 여러개의 간단한 계산을 매우 빠른 속도로 처리할 수 있다. 우리가 다루는 딥러닝 신경망의 계산은 수많은 뉴런들의 간단한 연산이 대부분이기 때문에 CPU보다 GPU에서 프로그램을 돌리는 것이 효율적이다.
보통 framework의 함수들은 GPU에서 연산을 돌리는 옵션을 갖고 있어 GPU 연산을 쉽게 구현 할 수 있다. 그리고 지금까지 우리가 layer를 만들때 backpropagation을 사용하기 위해 forward, backward 에 대한 각각 함수를 만들어서 특히 backward 함수는 chain rule diagram을 열심히 그리며 layer를 구현하였다. framework에서는 backward 함수가 이미 구현되어 있어 우리가 따로 구현할 필요가 없다.
먼저 CIFAR-10 데이터 로드부터 pytorch를 사용하면 쉽게 할 수 있다고 한다. Pytorch를 사용하면 자동으로 preprocessing된 train, val, test data 까지 구현할 수 있다.
NUM_TRAIN = 49000
# The torchvision.transforms package provides tools for preprocessing data
# and for performing data augmentation; here we set up a transform to
# preprocess the data by subtracting the mean RGB value and dividing by the
# standard deviation of each RGB value; we've hardcoded the mean and std.
transform = T.Compose([
T.ToTensor(),
T.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
# We set up a Dataset object for each split (train / val / test); Datasets load
# training examples one at a time, so we wrap each Dataset in a DataLoader which
# iterates through the Dataset and forms minibatches. We divide the CIFAR-10
# training set into train and val sets by passing a Sampler object to the
# DataLoader telling how it should sample from the underlying Dataset.
cifar10_train = dset.CIFAR10('./cs231n/datasets', train=True, download=True,
transform=transform)
loader_train = DataLoader(cifar10_train, batch_size=64,
sampler=sampler.SubsetRandomSampler(range(NUM_TRAIN)))
cifar10_val = dset.CIFAR10('./cs231n/datasets', train=True, download=True,
transform=transform)
loader_val = DataLoader(cifar10_val, batch_size=64,
sampler=sampler.SubsetRandomSampler(range(NUM_TRAIN, 50000)))
cifar10_test = dset.CIFAR10('./cs231n/datasets', train=False, download=True,
transform=transform)
loader_test = DataLoader(cifar10_test, batch_size=64)
다음은 pytorch에도 있지만 pytorch를 사용해 만든 함수중 하나인 flatten()함수에 대한 정보이다. flatten함수는 (N, C, H, W)의 4차원 텐서를 (N, C*H*W) 로 flatten 시켜준다. N개에 대한 각 데이터 차원을 flatten 하고 싶을 때, 특히 신경망 중 FullyConnectedLayer를 만들고 싶을때 활용할 수 있을 것이다.
다음은 pytorch를 사용해 3 layer convnet 함수를 만들어 보는 것이다. 아래의 조건에 맞는 layer들을 구현해야 한다.
- A convolutional layer (with bias) with channel_1 filters, each with shape KW1 x KH1, and zero-padding of two
- ReLU nonlinearity
- A convolutional layer (with bias) with channel_2 filters, each with shape KW2 x KH2, and zero-padding of one
- ReLU nonlinearity
- Fully-connected layer with bias, producing scores for C classes.
torch.nn.functional — PyTorch 1.13 documentation
torch.nn.functional — PyTorch 1.13 documentation
Shortcuts
pytorch.org
torch.nn — PyTorch 1.13 documentation
torch.nn — PyTorch 1.13 documentation
Shortcuts
pytorch.org
pytorch의 각 layer함수에 대한 설명이 있는 링크이다. 여기서 필요한 layer 함수들을 찾아보면 padding, stride, weight, 등을 어떻게 사용할 지 잘 나와있다. 위의 torch.nn.functional 함수는 forward를 구현하는 함수이고, torch.nn 함수는 layer를 만드는 함수이다. 여기서는 우리가 weight, bias를 만들었으므로 torch.nn.functional 함수를 사용한다. (F.relu, F.conv2d 등) 마지막 Fully-connected layer를 구현할 떄에는 위에서 사용한 flatten()함수를 사용한다.
def three_layer_convnet(x, params):
conv_w1, conv_b1, conv_w2, conv_b2, fc_w, fc_b = params
scores = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
x = F.relu(F.conv2d(x, conv_w1, conv_b1, padding=2))
x = F.relu(F.conv2d(x, conv_w2, conv_b2, padding=1))
x = flatten(x).mm(fc_w) + fc_b
scores = x
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return scores
다음은 pytorch를 사용한 random_weight() 함수에 대한 설명이다. random weight함수는 parameter의 weight를 iniialize 할떄 사용하는데, 초기 weight값들을 Kaiming normalization을 사용해 iniialize 해준다. zero_weight는 간단하게 말 그대로 weight들을 0 으로 initialize 해준다. bias를 만드는데 사용 할 것이다.
우리가 이렇게 weight를 임의로 만들어 주어서 각 layer에 부여해 주어도 되지만, 그냥 pytorch에서 layer를 만들면, 가장 기본으로 Xavier initialization을 사용해 weight initialization을 자동으로 해 준다.
다음은 pytorch를 사용해 구현한 check_accuracy 함수이다. 특이한 점으로는 accuracy를 구할 때에는 with torch.no_grad()를 위에 써주어야 하는데, 여기서 torch.no_grad()는 모델을 돌릴때 gradient update를 하지 않는다는 뜻이다. accuracy를 구할 때에는 train하는 목적이 아닌 test하는 목적이므로 torch.no_grad()를 반드시 써주고, model을 이용해 scores 얻기, max를 통해 predict 해주고, 예측값과 y(정답) 값을 비교해주며 num_correct / num_samples로 정확도를 구해준다.
다음은 pytorch를 사용해 구현한 train 함수이다. enumerate를 사용해 t를 구현하고, x와 y를 추출하고, x를 model을 통과시켜주어 예측 scores들을 얻어낸다. 얻은 scores를 pytorch의 cross_entropy layer를 통과시켜 loss를 얻어내고, 또 pytorch를 사용한 loss.backward()를 하면 pytorch가 자동으로 backpropagation을 해주어 params: 안의 각 weight, bias등의 파라미터들의 grads가 w.grad 로 저장되게 된다.
저장된 w.grad 와 learning rate를 곱해서 각 w에 반영해주면 SGD 방식의 gradient update가 완료된다. 여기서도 no_grad()를 사용해 gradient update 과정을 backward pass를 하지 않도록 한다. 마지막으로 w.grad.zero_()를 통해 저장되었던 w.grad들을 0으로 없애준다.
다음은 convnet의 각 parameter들을 위의 random_weight, zero_weight 함수로 구현하고, 직접 구현한 parameter 초기값을 사용해 train해보는 문제이다. 중간중간 convnet input - output dimension 계산을 신경써서 parameter의 사이즈를 계산하여 구현한다.
learning_rate = 3e-3
channel_1 = 32
channel_2 = 16
conv_w1 = None
conv_b1 = None
conv_w2 = None
conv_b2 = None
fc_w = None
fc_b = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
conv_w1 = random_weight((channel_1, 3, 5, 5))
conv_b1 = zero_weight((channel_1,))
# out.shape = (N,32,32,32)
conv_w2 = random_weight((channel_2, channel_1, 3, 3))
conv_b2 = zero_weight((channel_2,))
# out.shape = (N,16,32,32)
fc_w = random_weight((16 * 32 * 32, 10))
fc_b = zero_weight((10,))
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
params = [conv_w1, conv_b1, conv_w2, conv_b2, fc_w, fc_b]
train_part2(three_layer_convnet, params, learning_rate)
다음은 ThreeLayerConvNet을 nn.Module을 이용해 구현하는 문제이다. 아까 설명했던 nn.function() 함수와 nn.()함수의 차이이다. 함수 목록들은 torch.nn — PyTorch 1.13 documentation 에서 찾을 수 있고, 이를 활용하면 layer을 만들어 준다. 문제에서 특별히 kaiming normal을 사용해 weight initialize 하라 했으므로 nn.init.kaiming_normal_()함수로 weight initialize를 별도로 해준다.
forward 함수는 아까 구한 layer들을 forward 해주고, ReLU는 별도의 layer구현을 안해도 되어 nn.function()을 이용해 구현해준다. 그러고 FC layer 전에 flatten 해주고 FC layer 통과시켜 점수를 얻는다.
class ThreeLayerConvNet(nn.Module):
def __init__(self, in_channel, channel_1, channel_2, num_classes):
super().__init__()
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
self.conv1 = nn.Conv2d(in_channel, channel_1, kernel_size = 5, padding = 2)
nn.init.kaiming_normal_(self.conv1.weight)
self.conv2 = nn.Conv2d(channel_1, channel_2, kernel_size = 3, padding = 1)
nn.init.kaiming_normal_(self.conv2.weight)
self.fc = nn.Linear(channel_2 * 32 * 32, num_classes)
nn.init.kaiming_normal_(self.fc.weight)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
def forward(self, x):
scores = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = flatten(x)
scores = self.fc(x)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return scores
def test_ThreeLayerConvNet():
x = torch.zeros((64, 3, 32, 32), dtype=dtype) # minibatch size 64, image size [3, 32, 32]
model = ThreeLayerConvNet(in_channel=3, channel_1=12, channel_2=8, num_classes=10)
scores = model(x)
print(scores.size()) # you should see [64, 10]
test_ThreeLayerConvNet()
다음은 check_accuracy 함수인데, 이전의 model은 params를 외부에 저장하여 data와 params를 input으로 받아 train하고, 이에 따라 외부의 parameter gradient update, 그리고 test 했었다. 방금 위에서 구현한 모델은 반면에 params가 model 내부에 저장되어 있으므로 data만 입력해주면 알아서 test가 가능하다.
train함수 역시 parameter가 model내부에 저장되어 model의 입력값이 하나이고, 아까는 우리가 직접 SGD를 구현해서 gradient update 해주었지만 여기서는 pytorch의 optimizer.step() 을 사용해 Adam, Adagrad 등의 pytorch에서 구현된 optimizer를 사용해 업데이트 해 줄 수 있다. torch. optim 정보: torch.optim — PyTorch 1.13 documentation
다음으로 위의 함수를 사용해 주어진 lr, channel, SGD 을 사용해 model을 train해본다. 45% 이상 나와야된다고 한다.
learning_rate = 3e-3
channel_1 = 32
channel_2 = 16
model = None
optimizer = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = ThreeLayerConvNet(3, channel_1, channel_2, 10)
optimizer = optim.SGD(model.parameters(), lr = learning_rate)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
train_part34(model, optimizer)
다음은 위의 model 만드는 과정을 nn.Sequential을 통해 정말 간단하게 구현해 보는 것이다. nn.Sequential()은 여러개의 layer를 이어주는 역할을 한다. channel 계산을 잘 해주어 각 layer에 대입해준다. optim은 SGD, nesterov momentum을 사용한다. 정확도 55%이상 나와야 된다고 한다.
channel_1 = 32
channel_2 = 16
learning_rate = 1e-2
model = None
optimizer = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
model = nn.Sequential(nn.Conv2d(3, channel_1, kernel_size = 5, padding = 2),
nn.ReLU(),
nn.Conv2d(channel_1, channel_2, kernel_size = 3, padding = 1),
nn.ReLU(),
nn.Flatten(),
nn.Linear(channel_2 * 32 * 32, 10))
optimizer = optim.SGD(model.parameters(), lr = learning_rate,
momentum = 0.9, nesterov = True)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
train_part34(model, optimizer)
마지막으로는 지금까지 배워온 것들로 내가 직접 모델을 만드는데, 10 epochs 내에 val accuracy가 70% 이상인 모델을 만드는 것이다. 여러가지 사용해 보았는데 나는 (Batch Norm - Conv - Relu) * 3 - (FC) - (FC) - (softmax) 형식의 모델을 사용해 보았다. 사실 이 문제를 풀때에는 다른 기본적인 VGG, Alexnet과 같은 기본적인 network의 구조를 아직 모르는 상태여서 이것저것 해보다가 시간이 오래 걸리기도 했고, val acc도 겨우겨우 간신히 70%를 넘겼다.
out1_hwshape, out2_hwshape, out3_hwshape 을 통해 각 convolution layer를 지나며 H, W가 downsample 되는 정도를 매번 계산하여, 내가 kernel_size나 padding등의 숫자를 바꾸어도 마지막 FC layer에서 자동으로 dimension이 계산될 수 있도록 하였다.
model = None
optimizer = None
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
channel_1 = 64
channel_2 = 32
channel_3 = 16
learning_rate = 5e-4
pad_1 = 3
pad_2 = 2
kernel_size_1 = 7
kernel_size_2 = 5
kernel_size_3 = 3
out1_hwshape = 32 + 2 * pad_1 - (kernel_size_1 - 1)
out2_hwshape = out1_hwshape + 2 * pad_2 - (kernel_size_2 - 1)
out3_hwshape = out2_hwshape - (kernel_size_3 - 1)
model = nn.Sequential(nn.BatchNorm2d(3),
nn.Conv2d(3, channel_1, kernel_size = kernel_size_1, padding = pad_1),
nn.ReLU(),
nn.BatchNorm2d(channel_1),
nn.Conv2d(channel_1, channel_2, kernel_size = kernel_size_2, padding = pad_2),
nn.ReLU(),
nn.BatchNorm2d(channel_2),
nn.Conv2d(channel_2, channel_3, kernel_size = kernel_size_3),
nn.ReLU(),
nn.Flatten(),
nn.Linear(channel_3 * out3_hwshape * out3_hwshape, 32),
nn.Linear(32,10))
optimizer = optim.SGD(model.parameters(), lr = learning_rate,
momentum = 0.9, nesterov = True)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# You should get at least 70% accuracy
train_part34(model, optimizer, epochs=10)
Pytorch의 기초적인 사용법을 익히고 acc가 좋지는 않지만 간단한 모델을 만들어 보았는데 재미있기도 했고 모델의 정확도를 최대로 끌어 올리는 것이 생각보다 정망 어렵다는 것을 알 수 있었다.
'cs231n' 카테고리의 다른 글
cs231n Assignment 3: Q1 (Vanilla RNN 구현) (0) | 2023.02.25 |
---|---|
cs231n Assignment 2: Q6 (Saliency map, Fooling images, Class visualization 구현) (0) | 2023.02.21 |
cs231n Assignment 2: Q4 (CNN, Group Normalization 구현) (1) | 2023.02.18 |
cs231n Assignment 2: Q3 (Dropout 구현) (0) | 2023.02.15 |
cs231n Assignment 2: Q2 (Batch Normalization, Layer Normalization 구현) (0) | 2023.02.14 |