개인적으로 읽고 쓰는 공부용 리뷰입니다.

틀린 점이 있을 수도 있으니 감안하고 읽어주세요. 피드백은 댓글로 부탁드립니다.

paper overview

  • 논문링크 : paper link
  • fully convolutional network 
  • encoding & decoding

2015 CVPR에 나온 논문으로([Submitted on 14 Nov 2014 (v1), last revised 8 Mar 2015 (this version, v2)]), 2021년인 지금 깊게 공부하기엔 매우 올드하지만, 이 논문은 semantic segmentation의 초석을 닦은 연구라고 생각하기때문에, 한번쯤은 짚고 갈 필요가 매우 있다고 생각한다. 이 논문의 핵심은 semantic segmentation과 fully convolution이다.  semantic segmentation은 일반적인 classification과 유사하지만 결과물만 다르다. 이미지 전체를 토대로 classification을 하는 것이 아닌 픽셀단위로 classification을 하는 것이다. imagnet dataset을 예시로 들어보자면 classification 네트워크의 경우 ouput shape가 n x 1000 이라면 segmentation의 경우라면 ouput shape가 입력과 크기가 똑같은  N x H x W x 1000이 될 것 이다. 물론 imagenet으로는 학습 못한다. segmentation용으로 레이블링 된 데이터가 없으므로.

저 그림은 voc dataset이다. 그리고 이 데이터셋은 클래스가 21개다. 그렇기 때문에 위 그림의 아웃풋의 채널이 21인 것이다. pixelwise prediction 전까지는 classification downsampling과정이랑 같다. 그러나 그 뒤에 아웃풋의 shape가 다르다. 이 차이점이다. 보통 feature를 뽑아내는 과정을 encoding이라고 하고, 이 후에 이미지 크기로 다시 복원하는 과정을 decoding이라고 한다. 위 그림에는 이 디코딩 과정이 생략된 것이다. 그리고 이 모든 과정을 convolution layer로만 네트워크를 구축했다. 그래서 논문 이름이 fully convolutional network인 것이다. 보통 classification은 마지막에 fully connected layer(fcl)이 붙는데 fcl은 고정된 크기의 인풋을 요구한다. 근데 이게 사라졌으니까, 이론적으로는 크기에 불변이다. ㅋㅋ 근데 stride가 1/2씩 떨어지고 보통 1/32까지 떨구기때문에 입력 이미지의 크기는 32의 배수로 한다. ( 아니어도 되는데 fowarding 맞추기가 귀찮다.) 

figure 2의 위쪽은 classification을 의미한다. 이 과정이 semantic segmentation으로 포함되면 아래 부분처럼 heatmap형식으로 나타나는 것을 보여주는 figure다. 

figure 3는 fcn의 전체적인 플로우다. 전체적으로 encoding 과정은 전체적으로 VGG와 같다. 풀링레이어는 풀1에서 풀5까지 다섯개의 레이어가 있다. 각각 1/2씩 해상도가 떨어져서 1/32까지 떨어진다. 컨브6,7은 기존의 fcl을 컨볼루션으로 대체한 것이다. 32x 16x 8x 4x 2x 얘네들은 upsample layer로, transposed conv를 의미한다. feature map이 겹쳐져 있는부분은 덧셈을 의미한다. 

pool5 만 사용한것이 fcn32. pool5+ pool4가 fcn 16, pool5,4,3다 쓴것이 fcn8이다 fcn뒤에 붙은것은 아웃풋 피쳐맵의 누적 스트라이드라고 생각하면 된다. 

네트워크의 stride가 커지면 더 complex한 feature를 학습할수는 있지만 spatial information이 사라지기 마련이다. fcn은 이것을 앞쪽의 feature map을 inference에서 활용하는 쪽으로 방향을 잡았고, 많은 이 후 논문들이 이러한 방식을 택하고 있다. 따라서 결과 이미지로보나 수치로보나 fcn8s가 제일 좋다. 

개인적으로 읽고 쓰는 공부용 리뷰입니다.

틀린 점이 있을 수도 있으니 감안하고 읽어주세요. 피드백은 댓글로 부탁드립니다.

본래는 0. Install tensorrt 부터 하고싶었는데 아.. 설치과정은 굳이 리뷰하기 귀찮다
그리고 어차피 이걸 누가 주의깊게 읽을거라고 생각안한다.
원래 논문도 매주 하나씩 리뷰하려고 했는데 이게 상당히 시간을 요하는 작업이라 이게 이후에 이어질지는 사실 나도 잘 모르겠다.
아무튼 이번에 쓰는 포스트는 어떤 과정을 통해 내가 학습한 모델이 tensorrt 모델로 변환이 되는지 정리하려고 한다.
아래는 레퍼런스다.
https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/#create_network_c
https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/index.html
eehoeskrap.tistory.com/
레퍼런스를 먼저 말하는 이유는 위에 링크를 안보고는 바로 이해하기 힘들 것 같아서이다. 세번째에 링크된 꾸준희님 블로그는 관련된 포스트가 많다. 그리고 내가 쓰는 글에 비해 훨씬 나을 것 이다.
아무튼, nvidia에서는 빠른 속도를 위해 다양한 프레임워크에서 학습된 custom model들을 gpu에 최적화 시켜주는 라이브러리를 제공하는데 이게 tensorrt다. 결론만 말하자면, tensorrt(이하 trt)의 목적은 빠른 inference 속도를 위한 것이다.
이 과정이 처음에 접하기에는 굉장히 복잡하다.
다시 본 내용으로 와서 나는 pytorch, tensorflow 또는 caffe 등 다양한 framework를 통해 딥러닝 모델을 학습을 시킨다. 그리고 nvidia에서 만든 이 tensorrt는 이러한 모델을 parsing하는 기능을 제공하며, gpu에 최적화시켜 보다 빠른 추론속도를 가능케해주는 라이브러리다. 좋다. 단순히 trtexec를 써도되지만, 좀더 깊게 내가 직접 빌드할때가 분명히 있을것 이다. 그리고 100% 나처럼 무조건 다 직접 이해하고 짜야만 직성이 풀리는 사람도 있을 것이다. 삽질을 좋아하는 사람.
그리고 더좋은건 c++ 과 python 두 언어에 대한 api를 제공한다. pytorch를 사용하는 입장에서, 당연히 python api를 사용하고 싶지만, 그럴여건이 되지 않을 때도 있다. 이 포스트는 c++ 뉴비인(나 포함) 사람들에게 혹시나 도움이 될까 쓰는 개인 저장용 포스트다.(참고로 python api는 window를 지원하지않는다)
이 포스트를 보기전에 상단의 레퍼런스 첫번째 링크 적어도 2.1은 정독하는걸 매우 추천한다.
trt에서 custom model을 trt engine으로 빌드하는 과정은 다음과 같은 과정을 거친다. 이 과정의 목적은 tensorrt에서 사용하는 ICudaEngine을 만드는 것이다. 그리고 이 아래로 bold 처리된 것은 모두 클래스다.
1. create IBuilder by using createInferBuilder with Logger.(logger는 trt sample code에 있는거 사용 추천)
2. create IBuilderConfig by using IBuilder::createBuilderConfig().
3. define IBuilderConfig options. ex) fp16, int8, dla etc..
4. create INetworkDefinition by using IBuilder::createNetworkV2().
5. create proper IParser for defining INetworkDefinition.
6. define INetworkDefinition by using IParser::parse
7. call IBuilder::buildEngineWithConfig() with pre-defined INetworkDefinition & IBuilderConfig
8. save or run ICudaEngine with IExecutionContext
대충 흐름이 파악됐다면, 이제 천천히 한단계씩 보면 된다.

1. create IBuilder by using createInferBuilder with Logger

IBuilderICudaEngine을 만들기 위한 이런저것들을 정의할 클래스다.
auto builder = UniquePtr<IBuilder>(createInferBuilder(gLogger.getTRTLogger()));
대충 이런식으로 만든다 UniquePtr은 내가만든 포인터 형식인데 그냥 아래처럼 쓰면된다. 효율적인 포인터 사용을 위해 nvidia코드를 베낀것이다. 아무튼 여기까진 쉽다.
IBuilder* builder = nvinfer1::createInferBuilder(gLogger.getTRTLogger());
레퍼런스에 있는 예시처럼 (아래) 간단하게 만들어도 되지만, 그냥 sample코드에있는 logger.h, logging.h, 등등을 복붙해서 사용하는 것을 추천한다.
class Logger : public ILogger
{
void log(Severity severity, const char* msg) override
{
// suppress info-level messages
if (severity != Severity::kINFO)
std::cout << msg << std::endl;
}
} gLogger;

2. create IBuilderConfig by using IBuilder::createBuilderConfig().

auto config = UniquePtr<IBuilderConfig>( builder->createBuilderConfig() );
IBuilder를 만들었으면 위처럼 builderconfig를 만든다. 이글을 볼때쯤엔 다알겠지만 trt engine을 빌드할때, fp16(half), int8 등의 옵션을 쓰며 build할 수 있다. 이러한 옵션을 IBuilderConfig가 정의한다. 자세한건 다음에 블로깅될 포스트 또는 레퍼런스를 참고하면된다. 참고로 dla를 쓸지안쓸지도 이 클래스로 정의한다.

3. define IBuilderConfig options. ex) fp16, int8, dla etc..

2번에서 에서 얘기했다

4. create INetworkDefinition by using IBuilder::createNetworkV2().

auto network = UniquePtr<INetworkDefinition>(builder->createNetworkV2(explicitBatch));
위처럼 만든다. 뭐냐면 그냥 내 커스텀 모델의 네트워크 정보들을 담는 변수다. 이 변수를 만드는 과정이다.

5. create proper IParser for defining INetworkDefinition.

INetworkDefinition을 정의하기위해선 적절한 parser를 사용해야한다. 당연히 trt자체로 parser를 제공한다. 세가지 parser를 제공한다. caffer, uff, onnx. IParser는 다음처럼 만들면 된다. 간단하다.
auto parser = nvonnxparser::createParser(*network, gLogger);
auto parser = nvcaffeparser1::createCaffeParser();
auto parser = nvuffparser::createUffParser();

6. define INetworkDefinition by using IParser::parse

IParser를 만들었으면 IParser::parse()를 통해 내 INetworkDefinition을 정의하면된다. IParser::parse()를 이용하면 된다.
아마 직접 코드를 짜서 빌드를한다면 99.9%여기에서 에러가 날것이다. 지원하지 않는 레이어도 많고 연산도 많다. 해결 방법은 두가지다. 지원하는 레이어로만 구성해서 원하는 결과가 나오도록 모델을 구성하던가, 직접 커스텀 레이어를 짜던가. 후자는 나중에 포스팅이 될것이다 언젠간.

7. call IBuilder::buildEngineWithConfig() with pre-defined INetworkDefinition & IBuilderConfig
아무튼 이제 INetworkDefinitionIBuilderConfig를 정의했을것이다. 그럼 이제 빌드만 하면 된다. 아래처럼 IBuilder::createBuilderConfig()를 호출하면된다.
mEngine = shared_ptr<ICudaEngine>(
builder->buildEngineWithConfig( *network, *config ), InferDeleter() );
return 값이 ICudaEngine이다.




끝이다.

이제 이 결과 값으로 inference를 하면 된다. 저장을하던가. 근데 저장하려면 serealizing이 필요하다. 나중에 포스팅하겠다.
그리고 중요한건 이 아웃풋 엔진은 gpu에 최적화과 되어있는 상태다. 즉 내가 지금 이 과정까지 gtx 1080 에서 했다면 gtx titan에서 못쓴다. 가능하긴 하지만 비추천한다. 관련 officail 링크다. docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#troubleshooting

개인적으로 읽고 쓰는 공부용 리뷰입니다.

틀린 점이 있을 수도 있으니 감안하고 읽어주세요. 피드백은 댓글로 부탁드립니다.

 

paper overview

  • 논문링크 : paper link

  • Hierarchical Deep Aggregation(HDA)
  • Iterative Deep Aggregation(IDA)

원래 VGG 쓸 차롄데, 최근에 보고 코드를 짠거라 까먹기 전에 이거 먼저 리뷰를 쓴다.

 

이 논문의 주요 키워드는 aggregation이다. resnet의 residual connection부터 unet의 feature concatenation등 feature map의 hierarchical connection은 이전부터 많이 실험 및 연구 되어왔다. 이 논문에서는 과연 기존의 feature aggregation이 최선일까?라는 의문에서 이를 해결하기 위한 연구를 한 논문이다. 저자도 굉장히 유명한 Fisher Yu와 Evan Shelhamer다. 그리고 최신 네트워크의 backbone모델로도 많이 사용되고 있는 모델이다. 사실 내가 마지막으로 쓴 논문도 semantic segmentation에서 이 점을 어떻게 해결할까? 로 시작했던 점이어서 아쉽기도 하고 부러운.. 논문이다. 그리고 논문만 봐서는 사실 이해가 잘 안돼서 코드를 보고 역으로 추론한 것이 많다.

 

Feature aggregation 

사실 이 논문 외에도 semantic segmenation, object detection등 다양한 task에서 neural network의 중간 feature들을 어떻게 잘 종합하느냐에 따라 성능이 좌지우지 되는 것은 많은 연구를 통해 이미 증명되었다. 이 부분에 대해서는 논문은 계층적 그리고 반복적인 aggregation을 제안한다. 개인적으로 느끼기에는 논문이 영어가 너무 깔끔해서 쉽게 읽혔다. 다음 부분이 introduction의 핵심이다.

 

>>> "In this work, we investigate how to aggregate layers to better fuse semantic and spatial information for recognition and localization. Extending the “shallow” skip connections of current approaches, our aggregation architectures incorporate more depth and sharing. We introduce two structures for deep layer aggregation. iterative deep aggregation (IDA) and hierarchical deep aggregation (HDA)"

 

IDA & HDA

>>> "IDA focuses on fusing resolutions and scales while HDA focuses on merging features from all modules and channels. IDA follows the base hierarchy to refine resolution and aggregate scale stage-bystage. HDA assembles its own hierarchy of tree-structured connections that cross and merge stages to aggregate different levels of representation."

 

논문에서 인용한 단락이다. 진짜 이 논문은 다른 것 필요 없고 위 단락으로 사실 설명이 끝이다. IDA는 서로 다른 resolution의 featuremap 들을 지속적으로 fusing하기위한 aggretation을 의미하고,  HDA는 같은 resolution상에서(같은 계층 상에서) 그 tree들을 서로 잘 aggregation하는 connection을 의미한다.

 

왼쪽 그림이 논문의 figure2다. c와 f만 잘라왔지만, 당연히 논문에는 a~f까지 다있다. a와 b는 기존에 존재하는 방식에 대해 언급하고 있고 d,e는 다른 방식의 aggregation에 대해서 설명하고 있지만, 결국에 제안하는 방식은 왼쪽 그림의 c(IDA), f(HDA)다.

 

흰색 네모들은 큰거와 작은 것이 각각 conv stage와 conv block들을 의미하고 초록색이 aggregation node를 의미한다. 

stage와 block의 차이점은 block모인것이 stage라고 생각하면 된다. resnet의 큰 conv block을 생각하면 된다.

즉 IDA는 stage단위로 엮어주고, HDA는 block단위로 엮어주는 개념이다. 사실 이그림만 봐서는 제대로 이해가 안됐지만, figure 3까지 같이 보면 이해가 좀 쉽다. 아래서부터는 각 aggregation에 대한 디테일한 설명이다. 

 

 

 

HDA(Hierarchical Deep Aggregation) detail

figure 3

이 figure3가 전체적인 DLA의 큰 flow인데, HDA와 IDA의 차이점이 확연하게 차이난다. 일단 가장 큰 차이점은 resolution의 차이라고 생각하면 된다. 파란선이 down sample을 의미하는데 각 HDA안에서는 down sample이 존재하지 않는다. IDA는 서로 다른 resolution의 지속적인 연결이라고 언급했는데 여기서 노란선이 이를 보여준다. HDA는 같은 stage내 feature map들의 conntection이다. 저 흰색 박스는 resnet의 conv block을 생각하면 된다. 그리고 빨간 박스는 HDA의 Tree level이라고 생각하면 쉽다 왼쪽 빨간박스부터 level1, level2, level3의 HDA를 의미한다. 

 

여기서 흰 박스들은 resnet의 conv block이라고 했는데, 발그림이긴 하지만 아래 그림을 보면

왼쪽부터 basic convblock, basic conv block with projection, bottlenect convblock, bottleneck convblock with projection이다. 아마 resnet을 짜봤으면 바로 이해했을 것이다. 위 그림중 왼쪽의 connection은 residual connection을 나타내는데, 이는 덧셈 연산이기 때문에 해상도가 안맞거나, channel수가 안맞으면 연산이 불가하다. projection은 이럴 때 1x1 conv를 이용해 resolution이나 channel을 맞춰주는 역할이다. resolution이 안맞을 때(주로 down sampling) stride=2로 주는데 그러면 전체 feature map을 고려하지 못하기때문에 avgpool을 선행하고 projection을 하는 경우도 있으나 여기서 다룰 문제는 아니므로 스킵.

아무튼 왼쪽 두개는 일반적인 conv block을 의미하고 오른쪽 두개는 연산을 줄이기 위해 채널을 줄였다가 다시 늘리는 bottlenect block이다. 이를 설명하는 이유는 위 figure3에서 작은 흰색 박스가 바로 이 conv block을 의미하기 때문이다. (아래 그림 참조)

 

작은 흰 네모 박스가 conv block을 의미 아. 물론 당연히bottlneck block일수도 있음

 만약 좌측 그림처럼 그냥 흰색 네모 박스 두개가 연달아 있었다면 이것은 그냥 resnet과 같이 conv block을 나열한 것이다. 근데 HDA에서는 위 그림처럼 새 node를 두어 앞의 block의 output도 aggregation에 사용한다. 이게 HDA의 개념이다. (참고로 aggregation node내에서는 concat +  1x1 conv의 연산이 이뤄진다. 이 안에도 1개의 conv가 포함되어 있다.)

 

 

HDA level2

 

HDA level2다. 중간을 기점으로 왼쪽을 잘라보면 HDA level1과 정확히 똑같은 모양이 있다. 조금만 멀리서 보면 결국 중간 기준 오른쪽도 마찬가지로 같다.

 

맞다. HDA level1을 두번 이은 것이다.  

 

흰색 네모 박스가 basic block일 경우 각각 conv layer를 2개씩 갖고 있으므로 HDA level2는 총 10개의 conv가 들어간다. ( aggregation node도 1개씩 갖고있기때문에)

 

마찬가지로 HDA level1의 경우  2x2 + 1 해서 5개의 conv layer를 갖는다. 

 

 

 

 

HDA level3이다. 이또한 자세히 보면 HDA level2가 두번 나열되어있다. 즉 HDA1을 네번 이은 것이다.

맞다. tree 형태를 띄고 있다. 그래서 official code도 tree로 짜여져있는데, 개인적으로는 이 코드가 이해하기 너무 힘들다.

 

아무튼 HDA level3 는 8개의 conv block을 가지므로 basic block 기준, 2*8 + 4(aggregation) 총 20개의 conv layer를 가진다.

 

 

 

HDA를 종합해보자면 같은 계층 또는 Tree안에서 기존의 resnet과 달리 2개의 block마자 계층적인 aggregation이 이러난다. 하나의 Tree를 크게 보자면 뒤쪽일수록 more complex한 feature를 학습할 것이고 이를 보다 low한 앞쪽의 feature map과 합쳐주는 개념이다. 말 그대로 동일 Tree내에서 계층적인 aggregation을 하는 것이다. 물론 이 aggreation에는 앞서 말했듯이, concatenation을 사용한다. 

 

 

IDA(Iterative Deep Aggregation) detail

위의 figure3를 다시 보자. IDA는 노란 선으로 표현되어있다. 보면 노란선이 이어지는 하단의 conv block(흰 박스)들을 보면 중간에 down sampling이 일어난다. 논문에서도 언급된 다양한 resolution의 aggregation이란 이 것을 의미한다. down sampling은 receptive field를 크게 넓히면서 복잡한 feature를 학습하는데 도움을 주지만, 해상도가 작아지기 때문에 spatial information의 손실이 크다. 이를 보완하기위해 IDA를 사용하는 것이다. 단순한 노란선으로 연결되어있지만 해상도를 맞추기 위해 maxpooling을 사용하여 aggregation의 입력으로 들어간다. 즉 다른 feature map들과 함께 concat+ 1x1 conv를 하는 것이다. 

 

 

 

 

위 표를 보자

모든 DLA모델에대해 간략하게 나타낸 것이다. 모든 DLA는 6개의 stage로 구분했다.

stage 1,2 에서는 그냥 숫자가 적혀 있는데 이것은 그냥 conv layer 1개를 의미한다. 그리고 "-" 로 이어진 수는 HDA level - channel수 이다. 

 

예를 들어, DLA-34의 stage3에는 1-64가 적혀있다. 이것은 HDA level1 + 64 channel이란 소리다. 

 

사실 stage1과 stage2는 표대로만 보면 2개의 conv layer로 이루어진것처럼 보이지만 실제로는 input layer를 포함해서 총 3개의 conv layer이다. 

 

DLA34를 보자 34개의 conv layer가 있단 소리다.

 

stage1 + stage2 : 3개

stage3, HDA level1 : 5개 (위에 설명함)

stage4, HDA level2 : 10개

stage5, HDA level2 : 10개 

stage6, HDA level1개 5개 총 33개 + classifier에 1x1 conv1개 = 총 34개의 conv layer로 구성되어있다. 물론 중간에 max pool, bn, relu는 당연히 있다.

 

아 귀찮지만 공부 용도 이니 나중에 잘 기억하기 위해 DLA46까지만 보자면

stage1,2(3개) + stage3(7개 why? bottleneck이니까) + stage4(14개) +stage5(14개) +stage6(7개) + classifier(1개)= 46이다. 

 

C가 붙은건 보다시피 channel 수가 다른 모델에 비해 적다. compact 버전이다.

X가 붙은건 resnext 모델의 group conv를 사용한 것이다. 

X+C는 당연히 compact + group conv 를 의미한다.

 

 

DLA Segmentation

하.. 이건 나중에 쓸래 넘 힘들다 리뷰 

 

 

 

 

+ Recent posts