페이지

2022년 9월 3일 토요일

Defin-by-Run

 딥러닝 프레임워크는 동작 방식에 따라 크게 두 가지로 나눌 수 있습니다. 하나는 '정적 계산 그래프' 혹은 'Defin-and-Run' 방식이며, 다른 하나는 '동적 계산 그래프' 혹은 'Define-by-Run'방식입니다. 이번 컬럼에서는 이 두 방식을 설명하며 장단점도 함께 알아보겠습니다.


Defin-and-Run(정적 계산 그래프 방식)

Define-and-Run을 직역하면 '계산 그래프를 정의한 다음 데이터를 흘려보낸다'라는 뜻입니다. 계산 그래프 정의는 사용자가 제공하고, 프레임워크는 주어진 그래프를 컴퓨터가 처리할 수 있는 형태로 변환하여 데이터를 흘러보내는 식입니다. 이 흐름을 그림으로 표현하면 [그림 B-1]과 같습니다.



[그림  B-1]에서 보듯 프레임워크는 계산 그래프의 정의를 변환합니다. 이 책에서는 이 변환을 편의상 '컴파일'이라고 부르겠습니다. 컴파일에 의해 계산 그래프가 메모리상에 펼쳐지며, 실제 데이터를 흘려 보낼 준비가 갖쳐집니다. 여기서 중요한 점은 '계산 그래프정의'와 '데이터 흘려보내기' 처리가 분리되어 있다는 것입니다. 다음 의사코드(pscudocode)를 보면 더 명확하게 이해될 것입니다.

# 가상의 Define-and-Run 방식 프레임워크용 코드 예


# 계산 그래프 정의

a = Variable('a')

b = Variable('b')

c = a + b

d = c +  Constaint(1)


# 계산 그래프 컴파일

f = compile(d)


# 데이터 흘려보내기

d = f(a = np.array(2), b = np.array(3))


주석을 제외한 첫 네 줄로 계산 그래프를 정의했습니다. 주의할 점은 이 네 줄의 코드에서는 실제 계산이 이루어지지 않는다는 사실입니다. 실제 '수치'가 아닌 '기호(symbol)'를 대상으로 프로그래밍했기 때문입니다. 참고로 이런 프로그래밍 방식을 '기호 프로그래밍(symbolic programming)'이라고 합니다.

이와 같이 Defin-and -Run 방식 프레임워크에서는 실제 데이터가 아닌 기호를 사용한 추상적인 계산 절차를 코딩해야 합니다. 그리고 도메인 특화 언어(Domain Specific Language(DSL)를 사용해야 하죠. 여기서 도메인 특화 언어란 프레임워크 자체의 규칙들로 이루어진 언어를 뜻합니다. 앞의 예에서는 '상수는 Constant에 담아라' 라는 규칙을 따라야 합니다. 그 외에도, 예컨대 조건에 따라 분기하고 싶다면 if 문에 해당하는 특수 연산을 이용해야 합니다. 이것도 물론 도메인 특화 언어의 규칙입니다. 참고로 텐서플로에서는 if문의 역할로 tf.cond라는 연산을 사용합니다. 다음은 실제 텐서플로 코드 예입니다.



주석을 제외한 첫 네 줄로 계산 그래프를 정의했습니다. 주의할 점은 이 네줄의 코드에서는 실제 계산이 이루어지지 않는다는 사실입니다. 실제 '수치'가 아닌 '기호(symbol)'를 대상으로 프로그래밍됐기 때문입니다. 참고로 이런 프로그래밍 방식을 '기호 프로그래밍(symbolic programning)'이라고 합니다.

이와 같이 Define-and-Run방식 프레임워크에서는 실제 데이터가 아닌 기호를 사용한 추상적인 계산 절차를 코딩해야 합니다. 그리고 도메인 특화 언어(Domain-Specific Language. DSL)를 사용해야 하죠. 여기서 도메인 특화 언어란 프레임워크 자체의 규칙들로 이루어진 언어를 뜻합니다. 앞의 예에서는 '상수는 Constant에 담아라'라는 규칙을 따라야 합니다. 그 외에도, 예컨대 조건에 따라 분기하고 싶다면 if문에 해당하는 특수 연산을 이용해야 합니다. 이것도 물론 도메인 특화 언어의 규칙입니다. 참고로 텐서플로에서는 if문의 역할로 tf.cond라는 연산을 사용합니다. 다음은 실제 텐서플로 코드예입니다.

import tensorflow as tf


flg = tf.placeholder(dtype = tf.bool)

x0 = tf.placeholder(dtype = tf.float32)

x1 = tf.placeholder(dtype = tf.float32)

y = tf.cond(flg, lambda: x0 + x1, lambda: x0 * x1)


텐서플로는 이와 같이 데이터를 저장하는 플레이스홀더(tf.placeholder)로 이루어진 계산 그래프를 만듭니다. 마지막 줄에서는 tf.cond라는 연산을 사용하여 실행 시 flg값에 따라 처리 방식을 달리합니다. tf.cond 연산자이 파이썬의 if문 역할을 해주는 것입니다.


Define-and-Run 방식 프레임워크의 대부분은 도메인 특화 언어를 사용하여 계산을 정의합니다. 도메인 특화 언어는 한마디로 '파이썬 위에서 동작하는 새로운 프로그래밍 언어'라고 할수 있습니다(if문이나 for문 같은 흐름 제어용 명령이 따로 있다는 사실을 생각하면 '새로운 프로그래밍 언어'라고 불러도 이상하지 않을 것입니다). 그리고 미분을 하기 위해 설계된 언어이기도 합니다. 이러한 이유 때문에 최근에는 딥러닝 프레임워크를 일컬어 밉분 가능 프로그래밍(differentable programming)이라고도 합니다.


이상이 Define-and-Run의 개요입니다. 딥러닝 여명기에는 Define-and-Run 방식 프레임워크가 대부분을 차지했습니다. 대표적으로 텐서플로, 카페, CNTK가 있죠(텐서플로는 2.0부터 Define-by-Run방식도 도입했습니다). 그리고 다음세대로 등장한 것이 우리 DeZero도 채용할 Define-by-Run입니다.


Define-by-Run(동적 계산 그래프 방식)

Define-by-Run이라는 용어는 '데이터를 흘려보냄으로써 계산 그래프가 정의된다'라는 뜻입니다. '데이터 흘러보내기'와 '계산 그래프 구축'이 동시에 이루어진다는 것이 특징입니다.


DeZero의 경우 사용자가 데이터를 흘려보낼때(일반적인 수치 계산을 수행할 때) 자동으로 계산 그래프를 구성하는 '연결(참조)'을 만듭니다. 이 연결이 바로 DeZero의 계산 그래프에 해당 합니다. 구현 수준에서는 연결 리스트(linked list)로 표현되는 데, 연결 리스트를 사용하면 계싼이 끝난 후 헤딩 연결을 역방향으로 추적할 수 있기 때문입니다.


Define-by-Run 방식 프레임워크는 넘파이를 상요한느 일반적인 프로그래밍과 똑같은 형태로 코딩할 수 있습니다. 실제로 DeZero를 사용하면 코들르 다음과 같이 작성할 수 있습니다.

import numpy as np

from dezero import Variable


a = Variable(np.ones(10))

b = Variable(np.ones(10) * 2)

c = b * a

d = c + 1

print(d)


넘파이를 사용한 일반적인 프로그램과 흡사하죠, 유일한 차이는 넘파이 데이터를 Variable이라는 클래스로 감쌌다는 점입니다. 그 외에는 넘파이를 사용한 보통의 코드와 같고, 결괏값도 코드가 실행되면 즉시 구해집니다. 그리고 백그라운드에서는 계산 그래프를 위한 연결이 자동으로 만들어집니다.


Define-by-Run 방식은 2015년에 체이너(Chainer)에 의해 처음 제창되고, 이후 많은 프레임워크에 채용되고 있습니다. 대표적으로 파이토치, MXNet, DyNet, 텐서플로(2.0 이상에서는 기본값)를 들수 있습니다.


동적 계산 그래프 방식의 장점

동적 계산 그래프 프레임워크에서는 일반 넘파이를 사용할 때와 같은 방식으로 수치 계산이 가능합니다. 따라서 프레임워크 고유의 '도메인 특화 언어'를 배우지 않아도 됩니다. 계산 그래프를 '컴파일'하여 독자적인 데이터 구조로 변환할 필요도 없습니다. 즉, 일반 파이썬 프로그래밍으로 계산 그래프를 구축하고 실행할 수 있습니다. 파이썬의 if문이나 for문 등을 그래도 사용하여 계산 그래프를 만들 수 있다는 뜻이죠. 실제로 DeZero의 경우 다음과 같이 코딩이 가능합니다.

x = Variable(np.array(3.0))

y = Variable(np.array(0.0))


while True:

    y = y + x

    if y.data > 100:

        break

        

y.backward()

이와 같이 계산에 while문이나 if문을 사요여할 수 있습니다. 그러면 계산 그래프(DeZero의 경우는 계산 그래프를 이루는연결)가 자동으로 만들어집니다. 앞의 예에서는 while문과 if문만 사용했지만 클로저나 재귀 호출 등 파이썬에서 사용할 수 있는 프로그래밍 기법이라면 그대로 DeZero에서도 사용할 수 있습니다.


동적 계산 그래프는 디버깅에도 유리합니다. 계산 그래프가 파이썬 프로그램형태로 실행되기 때문에 디버깅도 항상 파이썬 프로그램으로 할 수 있습니다. pdb 같은 파이썬 디버거를 사용할 수 있다는 뜨시죠. 이에 반해 정적 계산 그래프 프레임워크에서는 컴파일을 거쳐 프레임워크만 이해하고 실행할 수 있는 표현 형식으로 변환됩니다. 당연히 파이썬(파이썬 프로세서)은 이 독자적인 표현 형식을 이해할 수 없습니다.

또한 정적 계산 그래프에서 디버깅이 어려운 본질적인 이유는 '계산 그래프 정의'와 '데이터 흘러보내기'작업이 분리되어 있다는데 있습니다. 왜냐하면 문제(버그)는 주로 데이터를 흘러보낼 때 발견되지만, 문제의 원인은 '계산 그래프 정의'에 있는 경우가 대부분이기 때문입니다. 다시 말해 문제 발생 시점과 원인이 만들어지는 시점이 떨어져 있어서 어디가 문제인지 특정하기 어려울 때가 많습니다.


정적 계산 그래프(Define-and-Run 방식)프레임워크는 데이터를 흘려보내기에 앞서 계산 그래프를 정의해야 합니다. 따라서 데이터를 흘려보내는 동안은 계산 그래프의 구조를 바꿀 수 없습니다. 또한 if문에 대응하는 tf.cond같은 전용 연산의 사용법을 익혀야 해서 프로그래머가 도메인 특화 언어를 새롭게 ㄷ배워야 하는 부담이 생깁니다.


정적 계산 그래프 방식의 장점

정적 계산 그래프의 가장 큰 장점은 성능입니다. 계산 그래프를 최적화하면 성능도 따라서 최적화됩니다. 그래서 계산 그래프 최적화는 계산 그래프의 구조와 사용되는 연산을 효울적인 것으로 변환하는 형태로 이뤄집니다. [그림 B-2]는 간단한 예입니다.


[그림 B-2]는 a * b + 1이라는 계산 그래프와 이를 최적화한 계산 그래프를 보여줍니다. 최적화 버젼에서는 곱셈과 덧셈을 한번에 수행하는 연산을 사용했습니다(많은 하드웨어에서 덧셈과 곱셈을 동시에 수행하는 명령을 제공합니다). 이 변환으로 인해 '두 개의 연산'을 '하나의 연산'으로 '축약'하여 계산 시간이 단축됩니다.


이처럼 작은 수준의 최적화뿐 아니라 꼐산 그래프 전체를 파악한 후 큰 그림에서 최적화 할 수도 있습니다. Define-and-Run 방식의 프레임워크는 데이터를 흘러보내기 전에 전체 계산 그래프가 손에 들어오므로 계산 그래프 전체를 고려해 최적할 수 있습니다. 예를 들어 for문 등에서 반복해 사용하는 연산을 하나로 '축약'하여 계{산 효율을 크게 끌어올릴 수 있는 경우도 있습니다.


신경망 학습은 주로 '신경망을 한 번만 정의하고, 정의된 신경망에 데이터를 여러번 흘려 보내는 '형태로 활용됩니다. 따라서 신경망 구축과 최적화에 시간을 조금 더 들이더라도 데이터를 반복해 흘려보내는 단계에서 만회할 가능성이 큽니다.


Define-and-Run방식 프레임워크의 또 다른 장점은 어떻게 컴파일하느냐에 따라 다른 실행 파일로 변환할 수 도 있다는 것입니다. 따라서 파이썬이 아닌 다른 환경에서도 데이터를 흘려보내는 게 가능합니다. 파이썬에서 벗어났을 때 얻는 가장 큰 혜택은 파이썬 자체가 주는 오버헤드가 사라진다는 것입니다. IoT기기처럼 자원이 부족한 에지(edge)전용 환경에서는 특히 중요한 특징입니다.


또한 ㅅ학습을 여러 대의 컴퓨터에 분산해 수행하는 경우에도 Define-and-Run 방식이 유리할 때가 있습니다. 특히 계산 그래프 자체를 분할하여 여러 컴퓨터로 분배하는 시나리오는 사전에 계산 그래프가 구축되어 있어야만 가능합니다. 따라서 Define-and-Run방식의 프레임워크가 유리합니다.


정리

지금까지 Define-and-Run과 Define-by-Run 각각은 나른의 장단이 있다고 설명 했습니다. 정리하면 다음과 같습니다.


정적 계산 그래프와 동적 계산 그래프 비교

Define-and-Run(정적계산 그래프)

장점: 성능이 좋다

신경망 구조를 최적화하기 쉽다.

분산 학습 시 더 편리하다


단점: 독자적인 언어(규칙)를 익혀야 한다.

동적 계산 그래프를 만들기 어렵니다.

디버깅하기 매우 어려울 수 있다.


 Define-by-Run(동적 계산 그래프)

-파이썬으로 계산 그래프를 제어할 수 있다.

- 디버깅이 쉽다.

- 도엊ㄱ인 계산 처리에 알맞다


단점: 성능이 낮을 수 있디.


[표 B-1]과 같이 두 방식은 나름의 장단점이 있습니다. 간단하게 정리하면 성능이 중요할 때는 Define-and-Run이 유리하고, 사용성이 중요할 때는 Define-by-Run이 휠씬 유리합니다.


어느 한 방식이 절대적이지 않기 때문에 두 모드를 모두 지원하는 프레임워크도 많습니다. 예를 들어 파이토치는 기본적으로 동적 계산 그래프 모드로 수행되지만 정적 계산 그래프 모드도 제공합니다(자세한 내용은 TorchScript 참고),  마찬가지로 체이너도 기본은 Define-by-Run이지만 Define-and-Run모드로 전환할 수 있습니다. 텐서플로 역시 2.0부터 Eager Execution이라는 동적 계산 그래프 모드가 표준으로 채택되었으며, 필요시 정적 계산 그래프로 전환할 수 있습니다.

또한 최근에는 프로그래밍 언어 자체에서 자동 미분을 지원하려는 시도로 볼수 있습니다. 유명한 예로는 Swift for TensorFlow를 들 수 있습니다. 스위프트(Swift)라는 범용 프로그래밍 언어를 확장하여(스위프트 컴파일러를 손질하여) 자동 미분 구조를 도입하려는 시도입니다. 자동 미분을 프로그래밍 언어 차원에서 지원하므로 성능과 사용성이라는 두 마리 토끼를 모두 잡을 수 있으리라 기대되고 있습니다.

24.3 Goldstein-Price 함수

 Goldstein-Price 함수를 수식으로 표현하면 다음과 같습니다.

f(x, y) = [ 1 + ( x + y + 1) ** 2 * (19 - 14x + 3 * x ** 2 - 14 * y + 6 * x * y + 3 * y ** 2)]

            [30 + ( 2 * x - 3 * y) ** 2 * (18 - 32 * x + 12 * x ** 2 + 48 * y - 36 * x * y + 27 * y ** 2)]

꽤 복잡해 보이지만 DeZero라면 어렵지 않게 표현할 수 있습니다. 직접 보시죠.


def goldstein(x, y):

    z = ( 1 + ( x + y + 1) ** 2 * ( 19 - 14*x + 3*x**2 - 14*y + 6*x*y * 3*y**2)) * \

        (30 + (2*x - 3*y)**2 * (18 - 32*x + 12*x**2 + 48*y - 36*x*y + 27*y**2))

    return z


수식과 비교해가며 코드로 옮기면 금방 끝날 것입니다. 반면 이 연산자들을 사용하지 않고 코딩하기란 보통 사람에게는 불가능할지 모릅니다. 그럼 Goldstein-Price 함수를 미분해볼까요?

x =Variable(np.array(1.0))

y = Variable(np.array(1.0))

z = goldstein(x, y)

z.backward()

print(x.grad, y.grad)


variable(-10074.0) variable(60336.0)

x에 대한 미분은 -5376.0이 나왔고, y에 대한 미분은 8064.0이 나왔습니다. 물론 올바른 결과입니다. 보다시피 DeZero는 Goldstein-Price 함수와 같은 복잡한 계산도 훌륭하게 미분할 수 있답니다! 또한 이 결과가 맞는지는 기울기 확인으로 검증할 수 있습니다. 이것으로 두 번째 고지도 무사이 정복했습니다.


이번 제2고지에서 DeZero가 크게 성장했습니다. 고지 점령을 시작할 무렵의 DeZero는 간단한 계산밖에 할 수 없었지만 지금은 복잡한 계산도 가능하게 되었습니다.(엄밀히 말하면 아무리 복잡하게 '연결'된 계산 그래프라도 올바르게 역전파할 수 있습니다). 또한 연산자를 오버로드한 덕에 보통의 파이썬 프로그래밍처럼 코드를 작성할 수 있습니다. 일반적인 파이썬 연산자를 이용해도 미분을 자동으로 계산할 수 있기 때문에 DeZero는 '일반적인 프로그래밍'을 '미분 가능'하게 만들었다고 표현할 수도 있습니다.

이제 DeZero의 기초는 충분히 닦았습니다. 다음 단계부터는 더 고급 계산도 처리할 수 있도록 DeZero를 확장해갈 것입니다.

24.2 matyas 함수

 이어서 matyas 함수를 살펴보죠. 수식으로는 z = 0.26(x ** 2 + y ** 2) - 0.48xy이며, DeZero로는 다음처럼 구현할 수 있습니다.

def matyas(x, y):

    z =  0.26 * ( x ** 2 + y ** 2) - 0.489 * x * y

    return z


x = Variable(np.array(1.0))

y = Variable(np.array(1.0))

z = matyas(x, y)

z.backward()

print(x.grad, y.grad)


variable(0.031000000000000028) variable(0.031000000000000028)

이번에도 수식을 그대로 코드로 옮길 수 있었습니다. 사칙연산 연산자는 자유롭게 사용할 수 있으므로 손쉽게 해결됐습니다. 만약 이 연산자들을 사용할 수 없다면 matyas함수를 다음과 같이 작성해야 합니다.

def matyas(x, y):

    z = Sub(mul(0.26, add(pow(x, 2), pow(y, 2))), mul(0.48, mul(x, y)))

    return z

사람에게는 읽기 어려운 코드죠, 이제 +와 ** 같은 연산자를 사용할 수 있다는게 얼마나 고마운 일인지 느껴질 것입니다. 이 연산자들 덕분에 타이핑 양도 줄이면서 일반 수식에 가까운 형태로 읽고 쓸 수 있는 것이죠, 그럼 마지막으로 Goldstein-Price 함수라는 복잡한 수식에 도전해 봅시다.


24.1 Sphere 함수

 Sphere 함수를 수식으로 표현하면 z =  x **  2 + y ** 2 입니다. 단순히 두 개의 입력 변수를 제곱하여 더하는 함수죠. 우리가 할 일은 그 미분(dz/dx와 dz/dy)을 계산하는 것입니다. 이번 절에서는 (x, y) = (1.0, 1.0)인 경우를 미분해보겠습니다. 코드는 다음과 같습니다.

import numpy as np

from dezero import Variable


def sphere(x, y):

    z = x ** 2 +  y ** 2

    return 


x = Variable(np.array(1.0))

y = Variable(np.array(1.0))

z = sphere(x, y)

z.backward()

print(x.grad, y.grad)

코드에서 보듯 원하는 계산을 z = x ** 2  + y ** 2로 표현할 수 있습니다. 그리고 x와 y에 대한 미분 모두 2.0이라고 나옵니다. 수식으로 확인하면 dx/dy = 2x, dz/dy = 2y가 되므로 ( x, y) = (1.0, 1.0)의 미분 결과는 (2.0, 2.0)입니다. 앞의 실행 결과와 일치하는 군요.

STEP 24 복잡한 함수의 미분

 DeZero는 이제 대표적인 연산자들(+, *, -, /, **)을 지원합니다. 따라서 평소 파이썬 프로그래밍을 하듯 코딩할 수 있습니다. 이 해택은 복잡한 수식을 코딩할 때 피부로 느껴질 것입니다. 그래서 이번 단계에서는 지금까지의 성과를 느낄 수 있는 복잡한 수식의 미분 몇 가지를 풀어보겠습니다.

이번 단계에서 다루는 함수들은 최적화 문제에서 자주 사용되는 테스트 함수입니다. 최적화 문제의 테스트 함수란 다양한 최적화 기법이 '얼마나 좋은가'를 평가하는 데 사용되는 함수를 뜻합니다. '벤치마크'용 함술하고 할 수 있겠네요. 테스트 함수에도 종류가 많은데, 위키배과의 'Test functions for optimization'페이지를 보면 대표적인 예를 확인할 수 있으며, [그림 24-1]과 같은 표로 정리되어 있습니다.

[그림 24-1]은 일부만 발췌한 것이며, 우리는 이 주 세 함수를 선택하여 실제로 미분해보려 합니다. 그러면 DeZero의 실력이 어느 정도인지 알 수 있겠죠. 우선 Sphere라는 간단한 함수에서 시작하겠습니다.

23.5 dezero 임포트하기

 이렇게 하여 dezero라는 패키지가 만들어졌습니다. 이제 이번 단계용 step23.py는 다음처럼 작성할 수 있습니다.

if '__file__' in globals():

    import os, sys

    sys.path.append(os.path.join(os.path.dirnmae(__file__), '..'))

    

import numpy as np

from dezero import Variable


x = Variable(np.array(1.0))

y = ( x + 3 ) ** 2

y.backward()


print(y)

print(x.grad)


variable(16.0)
variable(8.0)

우선 if '__file__' in globals(): 문장에서 __file__이라는 변수가 정의되어 있는지 확인합니다. python step23.py처럼 터미널에서 python 명령으로 실행한다면 __file__ 변수가 정의되어 있습니다. 이 경우 현재 파일(step23.py)이 위치한 디렉터리의 부모 디렉터리(..)를 모듈 검색 경로에 추가합니다. 이로써 파이썬 명령어를 어디에서 실행하든 dezero디렉터리의 파일들은 제대로 임포트할 ㅅ구 있게 됩니다. 예를 들어 명령줄에서 python steps/step23.py형태로 실행하든 cd steps; python step23.py 형태로 디렉터리를 옮겨 실행하든 상관없이 코드가 정상 작동합니다. 참고로 책 지면에서는 편의상 이 모듈 검색 경로 추가 코드는(매번 똑같이 반복되므로) 생략하고 보여줍니다.

검색 경로 추가 코드는 현재 개발 중인 dezero 디렉토리를 임포트하기 위해 일시적으로 사용하는 것입니다.(예를 들어 pip install dezero등의 명령으로) DeZero가 패키지로 설치된 경우라면 DeZero패키지가 파잇썬 검색 검토에 추가됩니다. 따라서 앞에서와 같이 경로를 수동으로 추가하는 일은 필요치 않게 됩니다. 또한 __file__ 변수는 파이썬 인터프리터의 인터렉티브 모드와 구글 콜랩(Google Colab)등의 환경에서 실행하느 경우에는 정의되어 있지 않습니다. 이점을 고려하여 (step 파일을 수정 없이 구글 콜랩에서도 동작하도록 하기 위해) 부모 디렉터리를 검색 경로에 추가될 때 if '__file__' in globals(): 라는 조건 검사 문장을 넣었습니다.


방금 보여준 코드가 step23.py의 전부입니다(생략한 코드는 없습니다). 이것으로 DeZero프레임워크의 원형이 와성되었습니다. 앞으로는 dezero디렉토리에 있는 파일(모듈)들을 확장하는 식으로 진행하겠습니다.



2022년 9월 2일 금요일

23.4 실제 __init__.py 파일

 이 책에서는 앞으로(23단계에서 32단계까지) DeZero코어 파일로 dezero/core_simple, py를 사용합니다. 그러다가 33단계부터는 dezero/core.py로 대체할 것입니다. 그래서 실제 dezero/__init__.py는 core_simple.py와 core.py중 하나를 선택해 임포트하도록 작성되어 있습니다.

is_simple_core = True


if is_simple_core:

    from dezero.core_simple import Variable

    from dezero.core_simple import Function

    from dezero.core_simple import using_config

    from dezero.core_simple import no_grad

    from dezero.core_simple import as_array

    from dezero.core_simple import as_variable

    from dezero.core_simple import setup_variable

    

else:

    from dezero.core import Variable

    from dezero.core import Function

    ...

    ...

    

setup_variable()

이와 같이 is_simple_core 플래그로 임포트할 대상을 선택합니다. is_simple_core가 True면 core_simple.py에서, False면 core.py에서 임포트가 이루어집니다.


코드를 실습해보려면 is_simple_core 플래그값을 적절히 수정하여 사용하기 바랍니다. 32단계까지는 True로 설정하고, 33단계부터는 False로 바꿔주면 됩니다.