commit 92227335bd42489fe0a25e7670b36d05fb4a519f
parent 051a8e243443b93520a4c6bd506abe1220550802
Author: Étienne Simon <esimon@esimon.eu>
Date:   Thu, 17 Apr 2014 11:53:53 +0200
Fix ranking & Speed up by abusing broadcasting
Diffstat:
3 files changed, 37 insertions(+), 23 deletions(-)
diff --git a/dataset.py b/dataset.py
@@ -57,18 +57,13 @@ class Dataset(object):
             relation = train_relation[f:t]
             yield (relation, left_positive, right_positive, left_negative, right_negative)
 
-    def iterate(self, name, batch_size):
-        def repeat_csr(matrix, size):
-            data = list(matrix.data)*size
-            indices = list(matrix.indices)*size
-            indptr = range(size+1)
-            return scipy.sparse.csr_matrix((data, indices, indptr), shape=(size, matrix.shape[1]), dtype=theano.config.floatX)
+    def iterate(self, name):
         N = getattr(self, name+'_size')
         relation = getattr(self, name+'_relation')
         left = getattr(self, name+'_left')
         right = getattr(self, name+'_right')
         for i in xrange(N):
-            yield (repeat_csr(relation[i], batch_size), repeat_csr(left[i], batch_size), right[i])
+            yield (relation[i], left[i], right[i])
 
     def universe_minibatch(self, batch_size):
         N = len(self.embeddings)
diff --git a/model.py b/model.py
@@ -78,14 +78,23 @@ class Model(object):
         inputs = tuple(S.csr_matrix() for _ in xrange(5))
         positive_left, positive_right = self.embeddings.embed(inputs[1]), self.embeddings.embed(inputs[2])
         negative_left, negative_right = self.embeddings.embed(inputs[3]), self.embeddings.embed(inputs[4])
-        positive_score = self.hyperparameters['similarity'](self.relations.apply(positive_left, inputs[0]), positive_right)
-        negative_score = self.hyperparameters['similarity'](self.relations.apply(negative_left, inputs[0]), negative_right)
+        relation = self.relations.lookup(inputs[0])
+        positive_score = self.hyperparameters['similarity'](self.relations.apply(positive_left, relation), positive_right)
+        negative_score = self.hyperparameters['similarity'](self.relations.apply(negative_left, relation), negative_right)
         score = self.hyperparameters['margin'] + positive_score - negative_score
         violating_margin = score>0
         criterion = T.mean(violating_margin*score)
 
         self.train_function = theano.function(inputs=list(inputs), outputs=[criterion], updates=self.updates(criterion))
-        self.scoring_function = theano.function(inputs=list(inputs[0:3]), outputs=[positive_score])
+
+        relation = T.addbroadcast(relation, 0)
+        broadcasted_left = T.addbroadcast(positive_left, 0)
+        broadcasted_right = T.addbroadcast(positive_right, 0)
+        left_score = self.hyperparameters['similarity'](self.relations.apply(broadcasted_left, relation), positive_right)
+        right_score = self.hyperparameters['similarity'](self.relations.apply(positive_left, relation), broadcasted_right)
+
+        self.left_scoring_function = theano.function(inputs=list(inputs[0:3]), outputs=[left_score])
+        self.right_scoring_function = theano.function(inputs=list(inputs[0:3]), outputs=[right_score])
 
     def updates(self, cost):
         """ Compute the updates to perform a SGD step w.r.t. a given cost.
@@ -117,18 +126,24 @@ class Model(object):
         """ Compute the mean rank and top 10 on a given data. """
         batch_size = self.hyperparameters['test batch size']
         count, mean, top10 = 0, 0, 0
-        for (relation, left, right) in self.dataset.iterate(name, batch_size): # TODO Test symmetric
-            scores = None
+        for (relation, left, right) in self.dataset.iterate(name):
+            left_scores, right_scores = None, None
             for entities in self.dataset.universe_minibatch(batch_size):
-                if left.shape != entities.shape:
-                    left = left[0:entities.shape[0]]
-                    relation = relation[0:entities.shape[0]]
-                batch_result = self.scoring_function(relation, left, entities)
-                scores = numpy.array(batch_result, dtype=theano.config.floatX) if scores is None else numpy.concatenate((scores, batch_result), axis=1)
-            rank = numpy.asscalar(numpy.where(numpy.argsort(scores)==right.indices[0])[1]) # FIXME Ugly
-            mean = mean + rank
-            count = count + 1
-            top10 = top10 + (rank<=10)
+                left_batch_result = self.left_scoring_function(relation, left, entities)
+                right_batch_result = self.right_scoring_function(relation, entities, right)
+                if left_scores is None:
+                    left_scores = numpy.array(left_batch_result, dtype=theano.config.floatX)
+                else:
+                    left_scores = numpy.concatenate((left_scores, left_batch_result), axis=1)
+                if right_scores is None:
+                    right_scores = numpy.array(right_batch_result, dtype=theano.config.floatX)
+                else:
+                    right_scores = numpy.concatenate((right_scores, right_batch_result), axis=1)
+            left_rank = numpy.asscalar(numpy.where(numpy.argsort(left_scores)==right.indices[0])[1]) # FIXME Ugly
+            right_rank = numpy.asscalar(numpy.where(numpy.argsort(right_scores)==left.indices[0])[1]) # FIXME Ugly
+            count = count + 2
+            mean = mean + left_rank + right_rank
+            top10 = top10 + (left_rank<=10) + (right_rank<=10)
         mean = float(mean) / count
         top10 = float(top10) / count
         return (mean, top10)
@@ -141,7 +156,7 @@ class Model(object):
         if self.hyperparameters['validate on training data']:
             (train_mean, train_top10) = self.error('train')
             print >>sys.stderr, 'train mean: {0:<15} train top10: {1:<15}'.format(train_mean, train_top10)
-        else
+        else:
             print >>sys.stderr, ''
 
     def test(self):
diff --git a/relations/translations.py b/relations/translations.py
@@ -31,9 +31,13 @@ class Translations(object):
 
         self.parameters = [self.R]
 
+    def lookup(self, relations):
+        """ Embed given relations. """
+        return S.dot(relations, self.R)
+
     def apply(self, inputs, relations):
         """ Apply the given relations to a given input. """
-        return S.dot(relations, self.R)+inputs
+        return relations + inputs
 
     def updates(self, cost, learning_rate):
         """ Compute the updates to perform a SGD step w.r.t. a given cost.