Vorausgesetzt, dass die Anzahl der Matrizen bekannt ist, bevor die Theano-Kompilierung stattfinden muss, kann man einfach reguläre Python-Listen von Theano-Matrizen verwenden.
Hier ist ein vollständiges Beispiel, das den Unterschied zwischen numpy und Theano-Versionen zeigt.
Dieser Code wurde aktualisiert, um einen Vergleich mit dem vektorisierten Ansatz von @ Divakar zu ermöglichen, der bessere Ergebnisse liefert. Zwei vektorisierte Ansätze sind für Theano möglich, eine, bei der Theano die Verkettung vornimmt, und eine, bei der numpy die Verkettung durchführt, deren Ergebnis dann an Theano übergeben wird.
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()
Wenn diese Drucke laufen (so etwas wie)
numpy_version1 1.47830819649
numpy_version2 1.77405482179
theano_version1 1.3603150303
theano_version2 1.81665318145
theano_version3 1.86912039489
Das behauptet Pass zeigt, dass die Theano und numpy Versionen beide das gleiche Ergebnis zu hohen Grad an Genauigkeit berechnen. Diese Genauigkeit wird deutlich reduziert, wenn float32
anstelle von float64
verwendet wird.
Die Timing-Ergebnisse zeigen, dass der vektorisierte Ansatz möglicherweise nicht vorzuziehen ist, sondern von den Matrixgrößen abhängt. Im obigen Beispiel sind die Matrizen groß und der Nicht-Verkettungsansatz ist schneller, aber wenn die Parameter n
und min_x
in der main
-Funktion geändert werden, um viel kleiner zu sein, dann ist der Verkettungsansatz schneller. Andere Ergebnisse können beim Ausführen auf einer GPU enthalten sein (nur Theano-Versionen).
Ist die Länge von 'L' vor der Theano-Zusammenstellung bekannt? –
@DanielRenshaw ja, und die Form jeder Matrix in L ist auch bekannt – dontloo