25. 계산그래프 시각화(1)¶
- 계산 그래프를 눈으로 확인하기
- 계산 그래프를 시각화하면 문제가 발생했을 때 원인이 되는 부분을 파악하기 쉬워짐. 또한 더 나은 계산 방법을 발견할 수도 있고, 신경망의 구조를 제3자에게 시각적으로 전달하는 용도로도 활용할 수 있음
- 시각화 도구로는 Graphviz를 활용하고, 사용법을 알아봄
26. 계산그래프 시각화(2)¶
- dezero/utils.py 파일 작성
In [1]:
import numpy as np
from dezero import Variable
In [2]:
def _dot_var(v, verbose=False):
dot_var = '{} [label="{}", color=orange, style=filled]\n'
name = '' if v.name is None else v.name
if verbose and v.data is not None:
if v.name is not None:
name += ': '
name += str(v.shape) + ' ' + str(v.dtype)
return dot_var.format(id(v), name)
- _dot_var라는 보조 함수부터 코드 작성. 이름 밑에 밑줄이 붙은 이유는 이 함수를 로컬에서만, 즉
get_dot_graph
함수 전용으로 사용할 것이기 때문. _dot_var
함수에 Variable 인스턴스를 건네면 인스턴스의 내용을 DOT 언어로 작성된 문자열로 바꿔서 변환함.- 변수 노드에 고유한 ID를 부여하기 위해 파이썬 내장함수인 id를 사용함.
- id 함수는 주어진 객체의 ID를 반환하는데, 객체 ID는 다른 객체와 중복되지 않기 때문에 노드의 ID로 사용하기에 적합함
- 또한 마지막 반한 직전에 format 메서드를 이용함. format 메서드는 문자열에 등장하는 "{}" 부분을 메서드 인수로 건넨 객체(문자열이나 정수 등)로 차례로 바꿔줌.
- 가령 앞의 코드에서는 dot_var 문자열의 첫 번째 {} 자리에는 id(v)와 같이, 두 번째 {} 자리에는 name의 값이 채워짐
In [3]:
x = Variable(np.random.randn(2, 3))
x.name = 'x'
print(_dot_var(x))
print(_dot_var(x, verbose=True))
- _dot_func는 DeZero 함수를 DOT 언어로 반환하는 편의 함수.
- 또한, 함수와 입력 변수의 관계, 함수와 출력 변수의 관계도 DOT 언어로 기술함.
- 복습하자면 DeZero 함수는 Function 클래스를 상속하고 inputs과 outputs라는 인스턴스 변수를 가지고 있음.
In [4]:
def _dot_func(f):
# for function
dot_func = '{} [label="{}", color=lightblue, style=filled, shape=box]\n'
ret = dot_func.format(id(f), f.__class__.__name__)
# for edge
dot_edge = '{} -> {}\n'
for x in f.inputs:
ret += dot_edge.format(id(x), id(f))
for y in f.outputs: # y is weakref
ret += dot_edge.format(id(f), id(y()))
return ret
In [5]:
x0 = Variable(np.array(1.0))
x1 = Variable(np.array(1.0))
y = x0 + x1
txt = _dot_func(y.creator)
print(txt)
- 준비가 끝났으면 본격적으로 get_dot_graph 함수를 구현함.
- 함수 코드의 로직은 Variable 클래스의 backward 메서드와 거의 같음. backward 메서드는 미분값을 전파했지만, 여기에서는 미분 대신 DOT 언어로 기술한 문자열을 txt에 추가함.
- 또한, 실제 역전파에서는 노드를 따라가는 순서가 중요함. 그래서 함수의 generation(세대)이라는 정숫값을 부여하고 그 값이 큰 순서대로 꺼냄. 하지만 get_dot_graph 함수에서는 노드를 추적하는 순서는 문제가 되지 않으므로 generation 값으로 정렬하는 코드를 주석으로 처리함.
- 계산 그래프를 DOT 언어로 반환할 때는 '어떤 노드가 존재하는 가'와 '어떤 노드끼리 연결되는가'가 문제임. 즉, 노드의 추적 '순서'는 문제가 되지 않으므로 generation을 사용하여 순서대로 꺼내는 구조는 사용하지 않아도 됨
In [6]:
def get_dot_graph(output, verbose=True):
txt = ''
funcs = []
seen_set = set()
def add_func(f):
if f not in seen_set:
funcs.append(f)
# funcs.sort(key=lambda x: x.generation)
seen_set.add(f)
add_func(output.creator)
txt += _dot_var(output, verbose)
while funcs:
func = funcs.pop()
txt += _dot_func(func)
for x in func.inputs:
txt += _dot_var(x, verbose)
if x.creator is not None:
add_func(x.creator)
return 'digraph g {\n' + txt + '}'
26.3 이미지 변환까지 한 번에¶
- get_dot_graph 함수는 계산 그래프를 DOT 언어로 변환함. 그런데 DOT 언어를 이미지로 변환하려면 dot 명령을 수동으로 실행해야 하므로 매번 하기에는 번거로움. 그래서 dot 명령 실행까지 한 번에 해주는 함수를 제공함
In [7]:
def plot_dot_graph(output, verbose=True, to_file='graph.png'):
dot_graph = get_dot_graph(output, verbose)
# 1. dot 데이터를 파일에 저장
tmp_dir = os.path.join(os.path.expanduser('~'), '.dezero')
if not os.path.exists(tmp_dir): # ~./dezero 디렉터리가 없다면 새로 생성
os.mkdir(tmp_dir)
graph_path = os.path.join(tmp_dir, 'tmp_graph.dot')
with open(graph_path, 'w') as f:
f.write(dot_graph)
# 2. dot 명령 호출
extension = os.path.splitext(to_file)[1][1:] # 확장자(png, pdf 등)
cmd = 'dot {} -T {} -o {}'.format(graph_path, extension, to_file)
subprocess.run(cmd, shell=True)
# Return the image as a Jupyter Image object, to be displayed in-line.
try:
from IPython import display
return display.Image(filename=to_file)
except:
pass
- 우선, 1 에서는 방금 구현한
get_dot_graph
함수를 호출하여 계산 그래프를 DOT 언어(텍스트)로 반환하고, 파일에 저장함. 대상 디렉터리는 ~./dezero 이고 파일 이름은 tmp_graph.dot으로 함(일시적으로 사용할 파일이므로 tmp라는 이름을 씀) os.path.expanduser('~')
라는 문장은 사용자의 홈 디렉터리를 뜻하는 '~'를 절대 경로로 풀어줌- 2 에서는 앞에서 저장한 파일 이름을 지정하여 dot 명령을 호출함. 이 때 plot_dot_graph함수의 인수인 to_file에 저장할 이미지 파일의 이름을 지정함. 참고로 파이썬에서 외부 프로그램을 호출하기 위해
subprogress.run
함수를 사용함
- 여기에서 구현한 함수는 앞으로 다양한 장소에서 사용되기 때문에 dezero/utils.py 에 추가함. 그럼 from dezero.utils import plot_dot_graph로 임포트 가능
In [8]:
import numpy as np
from dezero import Variable
from dezero.utils import plot_dot_graph
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
x = Variable(np.array(1.0))
y = Variable(np.array(1.0))
z = goldstein(x, y)
z.backward()
x.name = 'x'
y.name = 'y'
z.name = 'z'
plot_dot_graph(z, verbose=False, to_file='ch3)goldstein.png')
Out[8]:
In [ ]:
'기계학습 > 밑바닥딥러닝3 오독오독 씹기' 카테고리의 다른 글
Chapter 2. 자연스러운 코드로(step 23~24)/복잡한 함수 미분 (1) | 2021.01.21 |
---|---|
Chapter 2. 자연스러운 코드로(step 17~19)/ 연산자 오버로드 (0) | 2021.01.04 |
Chapter 2. 자연스러운 코드로(step 17~19) (0) | 2020.12.29 |
Chapter 2. 자연스로운 코드로(step 16) (0) | 2020.12.16 |
Chapter 2. 자연스러운 코드로(step 11~14) (0) | 2020.12.07 |