Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 # TODO(http://crbug.com/669639): there are lots of ways to make the code | 5 # TODO(http://crbug.com/669639): there are lots of ways to make the code |
| 6 # in this file better. We avoid having separate todos per task; instead | 6 # in this file better. We avoid having separate todos per task; instead |
| 7 # see that meta-issue ticket. | 7 # see that meta-issue ticket. |
| 8 | 8 |
| 9 import logging | 9 import logging |
| 10 import math | 10 import math |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 36 where particular values for that random variable called ``y``. The | 36 where particular values for that random variable called ``y``. The |
| 37 partition function is called ``Z``. And (somewhat non-conventionally) | 37 partition function is called ``Z``. And (somewhat non-conventionally) |
| 38 we will call the log of the partition function ``zeta``. | 38 we will call the log of the partition function ``zeta``. |
| 39 | 39 |
| 40 This class is distinct from ``LogLinearModel`` in that we do not require | 40 This class is distinct from ``LogLinearModel`` in that we do not require |
| 41 a specification of ``Y``. This class is sufficient for determining | 41 a specification of ``Y``. This class is sufficient for determining |
| 42 which ``y`` is the most likely, though it can only return a "score" | 42 which ``y`` is the most likely, though it can only return a "score" |
| 43 rather than returning a probability per se. | 43 rather than returning a probability per se. |
| 44 """ | 44 """ |
| 45 | 45 |
| 46 def __init__(self, feature_function, weights, epsilon=None): | 46 def __init__(self, meta_feature, meta_weight, epsilon=None): |
| 47 """Construct a new model with the given weights and feature function. | 47 """Construct a new model with the meta_feature and meta_weight. |
| 48 | 48 |
| 49 Args: | 49 Args: |
| 50 feature_function: A function ``X -> Y -> list(FeatureValue)``. N.B., | 50 wrapped_feature: A function ``X -> Y -> list(FeatureValue)``. N.B., |
|
chanli
2017/01/19 04:26:06
Not in argument list above?
Sharu Jiang
2017/01/19 23:49:52
Oops, should have deleted it.
| |
| 51 for all ``x`` and ``y`` the length of ``feature_function(x)(y)`` | 51 for all ``x`` and ``y`` the length of ``wrapped_feature(x)(y)`` |
| 52 must be the same as the length of ``weights``. | 52 must be the same as the length of ``weights``. |
| 53 weights (dict of float): the weights for the features. The keys of | 53 meta_feature (MetaFeature): A function ``X -> Y -> MetaFeatureValue``. |
| 54 the dictionary are the names of the feature that weight is | 54 N.B., for all ``x`` and ``y`` the length of ``wrapped_feature(x)(y)`` |
| 55 must be the same as the length of ``weights``. | |
| 56 All features. | |
| 57 meta_weight (MetaWeight): All weights. the weights for the features. | |
| 58 The keys of the dictionary are the names of the feature that weight is | |
| 55 for. We take this argument as a dict rather than as a list so that | 59 for. We take this argument as a dict rather than as a list so that |
| 56 callers needn't worry about what order to provide the weights in. | 60 callers needn't worry about what order to provide the weights in. |
| 57 epsilon (float): The absolute-error threshold for considering a | 61 epsilon (float): The absolute-error threshold for considering a |
| 58 weight to be "equal to zero". N.B., this should be a positive | 62 weight to be "equal to zero". N.B., this should be a positive |
| 59 number, as we will compare it against the absolute value of | 63 number, as we will compare it against the absolute value of |
| 60 each weight. | 64 each weight. |
| 61 """ | 65 """ |
| 62 if epsilon is None: | 66 if epsilon is None: |
| 63 self._epsilon = EPSILON | 67 self._epsilon = EPSILON |
| 64 else: | 68 else: |
| 65 self._epsilon = epsilon | 69 self._epsilon = epsilon |
|
chanli
2017/01/19 04:26:06
This if else can be simplified to
self._epsilon
lijeffrey
2017/01/19 15:23:35
even simpler is self._epsilon = epsilon or EPSILON
Sharu Jiang
2017/01/19 23:49:52
if epsilon would be False if epsilon equals to zer
| |
| 66 | 70 |
| 67 # TODO(crbug.com/680207) Filter zero weights, use sparse representaion of | 71 self._meta_weight = meta_weight |
| 68 # weight covector. | 72 self._meta_weight.DropZeroWeights(self._epsilon) |
| 69 self._weights = { | |
| 70 name: weight for name, weight in weights.iteritems() | |
| 71 if self.IsNonZeroWeight(weight) | |
| 72 } | |
| 73 | 73 |
| 74 self._quadrance = None | 74 self._quadrance = None |
| 75 | |
| 76 # TODO(crbug.com/674752): we need better names for ``self._features``. | 75 # TODO(crbug.com/674752): we need better names for ``self._features``. |
| 77 def _Features(x): | 76 def _Features(x): |
| 78 """Wrap ``feature_function`` to memoize things and ensure types. | 77 """Wrap ``wrapped_feature`` to memoize things and ensure types. |
| 79 | 78 |
| 80 This outer wrapping takes each ``x`` to a memoized instance of | 79 This outer wrapping takes each ``x`` to a memoized instance of |
| 81 ``_FeaturesGivenX``. That is, for each ``x`` we return a | 80 ``_FeaturesGivenX``. That is, for each ``x`` we return a |
| 82 ``MemoizedFunction`` from ``Y`` to ``dict(str to FeatureValue)``. | 81 ``MemoizedFunction`` from ``Y`` to ``dict(str to FeatureValue)``. |
| 83 """ | 82 """ |
| 84 fx = feature_function(x) | |
| 85 def _FeaturesGivenX(y): | |
| 86 """Wrap ``feature_function(x)`` to ensure appropriate types. | |
| 87 | |
| 88 This inner wrapper ensures that the resulting ``FeatureValue`` | |
| 89 array has the same length as the weight covector. | |
| 90 """ | |
| 91 fxy = fx(y) | |
| 92 # N.B., we're assuming that ``len(self.weights)`` is O(1). | |
| 93 assert len(fxy) == len(self.weights), TypeError( | |
| 94 "vector length mismatch: %d != %d" % (len(fxy), len(self.weights))) | |
| 95 return fxy | |
| 96 | |
| 97 # Memoize on ``Y``, to ensure we don't need to recompute | 83 # Memoize on ``Y``, to ensure we don't need to recompute |
| 98 # ``FeatureValue``s nor recheck the lengths. | 84 # ``FeatureValue``s nor recheck the lengths. |
| 99 return MemoizedFunction(_FeaturesGivenX) | 85 return MemoizedFunction(meta_feature(x)) |
| 100 | 86 |
| 101 # Memoize on ``X``, to ensure we share the memo tables on ``Y``. | 87 # Memoize on ``X``, to ensure we share the memo tables on ``Y``. |
| 102 self._features = MemoizedFunction(_Features) | 88 self._features = MemoizedFunction(_Features) |
| 103 | 89 |
| 104 # TODO(crbug.com/674752): we need better names for ``self._scores``. | 90 # TODO(crbug.com/674752): we need better names for ``self._scores``. |
| 105 # N.B., this is just the inner product of ``self.weights`` | 91 # N.B., this is just the inner product of ``self._meta_weight`` |
| 106 # against ``self._features(x)``. If we can compute this in some | 92 # against ``self._features(x)``. If we can compute this in some |
| 107 # more efficient way, we should. In particular, we will want to | 93 # more efficient way, we should. In particular, we will want to |
| 108 # make the weights sparse, in which case we need to use a sparse | 94 # make the weights sparse, in which case we need to use a sparse |
| 109 # variant of the dot product. | 95 # variant of the dot product. |
| 110 self._scores = MemoizedFunction(lambda x: self._features(x).map( | 96 self._scores = MemoizedFunction(lambda x: self._features(x).map( |
| 111 lambda fxy: math.fsum(self.SingleFeatureScore(feature) | 97 lambda fxy: self._meta_weight * fxy)) |
| 112 for feature in fxy.itervalues()))) | |
| 113 | |
| 114 def IsNonZeroWeight(self, weight): | |
| 115 return isinstance(weight, float) and math.fabs(weight) >= self._epsilon | |
| 116 | |
| 117 def SingleFeatureScore(self, feature_value): | |
| 118 """Returns the score (aka weighted value) of a ``FeatureValue``. | |
| 119 | |
| 120 Args: | |
| 121 feature_value (FeatureValue): the feature value to check. | |
| 122 | |
| 123 Returns: | |
| 124 The score of the feature value. | |
| 125 """ | |
| 126 return feature_value.value * self._weights.get(feature_value.name, 0.) | |
| 127 | |
| 128 # TODO(crbug.com/673964): something better for detecting "close to log(0)". | |
| 129 def LogZeroish(self, x): | |
| 130 """Determine whether a float is close enough to log(0). | |
| 131 | |
| 132 If a ``FeatureValue`` has a (log-domain) score of -inf for a given | |
| 133 ``Suspect``, then that suspect has zero probability of being the | |
| 134 culprit. We want to filter these suspects out, to clean up the | |
| 135 output of classification; so this method encapsulates the logic of | |
| 136 that check. | |
| 137 | |
| 138 Args: | |
| 139 x (float): the float to check | |
| 140 | |
| 141 Returns: | |
| 142 ``True`` if ``x`` is close enough to log(0); else ``False``. | |
| 143 """ | |
| 144 return x < 0 and math.isinf(x) | |
| 145 | 98 |
| 146 def ClearWeightBasedMemos(self): | 99 def ClearWeightBasedMemos(self): |
| 147 """Clear all the memos that depend on the weight covector.""" | 100 """Clear all the memos that depend on the weight covector.""" |
| 148 self._quadrance = None | 101 self._quadrance = None |
| 149 self._scores.ClearMemos() | 102 self._scores.ClearMemos() |
| 150 | 103 |
| 151 def ClearAllMemos(self): | 104 def ClearAllMemos(self): |
| 152 """Clear all memos, even those independent of the weight covector.""" | 105 """Clear all memos, even those independent of the weight covector.""" |
| 153 self.ClearWeightBasedMemos() | 106 self.ClearWeightBasedMemos() |
| 154 self._features.ClearMemos() | 107 self._features.ClearMemos() |
| 155 | 108 |
| 156 @property | 109 @property |
| 157 def weights(self): | 110 def meta_weight(self): |
| 158 """The weight covector. | 111 """The weight covector. |
| 159 | 112 |
| 160 At present we return the weights as an dict mapping feature name to its | 113 At present we return the weights as an dict mapping feature name to its |
| 161 weight, but in the future that may be replaced by a more general type which | 114 weight, but in the future that may be replaced by a more general type which |
| 162 specifies the semantics rather than the implementation details. | 115 specifies the semantics rather than the implementation details. |
| 163 """ | 116 """ |
| 164 return self._weights | 117 return self._meta_weight |
| 118 | |
| 119 @meta_weight.setter | |
| 120 def meta_weight(self, new_meta_weight): | |
| 121 self._meta_weight = new_meta_weight | |
| 122 self._meta_weight.DropZeroWeights(self._epsilon) | |
| 165 | 123 |
| 166 @property | 124 @property |
| 167 def l0(self): # pragma: no cover | 125 def l0(self): # pragma: no cover |
| 168 """The l0-norm of the weight covector. | 126 """The l0-norm of the weight covector. |
| 169 | 127 |
| 170 N.B., despite being popularly called the "l0-norm", this isn't | 128 N.B., despite being popularly called the "l0-norm", this isn't |
| 171 actually a norm in the mathematical sense.""" | 129 actually a norm in the mathematical sense.""" |
| 172 return float(len(self.weights) - self.weights.values().count(0.)) | 130 return self._meta_weight.l0 |
| 173 | 131 |
| 174 @property | 132 @property |
| 175 def l1(self): # pragma: no cover | 133 def l1(self): # pragma: no cover |
| 176 """The l1 (aka: Manhattan) norm of the weight covector.""" | 134 """The l1 (aka: Manhattan) norm of the weight covector.""" |
| 177 return math.fsum(math.fabs(w) for w in self.weights.itervalues()) | 135 return self._meta_weight.l1 |
| 178 | 136 |
| 179 @property | 137 @property |
| 180 def quadrance(self): | 138 def quadrance(self): |
| 181 """The square of the l2 norm of the weight covector. | 139 """The square of the l2 norm of the weight covector. |
| 182 | 140 |
| 183 This value is often more helpful to have direct access to, as it | 141 This value is often more helpful to have direct access to, as it |
| 184 avoids the need for non-rational functions (e.g., sqrt) and shows up | 142 avoids the need for non-rational functions (e.g., sqrt) and shows up |
| 185 as its own quantity in many places. Also, computing it directly avoids | 143 as its own quantity in many places. Also, computing it directly avoids |
| 186 the error introduced by squaring the square-root of an IEEE-754 float. | 144 the error introduced by squaring the square-root of an IEEE-754 float. |
| 187 """ | 145 """ |
| 188 if self._quadrance is None: | 146 return self._meta_weight.quadrance |
| 189 self._quadrance = math.fsum( | |
| 190 math.fabs(w)**2 for w in self.weights.itervalues()) | |
| 191 | |
| 192 return self._quadrance | |
| 193 | 147 |
| 194 @property | 148 @property |
| 195 def l2(self): | 149 def l2(self): |
| 196 """The l2 (aka Euclidean) norm of the weight covector. | 150 """The l2 (aka Euclidean) norm of the weight covector. |
| 197 | 151 |
| 198 If you need the square of the l2-norm, do not use this property. | 152 If you need the square of the l2-norm, do not use this property. |
| 199 Instead, use the ``quadrance`` property which is more accurate than | 153 Instead, use the ``quadrance`` property which is more accurate than |
| 200 squaring this one. | 154 squaring this one. |
| 201 """ | 155 """ |
| 202 return math.sqrt(self.quadrance) | 156 return math.sqrt(self.quadrance) |
| 203 | 157 |
| 158 # TODO(crbug.com/673964): something better for detecting "close to log(0)". | |
| 159 def LogZeroish(self, x): | |
| 160 """Determine whether a float is close enough to log(0). | |
| 161 | |
| 162 If a ``FeatureValue`` has a (log-domain) score of -inf for a given | |
| 163 ``Suspect``, then that suspect has zero probability of being the | |
| 164 culprit. We want to filter these suspects out, to clean up the | |
| 165 output of classification; so this method encapsulates the logic of | |
| 166 that check. | |
| 167 | |
| 168 Args: | |
| 169 x (float): the float to check | |
| 170 | |
| 171 Returns: | |
| 172 ``True`` if ``x`` is close enough to log(0); else ``False``. | |
| 173 """ | |
| 174 return x < 0 and math.isinf(x) | |
| 175 | |
| 204 def Features(self, x): | 176 def Features(self, x): |
| 205 """Returns a function mapping ``y`` to its feature vector given ``x``. | 177 """Returns a function mapping ``y`` to its feature vector given ``x``. |
| 206 | 178 |
| 207 Args: | 179 Args: |
| 208 x (X): the value of the independent variable. | 180 x (X): the value of the independent variable. |
| 209 | 181 |
| 210 Returns: | 182 Returns: |
| 211 A ``MemoizedFunction`` of type ``Y -> dict(str to float)``. | 183 A ``MemoizedFunction`` of type ``Y -> dict(str to float)``. |
| 212 """ | 184 """ |
| 213 return self._features(x) | 185 return self._features(x) |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 226 ``Probability(x)``. | 198 ``Probability(x)``. |
| 227 | 199 |
| 228 Args: | 200 Args: |
| 229 x (X): the value of the independent variable. | 201 x (X): the value of the independent variable. |
| 230 | 202 |
| 231 Returns: | 203 Returns: |
| 232 A ``MemoizedFunction`` of type ``Y -> float``. | 204 A ``MemoizedFunction`` of type ``Y -> float``. |
| 233 """ | 205 """ |
| 234 return self._scores(x) | 206 return self._scores(x) |
| 235 | 207 |
| 236 def FormatReasons(self, features): | |
| 237 """Collect and format a list of all ``FeatureValue.reason`` strings. | |
| 238 | |
| 239 Args: | |
| 240 features (iterable of FeatureValue): the values whose ``reason`` | |
| 241 strings should be collected. | |
| 242 | |
| 243 Returns: | |
| 244 A list of ``(str, float, str)`` triples; where the first string is | |
| 245 the feature name, the float is some numeric representation of how | |
| 246 much influence this feature exerts on the ``Suspect`` being blamed, | |
| 247 and the final string is the ``FeatureValue.reason``. The list is | |
| 248 sorted by feature name, just to ensure that it comes out in some | |
| 249 canonical order. | |
| 250 | |
| 251 At present, the float is the log-domain score of the feature | |
| 252 value. However, this isn't the best thing for UX reasons. In the | |
| 253 future it might be replaced by the normal-domain score, or by | |
| 254 the probability. | |
| 255 """ | |
| 256 formatted_reasons = [] | |
| 257 for feature in features: | |
| 258 feature_score = self.SingleFeatureScore(feature) | |
| 259 if self.LogZeroish(feature_score): # pragma: no cover | |
| 260 logging.debug('Discarding reasons from feature %s' | |
| 261 ' because it has zero probability' % feature.name) | |
| 262 continue | |
| 263 | |
| 264 formatted_reasons.append((feature.name, feature_score, feature.reason)) | |
| 265 | |
| 266 formatted_reasons.sort(key=lambda formatted_reason: formatted_reason[0]) | |
| 267 return formatted_reasons | |
| 268 | |
| 269 def AggregateChangedFiles(self, features): | |
| 270 """Merge multiple``FeatureValue.changed_files`` lists into one. | |
| 271 | |
| 272 Args: | |
| 273 features (iterable of FeatureValue): the values whose ``changed_files`` | |
| 274 lists should be aggregated. | |
| 275 | |
| 276 Returns: | |
| 277 A list of ``ChangedFile`` objects sorted by file name. The sorting | |
| 278 is not essential, but is provided to ease testing by ensuring the | |
| 279 output is in some canonical order. | |
| 280 | |
| 281 Raises: | |
| 282 ``ValueError`` if any file name is given inconsistent ``blame_url``s. | |
| 283 """ | |
| 284 all_changed_files = {} | |
| 285 for feature in features: | |
| 286 if self.LogZeroish(self.SingleFeatureScore(feature)): # pragma: no cover | |
| 287 logging.debug('Discarding changed files from feature %s' | |
| 288 ' because it has zero probability' % feature.name) | |
| 289 continue | |
| 290 | |
| 291 for changed_file in feature.changed_files or []: | |
| 292 accumulated_changed_file = all_changed_files.get(changed_file.name) | |
| 293 if accumulated_changed_file is None: | |
| 294 all_changed_files[changed_file.name] = changed_file | |
| 295 continue | |
| 296 | |
| 297 if (accumulated_changed_file.blame_url != | |
| 298 changed_file.blame_url): # pragma: no cover | |
| 299 raise ValueError('Blame URLs do not match: %s != %s' | |
| 300 % (accumulated_changed_file.blame_url, changed_file.blame_url)) | |
| 301 accumulated_changed_file.reasons.extend(changed_file.reasons or []) | |
| 302 | |
| 303 changed_files = all_changed_files.values() | |
| 304 changed_files.sort(key=lambda changed_file: changed_file.name) | |
| 305 return changed_files | |
| 306 | |
| 307 | 208 |
| 308 class LogLinearModel(UnnormalizedLogLinearModel): | 209 class LogLinearModel(UnnormalizedLogLinearModel): |
| 309 """A loglinear probability model. | 210 """A loglinear probability model. |
| 310 | 211 |
| 311 This class is distinct from ``UnnormalizedLogLinearModel`` in that | 212 This class is distinct from ``UnnormalizedLogLinearModel`` in that |
| 312 we can provide probabilities (not just scores). However, to do so we | 213 we can provide probabilities (not just scores). However, to do so we |
| 313 require a specification of the subsets of ``Y`` for each ``x``. | 214 require a specification of the subsets of ``Y`` for each ``x``. |
| 314 """ | 215 """ |
| 315 def __init__(self, Y_given_X, feature_function, weights, epsilon=None): | 216 def __init__(self, Y_given_X, meta_feature, meta_weight, epsilon=None): |
| 316 """Construct a new probabilistic model. | 217 """Construct a new probabilistic model. |
| 317 | 218 |
| 318 Args: | 219 Args: |
| 319 Y_given_X: a function from ``X`` to an iterable object giving the | 220 Y_given_X: a function from ``X`` to an iterable object giving the |
| 320 subset of ``Y`` which has non-zero probability given the | 221 subset of ``Y`` which has non-zero probability given the |
| 321 ``x``. When in doubt about whether some ``y`` has zero probability | 222 ``x``. When in doubt about whether some ``y`` has zero probability |
| 322 or not, it is always safe/correct to return a larger subset of | 223 or not, it is always safe/correct to return a larger subset of |
| 323 ``Y`` (it'll just take more computation time is all). This is | 224 ``Y`` (it'll just take more computation time is all). This is |
| 324 needed for computing the partition function and expectation. N.B., | 225 needed for computing the partition function and expectation. N.B., |
| 325 we do not actually need to know/enumerate of *all* of ``Y``, | 226 we do not actually need to know/enumerate of *all* of ``Y``, |
| 326 only the subsets for each ``x``. | 227 only the subsets for each ``x``. |
| 327 feature_function: A function ``X -> Y -> list(float)``. N.B., | 228 meta_feature (MetaFeature): A function ``X -> Y -> MetaFeatureValue``. |
| 328 for all ``x`` and ``y`` the length of ``feature_function(x)(y)`` | 229 N.B., for all ``x`` and ``y`` the length of ``wrapped_feature(x)(y)`` |
| 329 must be the same as the length of ``weights``. | 230 must be the same as the length of ``weights``. |
| 330 weights (dict of float): the weights for the features. The keys of | 231 All features. |
| 331 the dictionary are the names of the feature that weight is | 232 meta_weight (MetaWeight): All weights. the weights for the features. |
| 233 The keys of the dictionary are the names of the feature that weight is | |
| 332 for. We take this argument as a dict rather than as a list so that | 234 for. We take this argument as a dict rather than as a list so that |
| 333 callers needn't worry about what order to provide the weights in. | 235 callers needn't worry about what order to provide the weights in. |
| 334 epsilon (float): The absolute-error threshold for considering a | 236 epsilon (float): The absolute-error threshold for considering a |
| 335 weight to be "equal to zero". N.B., this should be a positive | 237 weight to be "equal to zero". N.B., this should be a positive |
| 336 number, as we will compare it against the absolute value of | 238 number, as we will compare it against the absolute value of |
| 337 each weight. | 239 each weight. |
| 338 """ | 240 """ |
| 339 super(LogLinearModel, self).__init__(feature_function, weights, epsilon) | 241 super(LogLinearModel, self).__init__(meta_feature, meta_weight, epsilon) |
| 340 | 242 |
| 341 self._Y = Y_given_X | 243 self._Y = Y_given_X |
| 342 | 244 |
| 343 def _LogZ(x): | 245 def _LogZ(x): |
| 344 score_given_x = self._scores(x) | 246 score_given_x = self._scores(x) |
| 345 return logsumexp([score_given_x(y) for y in self._Y(x)]) | 247 return logsumexp([score_given_x(y) for y in self._Y(x)]) |
| 346 self._zetas = MemoizedFunction(_LogZ) | 248 self._zetas = MemoizedFunction(_LogZ) |
| 347 | 249 |
| 348 def ClearWeightBasedMemos(self): | 250 def ClearWeightBasedMemos(self): |
| 349 """Clear all the memos that depend on the weight covector.""" | 251 """Clear all the memos that depend on the weight covector.""" |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 372 x (X): the value of the independent variable. | 274 x (X): the value of the independent variable. |
| 373 | 275 |
| 374 Returns: | 276 Returns: |
| 375 The normalizing constant of ``LogProbability(x)``. | 277 The normalizing constant of ``LogProbability(x)``. |
| 376 """ | 278 """ |
| 377 return self._zetas(x) | 279 return self._zetas(x) |
| 378 | 280 |
| 379 def Probability(self, x): | 281 def Probability(self, x): |
| 380 """The normal-domain distribution over ``y`` given ``x``. | 282 """The normal-domain distribution over ``y`` given ``x``. |
| 381 | 283 |
| 382 That is, ``self.Probability(x)(y)`` returns ``p(y | x; self.weights)`` | 284 That is, ``self.Probability(x)(y)`` returns ``p(y | x; self._meta_weight)`` |
| 383 which is the model's estimation of ``Pr(y|x)``. | 285 which is the model's estimation of ``Pr(y|x)``. |
| 384 | 286 |
| 385 If you need the log-probability, don't use this method. Instead, | 287 If you need the log-probability, don't use this method. Instead, |
| 386 use the ``LogProbability`` method which computes it directly and | 288 use the ``LogProbability`` method which computes it directly and |
| 387 thus is more accurate than taking the log of this one. | 289 thus is more accurate than taking the log of this one. |
| 388 """ | 290 """ |
| 389 logprob_given_x = self.LogProbability(x) | 291 logprob_given_x = self.LogProbability(x) |
| 390 return lambda y: math.exp(logprob_given_x(y)) | 292 return lambda y: math.exp(logprob_given_x(y)) |
| 391 | 293 |
| 392 def LogProbability(self, x): | 294 def LogProbability(self, x): |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 413 the particular array returned may not actually be one that the | 315 the particular array returned may not actually be one that the |
| 414 function returns; rather, it's a sort of average of all the results | 316 function returns; rather, it's a sort of average of all the results |
| 415 returned. For more information you can take a look at Wikipedia | 317 returned. For more information you can take a look at Wikipedia |
| 416 <https://en.wikipedia.org/wiki/Expected_value>. | 318 <https://en.wikipedia.org/wiki/Expected_value>. |
| 417 """ | 319 """ |
| 418 prob_given_x = self.Probability(x) | 320 prob_given_x = self.Probability(x) |
| 419 # N.B., the ``*`` below is vector scaling! If we want to make this | 321 # N.B., the ``*`` below is vector scaling! If we want to make this |
| 420 # method polymorphic in the return type of ``f`` then we'll need an | 322 # method polymorphic in the return type of ``f`` then we'll need an |
| 421 # API that provides both scaling and ``vsum``. | 323 # API that provides both scaling and ``vsum``. |
| 422 return vsum([prob_given_x(y) * f(y) for y in self._Y(x)]) | 324 return vsum([prob_given_x(y) * f(y) for y in self._Y(x)]) |
| 423 | |
| OLD | NEW |