nltk 3.2__zip → 3.3__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
  2. {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
  3. {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
  4. {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
  5. nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
  6. nltk-3.3/nltk/VERSION +1 -0
  7. {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
  8. {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
  9. {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
  10. {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
  11. {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
  12. {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
  13. {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
  14. {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
  15. {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
  16. {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
  17. {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
  18. {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
  19. {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
  20. {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
  21. {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
  22. {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
  23. {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
  24. nltk-3.3/nltk/ccg/logic.py +46 -0
  25. {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
  26. {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
  27. {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
  28. {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
  29. {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
  30. {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
  31. {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
  32. {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
  33. {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
  34. {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
  35. {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
  36. {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
  37. {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
  38. {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
  39. {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
  40. {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
  41. {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
  42. {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
  43. {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
  44. {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
  45. {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
  46. {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
  47. {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
  48. {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
  49. {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
  50. {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
  51. {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
  52. {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
  53. {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
  54. {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
  55. {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
  56. {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
  57. {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
  58. nltk-3.3/nltk/collections.py +640 -0
  59. {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
  60. nltk-3.3/nltk/compat.py +365 -0
  61. {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
  62. {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
  63. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
  64. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
  65. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
  66. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
  67. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
  68. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
  69. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
  70. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
  71. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
  72. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
  73. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
  74. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
  75. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
  76. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
  77. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
  78. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
  79. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
  80. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
  81. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
  82. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
  83. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
  84. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
  85. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
  86. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
  87. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
  88. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
  89. nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
  90. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
  91. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
  92. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
  93. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
  94. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
  95. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
  96. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
  97. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
  98. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
  99. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
  100. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
  101. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
  102. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
  103. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
  104. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
  105. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
  106. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
  107. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
  108. nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
  109. nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
  110. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
  111. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
  112. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
  113. {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
  114. {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
  115. {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
  116. {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
  117. {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
  118. {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
  119. {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
  120. {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
  121. {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
  122. {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
  123. {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
  124. {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
  125. {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
  126. {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
  127. {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
  128. {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
  129. {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
  130. {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
  131. {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
  132. {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
  133. {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
  134. {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
  135. {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
  136. {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
  137. {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
  138. {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
  139. nltk-3.3/nltk/metrics/aline.py +607 -0
  140. {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
  141. {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
  142. {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
  143. {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
  144. {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
  145. {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
  146. {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
  147. {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
  148. {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
  149. {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
  150. {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
  151. {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
  152. {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
  153. {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
  154. {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
  155. {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
  156. {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
  157. nltk-3.3/nltk/parse/corenlp.py +780 -0
  158. {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
  159. {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
  160. {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
  161. {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
  162. {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
  163. {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
  164. {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
  165. {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
  166. {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
  167. {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
  168. {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
  169. {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
  170. {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
  171. {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
  172. {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
  173. {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
  174. {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
  175. {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
  176. {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
  177. {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
  178. {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
  179. {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
  180. {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
  181. {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
  182. {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
  183. {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
  184. {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
  185. {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
  186. {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
  187. {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
  188. {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
  189. {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
  190. {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
  191. {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
  192. {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
  193. {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
  194. {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
  195. nltk-3.3/nltk/stem/arlstem.py +369 -0
  196. {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
  197. {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
  198. nltk-3.3/nltk/stem/porter.py +710 -0
  199. {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
  200. {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
  201. {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
  202. {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
  203. {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
  204. {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
  205. {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
  206. {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
  207. {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
  208. {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
  209. {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
  210. {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
  211. {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
  212. {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
  213. {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
  214. {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
  215. {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
  216. {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
  217. {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
  218. {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
  219. {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
  220. {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
  221. {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
  222. {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
  223. {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
  224. {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
  225. {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
  226. {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
  227. {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
  228. {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
  229. {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
  230. nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
  231. {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
  232. {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
  233. {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
  234. {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
  235. {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
  236. {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
  237. nltk-3.3/nltk/test/collections.doctest +20 -0
  238. {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
  239. {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
  240. {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
  241. nltk-3.3/nltk/test/concordance.doctest +55 -0
  242. {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
  243. {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
  244. {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
  245. {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
  246. {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
  247. {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
  248. {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
  249. {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
  250. {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
  251. {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
  252. {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
  253. {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
  254. {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
  255. {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
  256. {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
  257. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
  258. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
  259. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
  260. {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
  261. {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
  262. {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
  263. {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
  264. {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
  265. {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
  266. {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
  267. {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
  268. {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
  269. {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
  270. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
  271. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
  272. {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
  273. {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
  274. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
  275. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
  276. {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
  277. {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
  278. {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
  279. {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
  280. {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
  281. {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
  282. {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
  283. {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
  284. {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
  285. {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
  286. {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
  287. {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
  288. {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
  289. {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
  290. {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
  291. {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
  292. {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
  293. {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
  294. {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
  295. {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
  296. {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
  297. nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
  298. nltk-3.3/nltk/test/unit/test_aline.py +52 -0
  299. nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
  300. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
  301. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
  302. nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
  303. nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
  304. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
  305. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
  306. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
  307. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
  308. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
  309. nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
  310. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
  311. nltk-3.3/nltk/test/unit/test_senna.py +74 -0
  312. nltk-3.3/nltk/test/unit/test_stem.py +121 -0
  313. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
  314. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
  315. nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
  316. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
  317. nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
  318. nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
  319. nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
  320. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
  321. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
  322. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
  323. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
  324. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
  325. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
  326. nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
  327. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
  328. {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
  329. {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
  330. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
  331. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
  332. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
  333. {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
  334. {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
  335. {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
  336. {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
  337. {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
  338. {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
  339. {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
  340. nltk-3.3/nltk/tokenize/nist.py +173 -0
  341. {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
  342. {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
  343. nltk-3.3/nltk/tokenize/repp.py +151 -0
  344. {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
  345. {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
  346. {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
  347. nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
  348. {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
  349. nltk-3.3/nltk/tokenize/toktok.py +155 -0
  350. nltk-3.3/nltk/tokenize/treebank.py +363 -0
  351. nltk-3.3/nltk/tokenize/util.py +261 -0
  352. {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
  353. {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
  354. {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
  355. {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
  356. nltk-3.3/nltk/translate/chrf_score.py +137 -0
  357. {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
  358. {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
  359. nltk-3.3/nltk/translate/gleu_score.py +193 -0
  360. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
  361. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
  362. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
  363. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
  364. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
  365. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
  366. {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
  367. nltk-3.3/nltk/translate/nist_score.py +178 -0
  368. {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
  369. {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
  370. {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
  371. {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
  372. {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
  373. {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
  374. {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
  375. {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
  376. {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
  377. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
  378. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
  379. {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
  380. nltk-3.3/nltk/util.py +751 -0
  381. {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
  382. {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
  383. {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
  384. nltk-3.3/nltk.egg-info/requires.txt +31 -0
  385. {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
  386. {nltk-3.2 → nltk-3.3}/setup.py +102 -75
  387. nltk-3.2/nltk/VERSION +0 -1
  388. nltk-3.2/nltk/compat.py +0 -680
  389. nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
  390. nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
  391. nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
  392. nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
  393. nltk-3.2/nltk/six.py +0 -868
  394. nltk-3.2/nltk/stem/porter.py +0 -692
  395. nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
  396. nltk-3.2/nltk/test/unit/test_stem.py +0 -36
  397. nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
  398. nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
  399. nltk-3.2/nltk/tokenize/treebank.py +0 -124
  400. nltk-3.2/nltk/tokenize/util.py +0 -90
  401. nltk-3.2/nltk/util.py +0 -1364
  402. {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
  403. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
  404. {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
  405. {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
  406. {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
@@ -1,2355 +1,2410 @@
1
- # -*- coding: utf-8 -*-
2
- # Natural Language Toolkit: Probability and Statistics
3
- #
4
- # Copyright (C) 2001-2015 NLTK Project
5
- # Author: Edward Loper <edloper@gmail.com>
6
- # Steven Bird <stevenbird1@gmail.com> (additions)
7
- # Trevor Cohn <tacohn@cs.mu.oz.au> (additions)
8
- # Peter Ljunglöf <peter.ljunglof@heatherleaf.se> (additions)
9
- # Liang Dong <ldong@clemson.edu> (additions)
10
- # Geoffrey Sampson <sampson@cantab.net> (additions)
11
- # Ilia Kurenkov <ilia.kurenkov@gmail.com> (additions)
12
- #
13
- # URL: <http://nltk.org/>
14
- # For license information, see LICENSE.TXT
15
-
16
- """
17
- Classes for representing and processing probabilistic information.
18
-
19
- The ``FreqDist`` class is used to encode "frequency distributions",
20
- which count the number of times that each outcome of an experiment
21
- occurs.
22
-
23
- The ``ProbDistI`` class defines a standard interface for "probability
24
- distributions", which encode the probability of each outcome for an
25
- experiment. There are two types of probability distribution:
26
-
27
- - "derived probability distributions" are created from frequency
28
- distributions. They attempt to model the probability distribution
29
- that generated the frequency distribution.
30
- - "analytic probability distributions" are created directly from
31
- parameters (such as variance).
32
-
33
- The ``ConditionalFreqDist`` class and ``ConditionalProbDistI`` interface
34
- are used to encode conditional distributions. Conditional probability
35
- distributions can be derived or analytic; but currently the only
36
- implementation of the ``ConditionalProbDistI`` interface is
37
- ``ConditionalProbDist``, a derived distribution.
38
-
39
- """
40
- from __future__ import print_function, unicode_literals, division
41
-
42
- import math
43
- import random
44
- import warnings
45
- import array
46
- from operator import itemgetter
47
- from collections import defaultdict
48
- from functools import reduce
49
- from nltk import compat
50
- from nltk.compat import Counter
51
-
52
- from nltk.internals import raise_unorderable_types
53
-
54
- _NINF = float('-1e300')
55
-
56
- ##//////////////////////////////////////////////////////
57
- ## Frequency Distributions
58
- ##//////////////////////////////////////////////////////
59
-
60
- @compat.python_2_unicode_compatible
61
- class FreqDist(Counter):
62
- """
63
- A frequency distribution for the outcomes of an experiment. A
64
- frequency distribution records the number of times each outcome of
65
- an experiment has occurred. For example, a frequency distribution
66
- could be used to record the frequency of each word type in a
67
- document. Formally, a frequency distribution can be defined as a
68
- function mapping from each sample to the number of times that
69
- sample occurred as an outcome.
70
-
71
- Frequency distributions are generally constructed by running a
72
- number of experiments, and incrementing the count for a sample
73
- every time it is an outcome of an experiment. For example, the
74
- following code will produce a frequency distribution that encodes
75
- how often each word occurs in a text:
76
-
77
- >>> from nltk.tokenize import word_tokenize
78
- >>> from nltk.probability import FreqDist
79
- >>> sent = 'This is an example sentence'
80
- >>> fdist = FreqDist()
81
- >>> for word in word_tokenize(sent):
82
- ... fdist[word.lower()] += 1
83
-
84
- An equivalent way to do this is with the initializer:
85
-
86
- >>> fdist = FreqDist(word.lower() for word in word_tokenize(sent))
87
-
88
- """
89
-
90
- def __init__(self, samples=None):
91
- """
92
- Construct a new frequency distribution. If ``samples`` is
93
- given, then the frequency distribution will be initialized
94
- with the count of each object in ``samples``; otherwise, it
95
- will be initialized to be empty.
96
-
97
- In particular, ``FreqDist()`` returns an empty frequency
98
- distribution; and ``FreqDist(samples)`` first creates an empty
99
- frequency distribution, and then calls ``update`` with the
100
- list ``samples``.
101
-
102
- :param samples: The samples to initialize the frequency
103
- distribution with.
104
- :type samples: Sequence
105
- """
106
- Counter.__init__(self, samples)
107
-
108
- def N(self):
109
- """
110
- Return the total number of sample outcomes that have been
111
- recorded by this FreqDist. For the number of unique
112
- sample values (or bins) with counts greater than zero, use
113
- ``FreqDist.B()``.
114
-
115
- :rtype: int
116
- """
117
- return sum(self.values())
118
-
119
- def B(self):
120
- """
121
- Return the total number of sample values (or "bins") that
122
- have counts greater than zero. For the total
123
- number of sample outcomes recorded, use ``FreqDist.N()``.
124
- (FreqDist.B() is the same as len(FreqDist).)
125
-
126
- :rtype: int
127
- """
128
- return len(self)
129
-
130
- def hapaxes(self):
131
- """
132
- Return a list of all samples that occur once (hapax legomena)
133
-
134
- :rtype: list
135
- """
136
- return [item for item in self if self[item] == 1]
137
-
138
-
139
- def Nr(self, r, bins=None):
140
- return self.r_Nr(bins)[r]
141
-
142
- def r_Nr(self, bins=None):
143
- """
144
- Return the dictionary mapping r to Nr, the number of samples with frequency r, where Nr > 0.
145
-
146
- :type bins: int
147
- :param bins: The number of possible sample outcomes. ``bins``
148
- is used to calculate Nr(0). In particular, Nr(0) is
149
- ``bins-self.B()``. If ``bins`` is not specified, it
150
- defaults to ``self.B()`` (so Nr(0) will be 0).
151
- :rtype: int
152
- """
153
-
154
- _r_Nr = defaultdict(int)
155
- for count in self.values():
156
- _r_Nr[count] += 1
157
-
158
- # Special case for Nr[0]:
159
- _r_Nr[0] = bins - self.B() if bins is not None else 0
160
-
161
- return _r_Nr
162
-
163
- def _cumulative_frequencies(self, samples):
164
- """
165
- Return the cumulative frequencies of the specified samples.
166
- If no samples are specified, all counts are returned, starting
167
- with the largest.
168
-
169
- :param samples: the samples whose frequencies should be returned.
170
- :type samples: any
171
- :rtype: list(float)
172
- """
173
- cf = 0.0
174
- for sample in samples:
175
- cf += self[sample]
176
- yield cf
177
-
178
- # slightly odd nomenclature freq() if FreqDist does counts and ProbDist does probs,
179
- # here, freq() does probs
180
- def freq(self, sample):
181
- """
182
- Return the frequency of a given sample. The frequency of a
183
- sample is defined as the count of that sample divided by the
184
- total number of sample outcomes that have been recorded by
185
- this FreqDist. The count of a sample is defined as the
186
- number of times that sample outcome was recorded by this
187
- FreqDist. Frequencies are always real numbers in the range
188
- [0, 1].
189
-
190
- :param sample: the sample whose frequency
191
- should be returned.
192
- :type sample: any
193
- :rtype: float
194
- """
195
- if self.N() == 0:
196
- return 0
197
- return self[sample] / self.N()
198
-
199
- def max(self):
200
- """
201
- Return the sample with the greatest number of outcomes in this
202
- frequency distribution. If two or more samples have the same
203
- number of outcomes, return one of them; which sample is
204
- returned is undefined. If no outcomes have occurred in this
205
- frequency distribution, return None.
206
-
207
- :return: The sample with the maximum number of outcomes in this
208
- frequency distribution.
209
- :rtype: any or None
210
- """
211
- if len(self) == 0:
212
- raise ValueError('A FreqDist must have at least one sample before max is defined.')
213
- return self.most_common(1)[0][0]
214
-
215
- def plot(self, *args, **kwargs):
216
- """
217
- Plot samples from the frequency distribution
218
- displaying the most frequent sample first. If an integer
219
- parameter is supplied, stop after this many samples have been
220
- plotted. For a cumulative plot, specify cumulative=True.
221
- (Requires Matplotlib to be installed.)
222
-
223
- :param title: The title for the graph
224
- :type title: str
225
- :param cumulative: A flag to specify whether the plot is cumulative (default = False)
226
- :type title: bool
227
- """
228
- try:
229
- from matplotlib import pylab
230
- except ImportError:
231
- raise ValueError('The plot function requires matplotlib to be installed.'
232
- 'See http://matplotlib.org/')
233
-
234
- if len(args) == 0:
235
- args = [len(self)]
236
- samples = [item for item, _ in self.most_common(*args)]
237
-
238
- cumulative = _get_kwarg(kwargs, 'cumulative', False)
239
- if cumulative:
240
- freqs = list(self._cumulative_frequencies(samples))
241
- ylabel = "Cumulative Counts"
242
- else:
243
- freqs = [self[sample] for sample in samples]
244
- ylabel = "Counts"
245
- # percents = [f * 100 for f in freqs] only in ProbDist?
246
-
247
- pylab.grid(True, color="silver")
248
- if not "linewidth" in kwargs:
249
- kwargs["linewidth"] = 2
250
- if "title" in kwargs:
251
- pylab.title(kwargs["title"])
252
- del kwargs["title"]
253
- pylab.plot(freqs, **kwargs)
254
- pylab.xticks(range(len(samples)), [compat.text_type(s) for s in samples], rotation=90)
255
- pylab.xlabel("Samples")
256
- pylab.ylabel(ylabel)
257
- pylab.show()
258
-
259
- def tabulate(self, *args, **kwargs):
260
- """
261
- Tabulate the given samples from the frequency distribution (cumulative),
262
- displaying the most frequent sample first. If an integer
263
- parameter is supplied, stop after this many samples have been
264
- plotted.
265
-
266
- :param samples: The samples to plot (default is all samples)
267
- :type samples: list
268
- :param cumulative: A flag to specify whether the freqs are cumulative (default = False)
269
- :type title: bool
270
- """
271
- if len(args) == 0:
272
- args = [len(self)]
273
- samples = [item for item, _ in self.most_common(*args)]
274
-
275
- cumulative = _get_kwarg(kwargs, 'cumulative', False)
276
- if cumulative:
277
- freqs = list(self._cumulative_frequencies(samples))
278
- else:
279
- freqs = [self[sample] for sample in samples]
280
- # percents = [f * 100 for f in freqs] only in ProbDist?
281
-
282
- width = max(len("%s" % s) for s in samples)
283
- width = max(width, max(len("%d" % f) for f in freqs))
284
-
285
- for i in range(len(samples)):
286
- print("%*s" % (width, samples[i]), end=' ')
287
- print()
288
- for i in range(len(samples)):
289
- print("%*d" % (width, freqs[i]), end=' ')
290
- print()
291
-
292
- def copy(self):
293
- """
294
- Create a copy of this frequency distribution.
295
-
296
- :rtype: FreqDist
297
- """
298
- return self.__class__(self)
299
-
300
- # Mathematical operatiors
301
-
302
- def __add__(self, other):
303
- """
304
- Add counts from two counters.
305
-
306
- >>> FreqDist('abbb') + FreqDist('bcc')
307
- FreqDist({'b': 4, 'c': 2, 'a': 1})
308
-
309
- """
310
- return self.__class__(super(FreqDist, self).__add__(other))
311
-
312
- def __sub__(self, other):
313
- """
314
- Subtract count, but keep only results with positive counts.
315
-
316
- >>> FreqDist('abbbc') - FreqDist('bccd')
317
- FreqDist({'b': 2, 'a': 1})
318
-
319
- """
320
- return self.__class__(super(FreqDist, self).__sub__(other))
321
-
322
- def __or__(self, other):
323
- """
324
- Union is the maximum of value in either of the input counters.
325
-
326
- >>> FreqDist('abbb') | FreqDist('bcc')
327
- FreqDist({'b': 3, 'c': 2, 'a': 1})
328
-
329
- """
330
- return self.__class__(super(FreqDist, self).__or__(other))
331
-
332
- def __and__(self, other):
333
- """
334
- Intersection is the minimum of corresponding counts.
335
-
336
- >>> FreqDist('abbb') & FreqDist('bcc')
337
- FreqDist({'b': 1})
338
-
339
- """
340
- return self.__class__(super(FreqDist, self).__and__(other))
341
-
342
- def __le__(self, other):
343
- if not isinstance(other, FreqDist):
344
- raise_unorderable_types("<=", self, other)
345
- return set(self).issubset(other) and all(self[key] <= other[key] for key in self)
346
-
347
- # @total_ordering doesn't work here, since the class inherits from a builtin class
348
- __ge__ = lambda self, other: not self <= other or self == other
349
- __lt__ = lambda self, other: self <= other and not self == other
350
- __gt__ = lambda self, other: not self <= other
351
-
352
- def __repr__(self):
353
- """
354
- Return a string representation of this FreqDist.
355
-
356
- :rtype: string
357
- """
358
- return self.pformat()
359
-
360
- def pprint(self, maxlen=10, stream=None):
361
- """
362
- Print a string representation of this FreqDist to 'stream'
363
-
364
- :param maxlen: The maximum number of items to print
365
- :type maxlen: int
366
- :param stream: The stream to print to. stdout by default
367
- """
368
- print(self.pformat(maxlen=maxlen), file=stream)
369
-
370
- def pformat(self, maxlen=10):
371
- """
372
- Return a string representation of this FreqDist.
373
-
374
- :param maxlen: The maximum number of items to display
375
- :type maxlen: int
376
- :rtype: string
377
- """
378
- items = ['{0!r}: {1!r}'.format(*item) for item in self.most_common(maxlen)]
379
- if len(self) > maxlen:
380
- items.append('...')
381
- return 'FreqDist({{{0}}})'.format(', '.join(items))
382
-
383
- def __str__(self):
384
- """
385
- Return a string representation of this FreqDist.
386
-
387
- :rtype: string
388
- """
389
- return '<FreqDist with %d samples and %d outcomes>' % (len(self), self.N())
390
-
391
-
392
- ##//////////////////////////////////////////////////////
393
- ## Probability Distributions
394
- ##//////////////////////////////////////////////////////
395
-
396
- class ProbDistI(object):
397
- """
398
- A probability distribution for the outcomes of an experiment. A
399
- probability distribution specifies how likely it is that an
400
- experiment will have any given outcome. For example, a
401
- probability distribution could be used to predict the probability
402
- that a token in a document will have a given type. Formally, a
403
- probability distribution can be defined as a function mapping from
404
- samples to nonnegative real numbers, such that the sum of every
405
- number in the function's range is 1.0. A ``ProbDist`` is often
406
- used to model the probability distribution of the experiment used
407
- to generate a frequency distribution.
408
- """
409
- SUM_TO_ONE = True
410
- """True if the probabilities of the samples in this probability
411
- distribution will always sum to one."""
412
-
413
- def __init__(self):
414
- if self.__class__ == ProbDistI:
415
- raise NotImplementedError("Interfaces can't be instantiated")
416
-
417
- def prob(self, sample):
418
- """
419
- Return the probability for a given sample. Probabilities
420
- are always real numbers in the range [0, 1].
421
-
422
- :param sample: The sample whose probability
423
- should be returned.
424
- :type sample: any
425
- :rtype: float
426
- """
427
- raise NotImplementedError()
428
-
429
- def logprob(self, sample):
430
- """
431
- Return the base 2 logarithm of the probability for a given sample.
432
-
433
- :param sample: The sample whose probability
434
- should be returned.
435
- :type sample: any
436
- :rtype: float
437
- """
438
- # Default definition, in terms of prob()
439
- p = self.prob(sample)
440
- return (math.log(p, 2) if p != 0 else _NINF)
441
-
442
- def max(self):
443
- """
444
- Return the sample with the greatest probability. If two or
445
- more samples have the same probability, return one of them;
446
- which sample is returned is undefined.
447
-
448
- :rtype: any
449
- """
450
- raise NotImplementedError()
451
-
452
- def samples(self):
453
- """
454
- Return a list of all samples that have nonzero probabilities.
455
- Use ``prob`` to find the probability of each sample.
456
-
457
- :rtype: list
458
- """
459
- raise NotImplementedError()
460
-
461
- # cf self.SUM_TO_ONE
462
- def discount(self):
463
- """
464
- Return the ratio by which counts are discounted on average: c*/c
465
-
466
- :rtype: float
467
- """
468
- return 0.0
469
-
470
- # Subclasses should define more efficient implementations of this,
471
- # where possible.
472
- def generate(self):
473
- """
474
- Return a randomly selected sample from this probability distribution.
475
- The probability of returning each sample ``samp`` is equal to
476
- ``self.prob(samp)``.
477
- """
478
- p = random.random()
479
- p_init = p
480
- for sample in self.samples():
481
- p -= self.prob(sample)
482
- if p <= 0: return sample
483
- # allow for some rounding error:
484
- if p < .0001:
485
- return sample
486
- # we *should* never get here
487
- if self.SUM_TO_ONE:
488
- warnings.warn("Probability distribution %r sums to %r; generate()"
489
- " is returning an arbitrary sample." % (self, p_init-p))
490
- return random.choice(list(self.samples()))
491
-
492
-
493
- @compat.python_2_unicode_compatible
494
- class UniformProbDist(ProbDistI):
495
- """
496
- A probability distribution that assigns equal probability to each
497
- sample in a given set; and a zero probability to all other
498
- samples.
499
- """
500
- def __init__(self, samples):
501
- """
502
- Construct a new uniform probability distribution, that assigns
503
- equal probability to each sample in ``samples``.
504
-
505
- :param samples: The samples that should be given uniform
506
- probability.
507
- :type samples: list
508
- :raise ValueError: If ``samples`` is empty.
509
- """
510
- if len(samples) == 0:
511
- raise ValueError('A Uniform probability distribution must '+
512
- 'have at least one sample.')
513
- self._sampleset = set(samples)
514
- self._prob = 1.0/len(self._sampleset)
515
- self._samples = list(self._sampleset)
516
-
517
- def prob(self, sample):
518
- return (self._prob if sample in self._sampleset else 0)
519
-
520
- def max(self):
521
- return self._samples[0]
522
-
523
- def samples(self):
524
- return self._samples
525
-
526
- def __repr__(self):
527
- return '<UniformProbDist with %d samples>' % len(self._sampleset)
528
-
529
-
530
- @compat.python_2_unicode_compatible
531
- class RandomProbDist(ProbDistI):
532
- """
533
- Generates a random probability distribution whereby each sample
534
- will be between 0 and 1 with equal probability (uniform random distribution.
535
- Also called a continuous uniform distribution).
536
- """
537
- def __init__(self, samples):
538
- if len(samples) == 0:
539
- raise ValueError('A probability distribution must '+
540
- 'have at least one sample.')
541
- self._probs = self.unirand(samples)
542
- self._samples = list(self._probs.keys())
543
-
544
- @classmethod
545
- def unirand(cls, samples):
546
- """
547
- The key function that creates a randomized initial distribution
548
- that still sums to 1. Set as a dictionary of prob values so that
549
- it can still be passed to MutableProbDist and called with identical
550
- syntax to UniformProbDist
551
- """
552
- randrow = [random.random() for i in range(len(samples))]
553
- total = sum(randrow)
554
- for i, x in enumerate(randrow):
555
- randrow[i] = x/total
556
-
557
- total = sum(randrow)
558
- if total != 1:
559
- #this difference, if present, is so small (near NINF) that it
560
- #can be subtracted from any element without risking probs not (0 1)
561
- randrow[-1] -= total - 1
562
-
563
- return dict((s, randrow[i]) for i, s in enumerate(samples))
564
-
565
- def prob(self, sample):
566
- return self._probs.get(sample, 0)
567
-
568
- def samples(self):
569
- return self._samples
570
-
571
- def __repr__(self):
572
- return '<RandomUniformProbDist with %d samples>' %len(self._probs)
573
-
574
-
575
- @compat.python_2_unicode_compatible
576
- class DictionaryProbDist(ProbDistI):
577
- """
578
- A probability distribution whose probabilities are directly
579
- specified by a given dictionary. The given dictionary maps
580
- samples to probabilities.
581
- """
582
- def __init__(self, prob_dict=None, log=False, normalize=False):
583
- """
584
- Construct a new probability distribution from the given
585
- dictionary, which maps values to probabilities (or to log
586
- probabilities, if ``log`` is true). If ``normalize`` is
587
- true, then the probability values are scaled by a constant
588
- factor such that they sum to 1.
589
-
590
- If called without arguments, the resulting probability
591
- distribution assigns zero probability to all values.
592
- """
593
-
594
- self._prob_dict = (prob_dict.copy() if prob_dict is not None else {})
595
- self._log = log
596
-
597
- # Normalize the distribution, if requested.
598
- if normalize:
599
- if len(prob_dict) == 0:
600
- raise ValueError('A DictionaryProbDist must have at least one sample ' +
601
- 'before it can be normalized.')
602
- if log:
603
- value_sum = sum_logs(list(self._prob_dict.values()))
604
- if value_sum <= _NINF:
605
- logp = math.log(1.0/len(prob_dict), 2)
606
- for x in prob_dict:
607
- self._prob_dict[x] = logp
608
- else:
609
- for (x, p) in self._prob_dict.items():
610
- self._prob_dict[x] -= value_sum
611
- else:
612
- value_sum = sum(self._prob_dict.values())
613
- if value_sum == 0:
614
- p = 1.0/len(prob_dict)
615
- for x in prob_dict:
616
- self._prob_dict[x] = p
617
- else:
618
- norm_factor = 1.0/value_sum
619
- for (x, p) in self._prob_dict.items():
620
- self._prob_dict[x] *= norm_factor
621
-
622
- def prob(self, sample):
623
- if self._log:
624
- return (2**(self._prob_dict[sample]) if sample in self._prob_dict else 0)
625
- else:
626
- return self._prob_dict.get(sample, 0)
627
-
628
- def logprob(self, sample):
629
- if self._log:
630
- return self._prob_dict.get(sample, _NINF)
631
- else:
632
- if sample not in self._prob_dict: return _NINF
633
- elif self._prob_dict[sample] == 0: return _NINF
634
- else: return math.log(self._prob_dict[sample], 2)
635
-
636
- def max(self):
637
- if not hasattr(self, '_max'):
638
- self._max = max((p,v) for (v,p) in self._prob_dict.items())[1]
639
- return self._max
640
- def samples(self):
641
- return self._prob_dict.keys()
642
- def __repr__(self):
643
- return '<ProbDist with %d samples>' % len(self._prob_dict)
644
-
645
-
646
- @compat.python_2_unicode_compatible
647
- class MLEProbDist(ProbDistI):
648
- """
649
- The maximum likelihood estimate for the probability distribution
650
- of the experiment used to generate a frequency distribution. The
651
- "maximum likelihood estimate" approximates the probability of
652
- each sample as the frequency of that sample in the frequency
653
- distribution.
654
- """
655
- def __init__(self, freqdist, bins=None):
656
- """
657
- Use the maximum likelihood estimate to create a probability
658
- distribution for the experiment used to generate ``freqdist``.
659
-
660
- :type freqdist: FreqDist
661
- :param freqdist: The frequency distribution that the
662
- probability estimates should be based on.
663
- """
664
- self._freqdist = freqdist
665
-
666
- def freqdist(self):
667
- """
668
- Return the frequency distribution that this probability
669
- distribution is based on.
670
-
671
- :rtype: FreqDist
672
- """
673
- return self._freqdist
674
-
675
- def prob(self, sample):
676
- return self._freqdist.freq(sample)
677
-
678
- def max(self):
679
- return self._freqdist.max()
680
-
681
- def samples(self):
682
- return self._freqdist.keys()
683
-
684
- def __repr__(self):
685
- """
686
- :rtype: str
687
- :return: A string representation of this ``ProbDist``.
688
- """
689
- return '<MLEProbDist based on %d samples>' % self._freqdist.N()
690
-
691
-
692
- @compat.python_2_unicode_compatible
693
- class LidstoneProbDist(ProbDistI):
694
- """
695
- The Lidstone estimate for the probability distribution of the
696
- experiment used to generate a frequency distribution. The
697
- "Lidstone estimate" is parameterized by a real number *gamma*,
698
- which typically ranges from 0 to 1. The Lidstone estimate
699
- approximates the probability of a sample with count *c* from an
700
- experiment with *N* outcomes and *B* bins as
701
- ``c+gamma)/(N+B*gamma)``. This is equivalent to adding
702
- *gamma* to the count for each bin, and taking the maximum
703
- likelihood estimate of the resulting frequency distribution.
704
- """
705
- SUM_TO_ONE = False
706
- def __init__(self, freqdist, gamma, bins=None):
707
- """
708
- Use the Lidstone estimate to create a probability distribution
709
- for the experiment used to generate ``freqdist``.
710
-
711
- :type freqdist: FreqDist
712
- :param freqdist: The frequency distribution that the
713
- probability estimates should be based on.
714
- :type gamma: float
715
- :param gamma: A real number used to parameterize the
716
- estimate. The Lidstone estimate is equivalent to adding
717
- *gamma* to the count for each bin, and taking the
718
- maximum likelihood estimate of the resulting frequency
719
- distribution.
720
- :type bins: int
721
- :param bins: The number of sample values that can be generated
722
- by the experiment that is described by the probability
723
- distribution. This value must be correctly set for the
724
- probabilities of the sample values to sum to one. If
725
- ``bins`` is not specified, it defaults to ``freqdist.B()``.
726
- """
727
- if (bins == 0) or (bins is None and freqdist.N() == 0):
728
- name = self.__class__.__name__[:-8]
729
- raise ValueError('A %s probability distribution ' % name +
730
- 'must have at least one bin.')
731
- if (bins is not None) and (bins < freqdist.B()):
732
- name = self.__class__.__name__[:-8]
733
- raise ValueError('\nThe number of bins in a %s distribution ' % name +
734
- '(%d) must be greater than or equal to\n' % bins +
735
- 'the number of bins in the FreqDist used ' +
736
- 'to create it (%d).' % freqdist.B())
737
-
738
- self._freqdist = freqdist
739
- self._gamma = float(gamma)
740
- self._N = self._freqdist.N()
741
-
742
- if bins is None:
743
- bins = freqdist.B()
744
- self._bins = bins
745
-
746
- self._divisor = self._N + bins * gamma
747
- if self._divisor == 0.0:
748
- # In extreme cases we force the probability to be 0,
749
- # which it will be, since the count will be 0:
750
- self._gamma = 0
751
- self._divisor = 1
752
-
753
- def freqdist(self):
754
- """
755
- Return the frequency distribution that this probability
756
- distribution is based on.
757
-
758
- :rtype: FreqDist
759
- """
760
- return self._freqdist
761
-
762
- def prob(self, sample):
763
- c = self._freqdist[sample]
764
- return (c + self._gamma) / self._divisor
765
-
766
- def max(self):
767
- # For Lidstone distributions, probability is monotonic with
768
- # frequency, so the most probable sample is the one that
769
- # occurs most frequently.
770
- return self._freqdist.max()
771
-
772
- def samples(self):
773
- return self._freqdist.keys()
774
-
775
- def discount(self):
776
- gb = self._gamma * self._bins
777
- return gb / (self._N + gb)
778
-
779
- def __repr__(self):
780
- """
781
- Return a string representation of this ``ProbDist``.
782
-
783
- :rtype: str
784
- """
785
- return '<LidstoneProbDist based on %d samples>' % self._freqdist.N()
786
-
787
-
788
- @compat.python_2_unicode_compatible
789
- class LaplaceProbDist(LidstoneProbDist):
790
- """
791
- The Laplace estimate for the probability distribution of the
792
- experiment used to generate a frequency distribution. The
793
- "Laplace estimate" approximates the probability of a sample with
794
- count *c* from an experiment with *N* outcomes and *B* bins as
795
- *(c+1)/(N+B)*. This is equivalent to adding one to the count for
796
- each bin, and taking the maximum likelihood estimate of the
797
- resulting frequency distribution.
798
- """
799
- def __init__(self, freqdist, bins=None):
800
- """
801
- Use the Laplace estimate to create a probability distribution
802
- for the experiment used to generate ``freqdist``.
803
-
804
- :type freqdist: FreqDist
805
- :param freqdist: The frequency distribution that the
806
- probability estimates should be based on.
807
- :type bins: int
808
- :param bins: The number of sample values that can be generated
809
- by the experiment that is described by the probability
810
- distribution. This value must be correctly set for the
811
- probabilities of the sample values to sum to one. If
812
- ``bins`` is not specified, it defaults to ``freqdist.B()``.
813
- """
814
- LidstoneProbDist.__init__(self, freqdist, 1, bins)
815
-
816
- def __repr__(self):
817
- """
818
- :rtype: str
819
- :return: A string representation of this ``ProbDist``.
820
- """
821
- return '<LaplaceProbDist based on %d samples>' % self._freqdist.N()
822
-
823
-
824
- @compat.python_2_unicode_compatible
825
- class ELEProbDist(LidstoneProbDist):
826
- """
827
- The expected likelihood estimate for the probability distribution
828
- of the experiment used to generate a frequency distribution. The
829
- "expected likelihood estimate" approximates the probability of a
830
- sample with count *c* from an experiment with *N* outcomes and
831
- *B* bins as *(c+0.5)/(N+B/2)*. This is equivalent to adding 0.5
832
- to the count for each bin, and taking the maximum likelihood
833
- estimate of the resulting frequency distribution.
834
- """
835
- def __init__(self, freqdist, bins=None):
836
- """
837
- Use the expected likelihood estimate to create a probability
838
- distribution for the experiment used to generate ``freqdist``.
839
-
840
- :type freqdist: FreqDist
841
- :param freqdist: The frequency distribution that the
842
- probability estimates should be based on.
843
- :type bins: int
844
- :param bins: The number of sample values that can be generated
845
- by the experiment that is described by the probability
846
- distribution. This value must be correctly set for the
847
- probabilities of the sample values to sum to one. If
848
- ``bins`` is not specified, it defaults to ``freqdist.B()``.
849
- """
850
- LidstoneProbDist.__init__(self, freqdist, 0.5, bins)
851
-
852
- def __repr__(self):
853
- """
854
- Return a string representation of this ``ProbDist``.
855
-
856
- :rtype: str
857
- """
858
- return '<ELEProbDist based on %d samples>' % self._freqdist.N()
859
-
860
-
861
- @compat.python_2_unicode_compatible
862
- class HeldoutProbDist(ProbDistI):
863
- """
864
- The heldout estimate for the probability distribution of the
865
- experiment used to generate two frequency distributions. These
866
- two frequency distributions are called the "heldout frequency
867
- distribution" and the "base frequency distribution." The
868
- "heldout estimate" uses uses the "heldout frequency
869
- distribution" to predict the probability of each sample, given its
870
- frequency in the "base frequency distribution".
871
-
872
- In particular, the heldout estimate approximates the probability
873
- for a sample that occurs *r* times in the base distribution as
874
- the average frequency in the heldout distribution of all samples
875
- that occur *r* times in the base distribution.
876
-
877
- This average frequency is *Tr[r]/(Nr[r].N)*, where:
878
-
879
- - *Tr[r]* is the total count in the heldout distribution for
880
- all samples that occur *r* times in the base distribution.
881
- - *Nr[r]* is the number of samples that occur *r* times in
882
- the base distribution.
883
- - *N* is the number of outcomes recorded by the heldout
884
- frequency distribution.
885
-
886
- In order to increase the efficiency of the ``prob`` member
887
- function, *Tr[r]/(Nr[r].N)* is precomputed for each value of *r*
888
- when the ``HeldoutProbDist`` is created.
889
-
890
- :type _estimate: list(float)
891
- :ivar _estimate: A list mapping from *r*, the number of
892
- times that a sample occurs in the base distribution, to the
893
- probability estimate for that sample. ``_estimate[r]`` is
894
- calculated by finding the average frequency in the heldout
895
- distribution of all samples that occur *r* times in the base
896
- distribution. In particular, ``_estimate[r]`` =
897
- *Tr[r]/(Nr[r].N)*.
898
- :type _max_r: int
899
- :ivar _max_r: The maximum number of times that any sample occurs
900
- in the base distribution. ``_max_r`` is used to decide how
901
- large ``_estimate`` must be.
902
- """
903
- SUM_TO_ONE = False
904
- def __init__(self, base_fdist, heldout_fdist, bins=None):
905
- """
906
- Use the heldout estimate to create a probability distribution
907
- for the experiment used to generate ``base_fdist`` and
908
- ``heldout_fdist``.
909
-
910
- :type base_fdist: FreqDist
911
- :param base_fdist: The base frequency distribution.
912
- :type heldout_fdist: FreqDist
913
- :param heldout_fdist: The heldout frequency distribution.
914
- :type bins: int
915
- :param bins: The number of sample values that can be generated
916
- by the experiment that is described by the probability
917
- distribution. This value must be correctly set for the
918
- probabilities of the sample values to sum to one. If
919
- ``bins`` is not specified, it defaults to ``freqdist.B()``.
920
- """
921
-
922
- self._base_fdist = base_fdist
923
- self._heldout_fdist = heldout_fdist
924
-
925
- # The max number of times any sample occurs in base_fdist.
926
- self._max_r = base_fdist[base_fdist.max()]
927
-
928
- # Calculate Tr, Nr, and N.
929
- Tr = self._calculate_Tr()
930
- r_Nr = base_fdist.r_Nr(bins)
931
- Nr = [r_Nr[r] for r in range(self._max_r+1)]
932
- N = heldout_fdist.N()
933
-
934
- # Use Tr, Nr, and N to compute the probability estimate for
935
- # each value of r.
936
- self._estimate = self._calculate_estimate(Tr, Nr, N)
937
-
938
- def _calculate_Tr(self):
939
- """
940
- Return the list *Tr*, where *Tr[r]* is the total count in
941
- ``heldout_fdist`` for all samples that occur *r*
942
- times in ``base_fdist``.
943
-
944
- :rtype: list(float)
945
- """
946
- Tr = [0.0] * (self._max_r+1)
947
- for sample in self._heldout_fdist:
948
- r = self._base_fdist[sample]
949
- Tr[r] += self._heldout_fdist[sample]
950
- return Tr
951
-
952
- def _calculate_estimate(self, Tr, Nr, N):
953
- """
954
- Return the list *estimate*, where *estimate[r]* is the probability
955
- estimate for any sample that occurs *r* times in the base frequency
956
- distribution. In particular, *estimate[r]* is *Tr[r]/(N[r].N)*.
957
- In the special case that *N[r]=0*, *estimate[r]* will never be used;
958
- so we define *estimate[r]=None* for those cases.
959
-
960
- :rtype: list(float)
961
- :type Tr: list(float)
962
- :param Tr: the list *Tr*, where *Tr[r]* is the total count in
963
- the heldout distribution for all samples that occur *r*
964
- times in base distribution.
965
- :type Nr: list(float)
966
- :param Nr: The list *Nr*, where *Nr[r]* is the number of
967
- samples that occur *r* times in the base distribution.
968
- :type N: int
969
- :param N: The total number of outcomes recorded by the heldout
970
- frequency distribution.
971
- """
972
- estimate = []
973
- for r in range(self._max_r+1):
974
- if Nr[r] == 0: estimate.append(None)
975
- else: estimate.append(Tr[r]/(Nr[r]*N))
976
- return estimate
977
-
978
- def base_fdist(self):
979
- """
980
- Return the base frequency distribution that this probability
981
- distribution is based on.
982
-
983
- :rtype: FreqDist
984
- """
985
- return self._base_fdist
986
-
987
- def heldout_fdist(self):
988
- """
989
- Return the heldout frequency distribution that this
990
- probability distribution is based on.
991
-
992
- :rtype: FreqDist
993
- """
994
- return self._heldout_fdist
995
-
996
- def samples(self):
997
- return self._base_fdist.keys()
998
-
999
- def prob(self, sample):
1000
- # Use our precomputed probability estimate.
1001
- r = self._base_fdist[sample]
1002
- return self._estimate[r]
1003
-
1004
- def max(self):
1005
- # Note: the Heldout estimation is *not* necessarily monotonic;
1006
- # so this implementation is currently broken. However, it
1007
- # should give the right answer *most* of the time. :)
1008
- return self._base_fdist.max()
1009
-
1010
- def discount(self):
1011
- raise NotImplementedError()
1012
-
1013
- def __repr__(self):
1014
- """
1015
- :rtype: str
1016
- :return: A string representation of this ``ProbDist``.
1017
- """
1018
- s = '<HeldoutProbDist: %d base samples; %d heldout samples>'
1019
- return s % (self._base_fdist.N(), self._heldout_fdist.N())
1020
-
1021
-
1022
- @compat.python_2_unicode_compatible
1023
- class CrossValidationProbDist(ProbDistI):
1024
- """
1025
- The cross-validation estimate for the probability distribution of
1026
- the experiment used to generate a set of frequency distribution.
1027
- The "cross-validation estimate" for the probability of a sample
1028
- is found by averaging the held-out estimates for the sample in
1029
- each pair of frequency distributions.
1030
- """
1031
- SUM_TO_ONE = False
1032
- def __init__(self, freqdists, bins):
1033
- """
1034
- Use the cross-validation estimate to create a probability
1035
- distribution for the experiment used to generate
1036
- ``freqdists``.
1037
-
1038
- :type freqdists: list(FreqDist)
1039
- :param freqdists: A list of the frequency distributions
1040
- generated by the experiment.
1041
- :type bins: int
1042
- :param bins: The number of sample values that can be generated
1043
- by the experiment that is described by the probability
1044
- distribution. This value must be correctly set for the
1045
- probabilities of the sample values to sum to one. If
1046
- ``bins`` is not specified, it defaults to ``freqdist.B()``.
1047
- """
1048
- self._freqdists = freqdists
1049
-
1050
- # Create a heldout probability distribution for each pair of
1051
- # frequency distributions in freqdists.
1052
- self._heldout_probdists = []
1053
- for fdist1 in freqdists:
1054
- for fdist2 in freqdists:
1055
- if fdist1 is not fdist2:
1056
- probdist = HeldoutProbDist(fdist1, fdist2, bins)
1057
- self._heldout_probdists.append(probdist)
1058
-
1059
- def freqdists(self):
1060
- """
1061
- Return the list of frequency distributions that this ``ProbDist`` is based on.
1062
-
1063
- :rtype: list(FreqDist)
1064
- """
1065
- return self._freqdists
1066
-
1067
- def samples(self):
1068
- # [xx] nb: this is not too efficient
1069
- return set(sum([list(fd) for fd in self._freqdists], []))
1070
-
1071
- def prob(self, sample):
1072
- # Find the average probability estimate returned by each
1073
- # heldout distribution.
1074
- prob = 0.0
1075
- for heldout_probdist in self._heldout_probdists:
1076
- prob += heldout_probdist.prob(sample)
1077
- return prob/len(self._heldout_probdists)
1078
-
1079
- def discount(self):
1080
- raise NotImplementedError()
1081
-
1082
- def __repr__(self):
1083
- """
1084
- Return a string representation of this ``ProbDist``.
1085
-
1086
- :rtype: str
1087
- """
1088
- return '<CrossValidationProbDist: %d-way>' % len(self._freqdists)
1089
-
1090
-
1091
- @compat.python_2_unicode_compatible
1092
- class WittenBellProbDist(ProbDistI):
1093
- """
1094
- The Witten-Bell estimate of a probability distribution. This distribution
1095
- allocates uniform probability mass to as yet unseen events by using the
1096
- number of events that have only been seen once. The probability mass
1097
- reserved for unseen events is equal to *T / (N + T)*
1098
- where *T* is the number of observed event types and *N* is the total
1099
- number of observed events. This equates to the maximum likelihood estimate
1100
- of a new type event occurring. The remaining probability mass is discounted
1101
- such that all probability estimates sum to one, yielding:
1102
-
1103
- - *p = T / Z (N + T)*, if count = 0
1104
- - *p = c / (N + T)*, otherwise
1105
- """
1106
-
1107
- def __init__(self, freqdist, bins=None):
1108
- """
1109
- Creates a distribution of Witten-Bell probability estimates. This
1110
- distribution allocates uniform probability mass to as yet unseen
1111
- events by using the number of events that have only been seen once. The
1112
- probability mass reserved for unseen events is equal to *T / (N + T)*
1113
- where *T* is the number of observed event types and *N* is the total
1114
- number of observed events. This equates to the maximum likelihood
1115
- estimate of a new type event occurring. The remaining probability mass
1116
- is discounted such that all probability estimates sum to one,
1117
- yielding:
1118
-
1119
- - *p = T / Z (N + T)*, if count = 0
1120
- - *p = c / (N + T)*, otherwise
1121
-
1122
- The parameters *T* and *N* are taken from the ``freqdist`` parameter
1123
- (the ``B()`` and ``N()`` values). The normalizing factor *Z* is
1124
- calculated using these values along with the ``bins`` parameter.
1125
-
1126
- :param freqdist: The frequency counts upon which to base the
1127
- estimation.
1128
- :type freqdist: FreqDist
1129
- :param bins: The number of possible event types. This must be at least
1130
- as large as the number of bins in the ``freqdist``. If None, then
1131
- it's assumed to be equal to that of the ``freqdist``
1132
- :type bins: int
1133
- """
1134
- assert bins is None or bins >= freqdist.B(),\
1135
- 'bins parameter must not be less than %d=freqdist.B()' % freqdist.B()
1136
- if bins is None:
1137
- bins = freqdist.B()
1138
- self._freqdist = freqdist
1139
- self._T = self._freqdist.B()
1140
- self._Z = bins - self._freqdist.B()
1141
- self._N = self._freqdist.N()
1142
- # self._P0 is P(0), precalculated for efficiency:
1143
- if self._N==0:
1144
- # if freqdist is empty, we approximate P(0) by a UniformProbDist:
1145
- self._P0 = 1.0 / self._Z
1146
- else:
1147
- self._P0 = self._T / (self._Z * (self._N + self._T))
1148
-
1149
- def prob(self, sample):
1150
- # inherit docs from ProbDistI
1151
- c = self._freqdist[sample]
1152
- return (c / (self._N + self._T) if c != 0 else self._P0)
1153
-
1154
- def max(self):
1155
- return self._freqdist.max()
1156
-
1157
- def samples(self):
1158
- return self._freqdist.keys()
1159
-
1160
- def freqdist(self):
1161
- return self._freqdist
1162
-
1163
- def discount(self):
1164
- raise NotImplementedError()
1165
-
1166
- def __repr__(self):
1167
- """
1168
- Return a string representation of this ``ProbDist``.
1169
-
1170
- :rtype: str
1171
- """
1172
- return '<WittenBellProbDist based on %d samples>' % self._freqdist.N()
1173
-
1174
-
1175
- ##//////////////////////////////////////////////////////
1176
- ## Good-Turing Probability Distributions
1177
- ##//////////////////////////////////////////////////////
1178
-
1179
- # Good-Turing frequency estimation was contributed by Alan Turing and
1180
- # his statistical assistant I.J. Good, during their collaboration in
1181
- # the WWII. It is a statistical technique for predicting the
1182
- # probability of occurrence of objects belonging to an unknown number
1183
- # of species, given past observations of such objects and their
1184
- # species. (In drawing balls from an urn, the 'objects' would be balls
1185
- # and the 'species' would be the distinct colors of the balls (finite
1186
- # but unknown in number).
1187
- #
1188
- # Good-Turing method calculates the probability mass to assign to
1189
- # events with zero or low counts based on the number of events with
1190
- # higher counts. It does so by using the adjusted count *c\**:
1191
- #
1192
- # - *c\* = (c + 1) N(c + 1) / N(c)* for c >= 1
1193
- # - *things with frequency zero in training* = N(1) for c == 0
1194
- #
1195
- # where *c* is the original count, *N(i)* is the number of event types
1196
- # observed with count *i*. We can think the count of unseen as the count
1197
- # of frequency one (see Jurafsky & Martin 2nd Edition, p101).
1198
- #
1199
- # This method is problematic because the situation ``N(c+1) == 0``
1200
- # is quite common in the original Good-Turing estimation; smoothing or
1201
- # interpolation of *N(i)* values is essential in practice.
1202
- #
1203
- # Bill Gale and Geoffrey Sampson present a simple and effective approach,
1204
- # Simple Good-Turing. As a smoothing curve they simply use a power curve:
1205
- #
1206
- # Nr = a*r^b (with b < -1 to give the appropriate hyperbolic
1207
- # relationship)
1208
- #
1209
- # They estimate a and b by simple linear regression technique on the
1210
- # logarithmic form of the equation:
1211
- #
1212
- # log Nr = a + b*log(r)
1213
- #
1214
- # However, they suggest that such a simple curve is probably only
1215
- # appropriate for high values of r. For low values of r, they use the
1216
- # measured Nr directly. (see M&S, p.213)
1217
- #
1218
- # Gale and Sampson propose to use r while the difference between r and
1219
- # r* is 1.96 greater than the standard deviation, and switch to r* if
1220
- # it is less or equal:
1221
- #
1222
- # |r - r*| > 1.96 * sqrt((r + 1)^2 (Nr+1 / Nr^2) (1 + Nr+1 / Nr))
1223
- #
1224
- # The 1.96 coefficient correspond to a 0.05 significance criterion,
1225
- # some implementations can use a coefficient of 1.65 for a 0.1
1226
- # significance criterion.
1227
- #
1228
-
1229
- ##//////////////////////////////////////////////////////
1230
- ## Simple Good-Turing Probablity Distributions
1231
- ##//////////////////////////////////////////////////////
1232
-
1233
- @compat.python_2_unicode_compatible
1234
- class SimpleGoodTuringProbDist(ProbDistI):
1235
- """
1236
- SimpleGoodTuring ProbDist approximates from frequency to frequency of
1237
- frequency into a linear line under log space by linear regression.
1238
- Details of Simple Good-Turing algorithm can be found in:
1239
-
1240
- - Good Turing smoothing without tears" (Gale & Sampson 1995),
1241
- Journal of Quantitative Linguistics, vol. 2 pp. 217-237.
1242
- - "Speech and Language Processing (Jurafsky & Martin),
1243
- 2nd Edition, Chapter 4.5 p103 (log(Nc) = a + b*log(c))
1244
- - http://www.grsampson.net/RGoodTur.html
1245
-
1246
- Given a set of pair (xi, yi), where the xi denotes the frequency and
1247
- yi denotes the frequency of frequency, we want to minimize their
1248
- square variation. E(x) and E(y) represent the mean of xi and yi.
1249
-
1250
- - slope: b = sigma ((xi-E(x)(yi-E(y))) / sigma ((xi-E(x))(xi-E(x)))
1251
- - intercept: a = E(y) - b.E(x)
1252
- """
1253
- SUM_TO_ONE = False
1254
- def __init__(self, freqdist, bins=None):
1255
- """
1256
- :param freqdist: The frequency counts upon which to base the
1257
- estimation.
1258
- :type freqdist: FreqDist
1259
- :param bins: The number of possible event types. This must be
1260
- larger than the number of bins in the ``freqdist``. If None,
1261
- then it's assumed to be equal to ``freqdist``.B() + 1
1262
- :type bins: int
1263
- """
1264
- assert bins is None or bins > freqdist.B(),\
1265
- 'bins parameter must not be less than %d=freqdist.B()+1' % (freqdist.B()+1)
1266
- if bins is None:
1267
- bins = freqdist.B() + 1
1268
- self._freqdist = freqdist
1269
- self._bins = bins
1270
- r, nr = self._r_Nr()
1271
- self.find_best_fit(r, nr)
1272
- self._switch(r, nr)
1273
- self._renormalize(r, nr)
1274
-
1275
- def _r_Nr_non_zero(self):
1276
- r_Nr = self._freqdist.r_Nr()
1277
- del r_Nr[0]
1278
- return r_Nr
1279
-
1280
- def _r_Nr(self):
1281
- """
1282
- Split the frequency distribution in two list (r, Nr), where Nr(r) > 0
1283
- """
1284
- nonzero = self._r_Nr_non_zero()
1285
-
1286
- if not nonzero:
1287
- return [], []
1288
- return zip(*sorted(nonzero.items()))
1289
-
1290
- def find_best_fit(self, r, nr):
1291
- """
1292
- Use simple linear regression to tune parameters self._slope and
1293
- self._intercept in the log-log space based on count and Nr(count)
1294
- (Work in log space to avoid floating point underflow.)
1295
- """
1296
- # For higher sample frequencies the data points becomes horizontal
1297
- # along line Nr=1. To create a more evident linear model in log-log
1298
- # space, we average positive Nr values with the surrounding zero
1299
- # values. (Church and Gale, 1991)
1300
-
1301
- if not r or not nr:
1302
- # Empty r or nr?
1303
- return
1304
-
1305
- zr = []
1306
- for j in range(len(r)):
1307
- i = (r[j-1] if j > 0 else 0)
1308
- k = (2 * r[j] - i if j == len(r) - 1 else r[j+1])
1309
- zr_ = 2.0 * nr[j] / (k - i)
1310
- zr.append(zr_)
1311
-
1312
- log_r = [math.log(i) for i in r]
1313
- log_zr = [math.log(i) for i in zr]
1314
-
1315
- xy_cov = x_var = 0.0
1316
- x_mean = sum(log_r) / len(log_r)
1317
- y_mean = sum(log_zr) / len(log_zr)
1318
- for (x, y) in zip(log_r, log_zr):
1319
- xy_cov += (x - x_mean) * (y - y_mean)
1320
- x_var += (x - x_mean)**2
1321
- self._slope = (xy_cov / x_var if x_var != 0 else 0.0)
1322
- if self._slope >= -1:
1323
- warnings.warn('SimpleGoodTuring did not find a proper best fit '
1324
- 'line for smoothing probabilities of occurrences. '
1325
- 'The probability estimates are likely to be '
1326
- 'unreliable.')
1327
- self._intercept = y_mean - self._slope * x_mean
1328
-
1329
- def _switch(self, r, nr):
1330
- """
1331
- Calculate the r frontier where we must switch from Nr to Sr
1332
- when estimating E[Nr].
1333
- """
1334
- for i, r_ in enumerate(r):
1335
- if len(r) == i + 1 or r[i+1] != r_ + 1:
1336
- # We are at the end of r, or there is a gap in r
1337
- self._switch_at = r_
1338
- break
1339
-
1340
- Sr = self.smoothedNr
1341
- smooth_r_star = (r_ + 1) * Sr(r_+1) / Sr(r_)
1342
- unsmooth_r_star = (r_ + 1) * nr[i+1] / nr[i]
1343
-
1344
- std = math.sqrt(self._variance(r_, nr[i], nr[i+1]))
1345
- if abs(unsmooth_r_star-smooth_r_star) <= 1.96 * std:
1346
- self._switch_at = r_
1347
- break
1348
-
1349
- def _variance(self, r, nr, nr_1):
1350
- r = float(r)
1351
- nr = float(nr)
1352
- nr_1 = float(nr_1)
1353
- return (r + 1.0)**2 * (nr_1 / nr**2) * (1.0 + nr_1 / nr)
1354
-
1355
- def _renormalize(self, r, nr):
1356
- """
1357
- It is necessary to renormalize all the probability estimates to
1358
- ensure a proper probability distribution results. This can be done
1359
- by keeping the estimate of the probability mass for unseen items as
1360
- N(1)/N and renormalizing all the estimates for previously seen items
1361
- (as Gale and Sampson (1995) propose). (See M&S P.213, 1999)
1362
- """
1363
- prob_cov = 0.0
1364
- for r_, nr_ in zip(r, nr):
1365
- prob_cov += nr_ * self._prob_measure(r_)
1366
- if prob_cov:
1367
- self._renormal = (1 - self._prob_measure(0)) / prob_cov
1368
-
1369
- def smoothedNr(self, r):
1370
- """
1371
- Return the number of samples with count r.
1372
-
1373
- :param r: The amount of frequency.
1374
- :type r: int
1375
- :rtype: float
1376
- """
1377
-
1378
- # Nr = a*r^b (with b < -1 to give the appropriate hyperbolic
1379
- # relationship)
1380
- # Estimate a and b by simple linear regression technique on
1381
- # the logarithmic form of the equation: log Nr = a + b*log(r)
1382
-
1383
- return math.exp(self._intercept + self._slope * math.log(r))
1384
-
1385
- def prob(self, sample):
1386
- """
1387
- Return the sample's probability.
1388
-
1389
- :param sample: sample of the event
1390
- :type sample: str
1391
- :rtype: float
1392
- """
1393
- count = self._freqdist[sample]
1394
- p = self._prob_measure(count)
1395
- if count == 0:
1396
- if self._bins == self._freqdist.B():
1397
- p = 0.0
1398
- else:
1399
- p = p / (self._bins - self._freqdist.B())
1400
- else:
1401
- p = p * self._renormal
1402
- return p
1403
-
1404
- def _prob_measure(self, count):
1405
- if count == 0 and self._freqdist.N() == 0 :
1406
- return 1.0
1407
- elif count == 0 and self._freqdist.N() != 0:
1408
- return self._freqdist.Nr(1) / self._freqdist.N()
1409
-
1410
- if self._switch_at > count:
1411
- Er_1 = self._freqdist.Nr(count+1)
1412
- Er = self._freqdist.Nr(count)
1413
- else:
1414
- Er_1 = self.smoothedNr(count+1)
1415
- Er = self.smoothedNr(count)
1416
-
1417
- r_star = (count + 1) * Er_1 / Er
1418
- return r_star / self._freqdist.N()
1419
-
1420
- def check(self):
1421
- prob_sum = 0.0
1422
- for i in range(0, len(self._Nr)):
1423
- prob_sum += self._Nr[i] * self._prob_measure(i) / self._renormal
1424
- print("Probability Sum:", prob_sum)
1425
- #assert prob_sum != 1.0, "probability sum should be one!"
1426
-
1427
- def discount(self):
1428
- """
1429
- This function returns the total mass of probability transfers from the
1430
- seen samples to the unseen samples.
1431
- """
1432
- return self.smoothedNr(1) / self._freqdist.N()
1433
-
1434
- def max(self):
1435
- return self._freqdist.max()
1436
-
1437
- def samples(self):
1438
- return self._freqdist.keys()
1439
-
1440
- def freqdist(self):
1441
- return self._freqdist
1442
-
1443
- def __repr__(self):
1444
- """
1445
- Return a string representation of this ``ProbDist``.
1446
-
1447
- :rtype: str
1448
- """
1449
- return '<SimpleGoodTuringProbDist based on %d samples>'\
1450
- % self._freqdist.N()
1451
-
1452
-
1453
- class MutableProbDist(ProbDistI):
1454
- """
1455
- An mutable probdist where the probabilities may be easily modified. This
1456
- simply copies an existing probdist, storing the probability values in a
1457
- mutable dictionary and providing an update method.
1458
- """
1459
-
1460
- def __init__(self, prob_dist, samples, store_logs=True):
1461
- """
1462
- Creates the mutable probdist based on the given prob_dist and using
1463
- the list of samples given. These values are stored as log
1464
- probabilities if the store_logs flag is set.
1465
-
1466
- :param prob_dist: the distribution from which to garner the
1467
- probabilities
1468
- :type prob_dist: ProbDist
1469
- :param samples: the complete set of samples
1470
- :type samples: sequence of any
1471
- :param store_logs: whether to store the probabilities as logarithms
1472
- :type store_logs: bool
1473
- """
1474
- self._samples = samples
1475
- self._sample_dict = dict((samples[i], i) for i in range(len(samples)))
1476
- self._data = array.array(str("d"), [0.0]) * len(samples)
1477
- for i in range(len(samples)):
1478
- if store_logs:
1479
- self._data[i] = prob_dist.logprob(samples[i])
1480
- else:
1481
- self._data[i] = prob_dist.prob(samples[i])
1482
- self._logs = store_logs
1483
-
1484
- def samples(self):
1485
- # inherit documentation
1486
- return self._samples
1487
-
1488
- def prob(self, sample):
1489
- # inherit documentation
1490
- i = self._sample_dict.get(sample)
1491
- if i is None:
1492
- return 0.0
1493
- return (2**(self._data[i]) if self._logs else self._data[i])
1494
-
1495
- def logprob(self, sample):
1496
- # inherit documentation
1497
- i = self._sample_dict.get(sample)
1498
- if i is None:
1499
- return float('-inf')
1500
- return (self._data[i] if self._logs else math.log(self._data[i], 2))
1501
-
1502
- def update(self, sample, prob, log=True):
1503
- """
1504
- Update the probability for the given sample. This may cause the object
1505
- to stop being the valid probability distribution - the user must
1506
- ensure that they update the sample probabilities such that all samples
1507
- have probabilities between 0 and 1 and that all probabilities sum to
1508
- one.
1509
-
1510
- :param sample: the sample for which to update the probability
1511
- :type sample: any
1512
- :param prob: the new probability
1513
- :type prob: float
1514
- :param log: is the probability already logged
1515
- :type log: bool
1516
- """
1517
- i = self._sample_dict.get(sample)
1518
- assert i is not None
1519
- if self._logs:
1520
- self._data[i] = (prob if log else math.log(prob, 2))
1521
- else:
1522
- self._data[i] = (2**(prob) if log else prob)
1523
-
1524
- ##/////////////////////////////////////////////////////
1525
- ## Kneser-Ney Probability Distribution
1526
- ##//////////////////////////////////////////////////////
1527
-
1528
- # This method for calculating probabilities was introduced in 1995 by Reinhard
1529
- # Kneser and Hermann Ney. It was meant to improve the accuracy of language
1530
- # models that use backing-off to deal with sparse data. The authors propose two
1531
- # ways of doing so: a marginal distribution constraint on the back-off
1532
- # distribution and a leave-one-out distribution. For a start, the first one is
1533
- # implemented as a class below.
1534
- #
1535
- # The idea behind a back-off n-gram model is that we have a series of
1536
- # frequency distributions for our n-grams so that in case we have not seen a
1537
- # given n-gram during training (and as a result have a 0 probability for it) we
1538
- # can 'back off' (hence the name!) and try testing whether we've seen the
1539
- # n-1-gram part of the n-gram in training.
1540
- #
1541
- # The novelty of Kneser and Ney's approach was that they decided to fiddle
1542
- # around with the way this latter, backed off probability was being calculated
1543
- # whereas their peers seemed to focus on the primary probability.
1544
- #
1545
- # The implementation below uses one of the techniques described in their paper
1546
- # titled "Improved backing-off for n-gram language modeling." In the same paper
1547
- # another technique is introduced to attempt to smooth the back-off
1548
- # distribution as well as the primary one. There is also a much-cited
1549
- # modification of this method proposed by Chen and Goodman.
1550
- #
1551
- # In order for the implementation of Kneser-Ney to be more efficient, some
1552
- # changes have been made to the original algorithm. Namely, the calculation of
1553
- # the normalizing function gamma has been significantly simplified and
1554
- # combined slightly differently with beta. None of these changes affect the
1555
- # nature of the algorithm, but instead aim to cut out unnecessary calculations
1556
- # and take advantage of storing and retrieving information in dictionaries
1557
- # where possible.
1558
-
1559
- @compat.python_2_unicode_compatible
1560
- class KneserNeyProbDist(ProbDistI):
1561
- """
1562
- Kneser-Ney estimate of a probability distribution. This is a version of
1563
- back-off that counts how likely an n-gram is provided the n-1-gram had
1564
- been seen in training. Extends the ProbDistI interface, requires a trigram
1565
- FreqDist instance to train on. Optionally, a different from default discount
1566
- value can be specified. The default discount is set to 0.75.
1567
-
1568
- """
1569
- def __init__(self, freqdist, bins=None, discount=0.75):
1570
- """
1571
- :param freqdist: The trigram frequency distribution upon which to base
1572
- the estimation
1573
- :type freqdist: FreqDist
1574
- :param bins: Included for compatibility with nltk.tag.hmm
1575
- :type bins: int or float
1576
- :param discount: The discount applied when retrieving counts of
1577
- trigrams
1578
- :type discount: float (preferred, but can be set to int)
1579
- """
1580
-
1581
- if not bins:
1582
- self._bins = freqdist.B()
1583
- else:
1584
- self._bins = bins
1585
- self._D = discount
1586
-
1587
- # cache for probability calculation
1588
- self._cache = {}
1589
-
1590
- # internal bigram and trigram frequency distributions
1591
- self._bigrams = defaultdict(int)
1592
- self._trigrams = freqdist
1593
-
1594
- # helper dictionaries used to calculate probabilities
1595
- self._wordtypes_after = defaultdict(float)
1596
- self._trigrams_contain = defaultdict(float)
1597
- self._wordtypes_before = defaultdict(float)
1598
- for w0, w1, w2 in freqdist:
1599
- self._bigrams[(w0,w1)] += freqdist[(w0, w1, w2)]
1600
- self._wordtypes_after[(w0,w1)] += 1
1601
- self._trigrams_contain[w1] += 1
1602
- self._wordtypes_before[(w1,w2)] += 1
1603
-
1604
- def prob(self, trigram):
1605
- # sample must be a triple
1606
- if len(trigram) != 3:
1607
- raise ValueError('Expected an iterable with 3 members.')
1608
- trigram = tuple(trigram)
1609
- w0, w1, w2 = trigram
1610
-
1611
- if trigram in self._cache:
1612
- return self._cache[trigram]
1613
- else:
1614
- # if the sample trigram was seen during training
1615
- if trigram in self._trigrams:
1616
- prob = (self._trigrams[trigram]
1617
- - self.discount())/self._bigrams[(w0, w1)]
1618
-
1619
- # else if the 'rougher' environment was seen during training
1620
- elif (w0,w1) in self._bigrams and (w1,w2) in self._wordtypes_before:
1621
- aftr = self._wordtypes_after[(w0, w1)]
1622
- bfr = self._wordtypes_before[(w1, w2)]
1623
-
1624
- # the probability left over from alphas
1625
- leftover_prob = ((aftr * self.discount())
1626
- / self._bigrams[(w0, w1)])
1627
-
1628
- # the beta (including normalization)
1629
- beta = bfr /(self._trigrams_contain[w1] - aftr)
1630
-
1631
- prob = leftover_prob * beta
1632
-
1633
- # else the sample was completely unseen during training
1634
- else:
1635
- prob = 0.0
1636
-
1637
- self._cache[trigram] = prob
1638
- return prob
1639
-
1640
- def discount(self):
1641
- """
1642
- Return the value by which counts are discounted. By default set to 0.75.
1643
-
1644
- :rtype: float
1645
- """
1646
- return self._D
1647
-
1648
- def set_discount(self, discount):
1649
- """
1650
- Set the value by which counts are discounted to the value of discount.
1651
-
1652
- :param discount: the new value to discount counts by
1653
- :type discount: float (preferred, but int possible)
1654
- :rtype: None
1655
- """
1656
- self._D = discount
1657
-
1658
- def samples(self):
1659
- return self._trigrams.keys()
1660
-
1661
- def max(self):
1662
- return self._trigrams.max()
1663
-
1664
- def __repr__(self):
1665
- '''
1666
- Return a string representation of this ProbDist
1667
-
1668
- :rtype: str
1669
- '''
1670
- return '<KneserNeyProbDist based on {0} trigrams'.format(self._trigrams.N())
1671
-
1672
- ##//////////////////////////////////////////////////////
1673
- ## Probability Distribution Operations
1674
- ##//////////////////////////////////////////////////////
1675
-
1676
- def log_likelihood(test_pdist, actual_pdist):
1677
- if (not isinstance(test_pdist, ProbDistI) or
1678
- not isinstance(actual_pdist, ProbDistI)):
1679
- raise ValueError('expected a ProbDist.')
1680
- # Is this right?
1681
- return sum(actual_pdist.prob(s) * math.log(test_pdist.prob(s), 2)
1682
- for s in actual_pdist)
1683
-
1684
- def entropy(pdist):
1685
- probs = (pdist.prob(s) for s in pdist.samples())
1686
- return -sum(p * math.log(p,2) for p in probs)
1687
-
1688
- ##//////////////////////////////////////////////////////
1689
- ## Conditional Distributions
1690
- ##//////////////////////////////////////////////////////
1691
-
1692
- @compat.python_2_unicode_compatible
1693
- class ConditionalFreqDist(defaultdict):
1694
- """
1695
- A collection of frequency distributions for a single experiment
1696
- run under different conditions. Conditional frequency
1697
- distributions are used to record the number of times each sample
1698
- occurred, given the condition under which the experiment was run.
1699
- For example, a conditional frequency distribution could be used to
1700
- record the frequency of each word (type) in a document, given its
1701
- length. Formally, a conditional frequency distribution can be
1702
- defined as a function that maps from each condition to the
1703
- FreqDist for the experiment under that condition.
1704
-
1705
- Conditional frequency distributions are typically constructed by
1706
- repeatedly running an experiment under a variety of conditions,
1707
- and incrementing the sample outcome counts for the appropriate
1708
- conditions. For example, the following code will produce a
1709
- conditional frequency distribution that encodes how often each
1710
- word type occurs, given the length of that word type:
1711
-
1712
- >>> from nltk.probability import ConditionalFreqDist
1713
- >>> from nltk.tokenize import word_tokenize
1714
- >>> sent = "the the the dog dog some other words that we do not care about"
1715
- >>> cfdist = ConditionalFreqDist()
1716
- >>> for word in word_tokenize(sent):
1717
- ... condition = len(word)
1718
- ... cfdist[condition][word] += 1
1719
-
1720
- An equivalent way to do this is with the initializer:
1721
-
1722
- >>> cfdist = ConditionalFreqDist((len(word), word) for word in word_tokenize(sent))
1723
-
1724
- The frequency distribution for each condition is accessed using
1725
- the indexing operator:
1726
-
1727
- >>> cfdist[3]
1728
- FreqDist({'the': 3, 'dog': 2, 'not': 1})
1729
- >>> cfdist[3].freq('the')
1730
- 0.5
1731
- >>> cfdist[3]['dog']
1732
- 2
1733
-
1734
- When the indexing operator is used to access the frequency
1735
- distribution for a condition that has not been accessed before,
1736
- ``ConditionalFreqDist`` creates a new empty FreqDist for that
1737
- condition.
1738
-
1739
- """
1740
- def __init__(self, cond_samples=None):
1741
- """
1742
- Construct a new empty conditional frequency distribution. In
1743
- particular, the count for every sample, under every condition,
1744
- is zero.
1745
-
1746
- :param cond_samples: The samples to initialize the conditional
1747
- frequency distribution with
1748
- :type cond_samples: Sequence of (condition, sample) tuples
1749
- """
1750
- defaultdict.__init__(self, FreqDist)
1751
- if cond_samples:
1752
- for (cond, sample) in cond_samples:
1753
- self[cond][sample] += 1
1754
-
1755
- def __reduce__(self):
1756
- kv_pairs = ((cond, self[cond]) for cond in self.conditions())
1757
- return (self.__class__, (), None, None, kv_pairs)
1758
-
1759
- def conditions(self):
1760
- """
1761
- Return a list of the conditions that have been accessed for
1762
- this ``ConditionalFreqDist``. Use the indexing operator to
1763
- access the frequency distribution for a given condition.
1764
- Note that the frequency distributions for some conditions
1765
- may contain zero sample outcomes.
1766
-
1767
- :rtype: list
1768
- """
1769
- return list(self.keys())
1770
-
1771
- def N(self):
1772
- """
1773
- Return the total number of sample outcomes that have been
1774
- recorded by this ``ConditionalFreqDist``.
1775
-
1776
- :rtype: int
1777
- """
1778
- return sum(fdist.N() for fdist in compat.itervalues(self))
1779
-
1780
- def plot(self, *args, **kwargs):
1781
- """
1782
- Plot the given samples from the conditional frequency distribution.
1783
- For a cumulative plot, specify cumulative=True.
1784
- (Requires Matplotlib to be installed.)
1785
-
1786
- :param samples: The samples to plot
1787
- :type samples: list
1788
- :param title: The title for the graph
1789
- :type title: str
1790
- :param conditions: The conditions to plot (default is all)
1791
- :type conditions: list
1792
- """
1793
- try:
1794
- from matplotlib import pylab
1795
- except ImportError:
1796
- raise ValueError('The plot function requires matplotlib to be installed.'
1797
- 'See http://matplotlib.org/')
1798
-
1799
- cumulative = _get_kwarg(kwargs, 'cumulative', False)
1800
- conditions = _get_kwarg(kwargs, 'conditions', sorted(self.conditions()))
1801
- title = _get_kwarg(kwargs, 'title', '')
1802
- samples = _get_kwarg(kwargs, 'samples',
1803
- sorted(set(v for c in conditions for v in self[c]))) # this computation could be wasted
1804
- if not "linewidth" in kwargs:
1805
- kwargs["linewidth"] = 2
1806
-
1807
- for condition in conditions:
1808
- if cumulative:
1809
- freqs = list(self[condition]._cumulative_frequencies(samples))
1810
- ylabel = "Cumulative Counts"
1811
- legend_loc = 'lower right'
1812
- else:
1813
- freqs = [self[condition][sample] for sample in samples]
1814
- ylabel = "Counts"
1815
- legend_loc = 'upper right'
1816
- # percents = [f * 100 for f in freqs] only in ConditionalProbDist?
1817
- kwargs['label'] = "%s" % condition
1818
- pylab.plot(freqs, *args, **kwargs)
1819
-
1820
- pylab.legend(loc=legend_loc)
1821
- pylab.grid(True, color="silver")
1822
- pylab.xticks(range(len(samples)), [compat.text_type(s) for s in samples], rotation=90)
1823
- if title:
1824
- pylab.title(title)
1825
- pylab.xlabel("Samples")
1826
- pylab.ylabel(ylabel)
1827
- pylab.show()
1828
-
1829
- def tabulate(self, *args, **kwargs):
1830
- """
1831
- Tabulate the given samples from the conditional frequency distribution.
1832
-
1833
- :param samples: The samples to plot
1834
- :type samples: list
1835
- :param conditions: The conditions to plot (default is all)
1836
- :type conditions: list
1837
- :param cumulative: A flag to specify whether the freqs are cumulative (default = False)
1838
- :type title: bool
1839
- """
1840
-
1841
- cumulative = _get_kwarg(kwargs, 'cumulative', False)
1842
- conditions = _get_kwarg(kwargs, 'conditions', sorted(self.conditions()))
1843
- samples = _get_kwarg(kwargs, 'samples',
1844
- sorted(set(v for c in conditions for v in self[c]))) # this computation could be wasted
1845
-
1846
- width = max(len("%s" % s) for s in samples)
1847
- freqs = dict()
1848
- for c in conditions:
1849
- if cumulative:
1850
- freqs[c] = list(self[c]._cumulative_frequencies(samples))
1851
- else:
1852
- freqs[c] = [self[c][sample] for sample in samples]
1853
- width = max(width, max(len("%d" % f) for f in freqs[c]))
1854
-
1855
- condition_size = max(len("%s" % c) for c in conditions)
1856
- print(' ' * condition_size, end=' ')
1857
- for s in samples:
1858
- print("%*s" % (width, s), end=' ')
1859
- print()
1860
- for c in conditions:
1861
- print("%*s" % (condition_size, c), end=' ')
1862
- for f in freqs[c]:
1863
- print("%*d" % (width, f), end=' ')
1864
- print()
1865
-
1866
- # Mathematical operators
1867
-
1868
- def __add__(self, other):
1869
- """
1870
- Add counts from two ConditionalFreqDists.
1871
- """
1872
- if not isinstance(other, ConditionalFreqDist):
1873
- return NotImplemented
1874
- result = ConditionalFreqDist()
1875
- for cond in self.conditions():
1876
- newfreqdist = self[cond] + other[cond]
1877
- if newfreqdist:
1878
- result[cond] = newfreqdist
1879
- for cond in other.conditions():
1880
- if cond not in self.conditions():
1881
- for elem, count in other[cond].items():
1882
- if count > 0:
1883
- result[cond][elem] = count
1884
- return result
1885
-
1886
- def __sub__(self, other):
1887
- """
1888
- Subtract count, but keep only results with positive counts.
1889
- """
1890
- if not isinstance(other, ConditionalFreqDist):
1891
- return NotImplemented
1892
- result = ConditionalFreqDist()
1893
- for cond in self.conditions():
1894
- newfreqdist = self[cond] - other[cond]
1895
- if newfreqdist:
1896
- result[cond] = newfreqdist
1897
- for cond in other.conditions():
1898
- if cond not in self.conditions():
1899
- for elem, count in other[cond].items():
1900
- if count < 0:
1901
- result[cond][elem] = 0 - count
1902
- return result
1903
-
1904
- def __or__(self, other):
1905
- """
1906
- Union is the maximum of value in either of the input counters.
1907
- """
1908
- if not isinstance(other, ConditionalFreqDist):
1909
- return NotImplemented
1910
- result = ConditionalFreqDist()
1911
- for cond in self.conditions():
1912
- newfreqdist = self[cond] | other[cond]
1913
- if newfreqdist:
1914
- result[cond] = newfreqdist
1915
- for cond in other.conditions():
1916
- if cond not in self.conditions():
1917
- for elem, count in other[cond].items():
1918
- if count > 0:
1919
- result[cond][elem] = count
1920
- return result
1921
-
1922
- def __and__(self, other):
1923
- """
1924
- Intersection is the minimum of corresponding counts.
1925
- """
1926
- if not isinstance(other, ConditionalFreqDist):
1927
- return NotImplemented
1928
- result = ConditionalFreqDist()
1929
- for cond in self.conditions():
1930
- newfreqdist = self[cond] & other[cond]
1931
- if newfreqdist:
1932
- result[cond] = newfreqdist
1933
- return result
1934
-
1935
- # @total_ordering doesn't work here, since the class inherits from a builtin class
1936
- def __le__(self, other):
1937
- if not isinstance(other, ConditionalFreqDist):
1938
- raise_unorderable_types("<=", self, other)
1939
- return set(self.conditions()).issubset(other.conditions()) \
1940
- and all(self[c] <= other[c] for c in self.conditions())
1941
- def __lt__(self, other):
1942
- if not isinstance(other, ConditionalFreqDist):
1943
- raise_unorderable_types("<", self, other)
1944
- return self <= other and self != other
1945
- def __ge__(self, other):
1946
- if not isinstance(other, ConditionalFreqDist):
1947
- raise_unorderable_types(">=", self, other)
1948
- return other <= self
1949
- def __gt__(self, other):
1950
- if not isinstance(other, ConditionalFreqDist):
1951
- raise_unorderable_types(">", self, other)
1952
- return other < self
1953
-
1954
- def __repr__(self):
1955
- """
1956
- Return a string representation of this ``ConditionalFreqDist``.
1957
-
1958
- :rtype: str
1959
- """
1960
- return '<ConditionalFreqDist with %d conditions>' % len(self)
1961
-
1962
-
1963
- @compat.python_2_unicode_compatible
1964
- class ConditionalProbDistI(dict):
1965
- """
1966
- A collection of probability distributions for a single experiment
1967
- run under different conditions. Conditional probability
1968
- distributions are used to estimate the likelihood of each sample,
1969
- given the condition under which the experiment was run. For
1970
- example, a conditional probability distribution could be used to
1971
- estimate the probability of each word type in a document, given
1972
- the length of the word type. Formally, a conditional probability
1973
- distribution can be defined as a function that maps from each
1974
- condition to the ``ProbDist`` for the experiment under that
1975
- condition.
1976
- """
1977
- def __init__(self):
1978
- raise NotImplementedError("Interfaces can't be instantiated")
1979
-
1980
- def conditions(self):
1981
- """
1982
- Return a list of the conditions that are represented by
1983
- this ``ConditionalProbDist``. Use the indexing operator to
1984
- access the probability distribution for a given condition.
1985
-
1986
- :rtype: list
1987
- """
1988
- return list(self.keys())
1989
-
1990
- def __repr__(self):
1991
- """
1992
- Return a string representation of this ``ConditionalProbDist``.
1993
-
1994
- :rtype: str
1995
- """
1996
- return '<%s with %d conditions>' % (type(self).__name__, len(self))
1997
-
1998
-
1999
- class ConditionalProbDist(ConditionalProbDistI):
2000
- """
2001
- A conditional probability distribution modeling the experiments
2002
- that were used to generate a conditional frequency distribution.
2003
- A ConditionalProbDist is constructed from a
2004
- ``ConditionalFreqDist`` and a ``ProbDist`` factory:
2005
-
2006
- - The ``ConditionalFreqDist`` specifies the frequency
2007
- distribution for each condition.
2008
- - The ``ProbDist`` factory is a function that takes a
2009
- condition's frequency distribution, and returns its
2010
- probability distribution. A ``ProbDist`` class's name (such as
2011
- ``MLEProbDist`` or ``HeldoutProbDist``) can be used to specify
2012
- that class's constructor.
2013
-
2014
- The first argument to the ``ProbDist`` factory is the frequency
2015
- distribution that it should model; and the remaining arguments are
2016
- specified by the ``factory_args`` parameter to the
2017
- ``ConditionalProbDist`` constructor. For example, the following
2018
- code constructs a ``ConditionalProbDist``, where the probability
2019
- distribution for each condition is an ``ELEProbDist`` with 10 bins:
2020
-
2021
- >>> from nltk.corpus import brown
2022
- >>> from nltk.probability import ConditionalFreqDist
2023
- >>> from nltk.probability import ConditionalProbDist, ELEProbDist
2024
- >>> cfdist = ConditionalFreqDist(brown.tagged_words()[:5000])
2025
- >>> cpdist = ConditionalProbDist(cfdist, ELEProbDist, 10)
2026
- >>> cpdist['passed'].max()
2027
- 'VBD'
2028
- >>> cpdist['passed'].prob('VBD')
2029
- 0.423...
2030
-
2031
- """
2032
- def __init__(self, cfdist, probdist_factory,
2033
- *factory_args, **factory_kw_args):
2034
- """
2035
- Construct a new conditional probability distribution, based on
2036
- the given conditional frequency distribution and ``ProbDist``
2037
- factory.
2038
-
2039
- :type cfdist: ConditionalFreqDist
2040
- :param cfdist: The ``ConditionalFreqDist`` specifying the
2041
- frequency distribution for each condition.
2042
- :type probdist_factory: class or function
2043
- :param probdist_factory: The function or class that maps
2044
- a condition's frequency distribution to its probability
2045
- distribution. The function is called with the frequency
2046
- distribution as its first argument,
2047
- ``factory_args`` as its remaining arguments, and
2048
- ``factory_kw_args`` as keyword arguments.
2049
- :type factory_args: (any)
2050
- :param factory_args: Extra arguments for ``probdist_factory``.
2051
- These arguments are usually used to specify extra
2052
- properties for the probability distributions of individual
2053
- conditions, such as the number of bins they contain.
2054
- :type factory_kw_args: (any)
2055
- :param factory_kw_args: Extra keyword arguments for ``probdist_factory``.
2056
- """
2057
- self._probdist_factory = probdist_factory
2058
- self._factory_args = factory_args
2059
- self._factory_kw_args = factory_kw_args
2060
-
2061
- for condition in cfdist:
2062
- self[condition] = probdist_factory(cfdist[condition],
2063
- *factory_args, **factory_kw_args)
2064
-
2065
- def __missing__(self, key):
2066
- self[key] = self._probdist_factory(FreqDist(),
2067
- *self._factory_args,
2068
- **self._factory_kw_args)
2069
- return self[key]
2070
-
2071
- class DictionaryConditionalProbDist(ConditionalProbDistI):
2072
- """
2073
- An alternative ConditionalProbDist that simply wraps a dictionary of
2074
- ProbDists rather than creating these from FreqDists.
2075
- """
2076
-
2077
- def __init__(self, probdist_dict):
2078
- """
2079
- :param probdist_dict: a dictionary containing the probdists indexed
2080
- by the conditions
2081
- :type probdist_dict: dict any -> probdist
2082
- """
2083
- self.update(probdist_dict)
2084
-
2085
- def __missing__(self, key):
2086
- self[key] = DictionaryProbDist()
2087
- return self[key]
2088
-
2089
- ##//////////////////////////////////////////////////////
2090
- ## Adding in log-space.
2091
- ##//////////////////////////////////////////////////////
2092
-
2093
- # If the difference is bigger than this, then just take the bigger one:
2094
- _ADD_LOGS_MAX_DIFF = math.log(1e-30, 2)
2095
-
2096
- def add_logs(logx, logy):
2097
- """
2098
- Given two numbers ``logx`` = *log(x)* and ``logy`` = *log(y)*, return
2099
- *log(x+y)*. Conceptually, this is the same as returning
2100
- ``log(2**(logx)+2**(logy))``, but the actual implementation
2101
- avoids overflow errors that could result from direct computation.
2102
- """
2103
- if (logx < logy + _ADD_LOGS_MAX_DIFF):
2104
- return logy
2105
- if (logy < logx + _ADD_LOGS_MAX_DIFF):
2106
- return logx
2107
- base = min(logx, logy)
2108
- return base + math.log(2**(logx-base) + 2**(logy-base), 2)
2109
-
2110
- def sum_logs(logs):
2111
- return (reduce(add_logs, logs[1:], logs[0]) if len(logs) != 0 else _NINF)
2112
-
2113
- ##//////////////////////////////////////////////////////
2114
- ## Probabilistic Mix-in
2115
- ##//////////////////////////////////////////////////////
2116
-
2117
- class ProbabilisticMixIn(object):
2118
- """
2119
- A mix-in class to associate probabilities with other classes
2120
- (trees, rules, etc.). To use the ``ProbabilisticMixIn`` class,
2121
- define a new class that derives from an existing class and from
2122
- ProbabilisticMixIn. You will need to define a new constructor for
2123
- the new class, which explicitly calls the constructors of both its
2124
- parent classes. For example:
2125
-
2126
- >>> from nltk.probability import ProbabilisticMixIn
2127
- >>> class A:
2128
- ... def __init__(self, x, y): self.data = (x,y)
2129
- ...
2130
- >>> class ProbabilisticA(A, ProbabilisticMixIn):
2131
- ... def __init__(self, x, y, **prob_kwarg):
2132
- ... A.__init__(self, x, y)
2133
- ... ProbabilisticMixIn.__init__(self, **prob_kwarg)
2134
-
2135
- See the documentation for the ProbabilisticMixIn
2136
- ``constructor<__init__>`` for information about the arguments it
2137
- expects.
2138
-
2139
- You should generally also redefine the string representation
2140
- methods, the comparison methods, and the hashing method.
2141
- """
2142
- def __init__(self, **kwargs):
2143
- """
2144
- Initialize this object's probability. This initializer should
2145
- be called by subclass constructors. ``prob`` should generally be
2146
- the first argument for those constructors.
2147
-
2148
- :param prob: The probability associated with the object.
2149
- :type prob: float
2150
- :param logprob: The log of the probability associated with
2151
- the object.
2152
- :type logprob: float
2153
- """
2154
- if 'prob' in kwargs:
2155
- if 'logprob' in kwargs:
2156
- raise TypeError('Must specify either prob or logprob '
2157
- '(not both)')
2158
- else:
2159
- ProbabilisticMixIn.set_prob(self, kwargs['prob'])
2160
- elif 'logprob' in kwargs:
2161
- ProbabilisticMixIn.set_logprob(self, kwargs['logprob'])
2162
- else:
2163
- self.__prob = self.__logprob = None
2164
-
2165
- def set_prob(self, prob):
2166
- """
2167
- Set the probability associated with this object to ``prob``.
2168
-
2169
- :param prob: The new probability
2170
- :type prob: float
2171
- """
2172
- self.__prob = prob
2173
- self.__logprob = None
2174
-
2175
- def set_logprob(self, logprob):
2176
- """
2177
- Set the log probability associated with this object to
2178
- ``logprob``. I.e., set the probability associated with this
2179
- object to ``2**(logprob)``.
2180
-
2181
- :param logprob: The new log probability
2182
- :type logprob: float
2183
- """
2184
- self.__logprob = logprob
2185
- self.__prob = None
2186
-
2187
- def prob(self):
2188
- """
2189
- Return the probability associated with this object.
2190
-
2191
- :rtype: float
2192
- """
2193
- if self.__prob is None:
2194
- if self.__logprob is None: return None
2195
- self.__prob = 2**(self.__logprob)
2196
- return self.__prob
2197
-
2198
- def logprob(self):
2199
- """
2200
- Return ``log(p)``, where ``p`` is the probability associated
2201
- with this object.
2202
-
2203
- :rtype: float
2204
- """
2205
- if self.__logprob is None:
2206
- if self.__prob is None: return None
2207
- self.__logprob = math.log(self.__prob, 2)
2208
- return self.__logprob
2209
-
2210
- class ImmutableProbabilisticMixIn(ProbabilisticMixIn):
2211
- def set_prob(self, prob):
2212
- raise ValueError('%s is immutable' % self.__class__.__name__)
2213
- def set_logprob(self, prob):
2214
- raise ValueError('%s is immutable' % self.__class__.__name__)
2215
-
2216
- ## Helper function for processing keyword arguments
2217
-
2218
- def _get_kwarg(kwargs, key, default):
2219
- if key in kwargs:
2220
- arg = kwargs[key]
2221
- del kwargs[key]
2222
- else:
2223
- arg = default
2224
- return arg
2225
-
2226
- ##//////////////////////////////////////////////////////
2227
- ## Demonstration
2228
- ##//////////////////////////////////////////////////////
2229
-
2230
- def _create_rand_fdist(numsamples, numoutcomes):
2231
- """
2232
- Create a new frequency distribution, with random samples. The
2233
- samples are numbers from 1 to ``numsamples``, and are generated by
2234
- summing two numbers, each of which has a uniform distribution.
2235
- """
2236
- import random
2237
- fdist = FreqDist()
2238
- for x in range(numoutcomes):
2239
- y = (random.randint(1, (1 + numsamples) // 2) +
2240
- random.randint(0, numsamples // 2))
2241
- fdist[y] += 1
2242
- return fdist
2243
-
2244
- def _create_sum_pdist(numsamples):
2245
- """
2246
- Return the true probability distribution for the experiment
2247
- ``_create_rand_fdist(numsamples, x)``.
2248
- """
2249
- fdist = FreqDist()
2250
- for x in range(1, (1 + numsamples) // 2 + 1):
2251
- for y in range(0, numsamples // 2 + 1):
2252
- fdist[x+y] += 1
2253
- return MLEProbDist(fdist)
2254
-
2255
- def demo(numsamples=6, numoutcomes=500):
2256
- """
2257
- A demonstration of frequency distributions and probability
2258
- distributions. This demonstration creates three frequency
2259
- distributions with, and uses them to sample a random process with
2260
- ``numsamples`` samples. Each frequency distribution is sampled
2261
- ``numoutcomes`` times. These three frequency distributions are
2262
- then used to build six probability distributions. Finally, the
2263
- probability estimates of these distributions are compared to the
2264
- actual probability of each sample.
2265
-
2266
- :type numsamples: int
2267
- :param numsamples: The number of samples to use in each demo
2268
- frequency distributions.
2269
- :type numoutcomes: int
2270
- :param numoutcomes: The total number of outcomes for each
2271
- demo frequency distribution. These outcomes are divided into
2272
- ``numsamples`` bins.
2273
- :rtype: None
2274
- """
2275
-
2276
- # Randomly sample a stochastic process three times.
2277
- fdist1 = _create_rand_fdist(numsamples, numoutcomes)
2278
- fdist2 = _create_rand_fdist(numsamples, numoutcomes)
2279
- fdist3 = _create_rand_fdist(numsamples, numoutcomes)
2280
-
2281
- # Use our samples to create probability distributions.
2282
- pdists = [
2283
- MLEProbDist(fdist1),
2284
- LidstoneProbDist(fdist1, 0.5, numsamples),
2285
- HeldoutProbDist(fdist1, fdist2, numsamples),
2286
- HeldoutProbDist(fdist2, fdist1, numsamples),
2287
- CrossValidationProbDist([fdist1, fdist2, fdist3], numsamples),
2288
- SimpleGoodTuringProbDist(fdist1),
2289
- SimpleGoodTuringProbDist(fdist1, 7),
2290
- _create_sum_pdist(numsamples),
2291
- ]
2292
-
2293
- # Find the probability of each sample.
2294
- vals = []
2295
- for n in range(1,numsamples+1):
2296
- vals.append(tuple([n, fdist1.freq(n)] +
2297
- [pdist.prob(n) for pdist in pdists]))
2298
-
2299
- # Print the results in a formatted table.
2300
- print(('%d samples (1-%d); %d outcomes were sampled for each FreqDist' %
2301
- (numsamples, numsamples, numoutcomes)))
2302
- print('='*9*(len(pdists)+2))
2303
- FORMATSTR = ' FreqDist '+ '%8s '*(len(pdists)-1) + '| Actual'
2304
- print(FORMATSTR % tuple(repr(pdist)[1:9] for pdist in pdists[:-1]))
2305
- print('-'*9*(len(pdists)+2))
2306
- FORMATSTR = '%3d %8.6f ' + '%8.6f '*(len(pdists)-1) + '| %8.6f'
2307
- for val in vals:
2308
- print(FORMATSTR % val)
2309
-
2310
- # Print the totals for each column (should all be 1.0)
2311
- zvals = list(zip(*vals))
2312
- sums = [sum(val) for val in zvals[1:]]
2313
- print('-'*9*(len(pdists)+2))
2314
- FORMATSTR = 'Total ' + '%8.6f '*(len(pdists)) + '| %8.6f'
2315
- print(FORMATSTR % tuple(sums))
2316
- print('='*9*(len(pdists)+2))
2317
-
2318
- # Display the distributions themselves, if they're short enough.
2319
- if len("%s" % fdist1) < 70:
2320
- print(' fdist1: %s' % fdist1)
2321
- print(' fdist2: %s' % fdist2)
2322
- print(' fdist3: %s' % fdist3)
2323
- print()
2324
-
2325
- print('Generating:')
2326
- for pdist in pdists:
2327
- fdist = FreqDist(pdist.generate() for i in range(5000))
2328
- print('%20s %s' % (pdist.__class__.__name__[:20], ("%s" % fdist)[:55]))
2329
- print()
2330
-
2331
- def gt_demo():
2332
- from nltk import corpus
2333
- emma_words = corpus.gutenberg.words('austen-emma.txt')
2334
- fd = FreqDist(emma_words)
2335
- sgt = SimpleGoodTuringProbDist(fd)
2336
- print('%18s %8s %14s' \
2337
- % ("word", "freqency", "SimpleGoodTuring"))
2338
- fd_keys_sorted=(key for key, value in sorted(fd.items(), key=lambda item: item[1], reverse=True))
2339
- for key in fd_keys_sorted:
2340
- print('%18s %8d %14e' \
2341
- % (key, fd[key], sgt.prob(key)))
2342
-
2343
- if __name__ == '__main__':
2344
- demo(6, 10)
2345
- demo(5, 5000)
2346
- gt_demo()
2347
-
2348
- __all__ = ['ConditionalFreqDist', 'ConditionalProbDist',
2349
- 'ConditionalProbDistI', 'CrossValidationProbDist',
2350
- 'DictionaryConditionalProbDist', 'DictionaryProbDist', 'ELEProbDist',
2351
- 'FreqDist', 'SimpleGoodTuringProbDist', 'HeldoutProbDist',
2352
- 'ImmutableProbabilisticMixIn', 'LaplaceProbDist', 'LidstoneProbDist',
2353
- 'MLEProbDist', 'MutableProbDist', 'KneserNeyProbDist', 'ProbDistI', 'ProbabilisticMixIn',
2354
- 'UniformProbDist', 'WittenBellProbDist', 'add_logs',
2355
- 'log_likelihood', 'sum_logs', 'entropy']
1
+ # -*- coding: utf-8 -*-
2
+ # Natural Language Toolkit: Probability and Statistics
3
+ #
4
+ # Copyright (C) 2001-2018 NLTK Project
5
+ # Author: Edward Loper <edloper@gmail.com>
6
+ # Steven Bird <stevenbird1@gmail.com> (additions)
7
+ # Trevor Cohn <tacohn@cs.mu.oz.au> (additions)
8
+ # Peter Ljunglöf <peter.ljunglof@heatherleaf.se> (additions)
9
+ # Liang Dong <ldong@clemson.edu> (additions)
10
+ # Geoffrey Sampson <sampson@cantab.net> (additions)
11
+ # Ilia Kurenkov <ilia.kurenkov@gmail.com> (additions)
12
+ #
13
+ # URL: <http://nltk.org/>
14
+ # For license information, see LICENSE.TXT
15
+
16
+ """
17
+ Classes for representing and processing probabilistic information.
18
+
19
+ The ``FreqDist`` class is used to encode "frequency distributions",
20
+ which count the number of times that each outcome of an experiment
21
+ occurs.
22
+
23
+ The ``ProbDistI`` class defines a standard interface for "probability
24
+ distributions", which encode the probability of each outcome for an
25
+ experiment. There are two types of probability distribution:
26
+
27
+ - "derived probability distributions" are created from frequency
28
+ distributions. They attempt to model the probability distribution
29
+ that generated the frequency distribution.
30
+ - "analytic probability distributions" are created directly from
31
+ parameters (such as variance).
32
+
33
+ The ``ConditionalFreqDist`` class and ``ConditionalProbDistI`` interface
34
+ are used to encode conditional distributions. Conditional probability
35
+ distributions can be derived or analytic; but currently the only
36
+ implementation of the ``ConditionalProbDistI`` interface is
37
+ ``ConditionalProbDist``, a derived distribution.
38
+
39
+ """
40
+ from __future__ import print_function, unicode_literals, division
41
+
42
+ import math
43
+ import random
44
+ import warnings
45
+ import array
46
+ from operator import itemgetter
47
+ from collections import defaultdict, Counter
48
+ from functools import reduce
49
+ from abc import ABCMeta, abstractmethod
50
+
51
+ from six import itervalues, text_type, add_metaclass
52
+
53
+ from nltk import compat
54
+ from nltk.internals import raise_unorderable_types
55
+
56
+ _NINF = float('-1e300')
57
+
58
+ ##//////////////////////////////////////////////////////
59
+ ## Frequency Distributions
60
+ ##//////////////////////////////////////////////////////
61
+
62
+ @compat.python_2_unicode_compatible
63
+ class FreqDist(Counter):
64
+ """
65
+ A frequency distribution for the outcomes of an experiment. A
66
+ frequency distribution records the number of times each outcome of
67
+ an experiment has occurred. For example, a frequency distribution
68
+ could be used to record the frequency of each word type in a
69
+ document. Formally, a frequency distribution can be defined as a
70
+ function mapping from each sample to the number of times that
71
+ sample occurred as an outcome.
72
+
73
+ Frequency distributions are generally constructed by running a
74
+ number of experiments, and incrementing the count for a sample
75
+ every time it is an outcome of an experiment. For example, the
76
+ following code will produce a frequency distribution that encodes
77
+ how often each word occurs in a text:
78
+
79
+ >>> from nltk.tokenize import word_tokenize
80
+ >>> from nltk.probability import FreqDist
81
+ >>> sent = 'This is an example sentence'
82
+ >>> fdist = FreqDist()
83
+ >>> for word in word_tokenize(sent):
84
+ ... fdist[word.lower()] += 1
85
+
86
+ An equivalent way to do this is with the initializer:
87
+
88
+ >>> fdist = FreqDist(word.lower() for word in word_tokenize(sent))
89
+
90
+ """
91
+
92
+ def __init__(self, samples=None):
93
+ """
94
+ Construct a new frequency distribution. If ``samples`` is
95
+ given, then the frequency distribution will be initialized
96
+ with the count of each object in ``samples``; otherwise, it
97
+ will be initialized to be empty.
98
+
99
+ In particular, ``FreqDist()`` returns an empty frequency
100
+ distribution; and ``FreqDist(samples)`` first creates an empty
101
+ frequency distribution, and then calls ``update`` with the
102
+ list ``samples``.
103
+
104
+ :param samples: The samples to initialize the frequency
105
+ distribution with.
106
+ :type samples: Sequence
107
+ """
108
+ Counter.__init__(self, samples)
109
+
110
+ # Cached number of samples in this FreqDist
111
+ self._N = None
112
+
113
+ def N(self):
114
+ """
115
+ Return the total number of sample outcomes that have been
116
+ recorded by this FreqDist. For the number of unique
117
+ sample values (or bins) with counts greater than zero, use
118
+ ``FreqDist.B()``.
119
+
120
+ :rtype: int
121
+ """
122
+ if self._N is None:
123
+ # Not already cached, or cache has been invalidated
124
+ self._N = sum(self.values())
125
+ return self._N
126
+
127
+ def __setitem__(self, key, val):
128
+ """
129
+ Override ``Counter.__setitem__()`` to invalidate the cached N
130
+ """
131
+ self._N = None
132
+ super(FreqDist, self).__setitem__(key, val)
133
+
134
+ def __delitem__(self, key):
135
+ """
136
+ Override ``Counter.__delitem__()`` to invalidate the cached N
137
+ """
138
+ self._N = None
139
+ super(FreqDist, self).__delitem__(key)
140
+
141
+ def update(self, *args, **kwargs):
142
+ """
143
+ Override ``Counter.update()`` to invalidate the cached N
144
+ """
145
+ self._N = None
146
+ super(FreqDist, self).update(*args, **kwargs)
147
+
148
+ def setdefault(self, key, val):
149
+ """
150
+ Override ``Counter.setdefault()`` to invalidate the cached N
151
+ """
152
+ self._N = None
153
+ super(FreqDist, self).setdefault(key, val)
154
+
155
+ def B(self):
156
+ """
157
+ Return the total number of sample values (or "bins") that
158
+ have counts greater than zero. For the total
159
+ number of sample outcomes recorded, use ``FreqDist.N()``.
160
+ (FreqDist.B() is the same as len(FreqDist).)
161
+
162
+ :rtype: int
163
+ """
164
+ return len(self)
165
+
166
+ def hapaxes(self):
167
+ """
168
+ Return a list of all samples that occur once (hapax legomena)
169
+
170
+ :rtype: list
171
+ """
172
+ return [item for item in self if self[item] == 1]
173
+
174
+
175
+ def Nr(self, r, bins=None):
176
+ return self.r_Nr(bins)[r]
177
+
178
+ def r_Nr(self, bins=None):
179
+ """
180
+ Return the dictionary mapping r to Nr, the number of samples with frequency r, where Nr > 0.
181
+
182
+ :type bins: int
183
+ :param bins: The number of possible sample outcomes. ``bins``
184
+ is used to calculate Nr(0). In particular, Nr(0) is
185
+ ``bins-self.B()``. If ``bins`` is not specified, it
186
+ defaults to ``self.B()`` (so Nr(0) will be 0).
187
+ :rtype: int
188
+ """
189
+
190
+ _r_Nr = defaultdict(int)
191
+ for count in self.values():
192
+ _r_Nr[count] += 1
193
+
194
+ # Special case for Nr[0]:
195
+ _r_Nr[0] = bins - self.B() if bins is not None else 0
196
+
197
+ return _r_Nr
198
+
199
+ def _cumulative_frequencies(self, samples):
200
+ """
201
+ Return the cumulative frequencies of the specified samples.
202
+ If no samples are specified, all counts are returned, starting
203
+ with the largest.
204
+
205
+ :param samples: the samples whose frequencies should be returned.
206
+ :type samples: any
207
+ :rtype: list(float)
208
+ """
209
+ cf = 0.0
210
+ for sample in samples:
211
+ cf += self[sample]
212
+ yield cf
213
+
214
+ # slightly odd nomenclature freq() if FreqDist does counts and ProbDist does probs,
215
+ # here, freq() does probs
216
+ def freq(self, sample):
217
+ """
218
+ Return the frequency of a given sample. The frequency of a
219
+ sample is defined as the count of that sample divided by the
220
+ total number of sample outcomes that have been recorded by
221
+ this FreqDist. The count of a sample is defined as the
222
+ number of times that sample outcome was recorded by this
223
+ FreqDist. Frequencies are always real numbers in the range
224
+ [0, 1].
225
+
226
+ :param sample: the sample whose frequency
227
+ should be returned.
228
+ :type sample: any
229
+ :rtype: float
230
+ """
231
+ n = self.N()
232
+ if n == 0:
233
+ return 0
234
+ return self[sample] / n
235
+
236
+ def max(self):
237
+ """
238
+ Return the sample with the greatest number of outcomes in this
239
+ frequency distribution. If two or more samples have the same
240
+ number of outcomes, return one of them; which sample is
241
+ returned is undefined. If no outcomes have occurred in this
242
+ frequency distribution, return None.
243
+
244
+ :return: The sample with the maximum number of outcomes in this
245
+ frequency distribution.
246
+ :rtype: any or None
247
+ """
248
+ if len(self) == 0:
249
+ raise ValueError('A FreqDist must have at least one sample before max is defined.')
250
+ return self.most_common(1)[0][0]
251
+
252
+ def plot(self, *args, **kwargs):
253
+ """
254
+ Plot samples from the frequency distribution
255
+ displaying the most frequent sample first. If an integer
256
+ parameter is supplied, stop after this many samples have been
257
+ plotted. For a cumulative plot, specify cumulative=True.
258
+ (Requires Matplotlib to be installed.)
259
+
260
+ :param title: The title for the graph
261
+ :type title: str
262
+ :param cumulative: A flag to specify whether the plot is cumulative (default = False)
263
+ :type title: bool
264
+ """
265
+ try:
266
+ from matplotlib import pylab
267
+ except ImportError:
268
+ raise ValueError('The plot function requires matplotlib to be installed.'
269
+ 'See http://matplotlib.org/')
270
+
271
+ if len(args) == 0:
272
+ args = [len(self)]
273
+ samples = [item for item, _ in self.most_common(*args)]
274
+
275
+ cumulative = _get_kwarg(kwargs, 'cumulative', False)
276
+ if cumulative:
277
+ freqs = list(self._cumulative_frequencies(samples))
278
+ ylabel = "Cumulative Counts"
279
+ else:
280
+ freqs = [self[sample] for sample in samples]
281
+ ylabel = "Counts"
282
+ # percents = [f * 100 for f in freqs] only in ProbDist?
283
+
284
+ pylab.grid(True, color="silver")
285
+ if not "linewidth" in kwargs:
286
+ kwargs["linewidth"] = 2
287
+ if "title" in kwargs:
288
+ pylab.title(kwargs["title"])
289
+ del kwargs["title"]
290
+ pylab.plot(freqs, **kwargs)
291
+ pylab.xticks(range(len(samples)), [text_type(s) for s in samples], rotation=90)
292
+ pylab.xlabel("Samples")
293
+ pylab.ylabel(ylabel)
294
+ pylab.show()
295
+
296
+ def tabulate(self, *args, **kwargs):
297
+ """
298
+ Tabulate the given samples from the frequency distribution (cumulative),
299
+ displaying the most frequent sample first. If an integer
300
+ parameter is supplied, stop after this many samples have been
301
+ plotted.
302
+
303
+ :param samples: The samples to plot (default is all samples)
304
+ :type samples: list
305
+ :param cumulative: A flag to specify whether the freqs are cumulative (default = False)
306
+ :type title: bool
307
+ """
308
+ if len(args) == 0:
309
+ args = [len(self)]
310
+ samples = [item for item, _ in self.most_common(*args)]
311
+
312
+ cumulative = _get_kwarg(kwargs, 'cumulative', False)
313
+ if cumulative:
314
+ freqs = list(self._cumulative_frequencies(samples))
315
+ else:
316
+ freqs = [self[sample] for sample in samples]
317
+ # percents = [f * 100 for f in freqs] only in ProbDist?
318
+
319
+ width = max(len("%s" % s) for s in samples)
320
+ width = max(width, max(len("%d" % f) for f in freqs))
321
+
322
+ for i in range(len(samples)):
323
+ print("%*s" % (width, samples[i]), end=' ')
324
+ print()
325
+ for i in range(len(samples)):
326
+ print("%*d" % (width, freqs[i]), end=' ')
327
+ print()
328
+
329
+ def copy(self):
330
+ """
331
+ Create a copy of this frequency distribution.
332
+
333
+ :rtype: FreqDist
334
+ """
335
+ return self.__class__(self)
336
+
337
+ # Mathematical operatiors
338
+
339
+ def __add__(self, other):
340
+ """
341
+ Add counts from two counters.
342
+
343
+ >>> FreqDist('abbb') + FreqDist('bcc')
344
+ FreqDist({'b': 4, 'c': 2, 'a': 1})
345
+
346
+ """
347
+ return self.__class__(super(FreqDist, self).__add__(other))
348
+
349
+ def __sub__(self, other):
350
+ """
351
+ Subtract count, but keep only results with positive counts.
352
+
353
+ >>> FreqDist('abbbc') - FreqDist('bccd')
354
+ FreqDist({'b': 2, 'a': 1})
355
+
356
+ """
357
+ return self.__class__(super(FreqDist, self).__sub__(other))
358
+
359
+ def __or__(self, other):
360
+ """
361
+ Union is the maximum of value in either of the input counters.
362
+
363
+ >>> FreqDist('abbb') | FreqDist('bcc')
364
+ FreqDist({'b': 3, 'c': 2, 'a': 1})
365
+
366
+ """
367
+ return self.__class__(super(FreqDist, self).__or__(other))
368
+
369
+ def __and__(self, other):
370
+ """
371
+ Intersection is the minimum of corresponding counts.
372
+
373
+ >>> FreqDist('abbb') & FreqDist('bcc')
374
+ FreqDist({'b': 1})
375
+
376
+ """
377
+ return self.__class__(super(FreqDist, self).__and__(other))
378
+
379
+ def __le__(self, other):
380
+ if not isinstance(other, FreqDist):
381
+ raise_unorderable_types("<=", self, other)
382
+ return set(self).issubset(other) and all(self[key] <= other[key] for key in self)
383
+
384
+ # @total_ordering doesn't work here, since the class inherits from a builtin class
385
+ __ge__ = lambda self, other: not self <= other or self == other
386
+ __lt__ = lambda self, other: self <= other and not self == other
387
+ __gt__ = lambda self, other: not self <= other
388
+
389
+ def __repr__(self):
390
+ """
391
+ Return a string representation of this FreqDist.
392
+
393
+ :rtype: string
394
+ """
395
+ return self.pformat()
396
+
397
+ def pprint(self, maxlen=10, stream=None):
398
+ """
399
+ Print a string representation of this FreqDist to 'stream'
400
+
401
+ :param maxlen: The maximum number of items to print
402
+ :type maxlen: int
403
+ :param stream: The stream to print to. stdout by default
404
+ """
405
+ print(self.pformat(maxlen=maxlen), file=stream)
406
+
407
+ def pformat(self, maxlen=10):
408
+ """
409
+ Return a string representation of this FreqDist.
410
+
411
+ :param maxlen: The maximum number of items to display
412
+ :type maxlen: int
413
+ :rtype: string
414
+ """
415
+ items = ['{0!r}: {1!r}'.format(*item) for item in self.most_common(maxlen)]
416
+ if len(self) > maxlen:
417
+ items.append('...')
418
+ return 'FreqDist({{{0}}})'.format(', '.join(items))
419
+
420
+ def __str__(self):
421
+ """
422
+ Return a string representation of this FreqDist.
423
+
424
+ :rtype: string
425
+ """
426
+ return '<FreqDist with %d samples and %d outcomes>' % (len(self), self.N())
427
+
428
+
429
+ ##//////////////////////////////////////////////////////
430
+ ## Probability Distributions
431
+ ##//////////////////////////////////////////////////////
432
+
433
+ @add_metaclass(ABCMeta)
434
+ class ProbDistI(object):
435
+ """
436
+ A probability distribution for the outcomes of an experiment. A
437
+ probability distribution specifies how likely it is that an
438
+ experiment will have any given outcome. For example, a
439
+ probability distribution could be used to predict the probability
440
+ that a token in a document will have a given type. Formally, a
441
+ probability distribution can be defined as a function mapping from
442
+ samples to nonnegative real numbers, such that the sum of every
443
+ number in the function's range is 1.0. A ``ProbDist`` is often
444
+ used to model the probability distribution of the experiment used
445
+ to generate a frequency distribution.
446
+ """
447
+ SUM_TO_ONE = True
448
+ """True if the probabilities of the samples in this probability
449
+ distribution will always sum to one."""
450
+
451
+ @abstractmethod
452
+ def __init__(self):
453
+ """
454
+ Classes inheriting from ProbDistI should implement __init__.
455
+ """
456
+
457
+ @abstractmethod
458
+ def prob(self, sample):
459
+ """
460
+ Return the probability for a given sample. Probabilities
461
+ are always real numbers in the range [0, 1].
462
+
463
+ :param sample: The sample whose probability
464
+ should be returned.
465
+ :type sample: any
466
+ :rtype: float
467
+ """
468
+
469
+ def logprob(self, sample):
470
+ """
471
+ Return the base 2 logarithm of the probability for a given sample.
472
+
473
+ :param sample: The sample whose probability
474
+ should be returned.
475
+ :type sample: any
476
+ :rtype: float
477
+ """
478
+ # Default definition, in terms of prob()
479
+ p = self.prob(sample)
480
+ return (math.log(p, 2) if p != 0 else _NINF)
481
+
482
+ @abstractmethod
483
+ def max(self):
484
+ """
485
+ Return the sample with the greatest probability. If two or
486
+ more samples have the same probability, return one of them;
487
+ which sample is returned is undefined.
488
+
489
+ :rtype: any
490
+ """
491
+
492
+ @abstractmethod
493
+ def samples(self):
494
+ """
495
+ Return a list of all samples that have nonzero probabilities.
496
+ Use ``prob`` to find the probability of each sample.
497
+
498
+ :rtype: list
499
+ """
500
+
501
+ # cf self.SUM_TO_ONE
502
+ def discount(self):
503
+ """
504
+ Return the ratio by which counts are discounted on average: c*/c
505
+
506
+ :rtype: float
507
+ """
508
+ return 0.0
509
+
510
+ # Subclasses should define more efficient implementations of this,
511
+ # where possible.
512
+ def generate(self):
513
+ """
514
+ Return a randomly selected sample from this probability distribution.
515
+ The probability of returning each sample ``samp`` is equal to
516
+ ``self.prob(samp)``.
517
+ """
518
+ p = random.random()
519
+ p_init = p
520
+ for sample in self.samples():
521
+ p -= self.prob(sample)
522
+ if p <= 0: return sample
523
+ # allow for some rounding error:
524
+ if p < .0001:
525
+ return sample
526
+ # we *should* never get here
527
+ if self.SUM_TO_ONE:
528
+ warnings.warn("Probability distribution %r sums to %r; generate()"
529
+ " is returning an arbitrary sample." % (self, p_init-p))
530
+ return random.choice(list(self.samples()))
531
+
532
+
533
+ @compat.python_2_unicode_compatible
534
+ class UniformProbDist(ProbDistI):
535
+ """
536
+ A probability distribution that assigns equal probability to each
537
+ sample in a given set; and a zero probability to all other
538
+ samples.
539
+ """
540
+ def __init__(self, samples):
541
+ """
542
+ Construct a new uniform probability distribution, that assigns
543
+ equal probability to each sample in ``samples``.
544
+
545
+ :param samples: The samples that should be given uniform
546
+ probability.
547
+ :type samples: list
548
+ :raise ValueError: If ``samples`` is empty.
549
+ """
550
+ if len(samples) == 0:
551
+ raise ValueError('A Uniform probability distribution must '+
552
+ 'have at least one sample.')
553
+ self._sampleset = set(samples)
554
+ self._prob = 1.0/len(self._sampleset)
555
+ self._samples = list(self._sampleset)
556
+
557
+ def prob(self, sample):
558
+ return (self._prob if sample in self._sampleset else 0)
559
+
560
+ def max(self):
561
+ return self._samples[0]
562
+
563
+ def samples(self):
564
+ return self._samples
565
+
566
+ def __repr__(self):
567
+ return '<UniformProbDist with %d samples>' % len(self._sampleset)
568
+
569
+
570
+ @compat.python_2_unicode_compatible
571
+ class RandomProbDist(ProbDistI):
572
+ """
573
+ Generates a random probability distribution whereby each sample
574
+ will be between 0 and 1 with equal probability (uniform random distribution.
575
+ Also called a continuous uniform distribution).
576
+ """
577
+ def __init__(self, samples):
578
+ if len(samples) == 0:
579
+ raise ValueError('A probability distribution must '+
580
+ 'have at least one sample.')
581
+ self._probs = self.unirand(samples)
582
+ self._samples = list(self._probs.keys())
583
+
584
+ @classmethod
585
+ def unirand(cls, samples):
586
+ """
587
+ The key function that creates a randomized initial distribution
588
+ that still sums to 1. Set as a dictionary of prob values so that
589
+ it can still be passed to MutableProbDist and called with identical
590
+ syntax to UniformProbDist
591
+ """
592
+ samples = set(samples)
593
+ randrow = [random.random() for i in range(len(samples))]
594
+ total = sum(randrow)
595
+ for i, x in enumerate(randrow):
596
+ randrow[i] = x/total
597
+
598
+ total = sum(randrow)
599
+ if total != 1:
600
+ #this difference, if present, is so small (near NINF) that it
601
+ #can be subtracted from any element without risking probs not (0 1)
602
+ randrow[-1] -= total - 1
603
+
604
+ return dict((s, randrow[i]) for i, s in enumerate(samples))
605
+
606
+ def max(self):
607
+ if not hasattr(self, '_max'):
608
+ self._max = max((p,v) for (v,p) in self._probs.items())[1]
609
+ return self._max
610
+
611
+ def prob(self, sample):
612
+ return self._probs.get(sample, 0)
613
+
614
+ def samples(self):
615
+ return self._samples
616
+
617
+ def __repr__(self):
618
+ return '<RandomUniformProbDist with %d samples>' %len(self._probs)
619
+
620
+
621
+ @compat.python_2_unicode_compatible
622
+ class DictionaryProbDist(ProbDistI):
623
+ """
624
+ A probability distribution whose probabilities are directly
625
+ specified by a given dictionary. The given dictionary maps
626
+ samples to probabilities.
627
+ """
628
+ def __init__(self, prob_dict=None, log=False, normalize=False):
629
+ """
630
+ Construct a new probability distribution from the given
631
+ dictionary, which maps values to probabilities (or to log
632
+ probabilities, if ``log`` is true). If ``normalize`` is
633
+ true, then the probability values are scaled by a constant
634
+ factor such that they sum to 1.
635
+
636
+ If called without arguments, the resulting probability
637
+ distribution assigns zero probability to all values.
638
+ """
639
+
640
+ self._prob_dict = (prob_dict.copy() if prob_dict is not None else {})
641
+ self._log = log
642
+
643
+ # Normalize the distribution, if requested.
644
+ if normalize:
645
+ if len(prob_dict) == 0:
646
+ raise ValueError('A DictionaryProbDist must have at least one sample ' +
647
+ 'before it can be normalized.')
648
+ if log:
649
+ value_sum = sum_logs(list(self._prob_dict.values()))
650
+ if value_sum <= _NINF:
651
+ logp = math.log(1.0/len(prob_dict), 2)
652
+ for x in prob_dict:
653
+ self._prob_dict[x] = logp
654
+ else:
655
+ for (x, p) in self._prob_dict.items():
656
+ self._prob_dict[x] -= value_sum
657
+ else:
658
+ value_sum = sum(self._prob_dict.values())
659
+ if value_sum == 0:
660
+ p = 1.0/len(prob_dict)
661
+ for x in prob_dict:
662
+ self._prob_dict[x] = p
663
+ else:
664
+ norm_factor = 1.0/value_sum
665
+ for (x, p) in self._prob_dict.items():
666
+ self._prob_dict[x] *= norm_factor
667
+
668
+ def prob(self, sample):
669
+ if self._log:
670
+ return (2**(self._prob_dict[sample]) if sample in self._prob_dict else 0)
671
+ else:
672
+ return self._prob_dict.get(sample, 0)
673
+
674
+ def logprob(self, sample):
675
+ if self._log:
676
+ return self._prob_dict.get(sample, _NINF)
677
+ else:
678
+ if sample not in self._prob_dict: return _NINF
679
+ elif self._prob_dict[sample] == 0: return _NINF
680
+ else: return math.log(self._prob_dict[sample], 2)
681
+
682
+ def max(self):
683
+ if not hasattr(self, '_max'):
684
+ self._max = max((p,v) for (v,p) in self._prob_dict.items())[1]
685
+ return self._max
686
+ def samples(self):
687
+ return self._prob_dict.keys()
688
+ def __repr__(self):
689
+ return '<ProbDist with %d samples>' % len(self._prob_dict)
690
+
691
+
692
+ @compat.python_2_unicode_compatible
693
+ class MLEProbDist(ProbDistI):
694
+ """
695
+ The maximum likelihood estimate for the probability distribution
696
+ of the experiment used to generate a frequency distribution. The
697
+ "maximum likelihood estimate" approximates the probability of
698
+ each sample as the frequency of that sample in the frequency
699
+ distribution.
700
+ """
701
+ def __init__(self, freqdist, bins=None):
702
+ """
703
+ Use the maximum likelihood estimate to create a probability
704
+ distribution for the experiment used to generate ``freqdist``.
705
+
706
+ :type freqdist: FreqDist
707
+ :param freqdist: The frequency distribution that the
708
+ probability estimates should be based on.
709
+ """
710
+ self._freqdist = freqdist
711
+
712
+ def freqdist(self):
713
+ """
714
+ Return the frequency distribution that this probability
715
+ distribution is based on.
716
+
717
+ :rtype: FreqDist
718
+ """
719
+ return self._freqdist
720
+
721
+ def prob(self, sample):
722
+ return self._freqdist.freq(sample)
723
+
724
+ def max(self):
725
+ return self._freqdist.max()
726
+
727
+ def samples(self):
728
+ return self._freqdist.keys()
729
+
730
+ def __repr__(self):
731
+ """
732
+ :rtype: str
733
+ :return: A string representation of this ``ProbDist``.
734
+ """
735
+ return '<MLEProbDist based on %d samples>' % self._freqdist.N()
736
+
737
+
738
+ @compat.python_2_unicode_compatible
739
+ class LidstoneProbDist(ProbDistI):
740
+ """
741
+ The Lidstone estimate for the probability distribution of the
742
+ experiment used to generate a frequency distribution. The
743
+ "Lidstone estimate" is parameterized by a real number *gamma*,
744
+ which typically ranges from 0 to 1. The Lidstone estimate
745
+ approximates the probability of a sample with count *c* from an
746
+ experiment with *N* outcomes and *B* bins as
747
+ ``c+gamma)/(N+B*gamma)``. This is equivalent to adding
748
+ *gamma* to the count for each bin, and taking the maximum
749
+ likelihood estimate of the resulting frequency distribution.
750
+ """
751
+ SUM_TO_ONE = False
752
+ def __init__(self, freqdist, gamma, bins=None):
753
+ """
754
+ Use the Lidstone estimate to create a probability distribution
755
+ for the experiment used to generate ``freqdist``.
756
+
757
+ :type freqdist: FreqDist
758
+ :param freqdist: The frequency distribution that the
759
+ probability estimates should be based on.
760
+ :type gamma: float
761
+ :param gamma: A real number used to parameterize the
762
+ estimate. The Lidstone estimate is equivalent to adding
763
+ *gamma* to the count for each bin, and taking the
764
+ maximum likelihood estimate of the resulting frequency
765
+ distribution.
766
+ :type bins: int
767
+ :param bins: The number of sample values that can be generated
768
+ by the experiment that is described by the probability
769
+ distribution. This value must be correctly set for the
770
+ probabilities of the sample values to sum to one. If
771
+ ``bins`` is not specified, it defaults to ``freqdist.B()``.
772
+ """
773
+ if (bins == 0) or (bins is None and freqdist.N() == 0):
774
+ name = self.__class__.__name__[:-8]
775
+ raise ValueError('A %s probability distribution ' % name +
776
+ 'must have at least one bin.')
777
+ if (bins is not None) and (bins < freqdist.B()):
778
+ name = self.__class__.__name__[:-8]
779
+ raise ValueError('\nThe number of bins in a %s distribution ' % name +
780
+ '(%d) must be greater than or equal to\n' % bins +
781
+ 'the number of bins in the FreqDist used ' +
782
+ 'to create it (%d).' % freqdist.B())
783
+
784
+ self._freqdist = freqdist
785
+ self._gamma = float(gamma)
786
+ self._N = self._freqdist.N()
787
+
788
+ if bins is None:
789
+ bins = freqdist.B()
790
+ self._bins = bins
791
+
792
+ self._divisor = self._N + bins * gamma
793
+ if self._divisor == 0.0:
794
+ # In extreme cases we force the probability to be 0,
795
+ # which it will be, since the count will be 0:
796
+ self._gamma = 0
797
+ self._divisor = 1
798
+
799
+ def freqdist(self):
800
+ """
801
+ Return the frequency distribution that this probability
802
+ distribution is based on.
803
+
804
+ :rtype: FreqDist
805
+ """
806
+ return self._freqdist
807
+
808
+ def prob(self, sample):
809
+ c = self._freqdist[sample]
810
+ return (c + self._gamma) / self._divisor
811
+
812
+ def max(self):
813
+ # For Lidstone distributions, probability is monotonic with
814
+ # frequency, so the most probable sample is the one that
815
+ # occurs most frequently.
816
+ return self._freqdist.max()
817
+
818
+ def samples(self):
819
+ return self._freqdist.keys()
820
+
821
+ def discount(self):
822
+ gb = self._gamma * self._bins
823
+ return gb / (self._N + gb)
824
+
825
+ def __repr__(self):
826
+ """
827
+ Return a string representation of this ``ProbDist``.
828
+
829
+ :rtype: str
830
+ """
831
+ return '<LidstoneProbDist based on %d samples>' % self._freqdist.N()
832
+
833
+
834
+ @compat.python_2_unicode_compatible
835
+ class LaplaceProbDist(LidstoneProbDist):
836
+ """
837
+ The Laplace estimate for the probability distribution of the
838
+ experiment used to generate a frequency distribution. The
839
+ "Laplace estimate" approximates the probability of a sample with
840
+ count *c* from an experiment with *N* outcomes and *B* bins as
841
+ *(c+1)/(N+B)*. This is equivalent to adding one to the count for
842
+ each bin, and taking the maximum likelihood estimate of the
843
+ resulting frequency distribution.
844
+ """
845
+ def __init__(self, freqdist, bins=None):
846
+ """
847
+ Use the Laplace estimate to create a probability distribution
848
+ for the experiment used to generate ``freqdist``.
849
+
850
+ :type freqdist: FreqDist
851
+ :param freqdist: The frequency distribution that the
852
+ probability estimates should be based on.
853
+ :type bins: int
854
+ :param bins: The number of sample values that can be generated
855
+ by the experiment that is described by the probability
856
+ distribution. This value must be correctly set for the
857
+ probabilities of the sample values to sum to one. If
858
+ ``bins`` is not specified, it defaults to ``freqdist.B()``.
859
+ """
860
+ LidstoneProbDist.__init__(self, freqdist, 1, bins)
861
+
862
+ def __repr__(self):
863
+ """
864
+ :rtype: str
865
+ :return: A string representation of this ``ProbDist``.
866
+ """
867
+ return '<LaplaceProbDist based on %d samples>' % self._freqdist.N()
868
+
869
+
870
+ @compat.python_2_unicode_compatible
871
+ class ELEProbDist(LidstoneProbDist):
872
+ """
873
+ The expected likelihood estimate for the probability distribution
874
+ of the experiment used to generate a frequency distribution. The
875
+ "expected likelihood estimate" approximates the probability of a
876
+ sample with count *c* from an experiment with *N* outcomes and
877
+ *B* bins as *(c+0.5)/(N+B/2)*. This is equivalent to adding 0.5
878
+ to the count for each bin, and taking the maximum likelihood
879
+ estimate of the resulting frequency distribution.
880
+ """
881
+ def __init__(self, freqdist, bins=None):
882
+ """
883
+ Use the expected likelihood estimate to create a probability
884
+ distribution for the experiment used to generate ``freqdist``.
885
+
886
+ :type freqdist: FreqDist
887
+ :param freqdist: The frequency distribution that the
888
+ probability estimates should be based on.
889
+ :type bins: int
890
+ :param bins: The number of sample values that can be generated
891
+ by the experiment that is described by the probability
892
+ distribution. This value must be correctly set for the
893
+ probabilities of the sample values to sum to one. If
894
+ ``bins`` is not specified, it defaults to ``freqdist.B()``.
895
+ """
896
+ LidstoneProbDist.__init__(self, freqdist, 0.5, bins)
897
+
898
+ def __repr__(self):
899
+ """
900
+ Return a string representation of this ``ProbDist``.
901
+
902
+ :rtype: str
903
+ """
904
+ return '<ELEProbDist based on %d samples>' % self._freqdist.N()
905
+
906
+
907
+ @compat.python_2_unicode_compatible
908
+ class HeldoutProbDist(ProbDistI):
909
+ """
910
+ The heldout estimate for the probability distribution of the
911
+ experiment used to generate two frequency distributions. These
912
+ two frequency distributions are called the "heldout frequency
913
+ distribution" and the "base frequency distribution." The
914
+ "heldout estimate" uses uses the "heldout frequency
915
+ distribution" to predict the probability of each sample, given its
916
+ frequency in the "base frequency distribution".
917
+
918
+ In particular, the heldout estimate approximates the probability
919
+ for a sample that occurs *r* times in the base distribution as
920
+ the average frequency in the heldout distribution of all samples
921
+ that occur *r* times in the base distribution.
922
+
923
+ This average frequency is *Tr[r]/(Nr[r].N)*, where:
924
+
925
+ - *Tr[r]* is the total count in the heldout distribution for
926
+ all samples that occur *r* times in the base distribution.
927
+ - *Nr[r]* is the number of samples that occur *r* times in
928
+ the base distribution.
929
+ - *N* is the number of outcomes recorded by the heldout
930
+ frequency distribution.
931
+
932
+ In order to increase the efficiency of the ``prob`` member
933
+ function, *Tr[r]/(Nr[r].N)* is precomputed for each value of *r*
934
+ when the ``HeldoutProbDist`` is created.
935
+
936
+ :type _estimate: list(float)
937
+ :ivar _estimate: A list mapping from *r*, the number of
938
+ times that a sample occurs in the base distribution, to the
939
+ probability estimate for that sample. ``_estimate[r]`` is
940
+ calculated by finding the average frequency in the heldout
941
+ distribution of all samples that occur *r* times in the base
942
+ distribution. In particular, ``_estimate[r]`` =
943
+ *Tr[r]/(Nr[r].N)*.
944
+ :type _max_r: int
945
+ :ivar _max_r: The maximum number of times that any sample occurs
946
+ in the base distribution. ``_max_r`` is used to decide how
947
+ large ``_estimate`` must be.
948
+ """
949
+ SUM_TO_ONE = False
950
+ def __init__(self, base_fdist, heldout_fdist, bins=None):
951
+ """
952
+ Use the heldout estimate to create a probability distribution
953
+ for the experiment used to generate ``base_fdist`` and
954
+ ``heldout_fdist``.
955
+
956
+ :type base_fdist: FreqDist
957
+ :param base_fdist: The base frequency distribution.
958
+ :type heldout_fdist: FreqDist
959
+ :param heldout_fdist: The heldout frequency distribution.
960
+ :type bins: int
961
+ :param bins: The number of sample values that can be generated
962
+ by the experiment that is described by the probability
963
+ distribution. This value must be correctly set for the
964
+ probabilities of the sample values to sum to one. If
965
+ ``bins`` is not specified, it defaults to ``freqdist.B()``.
966
+ """
967
+
968
+ self._base_fdist = base_fdist
969
+ self._heldout_fdist = heldout_fdist
970
+
971
+ # The max number of times any sample occurs in base_fdist.
972
+ self._max_r = base_fdist[base_fdist.max()]
973
+
974
+ # Calculate Tr, Nr, and N.
975
+ Tr = self._calculate_Tr()
976
+ r_Nr = base_fdist.r_Nr(bins)
977
+ Nr = [r_Nr[r] for r in range(self._max_r+1)]
978
+ N = heldout_fdist.N()
979
+
980
+ # Use Tr, Nr, and N to compute the probability estimate for
981
+ # each value of r.
982
+ self._estimate = self._calculate_estimate(Tr, Nr, N)
983
+
984
+ def _calculate_Tr(self):
985
+ """
986
+ Return the list *Tr*, where *Tr[r]* is the total count in
987
+ ``heldout_fdist`` for all samples that occur *r*
988
+ times in ``base_fdist``.
989
+
990
+ :rtype: list(float)
991
+ """
992
+ Tr = [0.0] * (self._max_r+1)
993
+ for sample in self._heldout_fdist:
994
+ r = self._base_fdist[sample]
995
+ Tr[r] += self._heldout_fdist[sample]
996
+ return Tr
997
+
998
+ def _calculate_estimate(self, Tr, Nr, N):
999
+ """
1000
+ Return the list *estimate*, where *estimate[r]* is the probability
1001
+ estimate for any sample that occurs *r* times in the base frequency
1002
+ distribution. In particular, *estimate[r]* is *Tr[r]/(N[r].N)*.
1003
+ In the special case that *N[r]=0*, *estimate[r]* will never be used;
1004
+ so we define *estimate[r]=None* for those cases.
1005
+
1006
+ :rtype: list(float)
1007
+ :type Tr: list(float)
1008
+ :param Tr: the list *Tr*, where *Tr[r]* is the total count in
1009
+ the heldout distribution for all samples that occur *r*
1010
+ times in base distribution.
1011
+ :type Nr: list(float)
1012
+ :param Nr: The list *Nr*, where *Nr[r]* is the number of
1013
+ samples that occur *r* times in the base distribution.
1014
+ :type N: int
1015
+ :param N: The total number of outcomes recorded by the heldout
1016
+ frequency distribution.
1017
+ """
1018
+ estimate = []
1019
+ for r in range(self._max_r+1):
1020
+ if Nr[r] == 0: estimate.append(None)
1021
+ else: estimate.append(Tr[r]/(Nr[r]*N))
1022
+ return estimate
1023
+
1024
+ def base_fdist(self):
1025
+ """
1026
+ Return the base frequency distribution that this probability
1027
+ distribution is based on.
1028
+
1029
+ :rtype: FreqDist
1030
+ """
1031
+ return self._base_fdist
1032
+
1033
+ def heldout_fdist(self):
1034
+ """
1035
+ Return the heldout frequency distribution that this
1036
+ probability distribution is based on.
1037
+
1038
+ :rtype: FreqDist
1039
+ """
1040
+ return self._heldout_fdist
1041
+
1042
+ def samples(self):
1043
+ return self._base_fdist.keys()
1044
+
1045
+ def prob(self, sample):
1046
+ # Use our precomputed probability estimate.
1047
+ r = self._base_fdist[sample]
1048
+ return self._estimate[r]
1049
+
1050
+ def max(self):
1051
+ # Note: the Heldout estimation is *not* necessarily monotonic;
1052
+ # so this implementation is currently broken. However, it
1053
+ # should give the right answer *most* of the time. :)
1054
+ return self._base_fdist.max()
1055
+
1056
+ def discount(self):
1057
+ raise NotImplementedError()
1058
+
1059
+ def __repr__(self):
1060
+ """
1061
+ :rtype: str
1062
+ :return: A string representation of this ``ProbDist``.
1063
+ """
1064
+ s = '<HeldoutProbDist: %d base samples; %d heldout samples>'
1065
+ return s % (self._base_fdist.N(), self._heldout_fdist.N())
1066
+
1067
+
1068
+ @compat.python_2_unicode_compatible
1069
+ class CrossValidationProbDist(ProbDistI):
1070
+ """
1071
+ The cross-validation estimate for the probability distribution of
1072
+ the experiment used to generate a set of frequency distribution.
1073
+ The "cross-validation estimate" for the probability of a sample
1074
+ is found by averaging the held-out estimates for the sample in
1075
+ each pair of frequency distributions.
1076
+ """
1077
+ SUM_TO_ONE = False
1078
+ def __init__(self, freqdists, bins):
1079
+ """
1080
+ Use the cross-validation estimate to create a probability
1081
+ distribution for the experiment used to generate
1082
+ ``freqdists``.
1083
+
1084
+ :type freqdists: list(FreqDist)
1085
+ :param freqdists: A list of the frequency distributions
1086
+ generated by the experiment.
1087
+ :type bins: int
1088
+ :param bins: The number of sample values that can be generated
1089
+ by the experiment that is described by the probability
1090
+ distribution. This value must be correctly set for the
1091
+ probabilities of the sample values to sum to one. If
1092
+ ``bins`` is not specified, it defaults to ``freqdist.B()``.
1093
+ """
1094
+ self._freqdists = freqdists
1095
+
1096
+ # Create a heldout probability distribution for each pair of
1097
+ # frequency distributions in freqdists.
1098
+ self._heldout_probdists = []
1099
+ for fdist1 in freqdists:
1100
+ for fdist2 in freqdists:
1101
+ if fdist1 is not fdist2:
1102
+ probdist = HeldoutProbDist(fdist1, fdist2, bins)
1103
+ self._heldout_probdists.append(probdist)
1104
+
1105
+ def freqdists(self):
1106
+ """
1107
+ Return the list of frequency distributions that this ``ProbDist`` is based on.
1108
+
1109
+ :rtype: list(FreqDist)
1110
+ """
1111
+ return self._freqdists
1112
+
1113
+ def samples(self):
1114
+ # [xx] nb: this is not too efficient
1115
+ return set(sum([list(fd) for fd in self._freqdists], []))
1116
+
1117
+ def prob(self, sample):
1118
+ # Find the average probability estimate returned by each
1119
+ # heldout distribution.
1120
+ prob = 0.0
1121
+ for heldout_probdist in self._heldout_probdists:
1122
+ prob += heldout_probdist.prob(sample)
1123
+ return prob/len(self._heldout_probdists)
1124
+
1125
+ def discount(self):
1126
+ raise NotImplementedError()
1127
+
1128
+ def __repr__(self):
1129
+ """
1130
+ Return a string representation of this ``ProbDist``.
1131
+
1132
+ :rtype: str
1133
+ """
1134
+ return '<CrossValidationProbDist: %d-way>' % len(self._freqdists)
1135
+
1136
+
1137
+ @compat.python_2_unicode_compatible
1138
+ class WittenBellProbDist(ProbDistI):
1139
+ """
1140
+ The Witten-Bell estimate of a probability distribution. This distribution
1141
+ allocates uniform probability mass to as yet unseen events by using the
1142
+ number of events that have only been seen once. The probability mass
1143
+ reserved for unseen events is equal to *T / (N + T)*
1144
+ where *T* is the number of observed event types and *N* is the total
1145
+ number of observed events. This equates to the maximum likelihood estimate
1146
+ of a new type event occurring. The remaining probability mass is discounted
1147
+ such that all probability estimates sum to one, yielding:
1148
+
1149
+ - *p = T / Z (N + T)*, if count = 0
1150
+ - *p = c / (N + T)*, otherwise
1151
+ """
1152
+
1153
+ def __init__(self, freqdist, bins=None):
1154
+ """
1155
+ Creates a distribution of Witten-Bell probability estimates. This
1156
+ distribution allocates uniform probability mass to as yet unseen
1157
+ events by using the number of events that have only been seen once. The
1158
+ probability mass reserved for unseen events is equal to *T / (N + T)*
1159
+ where *T* is the number of observed event types and *N* is the total
1160
+ number of observed events. This equates to the maximum likelihood
1161
+ estimate of a new type event occurring. The remaining probability mass
1162
+ is discounted such that all probability estimates sum to one,
1163
+ yielding:
1164
+
1165
+ - *p = T / Z (N + T)*, if count = 0
1166
+ - *p = c / (N + T)*, otherwise
1167
+
1168
+ The parameters *T* and *N* are taken from the ``freqdist`` parameter
1169
+ (the ``B()`` and ``N()`` values). The normalizing factor *Z* is
1170
+ calculated using these values along with the ``bins`` parameter.
1171
+
1172
+ :param freqdist: The frequency counts upon which to base the
1173
+ estimation.
1174
+ :type freqdist: FreqDist
1175
+ :param bins: The number of possible event types. This must be at least
1176
+ as large as the number of bins in the ``freqdist``. If None, then
1177
+ it's assumed to be equal to that of the ``freqdist``
1178
+ :type bins: int
1179
+ """
1180
+ assert bins is None or bins >= freqdist.B(),\
1181
+ 'bins parameter must not be less than %d=freqdist.B()' % freqdist.B()
1182
+ if bins is None:
1183
+ bins = freqdist.B()
1184
+ self._freqdist = freqdist
1185
+ self._T = self._freqdist.B()
1186
+ self._Z = bins - self._freqdist.B()
1187
+ self._N = self._freqdist.N()
1188
+ # self._P0 is P(0), precalculated for efficiency:
1189
+ if self._N==0:
1190
+ # if freqdist is empty, we approximate P(0) by a UniformProbDist:
1191
+ self._P0 = 1.0 / self._Z
1192
+ else:
1193
+ self._P0 = self._T / (self._Z * (self._N + self._T))
1194
+
1195
+ def prob(self, sample):
1196
+ # inherit docs from ProbDistI
1197
+ c = self._freqdist[sample]
1198
+ return (c / (self._N + self._T) if c != 0 else self._P0)
1199
+
1200
+ def max(self):
1201
+ return self._freqdist.max()
1202
+
1203
+ def samples(self):
1204
+ return self._freqdist.keys()
1205
+
1206
+ def freqdist(self):
1207
+ return self._freqdist
1208
+
1209
+ def discount(self):
1210
+ raise NotImplementedError()
1211
+
1212
+ def __repr__(self):
1213
+ """
1214
+ Return a string representation of this ``ProbDist``.
1215
+
1216
+ :rtype: str
1217
+ """
1218
+ return '<WittenBellProbDist based on %d samples>' % self._freqdist.N()
1219
+
1220
+
1221
+ ##//////////////////////////////////////////////////////
1222
+ ## Good-Turing Probability Distributions
1223
+ ##//////////////////////////////////////////////////////
1224
+
1225
+ # Good-Turing frequency estimation was contributed by Alan Turing and
1226
+ # his statistical assistant I.J. Good, during their collaboration in
1227
+ # the WWII. It is a statistical technique for predicting the
1228
+ # probability of occurrence of objects belonging to an unknown number
1229
+ # of species, given past observations of such objects and their
1230
+ # species. (In drawing balls from an urn, the 'objects' would be balls
1231
+ # and the 'species' would be the distinct colors of the balls (finite
1232
+ # but unknown in number).
1233
+ #
1234
+ # Good-Turing method calculates the probability mass to assign to
1235
+ # events with zero or low counts based on the number of events with
1236
+ # higher counts. It does so by using the adjusted count *c\**:
1237
+ #
1238
+ # - *c\* = (c + 1) N(c + 1) / N(c)* for c >= 1
1239
+ # - *things with frequency zero in training* = N(1) for c == 0
1240
+ #
1241
+ # where *c* is the original count, *N(i)* is the number of event types
1242
+ # observed with count *i*. We can think the count of unseen as the count
1243
+ # of frequency one (see Jurafsky & Martin 2nd Edition, p101).
1244
+ #
1245
+ # This method is problematic because the situation ``N(c+1) == 0``
1246
+ # is quite common in the original Good-Turing estimation; smoothing or
1247
+ # interpolation of *N(i)* values is essential in practice.
1248
+ #
1249
+ # Bill Gale and Geoffrey Sampson present a simple and effective approach,
1250
+ # Simple Good-Turing. As a smoothing curve they simply use a power curve:
1251
+ #
1252
+ # Nr = a*r^b (with b < -1 to give the appropriate hyperbolic
1253
+ # relationship)
1254
+ #
1255
+ # They estimate a and b by simple linear regression technique on the
1256
+ # logarithmic form of the equation:
1257
+ #
1258
+ # log Nr = a + b*log(r)
1259
+ #
1260
+ # However, they suggest that such a simple curve is probably only
1261
+ # appropriate for high values of r. For low values of r, they use the
1262
+ # measured Nr directly. (see M&S, p.213)
1263
+ #
1264
+ # Gale and Sampson propose to use r while the difference between r and
1265
+ # r* is 1.96 greater than the standard deviation, and switch to r* if
1266
+ # it is less or equal:
1267
+ #
1268
+ # |r - r*| > 1.96 * sqrt((r + 1)^2 (Nr+1 / Nr^2) (1 + Nr+1 / Nr))
1269
+ #
1270
+ # The 1.96 coefficient correspond to a 0.05 significance criterion,
1271
+ # some implementations can use a coefficient of 1.65 for a 0.1
1272
+ # significance criterion.
1273
+ #
1274
+
1275
+ ##//////////////////////////////////////////////////////
1276
+ ## Simple Good-Turing Probablity Distributions
1277
+ ##//////////////////////////////////////////////////////
1278
+
1279
+ @compat.python_2_unicode_compatible
1280
+ class SimpleGoodTuringProbDist(ProbDistI):
1281
+ """
1282
+ SimpleGoodTuring ProbDist approximates from frequency to frequency of
1283
+ frequency into a linear line under log space by linear regression.
1284
+ Details of Simple Good-Turing algorithm can be found in:
1285
+
1286
+ - Good Turing smoothing without tears" (Gale & Sampson 1995),
1287
+ Journal of Quantitative Linguistics, vol. 2 pp. 217-237.
1288
+ - "Speech and Language Processing (Jurafsky & Martin),
1289
+ 2nd Edition, Chapter 4.5 p103 (log(Nc) = a + b*log(c))
1290
+ - http://www.grsampson.net/RGoodTur.html
1291
+
1292
+ Given a set of pair (xi, yi), where the xi denotes the frequency and
1293
+ yi denotes the frequency of frequency, we want to minimize their
1294
+ square variation. E(x) and E(y) represent the mean of xi and yi.
1295
+
1296
+ - slope: b = sigma ((xi-E(x)(yi-E(y))) / sigma ((xi-E(x))(xi-E(x)))
1297
+ - intercept: a = E(y) - b.E(x)
1298
+ """
1299
+ SUM_TO_ONE = False
1300
+ def __init__(self, freqdist, bins=None):
1301
+ """
1302
+ :param freqdist: The frequency counts upon which to base the
1303
+ estimation.
1304
+ :type freqdist: FreqDist
1305
+ :param bins: The number of possible event types. This must be
1306
+ larger than the number of bins in the ``freqdist``. If None,
1307
+ then it's assumed to be equal to ``freqdist``.B() + 1
1308
+ :type bins: int
1309
+ """
1310
+ assert bins is None or bins > freqdist.B(),\
1311
+ 'bins parameter must not be less than %d=freqdist.B()+1' % (freqdist.B()+1)
1312
+ if bins is None:
1313
+ bins = freqdist.B() + 1
1314
+ self._freqdist = freqdist
1315
+ self._bins = bins
1316
+ r, nr = self._r_Nr()
1317
+ self.find_best_fit(r, nr)
1318
+ self._switch(r, nr)
1319
+ self._renormalize(r, nr)
1320
+
1321
+ def _r_Nr_non_zero(self):
1322
+ r_Nr = self._freqdist.r_Nr()
1323
+ del r_Nr[0]
1324
+ return r_Nr
1325
+
1326
+ def _r_Nr(self):
1327
+ """
1328
+ Split the frequency distribution in two list (r, Nr), where Nr(r) > 0
1329
+ """
1330
+ nonzero = self._r_Nr_non_zero()
1331
+
1332
+ if not nonzero:
1333
+ return [], []
1334
+ return zip(*sorted(nonzero.items()))
1335
+
1336
+ def find_best_fit(self, r, nr):
1337
+ """
1338
+ Use simple linear regression to tune parameters self._slope and
1339
+ self._intercept in the log-log space based on count and Nr(count)
1340
+ (Work in log space to avoid floating point underflow.)
1341
+ """
1342
+ # For higher sample frequencies the data points becomes horizontal
1343
+ # along line Nr=1. To create a more evident linear model in log-log
1344
+ # space, we average positive Nr values with the surrounding zero
1345
+ # values. (Church and Gale, 1991)
1346
+
1347
+ if not r or not nr:
1348
+ # Empty r or nr?
1349
+ return
1350
+
1351
+ zr = []
1352
+ for j in range(len(r)):
1353
+ i = (r[j-1] if j > 0 else 0)
1354
+ k = (2 * r[j] - i if j == len(r) - 1 else r[j+1])
1355
+ zr_ = 2.0 * nr[j] / (k - i)
1356
+ zr.append(zr_)
1357
+
1358
+ log_r = [math.log(i) for i in r]
1359
+ log_zr = [math.log(i) for i in zr]
1360
+
1361
+ xy_cov = x_var = 0.0
1362
+ x_mean = sum(log_r) / len(log_r)
1363
+ y_mean = sum(log_zr) / len(log_zr)
1364
+ for (x, y) in zip(log_r, log_zr):
1365
+ xy_cov += (x - x_mean) * (y - y_mean)
1366
+ x_var += (x - x_mean)**2
1367
+ self._slope = (xy_cov / x_var if x_var != 0 else 0.0)
1368
+ if self._slope >= -1:
1369
+ warnings.warn('SimpleGoodTuring did not find a proper best fit '
1370
+ 'line for smoothing probabilities of occurrences. '
1371
+ 'The probability estimates are likely to be '
1372
+ 'unreliable.')
1373
+ self._intercept = y_mean - self._slope * x_mean
1374
+
1375
+ def _switch(self, r, nr):
1376
+ """
1377
+ Calculate the r frontier where we must switch from Nr to Sr
1378
+ when estimating E[Nr].
1379
+ """
1380
+ for i, r_ in enumerate(r):
1381
+ if len(r) == i + 1 or r[i+1] != r_ + 1:
1382
+ # We are at the end of r, or there is a gap in r
1383
+ self._switch_at = r_
1384
+ break
1385
+
1386
+ Sr = self.smoothedNr
1387
+ smooth_r_star = (r_ + 1) * Sr(r_+1) / Sr(r_)
1388
+ unsmooth_r_star = (r_ + 1) * nr[i+1] / nr[i]
1389
+
1390
+ std = math.sqrt(self._variance(r_, nr[i], nr[i+1]))
1391
+ if abs(unsmooth_r_star-smooth_r_star) <= 1.96 * std:
1392
+ self._switch_at = r_
1393
+ break
1394
+
1395
+ def _variance(self, r, nr, nr_1):
1396
+ r = float(r)
1397
+ nr = float(nr)
1398
+ nr_1 = float(nr_1)
1399
+ return (r + 1.0)**2 * (nr_1 / nr**2) * (1.0 + nr_1 / nr)
1400
+
1401
+ def _renormalize(self, r, nr):
1402
+ """
1403
+ It is necessary to renormalize all the probability estimates to
1404
+ ensure a proper probability distribution results. This can be done
1405
+ by keeping the estimate of the probability mass for unseen items as
1406
+ N(1)/N and renormalizing all the estimates for previously seen items
1407
+ (as Gale and Sampson (1995) propose). (See M&S P.213, 1999)
1408
+ """
1409
+ prob_cov = 0.0
1410
+ for r_, nr_ in zip(r, nr):
1411
+ prob_cov += nr_ * self._prob_measure(r_)
1412
+ if prob_cov:
1413
+ self._renormal = (1 - self._prob_measure(0)) / prob_cov
1414
+
1415
+ def smoothedNr(self, r):
1416
+ """
1417
+ Return the number of samples with count r.
1418
+
1419
+ :param r: The amount of frequency.
1420
+ :type r: int
1421
+ :rtype: float
1422
+ """
1423
+
1424
+ # Nr = a*r^b (with b < -1 to give the appropriate hyperbolic
1425
+ # relationship)
1426
+ # Estimate a and b by simple linear regression technique on
1427
+ # the logarithmic form of the equation: log Nr = a + b*log(r)
1428
+
1429
+ return math.exp(self._intercept + self._slope * math.log(r))
1430
+
1431
+ def prob(self, sample):
1432
+ """
1433
+ Return the sample's probability.
1434
+
1435
+ :param sample: sample of the event
1436
+ :type sample: str
1437
+ :rtype: float
1438
+ """
1439
+ count = self._freqdist[sample]
1440
+ p = self._prob_measure(count)
1441
+ if count == 0:
1442
+ if self._bins == self._freqdist.B():
1443
+ p = 0.0
1444
+ else:
1445
+ p = p / (self._bins - self._freqdist.B())
1446
+ else:
1447
+ p = p * self._renormal
1448
+ return p
1449
+
1450
+ def _prob_measure(self, count):
1451
+ if count == 0 and self._freqdist.N() == 0 :
1452
+ return 1.0
1453
+ elif count == 0 and self._freqdist.N() != 0:
1454
+ return self._freqdist.Nr(1) / self._freqdist.N()
1455
+
1456
+ if self._switch_at > count:
1457
+ Er_1 = self._freqdist.Nr(count+1)
1458
+ Er = self._freqdist.Nr(count)
1459
+ else:
1460
+ Er_1 = self.smoothedNr(count+1)
1461
+ Er = self.smoothedNr(count)
1462
+
1463
+ r_star = (count + 1) * Er_1 / Er
1464
+ return r_star / self._freqdist.N()
1465
+
1466
+ def check(self):
1467
+ prob_sum = 0.0
1468
+ for i in range(0, len(self._Nr)):
1469
+ prob_sum += self._Nr[i] * self._prob_measure(i) / self._renormal
1470
+ print("Probability Sum:", prob_sum)
1471
+ #assert prob_sum != 1.0, "probability sum should be one!"
1472
+
1473
+ def discount(self):
1474
+ """
1475
+ This function returns the total mass of probability transfers from the
1476
+ seen samples to the unseen samples.
1477
+ """
1478
+ return self.smoothedNr(1) / self._freqdist.N()
1479
+
1480
+ def max(self):
1481
+ return self._freqdist.max()
1482
+
1483
+ def samples(self):
1484
+ return self._freqdist.keys()
1485
+
1486
+ def freqdist(self):
1487
+ return self._freqdist
1488
+
1489
+ def __repr__(self):
1490
+ """
1491
+ Return a string representation of this ``ProbDist``.
1492
+
1493
+ :rtype: str
1494
+ """
1495
+ return '<SimpleGoodTuringProbDist based on %d samples>'\
1496
+ % self._freqdist.N()
1497
+
1498
+
1499
+ class MutableProbDist(ProbDistI):
1500
+ """
1501
+ An mutable probdist where the probabilities may be easily modified. This
1502
+ simply copies an existing probdist, storing the probability values in a
1503
+ mutable dictionary and providing an update method.
1504
+ """
1505
+
1506
+ def __init__(self, prob_dist, samples, store_logs=True):
1507
+ """
1508
+ Creates the mutable probdist based on the given prob_dist and using
1509
+ the list of samples given. These values are stored as log
1510
+ probabilities if the store_logs flag is set.
1511
+
1512
+ :param prob_dist: the distribution from which to garner the
1513
+ probabilities
1514
+ :type prob_dist: ProbDist
1515
+ :param samples: the complete set of samples
1516
+ :type samples: sequence of any
1517
+ :param store_logs: whether to store the probabilities as logarithms
1518
+ :type store_logs: bool
1519
+ """
1520
+ self._samples = samples
1521
+ self._sample_dict = dict((samples[i], i) for i in range(len(samples)))
1522
+ self._data = array.array(str("d"), [0.0]) * len(samples)
1523
+ for i in range(len(samples)):
1524
+ if store_logs:
1525
+ self._data[i] = prob_dist.logprob(samples[i])
1526
+ else:
1527
+ self._data[i] = prob_dist.prob(samples[i])
1528
+ self._logs = store_logs
1529
+
1530
+ def max(self):
1531
+ # inherit documentation
1532
+ return max((p,v) for (v,p) in self._sample_dict.items())[1]
1533
+
1534
+ def samples(self):
1535
+ # inherit documentation
1536
+ return self._samples
1537
+
1538
+ def prob(self, sample):
1539
+ # inherit documentation
1540
+ i = self._sample_dict.get(sample)
1541
+ if i is None:
1542
+ return 0.0
1543
+ return (2**(self._data[i]) if self._logs else self._data[i])
1544
+
1545
+ def logprob(self, sample):
1546
+ # inherit documentation
1547
+ i = self._sample_dict.get(sample)
1548
+ if i is None:
1549
+ return float('-inf')
1550
+ return (self._data[i] if self._logs else math.log(self._data[i], 2))
1551
+
1552
+ def update(self, sample, prob, log=True):
1553
+ """
1554
+ Update the probability for the given sample. This may cause the object
1555
+ to stop being the valid probability distribution - the user must
1556
+ ensure that they update the sample probabilities such that all samples
1557
+ have probabilities between 0 and 1 and that all probabilities sum to
1558
+ one.
1559
+
1560
+ :param sample: the sample for which to update the probability
1561
+ :type sample: any
1562
+ :param prob: the new probability
1563
+ :type prob: float
1564
+ :param log: is the probability already logged
1565
+ :type log: bool
1566
+ """
1567
+ i = self._sample_dict.get(sample)
1568
+ assert i is not None
1569
+ if self._logs:
1570
+ self._data[i] = (prob if log else math.log(prob, 2))
1571
+ else:
1572
+ self._data[i] = (2**(prob) if log else prob)
1573
+
1574
+ ##/////////////////////////////////////////////////////
1575
+ ## Kneser-Ney Probability Distribution
1576
+ ##//////////////////////////////////////////////////////
1577
+
1578
+ # This method for calculating probabilities was introduced in 1995 by Reinhard
1579
+ # Kneser and Hermann Ney. It was meant to improve the accuracy of language
1580
+ # models that use backing-off to deal with sparse data. The authors propose two
1581
+ # ways of doing so: a marginal distribution constraint on the back-off
1582
+ # distribution and a leave-one-out distribution. For a start, the first one is
1583
+ # implemented as a class below.
1584
+ #
1585
+ # The idea behind a back-off n-gram model is that we have a series of
1586
+ # frequency distributions for our n-grams so that in case we have not seen a
1587
+ # given n-gram during training (and as a result have a 0 probability for it) we
1588
+ # can 'back off' (hence the name!) and try testing whether we've seen the
1589
+ # n-1-gram part of the n-gram in training.
1590
+ #
1591
+ # The novelty of Kneser and Ney's approach was that they decided to fiddle
1592
+ # around with the way this latter, backed off probability was being calculated
1593
+ # whereas their peers seemed to focus on the primary probability.
1594
+ #
1595
+ # The implementation below uses one of the techniques described in their paper
1596
+ # titled "Improved backing-off for n-gram language modeling." In the same paper
1597
+ # another technique is introduced to attempt to smooth the back-off
1598
+ # distribution as well as the primary one. There is also a much-cited
1599
+ # modification of this method proposed by Chen and Goodman.
1600
+ #
1601
+ # In order for the implementation of Kneser-Ney to be more efficient, some
1602
+ # changes have been made to the original algorithm. Namely, the calculation of
1603
+ # the normalizing function gamma has been significantly simplified and
1604
+ # combined slightly differently with beta. None of these changes affect the
1605
+ # nature of the algorithm, but instead aim to cut out unnecessary calculations
1606
+ # and take advantage of storing and retrieving information in dictionaries
1607
+ # where possible.
1608
+
1609
+ @compat.python_2_unicode_compatible
1610
+ class KneserNeyProbDist(ProbDistI):
1611
+ """
1612
+ Kneser-Ney estimate of a probability distribution. This is a version of
1613
+ back-off that counts how likely an n-gram is provided the n-1-gram had
1614
+ been seen in training. Extends the ProbDistI interface, requires a trigram
1615
+ FreqDist instance to train on. Optionally, a different from default discount
1616
+ value can be specified. The default discount is set to 0.75.
1617
+
1618
+ """
1619
+ def __init__(self, freqdist, bins=None, discount=0.75):
1620
+ """
1621
+ :param freqdist: The trigram frequency distribution upon which to base
1622
+ the estimation
1623
+ :type freqdist: FreqDist
1624
+ :param bins: Included for compatibility with nltk.tag.hmm
1625
+ :type bins: int or float
1626
+ :param discount: The discount applied when retrieving counts of
1627
+ trigrams
1628
+ :type discount: float (preferred, but can be set to int)
1629
+ """
1630
+
1631
+ if not bins:
1632
+ self._bins = freqdist.B()
1633
+ else:
1634
+ self._bins = bins
1635
+ self._D = discount
1636
+
1637
+ # cache for probability calculation
1638
+ self._cache = {}
1639
+
1640
+ # internal bigram and trigram frequency distributions
1641
+ self._bigrams = defaultdict(int)
1642
+ self._trigrams = freqdist
1643
+
1644
+ # helper dictionaries used to calculate probabilities
1645
+ self._wordtypes_after = defaultdict(float)
1646
+ self._trigrams_contain = defaultdict(float)
1647
+ self._wordtypes_before = defaultdict(float)
1648
+ for w0, w1, w2 in freqdist:
1649
+ self._bigrams[(w0,w1)] += freqdist[(w0, w1, w2)]
1650
+ self._wordtypes_after[(w0,w1)] += 1
1651
+ self._trigrams_contain[w1] += 1
1652
+ self._wordtypes_before[(w1,w2)] += 1
1653
+
1654
+ def prob(self, trigram):
1655
+ # sample must be a triple
1656
+ if len(trigram) != 3:
1657
+ raise ValueError('Expected an iterable with 3 members.')
1658
+ trigram = tuple(trigram)
1659
+ w0, w1, w2 = trigram
1660
+
1661
+ if trigram in self._cache:
1662
+ return self._cache[trigram]
1663
+ else:
1664
+ # if the sample trigram was seen during training
1665
+ if trigram in self._trigrams:
1666
+ prob = (self._trigrams[trigram]
1667
+ - self.discount())/self._bigrams[(w0, w1)]
1668
+
1669
+ # else if the 'rougher' environment was seen during training
1670
+ elif (w0,w1) in self._bigrams and (w1,w2) in self._wordtypes_before:
1671
+ aftr = self._wordtypes_after[(w0, w1)]
1672
+ bfr = self._wordtypes_before[(w1, w2)]
1673
+
1674
+ # the probability left over from alphas
1675
+ leftover_prob = ((aftr * self.discount())
1676
+ / self._bigrams[(w0, w1)])
1677
+
1678
+ # the beta (including normalization)
1679
+ beta = bfr /(self._trigrams_contain[w1] - aftr)
1680
+
1681
+ prob = leftover_prob * beta
1682
+
1683
+ # else the sample was completely unseen during training
1684
+ else:
1685
+ prob = 0.0
1686
+
1687
+ self._cache[trigram] = prob
1688
+ return prob
1689
+
1690
+ def discount(self):
1691
+ """
1692
+ Return the value by which counts are discounted. By default set to 0.75.
1693
+
1694
+ :rtype: float
1695
+ """
1696
+ return self._D
1697
+
1698
+ def set_discount(self, discount):
1699
+ """
1700
+ Set the value by which counts are discounted to the value of discount.
1701
+
1702
+ :param discount: the new value to discount counts by
1703
+ :type discount: float (preferred, but int possible)
1704
+ :rtype: None
1705
+ """
1706
+ self._D = discount
1707
+
1708
+ def samples(self):
1709
+ return self._trigrams.keys()
1710
+
1711
+ def max(self):
1712
+ return self._trigrams.max()
1713
+
1714
+ def __repr__(self):
1715
+ '''
1716
+ Return a string representation of this ProbDist
1717
+
1718
+ :rtype: str
1719
+ '''
1720
+ return '<KneserNeyProbDist based on {0} trigrams'.format(self._trigrams.N())
1721
+
1722
+ ##//////////////////////////////////////////////////////
1723
+ ## Probability Distribution Operations
1724
+ ##//////////////////////////////////////////////////////
1725
+
1726
+ def log_likelihood(test_pdist, actual_pdist):
1727
+ if (not isinstance(test_pdist, ProbDistI) or
1728
+ not isinstance(actual_pdist, ProbDistI)):
1729
+ raise ValueError('expected a ProbDist.')
1730
+ # Is this right?
1731
+ return sum(actual_pdist.prob(s) * math.log(test_pdist.prob(s), 2)
1732
+ for s in actual_pdist)
1733
+
1734
+ def entropy(pdist):
1735
+ probs = (pdist.prob(s) for s in pdist.samples())
1736
+ return -sum(p * math.log(p,2) for p in probs)
1737
+
1738
+ ##//////////////////////////////////////////////////////
1739
+ ## Conditional Distributions
1740
+ ##//////////////////////////////////////////////////////
1741
+
1742
+ @compat.python_2_unicode_compatible
1743
+ class ConditionalFreqDist(defaultdict):
1744
+ """
1745
+ A collection of frequency distributions for a single experiment
1746
+ run under different conditions. Conditional frequency
1747
+ distributions are used to record the number of times each sample
1748
+ occurred, given the condition under which the experiment was run.
1749
+ For example, a conditional frequency distribution could be used to
1750
+ record the frequency of each word (type) in a document, given its
1751
+ length. Formally, a conditional frequency distribution can be
1752
+ defined as a function that maps from each condition to the
1753
+ FreqDist for the experiment under that condition.
1754
+
1755
+ Conditional frequency distributions are typically constructed by
1756
+ repeatedly running an experiment under a variety of conditions,
1757
+ and incrementing the sample outcome counts for the appropriate
1758
+ conditions. For example, the following code will produce a
1759
+ conditional frequency distribution that encodes how often each
1760
+ word type occurs, given the length of that word type:
1761
+
1762
+ >>> from nltk.probability import ConditionalFreqDist
1763
+ >>> from nltk.tokenize import word_tokenize
1764
+ >>> sent = "the the the dog dog some other words that we do not care about"
1765
+ >>> cfdist = ConditionalFreqDist()
1766
+ >>> for word in word_tokenize(sent):
1767
+ ... condition = len(word)
1768
+ ... cfdist[condition][word] += 1
1769
+
1770
+ An equivalent way to do this is with the initializer:
1771
+
1772
+ >>> cfdist = ConditionalFreqDist((len(word), word) for word in word_tokenize(sent))
1773
+
1774
+ The frequency distribution for each condition is accessed using
1775
+ the indexing operator:
1776
+
1777
+ >>> cfdist[3]
1778
+ FreqDist({'the': 3, 'dog': 2, 'not': 1})
1779
+ >>> cfdist[3].freq('the')
1780
+ 0.5
1781
+ >>> cfdist[3]['dog']
1782
+ 2
1783
+
1784
+ When the indexing operator is used to access the frequency
1785
+ distribution for a condition that has not been accessed before,
1786
+ ``ConditionalFreqDist`` creates a new empty FreqDist for that
1787
+ condition.
1788
+
1789
+ """
1790
+ def __init__(self, cond_samples=None):
1791
+ """
1792
+ Construct a new empty conditional frequency distribution. In
1793
+ particular, the count for every sample, under every condition,
1794
+ is zero.
1795
+
1796
+ :param cond_samples: The samples to initialize the conditional
1797
+ frequency distribution with
1798
+ :type cond_samples: Sequence of (condition, sample) tuples
1799
+ """
1800
+ defaultdict.__init__(self, FreqDist)
1801
+
1802
+ if cond_samples:
1803
+ for (cond, sample) in cond_samples:
1804
+ self[cond][sample] += 1
1805
+
1806
+ def __reduce__(self):
1807
+ kv_pairs = ((cond, self[cond]) for cond in self.conditions())
1808
+ return (self.__class__, (), None, None, kv_pairs)
1809
+
1810
+ def conditions(self):
1811
+ """
1812
+ Return a list of the conditions that have been accessed for
1813
+ this ``ConditionalFreqDist``. Use the indexing operator to
1814
+ access the frequency distribution for a given condition.
1815
+ Note that the frequency distributions for some conditions
1816
+ may contain zero sample outcomes.
1817
+
1818
+ :rtype: list
1819
+ """
1820
+ return list(self.keys())
1821
+
1822
+ def N(self):
1823
+ """
1824
+ Return the total number of sample outcomes that have been
1825
+ recorded by this ``ConditionalFreqDist``.
1826
+
1827
+ :rtype: int
1828
+ """
1829
+ return sum(fdist.N() for fdist in itervalues(self))
1830
+
1831
+ def plot(self, *args, **kwargs):
1832
+ """
1833
+ Plot the given samples from the conditional frequency distribution.
1834
+ For a cumulative plot, specify cumulative=True.
1835
+ (Requires Matplotlib to be installed.)
1836
+
1837
+ :param samples: The samples to plot
1838
+ :type samples: list
1839
+ :param title: The title for the graph
1840
+ :type title: str
1841
+ :param conditions: The conditions to plot (default is all)
1842
+ :type conditions: list
1843
+ """
1844
+ try:
1845
+ from matplotlib import pylab
1846
+ except ImportError:
1847
+ raise ValueError('The plot function requires matplotlib to be installed.'
1848
+ 'See http://matplotlib.org/')
1849
+
1850
+ cumulative = _get_kwarg(kwargs, 'cumulative', False)
1851
+ conditions = _get_kwarg(kwargs, 'conditions', sorted(self.conditions()))
1852
+ title = _get_kwarg(kwargs, 'title', '')
1853
+ samples = _get_kwarg(kwargs, 'samples',
1854
+ sorted(set(v for c in conditions for v in self[c]))) # this computation could be wasted
1855
+ if not "linewidth" in kwargs:
1856
+ kwargs["linewidth"] = 2
1857
+
1858
+ for condition in conditions:
1859
+ if cumulative:
1860
+ freqs = list(self[condition]._cumulative_frequencies(samples))
1861
+ ylabel = "Cumulative Counts"
1862
+ legend_loc = 'lower right'
1863
+ else:
1864
+ freqs = [self[condition][sample] for sample in samples]
1865
+ ylabel = "Counts"
1866
+ legend_loc = 'upper right'
1867
+ # percents = [f * 100 for f in freqs] only in ConditionalProbDist?
1868
+ kwargs['label'] = "%s" % condition
1869
+ pylab.plot(freqs, *args, **kwargs)
1870
+
1871
+ pylab.legend(loc=legend_loc)
1872
+ pylab.grid(True, color="silver")
1873
+ pylab.xticks(range(len(samples)), [text_type(s) for s in samples], rotation=90)
1874
+ if title:
1875
+ pylab.title(title)
1876
+ pylab.xlabel("Samples")
1877
+ pylab.ylabel(ylabel)
1878
+ pylab.show()
1879
+
1880
+ def tabulate(self, *args, **kwargs):
1881
+ """
1882
+ Tabulate the given samples from the conditional frequency distribution.
1883
+
1884
+ :param samples: The samples to plot
1885
+ :type samples: list
1886
+ :param conditions: The conditions to plot (default is all)
1887
+ :type conditions: list
1888
+ :param cumulative: A flag to specify whether the freqs are cumulative (default = False)
1889
+ :type title: bool
1890
+ """
1891
+
1892
+ cumulative = _get_kwarg(kwargs, 'cumulative', False)
1893
+ conditions = _get_kwarg(kwargs, 'conditions', sorted(self.conditions()))
1894
+ samples = _get_kwarg(kwargs, 'samples',
1895
+ sorted(set(v for c in conditions for v in self[c]))) # this computation could be wasted
1896
+
1897
+ width = max(len("%s" % s) for s in samples)
1898
+ freqs = dict()
1899
+ for c in conditions:
1900
+ if cumulative:
1901
+ freqs[c] = list(self[c]._cumulative_frequencies(samples))
1902
+ else:
1903
+ freqs[c] = [self[c][sample] for sample in samples]
1904
+ width = max(width, max(len("%d" % f) for f in freqs[c]))
1905
+
1906
+ condition_size = max(len("%s" % c) for c in conditions)
1907
+ print(' ' * condition_size, end=' ')
1908
+ for s in samples:
1909
+ print("%*s" % (width, s), end=' ')
1910
+ print()
1911
+ for c in conditions:
1912
+ print("%*s" % (condition_size, c), end=' ')
1913
+ for f in freqs[c]:
1914
+ print("%*d" % (width, f), end=' ')
1915
+ print()
1916
+
1917
+ # Mathematical operators
1918
+
1919
+ def __add__(self, other):
1920
+ """
1921
+ Add counts from two ConditionalFreqDists.
1922
+ """
1923
+ if not isinstance(other, ConditionalFreqDist):
1924
+ return NotImplemented
1925
+ result = ConditionalFreqDist()
1926
+ for cond in self.conditions():
1927
+ newfreqdist = self[cond] + other[cond]
1928
+ if newfreqdist:
1929
+ result[cond] = newfreqdist
1930
+ for cond in other.conditions():
1931
+ if cond not in self.conditions():
1932
+ for elem, count in other[cond].items():
1933
+ if count > 0:
1934
+ result[cond][elem] = count
1935
+ return result
1936
+
1937
+ def __sub__(self, other):
1938
+ """
1939
+ Subtract count, but keep only results with positive counts.
1940
+ """
1941
+ if not isinstance(other, ConditionalFreqDist):
1942
+ return NotImplemented
1943
+ result = ConditionalFreqDist()
1944
+ for cond in self.conditions():
1945
+ newfreqdist = self[cond] - other[cond]
1946
+ if newfreqdist:
1947
+ result[cond] = newfreqdist
1948
+ for cond in other.conditions():
1949
+ if cond not in self.conditions():
1950
+ for elem, count in other[cond].items():
1951
+ if count < 0:
1952
+ result[cond][elem] = 0 - count
1953
+ return result
1954
+
1955
+ def __or__(self, other):
1956
+ """
1957
+ Union is the maximum of value in either of the input counters.
1958
+ """
1959
+ if not isinstance(other, ConditionalFreqDist):
1960
+ return NotImplemented
1961
+ result = ConditionalFreqDist()
1962
+ for cond in self.conditions():
1963
+ newfreqdist = self[cond] | other[cond]
1964
+ if newfreqdist:
1965
+ result[cond] = newfreqdist
1966
+ for cond in other.conditions():
1967
+ if cond not in self.conditions():
1968
+ for elem, count in other[cond].items():
1969
+ if count > 0:
1970
+ result[cond][elem] = count
1971
+ return result
1972
+
1973
+ def __and__(self, other):
1974
+ """
1975
+ Intersection is the minimum of corresponding counts.
1976
+ """
1977
+ if not isinstance(other, ConditionalFreqDist):
1978
+ return NotImplemented
1979
+ result = ConditionalFreqDist()
1980
+ for cond in self.conditions():
1981
+ newfreqdist = self[cond] & other[cond]
1982
+ if newfreqdist:
1983
+ result[cond] = newfreqdist
1984
+ return result
1985
+
1986
+ # @total_ordering doesn't work here, since the class inherits from a builtin class
1987
+ def __le__(self, other):
1988
+ if not isinstance(other, ConditionalFreqDist):
1989
+ raise_unorderable_types("<=", self, other)
1990
+ return set(self.conditions()).issubset(other.conditions()) \
1991
+ and all(self[c] <= other[c] for c in self.conditions())
1992
+ def __lt__(self, other):
1993
+ if not isinstance(other, ConditionalFreqDist):
1994
+ raise_unorderable_types("<", self, other)
1995
+ return self <= other and self != other
1996
+ def __ge__(self, other):
1997
+ if not isinstance(other, ConditionalFreqDist):
1998
+ raise_unorderable_types(">=", self, other)
1999
+ return other <= self
2000
+ def __gt__(self, other):
2001
+ if not isinstance(other, ConditionalFreqDist):
2002
+ raise_unorderable_types(">", self, other)
2003
+ return other < self
2004
+
2005
+ def __repr__(self):
2006
+ """
2007
+ Return a string representation of this ``ConditionalFreqDist``.
2008
+
2009
+ :rtype: str
2010
+ """
2011
+ return '<ConditionalFreqDist with %d conditions>' % len(self)
2012
+
2013
+
2014
+ @compat.python_2_unicode_compatible
2015
+ @add_metaclass(ABCMeta)
2016
+ class ConditionalProbDistI(dict):
2017
+ """
2018
+ A collection of probability distributions for a single experiment
2019
+ run under different conditions. Conditional probability
2020
+ distributions are used to estimate the likelihood of each sample,
2021
+ given the condition under which the experiment was run. For
2022
+ example, a conditional probability distribution could be used to
2023
+ estimate the probability of each word type in a document, given
2024
+ the length of the word type. Formally, a conditional probability
2025
+ distribution can be defined as a function that maps from each
2026
+ condition to the ``ProbDist`` for the experiment under that
2027
+ condition.
2028
+ """
2029
+ @abstractmethod
2030
+ def __init__(self):
2031
+ """
2032
+ Classes inheriting from ConditionalProbDistI should implement __init__.
2033
+ """
2034
+
2035
+ def conditions(self):
2036
+ """
2037
+ Return a list of the conditions that are represented by
2038
+ this ``ConditionalProbDist``. Use the indexing operator to
2039
+ access the probability distribution for a given condition.
2040
+
2041
+ :rtype: list
2042
+ """
2043
+ return list(self.keys())
2044
+
2045
+ def __repr__(self):
2046
+ """
2047
+ Return a string representation of this ``ConditionalProbDist``.
2048
+
2049
+ :rtype: str
2050
+ """
2051
+ return '<%s with %d conditions>' % (type(self).__name__, len(self))
2052
+
2053
+
2054
+ class ConditionalProbDist(ConditionalProbDistI):
2055
+ """
2056
+ A conditional probability distribution modeling the experiments
2057
+ that were used to generate a conditional frequency distribution.
2058
+ A ConditionalProbDist is constructed from a
2059
+ ``ConditionalFreqDist`` and a ``ProbDist`` factory:
2060
+
2061
+ - The ``ConditionalFreqDist`` specifies the frequency
2062
+ distribution for each condition.
2063
+ - The ``ProbDist`` factory is a function that takes a
2064
+ condition's frequency distribution, and returns its
2065
+ probability distribution. A ``ProbDist`` class's name (such as
2066
+ ``MLEProbDist`` or ``HeldoutProbDist``) can be used to specify
2067
+ that class's constructor.
2068
+
2069
+ The first argument to the ``ProbDist`` factory is the frequency
2070
+ distribution that it should model; and the remaining arguments are
2071
+ specified by the ``factory_args`` parameter to the
2072
+ ``ConditionalProbDist`` constructor. For example, the following
2073
+ code constructs a ``ConditionalProbDist``, where the probability
2074
+ distribution for each condition is an ``ELEProbDist`` with 10 bins:
2075
+
2076
+ >>> from nltk.corpus import brown
2077
+ >>> from nltk.probability import ConditionalFreqDist
2078
+ >>> from nltk.probability import ConditionalProbDist, ELEProbDist
2079
+ >>> cfdist = ConditionalFreqDist(brown.tagged_words()[:5000])
2080
+ >>> cpdist = ConditionalProbDist(cfdist, ELEProbDist, 10)
2081
+ >>> cpdist['passed'].max()
2082
+ 'VBD'
2083
+ >>> cpdist['passed'].prob('VBD')
2084
+ 0.423...
2085
+
2086
+ """
2087
+ def __init__(self, cfdist, probdist_factory,
2088
+ *factory_args, **factory_kw_args):
2089
+ """
2090
+ Construct a new conditional probability distribution, based on
2091
+ the given conditional frequency distribution and ``ProbDist``
2092
+ factory.
2093
+
2094
+ :type cfdist: ConditionalFreqDist
2095
+ :param cfdist: The ``ConditionalFreqDist`` specifying the
2096
+ frequency distribution for each condition.
2097
+ :type probdist_factory: class or function
2098
+ :param probdist_factory: The function or class that maps
2099
+ a condition's frequency distribution to its probability
2100
+ distribution. The function is called with the frequency
2101
+ distribution as its first argument,
2102
+ ``factory_args`` as its remaining arguments, and
2103
+ ``factory_kw_args`` as keyword arguments.
2104
+ :type factory_args: (any)
2105
+ :param factory_args: Extra arguments for ``probdist_factory``.
2106
+ These arguments are usually used to specify extra
2107
+ properties for the probability distributions of individual
2108
+ conditions, such as the number of bins they contain.
2109
+ :type factory_kw_args: (any)
2110
+ :param factory_kw_args: Extra keyword arguments for ``probdist_factory``.
2111
+ """
2112
+ self._probdist_factory = probdist_factory
2113
+ self._factory_args = factory_args
2114
+ self._factory_kw_args = factory_kw_args
2115
+
2116
+ for condition in cfdist:
2117
+ self[condition] = probdist_factory(cfdist[condition],
2118
+ *factory_args, **factory_kw_args)
2119
+
2120
+ def __missing__(self, key):
2121
+ self[key] = self._probdist_factory(FreqDist(),
2122
+ *self._factory_args,
2123
+ **self._factory_kw_args)
2124
+ return self[key]
2125
+
2126
+ class DictionaryConditionalProbDist(ConditionalProbDistI):
2127
+ """
2128
+ An alternative ConditionalProbDist that simply wraps a dictionary of
2129
+ ProbDists rather than creating these from FreqDists.
2130
+ """
2131
+
2132
+ def __init__(self, probdist_dict):
2133
+ """
2134
+ :param probdist_dict: a dictionary containing the probdists indexed
2135
+ by the conditions
2136
+ :type probdist_dict: dict any -> probdist
2137
+ """
2138
+ self.update(probdist_dict)
2139
+
2140
+ def __missing__(self, key):
2141
+ self[key] = DictionaryProbDist()
2142
+ return self[key]
2143
+
2144
+ ##//////////////////////////////////////////////////////
2145
+ ## Adding in log-space.
2146
+ ##//////////////////////////////////////////////////////
2147
+
2148
+ # If the difference is bigger than this, then just take the bigger one:
2149
+ _ADD_LOGS_MAX_DIFF = math.log(1e-30, 2)
2150
+
2151
+ def add_logs(logx, logy):
2152
+ """
2153
+ Given two numbers ``logx`` = *log(x)* and ``logy`` = *log(y)*, return
2154
+ *log(x+y)*. Conceptually, this is the same as returning
2155
+ ``log(2**(logx)+2**(logy))``, but the actual implementation
2156
+ avoids overflow errors that could result from direct computation.
2157
+ """
2158
+ if (logx < logy + _ADD_LOGS_MAX_DIFF):
2159
+ return logy
2160
+ if (logy < logx + _ADD_LOGS_MAX_DIFF):
2161
+ return logx
2162
+ base = min(logx, logy)
2163
+ return base + math.log(2**(logx-base) + 2**(logy-base), 2)
2164
+
2165
+ def sum_logs(logs):
2166
+ return (reduce(add_logs, logs[1:], logs[0]) if len(logs) != 0 else _NINF)
2167
+
2168
+ ##//////////////////////////////////////////////////////
2169
+ ## Probabilistic Mix-in
2170
+ ##//////////////////////////////////////////////////////
2171
+
2172
+ class ProbabilisticMixIn(object):
2173
+ """
2174
+ A mix-in class to associate probabilities with other classes
2175
+ (trees, rules, etc.). To use the ``ProbabilisticMixIn`` class,
2176
+ define a new class that derives from an existing class and from
2177
+ ProbabilisticMixIn. You will need to define a new constructor for
2178
+ the new class, which explicitly calls the constructors of both its
2179
+ parent classes. For example:
2180
+
2181
+ >>> from nltk.probability import ProbabilisticMixIn
2182
+ >>> class A:
2183
+ ... def __init__(self, x, y): self.data = (x,y)
2184
+ ...
2185
+ >>> class ProbabilisticA(A, ProbabilisticMixIn):
2186
+ ... def __init__(self, x, y, **prob_kwarg):
2187
+ ... A.__init__(self, x, y)
2188
+ ... ProbabilisticMixIn.__init__(self, **prob_kwarg)
2189
+
2190
+ See the documentation for the ProbabilisticMixIn
2191
+ ``constructor<__init__>`` for information about the arguments it
2192
+ expects.
2193
+
2194
+ You should generally also redefine the string representation
2195
+ methods, the comparison methods, and the hashing method.
2196
+ """
2197
+ def __init__(self, **kwargs):
2198
+ """
2199
+ Initialize this object's probability. This initializer should
2200
+ be called by subclass constructors. ``prob`` should generally be
2201
+ the first argument for those constructors.
2202
+
2203
+ :param prob: The probability associated with the object.
2204
+ :type prob: float
2205
+ :param logprob: The log of the probability associated with
2206
+ the object.
2207
+ :type logprob: float
2208
+ """
2209
+ if 'prob' in kwargs:
2210
+ if 'logprob' in kwargs:
2211
+ raise TypeError('Must specify either prob or logprob '
2212
+ '(not both)')
2213
+ else:
2214
+ ProbabilisticMixIn.set_prob(self, kwargs['prob'])
2215
+ elif 'logprob' in kwargs:
2216
+ ProbabilisticMixIn.set_logprob(self, kwargs['logprob'])
2217
+ else:
2218
+ self.__prob = self.__logprob = None
2219
+
2220
+ def set_prob(self, prob):
2221
+ """
2222
+ Set the probability associated with this object to ``prob``.
2223
+
2224
+ :param prob: The new probability
2225
+ :type prob: float
2226
+ """
2227
+ self.__prob = prob
2228
+ self.__logprob = None
2229
+
2230
+ def set_logprob(self, logprob):
2231
+ """
2232
+ Set the log probability associated with this object to
2233
+ ``logprob``. I.e., set the probability associated with this
2234
+ object to ``2**(logprob)``.
2235
+
2236
+ :param logprob: The new log probability
2237
+ :type logprob: float
2238
+ """
2239
+ self.__logprob = logprob
2240
+ self.__prob = None
2241
+
2242
+ def prob(self):
2243
+ """
2244
+ Return the probability associated with this object.
2245
+
2246
+ :rtype: float
2247
+ """
2248
+ if self.__prob is None:
2249
+ if self.__logprob is None: return None
2250
+ self.__prob = 2**(self.__logprob)
2251
+ return self.__prob
2252
+
2253
+ def logprob(self):
2254
+ """
2255
+ Return ``log(p)``, where ``p`` is the probability associated
2256
+ with this object.
2257
+
2258
+ :rtype: float
2259
+ """
2260
+ if self.__logprob is None:
2261
+ if self.__prob is None: return None
2262
+ self.__logprob = math.log(self.__prob, 2)
2263
+ return self.__logprob
2264
+
2265
+ class ImmutableProbabilisticMixIn(ProbabilisticMixIn):
2266
+ def set_prob(self, prob):
2267
+ raise ValueError('%s is immutable' % self.__class__.__name__)
2268
+ def set_logprob(self, prob):
2269
+ raise ValueError('%s is immutable' % self.__class__.__name__)
2270
+
2271
+ ## Helper function for processing keyword arguments
2272
+
2273
+ def _get_kwarg(kwargs, key, default):
2274
+ if key in kwargs:
2275
+ arg = kwargs[key]
2276
+ del kwargs[key]
2277
+ else:
2278
+ arg = default
2279
+ return arg
2280
+
2281
+ ##//////////////////////////////////////////////////////
2282
+ ## Demonstration
2283
+ ##//////////////////////////////////////////////////////
2284
+
2285
+ def _create_rand_fdist(numsamples, numoutcomes):
2286
+ """
2287
+ Create a new frequency distribution, with random samples. The
2288
+ samples are numbers from 1 to ``numsamples``, and are generated by
2289
+ summing two numbers, each of which has a uniform distribution.
2290
+ """
2291
+ import random
2292
+ fdist = FreqDist()
2293
+ for x in range(numoutcomes):
2294
+ y = (random.randint(1, (1 + numsamples) // 2) +
2295
+ random.randint(0, numsamples // 2))
2296
+ fdist[y] += 1
2297
+ return fdist
2298
+
2299
+ def _create_sum_pdist(numsamples):
2300
+ """
2301
+ Return the true probability distribution for the experiment
2302
+ ``_create_rand_fdist(numsamples, x)``.
2303
+ """
2304
+ fdist = FreqDist()
2305
+ for x in range(1, (1 + numsamples) // 2 + 1):
2306
+ for y in range(0, numsamples // 2 + 1):
2307
+ fdist[x+y] += 1
2308
+ return MLEProbDist(fdist)
2309
+
2310
+ def demo(numsamples=6, numoutcomes=500):
2311
+ """
2312
+ A demonstration of frequency distributions and probability
2313
+ distributions. This demonstration creates three frequency
2314
+ distributions with, and uses them to sample a random process with
2315
+ ``numsamples`` samples. Each frequency distribution is sampled
2316
+ ``numoutcomes`` times. These three frequency distributions are
2317
+ then used to build six probability distributions. Finally, the
2318
+ probability estimates of these distributions are compared to the
2319
+ actual probability of each sample.
2320
+
2321
+ :type numsamples: int
2322
+ :param numsamples: The number of samples to use in each demo
2323
+ frequency distributions.
2324
+ :type numoutcomes: int
2325
+ :param numoutcomes: The total number of outcomes for each
2326
+ demo frequency distribution. These outcomes are divided into
2327
+ ``numsamples`` bins.
2328
+ :rtype: None
2329
+ """
2330
+
2331
+ # Randomly sample a stochastic process three times.
2332
+ fdist1 = _create_rand_fdist(numsamples, numoutcomes)
2333
+ fdist2 = _create_rand_fdist(numsamples, numoutcomes)
2334
+ fdist3 = _create_rand_fdist(numsamples, numoutcomes)
2335
+
2336
+ # Use our samples to create probability distributions.
2337
+ pdists = [
2338
+ MLEProbDist(fdist1),
2339
+ LidstoneProbDist(fdist1, 0.5, numsamples),
2340
+ HeldoutProbDist(fdist1, fdist2, numsamples),
2341
+ HeldoutProbDist(fdist2, fdist1, numsamples),
2342
+ CrossValidationProbDist([fdist1, fdist2, fdist3], numsamples),
2343
+ SimpleGoodTuringProbDist(fdist1),
2344
+ SimpleGoodTuringProbDist(fdist1, 7),
2345
+ _create_sum_pdist(numsamples),
2346
+ ]
2347
+
2348
+ # Find the probability of each sample.
2349
+ vals = []
2350
+ for n in range(1,numsamples+1):
2351
+ vals.append(tuple([n, fdist1.freq(n)] +
2352
+ [pdist.prob(n) for pdist in pdists]))
2353
+
2354
+ # Print the results in a formatted table.
2355
+ print(('%d samples (1-%d); %d outcomes were sampled for each FreqDist' %
2356
+ (numsamples, numsamples, numoutcomes)))
2357
+ print('='*9*(len(pdists)+2))
2358
+ FORMATSTR = ' FreqDist '+ '%8s '*(len(pdists)-1) + '| Actual'
2359
+ print(FORMATSTR % tuple(repr(pdist)[1:9] for pdist in pdists[:-1]))
2360
+ print('-'*9*(len(pdists)+2))
2361
+ FORMATSTR = '%3d %8.6f ' + '%8.6f '*(len(pdists)-1) + '| %8.6f'
2362
+ for val in vals:
2363
+ print(FORMATSTR % val)
2364
+
2365
+ # Print the totals for each column (should all be 1.0)
2366
+ zvals = list(zip(*vals))
2367
+ sums = [sum(val) for val in zvals[1:]]
2368
+ print('-'*9*(len(pdists)+2))
2369
+ FORMATSTR = 'Total ' + '%8.6f '*(len(pdists)) + '| %8.6f'
2370
+ print(FORMATSTR % tuple(sums))
2371
+ print('='*9*(len(pdists)+2))
2372
+
2373
+ # Display the distributions themselves, if they're short enough.
2374
+ if len("%s" % fdist1) < 70:
2375
+ print(' fdist1: %s' % fdist1)
2376
+ print(' fdist2: %s' % fdist2)
2377
+ print(' fdist3: %s' % fdist3)
2378
+ print()
2379
+
2380
+ print('Generating:')
2381
+ for pdist in pdists:
2382
+ fdist = FreqDist(pdist.generate() for i in range(5000))
2383
+ print('%20s %s' % (pdist.__class__.__name__[:20], ("%s" % fdist)[:55]))
2384
+ print()
2385
+
2386
+ def gt_demo():
2387
+ from nltk import corpus
2388
+ emma_words = corpus.gutenberg.words('austen-emma.txt')
2389
+ fd = FreqDist(emma_words)
2390
+ sgt = SimpleGoodTuringProbDist(fd)
2391
+ print('%18s %8s %14s' \
2392
+ % ("word", "freqency", "SimpleGoodTuring"))
2393
+ fd_keys_sorted=(key for key, value in sorted(fd.items(), key=lambda item: item[1], reverse=True))
2394
+ for key in fd_keys_sorted:
2395
+ print('%18s %8d %14e' \
2396
+ % (key, fd[key], sgt.prob(key)))
2397
+
2398
+ if __name__ == '__main__':
2399
+ demo(6, 10)
2400
+ demo(5, 5000)
2401
+ gt_demo()
2402
+
2403
+ __all__ = ['ConditionalFreqDist', 'ConditionalProbDist',
2404
+ 'ConditionalProbDistI', 'CrossValidationProbDist',
2405
+ 'DictionaryConditionalProbDist', 'DictionaryProbDist', 'ELEProbDist',
2406
+ 'FreqDist', 'SimpleGoodTuringProbDist', 'HeldoutProbDist',
2407
+ 'ImmutableProbabilisticMixIn', 'LaplaceProbDist', 'LidstoneProbDist',
2408
+ 'MLEProbDist', 'MutableProbDist', 'KneserNeyProbDist', 'ProbDistI', 'ProbabilisticMixIn',
2409
+ 'UniformProbDist', 'WittenBellProbDist', 'add_logs',
2410
+ 'log_likelihood', 'sum_logs', 'entropy']