어느새 2021년이 끝나간다. 올해로 20대도 마지막이고 30대 시작인데, 마음은 아직도 20대인 것 같다.

 

올해는 취업이라는 목표 하나에 집중하느라 많은 것을 소홀히 하고 놓쳤던 것 같다. 특히 인간관계가 가장 큰 영향을 받은 것 같다.

물론 같이 공부하느라 더 가까워진 사람도 있지만 그렇지 않은 사람이 더 많은 것 같다. 그래도 취업이라는 목표 하나는 이루었으니 둘 다 잃지는 않아서 다행이라고 생각한다.

 

상반기까지만 해도 올해 취업할 수 있을까? 라는 걱정이 앞섰는데, 정말 운이 좋았다고 생각한다. 빨리 취업한 덕분에 개발이 더 재밌어졌고 더 열심히 할 수 있는 것 같다.

 

처음 회사에 들어가서도 내가 아직 부족하지 않을까, 잘 할 수 있을까 하는 고민이 있었지만 이 역시도 쓸데없는 고민이었던 것 같다. 물론 개발 능력적으로는 부족할 수 있지만 개발 능력이 전부가 아니란 것을 깨달은 것도 있고, 지난 달의 내가 못 했던 일을 이번 달의 내가 해내는 것을 보면 점점 성장하고 있다는 것도 느낄 수 있다.

 

회사에 들어가기 전에는 정말 열심히 공부했었는데, 입사하고 나서는 회사일을 핑계로 공부를 미뤄두었던 것 같다. 내년에는 CKA 자격증을 목표로 자기계발도 신경써야겠다.

 

 

'잡담' 카테고리의 다른 글

첫 입사, 수습기간, 정직원  (2) 2021.11.23
1년간의 회고  (0) 2021.05.20
개발자로서의 첫 이력서 작성기  (0) 2021.03.07

지금 다니고 있는 회사에 MLOps Engineer 포지션으로 8월 9일에 입사하여 11월 19일에 수습기간 평가를 마치고 정직원이 되었다.

 

비전공자로 프로그래밍을 배우기 시작하여 정확히 1년 3개월 만에 취업에 성공하였다.

목표였던 올해 안에 취업하기도 달성해서 너무 좋다.

 

사실 적어도 2년 정도는 생각하고 있었고, 가능하면 올해 취업하고 싶었기 때문에 열심히 한 결과와 운이 꽤 따라줬던 것 같다.

 

42서울을 통해서 CS 기초지식과 문제를 해결하는 방법, 동료들과 함께 하는 방법을 배웠고, 아이펠을 통하여 배운 머신러닝/딥러닝에 대한 지식을 내가 하고자 하는 직무에 잘 녹여냈던 것 같다.

 

처음 합격 연락을 받고 입사하기 전에는 아무것도 모르는 내가 과연 회사에서 개발자로 일을 할 수 있을까? 라는 고민을 많이 했었다. 다행히 회사도 역시 사람이 사는 곳이었다. 오히려 처음 회사 코드를 봤을 땐, 이런 코드로도 회사가 돌아가는 구나 라는 생각이 들 정도였다. 물론 나는 삽질과 팔짱끼고 모니터만 쳐다보고 있는 매일이지만, 지난 달의 내가 해내지 못한 걸 이번 달의 나는 해내고 있는 것을 보면 내가 성장하고 있다는 것을 느낄 수 있다.

 

3개월간 일하면서 느낀 것은 일단 이 일이 내 적성에 잘 맞는다는 것이고, IT 기업이라고 해서 꼭 코드로만 문제를 해결할 필요는 없다는 것을 느꼈고, 사람 간의 의사소통이 중요하다고 느꼈다.

 

실제로 내가 짠 코드는 그렇게 많지 않을 것이다. 오히려 코드를 짜는 시간보다 같이 일하는 사람과 대화하고, 문제를 해결하는 방법에 대해 논의하는 시간이 더 많았을 것이다. 영업직을 했던 경험 덕분인지, 내가 생각했을 땐 의사소통에 큰 문제는 없었다고 생각한다.

 

3개월의 수습기간을 마치고 받은 평가에서는 대체로 긍정적으로 평가를 받아 무사히 정직원이 될 수 있었다. 평가 중 기억나는 평가가 있는데, 나를 처음 봤을 땐 남들의 이야기를 듣기 보다 내 의견을 더 피력하는데 주력하는 사람이었으나, 지금은 그런 부분이 빠지고 있어서 점점 더 협업하기 좋은 사람이 되어가고 있다는 평가였다. 수습기간 동안 내가 잘 하고 있는 게 맞나 싶었는데 다행히 잘 하고 있었던 것 같다.

 

지금 일하고있는 회사는 분위기나 문화, 같이 일하는 사람들이 너무 좋아서 지금 상태가 계속 유지된다면 회사가 계속 성장하여 상장하는 날 까지 계속 일해보고 싶다. 솔직히 회사 아이템에 대한 부분은 아직 잘 모르겠지만, 모든 스타트업이 다 그런거겠지 생각하고 재미라도 있으니 아무렴 뭐 어때.

 

갑자기 생각나서 두서 없이 적은 글이라 나중에 보면 무슨 말을 써놨는지도 모를 것 같다.

 

잠이나 자야지

'잡담' 카테고리의 다른 글

2021년 회고  (0) 2022.01.01
1년간의 회고  (0) 2021.05.20
개발자로서의 첫 이력서 작성기  (0) 2021.03.07

docker-compose는 여러개의 container를 실행할 때 유용하다.

docker run simple-webapp
docker run mongodb
docker run redis:alpine
docker run ansible

다음과 같은 docker-compose.yml 파일 하나로 여러개의 container를 동시에 실행할 수 있다.

# docker-compose.yml

web:
    image: "simple-webapp"
database:
    image: "mongodb"
messaging:
    image: "redis:alpine"
orchestration:
    image: "ansible"

만약 투표앱을 만드는 상황을 가정해보자.

  • voting-app : python
  • in-memory DB : redis
  • worker : .NET
  • db : PostgreSQL
  • result-app : Node.js
docker run -d --name=redis redis
docker run -d --name=db
docker run -d --name=vote -p 5000:80 voting-app
docker run -d --nameresult -p 5001:80 result-app
docker run -d --name=worker worker

그러나 위와 같이 container를 실행했을 때, 제대로 동작하지 않는다. 그 이유는 컨테이너 간의 통신은 연결되어 있지 않기 때문이다. 이 문제는 --link flag를 이용하여 각 컨테이너를 연결해줄 수 있다.

docker run -d --name=redis redis
docker run -d --name=db postgres:9.4
docker run -d --name=vote -p 5000:80 --link redis:redis voting-app
docker run -d --nameresult -p 5001:80 --link db:db result-app
docker run -d --name=worker --link db:db --link redis:redis worker

이렇게 하면 다음과 같이 컨테이너가 연결되게 된다.

 

그러나 해당 앱을 실행할 때 마다 위의 명령어를 입력하는 것은 매우 번거롭고 비효율적인 작업이다. 따라서 해당 작업을 자동화 한다면 더 효율적인 업무가 가능할 것이다. 바로 이럴 때 docker-compose 를 사용하는 것이다.

위의 container들을 docker-compose로 다시 작성해보면 다음과 같다.

# docker-compose.yml

redis:
    image: redis
    
db:
    image: postgres:9.4

vote:
    image: voting-app
    ports:
      - 5000:80
    links:
      - redis

result:
    image: result-app
    ports:
      - 5001:80
    links:
      - db

worker:
    image: worker
    links:
      - redis
      - db

각 service를 정의하고, 해당 속성에 맞는 요소들을 작성해주기만 하면 된다.

해당 앱을 실행할 때는 docker-compose up 명령어를 이용한다.

만약 DockerHub의 이미지가 아닌 local의 Docker 이미지를 사용하고 싶다면 image: 대신 build: ./DockerfileDirectory를 사용한다.

# docker-compose.yml

redis:
    image: redis
    
db:
    image: postgres:9.4

vote:
    build: ./vote
    ports:
        - 5000:80
    links:
        - redis

result:
    build: ./result
    ports:
        - 5001:80
    links:
        - db

worker:
    build: ./worker
    links:
        - redis
        - db

docker-compose version에 대하여

위에서 작성한 내용은 docker-compose version 1의 문법이다.

docker-compose version 2의 문법은 docker-compose.yml파일의 최상단에 version을 명시해주고, 기존의 항목들이 services의 하위 블록으로 들어오게 된다. 또한 같은 services 내에 있는 service라면 link 옵션을 지정하지 않아도 하나의 네트워크로 묶이게 된다.

depends_on 옵션을 사용하여 dependency를 지정할 수 있다.

# docker-compose.yml
version: "2"
services:
    redis:
        image: redis
        
    db:
        image: postgres:9.4

    vote:
        build: ./vote
        ports:
            - 5000:80
        depends_on:
            - redis

    result:
        build: ./result
        ports:
            - 5001:80

    worker:
        build: ./worker

version 3에서는 더 많은 것들이 바뀌었으니 참고

 

설치 환경

  • Windows 10 WSL2
    • Ubuntu : 20.04 LTS
    • Docker : v20.10.6
    • kuberenetes, kubectl : v1.19.7
    • Kubeflow : kfctl_k8s_istio.v1.0.0.yaml
    • kfctl: v1.2.0-0-gbc038f9
  • 단일 사용자가 로컬에서만 사용할 경우 : kfctl_k8s_istio
  • 여러 사용자가 사용해야 해서 인증이 필요한 경우 : kfctl_istio_dex

(istio_dex를 사용할 경우) kubernetes 설정 수정

Kubeflow를 설치하기 위해서는 kubernetes API server를 수정해야 하는데, Docker Desktop을 통해 kubernetes를 사용하고 있기 때문에 설정 파일이 로컬에 존재하지 않는다.

$ cat /etc/kubernetes/manifests/kube-apiserver.yaml cat: /etc/kubernetes/manifests/kube-apiserver.yaml: No such file or directory

따라서, 다음 방법을 이용하여 API server를 수정한다.

# Edit kube-apiserver.yaml in docker-desktop 
# docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh 
# vi /var/lib/kubeadm/manifests/kube-apiserver.yaml 
# ADD FOLLLOWING: spec.containers.command 
# - --service-account-signing-key-file=/run/config/pki/sa.key 
# - --service-account-issuer=kubernetes.default.svc 
# - --feature-gates=TokenRequest=true # - --feature-gates=TokenRequestProjection=true

sa.key 파일이 해당 위치에 없을 수도 있으므로 /etc/kubernetes/pki/sa.key 등에 있다면 해당 경로에 맞춰 내용을 수정한다.

kfctl 설치

wget https://github.com/kubeflow/kfctl/releases/download/v1.2.0/kfctl_v1.2.0-0-gbc038f9_linux.tar.gz
tar -xvf kfctl_v1.2.0-0-gbc038f9_linux.tar.gz
sudo mv kfctl /usr/local/bin/
rm kfctl_v1.2.0-0-gbc038f9_linux.tar.gz
kfctl version # kfctl_v1.2.0-0-gbc038f9

/usr/local/bin 으로 이동시키지 않을 경우 $PATH에 추가해서 사용할 수도 있다.

Kubeflow 설치

  • k8s_istio
wget https://raw.githubusercontent.com/kubeflow/manifests/v1.2-branch/kfdef/kfctl_k8s_istio.v1.2.0.yaml
kfctl apply -f kfctl_k8s_istio.v1.0.0.yaml -V
  • istio_dex
wget https://raw.githubusercontent.com/kubeflow/manifests/v1.2-branch/kfdef/kfctl_istio_dex.v1.2.0.yaml
kfctl apply -f kfctl_istio_dex.v1.0.0.yaml -V

설치가 완료되면 port-forward 명령어로 8080 포트로 접속할 수 있도록 한다.

kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80

istio_dex의 경우 초기 아이디와 비밀번호는 admin@kubeflow.org / 12341234 이다.

유저를 추가하는 방법에 대해서는 document에 나와있다.

localhost:8080 으로 접속하여 Kubeflow Dashboard에 접속했다면 설치 완료!

설치 완료 후 초기 컨테이너 image를 pull하고 running 하느라 시간이 다소 걸릴 수 있다. kubectl get pods -n kubeflow 에서 모든 pod 가 생성되어 동작할 때 까지 기다린다.

  • 1.2.0 버전에서 pipeline으로 바로 전송 시 에러가 발생하여 1.0.0 버전을 설치하였다.

프로그래밍을 배우기 시작한지 딱 1년 정도가 지났다. 42Ecole의 교육 과정이 한국에서도 진행된다고 했을 때, 지금이 기회라고 생각했다. 더 늦기 전에 내가 하고 싶은 것을 하기 위해 잘 다니고 있던 회사도 고민없이 그만두었다. 피신 시작 기간에 맞춰 퇴사했지만, 코로나로 인해 계속 지연되는 피신으로 인해 살짝 불안하기도 했었다. 다행히 5월에 피신을 시작할 수 있었고, 한 달 간 코딩, 밥, 잠 이외에는 아무것도 하지 않았다고 해도 과언이 아닐 정도로 몰두했다. 그 결과 본 과정에 합격했고, 여러 과제를 진행하면서 현재까지 무사히 잘 살아남아 있다.

 

피신을 시작하기 전엔 피신을 하면서 다 배울 수 있다고 해서 정말 아무런 준비도 하지 않고 피신을 시작했다. 첫 날에는 C언어가 아닌 난생 처음 만져보는 shell에 당황했다. 처음엔 이런 것도 배워야 하나? 라고 생각했는데, 지금 돌아보면 정말 좋은 출발이었던 것 같다. 오히려 지금은 GUI보다 CLI가 더 편할 때도 있다.

 

두 개의 shell 과제가 끝나고 C언어 과제를 시작했을 때 역시 당황하지 않을 수 없었다. "라이브러리를 쓸 수 없다고...? 그럼 어떻게 하라는 거지? 출력하는 다른 방법이 있나?" 한참을 고민하고 있을 때, 슬랙에 글 하나가 올라왔다. "Linux에서는 fd라는게 있는데 ~~~" 아직도 잊혀지지 않는 그 글의 내용은 바로 file descripter에 관한 내용이었다. 아마 이것이 내가 피신에서 배운 가장 첫 컴퓨터공학 지식이었던 것 같다. 그 이후로도 슬랙엔 피신을 진행하면서 많은 정보들이 공유되었고, 모르는 것은 직접 그 글을 올린 사람을 찾아가 물어보기도 했다.

 

피신에서 가장 좋았던 점은 지식과 정보의 나눔이었던 것 같다. 내가 모르는 건 가서 물어보고, 내가 아는 건 알려주는 그 과정이 너무 좋았다. 누군가 과제에 대해 고민하고 있으면 그 과제를 해결한 사람들이 너 나 할 것 없이 몰려가서 알려주곤 했다. 같은 과제를 하고 있는 사람이면 지금 어떤 방법으로 문제를 해결하고 있는지, 어떤 방법이 더 좋은지, 그런 얘기들을 나누며 과제를 하나씩 해결해나갔다. 뿐만 아니라, 내가 아직 진행하지 않은 과제에 대한 설명을 듣기 위해 일부러 평가를 진행한 경우도 자주 있었다.

 

개인적인 감상으로는 1기 2차 피시너들간의 교류가 유독 활발했던 것 같다. 처음에 피신 예정 인원은 150명 정도였는데, 코로나로 인하여 3개월 정도 연기되면서 약 90명 정도 밖에 남지 않았고, 따라서 거의 모든 피시너들을 알 수 있을 정도였다. 그래서 남은 소수 인원들 끼리 같이 잘해보자라는 마인드로 피신에 임해서 그랬던 것 같다.

 

올해 초 까지만 해도 본과정을 진행하면서 과연 내가 제대로 학습하고 있는 게 맞는지 의문이 들 때가 많았다. 그러나 최근에 기초를 공부하기 위해 운영체제 강의를 보면서 지금까지 학습했던 개념들이 나오는 것을 보고 잘 하고 있다는 확신이 들었다. 과제를 진행하면서 겪었던 각종 에러나 문제를 해결하는 과정들이 전부 컴퓨터공학 지식을 학습하는 과정이었던 것이다. 이 사실을 깨닫고 나서는 동료평가를 하는 기준이 조금 달라진 것 같다.

 

이전까지의 동료평가는 단순히 코드를 얼마나 깔끔하게 짰는지, 과제에서 요구하는 사항을 잘 만족하는지 그저 주어진 조건에만 맞게 평가를 진행했었다. 그러나 과제를 해결하는 과정이 컴퓨터공학 지식을 학습하는 과정이라는 것을 깨달은 이후에는 그 과제가 어떤 지식을 학습시키길 원하는가를 생각해보게 되었다. 예를 들어, 가장 첫 과제인 libft같은 경우는 그냥 단순히 라이브러리 함수를 똑같이 구현하는 과제가 아닌, 함수의 동작이 어떻게 이루어지고, 각각의 파라미터나 변수들이 어떤 것을 의미하는지 생각해보는 과제라고 생각한다.

 

예를 들어, libft를 평가하면서 항상 하는 질문은 다음과 같다.

#include <stdio.h>

int main()
{
        char *str;
        str = NULL;
        printf("%d\n", ft_strlen(str));

        return 0;
}

테스터를 모두 통과해도 위 코드를 실행시키면 segfault가 발생한다. 평가를 받는 사람은 자신의 코드가 잘못된 줄 알고 당황한다. 그러나 코드는 잘못되지 않았다. 그렇다면 왜 segfault가 발생하는가? 답은 간단하다. 라이브러리의 strlen 함수도 마찬가지로 segfault가 발생한다. 그냥 strlen 함수가 그렇게 만들어져있기 때문이다. str이 null pointer일 때의 동작이 정의되지 않아서 에러가 발생하는 것이다. 지금까지 평가를 진행하면서 이 질문에 대한 답변을 제대로 들은 경우는 많지 않았다. 아마 직접 테스트 해보지 않고 단순히 테스터에만 의존하여 과제를 해결했기 때문이 아닐까 생각한다.

 

테스터가 나쁘다는 것은 아니다. 테스터가 많은 부분에서 이점을 주는 것은 사실이다. 30분이라는 제한된 시간 동안 모든 함수가 제대로 동작하는지 각종 테스트케이스를 넣어보면서 확인하긴 어렵기 때문에 테스터를 사용하는 것은 큰 도움이 된다. 다만 테스터는 어디까지나 보조수단일 뿐, 테스터를 너무 맹신하지 않는 것이 좋다고 생각한다. 물론 나도 과거에는 이러한 부분들을 반성하고 있고, 지금은 테스터 결과와는 상관 없이 그 사람이 얼마나 과제를 이해하고 코드를 짰는지를 더 중점적으로 보는 편이다.

 

이전에 코로나로 클러스터 출입이 불가하여 원격으로 ft_server 과제를 평가했던 적이 있었다. 평가를 받으시는 분은 VNC 화면을 공유하여 평가를 진행하는데, 자꾸 알트탭으로 창을 전환하며 어딘가에 적혀진 글을 보면서 명령어를 입력하는 것이 보였다. 그래서 피평가자분께 혹시라도 치팅으로 의심될 수 있으니 해당 글을 보지 말고 평가를 진행하기를 요청드렸더니 명령어를 모른다고 하셔서 더 이상 평가를 진행할 수 없었다. 과제 평가 기준에는 부합하지만 내가 생각했을 때 Docker 명령어도 모르고 이 과제를 했다는 것은 이 과제에서 요구하는 바를 만족시키지 못했다고 설명하고 Fail을 드렸다. 평가가 끝난 후 피평가자분께서 "안일한 생각으로 과제 평가표만 넘길 정도로 과제를 진행한 자신에 대해 부끄럽고 죄송하다"며 DM을 보내주셨고, 잘 마무리 되었다.

 

그러나 항상 이렇게 훈훈한 평가만 있는 것은 아니었다. 과제 관련 내용을 질문했을 때, 해당 내용은 평가 기준이랑 관계 없지 않냐며 짜증을 내는 사람 뿐만 아니라 평가 기준을 다 만족하는데 왜 Fail 이냐고 따지는 피평가자도 일부 있었다. 해당 내용이 왜 평가랑 관계가 있는지, 설명한 후에 Fail을 드리고 나면 평가 Feedback 점수를 0점으로 테러하는 사람도 있었다. 어쩌면 다른 사람에게 평가받았으면 통과했을지도 모르지만, 과제가 요구하는 바를 깨달아버린 이상 어쩔 수 없다. 운도 실력이라고 하지 않았는가!

 

회고를 쓰다 보니 잠시 딴 길로 샜는데, 어쨌든 요점은 평가를 통과하기 위한 과제 진행은 지양하는 것이 바람직하다고 생각한다. 1년쯤 하고 보니 42는 정말 내가 노력하는 만큼 알게되고, 내가 아는 만큼 더 많은 것을 보고 배울 수 있다는 것을 느꼈다. 지금도 평가를 진행하면서 이미 지나온 과제이지만 나도 간과하고 넘어간 부분이 있기 때문에 끝난 과제에서도 배우는 것이 많다.

 

김수보 멘토님의 글에서도 알 수 있다시피, 우리는 기술을 배우는 것이 아니다. 문제를 풀고, 협력하고, 스스로를 가르치고, 창의적이고, 비판적 사고를 가지기 위한 수용능력을 개발하는 것이다. 그러기 위해서 라이브러리를 쓰지 않고 과제를 해결하는 것이고, 과제를 해결하는 것이 곧 문제를 해결하는 능력을 기르는 것이라고 생각한다. 나는 이 사실을 깨닫는데 1년이라는 시간이 걸린 것 같다.

 

1년이라는 시간이 생각보다 빠른 것 같다. 1년동안 그토록 좋아하던 게임을 거의 안했는데, 코딩하고 공부하는 게 재밌었기 때문에 게임 생각이 안 나서 참 다행인 것 같다. 최근에는 AIFFEL이라는 인공지능 관련 교육을 듣느라 42과정을 잠시 미뤄두긴 했지만, 다시 42과정에 집중할 생각이다. 이제 사람도 많아진 만큼 나보다 과제를 앞선 사람들도 많아졌으므로 내가 부족한 부분을 잘 찝어줄 것이라 생각한다. 얼른 cpp 해야지.

'잡담' 카테고리의 다른 글

2021년 회고  (0) 2022.01.01
첫 입사, 수습기간, 정직원  (2) 2021.11.23
개발자로서의 첫 이력서 작성기  (0) 2021.03.07

모델을 서빙하기에 앞서 무료로 쓸 수 있는 클라우드 서비스 중에 딥러닝 모델을 서빙할 만한 곳을 찾고 있었다.

딥러닝 모델이니 당연히 GPU 자원을 쓸 수 있는 곳이어야 한다고 생각했는데, 잘 생각해보니 그럴 필요가 없었다.

왜냐하면 딥러닝 모델에서 GPU를 사용하는 이유는 분산처리인데 내가 만들 서비스는 이미지 하나를 입력으로 받으므로 굳이 GPU를 사용할 필요가 없었던 것이다.

 

Deep Learning Inference & Serving Architecture 를 위한 실험 및 고찰 1 - GPU vs CPU 이라는 글에서 이미 어느 정도 정답을 유추할 수 있었다.

그러나 해당 글이 2017년도에 작성된 글이고, 그래도 얼마나 차이가 있을까 궁금하여 일단 실험을 진행해보았다.

실험 사양의 CPU는 AMD Ryzen 7 3700X 8core, GPU는 RTX 3070 8GB이다.

실험은 Jupyter notebook에서 %time, %timeit Magic Command를 이용하여 진행하였다.

 

실험 결과를 정리해서 나타내면 다음과 같다.

  CPU GPU
첫 Inference 343ms 6.7s
첫 Inference 이후 327 ms ± 3.2 ms 24.8 ms ± 644 µs

실험 결과는 GPU를 이용했을 때가 당연히 빨랐다. 다만, GPU는 첫 Inference에서 CUDA 라이브러리를 불러오는데 시간이 걸려서 약 6.7초 정도가 걸렸다.

CPU를 이용한 Inference도 트래픽이 많지 않다면 충분히 사용 가능하다고 생각했다. 물론 초당 수백, 수천장의 이미지가 들어온다면 문제가 발생하겠지만 지금은 서빙하는 것이 목적이기 때문에 크게 상관 없을 것 같다.

 

결과만 보면 위 글에서 도출한 결론과는 상반대는 결론이 나왔다. 이 부분은 아무래도 그 동안 CPU 성능의 발전에 비해 GPU 성능의 비약적인 발전때문이 아닌가 싶다. 4년 전에는 서버용 CPU와 K80의 부동소수점 연산 속도(FLOPS)가 비슷했을지 모르지만, 당장 수치적인 부분만 놓고 봐도 K80의 FP32(float) TFLOPS는 4.113 * 2인 8.226 TFLOPS이고, RTX 3070의 FP32(float) TFlops는 20.31 TFLOPS로 2.5배 정도 차이가 난다. 실제로 결과도 K80 Single Inference에서 50ms 정도인 것과 RTX 3070의 Inference Time을 비교해보면 2배 이상 차이가 난다. 또한, 3700X CPU의 FLOPS는 1,689.6 GFLOPS로 약 1.7TFLOP로 볼 수 있으니 RTX 3070의 Inference Time과 TFLOPS를 비교하면 얼추 일치한다고 볼 수 있다. 즉, 4년 전에 사용한 Azure VM의 CPU TFLOPS는 약 8~9 정도로 K80와 비슷한 성능이었기 때문에 이번에 진행한 실험과는 다른 결과가 나타났다고 생각할 수 있다.

 

항상 딥러닝 모델을 서빙하려면 좋은 컴퓨팅 자원이 있어야 한다고 생각했다. 물론 회사에서 서비스할 때에는 그렇겠지만, 개인이 테스트나 프로젝트를 목적으로 한다면 굳이 GPU를 사용하지 않고 CPU만으로도 충분한 Inference가 가능하다고 생각한다.

4월 15일부터 train을 시작하여 1epoch당 30분씩 약 300epoch을 돌리는데 무려 150시간이나 걸렸다.

자는 시간 제외하고는 항상 컴퓨터를 쓰고 있으니 자는 시간에만 틈틈이 학습시켜야 했는데, 메모리 부족으로 한 번에 10epoch이상 돌릴 수가 없었다.

원래 데이터셋도 6만장을 사용해야 했는데 1.6만장으로도 이 정도 걸린 걸 생각하면 더 많은 시간이 걸렸을 것이다.

150시간의 결과물

300epoch 훈련시킨 결과는 생각보다 만족스럽지 못하다. auto-painter 논문에서는 되게 잘 되는 것 처럼 보였으나, 실제로 해당 논문을 구현해놓은 github을 clone해서 직접 실행시켜보니 여전히 실제로 사용하기엔 한참 모자라보였다.

다만 해당 논문에서는 훈련시킬 때 두 가지 모델을 만들었는데, 하나는 픽셀 정보를 일부 남겨놓고 학습을 시켜서 추후 inference할 때 input image에 일부 색을 칠해주면 해당 부분은 그 색으로 칠해지는 경향이 있었다. 아마 이 부분에 있어서 모델을 더 개선시킨다면 재미를 줄 수 있는 정도의 서비스는 되지 않을까 생각한다.

그래도 완전 못 봐줄 정도의 성능은 아닌 것 같다. 일부 색을 비슷하게 추론하는 경우도 생각보다 많이 보였고, 신기한 것은 완전히 대비되는 색상을 가진 그림들이 많이 보였다. 무엇보다도 캐릭터의 피부색 만큼은 잘 찾아서 적절한 색으로 칠해지는 것을 확인할 수 있었다. 이 부분은 아무래도 VGG를 이용하여 feature를 추출한 것과, 일정한 형태(사람 모양)을 한 캐릭터들이기 때문에 가능했던 것 같다.

우선 이 모델을 이용해서 서빙해볼 생각이다. 지난번엔 Ainize를 이용하였지만, 이번엔 가능하면 아마존 클라우드를 이용해볼 생각이다. 아무래도 딥러닝 모델이다보니 inference에 컴퓨팅 파워가 필요해서 무료 플랜으로 가능한지는 모르겠지만 시도는 해봐야겠다.

더 많은 시도를 통해 모델을 개선해보고, 파라미터를 수정해보고 하고 싶은데 개인 컴퓨터로는 아무래도 한계가 있는 것 같다. 그래도 취미로 하기엔 재밌어서 다행이다. 추후 여유가 된다면 더 좋은 GPU로 실험해보고 싶다.

 

첫 순위권!

  이번에 아이펠 진행하면서 EDA 하는 방법에 대해 조금 더 자세히 학습했었기 때문에 배운 걸 이용해서 데이콘에 참가해봤다. 처음엔 아무런 EDA도 하지 않은 데이터에 RandomForest로 baseline을 잡고 시작했는데 생각보다 좋은 점수가 나온 것 같다. 모델은 당연히 lightgbm을 사용하였고 이전에 kaggle 대회 연습하면서 optuna를 사용해봤기 때문에 이번에도 역시 optuna를 이용하여 hyperparamter를 tuning 하였다.

  점수를 더 올리려면 train data가 더 필요하거나 feature engineering을 해야 할 것 같은데 아직 많이 경험해보질 않아서 어떤 feature를 조합해야 할지 감이 잘 오질 않는다. 시간이 될 때 kaggle의 다른 비슷한 대회를 참고해서 힌트를 얻어봐야 겠다.

  아직 대회 초반이라 분명히 아래로 밀릴 순위겠지만... 그래도 주말 내내 대회에 매달려서 10등 안에 들어본 것으로 만족한다. 이 기세를 유지해서 상금도 탈 수 있었으면 좋겠다 ㅎ

이전부터 뭐든 실제로 서비스를 만들어보는 것이 중요하다고 생각하여 아이펠을 진행하면서도 틈틈이 프로젝트를 위한 공부를 따로 하고 있었다. 그러다가 GAN을 이용한 노드를 진행했을 때, '이거다!' 하고 느낌이 와서 실제로 프로젝트로 진행해보기로 했다.

우선 기존에 아이펠 노드에서 진행한 모델은 내가 잘못 한건지, 노드가 잘못된건지 학습은 되는데 어딘가 이상한 결과물이 나와서 텐서플로 공식 튜토리얼을 보고 다시 모델을 만들어보았다.

실패한 학습 결과

데이터셋은 kaggle의 Sketch2Pokemon 데이터셋을 학습시켰다. train 이미지가 830장 밖에 되질 않았고, test 이미지도 train 이미지에 있는 중복 이미지여서 데이터가 부족하긴 했지만, 일단 서비스로 배포해보는 것에 의의를 두기로 했다.

프로젝트 진행은 다음과 같이 이루어졌다.

  • 2021-03-28 : 프로젝트 시작
  • 2021-03-28 ~ 2021-03-29 : 첫 번째 모델 학습 및 결과 확인
  • 2021-03-29 ~ 2021-03-30 : 두 번째 모델 학습 및 결과 확인
  • 2021-03-30 ~ 2021-04-01 : 모델 서비스화 진행 중
  • 2021-04-02 : Ainize로 배포 완료

기존과의 차이점이라고 한다면 이미지를 Normalize 해주는 시점이 조금 다르다는 것이다. 아마 이 부분에서 문제가 생겨서 위와 같은 이미지가 학습된 것으로 보인다. 또한, 이미지 데이터를 다룰 때에는 항상 Normalize, Denormalize에 신경써야 하는 것 같다. 어떤 Activation Function을 사용하느냐에 따라 Normalize 범위를 [-1, 1]로 할지, [0, 1]로 할지가 결정되기 떄문이다. 이번에 사용된 모델에서는 tanh를 Activation Function으로 사용했기 때문에 [-1, 1]의 범위에서 Normalize 해주었다.

우선 시작부터 1000 epoch 정도를 돌려보고 시작하기로 했다.

  • 학습 과정을 시각화 해본 결과, 400 epoch 정도 부터 큰 변화가 없는걸로 보아 local minima에 빠졌거나 학습이 종료된 것 같다.
  • 750 epoch 의 checkpoint를 불러와서 기울기를 1/10으로 줄여서 50 epoch 정도 더 학습을 시켜보았으나 큰 변화는 없는 것 같다.
  • 750 epoch 의 checkpoint를 불러와서 기울기를 10배로 늘려서 50 epoch 정도 더 학습을 시켰더니 오히려 원하지 않는 방향으로 학습이 진행되었다.

750 epoch에서의 learning rate 테스트는 자원이 남아서 학습이 종료됐을 때 learning rate를 조절하여 다시 학습시키면 어떻게 될까 궁금해서 진행해봤다.

좌 : Dicriminator loss / 우 : Generator loss
Train set에 있는 이미지 학습 결과

모델을 학습해본 결과, 두 Loss를 비교해봤을 때, disc_loss는 감소하고 gen_loss는 증가하는 것으로 보아 generator가 discriminator를 속이기 위한 이미지를 잘 생성하지 못하는 것으로 판단된다. 실제로 train과 test에 없는 전혀 새로운 이미지를 입력했을 때, 다음과 같이 잘 생성하지 못하는 결과를 보여줬다.

뒤틀린 황천의 피카츄

아무래도 기존에 있는 이미지는 원본이랑 비슷한 정도로 생성하는 것으로 보아 train set에 overfitting 된 것 같다. 추가로 다른 데이터셋을 더 활용하거나, 모델의 구조를 바꾸어서 다시 학습시켜봐야겠다.

 

프로젝트를 진행하면서 많은 문제점과 어려움을 겪었다. 우선 웹페이지에 이미지를 하나 띄우는 것 까진 쉬웠지만 생성된 이미지를 다시 화면에 출력하는 것이 이렇게 어려운 작업이 될 줄은 몰랐다. 프론트엔드 개발자들 대단해...

겪었던 문제점들을 정리해보면 다음과 같다.

  • 프론트엔드/백엔드 지식이 전혀 없어서 HTML부터 공부해서 페이지를 제작함
  • Inference 결과 tensor object로 반환되어 해당 object를 바로 웹에서 rendering 가능한 방법을 찾다가 결국 이미지로 저장하여 rendering 하는 방식으로 진행
  • 페이지는 정상적으로 만들어졌는데 계속해서 기존의 이미지를 가져옴 -> 브라우저 쿠키 문제
  • model의 크기가 100MB가 넘어가서 git lfs를 이용하여 git에 업로드 하였으나 Ainize에서 불러오지 못하는 문제가 발생
  • heroku에서 배포를 시도하였으나 메모리 용량 부족(500MB)으로 배포 실패
  • 기본 git lfs 용량을 모두 사용하여 wget으로 바로 다운로드 가능한 무료 웹서버를 찾아보았지만 당장 쓸 수 있는 게 없어서 개인 웹서버 이용하여 모델 다운로드 후 Ainize로 배포

모델을 만들어서 학습시키고 실행 가능한 python 파일로 정리하는 것 까진 어렵지 않았는데 웹페이지를 구축하는 게 가장 어려웠다. 단순 텍스트 데이터가 아닌 이미지 데이터를 웹에서 띄우는 방법을 몰라 html을 공부해서 겨우 띄울 수 있게 되었는데 브라우저 캐시 때문에 정상작동 하는 걸 오작동으로 인식하여 다른 방법을 찾았다. 결국 프론트엔드를 공부하고 있는 sunhpark에게 도움을 요청하여 js로 웹페이지를 좀 더 깔끔하게 만드는 데 성공했다.

 

로컬에서 테스트 할 땐 잘 되었는데 막상 배포하려고 하니 적절한 서비스가 없었다. 우선 git에 모델 용량이 100MB를 초과해서 git lfs를 이용하여 Ainize로 배포를 시도하였으나 디버깅 하다가 git lfs 무료 사용량을 전부 사용하여 다른 서비스를 찾아보았다. heroku로 배포를 시도했을 땐 메모리 용량 부족으로 실패하였다.

 

마지막 남은 EC2는 1년만 무료여서 최후의 보루로 남겨두고 Ainize로 배포해보기 위하여 무료 파일 서버 서비스를 찾아보았지만 마땅한 게 없어서 결국 임시로 웹서버를 만들어서 Ainize에서 Dockerfile을 빌드할 때 모델을 받아올 수 있도록 하였고, 결국 성공하였다.

 

구글 드라이브나 네이버 클라우드 같은 곳에 파일을 업로드해서 wget으로 다운로드 받는 방법도 생각했었다. 그러나, wget으로 직접 다운로드 가능한 링크들이 아니어서 다른 방법을 찾았던건데, 나중에 찾아보니 구글 드라이브에 올린 파일도 wget으로 받을 수 있는 방법이 있었다. 당시엔 배포에 정신이 팔려 있어서 검색해볼 생각도 못 했나보다.

 

이번 프로젝트를 진행해보면서 왜 직접 해보는 것이 중요한지 확실하게 깨달았다. 아주 작은 토이 프로젝트였지만 어떻게 보면 프론트엔드와 백엔드 두 부분 모두 조금이나마 경험해볼 수 있었고, 직접 모델 설계부터 서비스까지 배포한 것은 MLOps라고 볼 수도 있지 않을까?

 

다음번엔 서버에 데이터 저장도 하고, 실제로 아마존 EC2나 GCP, Azure 같은 서비스를 이용하여 배포도 해보고, 좀 더 재미있는 아이디어를 가지고 팀원도 모아서 프로젝트를 진행해보도록 해야겠다.

 

해당 서비스는 다음 링크에서 테스트해볼 수 있다.


Run on Ainize

'AI' 카테고리의 다른 글

Inference time은 CPU와 GPU 중 뭐가 더 빠를까?  (0) 2021.05.13
auto-painter 진행 상황  (0) 2021.05.11
[Monthly Dacon 14] 첫 in 10등!  (1) 2021.04.12
Heroku로 딥러닝 모델 배포하는 방법  (0) 2021.04.02
VGGNet 구현해보기  (0) 2021.03.27

은 포기하는 게 좋다.

 

scikit-learn과 같은 라이브러리를 이용해서 만든 머신러닝 모델들은 가능할 지도 모르겠지만,

내가 시도했던 GAN 모델 중 하나인 U-Net구조의 Pix2Pix 모델만 해도 Heroku에서 배포가 불가능 했다.

로그를 확인해보면 알겠지만, heroku에 할당되는 container에는 약 500MB의 메모리가 할당되는 것 같다. 그러나 딥러닝 모델을 돌리기엔 턱없이 부족한 메모리이기 때문에 모델을 불러오는 과정에서 이미 메모리 부족으로 에러가 발생한다. 따라서 딥러닝 모델을 배포하려면 Ainize나 아마존 EC2와 같은 클라우드 서비스를 이용하는 것이 좋을 것 같다.

+ Recent posts