[ML] Cross Entropy Loss는 Negative Class를 고려할까?

일반적으로 사용되는 아래 CE Loss의 구현을 Softmax를 빼고 보면 Loss Term에 Negative Class에 대한 고려가 들어가있지 않다. (마지막 정리부에서 다루지만 사실 CE Loss와 Softmax를 분리해서 보는 생각이 잘못된 것 같다.) 이 의문에서 시작해서 답을 찾아갔던 과정을 정리해보았다.

아래 수식에서 $p(x_{i})$는 정답값 분포 [0.0, 0.0, 1.0, 0.0, 0.0]를, $\hat{p}(x_{i})$는 Softmax를 거쳐나온 각 클래스별 확률 분포 $0<\hat{p}(x_{i})<1$를 의미한다.

$$-\sum_{i=1}^{C}{\{p(x_{i}){\cdot}log(\hat{p}(x_{i}))\}}$$

import numpy as np

# log(x) where 0 <= x < 1
# will return NaN or Inf if x == 0!
# We then add epsilon to x term.
eps = 1e-9

# Pseudo-outputs of softmax: softmax(logits)
# We can also calculate `logits` by applying
# inverse of the softmax: np.log(outputs)!
outputs = np.array([0.03, 0.05, 0.8, 0.1, 0.02])
label   = np.array([0.0,  0.0,  1.0, 0.0, 0.0 ])

# CE Loss calculation
ce_loss = -np.sum(label * np.log(outputs + eps))

# > ce_loss: 0.22314355006420974

CE Loss의 구현부를 보면 -np.sum(label * np.log(outputs + eps))으로 구성되는데, Negative Class의 Term은 label과 곱해지면서 상쇄되어 최종 Loss에 기여하지 않는다. 그러면 Softmax가 일반적으로 객체 분류문제에서 잘 작동하는 이유는 무엇일까? 내가 내린 결론은 Backpropagation에 있었다.

먼저, Stochastic Gradient Descent(SGD) 기법에서는 모델의 학습을 위한 방법으로 모델의 입력값에 대한 모델 가중치의 미분값Gradient을 구한다. 이를 더 쉽게 구하기 위하여 Chain Rule을 이용하며, 모델의 출력부부터 시작하여 모델의 입력부에 이르기까지 각 레이어별로 미분값을 구한다.

CE Loss $\mathcal{L}(s, y)$[1]만 고려했을 때, 이 Loss 함수를 입력값 $z$[2]에 미분하게 되면 다음과 같다. 상세한 미분 과정은 towardsdatascience 블로그(링크)에서 자세히 설명해주셨다.

$${{\partial{\mathcal{L}}}\over{\partial{\pmb{z}}}}=\pmb{s}-\pmb{y}$$

결국 Softmax 출력값과 레이블 값의 차이가 미분값이었던 것이다. 이렇게 함으로써 역전파Backpropagation 과정에서는 정답값이 아닌 클래스(0, 1, 3, 4번 클래스)의 확률도 잘 반영되어 학습이 진행되는 것이다.

Q. 그러면 CE Loss 값에는 Negative Class 값이 반영되지 않는가?

CE Loss는 Softmax 함수를 거친 뒤에 사용한다. 이를 중점으로 생각해 보면, Negative Class(0, 1, 3, 4번 Class)의 Logitsbefore softmax 값이 Positive Class(2번 Class)의 Probabilityafter softmax에 영향을 준다. 예를 들어, 아래 예시에서는 Negative Class의 영향력이 커질수록 Positive Class의 Probability는 작아진다.

import numpy as np

eps = 1e-9

# Define softmax (not used earlier)
def softmax(inputs: np.ndarray) -> np.ndarray:
    exps = np.exp(inputs)
    sums = np.sum(exps, axis=-1, keepdims=True)
    return exps / sums

# Pseudo-outputs of softmax: softmax(logits)
# We can also calculate `logits` by applying
# inverse of the softmax: np.log(outputs)!
outputs = np.array([0.03, 0.05, 0.8, 0.1, 0.02])
label   = np.array([0.0,  0.0,  1.0, 0.0, 0.0 ])

# Add arbitary value to logits and re-apply softmax
logits  = np.log(outputs)
logits[0:2] += 0.4
logits[3:5] += 0.3
outputs_m = softmax(logits)

# Show differences between `outputs` and `outputs_m`
print(outputs_m)
print(outputs_m - outputs)
# > outputs_m:
#   [0.04138864 0.06898107 0.73983032 0.12483331 0.02496666]
# > outputs_m - outputs:
#   [0.01138864  0.01898107 -0.06016968  0.02483331  0.00496666]

# CE Loss calculation
ce_loss = -np.sum(label * np.log(outputs + eps))
# > ce_loss: 0.22314355006420974

ce_loss_m = -np.sum(label * np.log(outputs_m + eps))
# > ce_loss_m: 0.30133442040075237

결국 CE Loss의 정의에서 input feature로는 probabilityoutput of softmax가 아니라, logitsbefore softmax를 중점으로 보았어야 했던 것이었다.

Conclusion

생각을 정리하다 보니, CE Loss와 Softmax를 한 묶음으로 생각하니까 모든 의문이 해결되었다. 처음에는 CE Loss term만 따로 두고 봐서 생겼던 의문이었지만, 결국 CE Loss의 정의에서 input feature로는 probabilityoutput of softmax가 아니라, logitsbefore softmax를 중점으로 보았어야 했던 것이었다.

본래 이 의문이 생겼던 것은, CE loss의 input probability를 변경하다가, 다른 값들을 넣어도 같은 loss value가 나왔던 것이 계기였다. 명백하게 확률의 분포가 다른데도 불구하고 loss 값이 동일하게 나와서 이에 의문이 들었었다.

outputs_1 = [0.05, 0.03, 0.8, 0.02, 0.1]
outputs_1 = [0.0, 0.0, 0.8, 0.2, 0.0]

다시금 생각해보면 CE loss를 계산할 때 이러한 분포의 차이는 중요하지 않았다. label이 두 개 이상이라면 다르게 판단해야 할지도 모르겠지만, 하나인 이상 다른 클래스의 확률이 얼마나 큰 지는 중요하지 않은 것이다.


[Object Detection] COCOEval에서 -1.000이 뜰 때 해결법

Problem: Precision이나 Recall이 -1.000 (잘못된 값)으로 나타남

Precision이나 Recall이 -1일 때는 areaRng의 small, medium, large가 데이터셋 분포에 맞게 잘 설정되었는지 확인해보자.

Average forward time: 0.54 ms, Average NMS time: 0.43 ms, Average inference time: 0.97 ms
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.287
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.443
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.326
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.287
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.483
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.535
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.535
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.535

잘 보면 area=small과 area=medium에서만 -1.000이 표시되는 것을 확인할 수 있다. 간단하게 COCOeval의 areaRng parameter를 변경하여 문제를 해결할 수 있다. 넣어야 하는 값은 데이터셋 내 Bounding box의 distribution을 직접 분석하여 구하자. (여기에서 COCO 데이터셋 내 객체들은 small이 41%, medium이 34%, 나머지 large가 24%라고 한다.)

하지만 이렇게 되면 일반적으로 여러 논문에서 표시하는 $AP_S$, $AP_M$, $AP_L$ metric과 비교할 수 없게 된다는 문제점이 있으므로 주의하자.

# COCOeval 초기화 부분
...
cocoEval = COCOeval(cocoGt, cocoDt, annType)

# areaRng parameter를 변경한다.
obj_area_sm = 777268
obj_area_md = 93240
cocoEval.params.areaRng = [
    [0, 1e5**2],
    [0, obj_area_sm],
    [obj_area_sm, obj_area_md],
    [obj_area_md, 1e5**2],
]
...

Further Research

기본 COCO Python Evaluator COCOEval에서는 아래 3개의 기준으로 all, small, medium, large 크기를 구분한다. COCO paper에서는 이러한 언급이 별도로 없지만 COCO Evaluation API에서 해당 내용을 구현한다(원본 코드 보러가기)

  • all: $0$ ~ $10000^2$
  • small: $0$ ~ $32^2$
  • medium: $32^2$ ~ $96^2$
  • large: $96^2$ ~ $10000^2$

Custom COCO-style Dataset이 이러한 크기보다 훨씬 큰 Object만을 가지고 있다면 이중 일부는 -1.000으로 표시되는 것으로 파악된다.

[Linux] PyTorch CUDA 오류 해결 – libcudnn_cnn_infer.so, libnvrtc.so

Could not load library libcudnn_cnn_infer.so.8. Error: libnvrtc.so: cannot open shared object file: No such file or directory

Anaconda/Miniconda로 최신 PyTorch 2.0과 CUDA 라이브러리(pytorch-cuda=11.8)를 설치하고 PyTorch 라이브러리를 로드할 때 이런 오류가 발생하는 경우가 있다.

해당 문제는 pytorch-cuda로 설치된 libnvrtc.so 파일이 제대로 링크되지 않아 발생한 문제이다. 이를 해결하기 위해서는 아래와 같이 기존의 libnvrtc.so.11.2libnvrtc.so.11.8 등을 프로그램이 열심히 찾고있는 libnvrtc.so로 링크해주면 된다.

현재 사용중인 conda 환경을 activate하고, 아래 Snippet을 실행하여 링크를 진행하여 문제없이 작동하였다.

NVRTC_LIB_DIR="${CONDA_PREFIX}/lib"
NVRTC_FILEPATH=$(find ${NVRTC_LIB_DIR} -type f -name 'libnvrtc.so.*')
NVRTC_FILENAME=${NVRTC_FILENAME##*/}
ln -sv ${NVRTC_FILENAME} "${NVRTC_LIB_DIR}/libnvrtc.so"

위 명령어 실행 결과로 아래와 같이 파일이 링크된 것을 확인할 수 있다. 혹시나 /lib/libnvrtc.so로 링크된 경우에는 conda activate를 한 뒤 다시 시도해보자. 현재 위치에 잘못 생성된 libnvrtc.so 파일을 삭제해주도록 하자.

'/home/jungin500/miniconda3/envs/torch/lib/libnvrtc.so' -> 'libnvrtc.so.11.8.89'

하지만 cuDNN 버전과 CUDA 버전이 달라지면 위 방법이 먹히지 않아 다른 오류가 발생할 수도 있다.

[해결됨] WSL2 CUDA undefined symbol: devicesetgpcclkvfoffset 문제 해결하기

[추가] 현재는 Fix된 이슈임

Windows 11 Insider Preview Build 22000.51이 나온 뒤에는 해결된 문제입니다. 아래 환경에서 테스트하였으니 apt 패키지와 드라이버를 업데이트 해보시기 바랍니다.

  • OS: Windows 11 Insider Preview Build 22000.51
  • Driver: NVIDIA 470.76
  • APT Package Version List
Inst libnvidia-container1 (1.4.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Inst libnvidia-container-tools (1.4.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Inst nvidia-container-toolkit (1.5.1-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Inst nvidia-container-runtime (3.5.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Inst nvidia-docker2 (2.6.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [all])
 Conf libnvidia-container1 (1.4.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Conf libnvidia-container-tools (1.4.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Conf nvidia-container-toolkit (1.5.1-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Conf nvidia-container-runtime (3.5.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [amd64])
 Conf nvidia-docker2 (2.6.0-1 NVIDIA CORPORATION [email protected]:1.0/bionic [all])

Environment: Windows 10 Insider Preview Build 21376.1
WSL2 Ubuntu release: 20.04
NVIDIA Forum Links: https://forums.developer.nvidia.com/t/nvidia-container-cli-error/177403/2?u=ji5489

문제 제기

if I run sudo nvidia-container-cli -k -d /dev/tty info

I got this error
where is ‘devicesetgpcclkvfoffset’ symbol?

– WARNING, the following logs are for debugging purposes only –
I0509 06:20:58.858216 1009 nvc.c:372] initializing library context (version=1.4.0, build=704a698b7a0ceec07a48e56c37365c741718c2df)
I0509 06:20:58.858281 1009 nvc.c:346] using root /
I0509 06:20:58.858287 1009 nvc.c:347] using ldcache /etc/ld.so.cache
I0509 06:20:58.858293 1009 nvc.c:348] using unprivileged user 65534:65534
I0509 06:20:58.858311 1009 nvc.c:389] attempting to load dxcore to see if we are running under Windows Subsystem for Linux (WSL)
I0509 06:20:58.891385 1009 dxcore.c:226] Creating a new WDDM Adapter for hAdapter:40000000 luid:1e946dc
I0509 06:20:58.917427 1009 dxcore.c:267] Adding new adapter via dxcore hAdapter:40000000 luid:1e946dc wddm version:3000
I0509 06:20:58.917515 1009 dxcore.c:325] dxcore layer initialized successfully
W0509 06:20:58.918854 1009 nvc.c:397] skipping kernel modules load on WSL
I0509 06:20:58.919404 1010 driver.c:101] starting driver service
E0509 06:20:58.950931 1010 driver.c:168] could not start driver service: load library failed: /usr/lib/wsl/drivers/nv_dispi.inf_amd64_43efafcd74b2efc9/libnvidia-ml.so.1: undefined symbol: devicesetgpcclkvfoffset
I0509 06:20:58.951399 1009 driver.c:203] driver service terminated successfully
nvidia-container-cli: initialization error: driver error: failed to process request

해결방법

I’m currently installing WSL2 Ubuntu 20.04 within Windows 10 Insider Preview Build 21376.1, and faced same issue like below.

# sudo nvidia-container-cli -k -d /dev/tty info

-- WARNING, the following logs are for debugging purposes only --

I0512 07:05:58.485519 5592 nvc.c:372] initializing library context (version=1.4.0, build=704a698b7a0ceec07a48e56c37365c741718c2df)
I0512 07:05:58.485581 5592 nvc.c:346] using root /
I0512 07:05:58.485601 5592 nvc.c:347] using ldcache /etc/ld.so.cache
I0512 07:05:58.485604 5592 nvc.c:348] using unprivileged user 65534:65534
I0512 07:05:58.485659 5592 nvc.c:389] attempting to load dxcore to see if we are running under Windows Subsystem for Linux (WSL)
I0512 07:05:58.502183 5592 dxcore.c:226] Creating a new WDDM Adapter for hAdapter:40000000 luid:185673b
I0512 07:05:58.512924 5592 dxcore.c:267] Adding new adapter via dxcore hAdapter:40000000 luid:185673b wddm version:3000
I0512 07:05:58.512956 5592 dxcore.c:325] dxcore layer initialized successfully
W0512 07:05:58.513266 5592 nvc.c:397] skipping kernel modules load on WSL
I0512 07:05:58.513404 5593 driver.c:101] starting driver service
E0512 07:05:58.521931 5593 driver.c:168] could not start driver service: load library failed: /usr/lib/wsl/drivers/nv_dispi.inf_amd64_43efafcd74b2efc9/libnvidia-ml.so.1: undefined symbol: devicesetgpcclkvfoffset
I0512 07:05:58.522047 5592 driver.c:203] driver service terminated successfully
nvidia-container-cli: initialization error: driver error: failed to process request

and I found out that apt-get experimental repository and stable one is mixed out – failing to install WSL2 one (which is available in experimental repository). as stable package is released, experimental(=WSL2) package should be released, but It wasn’t. see apt-cache madison result below.

# apt-cache madison libnvidia-container1
libnvidia-container1 |    1.4.0-1 | https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64  Packages
libnvidia-container1 |    1.3.3-1 | https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64  Packages
libnvidia-container1 | 1.3.3~rc.2-1 | https://nvidia.github.io/libnvidia-container/experimental/ubuntu18.04/amd64  Packages
libnvidia-container1 | 1.3.3~rc.1-1 | https://nvidia.github.io/libnvidia-container/experimental/ubuntu18.04/amd64  Packages
libnvidia-container1 |    1.3.2-1 | https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64  Packages
libnvidia-container1 |    1.3.1-1 | https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/amd64  Packages

The point is, we should install libnvidia-container1=1.3.3~rc.2-1 version rather than libnvidia-container1=1.4.0-1. but normal apt-get install nvidia-docker2 command would install 1.4.0-1 version, and It mostly like to fail in WSL2 environment.

I successfully installed older (WSL2-exclusive) packages via apt-get install command below:

apt-get install \
    libnvidia-container1=1.3.3~rc.2-1 \
    libnvidia-container-tools=1.3.3~rc.2-1 \
    nvidia-container-toolkit=1.4.1-1 \
    nvidia-container-runtime=3.4.1-1 \
    nvidia-docker2=2.5.0-1

Those commands will install older packages, and after sudo service docker stop and sudo service docker start, CUDA will work inside docker.

[간단리뷰 1주차] MobileNetV2: Inverted Residuals and Linear Bottlenecks

  • 원문: https://arxiv.org/abs/1801.04381
  • 참고자료: 논문, 블로그
  • 대분야: Optimization
  • 소분야: Model Optimization, TBD …
  • 제안 기법
    • TBD
  • 키워드: TBD

MobileNet V1 돌아보기 (간단요약)

  • 문제제기: Robotics, Self-driving car, Augmented Reality 등 실시간 처리가 필요한 분야에서는 낮은 컴퓨팅 성능으로 정확한 결과를 요구함
  • 제안사항: 경량화 CNN과 speed-accuracy tradeoff를 조절하는 Hyperparameter 제안
    • Depthwise Separable-Convolution
    • width/resolution multiplier
  • 결과 1 – Depthwise Separable Convolution으로 Full-Convolution의 계산량을 약 85% 줄임
    (ImageNet 기준 Accuracy는 1.1%p 감소)
  • 결과 2 – 모델 구성에 따라 41~569 mAddsMillion Multiply-Adds (0.5~4.2 Million parameters)로 구성이 가능 → Flexible하게 모델 크기를 구성할 수 있도록 선택지를 제공 but 이에 따른 accuracy 차이가 존재
  • 결과 3 – MobileNet 구조를 사용한 Object Detector 모델에서 낮은 연산량 대비 준수한 성능을 보여주고 있음

MobileNet V2

  • 문제제기
    • SOTA 네트워크가 요구하는 컴퓨팅 성능이 높아 모바일, 임베디드에 적용이 불가능합니다.
    • ReLU Activation의 비선형성 효과로 인해, 정보(manifold)가 손실됩니다.
      → 채널 수가 충분히 많아야 정보를 보존할 수 있습니다.
  • 제안
    • Depthwise Separable Convolution (MobileNet V1)
  • Linear Bottlenecks
    • ReLU Activation은 채널 내의 정보를 불가피하게 손실됩니다.
    • 채널 내에서의 정보는 손실되지만, 여러 채널 사이에서 Input Manifold로부터 임베딩된 정보(Manifold of Interest)를 얻을 수 있다고 합니다.
  • Inverted Residual
    • Bottleneck 블록은 모든 입력이 Bottleneck-Expansion 뒤에 따라오는 형태의 Residual Block과 형태가 비슷합니다.
    • 기존에 제안된 Residual Block에 Linear Bottleneck 형태를 붙인 형태

[베이스리뷰 2주차] 간단정리 – Pyramid Attention Network

  • 원문: https://arxiv.org/abs/1805.10180
  • 참고자료: 논문요약
  • 대분야: Image Segmentation
  • 소분야: Semantic Segmentation, Network Hierarchy
  • 키워드: PAN (Pyramid Attention Network), GAU (Global Attention Upsample)

문제점

FCN의 경우 작은 객체부분이 손상되어 있다.

[1] 공간해상도 손실 (Spatial resolution loss)

작은 객체 부분 Segmentation 정확도가 낮습니다. 다중 스케일 상에서 객체들의 카테고리를 결정하는데 문제가 있어 SPP나 ASPP를 사용하게 됩니다.

Part Ⅶ. Semantic Segmentation] 6. DeepLab [1] - 라온피플 머신러닝 아카데미 - : 네이버 블로그
그리드 모양의 Artifact

다만 이렇게 하면, 그리드 모양의 Artifact를 만들어 버린다는 단점이 있습니다. 논문에서는 이러한 단점을 보완하여 CNN의 출력으로부터 High-level feature의 Pixel-level attention을 추출하는 FPA(Feature Pyramid Attention)을 제안합니다.

[2] Segmentation Detail 손실 (Weak in restructuring original resolution binary prediction)

최상단의 그림에서 볼 수 있듯이, FCN의 경우 Segmetnation의 결과가 sharp하지 않습니다. 자전거의 핸들은 아예 없어져 있습니다. 이러한 문제는 U-Net 구조를 이용하거나 Kernel을 크게 접으면 어느정도 해결된다고 알려져 있습니다(논문 참조). 다만 연산량이 많아지는 문제가 있습니다. 이러한 문제를 논문에서는 GAU(Global Attention Upsample)모듈을 제안하고 이 모듈에서 global context를 추출하였습니다. 다른 방식들보다 연산량이 적다고 합니다. VOC2012 Cityscapes에서 SOTA를 달성했다고 합니다.

제안요소

Feature Pyramid Attention (FPA) Module

네트워크는 인코더-디코더 아키텍쳐로 구성되어 있습니다. Res-5까지 거쳐서 인코딩된 결과가 Feature Pyramid를 통과해 Attention을 받아(?), 원본 크기로 다시 커지게 됩니다. 파란색 선이 Downsampling, 빨간색 선이 Upsampling입니다.

  • 참고: Spatial Pyramid Pooling
  • 특징
    • Feature Pyramid Network와 비슷한 구성
    • 3개의 스케일을 이용하여 U-Shape structure 구성
    • Feature 자체가 작아서, 커널 크기를 크게 해도 (e.g. 7×7) 계산량이 매우 커지지는 않음

Global Attention Upsample (GAU) Module

Decoder단에서의 성능을 끌어올리기 위해 PSPNet이나 DeepLab에 사용된 Bilinear 방식과, DUC에서 사용된 One-step decoder module에서의 문제점을 지적하였습니다. GAU에서는 Decoder에서의 Upsampling 시, Low-level feature과 High-level feature를 동시에 볼 수 있는 방식을 고안하였습니다. (마찬가지로, 빨간색이 Upsampling입니다.)

성능

SE는 SENet Attention Module을, C333/C357은 각각 커널 사이즈 3-3-3과 3-5-7의 Feature Pyramid Attention Module의 사용을 나타냅니다. MAX와 AVE는 각각 Max Pooling, Average Pooling을 나타내며 GP는 Global Pooling Branch (FPA 상단)를 나타냅니다.

[베이스리뷰 2주차] 간단정리 – SPP (Spatial Pyramid Pooling)

  • SPP: Fast R-CNN에서 참고한 개념. SPPNet에서 제안
  • 요약: Conv Layer에서는 사실 Resolution이 문제되지 않으나, 마지막 FC Layer의 입력 Size는 고정입니다. 이로 인해 Input Image는 무조건 같은 크기로 Resizing해서 모델에 입력해야하는 제약 조건이 생겨나게 되었는데, SPP는 이를 같은 크기의 Feature로 조절해주는 Pooling을 제안합니다.

알고리즘

  1. Input Feature Map을, 미리 정해진 영역 (4×4, 2×2, 1×1 등)으로 분할합니다. 이 때, 분할한 각 영역을 Pyramid라고 합니다.
  2. 여기서는 Pyramid의 갯수가 3개입니다. 이 때 피라미드 내 한 칸(하나의 cell)을 bin이라고 합니다.
  3. 실제 Feature의 크기보다 bin이 더 큰 영역을 나타냅니다. Feature의 각 bin에 해당하는 부분에서 MaxPooling을 수행합니다.
  4. 결과 bin을 Flatten하여 이어붙입니다.
  5. 결과적으로 bin의 갯수는 동일하므로, FC-Layer의 입력은 항상 동일한 값이 됩니다!