R) DL - torchvision CNN kmnist

R) DL - torchvision CNN kmnist

CRAN RStudio mirror downloads
R의 torchvision 패키지에 있는 kmnist 데이터로 CNN 학습을 해보자.

데이터 준비

학습용 데이터와 평가용 데이터를 각각 ds_train, ds_test 객체에 저장하고 이를 확인해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
library("torchvision")
library("torch")

ds_train = kmnist_dataset(root = ".",
download = TRUE,
train = TRUE,
transform = transform_to_tensor)
ds_test = kmnist_dataset(root = ".",
download = TRUE,
train = TRUE,
transform = transform_to_tensor)
ds_train$data[1, 1:5, 10:20]
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
## [1,] 0 0 118 255 255 124 1 0 0 0 0
## [2,] 0 36 238 255 146 2 0 0 0 0 0
## [3,] 12 203 255 220 14 0 0 0 11 132 95
## [4,] 149 255 251 66 0 0 0 7 168 136 10
## [5,] 247 255 156 1 0 0 0 130 201 9 0

첫 번째 데이터의 일부를 출력했을 때 최소값이 0, 최대값이 255인데 정상적으로 변환이 되었다면 최대값이 1이어야 한다.

배치 사이즈는 32로 정한다. 이렇게 되면 60000개를 32로 나눈꼴이 되어 dl 객체의 길이는 1875가 됨을 알 수 있다.

1
2
3
dl = dataloader(ds_train, batch_size = 32, shuffle = TRUE)
length(dl)
## [1] 1875

신경망 구조 정의

model_kmnist_init() 함수에는 신경망 구조 관련 파라미터를 설정한다. 이는 model_kmnist_frame() 함수에서 정의하는 각 구조에 설정이 할당된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
model_kmnist_init = function(){
self$conv1 = nn_conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)
self$conv2 = nn_conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride = 1)
self$dropout1 = nn_dropout2d(p = 0.25)
self$dropout2 = nn_dropout2d(p = 0.5)
self$fc1 = nn_linear(in_features = 9216, out_features = 128)
self$fc2 = nn_linear(in_features = 128, out_features = 10)
}

model_kmnist_frame = function(x){
x = self$conv1(x)
x = nnf_relu(x)
x = self$conv2(x)
x = nnf_relu(x)
x = nnf_max_pool2d(x, 2)
x = self$dropout1(x)
x = torch_flatten(x, start_dim = 2)
x = self$fc1(x)
x = nnf_relu(x)
x = self$dropout2(x)
x = self$fc2(x)
output = nnf_log_softmax(x, dim = 1)
return(output)
}

net = nn_module(classname = "cnn_kmnist",
initialize = model_kmnist_init,
forward = model_kmnist_frame)
model = net()

하이퍼파라미터 설정

모델 학습은 cpu로 하며 Adam Optimizer(Adaptive Moment estimation)를 사용하고 학습율은 0.01로 지정하였다.

1
2
model$to(device = "cpu")
optimizer = optim_adam(model$parameters, lr = 0.01)

학습

학습 결과물의 기록을 위해 df_loss를 만들고 학습 진행상황을 잘 파악하기 위해 중간중간 cat() 함수로 Loss가 출력되도록 하였다. 그렇게 총 5 epoch를 학습하는 코드와 그 출력은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
df_loss = data.frame(epoch = rep(1:5, each = length(dl)),
loss = 0)
head(df_loss)
## epoch loss
## 1 1 0
## 2 1 0
## 3 1 0
## 4 1 0
## 5 1 0
## 6 1 0

for(epoch in 1:5){

loss_vec = c()

for(batch in enumerate(dl)){ # 1875
optimizer$zero_grad() # initialize
output = model(batch[[1]]$to(device = "cpu")) # get model predictions
loss = nnf_cross_entropy(input = output,
target = batch[[2]]$to(device = "cpu")) # calculate loss
loss$backward() # calculate gradient
optimizer$step() # apply weight updates

loss_vec = c(loss_vec, loss$item())
cat(sprintf("\rLoss mean: %3f", mean(loss_vec)))
}
cat(sprintf("\rLoss at epoch %d: %3f\n\n", epoch, mean(loss_vec)))
df_loss[df_loss$epoch == epoch, "loss"] = loss_vec
}
##
## Loss at epoch 1: 2.039157
##
## Loss at epoch 2: 1.874734
##
## Loss at epoch 3: 1.837059
##
## Loss at epoch 4: 1.826195
##
## Loss at epoch 5: 1.812667

모델 저장

torch 패키지에서는 torch_save() 함수로 모델 객체를 별도의 파일로 저장하고 torch_load() 함수로 이를 불러오는데 아직 torch 패키지에서는 “This function is experimental, don’t use for long term storage.” 라며 조심스러운 메세지를 torch_save() 함수의 도움말에 적어놓았다.

1
2
torch_save(obj = model, "torch_kmnist_b32_5epoch")
model_load = torch_load("torch_kmnist_b32_5epoch")

평가

테스트 데이터 세트의 첫 번째 배치 데이터를 뽑아서 평가해보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dl_test = dataloader(ds_test, batch_size = 32, shuffle = FALSE)
enum = enumerate(dl_test)

torch_manual_seed(123)
tensor_pred = model(enum[[1]][[1]]$to(device = "cpu"))
tensor_pred[1:3, ]
## torch_tensor
## Columns 1 to 8-26.8910 -18.5440 -12.7094 -1.8727 -6.6776 -8.3401 -24.4058 -17.7274
## -8.3716 -11.4243 -23.4084 -13.6978 -14.1819 -10.9714 -11.0589 -1.8250
## -1.3839 -11.2831 -21.6364 -23.2783 -6.9134 -14.5591 -26.8051 -17.1378
##
## Columns 9 to 10 -0.5630 -27.8833
## -7.5339 -17.0489
## -12.6150 -10.3133
## [ CPUFloatType{3,10} ]

pred_class_model = apply(as_array(tensor_pred),
MARGIN = 1, FUN = "which.max")
pred_class_model
## [1] 9 8 1 2 5 3 5 4 5 2 6 5 1 6 8 3 2 8 10 9 8 4 8 6 10 7 3 8 7 1 10 7

dl_test$dataset$targets[1:32]
## [1] 9 8 1 2 5 3 5 9 2 2 6 2 1 6 8 7 2 8 10 6 8 4 8 6 7 7 3 8 7 1 10 7

Metrics::accuracy(pred_class_model, dl_test$dataset$targets[1:32])
## [1] 0.8125

여기서 모델의 출력값을 정제할 때 각 row의 최대값의 위치를 얻기위해 which.max() 함수를 사용했다. 좀 더 자세하게 확인하고자 한다면 모델의 구조를 다시 보자. 말단에 nnf_log_softmax() 함수로 softmax를 정의한 것을 알 수 있다. 그래서 각 class별 확률 값이 log로 출력되는데 이 부분은 tensor_pred 객체에서 확인할 수 있다.

아무튼 첫 32개 데이터만 확인했지만 만족할만한 정확도가 아니라… 좀 더 학습해야 하지 않을까 한다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×