페이지

2022년 9월 3일 토요일

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로 바꿔주면 됩니다.

23.3 연산자 오버로드

 이것으로 step22.py의 코드 대부분이 옮겨졌습니다. 이제부터는 오버로드한 연산자들을 dezero로 옮기겠습니다. 이를 위해 코어 파일인 dezero/core_simple.py에 다음 함수들을 추가합니다.

def setup_variable():

    Variable.__add__ = add

    Varaibel.__radd__ = add

    Variable.__mul__ = mul

    Variable.__rmul__ = mul

    Variable.__neg__ = neg

    Variable.__sub__ = sub

    Variable.__rsub__ = rsub

    Variable.__truediv__ = div

    Variable.__rtruediv__ = rdiv

    Variable.__pow__ = pow


setup_variable은 Variable의 연산자들을 오버로드해주는 함수입니다. 이 함수를 호출하면 Variable의 연산자들이 설정됩니다. 그렇다면 이 함수는 어디에서 호출하면 좋을까요? 바로 dezero/__init__.py 파일입니다.


__init__.py는 모듈을 임포트할 때 가장 먼저 실행되는 파일입니다. 우리의 경우 dezero패키지에 솏한 모듈을 임포트할 때 dezero/__init__.py의 코드가 첫 번째로 호출됩니다. 그래서 dezero/__init__.py에 다음 코드를 작성해 넣어야 합니다.

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


setup_variable()

이와 같이 setup_variable 함수를 임포트해 호출하도록 합니다. 이렇게 함으로써 dezero 패키지를 이용하는 사용자는 반드시 연산자 오버로드가 이루어진 상태에서 Variable을 사용할 수 있습니다.

한편 __init__.py의 시작이 from dezero.core_simple import Variable인데, 이 문장이 실행됨으로써 dezero 패키지에 Variable 클래스를 곧바로 임포트할 수 있습니다. 옐르 들어 다음과 같이 이용할 수 있습니다.

# dezero를 이용하는 사용자의 코드


# from dezero.core_simple import Variable

from dezero import Variable


즉, 지금까지 from dezero.core_simple import Variable이라고 작성한 것을 from dezero import Variable처럼 짧게 줄일 수 있습니다. 마찬가지로 dezero/__init__.py의 임포트문들 덕분에 사용자는 나머지 Function이나 using_config 등도 '간소화된' 임포트를 이용할 수 있게 됩니다.



23.2 코어 클래스로 옮기기

 dezero 디렉터에 파일을 추가해보죠. 목표는 이전 단계의 step22.py 코드를 dezero/core_simple.py라는 코어(core,핵심)파일로 옮기는 것입니다. 파일 이름에 core를 붙인 이유는 지금까지 구현한 기능들이 DeZero의 핵심이라고 보기 때문입니다. 그리고 뒤에 가서는 최종 형태인 core.py로 교체할 계획이라서 당장은 core_simple.py로 시작하겠습니다.


그럼 step22.py에 정의된 다음 클래스들을 코어 파일로 복사해봅시다.

- Config

- Variable

- Function

- Add(Function)

- Mul(Function)

- Neg(Function)

- Sub(Function)

- Div(Function)

- Pow(Function)


여기에서 Add(Function)의 (Function)은 Function 클래스를 상속했다는 뜻입니다. 보다시피 Config, Variable, Function 클래스가 있고, Function 클래스를 상속한 함수(DeZero함수 클래스)가 여럿 개 있습니다. 이어서  step22.py에 정의한 파이썬 함수들도ㅓ 정리해야 합니다. 즉, 다음 함수들을 코어 파일로 옮길 것입니다.


- using_config

- no_grad

- as_array

- as_variable

- add

- mul

- neg

- sub

- rsub

- div

- rdiv

- pow


처음 두 함수는 DeZero설정 함수로, 역전파의 활성/비활성을 전환하는 데 상요합니다. 그다음의 as_array와 as_variable은 인수로 주어진 객체를 ndarray 또는 Variable로 변환하는 함수입니다. 나머지는 DeZero에서 사용하는 함수입니다. 자, 우선 step22.py에 담긴 클래스와 함수를 그대로 코어 파일에 복사합니다.


지금까지 Exp 클래스와 Square클래스 그리고 exp 함수와 square함수 등 DeZero에서 사용하는 구체적인 함수들도 구현했습니다. 하지만 이 코드들은 코어 파일에 넣지 않겠습니다. 이 코득들은 나중에 dezero/functions.py에 추가 할 겁니다.


이제 외부의 파이썬 파일에서 다음과 같이 dezero를 임포트할 수 있습니다.

import numpy as np

from dezero.core_simple import Variable


x = Variable(np.array(1.0))

print(x)


이와 같이 from dezero.core_simple import Variable 줄을 추가하여 Variable 클래스를 임포트할 수 있습니다. dezero.core_simple 처럼 파일 이름까지 명시한 점에 주의하세요. 바로 뒤에서 core_simple을 생략하고 from dezero import Variable로 사용할 수 있는 구조를 도입할 겁니다.


from ... import ... 구문을 사용하면 모듈 내의 클레스나 함수 등을 직접 임포트할 수 있습니다. 또한 import XXX is A라고 쓰면 XXX라는 모듈을 A라는 이름으로 임포트할 수 있습니다. 옐르 들어 import dezero.core_simple as dx 쓰면 dezero.core_simple모듈을 dz라는 이름으로 임포트합니다. 그런 다음 Variable 클래스를 사용하려면 dz.Variable이라고 쓰면 됩니다.