오늘은 deep learning 모델을 학습 시킬때 GPU 사용율이 저조한 경우 이 현상의 원이이 뭐고

어떻게 개선 할수 있는지에 대해 포스팅을 해보고자 한다. 어디 까지나 필자의 접근 법이고 완벽한 답은 아니다.. 

 

개발 환경:

frame work: pytorch 1.7

os: ubuntu 20.04

training env: multi gpu(distributed module 사용)

 

목차:

1. 현상

2. 원인 분석 및 profile

3. 개선

 

현상

학습 하는 네트워크는 CNN 기반의 segmentation 네트워크 였고 학습 데이터는 이미지 약 7만장 이상이었다. 

초기 input pipe line은 image-annotation file pair 를 읽어 들이는 원시적인 형태로 되어있었다.

annotation file에는 polygon 형태의 segmentation label 정보가 저장되어 있어서 이 정보를 mask image로 바꾸어 NAS에 저장해두었다.

디렉토리 구조는 아래와 같다.

Fig 1. 초기 디렉토리 구조

 

v100 tensor core gpu 로 위 상태 그대로 학습을 진행 했을때 배치 사이즈 48 기준 약 25분이 걸리는 신기한 현상이 발생했다.

 gpu 사용율은 아래 그림 Fig 2. 처럼 100% -> 9% -> 90% -> 10%->0% -> 100% 과 사용율의 등락율이 굉장히 커보였다. 

아래 그림은 위 상황에 대한 캡쳐 본으로 아래 명령을 입력해 확인할 수 있다.

watch -n 1 nvidia-smi

Fig 2. gpu 사용율(Volatile GPU-Util 항목)을 보면 위 그림에서 100% 사용하다가 아래 그림에서는 13%,0%,0%,100%로 사용율의 등락률이 큰 것을 확인할 수 있다.

 

원인 분석 및 Profile

위와 같은 현상이 나타나면 대부분 원인은 둘 중 하나다. 

  1. 파일이 저장된 위치가 NAS 니까 사용자가 많아 대역폭 확보가 안되어 File I/O가 병목의 원인이거나 
  2. I/O 가 문제가 아니라면 CPU 연산이 많아 병목의 원이이 되는 경우다.

위 현상을 진단할 수 있는 방법은 크게 2가지이다. 

  • iftop 을 이용해 File I/O speed를 체크해 보는 방법이 있고(엄밀히 말하면 network를 통한 file transfer speed)
  • htop 을 이용해 CPU 사용량을 체크해 보는 방법이 있다.

나는 sudo 권한이 없어서 1번 방법을 쓸 수 없어서 htop을 이용해 진단을 했다. 

방법은 간단하고 나름 합리적이고 크게 두 가지 케이스로 나눠 진다. 

  1. htop을 사용해 CPU 사용량을 모니터링했을 때 CPU 코어의 사용율이 저조 하고 데이터 로더 프로세스 상태가 D(uninterruptible sleep-usually I/O) 인 경우가 많다면 File I/O가 병목인 경우가 대부분이다. 
  2. CPU 코어의 사용율이 거의 100%에 가깝고 데이 로더 프로세스 상태가 D인 경우가 거의 없다면 CPU 연산이 병목의 원인인 경우가 많다.

본인의 경우가 케이스 2. 에 해당 한다면 GPU 프로파일을 한번쯤은 해보는게 좋다. 혹시 라도 네트워크의 구조 또는 loss의 구현이 비효율적으로 되어있어 GPU 사용율이 떨어질 가능 성이 있기 때문이다. 

 

본인의 경우에는 케이스 2에 해당되어 GPU 프로파일 까지 진행했었다. 

gpu프로파일에 nvidia NSIGHT SYSTEMS PROFILE을 이용했다. 

사용방법은 매우 간하며 아래와 같은 명령어를 사용하면 된다. 

nsys profile –t cuda,osrt,nvtx,cudnn,cublas -o baseline.qdstrm -w true python your_train_script.py

사용시 주의할 점은 반드시 <your_train_script.py> 가 의미 있는 유한 시간안에 정상 종료 해야 한다는 것이다. 

무슨 말이냐면 아래 코드 처럼 train iteration횟 수를 유한 시간안에 끝날 수 있는 횟수로 제한 하는 것이 필요 하다는 것이다. 

for i in range(10):
    nvtx.range_push('data loading')
    batch = next(dataloader)
    nvtx.range_pop()

    for key, data in batch.items():
    	batch[key]=data.cuda()

    nvtx.range_push('Forward pass')
    nvtx.range_push('Batch')
    out = model.forward(image.cuda())
    loss = getLoss(out)
    nvtx.range_pop()
    nvtx.range_pop()
    nvtx.range_push('backward pass')
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    nvtx.range_pop()

nvtx.range_push(), nvtx.range_pop() 등은 프로파일 결과 파일을 보기 편하게 해주는 역할을 해준다. 

NSIGHT (GPU driver 설치시 대부분 같이 설치 된다.)을 이용해 프로파일 결과 파일을 열면 아래와 같은 그래프를 볼 수 있다.

Fig 3. GPU 프로파일 결과

Fig 3. 의 붉은 박스 부분을 보면 NVTX 항목에 data loding, Batch, backward, data loading, batch, backward 라는 부분을 볼 수 있다. 각 항목은 위 예제 코드에서 직관 적으로 알수 있듯이 각각 data loading에 걸리는 시간, batch의 forward pass 연산에 걸리는 시간, backward pass에 걸리는 시간의 포션을 나타낸다.

 

그래프에서 확인 가능 하듯, data loading이 차지 하는 시간이 절대적으로 많다.

data loading에서 augmentation 을 하는데 이 연산은 cpu를 활용하고 gpu 연산에 비에 이 cpu 연산이 오래 걸려 gpu가 처리할 데이터를 꾸준히 공급하지 못하는 것이 낮은 gpu 사용율의 원인이었다. 이때 GPU 사용율을 높이고자 한다면 이 GPU starvation 의 간격을 최대한 줄이거나 더 나아가 GPU 연산부와 CPU 연산부가 서로 겹치도록 연산 파이프라인을 구축하는 것이 최선이다. 

 

이렇게 GPU 프로파일을 하고 보니 현재 문제가 되는 부분은 확실히 data loading 이다.

FILE I/O는 거의 문제가 안되고 있으니

CPU 연산이 절대적으로 많거나 비효율 적으로 연산을 하고 있는 것으로 해석된다. 

 

다음으로 data loader 내부에서 시간을 많이 잡아 먹는 부분을 특정 정하기 위해 python profile을 수행했다. 

cProfile이라는 python 모듈을 이용했는데 결과 캡쳐하는걸 깜박해... 사진은 생략한다. 

가슴아프게도 결과는 data augmentation이 약 40%를 차지 했고 

사용한 augmentation 은 flip, translation, rotation, gaussian noise, hue transform, shadow adding 등이다.

augmentation 후 약 3개의 함수에서  loss 계산에 필요한 특정 heat map, offset map을 만들기 위한 작업을 진행하는데

이 부분이 약 40%의 시간을 차지 했다. 나열하고 CPU연산이 많긴 하다. 

 

 

개선

augmentation 은 nvidia DALI를 사용하고자 했지만 아직 구현을 완료 하지 못했다. (40%의 성능 향상을 아직은 이루지 못했다는 소리다- 이 부분은 향후 과제다. DALI 버전을 완료 하면 내용을 보완해야겠다.)

 

나머지 40%의 개선을 이루기 위해 loss 계산에 필요핸 map 들을 만드는 부분을 수정했는데

결과는 이미지 1장당 약 4ms 걸리던 작업을 0.4ms 정도 걸리게 끔 구현을 다시 했다. 

 

사실 엄청난 걸한거 같지만 기존 코드가 말도 안되게 비효율 적인 구조를 가지고 있었다. 

numpy array를 처리 하는데 형태가 (n,1,2) 인 포인트의 좌표 를 담고 있는 배열을 (n 개의 (x,y) 좌표 값을 가진 배열)

python for문을 이용해 순회 하며 offset map 과 heat map 등을 만들고 있었다. 

for 문을 없에고 numpy indexing 과 matrix 계산으로 바꾸니 약 1/10 로 속도가 개선되었다. 

아래 그림은 수정한 코드에서 CPU 사용율을 캡쳐 한것이다. 

Fig 4. 개선 버전의 GPU profile 결과

Fig 5 를 Fig 3과 비교해보면 data loading 시간이 분명 줄었다. 27.xx 초 에서 10.372로 줄었으니 줄긴 많이 줄었다. 실제로

한 epoch 학습 시키는데  걸리는 시간도 50%가량 줄었다. (data loader가 10초 씩이나 걸리는게 말이 안된다고 생각 하겠지만 프로파일러가 샘플링을 하기 때문에 저렇게 말도 안되는 시간이 나온거다. 실제 몇초 인지 수치 보다 gpu 연산 시간을 나타내는 Batch 의 길이와 data loading의 길이 비율 차에 집중해서 보길 바란다.) 

 

여기까진 좋았다 원하던 결과다 다만 위와 같이 개선한 후에도 여전히 data loading 시간이 gpu의 forward + backward pass 연산 시간에 비해 더 많은 시간을 소모 한다. 그 결과 당연히 gpu 사용률의 등락 폭은 줄어 들지 않았다. 

아래 그림을 보면 이 분석을 시작한 현상이 그대로 남아 있음을 알 수 있다. 

Fig 5. data loader 코드 개선 버전의 gpu 사용률 

그래서 다시 htop로 cpu 사용률 모리터링을 해보니 아래 그림 Fig 6. 과 같이 cpu가 100%일을 하고 있지는 않다. 

Fig 5. CPU 바운드 원인 코드 개선 버전의 cpu 사용율

 

이전 섹션인 '원인 분석 및 profile'에서 CPU 가 거의 항상 100%사용 중임에도 불구하고

GPU 사용율이 낮았기 때문에 FILE i/o가 병목 현상의 원인은 아닐거라고 생각했다.

 그런데 코드를 개선 하고 나니 CPU 사용율은 절반 수준으로 낮아졌으나 data loading 시간은 여전히 오래 걸린다. 

 

여기서 의문이 들었다.

cpu가 저렇게 50~60%정도의 사용율을 보인다는 것을 File I/o의 문제라고 볼 수 있을까?

File I/o가 문제라면 cpu core의 사용율이 낮은게 아니라  말그대로 놀고 있는 0~1%의 사용율이 나와야 하는거 아닐까?

 

그럼 저렇게 cpu 사용율이 낮아졌는데 역전히 data loader가 batch를 생산하는데 시간이 오래 걸리는 원인이 뭘까?

 

좀더 분석을 해봐야겠다. 

 

 

칼만필터는 제어, 신호 등등 여기저기에 참 다양한 곳에 등장하는 추정기이다.

많이 쓰이는 지라 인터넷 여기저기 자료도 많고 설명 글도 참 많지만

아쉬운 점있다면 칼만필터를 이해하기 위해 필요한 백그라운드가 많은데

인터넷에 설명 글들을 읽다보면 글읽는 사람이 칼만필터를 이해하기 위해 필요한

배경지식을 다 알고 있다고 가정하고 설명한 글들이 대부분이라 조금 아쉬웠다.

그래서 이번 기회에 칼만필터를 수학적으로 유도해보고 유도과정에서 사용되는 배경지식을 함께 정리해 보고자 한다. 

글의 주된 목적은 칼만필터를 나만의 언어로 풀어 다음에 기억하기 쉽게 하는것 이다. 

 

칼만필터 설명은 다음과 같은 구성으로 이뤄진다. 

1. 이 글에서 칼만필터를 설명하기 위해 사용하는 기호 정의

2. 칼만필터의 개념(칼만필터의 개념도, 베이즈 정리를 이용한 칼만필터의 해석)

3.  특정 가정하에 칼만필터의 추정값을 구하는 공식(2/2)

4. 3. 에서 주어진 칼만필터의 추정값을 구하는 공식에 사용된 각각의 값들을
    구하는 방법과 이들을 구하는 과정에서 사용되는 수학적 배경지식(3/3)

 

칼만필터 정리 내용은 총 2페이지로 예상을 하고 있는데 1,2는 현재 페이지(1/2)에서

3.과 4.의  내용은 칼만필터(2/2)에 포스팅할 예정이다.

 

기호 정의:

이 글에서 사용되는 기호들은 아래와 같이 정의한다.

 

$x$: 칼만필터를 이용해 추정하고자 하는 랜덤변수.
         관측할수 없는 미지의 값으로 이 변수에 대한 초기 값만 알고 있다고 가정한다.

$z$: 관측값. 실험을 통해 관측가능한 값으로 칼만필터의 입력 중 하나이다.
         구하고자 하는 랜덤변수 $x$와 특정한 과계가 있고 칼만필터에서는 이 관측 값 $z$를
         통해 미지의 수 $x$에 대한 정보를 얻을수 있다고 본다.
         (왜 $z$를 이용해 $x$에 대한 정보를 얻을 수 있다고 보는지는 아래에 설명한다.)

$u$: 외부 입력. 칼만필터의 입력 중 하나.

 

위 값들에 시간 개념을 도입해 $x(k), z(k), u(k)$등의 기호로 사용 하면 시점 k에서의 $x,z,u$ 로 해석한다.

 

$\hat{x}(k|k-1)$: k-1시점의 $\hat{x}(k-1|k-1)$과 시스템 모델을 이용해 구한
                                 k시점에서의 랜덤 변수(또는 랜덤벡터) $x$의 추정값

                                 (일반적으로 $x(k|k-1)$을 k 시점에서 x의 prior knowledge로 본다.

                                  prior knowledge는 베이즈 정리의 prior knowledge를 의미)

 

$\hat{z}(k|k-1)$: x(k|k-1)과 측정 모델을 이용해 계산한 k 시점에서의 관측값 z의 예측값
                               (실제 관측값이 아니라 예측한 관측값이다.)

 

$\hat{x}(k|k)$ : k 시점에서 추정하고자 하는 랜덤 변수(또는 랜덤 벡터)

 

위에 언급한 시스템 모델과 측정모델을 아래와 같은 의미로 사용된다.

  • 시스템 모델: 추정하고자 하는 랜덤변수(또는 랜덤벡터) $x$의 k-1시점과 k시점 사이의 관계식.
    $$\hat{x}(k|k-1) = F(k-1)\hat{x}(k-1|k-1) G(k-1)u(k-1)+G_{w}(k-1)w(k-1)$$
    시스템 모델은 우리가 명확히 알고 있다고 가정한다.(사실 이걸 모르면 말이 안되므로..)
  • 관측 모델: 관측 값 $z(k)$와 추정하고자 하는 랜덤변수 $x$사이의 관계식.
    $$\hat{z}(k) = H(k)x(k)+v(k)$$
    관측 모델도 우리가 명확히 알고 있다고 가정한다.
    (이것도 마찬가지고 관측값과 관측값에 영향을 미치는 인자간의 관계를 모르면 문제를 풀기 매우 힘들어진다...)

 

칼만필터의 개념(베이즈 정리를 이용한 칼만필터의 개념설명):

 

칼만필터는 두 부분 1.예측 단계, 2. 측정 업데이트 단계 로 이루어져 있다. 

 

  1. 예측 단계: 시간을 k로 표현할때 시간 k-1에서 얻은 정보를 이용해 시간 k에서 필요한 정보를 예측 하는 단계
  2. 측정 업데이트 단계: 관측값과 시간 예측 단계에서 얻은 값을 이용해 추정하고자 하는 값을 업데이트 하는 단계

이를 예측 단계와 측정 업데이트 단계를 시간 흐름의 그림으로 표현하면 아래와 같다.

Fig. 1 칼만필터의 개념

위 그림을 보면 예측단계에서는 k-1시점에서 추정한 $\hat{x}(k-1)$을 이용해

k 시점에서의 $\hat{x}(k|k-1)$, $\hat{z}(k|k-1)$를 예측하고

측정 업데이트 단계에서 관찰 값 $z(k)$을 이용해 k시점에서의 최종 추정값 $\hat{x}(k|k)$을 업데이트 한다.

 

위 그림과 설명을 보고 기본기가 충실한 사람은 바로 베이즈 정리를 떠올릴 수 있는데 베이즈 정리는 아래와 같다.

$$p(x|z) = \frac{p(z|x)p(x)}{p(z)}$$

해석하자면 $x$에 대한 사전 정보 p(x), $x$가 주어졌을때 얻을수 있는 $z$의 정보($p(z|x)$),

그리고 $z$ 자체의 정보($p(z)$)를 이용해 $x$에 대한 정보를 업데이트(필터링) 할 수 있는 식으로 해석할 수 있다. 

(있어 보이는 표현을 사용하면 $x$에 대한 사전 정보를 prior distribution(prior knowledge),
$x$가 주어졌을때 얻을 수 있는 $z$의 정보($p(z|x)$)를 likelihood,
앞의 두 정보로 업데이트해 얻을 수 있는 $x$를 posterior distribution(posterior knowledge)라고 표현한다.)

 

베이즈 정리를 이용해 칼만필터의 시간 예측, 측정 업데이트 단계를 설명해 보면 아래와 같이 해석 할 수 있다.

  • 예측 단계:  k-1시점의 정보($\hat{x}(k-1)$)를 이용해 $\hat{x}(k|k-1)$의 사전 정보(베이즈 정리의 p(x))와 
                     likelihood $z(k|k-1)$ (베이즈 정리의 $p(z|x)$)를 구한다.
  • 측정 업데이트 단계: 관측한 $z(k)$ (베이즈 정리에서 $p(z)$)와 예측단계에서 구한
                     $\hat{x}(k|k-1)$, $z(k|k-1)$을 이용해 $x(k|k)$ (베이즈 정리에서 posterior에 해당)을 구한다.

위 설명은 주의 해서 살펴 보아야 한다. $z(k)$는 시점 k에서 실제로 관측한 값이고
$z(k|k-1)$은 k-1시점에서 구한 정보를 바탕으로측정모델을 이용해 추측한 관측값이다.  

 

예측 단계에서 $\hat{x}(k-1)$를 이용해 $\hat{x}(k|k-1)$를 예측할 때 시스템 모델을 사용하고
$\hat{x}(k|k-1)$를 이용해 관측값 $z(k|k-1)$을 예측 할때 관측 모델을 사용한다.

 

이를 바탕으로 칼만필터의 개괄적인 내용을 풀어 보자면

  1. k-1 시점에서 $x$의 posterior 정보를 구한다.
  2. k-1 시점에서 구한 $\hat{x}(k-1)$의 posterior와 시스템 모델을 이용해 k 시점에서의 $\hat{x}(k|k-1)$의 prior knowledge를 계산한다.
  3. $\hat{x}(k|k-1)$과 측정 모델을 이용해 관측값의 예상값 $\hat{z}(k|k-1)$를 구한다.
  4. k시점에서의 실제 관측값 $z(k)$와 관측 예측값 $\hat{z}(k|k-1)$을 차이를 이용해 $x(k)$의 사전정보 $\hat{x}(k|K-1)$가 
    어느정도의 오차를 내포하고있는 지 간접적으로 계산.
  5. 2,3,4에서 구한 정보와 베이즈 정리를 이용해 시점 k에서의 $x$의 posterior $\hat{x}(k|k)$를 구한다.

 

오늘은 칼만 필터 포스팅에서 사용할 기호에 대한 정의, 칼만필터의 동작 개념에 대해 정리해봤다. 

글로 풀어 설명하다 보니 표현이 이상한 부분, 미처 적지 못한 부분이 있을 수있는데 이런 부분은 틈틈히 생각 나는 데로 

수정해야겠다. 

 

다음 포스팅에서는 특정 가정하에서 MMSE(minimum mean square error)를 이용해 칼만필터의 수학적 유도과정을 설명을 정리해 보자.

오늘은 리뷰할 논문은 FCOS: fully convolution one-stage detection 이다. 

 

제목에서 알 수 있듯이 one-stage detector 이다. 

 

Title: FCOS: Fully Convolution One-Stage Detection

link: https://arxiv.org/abs/1904.01355

 

FCOS: Fully Convolutional One-Stage Object Detection

We propose a fully convolutional one-stage object detector (FCOS) to solve object detection in a per-pixel prediction fashion, analogue to semantic segmentation. Almost all state-of-the-art object detectors such as RetinaNet, SSD, YOLOv3, and Faster R-CNN

arxiv.org

 

 

요약:

자세히 살펴 보기전에 FCOS의 컨셉을 간단히 요약 하자면 저자는 anchor-based one stage는 training 할때 anchor box overlab의 해결하는데 시간이 오래 걸리므로 anchor-box free한 detector를 제안한다. Anchor box를 제거한 detector에서는 low recall rate, ambiguous sample 문제가 발생할 수 있는데 이런 문제를 해결할 수 있는 구조를 제안하고 해당 구조에 대한 효율성을 실험을 통해 입증했다. (문제를 해결하는데 사용한 개념은 FPN(feature pyramid network, per-pixel estimation, center-ness 등으로 아래에서 자세히 살펴 본다.)

 

Contribution:

1. Detection 문제를 segmentation 문제 처럼 per-pixel prediction 문제로 정의 하고이에 대한 구조를 제안함

2.  FCN(fully convolution network)을 사용해 문제를 해결함으로서 다양한 테크닉을 segmentation에서 사양하는 다양한 테크닉을 공유 해서 사용할 수 있다(고 주장함)

3. proposal free, anchor free 하게 문제를 정의 함으로써 hyper paramter의 개수를 줄임(사람에 대한 의존성을 줄인것이므로 이건 확실한 기여점이라고 개인적으로 생각한다.)

4. anchor box와 관련된 연산 감소(anchor box-GT box간의 매칭등에 소요 되는 연산량 감소)

5. One-stage로서 SOTA(state-of-the-art) performance를 이뤘고 2 stage detector에서 RPN으로 사용해도 anchor-based RPN보다 성능이 좋다. 

6. 확장 가능한 모델이다.(이 논문에서는 Detection 문제가 타겠이었지만 instance segmentation, key-point detection 등으로 쉽게 확장 가능 하다(고 주장한다.). FCN base 이므로 이 말을 맞는 말일거 같다.)

 

Anchor-based detector의 문제 점들

Anchor-based detector가 가지는 문제는 크게 hyper parameter에 관련된 문제, negative-positive sample의 imbalance에 대한 문제, computation(연산량) 문제로 나눠 생각 할수 있다. 

1. hyper parameter 관련 문제

  • anchor의 수, 사이즈, aspect ratio등은 predefined되어 있는데 이에 따라 성능에 영향을 쉽게 받는다.(수작업으로 튜닝 해야 하는 파라미터의 개수가 많다)
  • box scale과 aspect ratio가 고정이라서 다양한 모양을 가지는 small object에는 태생적으로 비효율 적이다. 

2. sample imbalance 문제

  • High recall rate을 달성하기 위해선 anchor box를 dense하게 배치 해야 하는데 이경우 많은 anchor box가 negative sample로 label되고 이는 학습 중 negative-positive sample 불균형을 악화 시킨다. 

3. 연산량 문제

  • anchor box는 IOU계산을 위해 predicted box-GT box간에 매칭을 해야 하는데 이때 발생하는 연산량이 비효율 적으로 많다.

 

기존 Anchor free detector들이 가지고 있던 문제 점들 

1. YOLO1낮은 recall rate을 보여 주는데 이건 object center 근처에 있는 몇몇 points들만 bbox 예측에 이용했기 때문이다. (object를 감싸는 bbox내에는 굉장히 많은 point가 있는데 그중 center 근처의 몇몇 개만 이용하는게 문제라고 주장함)

 

2. CornerNet에서는 같은 instance에 속한 left-top, right-bottom corner pair를 찾기 위한 추가 metric을 학습하고 post processing을 이용해 이들을 grouping 하므로 연산량이 많아져 비효율 적이다.

 

3. Dense Box 기반의 모델들은 overlapping bbox, 낮은 recall rate등의 문제로 인해 일반적인 object detection task에 부적합 하다고 여겨졌다.

 

FCOS의 전략

본 논문에서는 위에서 언급된 anchor free detector들이 가지고 있는 문제들을 per-pixel prediction과 FPN을 이용해 해결하고, 제안하는 구조에서 발생한 low quality bbox prediction문제를 해결하기 위해 center-ness score layer를 도입했다. 

 

1. Per-Pixel prediction

 - Anchor-based detector 들은 feature map의 각 픽셀 위치를 object center라고 가정하고 미리 정한 size의 box를 regression 한다. 이중 IoU가 낮은 bbox들은 negative sample이므로 실질적으로 학습과정에서 object의 위치를 추정하는데 사용되는 픽셀은 object center 근처에 있는 픽셀들이 전부라고 할 수 있다. 

저자는 이게 문제라고 생각하고 FCOS에서는 feature map의 모든 픽셀 위치에서 해당 픽셀이 특정 object에 속한 픽셀인지 아닌지, object에 속한 픽셀이라면 해당 픽셀에서 예상한 bbox는 무엇인지를 계산하는 방식이다. 

 

예를 들어 아래 그림이 feature map이라고 한면 이 논문이 제안하는 방식은 p1,p2,p3 및 노란박스 영역에 속한 모든 pixel들은 노란박스가 나타내는 object에 해당 하는 픽셀이므로 해당 픽셀들 각각에 대해 (마치 sementic segmentation과 같이)픽셀이 속한 class label을 예측하고, 각 위치에서 object bbox를 regression한다. 

Fig1. Per-pixel predicton의 개념

이런 방식으로 feature map의 모든 픽셀에 대해(Fig1에서는 $p_{1},p_{2},...,p_{n}$ 전부에 대해) 해당 픽셀이 속한 class $c^{(i)}$, bbox 추측을 위한 4개의 값 $(l^{*},t^{*}, r^{*}, b^{*})$을 추정하도록 네트워크를 구성했다. 그러니까 한 픽셀당 $ (l,t,r,b,c)$ 5개의 값을 예측 하는 것이다. 

 

이때 feature map 상의 픽셀 $(x,y)$가 라벨이 $c^{*}$인 object bbox 내부에 있는 픽셀이면  target label은 $c^{*}$로 정의 하고 positive sample이라고 보며 object bbox내부에 있는 픽셀이 아니라면 target label $c^{*} =0$ 이 되어 negative sample 로 정의 한다. 

 

bbox 예측에 사용되는 target $(l^{*},t^{*}, r^{*}, b^{*})$ 각각은 feature map상의 위치 $(x,y)$에 대해 해당 픽셀이 포함된 object bbox의 $(좌측 면, 위쪽 면, 오른쪽 면, 아래쪽 면)$  까지의 거리(단위pixel)에 해당하는 값으로 각각은 GT bbox 정보로 부터 아래와 같이 계산된다. 

 

$$ l^{*}=x-x_{0}^{(i)}, t^{*}=y-y_{0}^{(i)}, r^{*}=x_{1}^{(i)}-x, b^{*}=y_{1}^{(i)}-y             Eq(1)$$

위 식에서 $(x,y)$는 feature map 상의 위치, $(x_{0}^{(i)},y_{0}^{(i)})$는 object bbox의 left-top corner 좌표,  $(x_{1}^{(i)},y_{1}^{(i)})$는 object bbox의 right-bottom corner 좌표를 의미 한다.

 

$(l,t, r, b)$를 network에서 예측 한 후 IoU를 계한 하기 위한 $(x_{0}^{(i)},y_{0}^{(i)},x_{1}^{(i)},y_{1}^{(i)})$복원은 $Eq(1)$이용해 쉽게 가능하다. 

각 기호에 대한 정확한 의미는 아래 그림(Fig2.)를 참조 하면 더 직관적이다. 

Fig2.  왼쪽:  Bounding box를 encoding 한 (l,t,r,b)의  의미 .  오른쪽: ambiguous region(overlapped region) 의 예

특이하다고 생각 한 점은 각 픽셀의 class label $c$를 추측 하기 위해 multi class classification 컨셉을 사용 하지 않고 각 object class별로 binary classification으로 학습했다는 것이다. 

무슨 말이냐면 예를 들어 MS coco data 의 경우 object 카테고리가 80개 있다고 하면 class inference를 위한 network output은 총 80개의 channel로 구성될 것이다. 이때 channel 0은 vehicle, channel 1은 pedestrian, channel 2는 의자 등등이라면  임의의 $(x,y)$ 픽셀이 class 0 일 확률, class 1일 확률, class 2 일 확률을 채널축을 softmax를 취해 구하는 방식이 아니고, 각 channel을 별개로 보고 channel 0 에서는 vehicle에 속한 pixel과 그렇지 않은 pixel, channel 1 에서는 pedestrian에 속한 픽셀과 그렇지 않은 픽셀등을 각 채널 마다 binary classification loss를 구했다는 얘기이다. 

 

참조: bbox regression을 할떄 네트워크에서 추측한 $(l,t, r, b)$는 target 인 $(l^{*},t^{*}, r^{*}, b^{*})$ 을 regression 하는데 바로 사용되지 않고  실제로 $exp^{(s_{i}l)}$로 사용 되는데 이는 network에서 나오는 $(l,t,r,b)$이 큰 값을 갖게 하고 싶지 않아서 사용 하는 테크닉으로 $(l,t,r,b)$가 상대적으로 작은 값이어도 $exp^{(s_{i}l)}$은 큰 값이 되서 비교적 scale이 큰 $l^{*}$을 추정 할수 있기 때문이라고 한다. 

 

Loss function:

Fig3. Loss function

$L_{cls}$는 $focal loss$, $L_{reg})는 $IoU$ loss를 사용 했다.  $N_{pos}$는 positive sample 수이다.

본 논문에서 $\lambda=1$로 세팅했다.  

 

2. Multi-level Prediction with FPN 

이 논문에서 제안하는 FPN의 구조는 아래와 같다. 

Fig4. FCOS architecture. 가장 왼쪽의 숫자는 featuremap의 크기(Hxw)/output stride이다. P3,P4,P5,P6,P7은 feature pyramid이고 각 feature pyramid를 공용 head의 input으로 사용해 loss를 계산한다.

FPN(feature pyramid network)은 아래와 같은 FCOS의 weak point를 개선한다. 

 

weak point 1: final feature map 이 큰 output stride를 가질때, 작은 물체들은 해당 feature map에서 표현이 안될 수 있어서 찾는게 불가능 하므로 recall을 낮게 만드는 원인이 된다. 

weak point2: GT bbox 가 겹칠 경우 (Fig2. 오른쪽 overlapped region 참조) "overlap된 구간에있는 pixel들은 어떤 겹쳐진 두 bbox에 둘다 속하는데 어느 object의 bbox와 class를 타겟으로 학습해야 하는가?" 에 대한 문제가 생김

 

FPN을 이용 하면 서로 다른 size의 feature map을 만들 수 있는데 각 feature map에서 서로 다른 크기의 물체를 찾으면 위 약점들을 해결할 수 있다. 

 

weak point 1 해결되는 이유: FPN에서 생성되는 feature map은 서로 다른 output stride를 가지므로 작은 output stride를 가지는 feature map에서 작은 물체를 찾고, 큰 output stride를 가지는 feature map에서

큰 물체를 찾으면 해결가능

weak point 2 해결되는 이유: 저자의 관찰에 따르면 overlap은 대부분 bbox의 크기 차이가 상당한 object들간에 발생하는 경우가 대부분이라고 한다. 따라서 overlap을 생성하는 겹쳐진 물체는 서로 다른 feature map에서 찾게 끔 되므로 overlap 문제를 상당부분 해결 할 수 있다. (예를 들면 Fig2. 오른 쪽에서 사람은 feature map P3에서 찾게 끔 할당되고 테니스 라켓을 P5에서 찾게 끔 할당되게 된다는 의미이다.)

 

 정리하자면 Fig. 4 의 feature map $P_{3},P_{4},P_{5},P_{6},P_{7}$ 각각에서 찾아야 하는 object의 크기를 제안해 놓고 각 feature map에서 찾아야 하는 크기에 해당하는 object만 해당 feature map에서 detection된 object의 positive sample로 사용 하는 것이다. 

 

이걸 정리하자면 $i ={ 3,4,5,6,7}$ 이 feature map $P_{i}$의 레벨을 표현할때 각  $P_{i}$에서는 아래 조건에 해당하는 크기의 object를 positive sample로 학습하도록 한 것이다.  

$$m_{i-1} \leq max(l^{*},t^{*},r^{*},b^{*}) \leq m_{i}$$

 

논문에서 실제로 사용한 $m$ 값은 $\{m_{2},m_{3},m_{4},m_{5},m_{6},m_{7}\} =\{0,64,128,256,512, \infty\}$ 이다. 

 

3. Center-ness

FPN을 이용하고도 FCOS는 anchor based detector들 보다 성능이 떨어졌는데 이 원인은 object center에서 멀리 떨어진 location에서 bbox prediction이 좋지 않아서 였다. 예를 들자면 아래 Fig.5 에서 붉은 점 위치의 pixel에서 검은색에 해당 하는 bbox를 regression 할때 그 예측값 $(l,t,r,b)$가 썩 좋지 않다는 것이다. 

 

Fig5. low quality bbox의 원인

이런 문제를 해결하기 위해 object의 bbox를 결정할때 object center에서 멀리 떨어진 위치에서 예측된 값들의 영향성을 줄이기 위해 아래와 같이 center-ness 를 예측하는 layer를 추가해 이문제를 해결했다. 

center-ness는 Fig4에서 보면 알수 있듯이 classification output과 같은 head에서 계산된다. 

$$ center-ness^{*} = \sqrt{\frac{min(l^{*},r^{*})}{max(l^{*},r^{*})} \times \frac{min(t^{*},b^{*})}{max(t^{*},b^{*})}} $$

이렇게 계산된 center-ness의 heatmap은 아래와 같다. 

Fig6. centerness heatmap

 

실험:

Setting:

backbone: resnet-50

weight initialization: pretrained with imagenet (newly added layer: same as RetinaNet)

batch:16

optimizer: SGD

initial learning rate: 0.01, 0.001, 0.0001( 0.001, 0.0001은 각각 60k, 80k iteration에서 적용함)

input image size: $max(h,w) \leq 1333$, $min(h,w) \leq 800$

 

Fig7. BPR 및 ambiguous sample accuracy

BPR성능:

Anchor based detector에 비해 BPR(best possible recall)이 낮을 것이라는 예상과 다르게 FCOS는 Fig7.의  table 1에 보이듯 $P_{4}$feature map 만 사용 한경우에서 조차 95.55%의 recall rate을 보여준다. 

FPN을 사용 하면 98.4%로 $IoU \geq 0.4$ 인 경우만 고려했을 경우 Anchor base detector인 RetinaNet(with FPN)보다 훨씬 높고 모든 예측된 bbox를 다사용 한 RetinaNet(99.23%)비견될만 하다. 

 

Ambiguous Samples 에 대한 성능:

Fig7의 Table 2에서 보면 minival에서 FPN을 사용 하지 않고 feature map $P_{4}$만 사용 할 경우 $ \frac{ambiguous samples}{all positive sample} = 23.16%$이지만 FPN을 사용할 경우 \frac{ambiguous samples}{all positive sample} =7.14%$로 줄어든다(개인 적인 생각은 FPN이 FCOS에서 효과적으로 ambiguous samples문제를 해결한다는 것을 보여주기에 이결과만으로 충분하다고 생각한다..)

 

이 결과는 저자의 관찰결과(= 대부분의 overlap은 크기 차이가 상단한 object들 사이에 발생하므로 FPN을 사용해 서로 다른 level의 feature map에서 찾게 하면 해결이 가능하다)가 설득력 있다는 근거가 될수 있다.

 

 저자의 주장에 따르면 overlap이 같은 category에 속한 object들 사이에 일어나는 경우는 ambiguous location에 속한 픽셀(receptive field라고 생각하는게 더 정확할거 같다..)들은 어차피 class label이 같아  overlap에 관여하는 object중 어떤 object를 추정해도 성능 하락에 영향이 없으니 이런 경우를 배제 하면 $ \frac{ambiguous samples}{all positive sample} $은 Fig7의 Table 2의 Amb. samples(diff)(%)의 결과를 얻을 수 있다.(배제 한다는 것은 overlap이 서로 다른 category에 속한 object들에 의해 만들어지는 경우만 고려한다는 뜻이다.)

 

 이럴 경우 FPN사용시 \frac{ambiguous samples}{all positive sample} =3.75%$ 까지 하락한다.

(추가 적으로 실제 inference할때 ambiguous location에서 생성된 bbox의 비율을 살펴 보면 2.5%의 bbox만이 이 overlapped location에서 생성되었고 그중 서로 다른 category가 overlap된 경우는 1.5%였다고 한다. 즉, 저자가 말하고자 하는 위와 같은 자신의 주장은 타당하나는 것....)

 

Centerness 의 효용성:

 

Fig 8. Centerness의 효용성

Fig 8 은 centerness를 사용할 때와 그렇지 않을때 의 성능을 보여준다. 직관적으로 알 수 있듯이 결론은 사용하는게 성능 향상에 더 좋다. 다만 $center-ness^{+}$의 경우 center-ness를 Fig 4의 Regression branch 에서 학습 시켰을 경우의 결과 이고, $center-ness$ 는 Fig 4에서 처럼 classification branch에서 학습 시켰을 경우의 결과 이다. 

 

 

FCOS vs Anchor based detector:

Fig 9. FCOS vs Anchor based detector. Improvements FCOS performance result

Fig 9 은 anchor based detector 인 RetinaNet과의 비교이다. 위표는 FCOS와 RetinaNet과의 차이를 최대한 배제한 상태의 실험 결과이다.(FCOS를 설계 할때 기존 RetinaNet에서 사용 하지 않은 Group Normalization(GN)을 사용 하고, FPN을 생성할때 Fig 4에서 $P_{5}$를 사용했는데 RetinaNet에서는 $C_{5}$를 사용해 $P_{6}, P_{7}$을 만들었다는 차이가 있는데 위의 실험 결과는 FCOS에서 GN사용 배제, $P_{6}, P_{7}$ 생성시  $C_{5}$사용등 조건을 최대한 똑같이 맞췄다는 것이다.)

 

결과는 보는데로 비등하거나 FCOS가 더 좋다. Improvement는 논문 제출 이후 개선한 버전에 대한 결과 값이다. 

 

 참고로 위 결과를 얻기 위해 FCOS는 RetinaNet을 학습할때 사용했던 Hyperparameter를 그대로 사용했다고 하는데 Hyperparameter는 해당 model에 최적화 해서 사용 하므로 FCOS에 더 잘맞는 hyperparameter를 사용할 경우 성능은 더욱 좋아 질 수 있을 거라고 한다.

 

(원래 논문에는 region proposal에 사용했을 때 어떤지에 대한 실험도 있는데... 그건 나중에 정리하도록 하자...)

오랜만의 포스팅이다...

읽은 모든 논문에 대해 정리를 하려고 시작했는데..

습관이 덜되고 일에 치이다 보니.. 다시 열심히 해봐야지..

 

 

누군가 이 리뷰에서 잘못된 부분을 발견한다면 또는 글쓴이가 잘못 이해하고 있는 부분이 있다고 생각한다면...

주저 없이 댓글을 남겨 준다면 많은 도움이 될거같다.

 

오늘 리뷰할 논문은 AttendNets이라는 논문으로 DarwinAI 와 Waterloo 대학에서

제안한 내용으로 2020년 9월 archive에 올라왔다.

Title: AttendNets: Tiny Deep Image Recognition Neural Networks for the Edge via Visual Attention Condensers

paper link: https://arxiv.org/pdf/2009.14385.pdf

 

특징 및 Contribution:

1. 단말 기기에서 image recognition을 수행하기 위한 AttendNets이라는 모델을 제안

 

2. 단말 기기란 스마트 폰과 같은 기기를 말하며 제안된 모델은 서버의 도움없이 on-device에서 동작할 수 있는 모델이다.

-> 이를 위해 모델 개발시 low-pricision(정확히 말하자면 8bit pricision)이라는 제한 사항을 두었다.

 

3. Visual Attendtion Condenser(VAC)라는 모듈을 사용하는데 이 모듈은 spatial-channel selective attention을 향상시킨 attention condenser이다. (attention condenser라는 개념이 햇갈렸는데 아래에 이해한대로 열심히 정리해놨다.)

 

4. 이 모델은 machine-driven design 되었는데 내가 이해한 바가 맞다면 AutoML로 모델 개발했다는 말이다.

 

구조:

이 모델의 특징적 구조는 visual attention condenser라는 모듈인데 self attention machanism을 사용한 모듈이다.

 

1. VAC

Visual Attention condenser(VAC) 구조

위 그림은 이 논문에서 주로 사용 되는 self- attention을 위한 구조이다.

이전 까지 타 논문에서 제안되고 사용된 self-attention는 주로 network의 accuracy 향상을 위해 channel-wise attention과 local attention을 분리하고 합쳤던데 반해 이 논문에서는 self-attention machanism을 network 의 효율성 향상을 위한 방향으로 탐구했다.

 

논문의 필자가 주장하는 바에 따르면 제안된 VAC는 낮은 차원수(reduced dimensionality)의 통합된 embedding을 학습하고 생성 할 수 있는데 생성된 embedding은 channel공간과 spatial 공간의 activation관계를 잘 나타내는 특징을 가진 다고 한다.

 

VAC는 다음과 같은 하위 컴포넌트로 구성되어있다.

 

1. Down mixing layer : V'=M(V)

역할: channel reduction

구현 method: point wise convolution

 

2. Condensation layer: Q=C(V')

역할: 공간도메인에서의 차원 축소(featurea의size축소라고 해석해도 될거 같다) 및 강한 activation결과를 가지는 관심영역(ROI)간의 거리 축소를 통해 spatial attention 응집

구현 method: Max pooling

 

3. Embedding structure: K=E(Q)

역할: spatial-channel activation 관계의 특징을 내포하는 embedding K 생성

->spatial-channel activation relationship을 characterzing했다고 하는 이유는 down-mixing layer에서 channel 간의

activation 관계를 모델링 하고 condensation layer에서 spatial domain의 activation관계를 모델링 했기 때문인거 같다.

구현 method: grouped convolution, point wise convolution

 

4. Expansion layer: A=X(K)

역할 : V'에서 관심 영역을 boosting 하기 위한 self-attention value 생성을 위한 spatial domain dimensionality 확대

구현 method: unpooling

 

5. Selective attention mechanism: V''=F(V', A,S), where S is scale factor

역할: selective attention

구현 method: element wise multiplication

 

6. Upmixing layer: V'''=M'(V'')

역할: VAC 모듈의 output이 input V와 같은 channel dimensionality를 같게 하기 위한 channel dimensionality 확대

구현 method: point wise convolution

 

이를 종합해 보면 제안된 VAC모듈의 코드 형태는 다음과 같다고 생각해 볼수 있다.

(어디까지나 수도 코드이다)

down_mixing = torch.nn.Conv2d(in_ch, out_ch,kernel_size=(1,1)) # in_ch > out_ch
condensation_layer =torch.nn.MaxPool2d()
embedding_structure = torch.nn.Sequential(torch.nn.Conv2d(out_ch, out_ch2, kernel_size=(k,k), groups=n),\
        torch.nn.Conv2d(out_ch2, out_ch3, kernel_size=(1,1))) # where k>1, n>1
expansion_layer = torch.nn.MaxUnpool2d()
upmixing_layer = torch.nn.Conv2d(out_ch3, out_ch4, kernel_size=(1,1)) #where out_ch4> out_ch3

V' = down_mixing(V)
Q = condensation_layer(V')
K = embedding_structure(Q)
A = expansion_layer(K)
V'' =V'*A*S #where S is scale factor
V''' = upmixing_layer(V'')

 

2. Machine-driven Design

논문의 실험에 사용된 모델 구조는 식을 통해 machine-driven된 구조이다.

간단하게 생각하자면 디자인 하고자 하는 모델의 제한사항을 정의한 하고 그 제한 사항을 만족하는 모델을 iterative solving process같은 방식으로 찾는 것이다.

 

식에서 G(s)는 모델의 생성자이다. 생성된 모델 중 성능평가 함수 U()를 최대화 하면서 동작조건 $l_r(G)=1$ 을 만족하는 모델이 찾고자 하는 모델이다.

 

$l_r(G)$은 생성된 모델 G가 아래 두 조건을 만족하는지 판단한다.

1. $imagenet_{50}$에 대해 top-1 validation accuracy 가 71% 크거나 같다.

2. 8-bit weight pricision

 

본 논문에서는 prototype 모델에 대해 아래와 같은 기본적인 정의만 한 상태에서 구체적인 모델의 디자인은 위 수식을 통해 생성한다.

1. input channel=3

2. output logits은 average pooling, fully connected, softmax를 순서대로 거쳐 생성한다.

 

 

이렇게 생성된 모델은 아래와 같은 모습이다.

AttendNets achitecture-각 모듈을 나타내는 사각형 심볼안의 숫자는 각 layer의 output channel수를 의미하고 VAC 심볼 안의 숫자는 각각 down-mixing layer, first and second layer of embedding structure, up-mixing layer의 output channel 수를 의미한다. PEPE 모듈의 심볼안에 있는 숫자들은 각각 first projection layer, first expansion layer, second projection layer, second expansion layer의 output channel 수이다.

AttendNet-A와 B에서 공통적으로 보이는 특징은 VAC(visual attention condenser)모듈이 모델의 초반부에 집중적으로 사용되고 PEPE(projection-expansion-projection-expansion)모듈이 모델 후반부에 집중적으로 사용된다는 것이다.

PEPE모듈은 machine-driven exploration을 통해 발견한 구조이다.

 

VAC모듈이 모델 초반부에 많이 사용되는 것은 visual selective attention이 low-mid level visual feature extraction 단계에서 중요한 역할을 한다고 해석 할 수 있다.

 

AttendNets-A와 B의 구조적 차이는 효율성과 정확성의 균형을 이루며 특정 테스크를 수행하는데 필요한 모델을 찾기 위한 machine-driven exploration의 결이다.

 

논문의 필자는 이러한 구조적 차이를 macroarchitecture 와 microarchitecture 의 다양성이라고 표현했는데

macroarchitecture의 다양성이란 VAC, PEPE, spatial convolution, point wise convolution등이 서로 다르게 섞여있는 것이고

microarchitecture의 다양성이란VAC, PEPE 모듈 내부의 layer들이 서로 다른 channel수를 갖도록 configuration된것에서 알 수 있다고 주장한다.

 

(의문은 microarchitecture에 대한 주장은 이해가 가는데, macroarchitecture의 다양성에 대한 주장은 이해가 가지 않는다.

논문에서는 At macroarchitecture level, there is a heterogeneous mix of visual attention condensers, PEPE modules, spatial and pointwise convolutions, and fully-connected layers.

라고 표현했는데 모델 구조를 나타낸 그림에서 AttendNets-A와 B 에서 macroarchitecture level의 차이는 보이지 않는다. 내가 잘못 이해 하고 있는 걸까? )

 

결과

위 테이블은 $imageNet_{50}$에 대한 대조군과 제안된 AttendNets 의 실험 결과를 보여준다.

AttendNets은 대조군들에 비해 낮은 8-bits weight pricision과 적은 MACs을 가짐에도 불구하고

대조군들과 유사하거나 높은 Top-1 accuracy를 보여준다.

목표했던 단말에서 on-device로 동작 가능한 model을 제안한다는 주장에 힘을 실어 주는 결과로 보인다.

기 학습된 모델과 파라미터를 로드해 특정 레이어를 제외한 나머지 레이어의 _weight_을 freeze하고 finetuning하고자 하다가 또
어이없는 실수를 하고 말아 다시 정리한다.

환경:

apex.parallel DistributedDataParallel 을 이용해 학습

에러 발생 순서:

  1. DistributedDataParallel 을 이용해 학습 도중 _model.state_dict()_을 저장
  2. 학습 하던 모델에서 backbone 및 특정 _head_의 _weight_만 학습 시키기 위해 학습을 원하지 않는 _tensor_의 _requires_grad_를 _False_로 세팅 한다.
    대략 적인 코드는 다음과 같다.
model = modelFactory(model)
#wrap model using distributedDataParallel
if torch.cuda.is_available():
    model.cuda()
    if torch.distributed.is_initialized():
        model = DistributedDataParallel(model)

#load trained parameters
checkpoint = torch.load(path, map_location = lambda storage, loc: storage.cuda(torch.distributed.get_rank()))
model.load_state_dict(checkpoint['state_dict'])
model.freezePartOfModel() #<- 에러 발생 부분

위와 같은 작업 흐름을 따라 갈 경우 아래와 같은 에러가 발생할 수 있다.

AttributeError: 'DistributedDataParallel' has bo attribute 'freezePartOfModel'

발생 원인:

아래와 같이 DistributedDataParallel 로 모델을 wrap up 할 경우 기존 _model_은 _module_로 감싸여 진다.

modelDDP= DistributedDaraParallel(model)

즉, 감싸여져 있다는 의미는 model.a_라는 *attribute_에 접근 하기 위해서는 *modelDDP.module.a 로 접근 해야 한다는 것이다.

해결 방법:

아래 처럼 코드를 고치면 해결이 가능 하다.

if torch.distributed.is_distributed():
    model.module.freezePartOfModel()
else: # for single gpu usage
    model.freezePartOfModel()

 

학습된 파라미터 로드 중 표제와 같은 에러가 발생하는 경우가 있다. 이런 경우에 대한 해결책을 정리해본다.

에러 발생 작업 순서:

1. apex의 DistributedDataParallel를 이용해 multi gpu로 모델 학습 중 아래 코드를 이용해 모델 state 저장

torch.save('state_dict':model.state_dict())

 

2. 저장된 state_dict을 단일 gpu를 사용해 테스트 하기 위해 torch.load()로 복원 하던 중 다음과 같은 에러가 발생했다.

'''
Error(s) in loading state_dict for model: 
	Missing key(s) in state_dict: "backbone.block0.0.0.weight", ~, ~
	Unexpected key(s) in state_dict: "module.backbone.0.0.weight", ~, ~
'''

3. 위 에러 메시지에서 확인 가능 하듯 저장된 state_dict에는 모든 weight 이름 앞에 "module" 이라는 prefix가 추가되어 있다.

 

발생 사유:

아래와 같이 DistributedDataParallel 사용 시

DDPmodel = DistributedDataParallel(model)

리턴된 _DDPmodel_은 _model_을 _module_로 감싼 형태 이다.

즉, 기존 model_의 *attribute_은 *model.module.attribute 과 같이 접근 해야 하는데 그걸 빼먹은 것이다.

 

 

해결 방법 1:

학습 중 모델 저장 시 아래 코드를 이용해 모델 상태를 저장한다.

torch.save('state_dict':model.module.state_dict())

 

 

해결 방법 2:

본래 목적이 기 학습된 모델의 구조를 유지하고 파라미터의 극히 일부분만 finetuning_하는 것이 었기 때문에 *_해결 방법 1** 은 좋은 해결 방법은 아니다. 본래의 목적을 위해서는 로드된 모델 _state_dict_의 파라미터 이름에서 *module. (또는 .module)을 제거 하면 정상적인 로드가 가능하다.다음은 예제 코드이다.

loaded_state_dict = torch.load(state_dict_path)
new_state_dict = OrderedDict()
for n, v in loaded_state_dict.items():
    name = n.replace("module.","") # .module이 중간에 포함된 형태라면 (".module","")로 치환
    new_state_dict[name] = v

model.load_state_dict(new_state_dict)

비슷 한 이슈를 격은 사람들이 이 링크에 있는거 같다.

link:참조

빠른 구조의 네트워크를 찾던 중 아래 논문을 보게 되어 정리하고자 한다.

centernet 이란 이름의 논문이 두개 있어서 헷갈릴 수 있는데 여기서 리뷰하는 논문은 아래 논문이다.

 

title: Objects as points (2019년 아카이브에 올라왔다.)

paper link: arxiv.org/pdf/1904.07850.pdf

code link: github.com/xingyizhou/CenterNet

 

xingyizhou/CenterNet

Object detection, 3D detection, and pose estimation using center point detection: - xingyizhou/CenterNet

github.com

centernet의 특징

  • One-stage detector
    어찌 보면 당연하게도 centernet은 YOLO, SSD 등과 같은 one-stage detector이다.
    다만, YOLO나 SSD가 미리 정해진 anchor에서 bounding box의 overlap을 기준으로 삼는것과 달리
    centernet은 각 object에 대해 단 하나의 중심점을 key point estimation방식으로 추정한다.

  • Object detection 문제를 key point estimation문제로 정의해 풀었다.
    바로 위의 설명과 겹치는 부분이 많지만 기존 one-stage detector 들은 각 anchor 위치에서 정해진 비율의 bbox들에 대해

    overlap 정도를 기준으로 학습하지만 centernet은 feature map으로 부터 각 object의 center 위치를 확률 맵으로 나타내어 학습한다. center 위치를 key point로 정의하고 문제를 푼 것이다. 

기존 one-stage detector와 centernet의 차이(objects as points 논문에서 발췌)

  • 구조가 매우 단순해 이해 하기 쉽고 orientation, pose, 3d bbox 추정으로 쉽게 확장 가능 할 것같다.

구조

수치는 input: 3x256x512, output stride: 4 기준이다. 

centernet 구조

  • 위 centernet 구조 그림에서 처럼 centernet은 feature를 생성하는 backbone과 3개의 header로 이루어져 있다.

  • 해더는 각각 center heatmap, center offset, width and height 를 출력한다. 

  • center heatmap은 channel index가 각 object category에 해당한다. 예를 들어 사람의 object id=0 이면
    centerHeatmap[0,:,:]에는 사람의 center 위치에 해당하는 pixel의 값이 1에 가까울 것이다. 
    (그림에서 center heatmap 위의 ncls x 64 X128은 출력 텐서의 모양이고 ncls는 number of classes의 약자이다.)

  • center offset은 output stride때문에 발생하는 discretization error를 보상하기 위해 추정하는 것으로 각 object center 위치를
    input image scale로 해석할때 오차를 보상하기 위한 값이다. 예를 들어 input image에서 A 라는 object의 center가 (146, 133)에 
    위치한다면 출력 center heatmap 텐서에서 해당 object의 위치는 (36,33)이다. 하지만 실제 (146/4, 133/4)=(36.5, 33.25)이므로
    (0.5, 0.25)의 오차가 발생한다. 이 오차가 output stride에 의해 발생하는 discretization error 이고 이를 보상해 주기 위해 추정한다.
  • 마지막으로 width and height 텐서는 object의 width, height를 추정 하는 텐서이다. 예를 들어 object id =0 인 물체에의 width, height는 widthAndHeight[0:2, x,y] 위치의 값이고 object id=4인 물체의 width, height는 widthAndHeight[8:10,x,y] 위치에 저장된다.

Loss

  • center heatmap loss

center heatmap loss: focal loss

$Y_{xyc}$=1은 object center 위치의 GT 값이다. 학습 초반 $\hat{Y}_{xyc}$의 값이 작다면 $(1-\hat{Y}_{xyc})^{\alpha}$ 의 값이 상대적으로 커서 loss 가 커지고 학습 후반으로 갈록 $\hat{Y}_{xyc}$의 값이 0에 가까워 지면 $(1-\hat{Y}_{xyc})^{\alpha}$ 의 값이 기하 급수적으로 작아져 loss에 거의 영향을 미치지 않는다. 

반대로 center point가 아닌 위치에서 학습 초반에 $\hat{Y}_{xyc}$ 의 값이 크다면 $(1- Y_{xyc})^{\beta} (\hat{Y}_{xyc})^{\alpha}$의 값이 상대적으로 커서 loss에 영향을 크게 미치지만 학습 후반 부로 갈수록 이 값이 기하 급수적으로 작아져 loss가 줄어 든다.

 

  • center offset loss 

center offset loss

구조 설명에서 언급한것 처럼 output stride로 인해 발생하는 discretization error를 보상하기 위해 추정하는 offset값이다. 

$\frac{p}{R}-\tilde{p}$ 부분이 의미하는 것이 discretization error 이고 여기서 R은 output stride를 의미한다. 

p는 input image scale에서 object center의 x 또는 y 좌표 이고 $\tilde{p}$ 는 소수를 버림한 $\frac{p}{R}$ 값이다.

 

이 loss는 위와 같이 단순히 L1 loss를 이용한다.

 

  • width and height loss

width and height loss

width 와 height loss는 단순히 L1 loss 를 이용 하여 위와 같이 구한다. 

 

  • total loss

total loss는 위와 같고 논문에서는 $\lambda_{size}$ =0.1 , $\lambda_{off}$=1 로 설정했다.

 

 

이 정리만 봐선 내용 파악이 힘들것 같아 근시일 내에 GT 만드는 내용도 추가해서 정리 해야 할것 같다.....

pytorch 코드를 분석하다 torch.gather() 메서드의 동작이 헷갈려 정리해 본다.

 

torch.gather() 메서드는 input tensor로 부터 원하는 차원에서 주어진 index에 해당하는 원소만을 골라 새로운 텐서를 만들때 사용하며

 

만들어진 새로운 텐서는 주어진 index 텐서와 shape(또는 size)가 같다. 

 

일단 예제를 보자.

 

아래와 같은 3차원 텐서를 t 생각해보자

import torch
import numpy as np

t = torch.tensor([i for i in range(4*2*3)]).reshape(4,2,3)
print(t)

여기서 아래 그림과 같이 axis =0 에서 원소 1,0,3만으로 구성된 새로운 텐서를 만들려면 

Fig 1. gather 메서드의 동작 방식

 

# 1,0,3 은 추출하고 싶은 원소의 타겟 dimension의 원소 index 이다.
ind_A = torch.tensor([1,0,3])

# torch.gather()에서 index tensor의 차원수는 input tensor의 차수원수와 같아야 한다. 
# 즉 이 예제에서 t.dim() == ind_A.dim() 이어야 torch.gather()를 사용 할 수 있다.
# 이를 위해 ind_A의 차원을 t와 맞춰 주면
ind_A = ind_A.unsqueeze(1).unsqueeze(2)

# 여기 까지는 차원의 수만 맞춘것이다. gather가 정상적으로 동작하기 위해서는 타겟으로 하는 dimension를 제외한
# t와 ind_A의 나머지 dimension의 값이 같아야 한다. 
# 즉 내가 추출하고자 하는 원소가 dim 0의 원소라면 t.size(), ind_A.size() 에서 
# t.size(1)==ind_A.size(1) and t.size(2)==ind_A.size(2)의 조건을 만족해야 한다.
ind_A = ind_A.expand(ind_A.size(0), t.size(1), t.size(2))

# 여기 까지 코드를 실행 시킨면 ind_A.size() = [3,2,3] 이고 t.size()=[4,2,3] 이다.
# 앞서 설명했듯 target dimension 인 ind_A.size(0)!=t.size(0) 을 제외한 1,2 차원의 값이 2,3으로 같다.
# 최종적으로 위 그림 같이 dim=0에서 1,0,3 번째 원소를 추출하여 새로운 텐서를 구성하기 위해 아래 구문을 실면행하면된다.
res = res.gather(0,ind_A)

결과는 다음과 같다.

위 Fig 1.에서 처럼 t(input tensor)로 부터 dimension 0 의 1,0,3 번째 원소를 선택해 출력한 결과 이다. 

 

t.shape == [4,2,3] 이므로 dimension 0의 원소들은 shape 이 [2,3]인 행렬이다.

즉, t[1].shape==[2,3], t[0].shape == [2,3], t[3].shape==[2,3]

 

장황해 지만 다시 정리하면 torch.gather 메서드는 input tensor의 타겟 dimension으로 부터 원하는 원소를 추출해

새로운 텐서를 만들때 사용 하며 index tensor는 다음을 만족해야 한다.

 

1. inputTensor.dim()==indexTensor.dim()

2. inputTensor.size() == [x,y,z] 이고 indexTensor.size()==[x',y',z'] 일 때

   타겟 dimension=0 이면 y==y' and z==z' 이어야 한다.

   타겟 dimension=1 이면 x==x' and z==z' 이어야 한다. 

   타겟 dimension=2 이면 x==x' and y==y' 이어야 한다. 

 타겟 dimension 이란 torch.gather(dim=x, indexTensor) 에서 dim 파라미터에 할당되는 값을 의미한다.

 

+ Recent posts