gimmesilver's blog

Agbird.egloos.com

포토로그



기계 학습과 코딩의 종말 #1 - 논리회로와 뉴럴넷 데이터분석

1. 서론
  얼마 전 wired 에서 'Soon we won't program computers. We'll train them like dogs'라는 글을 읽었습니다. 기존에는 모든 알고리즘을 사람이 직접 코딩했다면 앞으로는 기계가 알고리즘을 스스로 만들도록 훈련시키는 것이 기존 프로그래밍을 대체할 것이라는 내용입니다. Wired는 약 8년 전에 이미 'The end of theory'라는 글로 재미를 본 적이 있는데 이번에도 상당히 논쟁을 불러일으킬만한 글을 실었네요.
  저는 충분히 가능한 시나리오라고 생각합니다. 반대 의견들을 보면 프로그래밍은 단순한 규칙이 아니다, 먼 미래에 가능하겠지만 아직 시기상조다 등의 의견들이 있는데 바둑의 사례를 생각해 보더라도 그다지 먼 미래는 아닐 것 같습니다. 어떤 이는 알고리즘같이 명료한 로직을 어떻게 훈련을 통해 학습시키냐는 의문을 갖습니다. 여기서는 바로 이 부분에 대해 한번 살펴 보고자 합니다.

2. 논리회로
  우리가 프로그래밍이라고 부르는 것들은 어떤 단순한 명령들로 이뤄진 일련의 과정입니다. 이것을 흔히 '알고리즘'이라고 부르죠. 그런데 이런 알고리즘 역시 좀 더 미시적인 관점에서 보면 매우 단순한 논리 연산의 조합으로 표현 가능합니다. 아마 컴퓨터 관련 학과를 전공하신 분들이라면 '논리 회로', '컴퓨터 구조', '운영체제', '컴파일러' 등의 수업을 들으셨을 것입니다. 이 수업들이 포괄적으로 알려 주는 것은 '0과 1로 표현되는 단순한 전기 신호를 조합해서 어떻게 우리는 복잡한 작업을 수행할 수 있는가' 입니다.

  우선 아무리 복잡한 프로그램도 프로그래밍 언어가 제공하는 문법 내에서 모두 표현 가능합니다. 가장 복잡한 언어 중 하나인 scala의 경우 keyword 가 70여개 정도 되고, C언어는 30개가 좀 넘습니다. 이런 keyword와 그 언어가 제공하는 문법을 잘 조합해서 우리는 몇 십만 줄이 넘는 대규모 코딩을 통해 워드도 만들고 엑셀도 만들고 웹 서비스도 제공하고 하는 것이죠. 그리고 이런 프로그램 소스는 다시 좀 더 단순한 문법으로 된 어셈블리어나 기계어로 변환됩니다. 이 단계에서는 아무리 복잡한 알고리즘이나 웹 서비스도 '저장하기', '불러오기', '비교하기', '건너뛰기' 정도의 단순 동작의 조합으로 치환됩니다.
  이런 저수준 로직은 다시 더 단순한 구조로 표현될 수 있습니다. 우리가 흔히 '비트 연산' 혹은 '논리 연산' 이라고 부르는 AND, OR, XOR, NOT 등이 그것입니다. 가령 두 개의 이진수를 비교하는 연산을 논리회로로 표현하면 아래와 같습니다.

<그림1> 1bit 비교연산기의 논리회로

  위 회로에서 A와 B에 0 혹은 1 값을 넣으면 출력으로 A가 B보다 큰지, 작은지 혹은 같은지에 따라 세 개의 출력값 중 하나만 1이 되고 나머지는 0이 출력됩니다. 이와 비슷하게 어셈블리 수준에서 표현되는 명령어들은 논리 연산의 조합으로 모두 표현할 수 있습니다. 실제 디지털 회로들은 이런 논리 연산의 조합을 구현한 하드웨어이며 여기에 입력으로 여러 개의 0와 1의 조합을 주면 원하는 결과가 출력되는 것이죠.

  결국 모든 알고리즘은 위와 같은 아주 단순한 논리 연산을 이리 저리 조합하여 표현할 수 있습니다. 혹시 FPGA(Field Programmable Gate Array)를 해보신 분은 쉽게 납득하실 수 있을 것 같네요. FPGA를 이용하면 엄청나게 많은 수의 NAND(혹은 NOR) 소자의 조합만으로 복잡한 계산기나 임베디드 로직을 구성하여 하나의 칩으로 만들 수 있습니다.

<그림 2> FPGA는 왼쪽 그림처럼 모든 연결 조합이 가능하게 구성된 논리 연산자들을 이용해서,
 오른쪽 그림처럼 원하는 형태의 논리 회로를 구성할 수 있는 반도체 소자입니다.

  물론 현실에서는 대개의 경우 그 조합의 규모와 복잡도가 매우 크기 때문에 중간 중간 적절한 추상화와 계층화를 통해 구현됩니다. 우리가 접하는 하드웨어나 소프트웨어는 ALU나 레지스터, 제어 유닛 등으로 추상화 되거나 OS의 커널, API, 각종 라이브러리 등으로 계층화되기 때문에 우리는 실제 엄청나게 복잡한 로직을 좀 더 간결한 추상 구조로 이해하고 구현할 수 있는 것이죠.
  그런데 이런 추상화가 매우 잘되어 있기 때문에 사람들은 이런 착각을 합니다. '알고리즘과 같이 복잡하고 심오한 개념은 기계가 이해할 수 없을 거야!' 하지만 실상 프로그램이나 알고리즘은 기계가 이해할 수 있을만큼 충분히 단순한 로직만을 활용하여 인간이 도저히 수용하기 힘든 규모의 복잡한 조합을 통해 구현되어 있습니다.

3. Neural network
  위에서 설명한 FPGA나 소프트웨어 프로그램은 사람이 디자인하고 구현한 결과물입니다. 디지털 회로를 설계하는 것도 사람이고 프로그래밍을 하는 것도 사람이죠. 그런데 wired의 기사에서는 이걸 기계가 스스로 구현할 수 있을 것이라고 주장하고 있습니다. 단지 사람은 기계가 로직을 구현할 수 있게끔 '훈련'을 시키면 된다는 것이죠. 다소 터무니없는 주장이라 치부할 수 있는데 이게 이론적으로 가능할 것이라 사람들이 생각하게 된 것은 바로 'deep learning' 때문입니다. 실상 deep learning은 neural network의 advanced 버전이기 때문에 여기서는 앞으로 용어를 neural network 라고 하겠습니다.
  neural network는 일종의 non-linear regression function 입니다. 아주 기본적인 형태는 logistic regression과 똑같이 생겼습니다. 이를 테면 이렇게 생겼죠.

g(x) = 1 / (1 + e^(-x))
f(x) = g(a0 + a1*x1 + a2*x2 + a3*x3 + ... + an *xn)
= 1 / (1 + e^-(a0 + a1*x1 + a2*x2 + a3*x3 + ... + an*xn))

  제가 '이를 테면' 이라는 표현을 사용한 이유는 모든 neural network이 저렇게 생기진 않아서입니다(학습 대상이 어떠냐에 따라 적용하는 비선형 함수(위 식에서 g(x))를 다르게 지정할 수 있습니다). 암튼 위 식은 logistic regression과 똑같습니다. 그런데 neural network는 이걸 좀 더 복잡하게 확장할 수 있습니다. 이를 테면 아래처럼 확장이 가능합니다(아래 식에서 g(x)함수는 위와 동일).

f(k) = g(a0 + a1*k1 + a2*k2 + a3*k3 + ... am*km)
k1 = h1(x) = g(b0 + b1*x1 + b2*x2 + b3*x3 + ... bn*xn)
k2 = h2(x) = g(c0 + c1*x1 + c2*x3 + c3*x3 + ... cn*xn)
...
km = hm(x) = g(d0 + d1*x1 + d2*x3 + d3*x3 + ... dn*xn)

  위 식을 그림으로 표현하면 아래와 같습니다. neural network에서 전 단계의 함수 결과값들은 가중치를 곱한 후 더해서 다음 단계의 g(x) 함수의 입력값으로 들어갑니다. logistic regression은 아래 그림에서 가운데 layer가 없는 형태라고 생각하시면 됩니다.

<그림 3> neural network diagram

  neural network에서는 가운데에 있는 layer(즉, k1, k2, k3, ... km)를 hidden layer라고 부릅니다. 겉으로 드러나지 않은 숨겨진 함수들이기 때문이죠. 마치 2장에서 설명한 추상화 구조와 비슷합니다. 겉으로는 단순히 프로그램만 노출되지만 내부적으로는 라이브러리, API, 어셈블리 명령어 등이 계층적으로 추상화된 구조와 유사합니다. 게다가 neural network는 이 hidden layer를 여러 개로 구성할 수도 있습니다(실제 상용 서비스에 적용되는 neural network는 수십 개 이상의 hidden layer를 구성하는 경우도 있다고 합니다).

  대체 이걸로 무슨 일을 할 수 있을까 싶습니다만 이 구조를 이용하면 이론적으로 어떤 논리 구조도 표현이 가능합니다. 예를 들어 2장에서 소개한 AND, OR, NOT 은 각각 아래와 같이 표현할 수 있습니다.

AND(x1, x2) = g(-30 + 20*x1 + 20*x2) = 1 / (1 + e^(30 -20*x1 - 20*x2))
OR(x1, x2) = g(-10 + 20*x1 + 20*x2) = 1 / (1 + e^(10 - 20*x1 - 20*x2))
NOT(x1) = g(10 - 20*x1) = 1 / (1 + e^(-10 + 20*x1))

<그림 4> 왼쪽부터 각각 AND, OR, NOT 에 대한 neural network diagram

  x1, x2에 각각 0 혹은 1을 넣고 위 식을 계산해 보면 AND 함수는 x1과 x2가 모두 1일 때만 1에 가까운 값이 나오고 나머지 경우에는 0에 가까운 값이 나옵니다. OR함수는 x1, x2가 모두 0일 때만 0에 가까운 값이 나오고 나머지 경우에는 1에 가까운 값이 나오며, 마지막으로 NOT은 x1과 반대의 값에 가까운 값이 나오죠.
  여기까지는 별로 대단할 것이 없지만 이제 이 단순한 구조를 조합하면 좀 더 복잡한 논리 연산도 만들 수 있습니다. 예를 들어 NAND나 XOR 함수는 위에 함수를 조합하여 아래와 같이 표현 가능합니다(수식으로 표현하기는 다소 복잡하니 diagram으로만 표현했습니다).

NAND(x1, x2) = NOT(AND(x1, x2))
XOR(x1, x2) = NOT(OR(AND(x1, x2), AND(NOT(x1), NOT(x2))))

<그림 5> NAND(왼쪽)와 XOR(오른쪽)에 대한 neural network diagram

  이렇듯 모든 논리 연산은 neural network 로 표현할 수 있습니다. neural network의 강력한 점은 바로 이렇게 여러 개의 hidden layer를 통해 어떤 복잡한 논리 연산도 모두 표현 가능하다는 점입니다(마치 FPGA처럼요). 그리고 이것을 통해 다음과 같은 추론이 가능합니다. '모든 프로그램 로직은 neural network로 표현이 가능하다.'

<그림 6> 오른쪽 그림처럼 neural network는 많은 layer와 unit을 이용하면 복잡한 로직도 표현 가능합니다. 
(왼쪽의 FPGA 구조와 왠지 비슷해 보이지 않나요?)

  그런데 위 수식에서 각 입력값의 가중치(diagram에서 화살표에 적혀있는 숫자들)는 어떻게 정해야 할까요? 바로 이 가중치를 적절하게 찾는 과정이 기계 학습에서 말하는 '훈련(training)'입니다. 서론에서 언급한 wired의 글에서는 이제 프로그래밍은 이 훈련으로 대체될 것이라고 얘기했죠. 만약 훈련을 통해 내가 원하는 로직이 되도록 가중치를 자동으로 찾을 수 있다면 기계 학습을 통해 자동으로 프로그래밍을 하는 것이 가능해 보입니다. 그렇다면 이 훈련은 어떻게 해야 할까요? 이 훈련 과정에 대해서는 다음 글에서 다루겠습니다.

핑백

덧글

댓글 입력 영역