아는 만큼 보인다

[프로젝트 회고] 시행착오 덩어리였던 우당탕탕 두번째 딥러닝 프로젝트 - 제가 주도한다구요? 본문

기록/회고 혹은 일기

[프로젝트 회고] 시행착오 덩어리였던 우당탕탕 두번째 딥러닝 프로젝트 - 제가 주도한다구요?

계토 2023. 10. 25. 19:09

네? 제가 할까요? 네! 제가 해볼게요!

 

역시 스타트업의 꽃말은 '인재 즉시 투입'이다. 비트 분류 프로젝트가 끝나고 슬슬 리듬 분류 프로젝트 POC를 하며 천천히 준비하던 시기였는데, 다른 쪽에서 일이 터져서 AI팀의 손이 부족한 상태였다. 또 한편으로, 뭔가 나도 주도적으로 하면 해볼 수 있을 것 같다는 아주 무모한 자신감이 생겼다! 그래서 자의 반 타의 반으로 내가 리듬 분류 모델 개발 프로젝트를 주도하게 되었다.

 

(사실 스스로 욕심을 낸 것도 있어서, 내가 나서서 데이터셋도 준비하고 학습도 미친듯이 돌리고 있으니까... 내가 주도적으로 할 수 있게 해주셨다. 그리고 배포 준비하는 것도 괜히 내가 하고 싶어서 내가 호다닥 해버렸다.)

 

이전 비트 분류 프로젝트가 하나의 심장 박동을 분류하는 모델을 개발해야하는 것이었다면, 이번 리듬 분류 프로젝트는, 그 비트들의 집단?을 분류하는 모델을 개발해야하는 프로젝트였다. 짧게는 1-2초부터 길게는 24시간까지 이어지는 리듬을 분류해내야 하는 프로젝트였다.

 

시행착오도 많았지만, 애당초 원했던 것처럼 아주 많은 성장을 할 수 있어서 좋았다. 데이터셋 구축부터 배포 준비까지 할 수 있었던 경험은.. 정말 귀하다!

 

네? 이 데이터도 아니라구요? 

하지만 오픈소스 데이터셋으로 진행한 POC 이후 거의 한 달 간 프로젝트가 제대로 시작되지 못했다....바로 데이터 때문이다 ㅜㅜ

 

사내 데이터 중에서도 실제 학습에 사용될 수 있는 데이터를 골라야 하는데, 이러한 정보가 메타데이터로 따로 저장되어 있지 않았다. 어떻게 해야 제대로 데이터셋을 만들 수 있는지 문서로 정리되어 있지도 않았고 그걸 알고 있는 회사 사람도 없었다. 지금까지 사내 데이터로 체계적으로 학습한 적이 별로 없었기 때문(이전 프로젝트에서는 소수의 ID로만 진행..)

 

그래서 처음에는 표면적으로 맞다고 간주되는(?) A + B 조건으로 쿼리하고 학습을 시작했다. 학습 후 모델 결과를 하나하나 뜯어보는데, 아무리 봐도 모델 Label이 너무 이상한 거였다. 그냥 이상한 데이터만 제외하면서 1-2주를 끌었다. 이상한 데이터가 나오면, 그 데이터 검사 날짜랑 메타데이터 뜯어보고, 그래도 이상하면 제외하고, 이걸 가내수공업 형식으로 하나하나 진행했다..ㅎㅎ 

 

그러다가 검사 날짜가 아무리 봐도 말도 안 되는 데이터를 발견해서 여기저기 수소문을 해보니... A + B + C 조건으로 검색을 해야하고, C를 알기 위해서는 별도의 테이블 2개를 Join해서 알아봐야 한다는 것이었다. 이 사실을 알아내는 것만 해도 너무 오래걸렸고, C를 알기 위해 필요한 그 테이블 2개도 내가 직접 사내 DB 뒤져가지고 찾아냈어야 했다. 중간에는 또 뭐 잘못 하고 ㅋㅋㅋ 어째저째 그렇게 해서 또 새로운 데이터셋을 얻었다...(^^) 근데 다 하고 보니 데이터가 너무 적은 것이다. 비즈니스팀에 이 정도가 있는게 맞냐고 했더니 또 말도 안된다고?!?!? 

 

심호흡을 했다. 마음을 가다듬자... 이미 3주를 날렸지만 아직 괜찮아... 아직..제대로 학습도 못했지만 괜찮아.. 다시 수소문을 해서 알아보니, A+C는 맞는데 B가 조금 잘못되어서 A+B'+C 조건으로 조회를 해서 데이터셋을 만들었어야 했다. B'가 진짜 어려운 부분이었는데, 예를 들어 각 검사마다 최종-최최종-최최최종 이라는 status가 있다고 하면, 당연히 최최최종으로 써야할 것 같지 않은가? 하지만 아니다. 최종부터 불러와야 한다는 것이다. 왜냐면 '최종'부터는 품질이 보장되었다는 걸 뜻하고, 그 이후 단계는 품질 관리 관련 단계를 떠나 사용자가 마음대로 하는 거라 알 수 없다나... 그래서 거의 4주가 다 되어가는 시점에 제대로 된 데이터셋을 구축할 수 있었다.

 

몇 번을 다시 쿼리하고, 데이터 다운로드하고, 전처리하고, 자르고 했는지 모르겠다. 무한반복! 아직 타임라인이 빠듯하진 않았지만 그래도 너무 속상하고 죄송했다.

 

그래도 의의가 있다면 덕분에 사내 데이터와 DB에 대해 더 잘 알게 되었고, 이번 나의 시행착오를 통해 다음 데이터셋 구축부터는 큰 혼란이 없을 것 같다는 점! 문서화도 꼼꼼히 해 놓아서 우리 팀의 자산이 될 것 같다(뿌듯!). 

 

학습이 잘 되는 데이터 비율(균형점)을 찾아라

심장 박동 분류 프로젝트에서도 그랬지만... 데이터 불균형의 문제는 리듬에서도 심각했다. 한 가지 비정상 리듬의 경우는 임상적 발생 빈도 자체가 다른 비정상 리듬보다 낮아서 학습이 잘 안되었던 것이다. 특히 혹시나 해서 noise rhythm도 함께 분류를 하는 식으로 모델을 구성했는데 noise rhythm이 비정상 리듬보다 많으니 자꾸 비정상 리듬을 noise로 보내는 불상사가 있었던 것. 모델은 똑같은데 비율만 바꿔도 성능이 꽤 많이 차이가 나서, 그 최적의 비율을 찾는 데에도 시간이 정말 많이 소요되었다(다 학습을 해봐야 알 수 있으니ㅜㅜ). 결국 normal & noise undersampling, 비정상 oversampling을 진행한 뒤에야 제대로 학습을 진행할 수 있었다. 

 

데이터 준비부터 비율 찾기까지 모델 자체 실험 외에도 결정해주어야 하는 것이 한두개가 아니었고 다음부터는 데이터 불균형 문제를 더 많이 신경쓰기로 다짐했다.

 

재현이 안돼! 

또 다른 난관 봉착 - 학습을 할 때마다 결과가 다르게 나온다.. 조건이 다 같은데 말이다. 모델이 불안정하다는 걸 뜻하는 것 같아서 온갖 시도를 다 해봤지만 쉽게 해결되지 않았다. 크기를 키웠다가 줄였다가 모델을 바꿨다가... 검색도 엄청 많이 해봤지만 결국은 random seed를 고정하고 실험하는 걸로 결정했다. 그러니 문제가 다 해결되었지만, 재현이 잘 되었던 모델도 있는데 왜 이건 안 될까...너무 자괴감 들었다ㅜ 그래도 seed 고정 후에는 최적화를 통해 원하는 성능까지 끌어올릴 수 있어서 다행이었다.

 

AI 최적화 = 일종의 반복 노동 그 자체..? (feat. 비즈니스 요구사항)

선행 논문을 읽고 이해하고 구현하고 테스트할 때까지는 두뇌를 꽤 미친듯이 사용해야 한다. 그러나 어느 정도 구조가 잡히고 최적화에 들어가게 되면 진정한 반복 노동, 무한 반복, 무한 루프 그 자체의 일만 남아있다. dilation 추가해봤다가, dropout 조절했다가 depth 늘렸다가 줄였다가, signal downsample 했다가 말았다가. 물론, 이 때에도 집중력과 꼼꼼함은 필요하다. 가끔 정신 놓고 실수로 같은 모델 돌려놓은 적도 있고, 멈추면 안되는 모델을 멈춘적도 있고....

 

조금 지루하긴 하지만 그래도 0.5% 씩이라도 지표가 상승하면 뿌듯하긴 하다! 특히, 팀원 중 한 분은, 모델 구조가 완전 바뀌지 않는 이상 일정 수치 이상의 정확도 상승은 어렵다는 견해를 갖고 있었는데, 나의 미친듯한 최적화로 기대 성능 이상을 달성했었다. 그래서 꽤 놀라셨던 것으로 기억한다 ㅎㅎ 

 

모델 자체만 최적화한다고 끝이 아니다. 후처리의 늪이 기다리고 있다. 후처리만 1달간 진행했다. 리듬의 특성상 XX 조건에서는 나올 수 없다거나, XX초 이하일 수 없다거나, RR 리듬 옆에는 나올 수 없다거나 등등 여러 조건이 있었다. 예를 들어 A 리듬 + 1초 normal + A 리듬이 등장하면 1초 normal을 A로 바꾼다거나, A+B+A+B가 반복되는 것은 임상적으로 불가하다는 자문을 얻어 하나로 통일하는 로직을 짠다든지... 물론 이 조건은 모델 추론 결과를 그냥 하나씩 보면서 이상한 부분이 있으면 물어보는 방식으로 알아냈다. 정말 많이 괴롭혔다 내가.. 매일매일 이미지 들고가서 이거 맞냐고 물어보고 ㅋㅋ 제가 편집할 땐 이랬는데...하면서 물어보고. 후처리 추가해서 metric 내리고, 없애서 metric 내리고, 순서도 모든 가능한 순서를 다 해서 실험해보고, 논리적으로 말이 되면서 성능 향상되는 순서로 고르고..... 한 달 간 결과와 씨름하며 6개의 후처리 로직을 만들어 냈다. 이 부분도 CTO와 공유드리니 정말 많이 대비했다고 칭찬 아닌 칭찬(?)을 해주셨다.  이 때에도 반복 작업의 연속이었다. 

 

특히, 심장 박동 분류와 달리 리듬 분류는 'precision'이 정말 중요했다. 비정상 리듬이 신호에 부여가 되면, 비정상 리듬 내의 심작 박동 label들이 그 리듬에 맞게 모두 변하게 된다. 그리고 이 변화는 비가역적이다. 그래서 실제 판독사 분들은 비정상 리듬을 잡는 건 정말 중요하지만 precision이 낮으면 힘들다고 하셨다. 판독을 보조하면서도 느낀 부분ㅜㅜ 참 어려웠던 것은, 사실 의학적으로 보면 recall이 높은게 좋긴 한데, 우리 웹 ui 시스템의 한계(?)로 그렇게하지 못한다는 것이었다. 편집 시스템만 바뀐다면, 즉 거짓 양성 리듬을 지웠을 때 원래 심장 박동 label이 부활만 해주면 문제가 아닌 문제인데, 현재 DB 시스템 및 우선순위의 이슈로 이게 밀려나서 AI도 거기에 맞춰야 하는 것이다. 그래서 모델 개발에서도 신경 쓴 부분이지만 후처리 과정에서는 precision을 정말 많이 잡으려고 노력했었다.

 

그러나 또(!) 어려웠던 점은, 리듬을 판독하는 기준이 꽤 "맥락 의존적"이기도 하고, 짧은 부분을 봐서는 판단이 잘 안 되는 경우가 많다는 점이었다. 예를 들어, 어떤 형태의 리듬이 A 환자에서는 비정상 리듬이었는데 B 환자에서는 아니었던 것..? 리듬은 이런 경우가 많지는 않지만 그래도 몇몇 경우가 있어서 환자마다 성능 편차가 조금 컸다. Best는 여러 개의 모델을 돌려서 환자에게 적합하게 작동하게 하는 건데,, 아직 거기까지는 갈 길이 멀다.

 

또 다른 어려운 예를 보자. 비정상리듬이 1시간 지속되는 경우가 있다. 딥러닝 모델은 특성상 고정된 길이의 input을 받아야 한다. 그리고 길이가 또 너무 길어지면(데이터가 너무 커지면) 모델이 너무 무거워지기도 한다. 그래서 보통 우리는 3초, 10초, 30초, 1분 등의 길이를 이용한다. 그러나 1시간 지속되는 비정상리듬의 가운데 1분을 딱 추출하면, 정상처럼 보이거나 판독이 어려운 경우가 있다. 리듬의 '시작'과 '끝'이 중요하기 때문이다. 이걸 봐야 판독을 잘 할 수 있는 것. 그래서 후처리를 할 때도 최대한 잘 '연결'시키고, 서로 참조할 수 있도록 하려고 노력했는데, 로직이 잘못 적용되었을 경우의 후폭풍이 심해서 아주 소극적으로 적용해야 했다. 아예 리듬의 '시작'과 '끝'만 잡는 모델도 실험을 해봤는데 데이터 양이 확 줄어버리는 바람에 학습이 잘 안되어서 우선은 이렇게 타협하게 되기도 했다.

 

어쨌거나 저쨌거나 최적화와 후처리 개발 과정 역시 첫번째 딥러닝 프로젝트 때처럼 "비즈니스 요구사항을 잘 맞추는 모델이 좋다"는 걸 증명해준 과정이었다. 

아니 나 코딩 좋아하네~! 

모델 개발+후처리 로직 개발까지 다~~~하고, 속도 최적화, 코드 정리, 모듈화, 서비스 코드로 사용될 수 있도록 리팩토링 등의 일만 남았었다. 그냥 내가 해보고 싶어서 내가 해버리고 매니징하는 분께 평가와 검토와 정정을 요청했다. 지금 생각해보면 조금 되바라졌을지도.. 그래도 내가 만든 모델에 내가 만든 로직이라 내가 최적화하는게 좋지 않을까해서 진행한 일이었다. 실제로도 후처리 쪽을 계속 최적화하여 성능이 꽤 좋아졌다. 특히 그냥 마구잡이로 numpy만 쓰는게 아니라, 생각을 많이 해야 했다. array를 너무 크게 쌓으면 아무리 numpy를 써도 memory를 너무 많이 잡아먹으면서 느려지고, 아무리 multiprocessing을 써도 같은 데이터를 여러 번 읽게 되면 비효율이 발생하고.. 그래서 노트에 array도 많이 그려보고, 코드 실행 과정도 많이 그려보면서 최적화할 수 있는 껀덕지(?)를 찾아내는 과정을 반복했다. 그래서 array를 최소한으로 쌓고 버리는 과정을 반복한다거나, 꼭 필요한 과정만 포함시켜 성능을 많이 향상시킬 수 있었다. 꽤 재밌는걸?

 

그리고 모듈화 챡챡 하고 class 챡챡 말고 argument 챡챡 정리하고... 기능들을 분화시키고 합치고 등등을 하면서 앗 나 코딩 좋아하네~! 라고 생각했다. ㅋㅋㅋ 좀 웃기긴 하네.. 그래도 어떻게 코드 불러올지 어떤 걸 불러올지 이런걸 고민하는게 생각보다 재밌어서 신기했다. 

 

또 배포 환경에 대해서도 잘 알 수 있어서 좋았다. CPU 개수, Ram 등등! 특히 우리는 현재 서빙 환경이 매우 열악해서 속도와 메모리 모두를 정말 잘 챙겨줘야 하는데 이 부분을 최적화하면서도 많이 배웠다.

처음부터 끝까지 해보는 경험의 중요성

이제 끝을 생각하며 뭐든지 미리 준비할 수 있다. 예를 들어, metric 준비하기, 기존 모델 metric 미리 뽑아 놓기, 모델 추론 코드도 가능하면 서비스와 닮게 짜놓기 등등. 나중에 필요하면서도 미리 해놓으면 좋은 것들을 진짜 미리하기 시작한 것이다!  논문과 마찬가지로, 논문을 한번 출판해보고 나면 나중에 필요한 디테일들을 미리 챙겨놓는 것처럼. 그래서 처음부터 끝까지 끌고간 경험이 정말 중요하다고 하나 보다.

 

Documentation 까지 챙기면서 더더욱 그랬다. 이젠 기록도 더 열심히 하고 documentation에 들어갈만한 내용 있으면 초안도 써놓고.. 이번에는 준비 없이 문서화를 시작해서 다시 뽑은 지표와 그래프가 한두개가 아니었다. 몹시 비효율적이었다고 할 수 있지.

 

 

 

이번 회고는 좀 구구절절하고 의식의 흐름대로 적게 된 것 같다. 말할 게 너무 많아! 

 

- 더 많이 공부하자

- 정말 challenge했지만 동시에 정말 많이 배웠다. 앞으로도 challenge한 일이 많이 있었으면 좋겠다. 우리 회사 화이팅!

- 적극적으로 일을 얻어내자? 하고 싶은 일 있으면 하고 싶다고 하자. 

- 이태리 장인 같은 데이터 사이언티스트라면 자신 있다. 될 때까지 한다!