LeeCreation! Media & Robot  
Front Page
Tag | Location | Media | Guestbook | Admin   
 
'Etc./연구관련'에 해당하는 글(50)
2018.07.14   쌩(?!)초보자의 Python 케라스(Keras) GAN 코드 분석 (draft)
2018.07.11   [요약 번역] 케라스(Keras)로 구현하는 오토인코더(AutoEncoder)
2018.07.10   Keras 참고자료
2018.06.15   [번역] 진정한 쌍방향 상호작용(Interactivity)은 인공지능(AI)을 필요로 한다
2017.01.24   utf-8 <-> CString


쌩(?!)초보자의 Python 케라스(Keras) GAN 코드 분석 (draft)

본 글은 [1]의 영어 원문에 나온 코드를 이해하기 위해 공부 용으로 정리한 글이다. 원래는 번역도 함께 진행하려고 했는데, 이미 번역본[2]이 있어서 이 글은 코드에 대한 분석만 진행한다.

[1] 원문 https://towardsdatascience.com/gan-by-example-using-keras-on-tensorflow-backend-1a6d515a60d0

[2] 한글 번역 https://brunch.co.kr/@rapaellee/18


전체 코드(약 200줄)

https://github.com/roatienza/Deep-Learning-Experiments/blob/master/Experiments/Tensorflow/GAN/dcgan_mnist.py


*주의: 본문은 아주아주 기초적인 코드 내용 분석까지 시도하기 때문에 기본적인 것들을 이미 아시는 분들은 지루하실 수 있습니다. 딥러닝 자체는 말할 것도 없고 개인적으로 케라스는 몇주 전에 설치했고, 파이썬 자체도 아주 잘 알고 있는 게 아니기 때문에, 파라미터를 하나하나 뜯어내서 공부했습니다.


  • Discriminator 정의 함수



우선 구조를 보고, 몇가지 생각해보자. 28x28 크기의 gray 이미지를 2D convolution을 이용해 각 단계를 거칠 때마다 해상도는 가로 절반, 세로 절반씩 줄어드는데, 깊이(? 코드 상에서 depth라고 정의해서 쓰고 있다)는 64부터 128, 256, 512로 두배씩 증가하고 있다. 아 그러고 보니 세번째에서 네번째는 해상도는 그대로네. 그리고 마지막에 바로 시그모이드 함수로 확률 값을 얻어낸단다. 왜 이런 구조를 생각해냈을까... 코드를 다 공부해보고 다시 질문해보자. 어쩌면 코드 말고 논문을 봐야 할지도 모르겠다. 그리고 마지막에 4x4x512 차원에서 어떻게 1차원으로 넘어갈 수 있는지 궁금하다. 코드를 보면 알겠지. 다른 CNN 코드를 보면 마지막에 fully connected layer를 쓰는데 그걸 안 쓰는 걸까. 아.. 뒤에 코드를 더 보고 알았다. 4x4x512 차원을 Flatten 함수로 8,192길이의 1차원으로 만든 후에 그걸 8,192 → 1로 만드는 fully connected(? 이것도 이렇게 부르는 게 맞나 모르겠는데 틀린 말도 아닌듯)가 있었다. 그림에 딱히 이걸 표현 안해 놓은듯.


이제 코드를 하나씩 보자. (진짜 한 줄씩 볼 예정임)


참고로 전체 코드(약 200줄)는 아래 링크에서 볼 수 있다.

https://github.com/roatienza/Deep-Learning-Experiments/blob/master/Experiments/Tensorflow/GAN/dcgan_mnist.py


지금부터 설명하는 코드는 Discriminator model을 만들어 주는 함수의 코드다.

self.D = Sequential()


시작부터 self.D라는 이상한 놈이 나타났다. python에서 self에 대한 것이 궁금하다면 아래 링크 [3]을 참고하기로 하자. 저 self가 어디서부터 왔고. self에 속한 D는 뭘 의미하는지 알아보자.

[3] https://wikidocs.net/1742


일단 이 코드는 discriminator라는 함수 안에 있는 코드로 self는 이 함수를 맴버함수로 가지고 있는 class를 가리키는 것일 것이다. 전체 코드를 확인해보면 이 class는 DCGAN이라는 이름의 클래스이고, 거기에 있는 맴버 변수 D는 DCGAN class에서 discriminator를 가리키는 변수로 정의되어 있다. 그러니깐 DCGAN이라는 class에서 사용할 discriminator model의 이름을 discriminator라는 함수 내에서 사용하기 위해 저렇게 표현되는 것이다.


그럼 이제 self.D가 discriminator model이라는 것을 알았고, Sequential()은 위 그림에서 봤던 구조를 '순서대로' 이어 붙일 것이기 때문에 사용된 코드다. Sequential을 이용해서 모델을 만드는 경우와 Functional한 방법으로 모델을 만드는 경우가 있는데 이는 링크만 남겨두고 나중에 더 공부해봐야겠다. 

(https://jovianlin.io/keras-models-sequential-vs-functional/)

*위 링크의 글을 번역하게 되어 추가로 아래에 링크를 남겨놓는다.

(http://leestation.tistory.com/777)


후... 첫줄 이해하는 것부터 이렇게 오래 걸리다니... 물론 200줄의 코드는 짧은 편이지만 이렇게 시간이 걸려서야... ㅋㅋ 그래도 어쩌겠나.. 공부해야지.


depth = 64


다음은 depth. 이건 위 그림을 보면 어떤 의미인지 알 수 있다. 그러나 나는 여전히 의문이다. 왜 64인가?! ㅋㅋ 지금 단계에서 이해하긴 어려울듯. 코드 해석이 다 끝나고 다시 한 번 퇴고하면서 설명을 보완해놔야겠다.


dropout = 0.4


overfitting을 막기 위해 dropout의 비율을 40%로 한다. dropout이 꼭 필요한 건지는 모르겠지만 해보니깐 더 좋더라... 그러니 쓰자. 뭐 이런 건데... 사공이 많으면 배가 산으로 가니깐 사공을 줄이자는 의미인데, 뭐 아주 틀린 말은 아니다.

[4] dropout과 관련된 설명 http://doorbw.tistory.com/147


input_shape = (self.img_rows, self.img_cols, self.channel)


입력 이미지는 DCGAN class에 img_rows와 img_cols에 정의된 대로 28x28의 해상도이고, gray 이미지라서 channel은 1이다. 곧 input의 shape은 (28, 28, 1). list 형태인 [28, 28, 1]이 아니라 tuple 형태다. 그리고 channel이 맨 마지막에 오는데, 찾아보니 별 말 없으면 data_format에 channels_last라는 옵션을 기본으로 사용하는 듯하다.

[5] list와 tuple의 차이 https://blog.naver.com/ijoos/221263485291


self.D.add(Conv2D(depth*1, 5, strides=2, input_shape=input_shape, padding='same', activation=LeakyReLU(alpha=0.2)))


드디어 본격적인 structure 추가가 시작됐다. Conv2D함수로로 만들어지는 2D convolution을 discriminator model에 추가하는 코드다. add 함수는 사실 저렇게 하면 되는 거구나... 알고 넘어가면 되는데, 뒤에 보면 Conv2D를 사용할 때 input_shape을 따로 알려주지 않는다. 즉, 처음 add하는 layer에만 input_shape을 알려주는데, 이게 아마도 자동으로 입출력 shape을 알고 매칭시켜주는 것 같다. Sequential이랑 functional 공부할 때 더 살펴봐야겠다.


이제 Conv2D 함수는 어떤 변수들을 파라메터로 받는지 확인해보자. [6]의 문서를 확인해보면, 

[6] https://keras.io/layers/convolutional/#conv2d

 

- 첫번째 파라메터는 integer 값으로 몇개의 filter를 쓸 것인가에 해당한다. 하나의 filter로 입력 이미지를 한 번 쭉 훑을 때마다 (convolution 할 때마다) 한 장의 output image가 생성되기 때문에 filter의 개수만큼 출력되는 output image가 생긴다. 이 숫자를 Channel이라고 부른다.

- 두번째 파라메터는 filter의 크기를 결정하는 건데, filter의 차원이 2D이기 때문에 (3, 3) 이런식으로 적기도 하는데 그냥 하나의 값만 적혀있으면 가로 세로 길이가 그 값으로 동일하다는 의미. 여기는 5라고 적혀있으므로 filter의 크기는 5x5가 된다. 그리고 많은 경우 3x3 filter를 쓰는데, 여기서는 strides를 2로(픽셀을 두 칸씩 건너뜀) 할 예정이기 때문에 5x5 filter를 써야 스킵되는 픽셀이 없게 된다.

- strides는 filter를 훑을 때 픽셀을 몇개씩 건너뛰며 convolution 계산을 시킬 건지 결정한다. 1이면 한칸씩 이동하는 거라서 입력과 같은 해상도의 출력이 나오고, 예제 코드에 쓰인 대로 2칸씩 이동하면 가로 세로 길이는 절반이 된다. 이거 때문에 28x28 해상도의 입력 이미지가 14x14 해상도의 출력으로 나오게 된다. 이 이유 때문에 strides를 1로 설정한 CNN 코드에서 처럼 pooling 같은 걸 따로 할 필요가 없던 것이다.

- input_shape은 앞에서 이미 설명했다.

- padding은 filter가 입력 이미지 가장자리 부분에 걸치게 될 때 빈 공간을 어떻게 채울 것인지 설정해주는 것이다. same인 걸 보니 가장자리 값을 그대로 복사해서 채우나보다. 그러고 보니 코너에서는 대각선 빈공간에는 어떤 값으로 채우나 모르겠다. 뭐 크게 중요하지는 않을듯.

- activation은 LeakyReLU를 썼는데 그냥 ReLU는 입력이 음수값일 때 그냥 0으로 만들어버리는데 얘는 입력이 음수값일 때 alpha 값을 곱해주어 아주 0으로 버리지는 않는 함수다. [7]

[7] https://keras.io/layers/advanced-activations/#leakyrelu

- 출력은 14x14x64의 차원을 갖는다. shape은 (None, 14, 14, 64) 형태. None에 해당하는 건 여러 개의 sample data를 한꺼번에 집어넣을 수 있도록 잡아둔 공간인데 (batch라고 부르기도 함), 지금은 모델을 디자인하는 단계이기 때문에 None이라고 표현했다. 


self.D.add(Dropout(dropout))


Dropout의 개념은 앞에서 설명했는데, fully connected layer에서의 dropout이 아니라 Conv2D에서의 dropout은 어떻게 적용되는 건지 궁금하다. 그냥 filter들을 하나씩 끄는 건가. 아니면 filter 내에 weight들을 따로 확률적으로 0으로 만들어 주는 건가. 나중에 더 찾아봐야겠다. 일단 기본적으로는 어떤 역할인지는 아니깐 넘어가자.


self.D.add(Conv2D(depth*2, 5, strides=2, padding='same', activation=LeakyReLU(alpha=0.2)))


두번째 2D convolution에 해당하는 코드. depth에 곱해지는 숫자가 두배가 된 것 빼고는 앞에서 다 이미 설명했다. 다만 입력의 shape에서 channel의 수가 64개가 되었기 대문에 filter의 shape은 그냥 (5, 5)가 아니라 (5, 5, 64)가 된다. 여러 channel에 대해 convolution이 어떻게 이루어지는가는 아래 링크의 영상을 보면 이해가 쉽다. 그리고 이 layer의 출력은 7x7x128의 차원을 갖는다. 출력 shape은 (None, 7, 7, 128)

https://youtu.be/KTB_OFoAQcc


self.D.add(Dropout(dropout))


또 한 번 dropout.


self.D.add(Conv2D(depth*4, 5, strides=2, padding='same', activation=LeakyReLU(alpha=0.2)))


세번째 2D convolution. 이번에는 depth에 4가 곱해졌다. 깊다... ㄷㄷ 출력은 4x4x256의 차원을 갖는다. 여기서 잠깐. 7x7이 왜 4x4가 되었는가?! 그것은 3.5x3.5가 될 수 없기 때문이다;; 가로 길이가 7인데, 1부터 시작해서 2칸씩 움직인다면, → 3 → 5 → 7 총 4칸을 거쳐가게 된다. 세로도 마찬가지. 그래서 4x4가 된다. 출력 shape은 (None, 4, 4, 256)


self.D.add(Dropout(dropout))


그리고 또 dropout.


self.D.add(Conv2D(depth*8, 5, strides=1, padding='same', activation=LeakyReLU(alpha=0.2)))


마지막 2D convolution. 여기서는 strides가 1이기 때문에 depth만 깊어지고 출력의 해상도는 입력과 같다. 이로써 이 Conv2D까지 거치게 되면 출력은 4x4x512의 차원이 된다. 출력 shape은 (None, 4, 4, 512)


self.D.add(Dropout(dropout))


마지막으로 dropout 한 번 더.


self.D.add(Flatten())


4x4x512 차원을 8,192 길이의 1차원으로 만들어주는 함수다. 출력 shape은 (None, 8192) 참고로 여기서도 data_format의 default 조건은 channel_last다. 그러니깐 가로줄부터 읽고, 세로줄 순서로 넘어가고, channel 순서로 데이터를 읽어서 1차원으로 나열한다는 것.


self.D.add(Dense(1))


다음으로 우리는 최종 출력이 0에서 1 사이의 값을 갖는 하나의 변수만 가지고 있으면 되기 때문에, 8,192 → 1로 만드는 가장 기본적인 neural network 형태[8]를 만들어 주었다. 출력 shape은 (None, 1)

[8] https://keras.io/layers/core/#dense


self.D.add(Activation('sigmoid'))


끝이 보인다!. 마지막으로 activation 함수를 넣어준다. sigmoid 함수는 실수값을 갖는 모든 입력 값을 0에서 1 사이 값으로 만들어 준다. [9] Activation layer의 출력 shape은 입력의 shape과 같다. 그래서 (None, 1)

[9] https://en.wikipedia.org/wiki/Sigmoid_function


self.D.summary()


summary() 함수는 D라는 model이 어떻게 디자인되어있는지를 출력해서 보여준다. 꼭 필요한 코드는 아니지만 내가 디자인 한 구조가 잘 반영되었는지 확인해볼 수 있기 때문에 유용한 코드다.



그래서 우리는 위와 같은 Discriminator를 만드는 코드를 살펴보았다! 파라메터 하나하나 분석하는 건 조금 귀찮은 일이었지만, 막상 다 살펴보고 나니 자신감이 생기는 것 같은 느낌적 느낌이다?!


  • Generator 정의 함수
지금부터 설명하는 코드는 Generator model을 만들어 주는 함수의 코드다.


이제는 이미지를 생성하는 generator에 대해 살펴보자! 입력단이 100차원의 노이즈인데, 각 element들은 -1에서 1 사이의 고르게 퍼져있는 랜덤값들이라고 한다. 진짜 노이즈다. 다만 -1부터 1 범위의 100차원 공간 상에 고루 퍼진 노이즈라서, 해당 100차원 공간상의 어떤 곳이든 출력되는 이미지가 손글씨처럼 나오도록 학습시키는 입력이라고 생각하면 되겠다. 근데, 100차원인 이유는 무엇일까... 뇌피셜에 의하면 VAE에서 손글씨를 학습할 때 latent space가 100차원 정도면 잘 학습이 되었나보지... ㅎㅎ


그 다음 convolution의 반대 역할을 하는 구조가 보인다. 이미지의 해상도는 점점 올라가고, 깊이(채널?)는 점점 작아진다. discriptor를 뒤집어 놓은듯한 구조이면서도 차이점이 있다. 왜 그런지는 역시 논문을 보거나... 노하우(?)가 생겨야 알 수 있겠지. 그리고 원문에 보면 원래 DCGAN은 fractionally-strided convolution을 사용하는 것으로 제안되었는데, upsampling이라는 기법을 사용했다고 한다. 그러면 더 그럴듯한 손글씨가 나온다나... 이유야 뭐 지금 수준에서 알긴 어렵다.


자 이제 코드로 들어가보자. 앞서 대충 코드를 익혀두었으니 이제는 조금 더 수월하게 보드를 이해할 수 있으리라!

self.G = Sequential()


Sequential 방법으로 generator의 모델을 만들기로 한다.


dropout = 0.4


여기서도 dropout은 0.4다.


depth = 64+64+64+64


으잉? depth가 64가 아닌 64가 4개 더해진 256? 아 뒤에 코드를 보니 깊이가 하나씩 바뀔 때마다 나누기를 하네...


dim = 7


아마 이미지 정보를 만들 때 시작하는 해상도가 7x7인가보다.


self.G.add(Dense(dim*dim*depth, input_dim=100))


입력 100 → 출력 12,544(=7x7x256)이 되는 가장 기본적인 fully connected neural network을 만든다. 


self.G.add(BatchNormalization(momentum=0.9))


함수 이름부터 무섭게 생긴 녀석이 나타났다... 일단 관련 문서[10]를 찾아보자. 근데 뭐 개념을 이해하는데는 큰 도움이 안된다.

[10] https://keras.io/layers/normalization/


사실 batch normalization은 딥러닝을 공부한 사람이면 한 번 쯤 들어봤을 법도 한데, 실제 개념은 이 글 쓰면서 다시 공부했다. -ㅁ- 기본적인 개념은 [11]에서 이해할 수 있고, 좀 더 구체적인 수식설명까지 보려면 [12]를 보면 된다. 혹시 한국어로 듣고 싶다면 [13] 여기로! 개인적으로는 [11]과 [12]를 본 후에 [13]을 보기를 권장한다.

[11] https://youtu.be/nUUqwaxLnWs

[12] https://youtu.be/5qefnAek8OA

[13] https://youtu.be/TDx8iZHwFtM


그러나! 저 링크를 다 들어가서 보고 싶지 않을테니 (ㅋㅋ;) 요약을 하자면 (근데 각 영상이 15분 미만만 보면 되기 때문에 그리 부담이 되는 것은 아니다.), 입력 데이터의 분포가 학습시에 계속 바뀌면서 들어오게 되면, 중간 layer들에서는 값들이 생각 이상으로 많이 출렁이게 되기 때문에(internal covariate shift라고 부름), 샘플 묶음(batch)마다의 평균과 분산을 계산해서 업데이트하고 이를 이용해 데이터를 정규화시켜서 사용하겠다는 개념이다. 그러면 각 layer마다 출력되는 값들의 분포가 안정적으로 유지되기 때문에 학습시에 효율이 올라간다고 한다. 모평균(population mean)과 모분산(population vaiance)으로 정규화 시키지 않는 이유는 학습할 때 모든 데이터를 다 가져다가 계산해서 쓰기에는 시간이 너무 오래걸리기 때문일 것이다. 어쨌든 이 방법을 쓰면 weights 값들의 초기화가 그다지 필요 없고, 학습 속도에도 도움이 되며, overfitting도 어느정도 막아준다고 한다(이거는 batch를 사용하기 때문인 것도 있을듯).


자 이제 그러면 다시 코드로 돌아와서, momentum 저 녀석은 뭔지 살펴보자. 이건 찾아보니, 현재 batch에서 계산된 평균과 분산을 기존까지 계산해온 평균과 분산에 어떻게 업데이트 시키느냐를 의미하는 것이다. 평균을 예로 들면, (새평균) = (기존평균)*0.9 + (현재 batch의 평균)*(1-0.9) 이렇게 하겠다는 의미. 코드 상에서 0.9로 설정되어 있어서 예시 수식에도 0.9를 썼다.


self.G.add(Activation('relu'))


Activation 함수로는 ReLU를 쓴다.


self.G.add(Reshape((dim, dim, depth)))


앞서 출력은 12,544(=7x7x256) 길이의 1차원이었기 때문에, 이를 (7, 7, 256) shape으로 바꾸어준다. 더 정확하게는 batch를 고려해서 (None, 7, 7, 256)이다.


self.G.add(Dropout(dropout))


앞서 batch normalization을 하면 overfitting을 줄일 수 있다고 하던데... 여기에서 dropout을 또 추가한 건.... 아마도 개발자 마음이겠지...?


self.G.add(UpSampling2D())


UpSampling2D 함수는, 2차원 데이터를 두번씩 반복해서 해상도를 두 배 늘리는 함수다. 가로와 세로에 따라서 반복하는 횟수를 지정할 수도 있는데 기본 값은 두 배 씩 늘리는 것이다. data_format의 기본값은 channel_last. 결론적으로 이 함수를 통해 얻어지는 출력은 (None, 14, 14, 256)이 된다.


self.G.add(Conv2DTranspose(int(depth/2), 5, padding='same'))


Conv2DTranspose는 직관적으로는 convolution이 하는 역할의 반대라고 생각하면 된다. 근데 실제 계산 과정은 뭔가 조금 다르다. 정상적인 방향의 convolution에서 filter가 한 번 계산될 때, 여러개 input 데이터가 filter와 곱해져서 하나의 output 데이터를 만든다. 반면 이를 역으로 생각한다면, 하나의 데이터로 여러 개의 데이터를 만들어야 하고, 그에 해당하는 여러 개의 데이터는 또 다른 데이터로부터 구해지는 것들과 병합돼야 한다. 이게 말로 설명하기 너무 어려운데, 기본 convolution은 many to one 계산의 반복이었다면, deconvolution은 many to many의 계산이 이루어져야 한다. 개념을 돕기 위해 영상[14]을 하나 첨부한다. 사실 근데 영상을 봐도 나는 잘 이해를 못했다. ㅋㅋ; zero padding 후에 일반 convolution 하듯이 deconvolution 하는 영상[15]도 있어서 가져와 본다. 다만 다중 channel일 때는 일반 convolution에서의 다중 channel을 다루는 것과는 다를 것이다.

[14] https://youtu.be/8DiqJj5tPlA

[15] https://youtu.be/uOKvea5pATM


입력 파라미터는 출력될 channel의 수, filter의 크기, padding의 종류가 표시되어 있다. 개념적으로 정의되어 사용되는 것들은 Conv2D와 크게 다른 건 없다. (deconvolution 알고리즘은 위에서 말했듯이 좀 다르겠지만) 그리고 한가지 더 봐야할 점은 strider를 따로 지정하지 않았는데 default 값이 1이다. 그래서 입력과 출력의 dimension이 같게 된다. 앞서 UpSampling2D로 차원을 뿔려놨으니, 여기서 출력도 뿔려진 상태로 유지가 된다. 다만 channel은 절반이 된다. 출력의 shape은 (None, 14, 14, 128)이다.


self.G.add(BatchNormalization(momentum=0.9))


여기서도 batch normalization을 붙여 준다. 아무래도 차원을 뻥튀기 하는 구조다보니 batch normalization이 유용하게 사용되나보다.


self.G.add(Activation('relu'))
self.G.add(UpSampling2D())


함수 설명은 중복이므로 최종 출력의 shape만 언급하고 넘어가겠다. 


(None, 14, 14, 128) → (None, 28, 28, 128)


self.G.add(Conv2DTranspose(int(depth/4), 5, padding='same'))


(None, 28, 28, 128)  (None, 28, 28, 64)


self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))
self.G.add(Conv2DTranspose(int(depth/8), 5, padding='same'))


(None, 28, 28, 64)  (None, 28, 28, 32)


self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))


self.G.add(Conv2DTranspose(1, 5, padding='same'))


드디어 마지막 단계까지 왔다. 이 과정을 거치면 28x28x1의 데이터가 얻어진다. 한 장의 이미지가 생성된 것이다. 다만 아직 데이터 값의 범위가 실수 값의 범위이다.

(None, 28, 28, 32)  (None, 28, 28, 1)


self.G.add(Activation('sigmoid'))


sigmoid 함수를 이용해서, 이제 각 픽셀값이 0에서 1의 값을 갖는 28x28의 이미지가 나왔다.



이제 discriminator와 generator의 modeling을 위한 함수 정의가 완료되었다. 이제는 각 모델을 불러와서 optimer와 loss function등을 정의하여 compile하고, 학습시키는 일이 남았다.


  • Discriminator Model 생성
optimizer = RMSprop(lr=0.0008, clipvalue=1.0, decay=6e-8)


코드만 보고 1차원적으로 생각했을 때는, optimizer를 RMSprop이라는 녀석으로 설정하는 코드다. 사실 좀 고민이 된다. optimizer의 깊은(?) 의미까지 다 들여다보아야 할까. 사실 어느 정도는 이해를 하고 있어야 내가 풀고자 하는 문제에서 어떤 optimizer가 적절한지를 선택할 수는 있을텐데... 일단, 간단하게라도 알아보고 가자. Optimizer는 우리가 모델링한 network의 weights 들이 어떤 값이 되어야만, 가장 최적화된 성능을 가지는지를 찾아주는 방법론이다. 가장 많이 들어봤을 방법으로는 gradient descent optimization[16]이 있다. 현재 weights 들을 조금씩 바뀌게 해서 어떻게 weights가 바뀔 때 목적한 성능에 더 가까워지느냐(혹은 loss function이 작아지느냐)를 미분 계산을 통해 찾아가는 방법으로, 몇몇 가정(assumption)들이 필요하기는 하지만, 어지간히 잘 동작하는 방법이다. 여기에도 몇몇 트릭들을 더 추가해서 여러 방법들이 존재할텐데, 사실 내가 그 정도까지 다 알고있지는 못하고, 예제 코드를 따라가는 수준에서 다 알 수는 없을 것 같다. 그래도 궁금하다면 [17]을 참고하면 좋을듯. (사실 나도 안 읽어봤다. 검색하다 찾은 글인데 그림이 직관적으로 잘 그려져 있기에... ㅎㅎ) 내가 다른 예제 코드에서 본 방법 중에는 Adam이라는 방법이 많이 보였었다. Keras에서 사용할 수 있는 다른 여러 optimizer들은 [18]에서 확인해볼 수 있다.

[16] http://ruder.io/optimizing-gradient-descent/

[17] http://ruder.io/deep-learning-optimization-2017/

[18] https://keras.io/optimizers/#usage-of-optimizers


다시 코드로 돌아와서, 본 예제에서는 RMSprop이라는 함수로 realistic한 이미지를 만들어내는 데에는 좋은 성능을 갖기 때문에 채용했다고 본문에서는 밝히고 있다. 참고로 RNN학습에도 좋은 성능을 보인다고 한다. 방법론은 [19]에서 발표자료로 설명해두었으니 참고해보자. 함수 파라미터로 lr은 learning rate에 해당하는 값으로 0.0008이라는 꽤나 작은 값을 사용했는데, 이 값은 아마도 이 예제 코드를 만든 사람들이 휴리스틱하게 찾은 값일테다. learning rate가 너무 크면 optimizer가 최적의 값을 찾지 못하고 이리 저리 방황하는 성능을 보이게 될테고, 너무 작으면 학습이 전혀 느리게 되는 단점이 있다. 우리가 앞서 디자인한 discriminator의 구조는 꽤나 복잡하기 때문에 이정도 작은 값은 써야 optimizer가 괜찮은 성능을 내는가보다. clipvalue는 gradients들의 절대 값이 여기서 설정한 값들보다 클 때 잘리도록(clipped) 하는 값이라고 한다. 그런데! 이게 Keras가 최신버젼으로 업데이트 되면서부터 이 값은 입력 파라미터 값이 더이상 아니다. ㅎㅎ 그래서 그냥 넘어가도록 하자. 실제 예제 코드에서도 clipvalue가 빠진 채로 함수가 불러지고있다. decay 값은 학습이 진행되면서 learning rate를 줄여나가는 비율이다. 앞서 learning rate가 클 수록 optimizer가 방황한다고 표현을 해놨는데, learning rate가 작다고 하더라도 마지막 정말 정확한 최적값을 찾고자 한다면 learning rate가 학습이 진행될 때마다 더 작아지게 하는 것도 방법 중 하나다.

[19] http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf


위에서 실컷 설명 파라미터를 설명했는데, 최신 버젼 소스코드를 보면 파라미터 값이 좀 다르게 아래와 같이 구현되어 있다.


optimizer = RMSprop(lr=0.0001, decay=3e-8)



self.DM = Sequential()
self.DM.add(self.discriminator())


이제 DCGAN 클래스에 DM이라는 이름의 discriminator model을 앞서 설명한 discriminator 함수로 만들어 줬다. 근데 D를 그냥 써도 되는데 왜 또 DM이라는 모델을 또 정의해서 거기에 D를 집어 넣어서 쓰는 걸까. 그건 discriminator만 학습 시킬 때와 generator와 함께 붙어서 학습될 때 각각 다른 compilation을 필요로 하기 때문일 것이다. 그래서 DM을 따로 정의해 compile을 하고, 뒤에서 보면 AM을 따로 정의해서 또 다른 compile을 넣어 놓는다.


self.DM.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])


이제 discriminator model을 어떻게 학습시킬지 설정해주자. loss는 loss function을 무엇을 쓸 지를 설정해주는 것으로 여기서는 cross entropy를 사용했다. discriminator model은 목표값과 유사한 값을 출력하도록 학습시키는 것이 아니고, 맞냐/아니냐(0 또는 1)의 값을 확실하게 이야기해주도록 학습시키기 때문에 cross entropy를 쓰는 것이 유리하다. cross entropy와 mse의 차이점이 궁금하다면, 아래 영상[20]의 29분 30초경부터의 설명을 보면 좋다. optimizer는 앞서 설명한 RMSprop를 사용하고, metrics은 학습시 한 epoch이 끝날 때마다 training set과 validation set(주어진 경우)에 대해 그 결과를 확인해볼 수 있게 어떤 걸 기록할지 알려주는 것이다. 여기서는 정확도를 확인해볼 수 있는 가장 기본적인 'accuracy'를 썼다. metrics 설정은 학습에 영향을 주는 것은 아니고 성능을 평가하기 위한 것으로만 사용된다. 참고 설명 [21]

[20] https://youtu.be/o_peo6U7IRM?t=29m35s

[21] https://machinelearningmastery.com/custom-metrics-deep-learning-keras-python/


  • Adversarial Model 생성


이제 generator와 discriminator가 어떻게 아래 그림과 같이 엮여서 동작하게 할지를 구현해보자.



Generator와 discriminator도 회상해볼 겸 다시 살펴보자.



기본적으로 generator에서 생성되는 모든 이미지는 noise로부터 생긴 이미지이 때문에 가짜 이미지다. discriminator는 가짜인지를 밝혀내는 역할을 하니, 이 adversarial model에 있는 discriminator가 가짜이미지를 걸러내지 못할 수록 generator가 이미지를 잘 만들고 있다는 이야기가 된다.


위 구조는 아래와 같은 코드로 구성되고, discriminator model을 만들 때와 learning rate과 decay 값이 좀 다를 뿐 구현 과정은 동일하다.


optimizer = RMSprop(lr=0.0004, clipvalue=1.0, decay=3e-8)
self.AM = Sequential()
self.AM.add(self.generator())
self.AM.add(self.discriminator())

self.AM.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])


아 참고로 여기서도 최신 코드에는 RMSprop를 위 예제가 아니라 아래와 같이 쓰고 있다. Adverarial model은 DCGAN 클래스에서 AM이라는 이름을 갖는다.


optimizer = RMSprop(lr=0.0001, decay=3e-8)


근데 난 여기서 한가지 궁금한 점이 생겼다. DM에도 discriminator가 학습되고 있고, AM에서도 disriminator가 들어가있는데, 그러면 한 discriminator에 optimizer가 두번 쓰이는 거고, 학습시에 충돌이 생기지 않을까?


역시나, 원글에서 그 다음 내용을 읽으니 추가 설명이 있었다.


  • Training 방법



학습 부분이 가장 어려운 파트라고 한다. 일단 discriminator model(DM)이 진짜 이미지와 가짜 이미지를 구별할 수 있도록 먼저 학습시킨다. 위 이미지에서 generator에서 나오는 이미지에 label을 0이라고(가짜 이미지라고) 알려준 상태로 학습을 시키는 것이다. 그러니깐 아직은 generator를 학습시키는 게 아니고 discriminator만 학습시키는 거다. 그리고 나중에 DM과 AM을 번갈아가면서 학습을 시킨다고 한다. 그니깐 DM과 AM이 동시에 학습되는 게 아니었다.


학습을 어떤 식으로 시키는지 코드를 보자.


images_train = self.x_train[np.random.randint(0,
self.x_train.shape[0], size=batch_size), :, :, :]


코드가 조금 복잡하다. 조금 들여다보면, x_train은 DCGAN 클래스에서 이미 MNIST 데이터를 불러온 애들이다. 데이터를 불러오는 코드를 보자.


from tensorflow.examples.tutorials.mnist import input_data

(중략)

self.x_train = input_data.read_data_sets("mnist", one_hot=True).train.images

self.x_train = self.x_train.reshape(-1, self.img_rows, self.img_cols, 1).astype(np.float32)


x_train은 텐서플로우에서 기본으로 제공하는 MINIST 데이터에서 training 이미지에 해당하는 데이터를 불러와서 (-1, 이미지의 세로크기, 이미지의 가로크기, 채널수)의 형태로 reshape된 녀석들이다. -1은 전체 데이터 갯수에 맞춰서 이미지의 세로크기, 가로크기, 채널 수가 결정되면 자동으로 유추될 수 있는 총 이미지의 갯수라고 생각하면 된다. 예를 들어 1,000 길이의 data가 있었고, 이미지의 크기가 10x10이라면, (-1, 10, 10, 1)로 reshape하면 -1에 해당하는 값은 10이라고 생각하면 되고, 이는 총 이미지의 개수와 같다. 이걸 일반화 시키기 위해 -1이라고 한 것.


그리고 이 x_train 데이터의 순서를 섞기 위해 numpy의 함수인 random.randint()를 썼다. random.randint() 함수는 중복된 index도 만들어낸다는 점이 있어서 한 데이터가 여러번 학습에 사용될 수도 있다. 여튼 randint() 함수의 첫 파라미터는 생성할 숫자의 최소값, 그 다음 파라미터는 최대값(이지만 포함하지 않는), 생성할 갯수(size)의 의미를 갖는다. 그러므로 현재 x_train에 들어있는 데이터 중에서 batch_size 갯수만큼을 임의로 뽑아서 하나의 images_train batch를 만들겠다는 의미다.


사실 이 코드는 keras도 아니고 numpy와 기본 python array 관련 코드인데 괜히 설명이 길어졌다;; 그래도 쌩초보인 나로서는 일일이 다 공부해서 확인해보는 수밖에...


noise = np.random.uniform(-1.0, 1.0, size=[batch_size, 100])


이제 노이즈를 만들 차례. -1부터 1 사이에 고르게 분포된 100차원의 랜덤 값을 batch_size 갯수만큼 만드는 코드다. noise 변수의 shape은 (batch_size, 100)이다.


images_fake = self.generator.predict(noise)


우선 generator에 noise를 넣어서 만들어진 이미지는 생성된 가짜 이미지이므로 images_fake라고 이름을 지정해 놓는다.


x = np.concatenate((images_train, images_fake))


concatenate는 말 그대로 두 데이터를 잇는다는 내용이다. 위 코드를 통해 x에는 MNIST 데이터로부터 읽어온 진짜 손글씨 이미지와, generator로부터 만들어진 가짜 손글씨 이미지가 모두 포함되게 된다.


y = np.ones([2*batch_size, 1])
y[batch_size:, :] = 0


label로 사용될 y의 전반부에는 진짜 이미지 갯수만큼 0으로, 후반부는 (즉, 가짜 이미지 갯수만큼은) 1로 설정해놓는다. 우리는 앞서 코드에서 진자 이미지 갯수와 가짜 이미지 갯수가 모두 batch_size였으므로 y의 길이는 총 2*batch_size가 된다. 위 코드는 일단 2*batch_size 길이 만큼 1로 가득찬 y를 만들고, 전반부 batch-size 갯수만큼을 0으로 다시 덮어 씌운 코드다. 두번째 줄에서 batch_size 뒤에 :이 더 붙은 것은 처음 인덱스부터 batch_size갯수만큼의 모든 element를 가리키는 의미다. :가 없으면 batch_size 번째의 element만 0이 되어 의도와 다르게 된다.


d_loss = self.discriminator.train_on_batch(x, y)


이제 우리가 만든 x와 y로 discriminator를 학습시키고, 출력 loss 값을 d_loss로 받아오는 코드다. 보통 하나의 모델을 여러번의 epoch을 통해 학습시키는 게 일반적이었지만, 여기서는 한 번에 한 batch 만을 통과 시켜서 학습시키는 모양이다.


y = np.ones([batch_size, 1])
noise = np.random.uniform(-1.0, 1.0, size=[batch_size, 100])


이번에는 AM을 학습시키기 위한 입력과 출력을 만든다. 입력은 모두 noise고, 따라서 y는 모두 가짜라고 이야기하도록 1로 세팅한다.


a_loss = self.adversarial.train_on_batch(noise, y)


그리고 AM을 noise와 y로 한 번 업데이트 시킨다. 출력 loss 값은 a_loss 로받아온다.


이렇게 구현된 training 과정(discriminator model과 adversarial model을 번갈아 가며 학습시키는 과정)을 계속 반복한다. 원글에 의하면 1000번 이상 반복하면 꽤 괜찮은 결과가 나온다고 한다.


코드 분석 끝!



[요약 번역] 케라스(Keras)로 구현하는 오토인코더(AutoEncoder)

오토인코더를 비롯하여 여러 변형들의 구현과 간략 설명이 있는 케라스 블로그 글을 읽는 중에 공부도할 겸 그 내용을 아주 간략하게 한글로 요약해보았다.


https://blog.keras.io/building-autoencoders-in-keras.html


예제 코드는 다음의 모델들을 포함하고 있다.

  • 흔히 보는 CNN을 사용하는 autoencoder가 아니라 걍 fully-connected layer
  • Sparse autoencoder
  • Deep fully-connected autoencoder
  • CNN을 사용한 autoencoder
  • 이미지에서 노이즈 제거하는 모델
  • Sequence-to-sequence autoencoder
  • VAE (variational autoencoder)


*다시 한 번 이야기하지만 이 글은 요약을 한 것이지, 전체 번역은 아니다.


  • Autoencoder는 무엇인가?

데이터를 압축하고 다시 압축을 푸는 알고리즘이다.


특징으로는 1) 데이타 맞춤적(data-specific)이다. 2) 무손실 압축 형태가 아니다. 3) 데이터로부터 자동적으로 학습된다.


  • 데이터 압축에 좋은 방법인가?
딱히 그렇지는 않다. 기존 압축 방법인 JPEG나 MP3 같은 것보다 효율적이지 않다.

  • 그럼 뭐가 좋은데?
딱 맞는 분야를 찾아가는 중이다. 일단 데이터의 노이즈를 제거하는데 좋고, 데이터의 시각화를 위한 차원 축소(dimensionality reduction)에 좋다.

  • 근데 왜 이렇게 난리냐?
일단 비지도학습(unsupervised learning)으로 학습이 된다는 게 매력적이다. 아주 엄밀하게는 self-supervised 학습이라고 하는 것이 맞겠다.


  • 가장 간단한 fully-connected layer 버젼의 autoencoder
코드는 홈페이지에서 참고하자.

학습을 시켜보면 loss 값이 0.1 근처에 머무는 걸 볼 수 있다.


  • 압축된 정보에 희박함(sparsity) 조건 추가하기
앞서 예제는 hidden layer의 크기(앞 예제에서는 32차원으로 설정)만이 학습 조건이었다. 근데 이건 사실 PCA(principal component analysis)랑 크게 다를 것이 없다. 그래서 조금 더 효율적인 압축을 위해서 희박함(sparsity)이라는 추가 조건을 더해주는 방법을 추가한다.


원문 홈페이지 코드 참고.


regularization을 추가하여 학습시키는 건데 overfit을 줄일 수 있다고 한다. 근데 결과를 보면 성능이 뭐 딱히 더 좋아졌는지 티가 나지는 않는다.


  • Deep fully-connected autoencoder
앞서 예제는 단일 layer를 갖는 encoder와 decoder로 구현된 것이었고, layer를 몇 개 더 추가해보자.


원문 홈페이지 코드 참고.


약간 더 좋아졌다.


  • CNN을 사용한 autoencoder

MNIST 데이터는 손글씨 이미지이기 때문에 CNN을 쓰는 게 더 적합해보인다.


세번의 2D convolution 세번의 MaxPooling으로 총 128차원의 정보로 데이터를 압축시켰다가, 다시 이미지를 복원하는 코드로 구성되어 있다. 원문 코드 참고.


참고로, 모델을 학습시키기 전에, 학습 과정의 결과들을 시각화하기 위해 TensorBoard를 사용한다. 터미널 창을 하나 켜고 아래 명령을 실행시키고 학습을 시작하면, /tmp/autoencoder 폴더에 값들이 기록된다.

"tensorboard --logdir=/tmp/autoencoder"


결과를 보면 확실히 좋아졌다. 앞서서는 압축한 데이터가 32차원의 데이터였는데, 여기서는 128차원(8x4x4)의 데이터이기 때문에 loss 값이 큰 차이 없어 보여도 성능은 좋아진 것이라고 한다.


*visualization code에 오타가 있다. for loop 안에 suplot에 쓰이는 i가 i+1이어야 한다.


  • 이미지에서 노이즈 제거하는 모델

MNIST 데이터에 가우시안 노이즈를 섞어서 노이즈가 있는 이미지 데이터를 만든다.


이 데이터를 학습하는 autoencoder는 약간 다른 구조를 썼다. 두번의 2D convolution과 두번의 MaxPooling으로 1,568차원(7x7x32)으로 정보를 압축시켰다가 이미지를 복원함. (아마도 노이즈가 포함되서 그런지 압축차원을 이전 모델이 갖던 128차원보다 더 크게 설정한듯 하다. 이건 뭐 개발자의 느낌적 느낌인 건가)


  • Sequence-to-sequence autoencoder

이번에는 이미지가 아닌 시퀀스 데이터에 대한 학습. LSTM과 같은 구조를 이용하여서 입력 시퀀스를 하나의 벡터로 만들고, 이를 다시 LSTM decoder에 n번 입력해서 원하는 길이(n)의 출력을 얻는 방법이다.


아닛?! 근데 여기서는 그냥 어떻게 코딩이 이루어질 수 있는지 예제 코드만 있다. 코드는 간단하긴 한데, 아무런 입력 데이터 예제 없이 그냥 코드만 보여주다니. ㅠㅠ


  • VAE (variational autoencoder)

대망의 VAE


Autoencoder랑 다른 점이 뭐냐하면, autoencoder는 latent vector를 찾아주는 반면에, VAE는 latent variable(평균과 표준편차)을 찾아준다. 이렇게 하면 latent 공간 상에서 데이터가 어떠한 분포를 갖고있는지 알 수 있고 이 분포로부터 얻어진 샘플로 decoder를 통해 입력을 다시 생성해낼 수 있다. (이게 꽤나 유용하다) 그래서 VAE가 '생성 모델(generative model)'이다.


VAE가 어떻게 동작하는가?


먼저, encoder는 입력 x를 받아와 latent 공간 상에서 어떤 평균과 표준편차를 갖는지 출력하는 역할을 한다. 그리고 이 평균과 표준편차로 decoder에 넘겨줄 z를 만든다. z = 평균 + 표준편차 x 랜덤값(0~1사이) 마지막으로 decoder는 x와 동일한 이미지를 만든다.


평균과 표준편차들은 두개의 loss function으로 학습된다. 하나는 재생성 loss function으로 기존 autoencoder에서 decoder가 입력과 동일한 데이터를 재생성하도록 학습시키는 것이다. 다른 하나는 학습된 latent 분포와 기존 분포 사이의 KL divergence를 계산하는 것으로 regularization처럼 동작한다. KL divergence는 사실 더 좋은 학습을 위한 것이라서 없어도 된다. (앞서 sparsity 조건을 추가할 때도 regularization에 대한 언급이 있었다.)


전체 코드는 Github 링크로 되어있고, 각 단계별 코드 설명이 되어있다.


먼저 입력을 어떻게 latent 공간의 변수로 매핑시키는지를 보자.


*여기부터는 코드 해석을 조금 더 추가해보았다.


x를 h라는 중간 레이어를 통과하게 하고, h로부터 latent 공간(z)의 평균값과, 표준편차를 얻을 수 있는 구조다. z의 차원수 만큼 평균과 표준편차가 존재하게 된다.


그 다음 정의된 sampling이라는 함수는, z 공간상의 평균과 표준편차를 파라미터로 받아서 decoder로 넘겨줄 z 값을 만들어준다.  z = 평균 + 표준편차 x 랜덤값(0~1사이) 입력은 하나의 값이 아니라 batch_size의 길이만큼 들어오기 때문에 함수 중간에 랜덤값(epsilon) 생성시에도 shape 설정을 길이에 맞게 해주었다. Lambda 함수는 keras에서 정의된 함수로 수식과 같은 임의의 함수를 layer 형태로 변환시켜주는 함수다.


이제 decoder_h layer를 이용해 z로부터 intermediate_dim 차원의 중간 vector인 h_decoded를 얻고, 이를 다시 decoder_mean layer를 통해 원본 데이타 크기의 출력을 만든다.


여기서 VAE전체, encoder만 따로, decoder(generator)만 따로 분리해서 정의해둔다.


Loss function은 VAE 전체 모델의 입출력이 같아지도록 하는 xent_loss와 KL divergence regularization을 이용하는 kl_loss의 합으로 정의된다.


본 예제 코드에서는 latent space를 2차원으로 설정했기 때문에, encoded된 입력들이 어떻게 분포되어 있는지를 그림으로 쉽게 나타낼 수 있다.


자 이제 기존에 학습된 입력이 아니더라도 우리는 데이터의 분포를 알 수 있기 때문에 새로운 출력을 생성해낼 수도 있다!


2차원 공간상에서 값들을 상하좌우로 옮겨가며 interpolated 된 z들을 decoder(generator)에 입력으로 넣어 얻은 출력들을 원문에서 볼 수 있다. 여기에 epsilon_std라는 변수가 어떤 값인지 안 정해지고 사용되었는데, 아마 시험적으로 출력이 가장 변화무쌍하게 보일 수 있는 값을 찾은 게 아닌가 싶다. 아! 예제코드를 직접 가서 보니 -4에서 4 범위 내로 되어있다. 그렇다면 설명문에 적힌 epsilon_std는 4/15 였는듯. 뭐 어차피 그냥 예제코드대로 돌릴 거면 큰 상관 없을듯.


덧)


VAE가 CNN이 아니고 fully connected leyer로 구현된 예제이기 때문에 CNN을 썼을 때는 어떨지 궁금하다. 근데 CNN 안써도 결과가 괜찮네. 


덧2)


예제 코드를 혹시나 Windows에서 돌린다면 GraphViz가 제대로 동작하지 않을 수 있다. 나는 그냥 plot_model이 들어간 line들을 다 주석처리하고 돌렸다.



Keras 참고자료
(tensorflow backend)
  • 설치

pip install keras

  • import

import tensorflow as tf

import keras


버젼 확인

tf.__version__

keras.__version__


추가로 함께 import 하면 좋은 것들

import numpy as np

import matplotlib.pyplot as plt


  • 공부자료

모델링 방법 Sequential API, Functional API

http://leestation.tistory.com/777


간단한 NN

http://pinkwink.kr/1119?category=580892 (타이타닉 생존)


CNN

http://pinkwink.kr/1121 (MNIST)


학습된 모델 불러오기

http://pinkwink.kr/1122


RNN

https://datascienceschool.net/view-notebook/1d93b9dc6c624fbaa6af2ce9290e2479/ (싸인 그래프 예측)


LSTM

https://tykimos.github.io/2017/04/09/RNN_Layer_Talk/ (나비야 노래)


AutoEncoder

https://blog.keras.io/building-autoencoders-in-keras.html (기본 AE부터 VAE까지)

위 글 요약번역 http://leestation.tistory.com/775

https://d2.naver.com/news/0956269

https://lilianweng.github.io/lil-log/2018/08/12/from-autoencoder-to-beta-vae.html

VAE설명

https://youtu.be/vmeJdZQNuGA


GAN

http://leestation.tistory.com/776


미리 구현된 함수형태 코드

https://github.com/Machine-Learning-Tokyo/DL-workshop-series/blob/master/ConvNets.ipynb?fbclid=IwAR3BASdXssKc-CHuqMHx39DYfwnDZP6wUDqhyOdHXgoRQFZ5r4-NVgytOE8



[번역] 진정한 쌍방향 상호작용(Interactivity)은 인공지능(AI)을 필요로 한다

진정한 쌍방향 상호작용(Interactivity) 인공지능(AI) 필요로 한다*

 

*본 글은 Stephane Bura "True Interactivity Requires Artificial Intelligence” 번역한 것입니다.

 

원글 출처: https://becominghuman.ai/true-interactivity-requires-artificial-intelligence-7f37ddd45e62

 

머지 않은 미래에 사람의 뇌와 컴퓨터는 매우 긴밀하게 연관될 것이며, 동반 관계는 사람의 뇌가 그동안 생각해왔던 방식이나, 오늘날 우리가 알고 있는 정보처리 기계가 데이터를 접근하던 방식과는 다르게 동작할 것이다. – J.C.R Licklider, “인간-컴퓨터 공생 (1960)” 중에서

 

컴퓨터는 진정한 의미에서 쌍방향적(interactive)이지 않으며, 의도적으로 그렇게 디자인 되지도 않았다.

 

처음에 기계는 무언가를 위해 완전히 숙달되고 제어되는 도구로 인식되었었다. 이후, 컴퓨터의 등장으로 우리가 사용하는 도구들은 점점 복잡해지게 되었고, 이에 완전한 제어라는 것은 어려운 것이 되었고, 이러한 도구들을 다루는 것은 힘든 일이 되었다. 이러한 문제를 해결하기 위해, 개발자들은 꼼수(trick) 쓰기 시작했는데, 그것은 사용자들이 기계들을 동작시킬 그들의 행동을 제한(constrain)하는 것이었다. 생각해보자. 당신이 ATM 기계를 사용할 , 자율적인 행동을 한다고 느낀 적이 있는가? 기계를 제어하기보다 사용자들은 기계에 의해 주어진 정확한 순서들을 따라야 한다. 주어진 순서를 그대로 완수하거나, 아니면 그만두는 선택지밖에는 없다.

 

컴퓨터와 상호작용하는 것은 세상에 있는 다른 것과는 뭔가 다르다. 사람과 상호작용하는 것과는 더욱 확실하게 전혀 다른 느낌이다. 컴퓨터는 우리를 컴퓨터에게 맞춰지도록(subservient) 하기 때문이다. 우리는 컴퓨터에 맞게 순응해야 하고 예상가능하게 행동해야 한다. 컴퓨터들은 우리가 그들만의 (때로는 숨겨진) 방식을 따르지 않으면 우리를 꾸짖기까지(scold) 한다.

 

하지만, 이러한 관계가 지속되어온지 50년이 지나고, 컴퓨터는 이제 사람과 함께 있을 정도로 강력해졌고, 쌍방향 상호작용에 대한 우리의 생각을 급진적으로 바꿔놓고 있다. 변화의 핵심에는 인공지능과 새로운 종류의 인터페이스가 있다.

 

쌍방향 상호작용(Interactivity)이란 무엇인가?

 

<모든 것의 디자인(The Design of Everyday Things)>에서, 저자 도널드 노만(Donald A. Norman) 한명의 사람(또는 행위자, actor) 어떻게 세상(또는 상호작용이 가능한 어떠한 시스템) 맞게 행동을 하며 살아가는지를 8가지 절차의 반복적인 과정으로 설명하고 있다.

 

  1. 목표 설정한다. (: “나는 중국 음식을 먹고 싶다.”)

  2. 목표를 향해가고자 하는 의도 형성한다. (: “중국 음식에 대한 나의 갈망을 만족시키기 위해 여러 가지 방법 중국 음식점에 가기로 결정했다.”)

  3. 의도에 맞는 계획(, 일련의 행동 과정) 세운다. (: “Yelp 앱을 통해 가까운 중국 음식점을 찾아보고, 내가 가지고 있는 돈이 충분한지, 차를 가져갈 있는지 등을 확인한다.”)

  4. 계획에 포함된 행동들을 실행에 옮긴다. (: “운전해서 음식점에 간다.”)

  5. 여기서, 세상(상호작용이 가능한 시스템) 행동과 변화에 따라 반응한다. (: 교통상황, 예약석 맡아놓기 )

  6. 자신이 가지고 있는 감각들을 통해 세상(시스템) 새로운 상태와 정보들을 감지한다.

  7. 상황을 해석하고 세상(시스템) 대한 자신의 기억(관념, 이해, model) 갱신한다. (: “주말에 음식점은 문을 닫는다는 것과 이러한 정보에 대해서는 Yelp 앱에 의존할 업다는 것을 기억해둔다.”)

  8. 결과물 평가하고, 새로운 목표가 필요한지 알아본다. , 현재 목표에 대한 검증을 진행한다. (: “아직도 중국 음식을 먹고 싶은가?”, “근처 다른 영업중인 음식점으로 가면 만족할 있을까?”)

 

과정은 그것을 수행하는 사람이나 시스템의 상태가 변할 때마다 반복된다. 몇몇 절차들은 생략될 수도 있다. ( 안에서는 매번 내가 중국 음식을 먹고 싶은지 재확인할 필요가 없다.”) 그리고 과정은 처음부터 시작하지 않을 수도 있다. (: “광고를 보고 바로 목표를 먼저 설정하기 이전에 물건을 사려는 의도가 생긴다.”) 하지만 앞서 설명한 8가지 절차가 일반적인 과정이다.

 

사람간의 상호작용에서, 사람은 행위자(actor)임과 동시에 상호작용하는 대상 시스템이 된다. 여기서의 상호작용이 컴퓨터와의 관계와 다른 점은, 사람은 지속적으로 상대방에 대한 관념(model) 수정하고 그에 따라 행동을 바꿔나간다는 것이다. 상대방이 , , 어떻게 행동하려는지에 대한 이해를 통해서 말이다. 이게 바로 우리가 협동하고, 협상하며, 관계를 만들어나가는 과정이다.

 

반면에, 컴퓨터는 사용자를 고려해서 행동을 바꿔나가지 않는다. (역자주: 적어도 그동안에는 대부분 그랬다.) 대신 사용자들이 자신의 목표나, 의도, 계획들을 스스로 바꾸기를 기대한다. 그래서 사용자의 행동만이 유일한 의사소통 수단이 된다. (역자주: ATM기기의 버튼을 누르는 행동을 해야만이 기계가 다음 동작으로 넘어갈 있는 입력이 되는 것처럼 말이다.) 그래서 사용자는 원하는 결과가 나올 때까지 컴퓨터의 순서를 최대한 이해해가면서 따라가야 한다. 여기서 우리는, 사람간의 상호작용에서 있는 14 절차가 아니라(역자주: 아마도 앞서 설명된 8가지 절차가 사람의 상호작용의 경우에는 14가지 절차로 확장되는듯하다.), 훨씬 간소화된, 그리고 의미나 정서적인 절차가 없는, 과정을 수행하게 된다. 오히려 컴퓨터가 우리를 기계로 만드는 것이다.

 

포토샾이나 워드프로세서 같은 컨텐츠 제작 소프트웨어들은 사용자들에게 세분화된(granular) 작업을 가능하게 해준다는 면에서 우리를 조금 배려해준다(humane) 있다. 사용자들은 자신들의 행동 계획들을 크게 확대할 있다. 대신 복잡성도 엄청나게 증가하긴 한다. 이해가 안된다면 이러한 프로그램들의 명령 메뉴나 도구창을 보시라.

 

우린 있다!

 

행위자(actor)로서의 상호작용 시스템

 

시스템과 진짜 상호작용한다는 무슨 뜻일까? 시스템을 한명의 행위자(actor) 실현시킨다는 것은 무슨 의미일까?

 

사용자를 파악하고 모델링할 있는 인공지능 시스템들은, 앞에서 설명한 상호작용 과정들의 절차를 높은 수준에서 수행할 있게 된다.

 

  1. 감각: 시스템은 사용자의 행동과 의미를 관찰할 있게 된다.

  2. 기억(Model): 시스템은 사용자가 누구인지, 알고 있는지, 원하는지 알고, 사용자의 정신 상태를 예측할 있게 된다.

  3. 결과물: 시스템은 사용자가 목표를 향해 어떤 과정을 진행 중인지 확인할 있다. 그리고 사용자가 도움을 필요로 하는지 확인할 있게 된다.

  4. 목표: 인공지능이 들어간 상호작용 시스템의 주된 목표는 사용자가 최고의 역량을 발휘하도록 도와서 사용자들의 목표를 달성하도록 하는 것이다.

  5. 의도: 의사 결정을 하는 높은 차원의 인공지능. 시스템은 쉽게 목표에 도달하기 위해, 어떻게 사용자를 파악하고 지도해야 하는지, 또는 어떻게 상호작용 방법들을 수정해야 하는지 알게 된다.

  6. 계획: 최적화, 계획수립, 계산, 수행을 하는 낮은 차원의 인공지능. 시스템은 자신의 특징들을 이용해 사용자의 목표를 달성함에 있어 어떠한 것이 최적인지를 있다.

  7. 행동: 시스템은 자신의 내부 상태의 변화를 명확하고 목적에 맞게 사용자에게 알릴 있기 때문에, 필요에 따라서 사용자들이 시스템의 기억(model) 수정할 있도록 돕는다.

 

쌍방향 상호작용(Interactivity) 비유할 , 동사(Verbs) 사용하는 것은 적합하지 않다

 

복사, 붙여넣기, 저장, 공유, 닫기 등등수행을 의미하는 이러한 동사들은 우리가 생각하는 계획, 의도, 목표를 컴퓨터가 이해하기 쉬운 표현들로 바꾸어 놓은 것들에 불과하다. 우리는 대부분의 작업을 우리 머리 속에서 해야 한다. 진정한 쌍방향 상호작용(interactivity) 새로운 용어와 어휘들을 필요로 한다. 사용자 자신들의 목적과 상호작용 과정에서 일어나는 일들에 대해 의견을 교환할 있는 어휘들 말이다. 앞서 상호작용 과정의 절차들에서 어떠한 것들이 논의되어야 하는지 우리는 살펴봤었다. 기존처럼 행동만 해야 하던 상호작용이 아니라 앞에서 말한 상호작용 과정들로 돌아오게 수록, 우리는 인공지능 상대방과 의미있고 유용한 의사소통을 하게될 있다.

 

  1. 계획: 사용자는 시스템에게 사용자들의 방법론과 사용자들이 따르는 규칙들에 대해 설명하고, 다음 행동에 대해 이야기할 있게 된다.

  2. 의도: 사용자는 무엇을, 원하는지 설명할 있다.

  3. 목표: 사용자는 상호작용을 통해 무엇을 얻고자 하는지, 그게 중요한지에 대해 말할 있다.

  4. 결과물: 사용자는 무엇이 상호작용을 성공으로 이끌었는지를 말할 있게 되고, 기대했던 상황이 아니거나 타협이 이루어져야 하는 상황에서 시스템과 협상이라는 것도 있다.

  5. 감각: 사용자는 상호작용 규칙을 생각해보고, 결과에 대해 시스템에게 피드백을 있다. (: “나는 지금 상태가 의미하는지 이해가 되지 않아”, “ 명령들이 뭐가 다른 거야?”)

 

이러한 어휘들이 제대로 사용될 있게 하기 위해서, 시스템은 어휘가 의미하는 정확한 개념에 대해서 이해할 있어야 한다. 이것은 시스템이 무엇을 있는지에 대해 내부적으로 상징적 의미를 이해하고 있어야 하며, 작업을 최적화하기 위해 이러한 의미 지식들을 추론 사고(reason) 있어야 한다. 나아가서, 사용자들의 목표와 의도 등을 파악해서, 시스템이 갖고 있는 지식 분야에 맞도록 변환할 수도 있어야 한다. 간단히 말하면, 시스템은 주어진 임무에 대해 협력 전문가 되어야 한다는 말이다.

 

인간지능 증대를 위한 쌍방향 상호작용

 

인공지능이 분야의 지식들을 표현하고, 추론하며, 협력 규칙을 수립하는 것들은, 작업들을 자동화해서 불투명하게 만들어버리는 것이 아니라, 인간 지능을 증대시키는 인공지능의 한가지 역할이다. 이러한 종류의 인공지능을 적용한 분야들은 컴퓨터와 우리의 관계를 변화시키고, 컴퓨터들을 엄격한 권위자(rigid authority)에서 우리를 이해해주는 강력한 조력자로 탈바꿈시켜준다. 그리고 상호작용 과정의 핵심을 이해함으로써 우리는 새로운 종류의 관계에 대해 정의할 있게 된다.

 

쌍방향 상호작용(Interactivity)이란, 행위자(actors) 각자의 목표를 이루기 위해, 상대의 행동에 영향을 주고, 이를 위해 서로에 대해 파악해가는 과정이다.

 

그리고 인공지능 상호작용 시스템의 목적은 사용자가 자신의 목표를 이루기 위해 최고의 역량을 발휘할 있도록 돕는데 있다는 것을 잊지 말자. 인공지능은 상호작용 과정을 효율성과 역량증진(empowerment) 선순환으로 바꿀 있다. (역자주: 서로가 자신의 목표를 이루기 위한 경쟁상황이나 시스템에게만 상호작용의 권한이 있는 상황이 아닌)

 

지금까지 컴퓨터는 우리를 기계처럼 다루어왔다. 개발자 입장에서 컴퓨터와 사용자간의 상호작용이 제대로 이루어지지 않는 경우 처리해야 하는 비용이 너무 컸기 때문이다. 그래서 쉬운 방법으로, 사용자의 행동을 제한해서, 그들이 만든 똑똑한프로그램이 오동작하지 않도록 만들었다. 이제 우리는 사용자를 이해하고 사용자들의 역량을 증대시킬 있는, 그래서 사용자를 사람처럼 대우할 있는 기술적인 수단들이 마련되었다. 그렇기 때문에, 이제는 그렇게 하지 않는 것이 비용을 치르는 것이 되었다.


*본 글은 Stephane Bura   "True Interactivity Requires Artificial Intelligence” 번역한 것입니다.

 

원글 출처https://becominghuman.ai/true-interactivity-requires-artificial-intelligence-7f37ddd45e62



utf-8 <-> CString

utf-8 (char*) -> CString



CString -> utf-8 (char*) 이건 코드가 너무 없더라 ㅠㅠ 고생 좀 했음





BLOG main image
미디어와 로봇에 관심이 많은 아이 그 영역을 넓혀보려 합니다. '영상 제작'과 '감정 로봇'이 블로그의 주소재입니다. 자유로운 답글 환영합니다!
 Notice
 Category
전체보기 (749)
내가 사랑하는 MJ (0)
아이가 생긴다면 (4)
Media (98)
Robot (447)
타인과 약자를 위한 (81)
Etc. (118)
연구관련 (50)
장비병 (24)
기타 (44)
 TAGS
연구
 Calendar
«   2024/05   »
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
 Recent Entries
 Recent Comments
 Recent Trackbacks
 Archive
 Link Site
LeeCreation! Media & Robot
 Visitor Statistics
Total :
Today :
Yesterday :
rss