04-29 02:46
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[Artificial Intelligence / Posts] Convolutional Neural Network - Pooling 본문

Artificial Intelligence/Posts

[Artificial Intelligence / Posts] Convolutional Neural Network - Pooling

cinema4dr12 2017. 6. 29. 20:13

by Geol Choi | Jul.


이번 포스팅에서는 회선신경망(Convolutional Neural Network; CNN)의 ConvNet 구조의 Conv 레이어 사이에서 이미지의 사이즈를 줄임으로써 파라미터 개수와 계산 시간을 줄이기 위한 방법으로 사용되는 풀링(Pooling)에 대해 알아보도록 한다.


[이미지 출처: CS231n Convolutional Neural Networks for Visual Recognition]


Theory

특히 많은 양의 픽셀을 갖는 복잡한 딥러닝 문제에 있어 CNN의 계산속도를 향상시키기 위해 CNN 구조에 Pooling Layer를 포함시킨다. 풀링은 회선 레이어(Convolutional Layer)에서 이미지의 크기와 해상도를 점차 줄여나가면서 계산량을 줄이고 이로써 계산속도를 높이고 과도적합(Overfitting)을 조절한다. Pooling Layer에서 실질적인 학습은 이루어지지 않는다.


(1) Max Pooling

Max Pooling은 대표적인 Pooling 방법이며, 정해진 Window 영역 내의 픽셀 밝기(Intensities)들 중 가장 높은 픽셀을 선택하는 방법이다 [D. Scherer, 2010].


이를 수학적으로 표현하면 다음과 같다:


\(a_{j} = \mathrm{max}_{N \times N}(a_{i}^{n \times n}u(n,n))\)


이 함수를 Max Pooling 함수하고 하며, 여기서, \(u(n,n)\)은 Window Function이라고 한다.


예를 들어, 4×4 크기의 픽셀 배열의 밝기값에 대한 Max Pooling 결과는 다음 이미지와 같다:




(2) Average Pooling

Average Pooling은 정해진 Window 영역 내의 픽셀 밝기(Intensities)들의 평균값을 계산하는 방법이며, 다음과 같은 수식으로 표현할 수 있다:


\(a_{j} = \mathrm{ave}_{N \times N}(a_{i}^{n \times n}u(n,n))\)


 


(3) 기타 Pooling 방법

Max Pooling과 Average Pooling 외에도 다음과 같은 Pooling 방법이 있다:


\(a_{j} = \mathrm{tanh}\begin{pmatrix} \beta \sum_{N \times N}a_{i}^{n \times n} + b \end{pmatrix}\)


그러나, Max Pooling이 가장 일반적인 방법이며 대부분의 상황에서 성능도 다른 방법들에 비해 뛰어난 것으로 알려져 있다.


Algorithm

사실 알고리즘이라고 거창하게 이야기할 것도 없이 방법은 매우 간단하다:


(1) 입력 이미지를 읽는다.

(2) 출력 이미지를 입력 이미지의 1/4배(폭과 높이를 각각 1/2배)로 설정한다.

(3) Max Pooling 계산을 할 Window의 Top-left Corner 픽셀의 인덱스를 결정한다.

(4) 각 Window에 대하여 해당 픽셀의 최대값을 계산한다.

Implementation

(1) 직렬코드

앞서 언급한 알고리즘을 토대로 다음과 같이 직렬 코드를 작성하였다:


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
40
41
#####################################################################################
# MAX POOLING FOR CNN (SERIAL CODE)
# @ Author: Geol Choi, ph.D.
# @ Date: Jun. 23, 2017
#####################################################################################
 
## remove variables
base::rm(list = ls())
 
## load library package
base::require(EBImage)
 
## read image data
input_img <- EBImage::readImage(files="./wallpaper.jpg")
 
## initialize result image
res_img.arr <- base::array(0.0, base::c(base::nrow(input_img)/2, base::ncol(input_img)/23))
 
## transform 
input_img.val <- EBImage::imageData(input_img)
 
i_seq <- base::seq(from=1, to=base::nrow(input_img.val), by=2)
j_seq <- base::seq(from=1, to=base::ncol(input_img.val), by=2)
 
xx <- 0
for(ii in i_seq) {
  xx <- xx + 1
  yy <- 0
  
  for(jj in j_seq) {
    yy <-yy + 1 
    R <- base::max(input_img.val[ii:(ii+1), jj:(jj+1), 1])
    G <- base::max(input_img.val[ii:(ii+1), jj:(jj+1), 2])
    B <- base::max(input_img.val[ii:(ii+1), jj:(jj+1), 3])
    res_img.arr[xx, yy, ] <- base::c(R, G, B)
  }
}
 
res_img <- EBImage::Image(res_img.arr, colormode="Color")
 
EBImage::display(x=res_img, method="raster")
cs

 

참고로 계산을 위해 사용한 이미지 "wallpaper.jpg"는 사이즈 1920×1080의 이미지이며, 직렬 계산 방식으로 작성한 위의 코드를 이용하여 계산한 결과 본 필자의 시스템에서 14.57초 걸렸다:


 사용자  시스템 elapsed 
  14.21    0.10   14.57


(2) 병렬코드


계산 시간을 줄이기 위해 위의 코드를 CPU 기반 병렬코드로 변환하였다. 코드 자체에서의 자세한 설명은 생략하되, R에서의 병렬계산 방법에 대한 자세한 내용은 필자가 작성한 포스팅 R에서 병렬처리하기를 참고하기 바란다.


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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#####################################################################################
# MAX POOLING FOR CNN (PARALLEL CODE)
# @ Author: Geol Choi, ph.D.
# @ Date: Jun. 23, 2017
#####################################################################################
 
## remove variables
base::rm(list = ls())
 
## load library package
if(!base::require(parallel)) {
  install.packages("parallel")
}
base::require(parallel)
 
if(!base::require(foreach)) {
  install.packages("foreach")
}
base::require(foreach)
 
if(!base::require(doParallel)) {
  install.packages("doParallel")
}
base::require(doParallel)
 
if(!base::require(EBImage)) {
  base::source("https://bioconductor.org/biocLite.R")
  BiocInstaller::biocLite("EBImage")
}
base::require(EBImage)
 
# 코어 개수 획득
numCores <- parallel::detectCores() - 1
 
# 클러스터 초기화
myCluster <- parallel::makeCluster(numCores)
doParallel::registerDoParallel(myCluster)
 
# 변수 등록
img <- EBImage::readImage(files="./wallpaper.jpg")
arrImg <- base::array(img, base::dim(img))
parallel::clusterExport(myCluster, "arrImg")
 
# CPU 병렬처리 정의
cacheParallel <- function() {
  i_seq <- base::seq(from=1, to=(base::nrow(arrImg) - 1), by=2)
  j_seq <- base::seq(from=1, to=(base::ncol(arrImg) - 1), by=2)
  
  parallel::parSapply(myCluster,
                      i_seq,
                      FUN = function(x) {
                        imgRow <- arrImg[x:(x+1), , ]
                        
                        ret.val <- base::array(0.0, base::c(base::as.integer(base::ncol(arrImg) / 2), 3))
                        
                        yy <- 0
                        for(jj in j_seq) {
                          yy <- yy + 1
                          R <- base::max(imgRow[1:2, jj:(jj+1), 1])
                          G <- base::max(imgRow[1:2, jj:(jj+1), 2])
                          B <- base::max(imgRow[1:2, jj:(jj+1), 3])
                          ret.val[yy, ] <- base::c(R, G, B)
                        }
                        
                        return(ret.val)
                      })
}
 
# cacheParallel 함수 실행
elapse <- base::system.time(out <- cacheParallel())
base::print(elapse)
 
# 클러스터 중지
parallel::stopCluster(myCluster)
 
# 결과 이미지 초기
res_img <- base::array(0.0,
                       base::c(base::as.integer(base::nrow(arrImg)/2),
                               base::as.integer(base::ncol(arrImg)/2),
                               3)
                       )
 
out_t <- base::t(out)
offset <- base::dim(img)[2]/2
 
initRow <- 1
for(i in 1:base::dim(img)[3]) {
  res_img[,,i] <- out_t[, initRow:(initRow + offset - 1)]
  initRow <- initRow + offset
}
 
res_img <- EBImage::Image(res_img, colormode = "Color")
EBImage::display(x=res_img, method="raster")
cs



위의 Max Pooling 병렬코드를 통해 계산한 결과는 다음과 같았다:


 사용자  시스템 elapsed 
   0.11    0.00    4.76


필자의 계산 환경에서 사용된 CPU 코어는모두 3개이며, 앞서 직렬코드 계산 시 사용한 1개의 코어의 경우에 비해 약 1/3의 시간이 단축되었다.


다음 이미지는 예제 계산 시 사용한 이미지이다.


wallpaper.jpg


이로써 Pooling에 대한 포스팅을 마치고자 한다.

Comments