https://github.com/levelstage/AutoMine
이번 포스트에서는 이 프로젝트를 질질 끌게 만든 제1 원인인 시각화에 대해 다루고자 한다. 사실 시각화 로직을 만드는 건 어려운 일이 아니다. 귀찮을 뿐이다. 어떻게 보여줄지를 정하고, 그 규칙대로 오브젝트를 배치하는 코드를 쭉 짜기만 하면 된다. 그런데 구현 난도와 별개로 시각화는 프로젝트에서 매우 큰 역할을 한다.
자동차가 앞으로 나아가기 위해서는 유리창이 필요 없다. 유리창이 없어도 액셀을 밟으면 자동차는 앞으로 간다. 그러나 사람이 자동차를 운전하기 위해서는 유리창이 필수이다. 앞이 보이지 않으면 어디로 가는지 알 수 없기 때문이다. 앞을 보지 못하는 자동차 같은 건, 있어도 아무런 필요가 없다.
요점은, 시각화는 자동차의 창문과 같은 역할을 한다는 것이다. 이 프로그램이 제대로 동작하는지 확인하는 행위는 로직상으로는 아무 의미가 없지만, 프로젝트를 진행하는 데에 있어서는 매우 중요한 역할을 한다.
내가 만약 이 글을 읽는 독자라면, "당연한 소리를 왜 하지?" 하고 생각할 것 같다. 그렇다. 당연한 소리이다. 자동차에는 당연히 창문을 달아야 한다. 그런데 내가 단 창문이 왜곡되어 있다면? 창문처럼 보이지만 실물과 전혀 다른 광경을 보여주는 전자 스크린이라면?
그렇다. 내가 처한 상황이 바로 그것이었다.
앞서 시각화를 만드는 것이 그닥 어려운 일은 아니라고 했다. 과거의 나 역시 똑같이 생각했고, "이 정도면 인공지능한테 대충 맡겨도 되지 않을까?" 하고 던졌다. 대충 실행해 보니 예쁘게 잘 나오는 것 같아 무시하고 내가 집중해야 할 것들에 집중했다. 그때 코드를 제대로 읽어보았어야 했는데.

이것이 내가 인공지능을 통해 생성한 중간 결과 확인기 MsTester의 초기 버전 작동 모습이다. 이 데이터를 보고 나는 절망했다. 인공지능이 문제를 전혀 맞추지 못하고, 적당히 높은 정도의 평균값만을 뱉어내고 있다고 생각했기 때문이다. 인공지능이 지뢰찾기를 푸는 게 아니라, 적당히 한 종류의 값만으로 출력값을 도배하는 행위를 하고 있다고, 사실상 문제풀이를 포기했다고 생각했다. 그래서 모델에 대한 쓸데없는 수정을 거듭했다. 신경망 층 수도 늘려보았고, 마스킹 및 확실한 칸에 대한 가중 학습을 추가했다.
솔직히, 우연히 저 "AI의 예측값"에 위치한 숫자들을 자세히 들여다보고 번뜩임을 얻지 않았다면 여기서 포기했을지도 모른다. 그렇다. 정답이 0에 가까운 숫자는 값이 0.5에 가까워지며, 0.5 아래의 값은 하나도 존재하지 않는다. 정답이 1에 가까운 값은 0.73 언저리로 수렴한다. 어? 이거 시그모이드 아닌가?
아니나 다를까. AI는 결과값이 시그모이드가 이미 씌워진 상태로 들어올 거라고 생각하지 못하고, 자기 마음대로 결과값에 한 번 더 시그모이드를 씌우는 코드를 작성해 버린 것이었다. 나는 "아 내 코드를 보여줬으니, 적당히 눈치껏 적절한 테스터를 만들어주겠지?" 하는 말도 안 되는 안일함을 저질러 버리고 만 것이다.
수정은 너무나도 간단했다.

코드 한 줄 지우는 게 다였다. 이 이후로 "아무리 간단한 작업이더라도, AI한테 맡길 때는 내가 그 코드를 꼭 읽어봐야지!" 하고 다짐했다. 정말 다시는 이런 바보 같은 실수를 되풀이하고 싶지 않다. 스스로가 너무나도 부끄러웠다.
MsTester는 어디까지나 개발 중 중간 결과 확인을 위해 만든 시각화 도구로, 신경망 학습이 완료된 후에는 이 도구가 아닌 새로운 시각화 도구(그림으로 모든 상황을 보여줄 수 있는)를 만들 필요가 있었다. 그리하여 내가 익숙한 C# 환경에서 간단한 그래픽 처리를 쉽게 할 수 있는 MonoGame을 활용해 가벼운 지뢰찾기 게임을 먼저 만들고, 게임판의 오른편에 해당 판에 대한 인공지능의 예측 결과를 색으로 알기 쉽게 볼 수 있는 AutoMineGame 클라이언트를 만들었다.

C#에서 파이썬을 연동하는 방법은, 우선 C# 내부에서 파이썬 프로세스를 열어놓고, 학습된 모델의 pkl 파일 주소를 불러온 후, app.py라는 별도로 만들어진 파이썬 코드를 실행시킨다. 해당 코드는 표준입출력으로 판의 상태를 받아 불러온 모델에서 순전파시키고, 나온 값을 마찬가지로 표준입출력으로 뱉어내는 간단한 구조를 가지고 있다.
C#의 Stream을 활용하면, 그 표준입출력을 프로그램 간의 간단한 대화 창구로 활용할 수 있는 것이다. 프로세스 간의 통신 내용이 '판의 상태', '판의 예측 확률 정보' 이 2가지밖에 없기 때문에, 따로 복잡한 기술을 사용할 것 없이 이렇게 간단히 구현했다.
이것으로 생각보다 꽤 힘들었던 AutoMine 프로젝트가 종료되었다. 사실 따지고 보면 그렇게 어려운 프로젝트가 아니었다. 간단한 CNN을 구현하는 것쯤은 책만 보고 따라 하면 얼추 할 수 있는 것이고, 시각화 도구 만드는 것도 그야말로 원래는 'AI 딸깍'으로 다 해결되는 것이니까. 정말 안일함 때문에 질질 끌려버린 프로젝트였다고 할 수 있다.
그런데 안일함 말고도, 이 프로젝트를 질질 끌게 만든 또 하나의 원인이 있다. 그것은 그래픽카드의 부재이다!
이 프로젝트는 100% 그래픽카드가 달려있지 않은 내 노트북만을 활용해서 진행했기 때문에, CuPy가 아닌 NumPy를 활용해야 했고, 어쩔 수 없이 학습 속도가 매우 오래 걸렸다. 일반적으로 학습을 5,000~10,000이터레이션(iteration) 정도 진행했는데, 1이터당 0.5초가 걸렸기 때문에 1만 이터를 돌릴 경우 5,000초, 즉 1시간 반 정도 걸렸다. 뭐 하나 수정할 때마다 1시간 반 동안 화면을 멍하니 보고 있어야 했다.
그런데, 내 노트북의 CPU에는 내장 그래픽이 있다. 그래서 "이걸 활용할 수는 없나?" 하고 이것저것 찾아봤는데 내가 찾은 한에서는 마땅한 수가 없었다.
그리고 그 순간 머리에 이런 생각이 스치게 된다.
"내가 만들어 볼까?"
그리하여 다음 포스트에서는 AutoMine에서의 모든 행렬 연산을 Intel CPU의 내장 그래픽 카드를 이용해 돌림으로써 처리 시간을 단축하고 모델의 개선을 더 쉽게 할 수 있도록 하는 것을 목표로 프로젝트를 확장해볼 계획이다. 이른바 AutoMine++ 프로젝트!
그러나 내가 이런 하드웨어/시스템 분야에서 완전히 문외한이기 때문에, 해당 프로젝트를 진행하기 위해서는 약간의 공부가 필요할 것 같다. 그리하여 AutoMine++는 바로 진행되지 않고, 그전에 간단한 이론 정리 등을 올려나갈 계획이다.
길고 재미없는 글을 끝까지 읽어주셔서 감사합니다.