지난 알고리즘 공부하기 Step1(뭘 공부해야 하지?)와 Step2(입출력 30문제 풀며 이해하기)에 이어서 Dynamic Programming 공부를 하려 한다. 

[공부 방법]

- 지적 호기심이 충분한 사람이나, 도전정신이 강한 사람이라면 이론 공부 후에 바로 문제에 도전 할 수 있을 것이다. 하지만 개인적으로는 강의 영상을 통해 동적 프로그래밍이 무엇이고 어떤 예제들이 있는지, 예제들을 어떻게 접근해서 어떻게 구현(코딩)해 나가는지 몇가지 예시를 보고 깊게 학습하는 것이 흥미를 잃지 않고 '지속 가능한' 학습 방법으로는 좋은 것 같다. 나는 아래의 나동빈님의 영상을 통해 동적 프로그래밍이 무엇인지 감을 잡았다.

- 위의 영상 1번을 시청하고, 2번은 시청하면서 잠시 멈추고 스스로 문제를 풀어보고 풀이를 보고 이해하고 이런 방식으로 하면 좋은 것 같다.

1번 : https://www.youtube.com/watch?v=YHZiWaL49HY

DP 개념 설명 및 예제 풀이 - 1

2번 : https://www.youtube.com/watch?v=kYoKLm8BZtI

DP 예제풀이 - 2

[동적 계획법]

- 수학과 컴퓨터 공학, 그리고 경제학에서 동적 계획법이란 복잡한 문제를 간단한 여러 개의 문제로 나누어 푸는 방법을 말한다. 이것은 부분 문제 반복과 최적 부분 구조를 가지고 있는 알고리즘을 일반적인 방법에 비해 적은 시간 내에 풀 때 사용한다. (위키피디아)

 

[동적 계획법을 이해하기 위한 가장 좋은 예시 문제는?]

- 피보나치 수열 계산을 재귀적으로 했을 경우 VS 동적 계획법을 활용 할 경우를 비교하면 정확히 동적 계획법이 무엇인지 알 수 있다.
* 피보나치 수열 (An = An-1 + An-2)

1. 재귀함수로 문제를 해결할 경우.

1
2
3
4
5
6
7
8
9
10
11
function 피보나치(i){
    
    if(i == 1 || i == 2return 1;
    else return  피보나치(i-1+ 피보나치(i-2);
 
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

피보나치(5) 를 계산하기 위해서는?
-> 피보나치(4)와 피보나치(3)을 계산해야 함.

피보나치(3)을 계산하기 위해서는?
-> 피보나치(2)와 피보나치(1)을 계산해야 함.

피보나치(4)를 계산하기 위해서는?
-> 피보나치(3)과 피보나치(2)를 계산해야 함

....
이렇게 나아가다 보면, 피보나치 (4), 피보나치(3), 피보나치(2), 피보나치(1)을 반복 계산하게 되고 이는 계산에 심각한 낭비를 가져온다.
예를들면 피보나치(50)을 계산하기 위해서 49,48를 계산하고 49를 위해서 48,47을 계산하는데 이는 트리구조로 아래로 내려갈 수록 2배의 계산이 필요하다. 총 피보나치(N)을 계산하기 위해 2^50을 계산해야 한다.

"그럼 중복을 기억해서 줄이면 되지 않나?" 이 생각이 바로 동적 계획법이다.

2. 피보나치 수열을 동적 계획법으로 계산할 경우.

1
2
3
4
5
6
7
8
9
10
11
int 기억장소[N];
 
function 피보나치(i){
    
    if(i == 1 || i == 2return 1;
    else if (기억장소[i] != 0return 기억장소[i];
    else return 기억장소[i] = 피보나치(i-1+ 피보나치(i-2);
 
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 위와 같이 피보나치 수열 계산을 동적 계획법을 활용하면, '기억장소'를 활용하여 memorize 시키고 이미 푼 문제의 답을 다시 활용한다.
이런식으로 코드를 짜면 각각 한 번만 계산하면 되니 총 N번의 실질적 계산이 이뤄질 뿐이다.

알고리즘 공부하기 (Step 1, 뭐 부터 어떻게 공부해야 하지?)

백준 사이트에서 공부를 시작하려고 하는데 추천 받은 문제 카테고리에 따라

입출력 - DP - 기타 - bfs,dfs - binary search - 분할 정복 - 그리디 - 완전탐색 순서로 풀어 보려고 한다.

이번 주말에 풀었던 입출력 문제들은 다음과 같다. (boj.kr/2557 형태로 검색!) 약 30문제, 5시간 정도 걸린 것 같다.
BOJ - 2557, 1000, 2558, 10950, 10951, 10952, 10953, 11021, 11022, 11718, 11719, 11720, 11721, 2741, 2742, 2739, 1924, 8393, 10818, 2438, 2439, 2440, 2441, 2442, 2445, 2522, 2446, 10991, 10992

 시간 여유가 없으면 힘들겠지만, 주말에 몰아서 문제들을 풀다보니 확실히 하루에 긴 시간을 투자해서 공부하는 것이 짧게 여러번 공부하는 것 보다 학습 효율이 좋은 것 같다. 실수 했던 부분이나 배운 부분을 바로 다음 문제에 활용 가능해서 기억 -> 체화로 넘어갈 수 있는 것 같다.

 또 더블 모니터를 활용하면 한쪽에 문제 한쪽에 코딩을 할 수 있어서 확실히 더 효율이 높은 것 같다.

문제번호

배운 사항 & 실수 한 부분

10951

cin.eof()를 활용하는 방법 https://takeknowledge.tistory.com/20

[주어진 테스트 케이스의 갯수 없이 입력이 끝날 때 까지 받아야 하는경우, cin.eof()==true 일때 까지 받아준다.]

string 입,출력 https://giantpark197cm.tistory.com/142

[string s; getline(cin, s);]

11720

char를 받은 후, char값에서 -’0’을 처리해줘서 값을 만들기.

11721

strlen(char) 활용. 끝이 null 값이라 배열의 길이와 상관 없이 활용할 수 있다.

대신 #include <string.h>

2741

for문을 활용해서 입,출력을 사용할 때 주의 사항

단순히 cin과 cout을 활용하면 입,출력 시간이 오래 걸리기 때문에 scanf나 printf를 활용하자. 또한 endl; 대신 개행문자(\n)을 활용하자.

혹은 https://www.acmicpc.net/problem/15552 를 참조하면, cin과 cout은 서로 입,출력이 묶여있어서 cin.tie(NULL)을 활용하면 입, 출력을 따로 활용 가능하여 속도 향상이 가능하다고 한다.

1924


scanf 로 두개의 변수를 받으려 할 때, scanf(%d, %d) 이런식으로 중간에 ,를 넣으면 안된다.

scanf(“%d %d”, &a, &,b);

10818

int형의 범위는 -21억~21억 정도로 생각하자. 숫자를 쉽게 읽기 위해서는 (1억은 0이 8개)

100,000,000 = 1억

2445

for loop의 variable scope 는 for loop 내부만.

2522

별찍기, 중심을 기준으로 대칭 구조는 for문의 step 을 활용해서 step 이 N일때 -1로 바꾸어 주면 쉽다.

2446

scanf()에는 뒤에 꼭 주소값을 넣어줘야 한다! “&a”

 

그럼 다음은 DP 이론을 정리하고, DP 문제를 풀어 봐야겠다.

DP - 1463, 11726, 11727, 9095, 10844, 11057, 2193, 9465, 2156, 11053, 11055, 11722, 11054, 1912, 2579, 1699, 2133, 9461, 2225, 2011, 11052

 

 

Reference : https://plzrun.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4PS-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

프로세스와 스레드의 차이를 어렴풋이 알고 있었는데, 좀 더 자세히 정리하고 싶어서 글을 쓴다.

"프로세스와 스레드의 차이에 대해 설명해 주세요"

- 프로세스는 운영체제로 부터 자원을 할당 받는 작업의 단위이고, 스레드는 할당 받은 자원을 활용하는 실행의 단위 입니다. (참조글 인용)

그럼 위에서 말하는 할당 받는 자원은 무엇인가요?

- CPU 시간, 운영되기 위해 필요한 주소 공간, 독립 메모리 영역(Code, Data, Stack, Heap)

멀티 프로세싱과 멀티 스레딩의 차이는 무엇인가요? 

- 멀티 프로세싱을 하게 되면 프로세스에 할당 해야 하는 자원의 양도 많아지고 자원 할당을 위한 시스템콜 이 많아져 , 하나의 프로세스에서 멀티 스레딩으로 구현하게 된다면 자원 할당도 효율적으로 처리 할 수 있으며, 하나의 자원을 여러 스레드가 활용해야 한다면 스레드간의 통신 비용이 프로세스간의 통신 비용보다 낮기 때문에 효율적입니다. 하지만, 그만큼 프로그래머의 주의를 요구합니다.

무슨 주의가 필요하고, 멀티 스레딩의 단점은 무엇인가요?

- 일단, 동기화 문제 때문에 디버깅이 까다롭습니다. 또한 하나의 스레드의 문제가 생기면 전체에 영향을 줍니다. 추가로, 하나의 자원을 공유할 때 병목 현상이 발생할 수 있습니다. 

병목현상을 막기 위해선 어떻게 해야 할까요?

- 과도한 Lock의 사용을 자제하고, 적절한 범위의 Lock을 걸 수 있도록 해야 합니다.

스레드가 프로세스로 부터 할당 받는 자원은 무엇인가요?

- 같은 프로세스안의 여러 스레드들은 프로세스의 자원(code, data, stack, heap)중에 개별 Stack을 할당 받습니다. 나머지 영역은 다른 스레드와 공유합니다.

 

 

 

[참조]

정리에 참조한 글 1 (OS 전공 지식 글)

 

[OS] 프로세스와 스레드의 차이 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

정리에 참조한 글 2 (고차원 적인 글)

 

프로세스와 스레드의 차이

기술 면접 단골손님 feat. 운영체제 | 프로세스와 스레드에 대해서 설명해주세요. 익숙한 질문입니다. 신입 개발자 면접 질문 목록에 빠지지 않고 등장하는 질문인데요. 아무리 쉽고 익숙한 질문도 머릿속에서만 생각한다면 질문을 받았을 때 말로 표현하기 힘듭니다. 때문에 이 포스팅을 작성합니다. 프로세스는 운영체제로부터 자원을 할당받는 작업의 단위이고 스레드는 프로세스가 할당받은 자원을 이용하는 실행

brunch.co.kr

 

Data Structure

새로운 개념이나 분야를 공부해 나갈 처음으로 해야 일은무엇을 모르는지 아는 이다. 요즘같은 영상 시대에서 주로 사용하는 방법은 유튜브에 정리된 개요 강의가 있는지 확인하는 것이다. 10분이라는 짧은 시간동안 자료구조가 무엇인지 ~ 어떤 자료구조들이 있고 있는지를 알려주는 좋은 개요 강의를 찾아서 요약해 보려 한다.아래의 80%는 해당 유튜브 영상을 참조하여 정리한 것이다.

 

자료구조?

상황에 맞게 데이터를 읽고 불러오기 쉽게 정리할 있는 데이터의 구조

 

1. 배열 (Array)

다른 말로는 리스트나 벡터로 불린다. 메모리에 저장된 연속된 . 배열에 저장되어 있는 값을 찾기 위해 우리는 “INDEX” 알아야 한다.

배열이 메모리에 저장되는 방식

 

 

 

2. 문자열(STRING)

문자나 숫자, 특수문자 등으로 만들어진 배열.

문자열이 메모리에 저장되는 방식, 마지막 (zero)가 NULL을 뜻한다.

STRING 메모리 저장 방식은 기본적으로 배열과 동일하지만 마지막 문자 뒤에 Null Caracter Binary 0 넣는다. (-어디서 끝나는지 알기 위함.)

 

3. MATRIX

배열의 배열, 스프레드 시트의 표나 화면의 픽셀 처럼 2차원적으로 확장된 자료를 다루고 싶을 사용. 

 

[ARRAY, STRING, MATRIX] 각각 하나의 숫자나 문자를 저장하는 구조.

하지만 연관있는 데이터를 연결시켜 저장하고 싶을 때가 있다. 예를들면 아이디와 비밀번호처럼 이때 활용할 있는 자료구조들이 아래와 같은 것들이다.

 

4. 구조체 Struct

단일 숫자가 아닌 여러 데이터를 번에 저장 가능한 복합 데이터로 변수

구조체는 이처럼 서로 연관있는 변수를 함께 저장할 때 쓰인다.

STRUCT 배열을 만들 있다. 구조체 배열은 크기가 고정되고 확장이 불가능 하다. 특성 때문에 새로운 값을 중간에 넣기가 힘들다. ( 찼을 경우) 하지만 이런 STRUCT 구조를 활용해서 유연한 데이터 구조를 만들 있다. (=> NODE)

 

5. 노드(NODE)

NODE 변수임과 동시에 포인터(메모리 주소를 가리키는 변수)이다.

많은 자료 구조의 기본인 노드는 변수하나와 포인터(들)을 가지고 있다.

NODE 여러개 저장 있는 유연한 구조체가 바로 LINKED LIST이다.

 

6. LINKED LIST

여러개의 NODE 저장하는 데이터 구조. 크기를 미리 지정해야 하는 Array 달리 링크드리스트는 단순히 포인터를 이동시키는 만으로 상황에 따라서 크기를 늘리고 줄일 있다.

링크드리스트를 추상화해 보여주는 사진, 위는 연결되어 Circular한 Linked List이다.
위처럼 포인터의 재 연결만으로 링크드 리스트는 쉽게 데이터를 추가, 삭제 할 수 있다.

여러개의 NODE 저장하는 데이터 구조. 크기를 미리 지정해야 하는 Array 달리 링크드리스트는 단순히 포인터를 이동시키는 만으로 상황에 따라서 크기를 늘리고 줄일 있다.

LINKED LIST가 저장되는 방식

위의 메모리 구조는 LINKED LIST 유연함을 나타냈는데, 포인터의 이동을 보면 1008- 1002 - 1000으로 이동하며 “CIRCULAR” LINKED LIST임을 있다.

다음 포인터의 값을 0(NULL)으로 해주면 연결 리스트를 끝낼 있다.

 유연성(단순히 포인터의 이동으로 추가, 삭제가 쉬운) 가졌기 때문에 순서를 바꾸거나, Trim(쪼개기), Reverse(뒤집기) 쉽게 있고 이로인해 복잡한 자료 구조들이 LINKED LIST 활용해서 구축되었다. 가장 보편적인 것이 "Queue Stack"이다.

 

7. 큐(Queue) : First In First Out

 먼저 사람이 먼저 나간다. FIFO 구조를 가진 Queue 링크드리스트로 만들어진다. 나가는 것을 어떻게 구현할까? 단순히 앞의 제일 앞의 포인터를 한칸 뒤로 이동 시키면 된다.  

Queue를 추상화 해서 설명하는 그림

위의 그림에서 Dequeue(먼저 온 사람인 HANK를 뺀다면)하기 위해서는 아래처럼 포인터를 하나 옮겨주면 된다.

Dequeue 후의 모습

위의 그림에서 새로운 사람이 추가된다면? INQUEUE

Inqueue후의 모습

 

8. 스택 : Last In First Out (LIFO)

마지막으로 들어온 사람이 먼저 나가려면 어떤 구조를 만들어야 할까? 자료구조가 LIFO, STACK이다.

Stack에서 나가는 것은 POP , 들어오는 것은 PUSH 부른다.

 

9. 트리(Tree)

Node에서 포인터를 두개 갖기만 한다면 Tree 구조를 만들 있다.

트리 노드의 구조체
값이 저장되어 있는 트리를 추상화한 그림

 트 구조에서 제일 위의 Node 루트(Root)라고 하며, 제일 아래 Node 리프(Leaf) 하며, 노드(A) 밑의 노드들은(A) 자식 노드(Children Node)라고 한다. 반대의 경우는 부모 노드(Parent Node)라고 한다.

노드의 자식 노드의 개수는 제한이 없지만, 위의 그림 예시처럼 노드의 자식이 두개까지인 것을 Binary Tree라고 한다.

트리의 하나의 특징은 뿌리에서 잎까지 한가지 경로만 존재 한다는 것이다.

 

 그럼 (규칙적인 트리와 달리)서로 제멋대로 연결되어 있는 자료들은 어떻게 표현할까? 그걸 표현하기 위한 구조가 아래의그래프(Graph)”구조이다.

 

10. 그래프(Graph)

 

그래프를 추상화한 모습

 위에서 설명한 처럼 제멋대로 연결되어 있는(리프, 루트, 부모, 자식의 개념이 없는) 자료 구조를 그래프로 표현 있다.

  이는 단순히 하나의 노드에서 여러개의 포인터를 포함시킴으로써 만들 있다. 

 

 

위의 구조 아니라 다양한 변형된 구조(Heap, Red Black Tree) 있으나 영상에선 다루지 않았다. 하지만 Heap 기본 구조중 하나이니 같이 정리한다.

 

11. Heap () : 우선순위 Queue

Max Heap예시 위로갈수록 커진다.

 최댓값 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전 이진 트리(Complete Binary Tree) 기본으로 자료 구조(Tree Based-Structure)

 Max Heap Min Heap 있는데, 그대로 Max Heap 부모 노드가 항상 자식 노드의 값보다 특징을 같고 있으며  Min Heap 반대의 특성을 갖는다.

 Insert Delete Upheap, Downheap으로 부르며 다음에 구체적으로 리하겠다.

 

 위의 내용으로 어느정도 전체적인 개요를 머릿속에 넣을 수 있었다. 자료 구조들을 직접 코딩해서 만들어야 할까? 아니다 이미 언어들에서 만들어놓은 구조들이 있는데 이게 유명한 C++ STL(Standard Template Library) / Java Java Class Library등이다. 앞으로는 각각의 자료구조에 대해 구체적으로 공부하면서 내용을 정리 해야겠다.

 

 

Epoch / Batch Size / Iteration?

Epoch : 전체 학습 데이터를 1 학습 완료 했을 1 Epoch이라고 한다
> 높은 Epoch수는 동일 학습 데이터를 여러번 학습 시키는 것이므로 Overfitting 일어날 가능성이 있으며 반대는 Undefitting 일어나기 쉽다.

Batch Size : 1번에 학습 시키는 데이터의 ,  Batch Size 20이면 20개의 데이터를 번에 학습시킨다.

> 1 Batch Size만큼 학습을 시키기 위한 Loss, Back Propagation 등이 일어나므로 Batch Size 작게 잡으면 학습은 빠르게 일어날 있지만 부정확한 학습을 있음 : Outlier들을 다른 샘플 데이터들과 함께 학습시키지 않고 개별 Propagation 시키는 일들이 많이 발생할 것이므로.

> 가용 메모리가 적고, 학습을 빠르게 시켜야 한다 : Batch Size 상대적으로 작게

> 가용 메모리가 크고, 학습의 안정화를 시켜야 한다 : Batch Size 상대적으로 크게

 

Iteration : 1 Epoch Data / Batch Size

> 2000개의 Data, 20 Batch Size라면 100 Iteration

 

 

Generalization / Regularization / Standardization / Normalization?

Generalization

  • 한정된 정보로 세상에 편견을 가지는 것을 막기 위함. 
  • 딥러닝이나 머신 러닝은 한정된 데이터(샘플 데이터) 학습하여 똑똑한 모델(기계) 만드는 것이 목표. 한정된 데이터가 학습하려는 대상의 특징을 적절히 반영하고 있다면 다행이지만 그렇지 않은 경우가 많음.(Outlier들을 포함).
  • 데이터의 양을 늘리는 것이 제일 좋은 방법이지만 양질의 데이터를 구하기 쉽지 않고 / 학습 시간이 오래 걸림.
  • 이런 Generalization 하기 위해 다양한 방식(ex Regularization) 도입함.

Regularization : Overfitting 막는 방법(= Generalization 시키기 위함)

  • 기계학습의 방향은 Cost Function이나 Error Function 작아지는 방향.
  • 하지만 단순히 최대한 작은 Error 만들기 위해서 Error Function Sample Data 맞게 구한다면 Overfitting 발생하며, Outlier들까지 케어 하기 위해 특정 Weight값이 커지는 현상이 발생한다.
  • 이렇게 특정 Weight값이 너무 커지는 것을 방지하기 위해 Cost Function 변화시켜 Weight값이 작아지는 방향(weight decay)으로 학습 시키도록 한다.

> L1 Regularization / L2 Regularization

L1 : Cost function 뒤에 1차항을 붙여 Weight 대한 편미분식이 W 감소 시키도록 만듬.
- 상수단위로 작아지기 때문에 중요한 Weight 말고는 0 가까이 . 몇개의 의미있는 값을 끌어내기 위해서는 이를 선택.

L2 : Cost function 뒤에 2차항을 붙여 편미분 식이 W 감소 시키도록 만듬

 

Feature Scaling (Normalization, Standardization)

Data Normalization혹은 Feature Scaling으로 불리는 작업은 변수의 범위를 일정하게 하여 비교 가능하게 하고, Outlier들의 값들을 상쇄 시켜 주는 역할을 한다.

Standardization : 변수에서 평균을 빼고 표준편차로 나누는 . 속성들을 같은 분포를 따를 있도록 .

Normalization : 아래와 같이 변수의 범위를 0~1 제한 시키는 방식이나 log 취해 값을 줄이는 방식등이 있다.

 

Loss function 종류

1. 평균 제곱 오차 ( MSE : Mean Squared Error )

MSE

(Yk 실제 , Tk 신경망에서 내놓은 분류 결과)

2. 교차 엔트로피 에러 ( CEE : Cross- Entropy Error )

CEE

 예측이 틀렸을 경우 - tk[1,0,0] / yk[0,1,0] 경우 : inifinity 

 예측이 정확히 맞았을 경우 - tk[1,0,0] / yk [1,0,0] 경우 : 0

 

Supervised Learning / Unsupervised Learning / Reinforcement Learning

Supervised Learning(지도학습)?

Labeled Data 학습하는 방식.

> CNN, RNN등을 활용, Continuous Regression / Discrete Classification

 

Unsupervised Learning(비지도학습)?

Unlabeled Data 학습하는 방식.

> Clustering, AutoEncoder

 

Reinforcement Learning(강화학습)?

에이전트(Agent) 주어진 상황(State)에서 특정 행동(Action) 하고 보상(Reward) 받으며 학습하는 방식. 에이전트가 최대의 보상(Max Reward) 받을 있도록 학습한다.

> Q - Learning, Deep-Q-Network

 

 

BackPropagation, Gradient Descent

Backpropagation

딥러닝에서 오차를 줄이기 위해서 Weight값과 Bias값을 조정해야 하는데, BackPropagation은 결과값을 통해 조절되어져야 할 Weight값을 각 weight로 전달하는 과정이라 볼 수 있다.

Gradient Descent

오차를 줄이기 위해 w 각각 얼마나 어떤 방향으로 조절되어야 하는가? 이걸 오차 함수의 기울기(=Gradient) 판단하고 Gradient만큼 이동하는걸 Gradient Descent라고 한다.

 

 

Neural Network의 기본 이해

참고한 비디오 : https://www.youtube.com/watch?v=aircAruvnKk

신경망이란 무엇인가? 딥러닝에 대해서

 

AI의 Hello World인 MNIST데이터를 기본으로 정리하자.

[Neural Network] Neuron + Network

MNIST(0~9까지 적힌 손글씨 데이터)를 넣으면 원하는 출력값이 나오는 네트워크

 

Neuron : Things that hold a number ( 0.0~1.0의 수를 담는 공간, 0.37 같은 해당 숫자는 Activation이라 부른다. )입력레이어 - 히든 레이어 - 출력 레이어로 구성된다.

Input Layer는 28X28 픽셀의 밝기값을 벡터에 담아 펼친 값

- MNIST데이터는 28*28 의 픽셀로 이뤄진 이미지 데이터이므로, 첫 레이어를 784로 잡는다.

- 연결 : 이를 다음 레이어에 연결할 때 Weight를 곱하고 Bias를 더해 다음 출력값을 연결한다.

EX) H1 = (W1)X(I1) + bias1 + (W2)(I2) + Bias2 + ..... (W784)(I784) + Bias 784

Hidden Layer : 임의의 값으로 히든 레이어를 둘 수 있고, 히든 레이어의 뉴런의 개수도 임의로 둘 수 있다.

Output Layer : 우리의 목적이 0~9의 손글씨 데이터를 구별해 내는 것이므로 0~9까지 10개의 output레이어를 둔다. 각각의 Output 값은 손글씨 데이터가 해당 숫자일 확률을 나타낸다.

뉴럴네트워크에서 우리가 학습해야 하는 것?

Weight와 Bias의 값.

어떻게 학습할 수 있는지?

Gradient Decent / BackPropagation 

 

인공지능 분야에서 최근 정말 핫한 GAN에 대해서 추상적으로는 이해하는 것은 어렵지 않지만, 딥하게 이해하기 위해 글을 작성한다.

(앞으로 공부를 하면서 주의 하려고 하는 점은, 개념적인 것과 코드 사이의 관계를 보고 실습을 진행하는 것을 목표로 하려 한다. 매번 개념적인 부분만 학습하다 보니 뜬구름 잡는 느낌이 있고 내가 만들 수 있을까? 하는 생각이 걱정으로 바뀌는 것 같아서.)

본문은 dreamgonfly님의 쉽게 씌어진 GAN을 제가 이해하기 편하게 정리한 글입니다.

- 원문 쉽게씌어진 GAN :  https://dreamgonfly.github.io/2018/03/17/gan-explained.html

흐름 정리를 위해 추가로 사용한 글 : https://pathmind.com/kr/wiki/generative-adversarial-network-gan

 

생성적 적대 신경망(GANs)에 대한 초보자용 가이드 (GANs)

생성적 적대 신경망(GANs)이란 한 네트워크가 다른 네트워크와 겨루는 두개의 네트워크로 구성된 심층 신경망 구조이다.

pathmind.com

 

GAN의 기본 개념

 

Generator 와 Discriminator가 서로 상호 작용하며 점점 발전하는 네트워크라고 할 수 있다.

출처 : wiki 내 이미지 (from O'Relly)

1. Generator의 목적은 현실과 비슷한 지폐(위조지폐, 이미지)를 만들고

2. Discriminator는 해당 지폐가 위조 지폐인지 진짜 지폐인지(0 OR 1) 구분해 내는 것이다.

3. Generator는 생성된 이미지를 통해 Discriminator를 속이려 하고 Discriminator는 정확히 구분하려고 하는 과정을 통해 진짜에 가까운 이미지를 만들어 낼 수 있게 된다.

위 세개로 나누어 공부해 보자.

 

1. Generator의 목적은 다양할 수 있지만, 예를들어 현실과 비슷한 지폐(위조지폐, 이미지)를 만든다.

1의 과정은 정확히는 Noise Vector(부분 정보, 실제로는 랜덤 값)를 받아 이를 Up-Sampling(실제 이미지 처럼 변경)하여 이미지를 만드는 것이다. 이 부분 부터 코드적 관점과 현실적 관점에서 살펴보자.

참조 글 :

Image Completion in Deep Learning : https://bamos.github.io/2016/08/09/deep-completion/#step-1-interpreting-images-as-samples-from-a-probability-distribution

 

Image Completion with Deep Learning in TensorFlow

Image Completion with Deep Learning in TensorFlow August 9, 2016 Introduction Content-aware fill is a powerful tool designers and photographers use to fill in unwanted or missing parts of images. Image completion and inpainting are closely related technolo

bamos.github.io

쉽게 씌어진 GAN : https://dreamgonfly.github.io/2018/03/17/gan-explained.html

 

쉽게 씌어진 GAN

이 글은 마이크로소프트웨어 391호 인공지능의 체크포인트(THE CHECKPOINT OF AI)에 ‘쉽게 쓰이는 GAN’이라는 제목으로 기고된 글입니다. 블로그에는 이 글의 원제이자 윤동주 시인의 ‘쉽게 씌어진 시’를 따라 지어진 제목인 ‘쉽게 씌어진 GAN’으로 포스팅합니다.

dreamgonfly.github.io

 

[ 현실적 관점 ]

어떻게 Noise Vector를 통해서 이미지를 만드는 것일까?

몇개 안되는 정보(Noise Vector)를 통해서 원래의 이미지를 만들어 내려면 정보 추론이 필요한데 그것이 1) Contextual Information 과 2) Perceptual Information 이다.

1) Contextual Information : 추론하고 싶은 픽셀의 주변 픽셀을 통해 해당 픽셀의 값을 추론한다. 맥락적 정보.

2) Perceptual Information : 이미지가 현실적이기 위해선 해당 픽셀의 값이 어떻게 되어야 하는지 추론한다. 쉽게 말하면 '그럴듯 하게'이다.  이를 위해선 기존에 학습되어 있어야 한다. AI가 만든 고양이 사진을 우리가 이상하다고 생각하는 것은 이 인지적 정보를 통해서 파악하는 것이다. 인지적 정보

인간은 위의 두가지 정보를 쉽게 알 수 있지만, 어떻게 그걸 알아내고 있는지 알고리즘으로 표현할 방법이 없다. 아직까지는 이를 제일 잘 해내고 있는 것이 통계와 머신러닝을 활용하는 방법이다.

 

[ 코드 관점 ]

Noise Vector는 무엇일까? 어떤 기준으로 만드는 것일까?

Noise Vector는 관용적으로 변수 Z 에 생성, 저장하여 활용하는데 이는 단순하게 균등분포(Uniform Distribution)이나 정규분포(Normal Distribution)에서 무작위로 추출된 값이다. Z 벡터가 존재하는 공간을 잠재공간(Latent Space)라고도 한다.

1
2
3
4
5
6
7
 
#pytorch로 코드를 작성한다면 z값은 단순히 이런 느낌
= Variable(torch.randn((batch_size, 100)))
 
#위에서 생성한 z값을 Generator에 넣어준다.
Generator(z)
 
Colored by Color Scripter

1) Z의 크기는 어떻게 정하는 것이지?

- 임의로 결정하는 것이다. 하지만 Z의 값을 통해서 이를 이미지로 만드는 것이기 때문에, 이미지의 특성을 충분히 담을 수 있을 정도의 크기여야 한다. 위 코드에서는 100차원으로 잡았다.

2) 위에서 '충분한'을 어떻게 알 수 있고, Z벡터의 의미를 어떻게 받아들여야 하지?

- 알기 힘들다. 일반적인 GAN에서는 Z의 의미를 쉽게 이해하기 힘들고 이 부분을 보완한 변형된 GAN중 하나가 DC GAN이다. Z의 특정 성분이 이미지의 특징(예를들면 얼굴의 모양 계란형 / 동그란 얼굴 / 네모 얼굴)등을 담당할 텐데 기존 GAN에서는 해당 성분을 찾을 수 없었다면 DCGAN에서 해당 성분을 찾아낼 수 있도록 하였다.

 

Generator : Noise Vector를 어떻게 Upsampling 하여 이미지를 만드는 것일까? (본문의 모든 코드는 Dreamgonfly님의 github 코드)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#Generator Class
#인공지능 세계의 Hello World라 불리는 MNIST 데이터셋을 활용하여 이미지 생성을 하는 GAN을 만드는 주제
class Generator(nn.Module):
 
    # 네트워크 구조, 100차원의 Noise Vector를 - 256 - 512 - 1024 - 28*28 순서로 변형시켜 Up sampling 함.
# 마지막에서는 Tanh를 사용하여 -1부터 1사이의 픽셀 값으로 내보낸다.
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(in_features=100, out_features=256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(in_features=256, out_features=512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(in_features=512, out_features=1024),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(in_features=1024, out_features=28*28),
            nn.Tanh())
    
    # (batch_size x 100) 크기의 랜덤 벡터를 받아 
    # 이미지를 (batch_size x 1 x 28 x 28) 크기로 출력한다.
    def forward(self, inputs):
        return self.main(inputs).view(-112828)
 
Colored by Color Scripter

- 위 코드를 보면 Generator도 결국 하나의 Network일 뿐이다. 100차원의 Noise Vector를 받아서 이를 여러 단계의 Layer를 통해 충분한 매개변수를 붙여주고 마지막에 이미지 형태로 내보내기 위해 28 X 28 사이즈로 출력한다.

 

2. Discriminator는 해당 지폐가 위조 지폐인지 진짜 지폐인지(0 OR 1) 구분해 내는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# <코드3> GAN의 구분자(Discriminator)
 
# 구분자는 이미지를 입력으로 받아 이미지가 진짜인지 가짜인지 출력한다.
class Discriminator(nn.Module):
    
# 네트워크 구조
def __init__(self):
    super(Discriminator, self).__init__()
    self.main = nn.Sequential(
      nn.Linear(in_features=28*28, out_features=1024),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=1024, out_features=512),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=512, out_features=256),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=256, out_features=1),
      nn.Sigmoid())
    
  # (batch_size x 1 x 28 x 28) 크기의 이미지를 받아
  # 이미지가 진짜일 확률을 0~1 사이로 출력한다.
   def forward(self, inputs):
    inputs = inputs.view(-128*28)
    return self.main(inputs)
 
Colored by Color Scripter

- 위 코드의 네트워크 구조를 보면 이미지 형태 28*28을 받아 마지막 Sigmoid 함수를 거쳐 해당 이미지가 진짜일 확률을 0~1사이값으로 반환한다.

 

3. Generator는 생성된 이미지를 통해 Discriminator를 속이려 하고 Discriminator는 정확히 구분하려고 하는 과정을 통해 진짜에 가까운 이미지를 만들어 낼 수 있게 된다.

 

3-1. 먼저 Discriminator가 Real Image를 잘 맞출 수 있도록 학습시킨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  # 진짜 이미지를 구분자에 넣는다.
 D_result_from_real = D(real_data)
# 구분자의 출력값이 정답지인 1에서 멀수록 loss가 높아진다. criterion은 BCELoss Function.
 D_loss_real = criterion(D_result_from_real, target_real)
 
# 생성자에 입력으로 줄 랜덤 벡터 z를 만든다.
z = Variable(torch.randn((batch_size, 100)))
        
if use_gpu:
 z = z.cuda()
            
# 생성자로 가짜 이미지를 생성한다.
 fake_data = G(z)
        
# 생성자가 만든 가짜 이미지를 구분자에 넣는다.
 D_result_from_fake = D(fake_data)
# 구분자의 출력값이 정답지인 0에서 멀수록 loss가 높아진다.
 D_loss_fake = criterion(D_result_from_fake, target_fake)
        
# 구분자의 loss는 두 문제에서 계산된 loss의 합이다.
 D_loss = D_loss_real + D_loss_fake
        
# 구분자의 매개 변수의 미분값을 0으로 초기화한다.
 D.zero_grad()
# 역전파를 통해 매개 변수의 loss에 대한 미분값을 계산한다.
 D_loss.backward()
# 최적화 기법을 이용해 구분자의 매개 변수를 업데이트한다.
 
Colored by Color Scripter

- D_loss_real : Real Data를 Discriminator에 넣으면 나와야 하는 값인 target_real(1로 이뤄진 tensor)과 비교하여 Loss를 계산한다.

- D_loss_fake : Fake Data를 Discriminator에 넣으면 나와야 하는 값인 target_fake(0으로 이뤄진 tensor)과 비교하여 Loss를 계산한다.

- Loss.backward()를 통해서 미분값을 역 전파하고, 해당 미분값을 통해 optimizer.step()을 활용해 Learning Rate와 비례해 값을 업데이트한다.

 

3-2. Generator는 Discriminator를 속일 수 있도록 학습해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# <코드8> 생성자 학습시키기
 
# 생성자에 입력으로 줄 랜덤 벡터 z를 만든다.
= Variable(torch.randn((batch_size, 100)))
= z.cuda()
 
# 생성자로 가짜 이미지를 생성한다.
fake_data = G(z)
 
# 생성자가 만든 가짜 이미지를 구분자에 넣는다.
D_result_from_fake = D(fake_data)
 
# 생성자의 입장에서 구분자의 출력값이 1에서 멀수록 loss가 높아진다.
G_loss = criterion(D_result_from_fake, target_real)
 
# 생성자의 매개 변수의 미분값을 0으로 초기화한다.
G.zero_grad()
 
# 역전파를 통해 매개 변수의 loss에 대한 미분값을 계산한다.
G_loss.backward()
 
# 최적화 기법을 이용해 생성자의 매개 변수를 업데이트한다.
 
Colored by Color Scripter

- Noise Vector인 z를 활용해 Generator로 새로운 이미지를 만들고, 해당 이미지를 D에 넣어 나온 값이 1이 나와야 목적을 달성하는 것이다.

- Loss를 (D(G(z)), 1)끼리 비교한다. G(z)는 생성된 Fake Image / D(G(z)) 는 Fake Image가 Discriminator입장에서 얼마나 Real한지를 반환.

- 마찬가지로 Backward로 미분값을 계산하고, Step()을 통해 Learning Rate와 비례해 매개변수들을 업데이트 한다.

 

위의 과정을 반복하여 학습하면서 Real한 이미지를 만들기 위한 Parameter값을 찾게 되는 구조가 GAN이다.

 

* 번외 - Pytorch에서 Backward()와 Step()

Backward()를 하면 각 Parameter에 Gradient값을 저장해 줌.

Step()을 하면 Parameter에 저장된 Gradient에 Learning Rate를 곱해서 Parameter를 업데이트 함.

 

 

GAN의 한계

 

GAN의 개념은 혁신적이지만 고질적인 문제가 있었다. GAN을 학습 시키는 것이 어렵다는 것이다. 그 이유는 여러가지이지만 '쉽게 씌어진 GAN'에서는 다음 세개를 얘기한다.

1. G 와 D가 함께 비슷한 속도로 성장을 해야 한다.

2. Mode Collapse현상 : 생성자가 계속해서 비슷한 이미지만 만들어 내는 상황

3. 텍스트 같은 표현이 불연속적인(실수가 아닌) 것들을 학습하기가 어려움

 

위 GAN의 한계를 극복하기 위해 시도된 것 중 큰 발전을 만든 GAN이 바로 DCGAN이다.

 

DCGAN(Deep Convolutional GAN)

 

DCGAN의 가장 큰 특징은 좋은 최적화 기법을 알아낸 것과 적절한 학습 속도를 알아낸 것이다.

 

아래는 여러 DCGAN의 특징이다.

1. 이미지의 위치 정보를 잃게 만드는 선형 Layer와 Pooling Layer 대신, Convolution과 Transposed Convolution을 사용함. 

(Pooling Layer, 예를들면 Max pool은 이미지의 특정 정보만 활용하여 특징을 잘 잡아주긴 한다.)

2. 배치 정규화(Batch Normalize)를 시켜준다. 평균과 분산을 활용해서 입력 데이터 분포를 다듬는다.

- 이는 역 전파를 레이어에 쉽게 전달 할 수 있도록 한다.

3. 모든 레이어에 ReLU, LeakyReLU를 활용한다.

4. 잠재공간에 데이터 특성이 투영됐는지 살펴보는 것. 

 

[ 코드 ]

- 코드에서는 기존 GAN과 대부분 동일하고, 구분자와 생성자 Layer들에 Transposed Convolution 적용, 배치 정규화, ReLU/LeakyReLU 활용만 넣어주면 된다.

 

 

cGAN(Conditional GAN)

 

기존 GAN은 이미지를 생성하는 반면, cGAN은 이미 있는 이미지를 다른 영역의 이미지로 변형할 때 사용한다.

이를 위해 당연히 cGAN의 생성자에서는 잠재 공간을 받기 보다 변형할 이미지를 입력으로 받고, 구분자는 변형 이미지가 올바르게 변형 되었는지(그럴듯 한지와 변형할 이미지를 적절히 변경 했는지)

EX) 스케치에 채색, 흑백 사진 컬러, 낮->밤

1. 기존 GAN에서는 하나의 이미지 도메인 내의 움직임이었다면, cGAN은 그걸 극복했다.

 

 

다양한 GAN의 종류

WGAN(Wassertein GAN) : 실제 데이터와 생성 데이터의 분포가 얼마나 다른지 측정하는 거리 개념을 변경해 안정적 학습을 하게 만듬.

EBGAN(Energy-based GAN) : GAN을 에너지 관점에서 바라봐서 안정적 학습.

BEGAN(Boundary Equilibrium GAN) : WGAN / EBGAN 을 발전 시켜 이미지 퀄리티을 높이고, 다양성을 컨트롤 할 수 있게 함.

CycleGAN / DiscoGAN : 매칭 데이터 셋이 없어도 이미지 변형을 가능하게 함 ( cGAN 의 업그레이드 )

StarGAN : 아이디어를 확장 시켜 세 개 이상의 이미지 변형을 시도함.

SRGAN(Super-Resolution GAN) : 사진 해상도를 높임

SEGAN(Speech Enhancement GAN) : 음성 녹음의 노이즈를 줄여 줌.

 

 

+ Recent posts