Source code for theanolm.backend.classdistribution

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""A module that implements classes for sampling noise words.
"""

from abc import abstractmethod, ABCMeta

import numpy
import theano
import theano.tensor as tensor


[docs] class ClassDistribution(object, metaclass=ABCMeta): """Base Class for Probability Distributions A probability distribution class implements methods for sampling words and converting words to probabilities. """
[docs] def __init__(self, random): """Constructs a probability distribution. :type random: MRG_RandomStreams :param random: a random number generator """ self._random = random self._float_type = numpy.dtype(theano.config.floatX).type
[docs] @abstractmethod def sample(self, minibatch_size, num_samples): """Samples given number of class IDs per mini-batch element. """ assert False
[docs] @abstractmethod def probs(self, class_ids): """Converts class IDs to probabilities. The returned value may be a NumPy array or Theano tensor. It may be an array of the same shape as ``class_ids`` or a scalar which can be broadcasted to that shape. :type class_ids: numpy.ndarray :param class_ids: classes whose probabilities are requested :rtype: theano.config.floatX, numpy.ndarray, or Variable :returns: an array of the same shape as ``class_ids`` or a scalar which can be broadcasted to that shape """ assert False
[docs] class UniformDistribution(ClassDistribution):
[docs] def __init__(self, random, support): """Constructs a uniform probability distribution. :type random: MRG_RandomStreams :param random: a random number generator :type support: int :param support: the sampled values will be in the range from 0 to ``support - 1`` """ super().__init__(random) self._support = support
[docs] def sample(self, minibatch_size, num_samples): """Samples given number of class IDs per mini-batch element. :type minibatch_size: int :param minibatch_size: number of mini-batch elements :type num_samples: int :param num_samples: number of samples to draw for each mini-batch element :rtype: Variable :returns: a 2-dimensional tensor variable describing the ``num_samples`` random samples for ``minibatch_size`` mini-batch element """ sample = self._random.uniform(size=(minibatch_size, num_samples), high=self._support) return sample.astype('int64')
[docs] def probs(self, class_ids): """Converts class IDs to probabilities. The returned value is a scalar. It will be broadcasted to the correct shape by Theano. :type class_ids: Variable :param class_ids: a symbolic vector describing the classes whose probabilities are requested (ignored) :rtype: theano.config.floatX :returns: the probability of a single class """ return self._float_type(1.0 / self._support)
[docs] class LogUniformDistribution(ClassDistribution):
[docs] def __init__(self, random, support): """Constructs a probability distribution such that the logarithm of the samples is uniformly distributed. :type random: MRG_RandomStreams :param random: a random number generator :type support: int :param support: the sampled values will be in the range from 0 to ``support - 1`` """ super().__init__(random) # Random numbers will be in the range [0, log(support + 1)[. self._log_support = numpy.log(support + 1)
[docs] def sample(self, minibatch_size, num_samples): """Samples given number of class IDs per mini-batch element. :type minibatch_size: int :param minibatch_size: number of mini-batch elements :type num_samples: int :param num_samples: number of samples to draw for each mini-batch element :rtype: Variable :returns: a 2-dimensional tensor variable describing the ``num_samples`` random samples for ``minibatch_size`` mini-batch element """ logs = self._random.uniform(size=(minibatch_size, num_samples), high=self._log_support) # The exponent will be in the range [1, support + 1[. sample = tensor.exp(logs) - 1 return sample.astype('int64')
[docs] def probs(self, class_ids): """Converts class IDs to probabilities. The returned value is a scalar. It will be broadcasted to the correct shape by Theano. :type class_ids: Variable :param class_ids: a symbolic vector describing the classes whose probabilities are requested :rtype: Variable :returns: probabilities of the classes """ # A sample will be in the range [x, x + 1[ when the log is in the range # [log(x + 1), log(x + 2)[. Thus the probability of x is # (log(x + 2) - log(x + 1)) / log_support. range = (class_ids + 2) / (class_ids + 1) return tensor.log(range) / self._log_support
[docs] class MultinomialDistribution(ClassDistribution):
[docs] def __init__(self, random, probs): """Constructs a multinomial probability distribution. :type random: MRG_RandomStreams :param random: a random number generator :type probs: Variable :param probs: a tensor vector that defines the distribution where to sample from """ super().__init__(random) self._probs = probs
[docs] def sample(self, minibatch_size, num_samples): """Samples given number of class IDs per mini-batch element. Theano supports currently only sampling without replacement. Thus if a different set of samples is required for each mini-batch element, we repeat the distribution for each mini-batch element. At some point the old interface, ``multinomial_wo_replacement()`` was faster, but is not supported anymore. In the future ``target='cpu'`` may improve the speed. :type minibatch_size: int :param minibatch_size: number of mini-batch elements :type num_samples: int :param num_samples: number of samples to draw for each mini-batch element :rtype: Variable :returns: a 2-dimensional tensor variable describing the ``num_samples`` random samples for ``minibatch_size`` mini-batch element """ probs = self._probs[None, :] probs = tensor.tile(probs, [minibatch_size, 1]) # return self._random.multinomial_wo_replacement(pvals=probs, n=num_samples) sample = self._random.choice(size=num_samples, replace=False, p=probs, target='cpu') # Some versions of Theano seem to return crazy high or low numbers because # of some rounding errors, so we take the modulo to be safe. sample %= probs.shape[1] return sample
[docs] def probs(self, class_ids): """Converts class IDs to probabilities. The returned value is a scalar. It will be broadcasted to the correct shape by Theano. :type class_ids: Variable :param class_ids: a symbolic vector describing the classes whose probabilities are requested :rtype: Variable :returns: symbolic vector describing the probabilities of the classes """ return self._probs[class_ids]