본문 바로가기
AIML 분야/Video Classification & Detection

[Large-scaled video dataset 학습하기] Kinetics700 pretrained model 만들기 (feat. opencv deadlock)

by 포숑은 맛있어 2021. 2. 4.
반응형

 

Large-scaled video dataset을 직접 학습할 때 발생할 수 있는 거의 모든 문제를 겪어본 것 같다.

video 데이터를 다루는 첫 연구부터 뭣모르고 백본을 만드는 바람에... 이런 상황이 발생했는데, 만약에 비슷한 이슈가 발생한다면 이 삽질과정과 비슷한 해결법을 통해 디버깅이 가능할것 같다.

 

고려해야할 것 요약

  • 용량 엄청나게 넉넉한거 아니면 프레임 직접 잘라서 저장할 생각은 버리는 게 좋다.
    자르는 시간도 엄청나게 오래걸리며, 데이터 꽉꽉 차니까 접근 시간도 기분탓인지 더 걸리는 듯 하다.
    • raid는 용량이 제한되어있으니 저장하기가 조금 꺼려졌다. 비우려고 하면 비울 수는 있겠는데, 지금 이거 하나만 하는 게 아닌데 키네틱스 하나가 엄청나게 잡아먹게 하는 상황을 만들고싶진 않았다.
    • raid외에 다른 추가 스토리지도 있겠으나, 용량은 크지만 역시 access time이 정말 많이 느리다. 특히나 Kinetics같은 큰 데이터를 넣으니 접근속도가 더 느려져서, 학습할때 여길 접근하는건 배보다 배꼽이 더 큰 상황이 발생한다.
    • 위와 같은 이유로, 잘라서 저장하기보다는 동적으로 읽어오는 것을 추천한다.
  • 그러면 __getitem__()에서 비디오를 읽어 텐서를 리턴해야한다.
  • 그렇기에 opencv는 멀리하자. 메모리 터져서 에러뜨거나 데드락 걸린다.
  • 그러면 무엇을 쓰느냐. ffmpeg같은걸 직접 써도 괜찮고, 아니면 pytorch에서 직접 지원하는 게 있다.
    torchvision의 read_video() 함수를 사용하면 pyav 기반으로 코드 한줄이면 가볍게 읽어온다.
    꼭 opencv 아니어도 비디오를 불러오는 모듈들은 찾아보면 여러가지 종류가 있는데, 난 그냥 pytorch의 것을 사용했다.
  • 만약 프레임을 잘라 저장한게 아니라면, exception handling 필수다. 혹시라도 손상되어 읽지 못할 비디오가 존재할 수 있기 때문. 이건 dataset initialization시에는 알 수 없으며, 결국에는 __getitem__()시에만 알 수 있다.
    따라서 None처리를 해주고, 데이터로더의 collate_fn을 직접 정의하여 None 데이터를 제외하도록 처리해주자.
  • 이 과정 중간에 spatial & temporal augmentation을 넣어줄 것도 생각해야한다.
    찾아보니 spatial augmentation은 PIL image 형태로 처리하는 것이 일반적인 것 같다.
    temporal은 그리 신경쓰일건 없다. 나는 temporal stride라든가 window size를 고려하여 frame number를 랜덤 위치에서 뽑아주고, 그 인덱스가지고 불러왔다.

 


 

아래부터는 조금 더 자세히 각각의 이슈에 대해 다룬다.

삽질하는 순서대로 글이 작성되어있어서 보기 불편할 수 있다.

위 모든 내용을 깨닫는 과정이었다.

 

상황

내가 만든 model architecture를 가지고 Kinetics700 pretrained model을 만들려고 한다.

Kinetics (특히 700...)는 데이터가 너무 커서 프레임을 잘라 스토리지에 저장하기가 뭐하다.

프레임을 자르지 않으면 800GB정도이지만, 10초단위의 비디오라고 해도 프레임을 자르는 순간 어마무시하게 커진다.

자른 이미지 용량이 2TB가 넘은 이후로는 해보지 않아서 모르지만, 적어도 이 이상이라는건 알 수 있다. 이정도라면 영상을 112*112로 리사이즈를 하더라해도 용량문제 해결을 장담할 수 없다.

 

그래서 첫번째 시도로는, opencv를 이용해서 dataloader에서 프레임을 자르게 했었다.

그 코드는 github.com/r1ch88/SlowFastNetworks/blob/master/lib/dataset.py를 참고했다.

 

 

그런데 문제가 발생한다.

특정 개수 이상의 데이터 이후에는 데드락에 걸려 그냥 가만히 아무것도 하지 않는 것.

  • 예를 들어, 32배치로 29iter가 돌면 데드락
  • 128배치로 7 iter가 돌면 데드락
  • 7*4=28이니까 다시말해, 특정 분량의 데이터를 처리하고나면 문제가 발생하는 것이다.
  • 이렇게 두가지 현상을 보고 아무래도 뭔가 데이터 처리때문에 램이 부족해서 생기는 문제인가 싶었다.

GPU 메모리는 할당되어있는데 idle 상태이고, 그렇다고 프로세스도 죽지 않고 가만히 기다리기만 한다.

 

 

원인?

문제를 찾아본 결과, opencv의 문제로 판명났다. 아마도.

fork를 계속 해가지고 뭔가 리셋이 안되어서(?) 발생하는 그런 느낌이다. 인텔 망했나?

github.com/opencv/opencv/issues/5150

github.com/pytorch/pytorch/issues/1838

위 링크를 통해 알 수 있다.

 

 

시도 (해결되지 않음)

1. 단순히 shared memory 사이즈를 늘리는건 그냥 문제를 늦추는 꼴 밖에 안될 것이다.

시도 해봤는데 sudo kill -9를 먹고도 죽지 않는 무적의 좀비가 탄생하였다.

nvidia-smi하면 프로세스 목록에는 안뜨는데 멍청하게 gpu 메모리를 마구 잡아먹고있다.

대체?

 

2. 그래서 위 링크대로 Multiprocessing 처리를 하여 spawn을 하고자 했다.

하지만 demonic process는 처리할 수 없다는 에러가 발생한다.

 

3. 또 누군가가 multiprocessing할때 setNumThreads(0), -1을 메인과 job 각각에 추가하도록 했다.

안된다.

 

 

해결법?

기력이 딸려서 아직 시도하지 않았다.

일단 opencv는 집어치우고, 그냥 다른 것에 맞춰 코드를 전부 다시 짜는 게 속 편할 것 같다.

회사동료분들의 얘기를 들어보니, ffmpeg 기반으로 하는 것이 코드가 잘 되어있기 때문에 사용을 추천한다고 하신다.

 

1. pytorch.org/vision/0.8/io.html 찾다보니 이런 게 있다. torchvision에 io처리하는게 있어서, 비디오 넣으면 tensor로 바꿔준다.

2. ffmpeg. 이걸 다른 데이터셋 프레임 자를때밖에 안써봐서 (그냥 있는 코드 긁어서 씀) 몰랐는데, 얘는 그런 처리가 잘 되어있다고 하신다.

그래서 opencv를 버리고 ffmpeg으로 갈아타라는 조언을 주셨다. 요즘 인텔은...

pytorch.org/vision/stable/index.htm

이 링크에 나오는 video_reader가 ffmpeg 기반인 것 같은데, 1의 것이랑 같은 얘기인가? 저 set_video_backend 해서 뭘 어떻게 쓰라는건지 모르겠다. 둘이 같은건가?

 

=> (ffmpeg을 잘은 모르지만) 저 두가지가 같은거라고 볼 수 있었다. torchvision.io에서 지원해주기 때문.

torchvision.io는 torchvision 0.8.1에 릴리즈 되었으며, 이를 위해서는 torch 1.7.0 버전 이상이 필수이다.

torch 1.7.0은 구린 그래픽 드라이버라면 깔리지 않기 때문에 글카 드라이버 업데이트가 필수이다.

 

눈물이 난다. 데이터로더 하나 짠다고 지금 내가 뭘 하는것인가.

 

 

 

[2021.02.05]

torchvision의 read_video() 이용을 위해 글카 업데이트 했다.

업데이트 하는 것도 따로 포스트로 적어놨다. 약간 두서없긴 하지만.

 

한번 테스트겸 read_video()로 비디오를 읽어보자.

 

import torchvision
import av


pth = "대충 비디오 path 입력"


print(torchvision.__version__)
print(av.__version__)
torchvision.set_video_backend('pyav')
from torchvision.io import read_video
a = read_video(pth, 0, 2, pts_unit="sec")


print(a)

 

 

비디오 Path는 그냥 키네틱스 비디오 아무거나 입력해줬다.

 

실행하면 겁나 빠르게 결과가 나온다.

비디오 텐서 불러온거 쭈욱 출력해준다.

 

 

야호. 이제 이거에 맞춰서 데이터로더 코딩만 다시 하면 되는건가.

 

제발 이 친구는 데드락이 걸리지 않길 바란다...

성공하면 opencv 망했다고 외치러 오겠다.

 


 

DataLoader 코딩

  1. DataLoader의 get item을 구현하는데, 이 안에서 프레임을 잘라온다.
  2. pytorch.org/vision/stable/io.html 여기에 있는 read_video를 사용한다. 일단 pyav를 선택했다.
    output : torch tensor
  3. 그런데 augmentation을 적용해야한다.
    1. 혹시 이것도 torch에서 지원해주지 않을까?
      찾아봤는데 torchvision이 아닌 torchvideo가 새로 나온다고 문서가 발견되었다. 그런데 pip으로 깔아봤는데 릴리즈 버전이 0.0.0밖에 없었다.
      모듈을 전부 확인한 결과, 아직 구현되지 않은 것을 알 수 있었다. 구현된 코드는 있는데 릴리즈가 안된듯... Not Implemented로 도배된 모듈들...ㅠ
      언젠가는 나올것같으니 나중에 쓰는걸로...
    2. temporal transform의 경우, 원래 하던 방식대로 했다. temporal_transform 함수의 output이 프레임 번호 list이기 때문에 이걸로 읽어온 비디오에서 선택하는걸로 했다.
    3. spatial transform의 경우, 다른 방법이 없다.
      PIL image로 한장한장 만들어준 후, 원래 적용하던 방식대로 spatial transform을 적용하는 것.
      보통 spatial transform의 경우, 비디오라고 해도 PIL상태에서 적용하는 것이 일반적이라고 한다.
      (아직 릴리즈되진 않았으나) torchvideo.transforms를 뜯어봐도 tensor상태에서는 Normalize등의 transform만 적용하며, 그외 spatial transform같은건 ToPILVideo같은 함수를 통해 먼저 변환하고, 그 다음에 torchvision의 함수들을 씌우는 형태로 구현해놨다.

이게 체감상 opencv보다 훨씬 더 빠르다.

방금 디버깅 끝내고 kinetics700 돌리기 시작해서 정확한건 돌려봐야 시간측정이 될 것 같다.

 


 

[2021.02.08]

데드락은 아닌데 프로그램이 죽어있다.

opencv로 돌린 것 보다는 많이 돌았는데, 에폭 중간에 죽은거봐서 또 메모리문제일까?

여러번 돌려봤으나, 불길하게도 매번 똑같은 iteration에서 죽어있다...

 

 

예상 원인 1 : 읽을 수 없는 비디오의 경우

frame indices가 없다는 에러인데, 비디오 읽는걸 실패해서 그랬을 수 있다.

그러면 getitem에서 중간중간 None이 나오는 문제이기 때문에 dataloader를 바꾸거나, 정말 읽을 수 있는 데이터만 dataset 리스트에 만들어야 문제가 해결될 것 같다. 물론 이게 헛다리짚은걸수도 있다.

 

 

예상 원인 2 : opencv와 똑같이 또 램이 터지는 문제

이것만큼은 제발 아니길 바란다. 더이상 해결할 방법이 안보인다...

torchvision의 read_video 코드 보면 그냥 pyav로 읽어오는 코드 뿐인데다가, with open했으니까 컨테이너 제대로 close() 되었을텐데.

pytorch.org/vision/stable/_modules/torchvision/io/video.html#read_video

아무튼 이 경우는 아니었으면 좋겠고, 아니어야한다...

 

 

 

panoptic segmentation도 돌려봐야하기 때문에, 잠깐 미뤄놓았다.

내일 다시 시도하는걸로.

죽기 전에는 돌릴 수 있는건지 약간 자신감이 하락했다.

 


 

[2021.02.09]

아무래도 clip length = 0이나 None에 걸리는 게 맞는 것 같다. (예상 원인1)

따라서, 두가지 방법을 생각했다.

 

1. dataset init시에 읽어올 수 있는 비디오만 데이터셋 리스트에 넣어주기

=> 이거 어제 돌려놓고 갔는데 아직도 다 체크를 못했다.

늦을건 알았지만 아직까지도 다 못읽어봤을줄이야... 진짜 터진건 아닌지 불길해진다.

이 방법은 아닌걸로.

 

2. dataloader의 getitem에서 exception handling을 해주기.

찾아보니까 이 이슈는 이미지에 대해서 얘기했는데, 누군가가 나와 같은 상황에서 이 방법을 썼다는 댓글이 있다.

github.com/pytorch/pytorch/issues/1137

 

이걸 당장 돌려보고 와야겠다.

얼른 64iter를 넘어서도 돌아가주라.

안되면 잠깐 접어놓고 GPU는 다른 실험에 써야겠다.

 

-

 

[02.09 점심시간]

방금 2의 방법으로 성공했다!

 

아래와 같은 방법으로 예외처리하여 해결하였다.

 

예외처리 과정

  1. try catch문으로 비디오 읽어오는 것 감싸기
  2. clip length가 0이거나 읽어오는것 실패시, raise로 에러 뱉고 None을 리턴
  3. train loader의 collate_fn을 재정의해서 넣어주기. 여기서 None을 제외하는 처리를 한다.
    그러면 데이터로더가 getitem()으로 비디오를 읽어올 때, None 데이터는 빼고 알아서 가공해서 넣어줄테니 문제 없다.

 

validation loader에 뭔가 안한것같은 기분인데.. 일단 1에폭 돌면서 로스가 제대로 수렴하는지부터 확인해야겠다.

아무튼 (자잘한 버그들을 수정하면) Kinetics700으로 내 모델에 대해 pretraining이 가능할 것이다.

성능이 얼마나 나올지는 궁금하다.

 

 

끝!

반응형

댓글