Repository: OpenMined/PySyft
Pull request #367 (2017-10-23 07:02:55)

Implement Default multinomial Functionality for Base Tensor Type
<!--
Thanks for submitting a PR. Please make sure  that you have read the contribution guidelines: https://github.com/OpenMined/Docs/blob/master/contributing/guidelines.md
-->
#### Reference Issue

<!-- Example: Fixes #334 -->


#### What does your implementation fix? 

#### Explain your changes.


#### include other comments?(if any)


<!--
Please note that we are a small team of volunteers so patience is
essential; We value all the contributions, no matter how small they are. 
If we are slow to review, either the pull request needs 
some tweaking,convincing, etc. 
or probably the reviewers could be busy. In any
case, we hope for your understanding in the process.

However If you feel that  this needs to be fastened up.
Reachout to us on our slack #pysyft channel.

Thanks for the contribution!
-->


Patch:
diff --git a/syft/tensor.py b/syft/tensor.py
index 7cf7956e28..3f3288a7f0 100644
--- a/syft/tensor.py
+++ b/syft/tensor.py
@@ -1930,6 +1930,39 @@ class TensorBase(object):
         self.data = np.random.binomial(1, p.data)
         return self
 
+    def multinomial(self, p, num_samples, replacement=False):
+        """
+        Returns Tensor with random numbers from the Multinomial Distribution.
+
+        Returns Tensor with random numbers
+        from a multinomial distribution with probability
+        specified by p(arr_like), number of draws specified by num_samples,
+        and whether to replace the draws specified by replacement.
+
+        The p Tensor should be a tensor containing probabilities to
+        be used for drawing the multinomial random number.
+        The values of p do not need to sum to one (in which case we use the values as weights),
+        but must be non-negative and have a non-zero sum.
+
+        Parameters
+        ----------
+        p: TensorBase
+            Weights for the multinomial distribution
+        num_samples: Int
+            Number of samples to be drawn. If replacement is false, this must be lower than the length of p.
+        replacement: bool, optional
+            Whether to draw with replacement or not
+
+        Returns
+        -------
+        Output Tensor
+        """
+        if self.encrypted:
+            return NotImplemented
+        p = _ensure_tensorbase(p)
+        p = p / p.sum()
+        return TensorBase(np.random.choice(len(p), num_samples, replacement, p.data))
+
     def uniform_(self, low=0, high=1):
         """
         Fills the tensor in-place with numbers sampled unifromly
@@ -2598,7 +2631,7 @@ class TensorBase(object):
 
         Returns
         -------
-        Outut Tensor
+        Output Tensor
         """
         if self.encrypted:
             return NotImplemented


Test patch:
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
index 6ed5aec232..02b4f06d27 100644
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -730,6 +730,15 @@ class BernoulliTests(unittest.TestCase):
         self.assertTrue(np.all(t1.data >= 0) and np.all(t1.data <= 1))
 
 
+class MultinomialTests(unittest.TestCase):
+    def test_multinomial(self):
+        p = TensorBase([0.25, .25, .25, .25])
+        t1 = TensorBase(np.zeros((1, 5)))
+        t2 = t1.multinomial(p, len(p))
+        self.assertTupleEqual((len(p),), t2.shape())
+        self.assertTrue(np.all(t2.data >= 0) and np.all(t2.data <= len(p)))
+
+
 class CauchyTests(unittest.TestCase):
     def test_cauchy_(self):
         t = TensorBase(np.zeros([1, 2, 3, 4]))