P Stage가 이번주 월요일부로 시작되었다.

 

사실 부스트 캠프에서 가장 걱정되었던 부분이 나에게는 P Stage였다.

 

올해로 갓 21살의 나이에 대학교 1학년을 마치고 AI 관련 강의를 부스트코스에서 수강하던 와중에 모집 글을 보고, '떨어지는 경험이라도 쌓고 익숙해지자'라는 생각으로 지원했었는데 운이 좋게도 붙어버린 것.

 

실제로 부스트 캠프 내에서도 내가 제일 나이가 어린 것 같았다.

 

Python이 가장 자신 있는 언어라고 해도 (하다못해 전문적으로 자신 있는 것이 아니라 이 때까지만 해도 list comprehension에 익숙해져서 재미 들린 시기) '대학교 1학년 마친 사람이 어떻게 나보다 나이 많은 사람들의 경험을 이길 수 있을까?' 라는 걱정이 앞섰다.

 

특히, 내 경우에는 학교에서 수업시간에 진행한 "팀플"로 2번 프로젝트 해본 경험 및 동아리에서 참여한 KUCIS 프로젝트로 Malware e-mail classification 참여 해 본 것이 전부였다.

 

조금 체계적으로 진행하는 프로젝트는 동아리에서 했던 프로젝트 단 하나 뿐이었으며 더군다나 이것도 내가 적극 참여할 수 없었던 프로젝트였다.

 

그 이유는, 내가 딱 AI에 시작하자마자 신청해본 프로젝트라서 데이터 수집부터 model 생성, train까지 해 본 적이 없는 나에게는 정말 따라가고, 모델 복사해서 파라미터 수정해보는 것이 전부였었기 때문이다.

 

즉, 부스트 캠프에 참여하기 전에 실질적으로 체계적인 프로젝트에 참여해 본 경험은 0에 가까웠다.

 

그렇기에 P Stage가 가장 걱정되었던 것.

 

물론, U Stage가 어렵지 않은 것은 아니었다. 정말 어려웠고, 나에게 첫 AI 교육이기 때문에 나의 것으로 만들고 싶은 내용들을 최대한 만들어보려 했고, 정말 빡셌다.

 

하지만, P Stage를 시작하면서 처음 부딪힌 난관이 "배운 내용을 바탕으로 코드를 어떻게 짜는가?" 였다.

 

U Stage에서 이론을 5주? 6주?간 배우고, 과제로 코드에 빈칸을 채우거나 몇 몇 줄을 짜보는 것 정도는 나왔었지만 (이마저도 나는 매우 매우 매우 힘들어했고, 70% 확률로 스스로 문제를 해결하지 못했었다;;) EDA부터 베이스 라인 코드 작성까지 나는 아무것도 할 수 없었다.

 

이 점이 너무 너무 힘들었다.

 

U Stage를 거쳐가면서 배운 이론과 과제들을 바탕으로 P Stage에서는 내가 어느정도 코드를 조금 다룰 수 있게 되지 않을까 내심 기대했었는데 한 순간에 꽈당했다. (물론, 말도 안되는 생각이긴 하다. 수학으로 따지면 미적분 이론 배우고 바로 수능 29번, 30번 문제 푸는 꼴이니까..)

 

문제는, 다 같이 힘들어 하면 모를까... 부스트 캠프 내에서 정말 날아다니는 캠퍼분들이 많이 존재했다.

 

내가 현재 가지고 싶은 모습들이 다른 많은 캠퍼분들은 가지고 있었던 것.

 

물론, 보이는 캠퍼분들이 전부가 아니라 실제로 나처럼 끙끙대는 캠퍼분들도 존재하겠지만, 너무 gap 차이가 크게 나는 것 아닐까? 하는 생각이 많이 들었다.

 

실제로, 내 팀에서 (한 팀당 인원은 5명) 나를 제외한 4명은 각자가 해당 프로젝트에 기여하는 부분들이 명확하게 존재하거나 무언가 성과를 내는데, 나는 주어진 베이스 코드를 바탕으로 실험해보다가 에러 잡는 것에만 몇시간 투자하고 결국에는 에러를 못잡고 마무리되는 상황들이 존재했다.

 

더군다나 나는 서버를 연동시키는 법을 몰라 주어진 베이스 코드(py파일)을 서버의 jupyter lab에서 실행하고 수정하기 위해서 ipynb로 바꿔가며 시도한지라 다른 팀원분들의 코드를 받아오더라도 작업이 필요해 번거로웠다.

 

결국에 금요일에 idle에 서버를 연동시키고자 3시간정도 투자하였는데 항상 마지막 에러가 잡히지 않아서 멈추고 (시간을 너무 많이 소요한 것 같아) ipynb로 다시 작업을 진행하였다.

 

이러한 과정을 거치다보니 소위 "현타 왔다"라는 말처럼 내 자신에 대해 한숨이 나왔다.

 

'왜 나는 다른 캠퍼분들처럼 실험 하나 제대로 못하는가?'

 

'왜 나는 에러를 못잡고 서버도 연동을 못시켜서 이 모양 이꼴인가?'

 

'프로젝트에서 버스타기 싫어하면서 왜 버스타고 있는 꼴이 되어가는가?'

 

'level 2 팀원 분들을 구할 때는 적극적으로 했으면서 level 2에서도 민폐만 끼치고 싶은가?'

 

등등 온갖 혐오를...

 

너무 힘들었는데 또 포기하고 싶지는 않았다.

 

휴학하고자 수강신청도 안해놨었고.. 더군다나 이렇게 제대로 체계적인 교육을 어렵다고 포기하기에는 나에게 주어진 큰 기회를 내가 못잡는 것 같아서 더 한심할 것 같기 때문이었다.

 

이 시점에 내가 옳게 가기 위해서 조언이 필요한 것 같다는 생각이 들었고 변성윤 마스터님과 많은 캠퍼분들에게, 김태진 마스터님에게 질문을 올렸었다.

 

이 답변들이 나에게 하나의 큰 버팀목이 되어준 것 같다.

 

"처음에는 잘 할 수 없고 일단 나 자신을 위로 해 준다."

 

"내가 코드 작성을 못 하는 부분이 있다면 어떤 부분을 못하는지 분석해보고 그 문제점들을 하나씩 해결해 나간다."

 

"새로 배운 내용, 코드들을 노션에 정리한다."

 

"숲을 보는것보다는 나무 하나 하나를 이해하고 분석해 나아간다."

 

"조급해 하지 말고 우선 순위를 잡아 나아가자."

 

"이론만 보고 코드를 작성하는 사람은 천재다. 그것은 욕심이다." (뜨끔)

 

"원하는 이상치와 현재 상태의 간극을 좁히는 공부를 진행해라."

 

와 같은 정말 좋은 말씀들을 많이 남겨주셨다.

 

해당 내용을 바탕으로 아래 내가 해볼 공부들을 적어보았다.

 

 

아직 우선순위는 정해지지 않았다.

 

프로젝트가 한 4일정도 남아있어서 프로젝트에서 필요한 부분을 먼저 시도해 보지 않을까 생각이 든다.

 

또한, 내가 "왜" 이 코드들을 아직 작성하기 어려워 하는지가 "어떤 흐름으로 작성하고 어떤 기능들을 사용하는지 아직 잘 모르기 때문"이라고 생각해서 이 부분을 다양하고 많은 코드들을 보면서 "flow"를 읽고 한 줄 한 줄 코드가 어떤 기능을 하는지 숙지하면서 프로젝트를 통해 직접 짜보는 것으로 생각하고 있다.

 

 

내 목표는 사람 살리는 인공지능을 만드는 것이다.

 

부스트 캠프는 나의 목표를 이루기 위해 나아가는 첫 시작이 될 것이다.

 

첫 시작이 매우 중요하다고 하였는데, 정말 좋은 곳에서 시작할 수 있게 된 기회를 얻었다.

 

다른 사람들은 21살의 나이에 도전하는 것 자체가 대단하다고 하지만 나는 도전에만 멈추는 것이 아니라 성공하고 싶고 도움을 주고 싶다.

 

내가 이 공부들을 꼭 성공하고 세상에 한 획을 쓰리라!!

 

( + 개인 idle에 서버를 연동시키는 것을 오늘 드디어 성공했다. 운이 조금 작용했는데, 다른 캠퍼님이 올려주신 서버 연동법을 보고 시도를 반복하다가 server의 workspace 폴더에 내가 작성한 코드들이 올라간다는 것을 알게 되었고 CLI에서 성공적으로 실행할 수 있었다!!! 사실 CLI로 train.py를 바로 실행시켰을 때 에러가 많이 났었지만, 앞서 구글링을 통해서, 다른 캠퍼분과 같이 이야기하며 에러를 jupyter 상에서라도 해결했던 것을 메모해 둔 것이 크나큰 도움이 되었다. 역시 구글링과 같이 공부하는 것, 그리고 머리를 식히는 것은 매우 중요하다 ㅋㅋㅋ)

사실 처음에 코드를 이렇게 작성했었다.

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

sentence = list(input().strip())    # 줄바꿈 문자를 제외한 input을 한 글자마다 split
# print(sentence)    # DEBUGGING
cursor = len(sentence)    # 문장이 입력되면 cursor는 마지막에 위치
M = int(input())    # command 개수인 M을 입력

for _ in range(M):    # command 개수만큼 반복
    cmd = input().strip()    # cmd를 줄바꿈 문자를 제외하여 입력
    try:
        cmd, str = cmd.strip().split(' ')    # 명령어가 P일 경우 split하여 str을 저장
    except:
        pass    # 명령어가 P가 아닐 경우 특별한 event가 존재X

    # print('before: ', cursor)    # DEBUGGING
    
    if cmd == 'L' and cursor != 0:    # L 명령어 + cursor가 맨 앞이 아니라면 왼쪽으로 한 칸 이동
        cursor -= 1
    elif cmd == 'D' and cursor != len(sentence):    # D 명령어 + cursor가 맨 뒤가 아니라면 오른쪽으로 한 칸 이동
        cursor += 1
    elif cmd == 'B' and cursor != 0 and len(sentence) != 0:    # B 명령어 + cursor가 맨 앞이 아니라면 cursor 위치의 값 제거
        # print(sentence)    # DEBUGGING
        cursor -= 1
        del sentence[cursor]    # 제거 후 자동으로 값이 들어오므로 cursor 위치를 옮길 필요X
        
        # print(sentence, cursor)    # DEBUGGING
    elif cmd == 'P':    # P 명렁어가 입력된 경우 cursor 위치에 str을 저장 한 후, 오른쪽으로 한 칸 이동
        # if cursor == len(sentence) - 1:
        #     sentence.insert(cursor + 1, str)
        # else:
        sentence.insert(cursor, str)
        cursor += 1
    
    # print('after: ', cursor)    # DEBUGGING

print(''.join(sentence))    # list를 str로 합침

사실 이것도 print()를 이용한 디버깅을 통해서 에러 잡고 잡고 잡고 잡아서 만든 코드였는데

 

시간 초과라니 ... ^^;;

 

stdin을 썼는데도 나타난 시간 초과 관련한 오류는 아직 잡을 수 있는 방법을 몰라서 인터넷에 뒤적여 봤다.

 

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

stack_left = list(input().strip())    # 입력받은 문자를 줄바꿈 문자를 제외하여 한 글자씩 리스트에 저장
stack_right = list()    # 커서가 가장 마지막에 있으므로 커서의 우측은 아직까지 없으므로 빈 리스트 생성
M = int(input())    # command 개수 입력

for _ in range(M):    # command 개수만큼 반복
    command = input()    # command 입력
    if command[0] == 'L' and len(stack_left) != 0:    # command가 커서 좌측 한 칸 이동이고, 커서가 가장 좌측에 위치하지 않았다면
        stack_right.append(stack_left.pop())    # 커서를 좌측으로 한 칸 이동 (이 때, 커서가 좌측으로 이동하면서 지나간 값은 stack_right에 저장)
    elif command[0] == 'D' and len(stack_right) != 0:    # command가 커서 우측 한 칸 이동이고, 커서가 가장 우측에 위치하지 않았다면
        stack_left.append(stack_right.pop())    # 커서를 우측으로 한 칸 이동 (마찬가지로, 커서가 우측으로 이동하면서 지나간 값을 stack_left에 저장)
    elif command[0] == 'B' and len(stack_left) != 0:    # command가 커서 위치에 있는 값 제거이고, 커서가 가장 좌측에 위치하지 않았다면
        stack_left.pop()    # 해당 값 제거 (제거이므로 stack_right에 저장하지 않으며 stack_left에서 pop을 진행하였기에 커서가 자동으로 이동되는 효과)
    elif command[0] == 'P':    # command가 커서 위치에 값 추가라면
        stack_left.append(command[2])    # 해당 위치에 문자 추가 (stack_left에 추가했기 때문에 커서가 자동으로 이동되는 효과)

stack_left.extend(stack_right[::-1])    # stack_right에는 원래 문자의 순서가 아닌 거꾸로 저장되어 있으므로 돌려서 stack_left와 합)
print(''.join(stack_left))    # 문자열로 출력하기 위한 join 함수 사용

 

참고 : https://bladejun.tistory.com/12

 

관련한 코드와 같이 적혀있었는데, 아이디어 적인 면에서는 정말 훌륭한 풀이였으나

 

"왜?"는 해결하지 못했다.

 

https://wayhome25.github.io/python/2017/06/14/time-complexity/를 보면 이유를 참고할 수 있다!

 

pop은 시간 복잡도가 O(1)인 반면 del은 O(N)이였고 insert도 시간 복잡도가 O(N)이였다.

 

그렇기에 시간이 훨씬 소모되어 시간 초과가 일어났던 것..!!

 

 

 

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 1158 - 요세푸스 문제 (Python)  (0) 2022.03.01
BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1847 - 스택 수열  (0) 2022.02.24
BaekJoon 9012 - 괄호  (0) 2022.02.23
BaekJoon 9093 - 단어 뒤집기  (0) 2022.02.22

꽤나 고생 좀 했다..

 

사실 이 문제에는 강력한 힌트가 있었다.

 

test 케이스 숫자에 해당하는 "모든 정수"가 결국에는 pop이 되어야 했던 것.

 

이 사실을 늦게 알아서... 많이 고생했다. ㅋㅋㅋ

 

이 점을 적용하면 코드는 아래와 같이 간단해진다!

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

last_in = 1
result = []
num_list = []

FLAG = True

T = int(input())    # 테스트 케이스 개수를 입력

for _ in range(T):    # 테스트 케이스 개수만큼 반복
    num = int(input())    # 정수 입력
    
    while last_in <= num:    # 마지막 입력된 정수가 num이 될 때까지 반복
        num_list.append(last_in)    # 정수를 stack에 저장
        # print("input >>>", last_in)
        result.append('+')    # stack에 쌓으므로 '+' 저장
        last_in += 1    # 입력할 정수가 다음 숫자로 넘어감
        # print(last_in)

    # while num in num_list:
    if num_list[-1] == num:    # 내려올 때는 무조건 한 칸씩 내려와야 함
        result.append('-')    # pop이므로 '-' 저장
        num_list.pop()    # 마지막 값 pop
        # print("out >>>", last_out)
    else:    # 여러 칸 내려온다면?
        FLAG = False    # 불가능!
        break    # for문 종료

if FLAG:
    for value in result:
        print(value)
else:
    print('NO')

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 9012 - 괄호  (0) 2022.02.23
BaekJoon 9093 - 단어 뒤집기  (0) 2022.02.22
BaekJoon 10828 - 스택  (0) 2022.02.21

Python이기에 간단하게 작성 해 볼 수 있는 코드를 쓴 것 같은 느낌이 든다..ㅎㅎ

 

처음 든 생각은 단순하게 괄호 '('와 ')'의 개수 비교였으나 순서도 모두 맞아야 한다는 느낌이 들었고 역시나 테스트 케이스에도 그 점이 인지되어 있었다.

 

예전에 programmers 카카오 lv1 문제인가에서 (아마 id 생성 관련해서 7단계로 나누어서 해결하는 문제였던 것 같다.) best 풀이 중에 while로 탐색 및 반복하는 풀이가 기억에 강하게 남았었는데, 문득 이 방법이 생각나서 적용해보았다.

 

이를 활용하면 나중에 올바른 괄호 쌍의 개수도 쉽게 구할 수 있다!

 

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

num = int(input())    # 테스트 케이스 개수를 입력
for _ in range(num):    # 테스트 케이스 개수만큼 반복
    sentence = input().strip()    # 괄호 문자열을 입력받고 '\n'문자 제거
    while '()' in sentence:    # 괄호쌍이 문자에 존재하면 계속 반복
        sentence = sentence.replace('()', '')    # 괄호쌍을 없애버림
    print('YES') if sentence == '' else print('NO')    # 괄호쌍이 순서에 맞게 존재했다면 모두 제거되고 그렇지 않다면 남음

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 1847 - 스택 수열  (0) 2022.02.24
BaekJoon 9093 - 단어 뒤집기  (0) 2022.02.22
BaekJoon 10828 - 스택  (0) 2022.02.21

Python의 list to string과 string to list에 익숙해지는 시간입니다.

 

기본적으로, Python에서는 split()함수를 통해서 string을 list에 담고, join()함수를 통해서 list를 하나의 string으로 합칩니다.

 

10828번 문제에서도 split을 사용했지만, split을 사용하지 않는 풀이가 많이 존재했는데 이 문제는 split과 join을 알면 훨씬 쉽게 풀 수 있는 문제입니다.

 

10828번 문제에서 시간 초과 문제가 발생하여 이번에도 발생할 것 같아 (구조가 비슷해서) 똑같이 sys 모듈을 불러와서 input을 재정의 하였습니다.

 

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

num = int(input())    # 테스트 케이스 개수를 입력
sentence = []    # 테스트 케이스를 저장받을 리스트 선언

for _ in range(num):    # 테스트 케이스 개수만큼 반복
    sentence = input().strip().split(' ')    # 테스트 케이스를 입력받고, 공백으로 분리
    for idx, value in enumerate(sentence):    # enumerate로 index와 value를 받아오면서
        sentence[idx] = value[::-1]    # value의 값을 뒤집어서 저장
    print(' '.join(sentence))    # 출력물은 공백을 기준으로 리스트를 합침

 

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 1847 - 스택 수열  (0) 2022.02.24
BaekJoon 9012 - 괄호  (0) 2022.02.23
BaekJoon 10828 - 스택  (0) 2022.02.21

자료구조의 첫 번째인 스택을 구현하는 문제입니다.

 

스택은 Last In First Out으로, 나중에 들어오는 값이 먼저 출력된다는 뜻입니다. (줄여서 LIFO - 후입 선출 이라고도 합니다.)

 

문제를 그대로 따라가면 풀리는데 문제는 input()을 그대로 사용하면 시간 초과 문제가 발생합니다.

 

따라서, 조금 더 시간을 적게 소모하는 sys.stdin.readline을 사용하여 input을 재정의하면 시간 초과가 발생하지 않습니다.

 

import sys
input = sys.stdin.readline    # 시간 초과 error를 해결하기 위한 input함수 재정의

rep_num = int(input())    # 명령어 개수 입력
stack = []    # stack list

for _ in range(rep_num):    # 명령어 개수만큼 반복
    command = input()    # 명령어 한 줄 입력
    if "push" in command:    # 명령어가 push라면
        stack.append(int(command.split(" ")[1]))    # 공백을 기준으로 split 한 후, 1번 인덱스 값을 int로 변환하여 stack에 저장
    elif "pop" in command:    # 명령어가 pop라면
        if len(stack) == 0:    # 빈 stack이라면 -1 출력
            print(f"{-1}")
        else:    # 빈 stack이 아니라면 stack의 마지막 값 추출 및 출력
            print(stack.pop())
    elif "size" in command:    # 명령어가 size라면
        print(len(stack))    # stack의 크기 출력
    elif "empty" in command:    # 명령어가 empty라면
        if len(stack) == 0:    # 빈 stack이라면 1 출력
            print(f"{1}")
        else:    # 빈 stack이 아니라면 0 출력
            print(f"{0}")
    else:    # 명령어가 top라면
        if len(stack) == 0:    # 빈 stack이라면 -1 출력
            print(f"{-1}")
        else:    # 빈 stack이 아니라면 stack의 마지막 값 출력
            print(stack[-1])

 

'Algorithm Test > BaekJoon' 카테고리의 다른 글

BaekJoon 10845 - 큐 (Python)  (0) 2022.02.28
BaekJoon 1406 - 에디터 (Python)  (0) 2022.02.25
BaekJoon 1847 - 스택 수열  (0) 2022.02.24
BaekJoon 9012 - 괄호  (0) 2022.02.23
BaekJoon 9093 - 단어 뒤집기  (0) 2022.02.22

네이버 부스트캠프 AI Tech 3기의 4주 차가 지나가고 지금은 5주 차로 접어드는 시기이다.

 

4주 차 내용에서 어려웠던 CNN 기술들, Transformer, Auto Encoder (VAE, AAE 등), Generative Models (GAN 등)을 논문을 통해서 공부해보고자 생각했고, 처음 선택하게 된 논문은 Ian Goodfellow의 Generative Adversarial Nets 즉, GAN의 관한 논문이다.

 

 

왜 이 논문을 선택했냐고 묻는다면 크게 세 가지 이유를 말할 수 있는데,

 

1. 내가 희망하는 분야인 medical 분야 중 신약 분야에서 Graph Neural Networks (GNN)과 더불어 GAN이 많이 사용되고 있기 때문

 

2. 수업에서의 난이도가 개인적으로 많이 어렵게 느껴졌고, 첫 번째 이유인 내가 희망하는 분야에서 많이 사용되는 모델인데 어렵다고 나중에 공부하자는 마인드는 모순되었기 때문

 

3. 현재까지 resnet이 많이 사용된 것처럼 GAN이 현재뿐만 아니라 미래에도 많이 사용되거나 GAN을 바탕으로 모델들이 발전할 것 같기 때문이다.

 

 

1. Abstract

 

바로 논문의 Abstract로 넘어가 보자.

 

Abstract를 읽어보면 목적과 개념에 대해서 상단에 언급이 되어있고, GAN의 특징이 하단에 적혀있다.

 

특히, Abstract에서 D와 G가 많이 보이는데 이는 GAN의 핵심이 되는 개념 중 하나인 Generative model (Generator)와 Discriminative model (Discriminator)이다.

 

이 개념은 조금 이따가 보기로 하고 Abstract에 적혀있는 내용 중 또 다른 핵심 개념들인 adversarial process, minimax, Markov chains 등을 먼저 알아보고 넘어가 보려고 한다. (지나가면서 unrolled approximate inference networks도 잠깐 언급하겠다.)

 

 

먼저, minimax에 대해서 알아보자.

 

사실 minimax는 구글에 검색해서 wiki를 보면 바로 이해가 되는 개념이긴 하다.

 

wiki에 따르면, 최악의 경우에서 발생 가능한 손실을 최소화한다는 규칙이라고 적혀있는데, 예시를 통해서 보면 조금 더 이해하기 쉬울 것 같다.

 

예를 들어, 도박을 하면 보통 1위에게 모든 돈을 몰아준다.

 

이때, 게임에서 패배하더라도 최악으로 패배할 때의 손실을 최소화하겠다는 알고리즘이다.

 

쉽게 말해서 시험을 엄청 망치면 부모님한테 역대급으로 혼날 것이고, 혼나더라도 많이보다는 적게 혼나기 위해 우리가 변명하는 것을 생각하면 될 것 같다. (물론, 실제로는 그러면 더 혼난다 ㅋㅋㅋ)

 

 

다음으로는 Markov chain을 알아보겠다.

 

Markov chain을 보기 전에 Markov process를 이해해야 한다.

 

Markov process는 GAN에서만 필요한 개념이 아니라 Auto-regressive Model를 다룰 때에도 필요한 개념으로 알고 있는데, 잘 알아두어야 할 것 같다.

 

Markov process의 개념은 어떠한 미래 상태가 있고, 그 미래 상태의 확률 분포가 현재 상태에 의해서만 결정된다는 뜻이다.

 

즉, 과거의 결과를 미래의 상태를 계산할 때 반영하지 않겠다는 뜻으로 볼 수 있겠다.

 

이때, Markov chain은 Markov process에서 이산 시간일 경우만 고려한 것이다.

 

참고로 이산 시간에서 "이산"이 뜻하는 것은 키나 몸무게와 같이 연속적인 값이 아닌 주사위 눈금 같은 비연속적이나 숫자로 표시되는 값을 말한다. (1, 2, 3, 4, 5, 6도 연속이지 않냐라고 생각할 수 있지만, 1과 2 사이에는 무수한 숫자가 존재하므로 이산으로 보자는 것이다.)

 

Markov process에 관한 예시를 하나 들어보겠다.

 

 

이번 달에 민초파인 사람이 다음 달에도 민초파일 확률을 90% (0.9), 다음 달에는 반민초파가 될 확률을 10% (0.1)라 하자.

 

또한, 이번 달에 반민초파인 사람이 다음 달에도 민초파일 확률을 60% (0.6), 다음 달에도 반민초파일 확률을 40% (0.4)라고 하자.

 

초기에 민초파인 인원은 10만 명이고, 반민초파인 인원은 100만 명으로 정했다.

 

 

위에서의 확률을 그대로 행렬로 변환하는데, 이 행렬을 상태 변이 확률 행렬이라고 한다.

 

자, 그러면 Markov chain에 따라서 다음 달에 민초파인 사람과 반민초파인 사람의 인원수를 구해보자.

 

과거가 어쨌든 간에 현재의 상태 변이 확률 행렬만 계산에 반영되므로 이를 초기 값에 곱해주면 민초파와 반민초파의 인원이 각각 69만 명, 41만 명으로 계산될 것이다.

 

 

그러면 2개월 후의 인원도 마찬가지로 구해보자.

 

현재의 민초파와 반민초파의 인원은 각각 69만 명, 41만 명이고 어떠한 과거의 요소가 개입이 되지 않으므로 똑같이 행렬 곱 계산을 해주면 된다.

 

자세한 계산은 위의 PPT 이미지를 참고하면 될 것 같다.

 

 

이를 조금 더 발전시켜보면 Markov chain의 상태 변이 확률 행렬과 초기값이 주어졌을 때, n만큼 지난 후의 값은 행렬곱을 n번 반복한 것과 동일하다고 볼 수 있는 것이다.

 

Markov chain은 Auto Encoder 외에도 다수의 내용에서도 등장하는 개념이니 조금 깊게 다루어 보았다.

 

 

여기서 unrolled approximate inference nets에 대해서 잠깐만 짚고 넘어가겠다.

 

Abstract에 보면 unrolled approximate inference가 나오는데 이 개념(approximate inference)은 논문에서 계속 Markov Chain과 함께 과거 Generative model과 GAN을 비교하는 동안 등장한다.

 

approximate inference는 앞서 언급했다시피 Auto-regressive model에서 나오는 개념으로 (이는 VAE와 AAE에서 필요한 개념의 일부입니다.) 당시에는 Bayesian network와 같은 구조를 활용한 model을 활용했는데 이를 말하는 것으로 보고 있다.

 

따라서, unrolled approximate inference nets에 너무 집중하지 말고 GAN이 이와 달리 여러 step 없이 end to end로 학습과 inference 된다는 것을 강조하는 데에 사용된다고만 알고 있으면 될 것 같다. (사실 동일한 이유로 Markov chain도 깊게 다룰 이유가 없었지만, 개인적으로 Markov chain은 사용 범위가 넓다고 생각하여 자세하게 다뤘다.)

 

 

끝으로 이 논문의 핵심인 adversarial process와 generative model G, discriminative model D에 대해서 알아보는데, 이를 Introduction과 연계하여 알아보겠다.

 

먼저, adversarial을 영어 그대로 하면 "적대적인, 대립하는"으로 라이벌을 이용한 공부와 유사하다고 볼 수 있다.

 

Generative Adversarial Networks에서 사용되는 적대적 존재들은 조금 특별하게도 Introduction의 중반부에 자세한 예시와 함께 나와있다.

 

 

2. Introduction

해당 이미지 출처 :&nbsp;https://sites.google.com/site/aidysft/generativeadversialnetwork

Introduction에 따르면, generative model에서 대립하는 존재는 discriminative model과 generative model이라고 적혀있는데 이 예시는 GAN을 검색해본 사람이라면 누구나 들어본 경찰과 위조지폐범 이야기이다.

 

일단은 discriminative model의 학습이 data 분포에서 온 것인지 아니면 모델(즉, generative model)에서 온 것인지를 결정함으로써 학습한다고 되어 있다.

 

이 다음에 generative model을 감지되지 않게끔 가짜 지폐를 생산 및 사용하고자 시도하는 위조범과 동일하게 생각할 수 있다고 비유를 주고, discriminative model을 가짜 지폐를 찾고자 하는 경찰로 비유하고 있다.

 

즉, Generative model (Generator)은 가짜 data를 생성하는 생성 모델, Discriminative model (Discriminator)은 가짜와 진짜를 구별하는 분류 모델이라고 생각하면 된다.

 

Generative model과 Discriminative model이 등장하게 된 배경은 Introduction의 초반부에 적혀있다.

 

조금 논문을 해석하는 방향으로 보면서 설명하겠다.

 

Deep Learning의 기본적인 목표는 AI가 학습한 data의 종류 외에도 들어오는 data를 확률 분포로 나타낼 수 있는 계층적으로 깊은 형태의 모델을 발견하는 것이라고 한다.

 

또한, 이전까지 성능이 좋았던 Deep learning은 고차원으로 구성된 Discriminative model을 사용하고 있었고 piecewise linear를 사용하여 backpropagation과 dropout을 잘 되게 했었다. (piecewise linear는 https://dawoum.ddns.net/wiki/Piecewise_linear_function에 잘 설명되어 있다.)

 

하지만, 과거의 generative models를 "deep"하게 쌓을수록 복잡한 확률 계산을 근사하기 어려웠고 piecewise linear의 장점이 별로 영향을 끼치지 않았기에 성능이 나오지 않게되었고 이를 바탕으로 새로운 generative model을 고안했다고 적혀있다.

 

Introduction 마지막에는 "adversarial nets"에 대한 정의가 나오는데, 그대로 해석해보면 generative model이 multilyaer perceptron을 통해서 random noise를 전달하여 sample(즉, discriminator를 헷갈리게 하는 fake data)을 생성하며 discriminative model도 multilayer perceptron인 model이라고 되어 있다.

 

마지막이 GAN의 핵심이 되는 말인데, 과거에 Markov chain이나 다른 inference를 사용하여 복잡한 절차를 거친 모델들과 달리 GAN은 이런 복잡한 절차 필요 없고 generative model은 forward propagation으로 sample을 생성한다는 점, 그럼에도 불구하고 generative model과 discriminative model은 backpropagation과 dropout이 잘 되어 성능도 잘 나온다는 점을 강조하고 있다.

 

이 부분은 바로 이어서 나오는 Related Work의 초반부에서 과거의 모델들을 언급하며 설명이 더 자세하게 되어 있는데 이정도면 충분할 것 같다. (잠깐 언급하자면, 과거의 모델인 Boltzmann machines, deep Boltzmann machines의 특징과 단점에서부터 Deep belief networks(DBNs)로의 발전, noise-contrastive estimation(NCE)와의 비교 등이 적혀있으며 이 과정에서 discriminative model을 어떻게 사용했고, generative model을 어떻게 했는지 언급하고 있다. 이후 GSNs와 Markov Cahin에 관한 이야기, backpropagation에 대한 이야기까지 나와있다.)

 

 

3. Adversarial nets

여기서 잠깐 이런 의문이 들 수 있다.

 

"아니, GAN이 adversarial learning을 이용해서 학습하는 것도 알겠습니다. adversarial이 되는 주체들은 generative model과 discriminative model이라는 것도요. 그리고 generative model은 가짜 데이터를 discriminative model에게 주어서 헷갈리게 한다는 점도 알겠습니다. 그러면 앞에서 다룬 background 개념들은 어디에 쓰이고, 왜 이렇게 헷갈리게 하는 게 더 도움이 된다는 건가요?"

 

두 가지를 먼저 대답하겠다.

 

하나는 Markov Chain이 과거의 Model들에는 많이 쓰였으나 GAN에서는 쓰이지 않았다는 점을 강조하고자 언급한 점 (앞에서도 말했지만 이는 Deep learning을 공부하다보면 많이 나오는 개념이기에 알아두면 좋겠다는 생각에 길게 썼다.)

 

두 번째로는 minimax와의 관계이다.

 

위의 사진에는 이상한 수식이 적혀있는데, 하나하나 보겠다.

 

등호의 좌측에 적혀있는 minmax V(D, G)는 G는 V함수를 최소화하고, D는 V함수를 최대화하겠다는 의미이다.

 

여기서 $ D(x) $는 second multilayer perceptron (discriminative model)이 G가 만든 data인 $ p_g $인지, 아니면 real data에서 온 $ x $인지를 확률로 나타내는 함수이다.

 

여기서, discriminative model과 generative model이 adversarial이라는 관점을 적용시켜보면 D는 $D(x)$를 높여야하고 (= discriminative model이 generative model가 만든 가짜 data인지 아니면 진짜 data인지를 제대로 판별) G는 반대로 D(x)를 낮추어야 한다. (여기서 x는 real data에서 온 data라고 했을 때)

 

또한 G가 만든 데이터 z에 대해서 $ G(z) $가 $D(x)$의 input으로 올 때 (즉 수식으로는 $ D(G(z)) $) D는 이를 real data로 판별할 확률을 줄여야 하고, G는 높여야 한다.

 

수식에서의 통일성을 주고자 $ 1 - D(G(z)) $로 바꾸어서 (D의 output은 확률이므로) 보면 D는 $ D(x) $와 $ 1 - D(G(z)) $를 높여야 하며, G는 반대로 모두 낮춰야 한다.

 

이를 모두 합쳐서 수식으로 나타내면 바로 위의 자료에서 등호의 우측 수식이 되는 것이다.

(첨언하자면, 통계에서 확률에 log를 씌워 정규성을 높인다고 한다.. 또한 $ \mathbb{E}_{x\sim p_{data}} $는 data의 확률 분포를 따르는 $ x $의 기댓값으로 해석하면 된다. 우측에 동일하게 생긴 것도 마찬가지 -> $ z $의 의미를 생각해서!)

 

즉, 위의 의문에서 adversarial의 특징이 minimax와 맞았고 그로 인해 사용 되었다고 보면 될 것 같다.

 

이를 통해서 GAN의 필수 수식을 이해하였고 작동 원리까지 이해할 수 있었다.

 

이 아래부터는 G와 D를 어떻게 학습에 사용하였고 어떻게 성능을 내고자 했는지에 대한 내용이 적혀있다.

 

해당 부분부터는 수학 수식이 많이 나오기 때문에 주의하기 바란다.

(선형대수와 확률통계도 안 듣고 수업 듣는 나를 가장 잘 표현하는 이모티콘)

 

 

--- 이 이후는 다시 읽었을 때 계속해서 작성할 예정 ---

 

 

 

 

 

+ Recent posts