Visual studio code, pipenv를 활용한 강화학습 개발 환경 꾸리기(6)

Debugging

이번 포스트에서는 CartPole 예제에서 VS code와 Jypyter Notebook을 이용해 디버깅하는 방법에 관해 설명하겠습니다.

사실 이런 내용은 강화학습에 국한된 이야기가 아닙니다. 일반적인 Python 개발에 있어, 아니 Python 뿐만 아니라 거의 모든 프로그래밍에 적용할 수 있는 방법이고 그렇게 하는 것이 거의 필수라 생각합니다.

그래서 이 포스트를 따로 떼 놓아 새로운 제목을 달지 고민해 보았습니다만 그냥 이 시리즈에 두기로 했습니다.

vscode_debug

VS code에서 지원하는 여러 Debugger extensions

출처


Breakpoint

기초적인 breakpoint 걸기 연습을 해보겠습니다. PyTorch CartPole 예제에서 복사하셨다면 6번째 셀의 아래 그림과 같은 라인에 라인넘버 앞에 빨간점을 클릭해서 찍어봅시다. 혹은 라인을 클릭하고 F9 단축키를 통해서도 찍을 수 있습니다.

breakpoint

Breakpoint 설정과 위쪽 모든 셀 실행


그 다음 오른쪽 위 셀 메뉴 아이콘에서 위쪽 셀 모두 실행 (Execute Above Cells)를 눌러서 해당 셀 위에 있는 모든 셀을 실행하겠습니다. 다음으로 누를 버튼은 디버그 버튼입니다. 디버그 버튼은 오른쪽에 셀 실행 버튼 옆에 숨어있습니다. 네번째 포스트를 참고해주세요.

코드가 실행되다가 우리가 설정한 breakpoint에 딱 걸려있는게 보이시나요?

breakpoint_works

Breakpoint 동작 모습과 디버깅 콘솔


이렇게 스크립트가 도는 중간에 잠시 멈춤 상태에서 현재 저장된 변수들의 값을 보려면 어떻게 해야 할까요? 데이터 뷰어를 사용하면 되겠죠? 기억이 나지 않는다면 본 시리즈의 네번째 포스트를 또 참고해주세요.

코드를 한줄 진행시켜 볼까요? F10 키를 눌러주세요. 이 단축키가 수행하는 동작의 이름을 Step Over라고 합니다. 해당 줄의 코드를 실행하고 다음 줄로 넘어간 것을 볼 수 있습니다. 방금 실행한 코드를 보면 select_action() 함수가 state를 인수로 받아 실행하고 return된 값을 action 이라는 변수에 담는 동작을 했네요.

Jupyter Notebook 탭에서 action 이라는 변수 행이 새로 생긴 것을 감지하셨나요?

action_var

11번 줄 실행 직후에 새로 생긴 action 변수


혹시 못 보셨으면 다시 한번 위 과정을 반복해 보겠습니다. 변수가 생기는 것을 봤어도 다시 해보겠습니다. kernel restart 개념을 알아야 하거든요!

Restart

사실 대단한 개념은 아닙니다. 지금 실행중인 스크립트를 중지해보겠습니다. 위쪽 메뉴에서 Interrupt를 누르셔도 좋고 Stop Cell Execution을 누르셔도 좋습니다.

interrupt

여러 중단 버튼들


버튼을 누르면 실행 중이던 스크립트는 멈췄지만 여전히 밑에 variables 영역에 데이터가 남아있는 것을 볼 수 있습니다. 이 상황에서 다시 스크립트를 실행시키면 어떻게 될까요? 당연히 남아있던 변수들이 새로 실행하는 스크립트에 그대로 적용되어 영향을 주겠죠? 물론 스크립트에 초기화 코드가 잘 작성되어 있으면 큰 무리가 없을 수도 있습니다. 그렇지 않다면 이 변수들을 깔끔하게 삭제하는 것이 가장 간단한 방법일 것입니다.

눈치채셨겠지만 변수를 깔끔하게 삭제하는 방법은 Interrupt 버튼 옆에 Restart 버튼을 누르는 것입니다. 이 버튼은 파이썬 인터프리터를 다시 실행하는 버튼입니다. 눌러보시면 변수들이 깔끔히 삭제된 것을 볼 수 있습니다.

그 옆에 Clear All Outputs 버튼은 무엇일까요? 이 버튼은 각 코드 셀의 출력 메시지를 깔끔히 지워주는 버튼입니다. 한번 눌러보시면 무슨 버튼인지 바로 알 수 있습니다.

이렇게 커널에 담긴 변수와 출력 메시지를 완전히 지우는 법을 알아봤습니다. 다시 한번 중단점 부분까지 스크립트를 실행해보시고 action 변수가 새로 생기는 것을 확인해 보면 좋겠습니다.

다른 셀의 Breakepoint

중단점 동작은 지금 디버깅하는 셀에서만 국한된 것이 아닙니다. 아래의 그림과 같이 random() 함수, 35 라인에 중단점을 걸어두고 해당 셀 위쪽 모두 실행 후 해당 셀을 디버깅 모드로 실행해 보겠습니다.

other breakpoint

함수 내부의 중단점


아무런 중단 없이 스크립트가 수행되었다면 정상입니다. 아시다시피 이 부분은 select_action 함수를 정의하는 부분으로 지금은 이 함수가 실행되지 않았습니다. 단순히 커널에 "이런 함수가 있어"라고 알려주는 것 뿐이죠.

그러면 언제 중단점이 동작할까요? 당연히 함수 콜이 있을 때겠죠? 함수를 콜하는 부분은 위 문단에서 언급한 부분(select_action 함수가 있는 11번째 줄)입니다. 아래 그림처럼 11번째 줄, select_action 함수 앞의 중단점을 지우고 셀을 디버그 모드로 실행해 볼까요?

other breakpoint2

중단점 지우고 다시 실행


결과는 어떤가요? 위위 그림의 random() 함수를 실행하는 부분에 코드가 딱 걸렸나요? 해당 셀이 아니어도 실제 실행되는 지점에서 중단이 되는 것을 확인하실 수 있습니다. 이처럼 코드를 중단시키는 기능은 같은 파일안에서만 가능한 것이 아니라는 것을 꼭 기억해 주세요. 즉, 중단점이 다른 파일에 있어도, 다른 패키지에 있어도 모두 잘 동작합니다.

Conditional Breakpoint

중단점을 사용할 때, 매 라인 수행 때 마다 중단점이 적중합니다. 하지만 어떤 조건에 따라 중단점이 활성화되게 설정한다면 디버깅이 훨씬 수월해 질 수 있습니다. 예를 들어 우리의 코드가 에피소드 49번째에 꼭 오류가 난다고 가정을 해 봅시다. 루프문 안에 중단점을 걸고 F5키를 49번 누를 수도 있지만 만번, 십만번이라면 힘들겠죠. 또한 다른 복잡한 상황에서만 중단시키고 싶을 수도 있습니다.

그럼 예제를 따라해보겠습니다. 아래 그림처럼 6번째 cell의 9라인 앞에서 마우스 우클릭을 해볼까요?

conditional breakpoint

Conditional Breakpoint


그럼 해당 라인 밑에 조건을 적을 수 있는 박스가 나오고 세가지 메뉴, Expression, Hit Count, Log Message 중 선택을 할 수 있습니다. Expression을 선택하고 박스에는 아래 조건을 넣고 엔터를 눌러주세요.

i_episode == 49

그리고 해당 셀 위 전부실행 -> 해당 셀 디버그 실행을 해주세요. 당연히 for 문의 49번째 iteration에서만 해당 중단점이 동작하는 것을 확인할 수 있습니다.

자 그럼 이제 Hit CountLog Message는 무엇인지 감이 오시나요? Hit Count는 그 횟수 만큼 해당 라인을 지나 갔을 때 중단시키라는 뜻이겠죠? 따라서 이 칸에 49를 넣으면 이전과 완전히 동일한 동작을 합니다.

Log Message는 해당 라인을 지날 때 로그 메세지를 출력합니다. 이 칸에는 format 출력 형식 역시 지원합니다. 덕분에 우리는 변수의 내용 역시 로그 메세지로 확인할 수 있습니다. 아래 그림 처럼 i_episode : {i_episode} 문구를 넣고 실행해 보시고 디버그 콘솔의 출력 결과를 확인해 보세요.

log message

Log message formatting