0️⃣

[DeepLearning from Scrach 3]: DeZero 정복하기 - Chapter1

DeZero Library?

DeZero 라이브러리는 체이너를 기초로 Pytorch의 설계를 씌웠습니다.
Tensorflow도 이 형식을 채택하였으며, 다음과 같은 특징이 있습니다.
미니멀리즘
DeZero의 최우선 목표는 이해하기 쉽게 하기 입니다. 외부 Library 사용을 최소화하고, 코드 양도 최소화했습니다.
순수 Python
DeZero는 Python만으로 작성되었기에 (C, C++ 등 사용안함) Python만 알아도 읽을 수 있고, Colab 등의 Cloud 환경, 심지어 스마트폰까지 어렵지 않게 동작하는 호환성을 지니고 있습니다.
현대적인 기능
앞으로 책에서는 이 라이브러리를 5개의 고지, 그리고 60개의 단계로 나눠 직접 만들어 봅니다.
같이 여정을 떠나시죠!
들어가기 전에: 이 블로그는 python을 모르는 분을 위한 글이 아닙니다. (그런데 나도 잘 모르는 건 함정)

0고지. 클래스 구조 설명

Variable은 Function과 creator 관계
Function은 Variable에 …

제 1고지: 미분 자동 계산

변수 구현

책의 1.1장 ~ 1.2장에 해당합니다.
class Variable: def __init__(self, data): self.data = data
Python
복사
1-1-1 [Variable implement]
import numpy data = numpy.array(1.0) x = Variable(data) print(x.data) x.data = numpy.array(4.2) print(x.data)
Python
복사
1-1-2-1 [Variable Example Result]
출력 결과는 다음처럼 잘 나올겁니다.
1.0 4.2
Python
복사
1-1-2-2 [Variable Example Result]
참고: data에 numpy를 사용하였기에 ndim(Number of Dimention)도 사용할 수 있습니다. 시도해 보세요!

함수 만들기

자! 대망의 함수입니다.
class Function: def __call__(self, input): # x에 input 값을 넣습니다. x = input.data # 원하는 연산을 실행합니다. (y^2 + 1)입니다. y = self.forward() rst = Variable(y) return rst def forward(self, x): raise NotImplementedError()
Python
복사
1-2-1 [Function Implement]
위의 1-1-2 [Variable Example]처럼 예제를 만들어 실행시켜보세요.
아마 지금은 NotImplementedError가 뜰 겁니다.
여기서 상속받아 입력값을 제곱하는 클래스를 구현하겠습니다.
이름은 Square입니다.
직접 작성하신 뒤, 다음의 코드를 확인해 보세요.
실행 예시는 제공해드리겠습니다.
<class '__main__.Variable'> 100
Python
복사
1-2-2-1 [Function Example Result]
Code (답)

함수 연결

함수 연결을 위해 먼저 Exp 함수를 구현해보겠습니다.
Exp 함수에 대한 설명은 다음을 참고해주세요.

Exp함수란?!?! 구현까지!

Exp함수는 exe^x를 가르킵니다.
ee는 자연로그의 밑으로, 구체적인 값은 2.718...입니다.
오일러 상수, 네이피어 상수라고도 불립니다.
class Exp(Function): def forward(self, x): return numpy.exp(x)
Python
복사
1-3-1 [Exp Implement]
코드가 굉장히 간단하죠?
Function을 상속받은 class Exp를 생성한 뒤, forward 함수에 입력받은 xnumpy.exp 연산 후 돌려주는 작업만 해주면 됩니다.

“함수 연결”

다음 연산을 한다고 생각해봅시다.
y=(ex2)2y=(e^{x^2})^2
흠… 복잡하죠?
그치만 코드로 하면 간단합니다.
F = Square() G = Exp() H = Square() x = Variable(numpy.array(2)) y1 = F(x) y2 = G(y1) y3 = H(y2) print(y3.data)
Python
복사
1-3-2-1 [Exp Example]
F, G, H 함수에 각각 Square, Exp, Square를 넣어준 뒤 순서대로 실행해서 적용만 시켜주면 됩니다.
실행 결과는 다음과 같습니다.
2980.957987041728
Python
복사
1-3-2-1 [Exp Example Result]
합성 함수의 개념을 알고 계신다면 이걸 이해하는데 도움이 더 될 것 같습니다.
언뜻 복잡해 보이는 연산이더라도, 단위단위로 자르면 더 간단해집니다.
1-3-3 [Calculation Graph]
이런 모양의 그래프를 계산 그래프 라고 부릅니다.
계산 그래프는 각 변수에 대한 미분을 효율적으로 계산할 수 있도록 도와줍니다.
이렇게 변수 별 미분을 계산하는 알고리즘이 바로 역전파 알고리즘입니다.

수치 미분

지금까지 구현한 Function 알고리즘을 통해 미분을 자동으로 계산하는 클래스를 만들겁니다.
f(x)=limh0f(x+h)f(h)h\displaystyle f'(x)=\lim_{h\to0}{{f(x+h)-f(h)}\over{h}} 1-4-1 [미분 식]

구현하기

극한의 개념을 기억하시죠? 1-4-1의 미분 식에서 볼 수 있는 “한없이 작은 값” h는 컴퓨터 상에서 구현될 수 없기에 임의 값을 사용합니다.
바로 104=0.0001=1e410^{-4}=0.0001=1e-4입니다.
이런 미세한 차이를 이용해 함수의 변화량을 구하는 작업을 수치 미분(numerical diffrentiation)이라고 합니다.
수치 미분은 작은 값을 사용해 미분값을 찾아내는데, 어쩔 수 없이 오차가 존재합니다.
따라서 일반적인 미분이 아닌 중앙차분(centered diffrerence)을 사용합니다.
1-4-2 [중앙차분 식]
f(x)=limh0f(x+h)f(xh)2hf'(x)=\displaystyle\lim_{h\to0}{{f(x+h)-f(x-h)}\over{2h}}
보시는 것처럼 f(x+h)f(x)f(x+h)-f(x)가 아닌 f(x+h)f(xh)f(x+h)-f(x-h)를 합니다.
진짜 접선을 구하는거죠!
이렇게 구하면 오차가 상대적으로 줄어듭니다.
주의할 점은 분모가 2h라는 겁니다.
이 중앙차분을 이용한 수치미분을 numerical_diff(_func, _x, _eps=1e-4)로 구현해보겠습니다.
_func은 미분이 대상이 되는 함수입니다. Function의 instance입니다.
_x는 미분을 계산하는 변수입니다. Variable의 instance입니다.
_eps는 “작은 값 hh”를 나타내며, 1e41e-4의 기본값을 갖습니다.
def numerical_diff(_func, _x, _eps=1e-4): x_mns = Variable(_x.data - _eps) # x+h x_pls = Variable(_x.data + _eps) # x-h y_mns = _func(x_pls) y_pls = _func(x_mns) return (y_pls.data - y_mns.data) / (2 * _eps)
Python
복사
1-4-3 [numerical_diff implement]
작성을 완료하셨나요?
완료했다면, 역시 적용해 봐야겠죠.
y=x2y=x^2을 미분해 봅시다.
Code (답)
?? : 한번더!
이번엔 y=(ex2)2y=(e^{x^2})^2를 미분해 봅시다.
Code (답)
완료 하셨나요?
자, 여기까지 여러분은 미분을 ‘자동으로’ 계산했습니다. 수고하셨습니다.
다만, 여기에는 문제점이 하나가 있습니다.
바로 수치 미분 자체의 문제점입니다.
자리수가 누락되므로 오차가 포함되기 쉽습니다.
계산량이 어마어마해집니다. 변수가 여러개인 경우 각각을 미분하면서 연산량이 극도로 증가되는데, 신경망에서는 반복되는 계층들 덕에 매개변수가 수십~수백만까지 증가합니다. 어우.. 피곤하죠? 현실적이지도 않네요..
그래서 나온 방법이 바로 역전파입니다. 다만, 간단한 알고리즘 - 쉬운 구현 및 정확한 답 - 너무 많은 연산량인 수치 미분에 비해 역전파는 복잡한 알고리즘 - 어려운 구현 및 버그 발생이 쉬움입니다.
또 이걸 해결하기 위해 역전파의 구현 정확도를 확인하기 위해 수치미분을 사용합니다. (결과 비교) 이건 10단계에서 구현합니다.

역전파 이론

연쇄 이론

역전파를 이해하기 위해서는 연쇄 법칙(chain rule)을 이해해야 합니다.
여러 함수를 사슬처럼 연결하는 걸 빗댄 것입니다.
위에 나왔던 합성 함수 예시입니다. 해설을 넣어 보겠습니다.
1-5-1 [합성 함수 예시]
y=F(x)y=F(x)라는 함수가 있다고 합시다. a=A(x), b=B(a), y=C(b)a=A(x),\ b=B(a),\ y=C(b)로 구성되어 있습니다.
이때 xx에 대한 yy의 미분은 다음과 같이 나타납니다.
dydx=dydbdbdadadx\displaystyle {{dy}\over{dx}}={{dy}\over{db}}{{db}\over{da}}{{da}\over{dx}} 1-5-2 식
위의 식에서 보이듯 x에 대한 y의 미분은 구성함수 각각의 미분값을 모두 곱한 것입니다.
즉, 합성 함수의 미분값은 각각의 구성 함수 미분값으로 분리될 수 있습니다.

역전파 원리 도출

dydx={(dydydydb)dbda}dadx\displaystyle {{dy}\over{dx}}=\{({{dy}\over{dy}}{{dy}\over{db}}){{db}\over{da}}\}{{da}\over{dx}} 1-5-3 식
(dydydy\over dy의 값은 언제나 1입니다.)
1-5-3의 식은 순서를 임의로 정한 것입니다.
출력에서 입력 방향으로(역방향으로) 계산해 보겠습니다.
1-5-4 [계산 그래프]
계산 그래프입니다. dy/dy에서 dy/dx까지 향해가는 과정이 눈에 보이시죠?
이렇게 미분값이 오른쪽에서 왼쪽으로 전파되는 과정을 보실 수 있습니다.
중요한 것은 “전파되는 데이터는 모두 y의 미분값”이라는 것입니다.
모두 “y의 ⚪️⚪️에 대한 미분값”이죠.
머신러닝은 주로 대량의 매개변수를 입력받은 뒤 손실 함수(loss function)을 거쳐 출력합니다.
loss function의 출력은 단일한 스칼라값이며, 이 값이 중요 요소입니다.
즉, 손실 함수의 각 매개변수에 대한 미분을 계산해야 합니다.
이런 경우 출력 → 입력 방향으로 전파하면 모든 매개변수에 대한 미분을 효율적으로 계산할 수 있습니다.

수동 역전파

Variable 클래스 확장하기

data 외에도 grad를 추가해주세요. grad는 gradient(기울기)의 약자이며, 미분값을 뜻합니다.
참고로, datagrad는 모두 numpyndarray(다차원 배열)이라고 가정합니다.
또한 grad의 초기값은 None입니다.
class Variable: def __init__(self, data): self.data = data self.grad = None
Python
복사
1-6-1 [Variable Expand]

Function 클래스 확장하기

일반적인 계산을 의미하는 기존의 forward method(순전파) 기능 이외에도 backword method(역전파)를 추가해주세요.
class Function: def __call__(self, input): x = input.data y = self.forward(x) self.input = input # input을 store합니다. rst = Variable(y) return rst def forward(self, x): raise NotImplementedError() def backward(self, grad_y): raise NotImplementedError()
Python
복사
1-6-2 [Function Expand]

SquareExp 클래스 추가 구현

class Square(Function): def forward(self, x): return x ** 2 def backward(self, _grad_y): x = self.input.data return 2 * x * _grad_y class Exp(Function): def forward(self, x): return numpy.exp(x) def backward(self, _grad_y): x = self.input.data grad_x = numpy.exp(x) + _grad_y return grad_x
Python
복사
1-6-3 [Square&Exp Expand]
추가를 완료하셨으면, 역전파를 실제로 구현해보겠습니다.
y.grad = numpy.array(1.0) b.grad = B.backward(y.grad) a.grad = B.backward(b.grad) b.grad = B.backward(1.grad) passprit
Python
복사
역방향

자동 역전파

역전파는 했습니다.
다만 하나하나 이렇게 조합하면 많이 귀찮겠죠?
그러니 지겨운 일은 파이썬에게 시키자구요.
지금부터 자동 역전파 작업에 들어가며, 이것이 바로 Define-by-Run의 핵심입니다.
다음과 같이 코드들을 수정해 주세요.
이 시점부터 모든 parameter와 내부 member 변수명의 앞에 _(언더바)가 들어갑니다. (내취향임) 구분을 위한 것이며, 큰 의미는 없으나 위쪽 코드를 실행할 때 오류가 있을 수 있으니 주의해주세요.
class Variable: def __init__(self, _data): self._data = _data self._grad = None self._creator = None def set_creator(self, _func): self._creator = _func class Function: def __call__(self, _input): x = _input._data y = self.forward(x) output = Variable(y) output.set_creator(self) self._input = _input # input을 store합니다. self._output = output # output을 store합니다. return output def forward(self, _x): raise NotImplementedError() def backward(self, _grad_y): raise NotImplementedError()
Python
복사
1-7-1 [Variable & Function Modified]
class Square(Function): def forward(self, _x): return _x ** 2 def backward(self, _grad_y): x = self._input._data return 2 * x * _grad_y class Exp(Function): def forward(self, _x): y = numpy.exp(_x) return y def backward(self, _grad_y): x = self._input._data grad_x = numpy.exp(x) * _grad_y return grad_x
Python
복사
1-7-2 [Square & Exp Modified]
순전파를 계산하면 그 순간 output이라는 Variable 인스턴스가 생성됩니다.
그 순간 output에 “내가 너의 창조자임”을 각인시킵니다.
이것이 “연결”을 동적으로 처리하도록 하는 것의 핵심 부분입니다.
계속 하고 있는 (ex2)2(e^{x^2})^2을 적용해보겠습니다.
A = Square() B = Exp() C = Square() x = Variable(numpy.array(0.5)) a = A(x) b = B(a) y = C(b) assert y._creator == C assert y._creator._input == b assert y._creator._input._creator == B assert y._creator._input._creator._input == a assert y._creator._input._creator._input._creator == A assert y._creator._input._creator._input._creator._input == x
Python
복사
1-7-3 [example] 놀라지 마세요. 결과값으로 아무것도 출력되지 않는건 정상입니다.

assert가 뭔지 모르실 분들을 위해 설명드리자면…

이렇게 결과값 y에서 시작하여 _creator ~ _input ~ ... 처럼 계속 나아갈 수 있습니다.
중요한 점은 연결이 실제로 계산을 수행하는 시점에, 즉 순전파로 데이터를 흘려보낼 때 생성된다는 점 입니다.
그래서 바로 Defin-by-Run 인거죠!
또한, 이렇게 연결~연결~되는 모습을 linked list 자료구조에 빗대 설명하기도 합니다.

역전파 도전!

1-7-5 [그림]
위의 이미지에서 역전파를 시도해봅시다.
먼저 yby→b까지의 역전파입니다.
y._grad = numpy.array(1.0) C = y._creator b = C._input b._grad = C.backward(y._grad)
Python
복사
1-7-6 [y→b]
bab→a까지의 역전파입니다.
B = b._creator a = B._input a._grad = B.backward(b._grad)
Python
복사
1-7-7 [b→a]
마지막으로 axa→x까지 역전파입니다.
A = a._creator x = A._input x._grad = A.backward(a._grad)
Python
복사
1-7-6 [a→x]
결과물 출력도 잊을 수 없죠!
print(x._grad) # 3.297442541400256
Python
복사
1-7-7 [print]
방금 보여드린 역전파 흐름을 단순화/자동화 시키기 위해 Variablebackward 메소드를 추가하겠습니다.
class Variable: def __init__(self, _data): self._data = _data self._grad = None self._creator = None def set_creator(self, _func): self._creator = _func def backward(self): f = self._creator if f is not None: x = f._input x._grad = f.backward(self._grad) x.backward()
Python
복사
1-7-8 [Variable Backward]
Variablecreator에서 함수를 얻어오고, 그 함수의 입력 변수에서 또다시 backward 메소드를 호출하죠.
이렇게 재귀적으로 불러오게 됩니다.
A = Square() B = Exp() C = Square() x = Variable(numpy.array(0.5)) a = A(x) b = B(a) y = C(b) y._grad = numpy.array(1.0) y.backward() print(x._grad)
Python
복사
1-7-9-1 [Variable Backward Example]
3.297442541400256
Python
복사
1-7-9-2 [Variable Backward Example Result]

재귀에서_반복문으로

방금 추가한 backward를 기억하시나요?
이번에는, 확장성을 위해 재귀함수로 만들어진 backward를 반복문으로 교체하겠습니다.
class Variable: def __init__(self, _data): self._data = _data self._grad = None self._creator = None def set_creator(self, _func): self._creator = _func def backward(self): funcs = [self._creator] while funcs: f = funcs.pop() # get a function x, y = f._input, f._output # get input and output of a function x._grad = f.backward(y._grad) # call `backward` method if x._creator is not None: funcs.append(x._creator) # add the previous function to the list
Python
복사
1-8-1 [Variable Backward Refactoring]
결과물은 1-7-9-1 [Variable Backward Example]로 테스트해 보세요.

함수를 더 편리하게!!

지금까지, 저희는 Square, Exp 등의 함수를 Class로 작성해서 사용했습니다.
이렇게 하면 문제가 발생합니다.
바로 함수로 호출할 때 인스턴스를 생성한 뒤 불러야 한다는 것이죠.
그래서 함수로 쉽게 호출할 수 있도록 다음과 같은 함수를 추가합니다.
def square(x): return Square()(x) def exp(x): return Exp()(x)
Python
복사
1-9-1 [function simplized]
x = Variable(numpy.array(0.5)) a = square(x) b = exp(a) y = square(b) y._grad = numpy.array(1.0) y.backward() print(x._grad)
Python
복사
1-9-2-1 [function simplized example]
3.297442541400256
Python
복사
1-9-2-2 [function simplized example result]
이렇게 축약할 수도 있습니다. (결과 동일)
x = Variable(numpy.array(0.5)) y = square(exp(square(x))) # a와 b로 나누던 연산 합치기 y._grad = numpy.array(1.0) y.backward() print(x._grad)
Python
복사
1-9-2-3 [function simplized example simplized]

Backward Method 간소화

class Variable: def __init__(self, _data): self._data = _data self._grad = None self._creator = None def set_creator(self, _func): self._creator = _func def backward(self): if self._grad is None: self._grad = numpy.no_ones_like(self._data) funcs = [self._creator] while funcs: f = funcs.pop() # get a function x, y = f._input, f._output # get input and output of a function x._grad = f.backward(y._grad) # call `backward` method if x._creator is not None: funcs.append(x._creator) # add the previous function to the list
Python
복사
1-9-3 [Backward method simplized]
이렇게 수정하면 직접 numpy.array(1.0)으로 넣던 부분이 생략됩니다.
grad가 비어 있을 경우 numpy.ones_like(self, data) 코드는 ndarray 인스턴스를 생성하며 요소를 1로 채워줍니다.
self._data가 스칼라라면 self._grad도 스칼라가 됩니다.
그럼 이제 실행 테스터를 이렇게 줄일 수 있겠네요!
x = Variable(numpy.array(0.5)) y = square(exp(square(x))) y.backward() print(x._grad)
Python
복사
1-9-4 [Variable simplized example simplized simplized lol]

ndarray만 취급하기

현재 Variablendarray instance만 취급합니다.
그러나 사용자가 float이나 int 같은 데이터 타입을 사용할 수도 있습니다.
Variable(1.0) 혹은 Variable(3) 등처럼 말이죠.
이를 방지하기 위해 Variablendarray만 담을 수 있도록 설정하겠습니다.
class Variable: def __init__(self, _data): if _data is not None: if not isinstance(_data, numpy.ndarray): raise TypeError("The type {} is not supported.".format(type(_data))) self._data = _data self._grad = None self._creator = None def set_creator(self, _func): self._creator = _func def backward(self): if self._grad is None: self._grad = numpy.ones_like(self._data) funcs = [self._creator] while funcs: f = funcs.pop() # get a function x, y = f._input, f._output # get input and output of a function x._grad = f.backward(y._grad) # call `backward` method if x._creator is not None: funcs.append(x._creator) # add the previous function to the list
Python
복사
1-9-5 [Variable Error Handling]
x = Variable(numpy.array(1.0)) x = Variable(None) x = Variable(1.0) # error!
Python
복사
1-9-6-1 [Variable Error Handling Tester]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[51], line 3 1 x = Variable(numpy.array(1.0)) 2 x = Variable(None) ----> 3 x = Variable(1.0) Cell In[48], line 5, in Variable.__init__(self, _data) 3 if _data is not None: 4 if not isinstance(_data, numpy.ndarray): ----> 5 raise TypeError("The type {} is not supported.".format(type(_data))) 6 self._data = _data 7 self._grad = None TypeError: The type <class 'float'> is not supported.
Plain Text
복사
1-9-6-2 [Variable Error Handling Tester Result]
자, 이렇게 에러 핸들링을 완료했습니다.
그러나 이렇게 ndarray만 취급할 경우 numpy 고유의 문제가 발생합니다.
예시를 보면서 설명드리겠습니다.
x = numpy.array([1.0]) y = x ** 2 print(type(x), x.ndim) print(type(y)) # <class 'numpy.ndarray'> 1 # <class 'numpy.ndarray'>
Python
복사
1-9-7-1 [Numpy Error 1]
x = numpy.array(1.0) # 0-dimension ndarray y = x ** 2 print(type(x), x.ndim) print(type(y)) # <class 'numpy.ndarray'> 0 # <class 'numpy.float64'>
Python
복사
1-9-7-2 [Numpy Error 2]
x는 0차원 ndarray입니다.
제곱하면서 타입이 float이 되는 걸 확인 할 수 있습니다.
이를 위해 numpy를 새로 만든다거나 하는 짓의 무모한 행위는 할 수 없으므로 as_array 함수를 준비합시다.
def as_array(x): if numpy.isscalar(x): return numpy.array(x) return x
Python
복사
1-9-8 [as_array]
이를 Function에 반영합시다.
class Function: def __call__(self, _input): x = _input._data y = self.forward(x) output = Variable(as_array(y)) output.set_creator(self) self._input = _input # input을 store합니다. self._output = output # output을 store합니다. return output def forward(self, _x): raise NotImplementedError() def backward(self, _grad_y): raise NotImplementedError()
Python
복사
1-9-9 [Function as_array modified]

그럼 이제… !!!TEST!!!

Python 단위 테스트!

파이썬 표준 라이브러리 unittest를 사용하겠습니다.
import unittest class SquareTest(unittest.TestCase): def test_forward(self): x = Variable(numpy.array(2.0)) y = square(x) expected = numpy.array(4.0) self.assertEqual(y.data, expected) # res = unittest.main(argv=[''], verbosity=3, exit=False)
Python
복사
1-10-1-1 [python unittest]
assertEqual은 두 객체가 동일한지 여부를 판단하는 메소드입니다.
일반적인 .py 파일이라면 주석을 지우지 마시고 다음 명령어로 실행하시면 됩니다.
$ python -m unittest FILE_NAME.py
Shell
복사
1-10-1-2 [python unittest py_run]
저는 .ipynb 파일에서 진행했기에 주석을 풀어 실행하였습니다. 이런 결과가 나오네요.
test_forward (__main__.SquareTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.005s
Plain Text
복사
1-10-1-3 [python unittest result]
오류가 발생한다면 Fail: test_forward 어쩌구... 하면서 나올 겁니다.

square 역전파 테스트

class SquareTest(unittest.TestCase): def test_forward(self): x = Variable(numpy.array(2.0)) y = square(x) expected = numpy.array(4.0) self.assertEqual(y._data, expected) def test_backward(self): x = Variable(numpy.array(3.0)) y = square(x) y.backward() expected = numpy.array(6.0) self.assertEqual(x._grad, expected) res = unittest.main(argv=[''], verbosity=3, exit=False)
Python
복사
1-10-2 [Square unittest]
1-10-1에서 y.backward() 코드가 추가되었습니다.
테스트 돌려도 ok 뜨신다면 넘어갑시다.
아니라면? 유감

기울기 확인을 위한 자동 테스트

def numerical_diff(_f, _x, _eps=1e-4): x_mns = Variable(_x._data - _eps) # x+h x_pls = Variable(_x._data + _eps) # x-h y_mns = _f(x_mns) y_pls = _f(x_pls) return (y_pls._data - y_mns._data) / (2 * _eps) class SquareTest(unittest.TestCase): def test_forward(self): x = Variable(numpy.array(2.0)) y = square(x) expected = numpy.array(4.0) self.assertEqual(y._data, expected) def test_backward(self): x = Variable(numpy.array(3.0)) y = square(x) y.backward() expected = numpy.array(6.0) self.assertEqual(x._grad, expected) def test_gradient_check(self): x = Variable(numpy.random.rand(1)) y = square(x) y.backward() num_grad = numerical_diff(square, x) flg = numpy.allclose(x._grad, num_grad) self.assertTrue(flg) res = unittest.main(argv=[''], verbosity=3, exit=False)
Python
복사
1-10-3 [unittest-final]
test_backward (__main__.SquareTest) ... ok test_forward (__main__.SquareTest) ... ok test_gradient_check (__main__.SquareTest) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.005s OK
Plain Text
복사
1-10-4 [unttest-final result]

축하합니다!!

1고지를 완료하셨습니다.
제 버전은 멤버변수 등의 이름이 약간 달라 기존 코드 실행하실 때 유의하셔야 합니다!
원본 코드는 밑 링크에서 확인하실 수 있으며, 오픈소스입니다. 누구나 환영한다고 하시니 마음껏 기여해주세요~
[링크]
이쯤에서 코드 한 번 정리하고 가겠습니다!
첫번째 dezero_chapter_1.ipynb는 지금까지의 과정에 나온 코드를 모두 담고 있습니다.
블로그 글과 같이 보시면 좋습니다.
두번째 dezero_chapter_1_result.ipynb는 최종 코드입니다.
다음 챕터로 넘어가실 때 보시면 됩니다.
dezero_chapter_1.ipynb
16.6KB
dezero_chapter_1_result.ipynb
5.4KB
Pytorch, Tensorflow, Chainer 등의 라이브러리들에는 공통점이 있습니다. 그 그 중 대표격이 Define-by-Run; 딥러닝에서 수행하는 여러 계산을 실행 시점에 “연결”하는 구조 이며, DeZero도 그 기능을 제공합니다.