%matplotlib inline
왜 갑자기 graph?
Theano로 만든 code를 debuggin 하거나 profiling 하는 건 쉽지 않다.
블랙박스 속에서 무슨 일이 벌어지고 있는 지 모른다면
이해하기 위해 필요한 만큼만 들여다 보자. 자세한 건 Extending Theano를 보고...
Theano code를 쓰는 첫번째 단계는 관련된 모든 수학적 관계식을 symbolic placeholder (variable)를 이용하여 기술하는 것이다.
이러한 expression에는 사칙연산, 삼각함수 등 여러가지가 있다.
이러한 모든 expression은 내부적으로는 ops 로 나타내어진다.
op.라는 것은 특정한 input을 받아 연산하고 정해진 type의 output을 내는 것을 의미한다.
대부분의 프로그래밍 언어에서 이야기하는 function definitions으로 생각해도 되겠다.
Theano는 내부적으로 variable node, op node, apply node 가 연결된 graph structure를 만든다.
apply node는 vaiable에 뭔가를 하는 op을 나타낸다고 생각하자.
op에 의해 나타내어지는 연산의 정의와 특정한 data type에 적용하는 것 그래서 apply node로 나타나는 것 사이의 차이를 그려보는 것은 중요한 일이다.
우선 아래 graph의 예를 보자.
from theano import *
import theano.tensor as T
from theano import function
Using gpu device 0: GeForce GTX 980 Ti
x = T.dscalar('x')
y = T.dscalar('y')
z = x + y
f = function([x, y], z)
import matplotlib.pyplot as plt
import Image
im = Image.open('apply1.png')
plt.imshow(im)
<matplotlib.image.AxesImage at 0x7fde103ba450>
import theano
x = theano.tensor.dmatrix('x')
y = x * 2.
type(y.owner)
하면 <class 'theano.gof.graph.Apply'>
의 결과를 얻을 수 있는데, 이것은 Apply node와 input을 이어주는 것이라 한다.type(y.owner)
theano.gof.graph.Apply
y.owner.op.name
'Elemwise{mul,no_inplace}'
len(y.owner.inputs)
2
y.owner.inputs[0]
x
y.owner.inputs[1]
DimShuffle{x,x}.0
DimShuffle
이라는 op를 통해 이루어진다.그래서 각각의 type을 한번 알아보면
type(y.owner.inputs[1])
theano.tensor.var.TensorVariable
type(y.owner.inputs[1].owner)
theano.gof.graph.Apply
y.owner.inputs[1].owner.op
<theano.tensor.elemwise.DimShuffle at 0x7fde0c33e550>
y.owner.inputs[1].owner.inputs
[TensorConstant{2.0}]
이 그래프 구조를 안다면 자동화된 미분(automatics differentiation)은 쉽다고 한다.
chain rule 에 따라 진행 가능.
tensor.grad()
가 하는 일은 output에서부터 input까지 그래프를 역탐색하는데, 모든 apply node를 둘러보아야 하는 것이다.
각 apply node에 대해, op는 gradient의 ...
자세한 내용은 differentiation 에 있으니 읽어 보세요.
from theano import pp
x = T.dscalar('x')
y = x ** 2
gy = T.grad(y, x)
pp(gy) # print out the gradient prior to optimization
# '((fill((x ** 2), 1.0) * 2) * (x ** (2 - 1)))'
'((fill((x ** TensorConstant{2}), TensorConstant{1.0}) * TensorConstant{2}) * (x ** (TensorConstant{2} - TensorConstant{1})))'
f = function([x], gy)
f(4)
# array(8.0)
array(8.0)
f(94.2)
# array(188.40000000000001)
array(188.4)
import theano
a = theano.tensor.vector("a") # declare symbolic variable
b = a + a ** 10 # build symbolic expression
f = theano.function([a], b) # compile function
print f([0, 1, 2]) # prints `array([0,2,1026])`
[ 0. 2. 1026.]
theano.printing.pydotprint(b, outfile="./symbolic_graph_unopt.png", var_with_name_simple=True)
The output file is available at ./symbolic_graph_unopt.png
theano.printing.pydotprint(f, outfile="./symbolic_graph_opt.png", var_with_name_simple=True)
The output file is available at ./symbolic_graph_opt.png
im = Image.open('symbolic_graph_unopt.png')
plt.imshow(im)
<matplotlib.image.AxesImage at 0x7fde4d599210>
im = Image.open('symbolic_graph_opt.png')
plt.imshow(im)
<matplotlib.image.AxesImage at 0x7fde4cb45d50>
>>> import numpy
>>> import theano
>>> import theano.tensor as T
>>> rng = numpy.random
>>> # Training data
>>> N = 400
>>> feats = 784
>>> D = (rng.randn(N, feats).astype(theano.config.floatX), rng.randint(size=N,low=0, high=2).astype(theano.config.floatX))
>>> training_steps = 10000
>>> # Declare Theano symbolic variables
>>> x = T.matrix("x")
>>> y = T.vector("y")
>>> w = theano.shared(rng.randn(feats).astype(theano.config.floatX), name="w")
>>> b = theano.shared(numpy.asarray(0., dtype=theano.config.floatX), name="b")
>>> x.tag.test_value = D[0]
>>> y.tag.test_value = D[1]
>>> # Construct Theano expression graph
>>> p_1 = 1 / (1 + T.exp(-T.dot(x, w)-b)) # Probability of having a one
>>> prediction = p_1 > 0.5 # The prediction that is done: 0 or 1
>>> # Compute gradients
>>> xent = -y*T.log(p_1) - (1-y)*T.log(1-p_1) # Cross-entropy
>>> cost = xent.mean() + 0.01*(w**2).sum() # The cost to optimize
>>> gw,gb = T.grad(cost, [w,b])
>>> # Training and prediction function
>>> train = theano.function(inputs=[x,y], outputs=[prediction, xent], updates=[[w, w-0.01*gw], [b, b-0.01*gb]], name = "train")
>>> predict = theano.function(inputs=[x], outputs=prediction, name = "predict")
>>> theano.printing.pprint(prediction)
'gt((TensorConstant{1} / (TensorConstant{1} + exp(((-(x \\dot HostFromGpu(w))) - HostFromGpu(b))))), TensorConstant{0.5})'
The pre-compilation graph:
>>> theano.printing.debugprint(prediction)
Elemwise{gt,no_inplace} [@A] '' |Elemwise{true_div,no_inplace} [@B] '' | |DimShuffle{x} [@C] '' | | |TensorConstant{1} [@D] | |Elemwise{add,no_inplace} [@E] '' | |DimShuffle{x} [@F] '' | | |TensorConstant{1} [@D] | |Elemwise{exp,no_inplace} [@G] '' | |Elemwise{sub,no_inplace} [@H] '' | |Elemwise{neg,no_inplace} [@I] '' | | |dot [@J] '' | | |x [@K] | | |HostFromGpu [@L] '' | | |w [@M] | |DimShuffle{x} [@N] '' | |HostFromGpu [@O] '' | |b [@P] |DimShuffle{x} [@Q] '' |TensorConstant{0.5} [@R]
The post-compilation graph:
>>> theano.printing.debugprint(predict)
Elemwise{gt,no_inplace} [@A] '' 7 |HostFromGpu [@B] '' 6 | |GpuElemwise{Composite{scalar_sigmoid((-((-i0) - i1)))}}[(0, 0)] [@C] '' 5 | |GpuGemv{inplace} [@D] '' 4 | | |GpuAlloc{memset_0=True} [@E] '' 3 | | | |CudaNdarrayConstant{0.0} [@F] | | | |Shape_i{0} [@G] '' 2 | | | |x [@H] | | |TensorConstant{1.0} [@I] | | |GpuFromHost [@J] '' 1 | | | |x [@H] | | |w [@K] | | |TensorConstant{0.0} [@L] | |GpuDimShuffle{x} [@M] '' 0 | |b [@N] |TensorConstant{(1,) of 0.5} [@O]
The pre-compilation graph:
>>> theano.printing.pydotprint(prediction, outfile="logreg_pydotprint_prediction.png", var_with_name_simple=True)
# The output file is available at pics/logreg_pydotprint_prediction.png
The output file is available at logreg_pydotprint_prediction.png
>>> theano.printing.pydotprint(predict, outfile="logreg_pydotprint_predict.png", var_with_name_simple=True)
The output file is available at logreg_pydotprint_predict.png
>>> theano.printing.pydotprint(train, outfile="logreg_pydotprint_train.png", var_with_name_simple=True)
The output file is available at logreg_pydotprint_train.png