Biorąc pod uwagę, że liczba macierzy jest znana, zanim kompilacja Theano musi się odbyć, można po prostu użyć zwykłych list Pythona macierzy Theano.
Oto pełny przykład pokazujący różnicę między wersjami numpy i Theano.
Ten kod został zaktualizowany, aby uwzględnić porównanie z wektoryzowanym podejściem @ Divakar, które działa lepiej. Możliwe są dwa wektoryzowane podejścia do Theano, w którym Theano wykonuje konkatenację, i takie, w którym numpy wykonuje konkatenację, której wynik jest następnie przekazywany Theano.
import timeit
import numpy as np
import theano
import theano.tensor as tt
def compile_theano_version1(number_of_matrices, n, dtype):
assert number_of_matrices > 0
assert n > 0
L = [tt.matrix() for _ in xrange(number_of_matrices)]
res = tt.zeros(n, dtype=dtype)
for M in L:
res += tt.dot(M.T, M)
return theano.function(L, res)
def compile_theano_version2(number_of_matrices):
assert number_of_matrices > 0
L = [tt.matrix() for _ in xrange(number_of_matrices)]
concatenated_L = tt.concatenate(L, axis=0)
res = tt.dot(concatenated_L.T, concatenated_L)
return theano.function(L, res)
def compile_theano_version3():
concatenated_L = tt.matrix()
res = tt.dot(concatenated_L.T, concatenated_L)
return theano.function([concatenated_L], res)
def numpy_version1(*L):
assert len(L) > 0
n = L[0].shape[1]
res = np.zeros((n, n), dtype=L[0].dtype)
for M in L:
res += np.dot(M.T, M)
return res
def numpy_version2(*L):
concatenated_L = np.concatenate(L, axis=0)
return np.dot(concatenated_L.T, concatenated_L)
def main():
iteration_count = 100
number_of_matrices = 20
n = 300
min_x = 400
dtype = 'float64'
theano_version1 = compile_theano_version1(number_of_matrices, n, dtype)
theano_version2 = compile_theano_version2(number_of_matrices)
theano_version3 = compile_theano_version3()
L = [np.random.standard_normal(size=(x, n)).astype(dtype)
for x in range(min_x, number_of_matrices + min_x)]
start = timeit.default_timer()
numpy_res1 = np.sum(numpy_version1(*L)
for _ in xrange(iteration_count))
print 'numpy_version1', timeit.default_timer() - start
start = timeit.default_timer()
numpy_res2 = np.sum(numpy_version2(*L)
for _ in xrange(iteration_count))
print 'numpy_version2', timeit.default_timer() - start
start = timeit.default_timer()
theano_res1 = np.sum(theano_version1(*L)
for _ in xrange(iteration_count))
print 'theano_version1', timeit.default_timer() - start
start = timeit.default_timer()
theano_res2 = np.sum(theano_version2(*L)
for _ in xrange(iteration_count))
print 'theano_version2', timeit.default_timer() - start
start = timeit.default_timer()
theano_res3 = np.sum(theano_version3(np.concatenate(L, axis=0))
for _ in xrange(iteration_count))
print 'theano_version3', timeit.default_timer() - start
assert np.allclose(numpy_res1, numpy_res2)
assert np.allclose(numpy_res2, theano_res1)
assert np.allclose(theano_res1, theano_res2)
assert np.allclose(theano_res2, theano_res3)
main()
Kiedy uruchomić ten nadrukami (coś podobnego)
numpy_version1 1.47830819649
numpy_version2 1.77405482179
theano_version1 1.3603150303
theano_version2 1.81665318145
theano_version3 1.86912039489
twierdzi przepustkę, pokazując, że Theano i NumPy wersje zarówno obliczyć ten sam wynik dużą dokładnością. Oczywiście ta dokładność zmniejszy się, jeśli użyjemy float32
zamiast float64
.
Wyniki pomiaru czasu pokazują, że podejście wektoryzacji może nie być preferowane, zależy to od rozmiarów matrycy. W powyższym przykładzie macierze są duże, a podejście bez konkatenacji jest szybsze, ale jeśli parametry i min_x
są zmienione w funkcji main
, aby były znacznie mniejsze, wówczas podejście łączenia jest szybsze. Inne wyniki mogą zachowywać się podczas pracy na GPU (tylko wersje Theano).
Czy długość 'L' znana jest przed kompilacją Theano? –
@DanielRenshaw tak, a kształt każdej matrycy w L jest również znany – dontloo