Учитывая, что число матриц известно до того, как должна произойти компиляция Theano, можно просто использовать обычные списки на языке Python из матриц Theano.
Вот полный пример, показывающий разницу между версиями numpy и Theano.
Этот код был обновлен, чтобы включить сравнение с векторизованным подходом @ Divakar, который работает лучше. Для Теано возможны два векторизованных подхода, где Теано выполняет конкатенацию, а один, где numpy выполняет конкатенацию, результат которой затем передается в 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()
При запуске Печатает (что-то вроде)
numpy_version1 1.47830819649
numpy_version2 1.77405482179
theano_version1 1.3603150303
theano_version2 1.81665318145
theano_version3 1.86912039489
утверждает проход, показывая, что Theano и Numpy версии оба вычислить тот же результат высокой степенью точности. Очевидно, что эта точность будет уменьшаться при использовании float32
вместо float64
.
Результаты синхронизации показывают, что векторизованный подход может быть не предпочтительным, он зависит от размеров матрицы. В приведенном выше примере матрицы велики, а подход без конкатенации выполняется быстрее, но если параметры и min_x
изменены в функции main
намного меньше, то подход конкатенации выполняется быстрее. Другие результаты могут выполняться при работе на GPU (только версии Theano).
Является ли длина 'L' известна перед сборником Theano? –
@ DanielRenshaw да, и форма каждой матрицы в L также известна. – dontloo