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,1492 +1,1510 @@
1
- # Natural Language Toolkit: Maximum Entropy Classifiers
2
- #
3
- # Copyright (C) 2001-2015 NLTK Project
4
- # Author: Edward Loper <edloper@gmail.com>
5
- # Dmitry Chichkov <dchichkov@gmail.com> (TypedMaxentFeatureEncoding)
6
- # URL: <http://nltk.org/>
7
- # For license information, see LICENSE.TXT
8
-
9
- """
10
- A classifier model based on maximum entropy modeling framework. This
11
- framework considers all of the probability distributions that are
12
- empirically consistent with the training data; and chooses the
13
- distribution with the highest entropy. A probability distribution is
14
- "empirically consistent" with a set of training data if its estimated
15
- frequency with which a class and a feature vector value co-occur is
16
- equal to the actual frequency in the data.
17
-
18
- Terminology: 'feature'
19
- ======================
20
- The term *feature* is usually used to refer to some property of an
21
- unlabeled token. For example, when performing word sense
22
- disambiguation, we might define a ``'prevword'`` feature whose value is
23
- the word preceding the target word. However, in the context of
24
- maxent modeling, the term *feature* is typically used to refer to a
25
- property of a "labeled" token. In order to prevent confusion, we
26
- will introduce two distinct terms to disambiguate these two different
27
- concepts:
28
-
29
- - An "input-feature" is a property of an unlabeled token.
30
- - A "joint-feature" is a property of a labeled token.
31
-
32
- In the rest of the ``nltk.classify`` module, the term "features" is
33
- used to refer to what we will call "input-features" in this module.
34
-
35
- In literature that describes and discusses maximum entropy models,
36
- input-features are typically called "contexts", and joint-features
37
- are simply referred to as "features".
38
-
39
- Converting Input-Features to Joint-Features
40
- -------------------------------------------
41
- In maximum entropy models, joint-features are required to have numeric
42
- values. Typically, each input-feature ``input_feat`` is mapped to a
43
- set of joint-features of the form:
44
-
45
- | joint_feat(token, label) = { 1 if input_feat(token) == feat_val
46
- | { and label == some_label
47
- | {
48
- | { 0 otherwise
49
-
50
- For all values of ``feat_val`` and ``some_label``. This mapping is
51
- performed by classes that implement the ``MaxentFeatureEncodingI``
52
- interface.
53
- """
54
- from __future__ import print_function, unicode_literals
55
- __docformat__ = 'epytext en'
56
-
57
- try:
58
- import numpy
59
- except ImportError:
60
- pass
61
-
62
- import tempfile
63
- import os
64
- from collections import defaultdict
65
-
66
- from nltk import compat
67
- from nltk.data import gzip_open_unicode
68
- from nltk.util import OrderedDict
69
- from nltk.probability import DictionaryProbDist
70
-
71
- from nltk.classify.api import ClassifierI
72
- from nltk.classify.util import CutoffChecker, accuracy, log_likelihood
73
- from nltk.classify.megam import (call_megam,
74
- write_megam_file, parse_megam_weights)
75
- from nltk.classify.tadm import call_tadm, write_tadm_file, parse_tadm_weights
76
-
77
- ######################################################################
78
- #{ Classifier Model
79
- ######################################################################
80
-
81
- @compat.python_2_unicode_compatible
82
- class MaxentClassifier(ClassifierI):
83
- """
84
- A maximum entropy classifier (also known as a "conditional
85
- exponential classifier"). This classifier is parameterized by a
86
- set of "weights", which are used to combine the joint-features
87
- that are generated from a featureset by an "encoding". In
88
- particular, the encoding maps each ``(featureset, label)`` pair to
89
- a vector. The probability of each label is then computed using
90
- the following equation::
91
-
92
- dotprod(weights, encode(fs,label))
93
- prob(fs|label) = ---------------------------------------------------
94
- sum(dotprod(weights, encode(fs,l)) for l in labels)
95
-
96
- Where ``dotprod`` is the dot product::
97
-
98
- dotprod(a,b) = sum(x*y for (x,y) in zip(a,b))
99
- """
100
- def __init__(self, encoding, weights, logarithmic=True):
101
- """
102
- Construct a new maxent classifier model. Typically, new
103
- classifier models are created using the ``train()`` method.
104
-
105
- :type encoding: MaxentFeatureEncodingI
106
- :param encoding: An encoding that is used to convert the
107
- featuresets that are given to the ``classify`` method into
108
- joint-feature vectors, which are used by the maxent
109
- classifier model.
110
-
111
- :type weights: list of float
112
- :param weights: The feature weight vector for this classifier.
113
-
114
- :type logarithmic: bool
115
- :param logarithmic: If false, then use non-logarithmic weights.
116
- """
117
- self._encoding = encoding
118
- self._weights = weights
119
- self._logarithmic = logarithmic
120
- #self._logarithmic = False
121
- assert encoding.length() == len(weights)
122
-
123
- def labels(self):
124
- return self._encoding.labels()
125
-
126
- def set_weights(self, new_weights):
127
- """
128
- Set the feature weight vector for this classifier.
129
- :param new_weights: The new feature weight vector.
130
- :type new_weights: list of float
131
- """
132
- self._weights = new_weights
133
- assert self._encoding.length() == len(new_weights)
134
-
135
- def weights(self):
136
- """
137
- :return: The feature weight vector for this classifier.
138
- :rtype: list of float
139
- """
140
- return self._weights
141
-
142
- def classify(self, featureset):
143
- return self.prob_classify(featureset).max()
144
-
145
- def prob_classify(self, featureset):
146
- prob_dict = {}
147
- for label in self._encoding.labels():
148
- feature_vector = self._encoding.encode(featureset, label)
149
-
150
- if self._logarithmic:
151
- total = 0.0
152
- for (f_id, f_val) in feature_vector:
153
- total += self._weights[f_id] * f_val
154
- prob_dict[label] = total
155
-
156
- else:
157
- prod = 1.0
158
- for (f_id, f_val) in feature_vector:
159
- prod *= self._weights[f_id] ** f_val
160
- prob_dict[label] = prod
161
-
162
- # Normalize the dictionary to give a probability distribution
163
- return DictionaryProbDist(prob_dict, log=self._logarithmic,
164
- normalize=True)
165
-
166
- def explain(self, featureset, columns=4):
167
- """
168
- Print a table showing the effect of each of the features in
169
- the given feature set, and how they combine to determine the
170
- probabilities of each label for that featureset.
171
- """
172
- descr_width = 50
173
- TEMPLATE = ' %-'+str(descr_width-2)+'s%s%8.3f'
174
-
175
- pdist = self.prob_classify(featureset)
176
- labels = sorted(pdist.samples(), key=pdist.prob, reverse=True)
177
- labels = labels[:columns]
178
- print(' Feature'.ljust(descr_width)+''.join(
179
- '%8s' % (("%s" % l)[:7]) for l in labels))
180
- print(' '+'-'*(descr_width-2+8*len(labels)))
181
- sums = defaultdict(int)
182
- for i, label in enumerate(labels):
183
- feature_vector = self._encoding.encode(featureset, label)
184
- feature_vector.sort(key=lambda fid__: abs(self._weights[fid__[0]]),
185
- reverse=True)
186
- for (f_id, f_val) in feature_vector:
187
- if self._logarithmic:
188
- score = self._weights[f_id] * f_val
189
- else: score = self._weights[f_id] ** f_val
190
- descr = self._encoding.describe(f_id)
191
- descr = descr.split(' and label is ')[0] # hack
192
- descr += ' (%s)' % f_val # hack
193
- if len(descr) > 47:
194
- descr = descr[:44]+'...'
195
- print(TEMPLATE % (descr, i*8*' ', score))
196
- sums[label] += score
197
- print(' '+'-'*(descr_width-1+8*len(labels)))
198
- print(' TOTAL:'.ljust(descr_width)+''.join(
199
- '%8.3f' % sums[l] for l in labels))
200
- print(' PROBS:'.ljust(descr_width)+''.join(
201
- '%8.3f' % pdist.prob(l) for l in labels))
202
-
203
- def show_most_informative_features(self, n=10, show='all'):
204
- """
205
- :param show: all, neg, or pos (for negative-only or positive-only)
206
- """
207
- fids = sorted(list(range(len(self._weights))),
208
- key=lambda fid: abs(self._weights[fid]),
209
- reverse=True)
210
- if show == 'pos':
211
- fids = [fid for fid in fids if self._weights[fid] > 0]
212
- elif show == 'neg':
213
- fids = [fid for fid in fids if self._weights[fid] < 0]
214
- for fid in fids[:n]:
215
- print('%8.3f %s' % (self._weights[fid],
216
- self._encoding.describe(fid)))
217
-
218
- def __repr__(self):
219
- return ('<ConditionalExponentialClassifier: %d labels, %d features>' %
220
- (len(self._encoding.labels()), self._encoding.length()))
221
-
222
- #: A list of the algorithm names that are accepted for the
223
- #: ``train()`` method's ``algorithm`` parameter.
224
- ALGORITHMS = ['GIS', 'IIS', 'MEGAM', 'TADM']
225
-
226
- @classmethod
227
- def train(cls, train_toks, algorithm=None, trace=3, encoding=None,
228
- labels=None, gaussian_prior_sigma=0, **cutoffs):
229
- """
230
- Train a new maxent classifier based on the given corpus of
231
- training samples. This classifier will have its weights
232
- chosen to maximize entropy while remaining empirically
233
- consistent with the training corpus.
234
-
235
- :rtype: MaxentClassifier
236
- :return: The new maxent classifier
237
-
238
- :type train_toks: list
239
- :param train_toks: Training data, represented as a list of
240
- pairs, the first member of which is a featureset,
241
- and the second of which is a classification label.
242
-
243
- :type algorithm: str
244
- :param algorithm: A case-insensitive string, specifying which
245
- algorithm should be used to train the classifier. The
246
- following algorithms are currently available.
247
-
248
- - Iterative Scaling Methods: Generalized Iterative Scaling (``'GIS'``),
249
- Improved Iterative Scaling (``'IIS'``)
250
- - External Libraries (requiring megam):
251
- LM-BFGS algorithm, with training performed by Megam (``'megam'``)
252
-
253
- The default algorithm is ``'IIS'``.
254
-
255
- :type trace: int
256
- :param trace: The level of diagnostic tracing output to produce.
257
- Higher values produce more verbose output.
258
- :type encoding: MaxentFeatureEncodingI
259
- :param encoding: A feature encoding, used to convert featuresets
260
- into feature vectors. If none is specified, then a
261
- ``BinaryMaxentFeatureEncoding`` will be built based on the
262
- features that are attested in the training corpus.
263
- :type labels: list(str)
264
- :param labels: The set of possible labels. If none is given, then
265
- the set of all labels attested in the training data will be
266
- used instead.
267
- :param gaussian_prior_sigma: The sigma value for a gaussian
268
- prior on model weights. Currently, this is supported by
269
- ``megam``. For other algorithms, its value is ignored.
270
- :param cutoffs: Arguments specifying various conditions under
271
- which the training should be halted. (Some of the cutoff
272
- conditions are not supported by some algorithms.)
273
-
274
- - ``max_iter=v``: Terminate after ``v`` iterations.
275
- - ``min_ll=v``: Terminate after the negative average
276
- log-likelihood drops under ``v``.
277
- - ``min_lldelta=v``: Terminate if a single iteration improves
278
- log likelihood by less than ``v``.
279
- """
280
- if algorithm is None:
281
- algorithm = 'iis'
282
- for key in cutoffs:
283
- if key not in ('max_iter', 'min_ll', 'min_lldelta',
284
- 'max_acc', 'min_accdelta', 'count_cutoff',
285
- 'norm', 'explicit', 'bernoulli'):
286
- raise TypeError('Unexpected keyword arg %r' % key)
287
- algorithm = algorithm.lower()
288
- if algorithm == 'iis':
289
- return train_maxent_classifier_with_iis(
290
- train_toks, trace, encoding, labels, **cutoffs)
291
- elif algorithm == 'gis':
292
- return train_maxent_classifier_with_gis(
293
- train_toks, trace, encoding, labels, **cutoffs)
294
- elif algorithm == 'megam':
295
- return train_maxent_classifier_with_megam(
296
- train_toks, trace, encoding, labels,
297
- gaussian_prior_sigma, **cutoffs)
298
- elif algorithm == 'tadm':
299
- kwargs = cutoffs
300
- kwargs['trace'] = trace
301
- kwargs['encoding'] = encoding
302
- kwargs['labels'] = labels
303
- kwargs['gaussian_prior_sigma'] = gaussian_prior_sigma
304
- return TadmMaxentClassifier.train(train_toks, **kwargs)
305
- else:
306
- raise ValueError('Unknown algorithm %s' % algorithm)
307
-
308
-
309
- #: Alias for MaxentClassifier.
310
- ConditionalExponentialClassifier = MaxentClassifier
311
-
312
-
313
- ######################################################################
314
- #{ Feature Encodings
315
- ######################################################################
316
-
317
- class MaxentFeatureEncodingI(object):
318
- """
319
- A mapping that converts a set of input-feature values to a vector
320
- of joint-feature values, given a label. This conversion is
321
- necessary to translate featuresets into a format that can be used
322
- by maximum entropy models.
323
-
324
- The set of joint-features used by a given encoding is fixed, and
325
- each index in the generated joint-feature vectors corresponds to a
326
- single joint-feature. The length of the generated joint-feature
327
- vectors is therefore constant (for a given encoding).
328
-
329
- Because the joint-feature vectors generated by
330
- ``MaxentFeatureEncodingI`` are typically very sparse, they are
331
- represented as a list of ``(index, value)`` tuples, specifying the
332
- value of each non-zero joint-feature.
333
-
334
- Feature encodings are generally created using the ``train()``
335
- method, which generates an appropriate encoding based on the
336
- input-feature values and labels that are present in a given
337
- corpus.
338
- """
339
- def encode(self, featureset, label):
340
- """
341
- Given a (featureset, label) pair, return the corresponding
342
- vector of joint-feature values. This vector is represented as
343
- a list of ``(index, value)`` tuples, specifying the value of
344
- each non-zero joint-feature.
345
-
346
- :type featureset: dict
347
- :rtype: list(tuple(int, int))
348
- """
349
- raise NotImplementedError()
350
-
351
- def length(self):
352
- """
353
- :return: The size of the fixed-length joint-feature vectors
354
- that are generated by this encoding.
355
- :rtype: int
356
- """
357
- raise NotImplementedError()
358
-
359
- def labels(self):
360
- """
361
- :return: A list of the \"known labels\" -- i.e., all labels
362
- ``l`` such that ``self.encode(fs,l)`` can be a nonzero
363
- joint-feature vector for some value of ``fs``.
364
- :rtype: list
365
- """
366
- raise NotImplementedError()
367
-
368
- def describe(self, fid):
369
- """
370
- :return: A string describing the value of the joint-feature
371
- whose index in the generated feature vectors is ``fid``.
372
- :rtype: str
373
- """
374
- raise NotImplementedError()
375
-
376
- def train(cls, train_toks):
377
- """
378
- Construct and return new feature encoding, based on a given
379
- training corpus ``train_toks``.
380
-
381
- :type train_toks: list(tuple(dict, str))
382
- :param train_toks: Training data, represented as a list of
383
- pairs, the first member of which is a feature dictionary,
384
- and the second of which is a classification label.
385
- """
386
- raise NotImplementedError()
387
-
388
- class FunctionBackedMaxentFeatureEncoding(MaxentFeatureEncodingI):
389
- """
390
- A feature encoding that calls a user-supplied function to map a
391
- given featureset/label pair to a sparse joint-feature vector.
392
- """
393
- def __init__(self, func, length, labels):
394
- """
395
- Construct a new feature encoding based on the given function.
396
-
397
- :type func: (callable)
398
- :param func: A function that takes two arguments, a featureset
399
- and a label, and returns the sparse joint feature vector
400
- that encodes them::
401
-
402
- func(featureset, label) -> feature_vector
403
-
404
- This sparse joint feature vector (``feature_vector``) is a
405
- list of ``(index,value)`` tuples.
406
-
407
- :type length: int
408
- :param length: The size of the fixed-length joint-feature
409
- vectors that are generated by this encoding.
410
-
411
- :type labels: list
412
- :param labels: A list of the \"known labels\" for this
413
- encoding -- i.e., all labels ``l`` such that
414
- ``self.encode(fs,l)`` can be a nonzero joint-feature vector
415
- for some value of ``fs``.
416
- """
417
- self._length = length
418
- self._func = func
419
- self._labels = labels
420
-
421
- def encode(self, featureset, label):
422
- return self._func(featureset, label)
423
-
424
- def length(self):
425
- return self._length
426
-
427
- def labels(self):
428
- return self._labels
429
-
430
- def describe(self, fid):
431
- return 'no description available'
432
-
433
- class BinaryMaxentFeatureEncoding(MaxentFeatureEncodingI):
434
- """
435
- A feature encoding that generates vectors containing a binary
436
- joint-features of the form:
437
-
438
- | joint_feat(fs, l) = { 1 if (fs[fname] == fval) and (l == label)
439
- | {
440
- | { 0 otherwise
441
-
442
- Where ``fname`` is the name of an input-feature, ``fval`` is a value
443
- for that input-feature, and ``label`` is a label.
444
-
445
- Typically, these features are constructed based on a training
446
- corpus, using the ``train()`` method. This method will create one
447
- feature for each combination of ``fname``, ``fval``, and ``label``
448
- that occurs at least once in the training corpus.
449
-
450
- The ``unseen_features`` parameter can be used to add "unseen-value
451
- features", which are used whenever an input feature has a value
452
- that was not encountered in the training corpus. These features
453
- have the form:
454
-
455
- | joint_feat(fs, l) = { 1 if is_unseen(fname, fs[fname])
456
- | { and l == label
457
- | {
458
- | { 0 otherwise
459
-
460
- Where ``is_unseen(fname, fval)`` is true if the encoding does not
461
- contain any joint features that are true when ``fs[fname]==fval``.
462
-
463
- The ``alwayson_features`` parameter can be used to add "always-on
464
- features", which have the form::
465
-
466
- | joint_feat(fs, l) = { 1 if (l == label)
467
- | {
468
- | { 0 otherwise
469
-
470
- These always-on features allow the maxent model to directly model
471
- the prior probabilities of each label.
472
- """
473
- def __init__(self, labels, mapping, unseen_features=False,
474
- alwayson_features=False):
475
- """
476
- :param labels: A list of the \"known labels\" for this encoding.
477
-
478
- :param mapping: A dictionary mapping from ``(fname,fval,label)``
479
- tuples to corresponding joint-feature indexes. These
480
- indexes must be the set of integers from 0...len(mapping).
481
- If ``mapping[fname,fval,label]=id``, then
482
- ``self.encode(..., fname:fval, ..., label)[id]`` is 1;
483
- otherwise, it is 0.
484
-
485
- :param unseen_features: If true, then include unseen value
486
- features in the generated joint-feature vectors.
487
-
488
- :param alwayson_features: If true, then include always-on
489
- features in the generated joint-feature vectors.
490
- """
491
- if set(mapping.values()) != set(range(len(mapping))):
492
- raise ValueError('Mapping values must be exactly the '
493
- 'set of integers from 0...len(mapping)')
494
-
495
- self._labels = list(labels)
496
- """A list of attested labels."""
497
-
498
- self._mapping = mapping
499
- """dict mapping from (fname,fval,label) -> fid"""
500
-
501
- self._length = len(mapping)
502
- """The length of generated joint feature vectors."""
503
-
504
- self._alwayson = None
505
- """dict mapping from label -> fid"""
506
-
507
- self._unseen = None
508
- """dict mapping from fname -> fid"""
509
-
510
- if alwayson_features:
511
- self._alwayson = dict((label, i+self._length)
512
- for (i, label) in enumerate(labels))
513
- self._length += len(self._alwayson)
514
-
515
- if unseen_features:
516
- fnames = set(fname for (fname, fval, label) in mapping)
517
- self._unseen = dict((fname, i+self._length)
518
- for (i, fname) in enumerate(fnames))
519
- self._length += len(fnames)
520
-
521
- def encode(self, featureset, label):
522
- # Inherit docs.
523
- encoding = []
524
-
525
- # Convert input-features to joint-features:
526
- for fname, fval in featureset.items():
527
- # Known feature name & value:
528
- if (fname, fval, label) in self._mapping:
529
- encoding.append((self._mapping[fname, fval, label], 1))
530
-
531
- # Otherwise, we might want to fire an "unseen-value feature".
532
- elif self._unseen:
533
- # Have we seen this fname/fval combination with any label?
534
- for label2 in self._labels:
535
- if (fname, fval, label2) in self._mapping:
536
- break # we've seen this fname/fval combo
537
- # We haven't -- fire the unseen-value feature
538
- else:
539
- if fname in self._unseen:
540
- encoding.append((self._unseen[fname], 1))
541
-
542
- # Add always-on features:
543
- if self._alwayson and label in self._alwayson:
544
- encoding.append((self._alwayson[label], 1))
545
-
546
- return encoding
547
-
548
- def describe(self, f_id):
549
- # Inherit docs.
550
- if not isinstance(f_id, compat.integer_types):
551
- raise TypeError('describe() expected an int')
552
- try:
553
- self._inv_mapping
554
- except AttributeError:
555
- self._inv_mapping = [-1]*len(self._mapping)
556
- for (info, i) in self._mapping.items():
557
- self._inv_mapping[i] = info
558
-
559
- if f_id < len(self._mapping):
560
- (fname, fval, label) = self._inv_mapping[f_id]
561
- return '%s==%r and label is %r' % (fname, fval, label)
562
- elif self._alwayson and f_id in self._alwayson.values():
563
- for (label, f_id2) in self._alwayson.items():
564
- if f_id == f_id2:
565
- return 'label is %r' % label
566
- elif self._unseen and f_id in self._unseen.values():
567
- for (fname, f_id2) in self._unseen.items():
568
- if f_id == f_id2:
569
- return '%s is unseen' % fname
570
- else:
571
- raise ValueError('Bad feature id')
572
-
573
- def labels(self):
574
- # Inherit docs.
575
- return self._labels
576
-
577
- def length(self):
578
- # Inherit docs.
579
- return self._length
580
-
581
- @classmethod
582
- def train(cls, train_toks, count_cutoff=0, labels=None, **options):
583
- """
584
- Construct and return new feature encoding, based on a given
585
- training corpus ``train_toks``. See the class description
586
- ``BinaryMaxentFeatureEncoding`` for a description of the
587
- joint-features that will be included in this encoding.
588
-
589
- :type train_toks: list(tuple(dict, str))
590
- :param train_toks: Training data, represented as a list of
591
- pairs, the first member of which is a feature dictionary,
592
- and the second of which is a classification label.
593
-
594
- :type count_cutoff: int
595
- :param count_cutoff: A cutoff value that is used to discard
596
- rare joint-features. If a joint-feature's value is 1
597
- fewer than ``count_cutoff`` times in the training corpus,
598
- then that joint-feature is not included in the generated
599
- encoding.
600
-
601
- :type labels: list
602
- :param labels: A list of labels that should be used by the
603
- classifier. If not specified, then the set of labels
604
- attested in ``train_toks`` will be used.
605
-
606
- :param options: Extra parameters for the constructor, such as
607
- ``unseen_features`` and ``alwayson_features``.
608
- """
609
- mapping = {} # maps (fname, fval, label) -> fid
610
- seen_labels = set() # The set of labels we've encountered
611
- count = defaultdict(int) # maps (fname, fval) -> count
612
-
613
- for (tok, label) in train_toks:
614
- if labels and label not in labels:
615
- raise ValueError('Unexpected label %s' % label)
616
- seen_labels.add(label)
617
-
618
- # Record each of the features.
619
- for (fname, fval) in tok.items():
620
-
621
- # If a count cutoff is given, then only add a joint
622
- # feature once the corresponding (fname, fval, label)
623
- # tuple exceeds that cutoff.
624
- count[fname, fval] += 1
625
- if count[fname, fval] >= count_cutoff:
626
- if (fname, fval, label) not in mapping:
627
- mapping[fname, fval, label] = len(mapping)
628
-
629
- if labels is None:
630
- labels = seen_labels
631
- return cls(labels, mapping, **options)
632
-
633
- class GISEncoding(BinaryMaxentFeatureEncoding):
634
- """
635
- A binary feature encoding which adds one new joint-feature to the
636
- joint-features defined by ``BinaryMaxentFeatureEncoding``: a
637
- correction feature, whose value is chosen to ensure that the
638
- sparse vector always sums to a constant non-negative number. This
639
- new feature is used to ensure two preconditions for the GIS
640
- training algorithm:
641
-
642
- - At least one feature vector index must be nonzero for every
643
- token.
644
- - The feature vector must sum to a constant non-negative number
645
- for every token.
646
- """
647
- def __init__(self, labels, mapping, unseen_features=False,
648
- alwayson_features=False, C=None):
649
- """
650
- :param C: The correction constant. The value of the correction
651
- feature is based on this value. In particular, its value is
652
- ``C - sum([v for (f,v) in encoding])``.
653
- :seealso: ``BinaryMaxentFeatureEncoding.__init__``
654
- """
655
- BinaryMaxentFeatureEncoding.__init__(
656
- self, labels, mapping, unseen_features, alwayson_features)
657
- if C is None:
658
- C = len(set(fname for (fname, fval, label) in mapping))+1
659
- self._C = C
660
-
661
- @property
662
- def C(self):
663
- """The non-negative constant that all encoded feature vectors
664
- will sum to."""
665
- return self._C
666
-
667
- def encode(self, featureset, label):
668
- # Get the basic encoding.
669
- encoding = BinaryMaxentFeatureEncoding.encode(self, featureset, label)
670
- base_length = BinaryMaxentFeatureEncoding.length(self)
671
-
672
- # Add a correction feature.
673
- total = sum(v for (f, v) in encoding)
674
- if total >= self._C:
675
- raise ValueError('Correction feature is not high enough!')
676
- encoding.append((base_length, self._C-total))
677
-
678
- # Return the result
679
- return encoding
680
-
681
- def length(self):
682
- return BinaryMaxentFeatureEncoding.length(self) + 1
683
-
684
- def describe(self, f_id):
685
- if f_id == BinaryMaxentFeatureEncoding.length(self):
686
- return 'Correction feature (%s)' % self._C
687
- else:
688
- return BinaryMaxentFeatureEncoding.describe(self, f_id)
689
-
690
-
691
- class TadmEventMaxentFeatureEncoding(BinaryMaxentFeatureEncoding):
692
- def __init__(self, labels, mapping, unseen_features=False,
693
- alwayson_features=False):
694
- self._mapping = OrderedDict(mapping)
695
- self._label_mapping = OrderedDict()
696
- BinaryMaxentFeatureEncoding.__init__(self, labels, self._mapping,
697
- unseen_features,
698
- alwayson_features)
699
-
700
- def encode(self, featureset, label):
701
- encoding = []
702
- for feature, value in featureset.items():
703
- if (feature, label) not in self._mapping:
704
- self._mapping[(feature, label)] = len(self._mapping)
705
- if value not in self._label_mapping:
706
- if not isinstance(value, int):
707
- self._label_mapping[value] = len(self._label_mapping)
708
- else:
709
- self._label_mapping[value] = value
710
- encoding.append((self._mapping[(feature, label)],
711
- self._label_mapping[value]))
712
- return encoding
713
-
714
- def labels(self):
715
- return self._labels
716
-
717
- def describe(self, fid):
718
- for (feature, label) in self._mapping:
719
- if self._mapping[(feature, label)] == fid:
720
- return (feature, label)
721
-
722
- def length(self):
723
- return len(self._mapping)
724
-
725
- @classmethod
726
- def train(cls, train_toks, count_cutoff=0, labels=None, **options):
727
- mapping = OrderedDict()
728
- if not labels:
729
- labels = []
730
-
731
- # This gets read twice, so compute the values in case it's lazy.
732
- train_toks = list(train_toks)
733
-
734
- for (featureset, label) in train_toks:
735
- if label not in labels:
736
- labels.append(label)
737
-
738
- for (featureset, label) in train_toks:
739
- for label in labels:
740
- for feature in featureset:
741
- if (feature, label) not in mapping:
742
- mapping[(feature, label)] = len(mapping)
743
-
744
- return cls(labels, mapping, **options)
745
-
746
-
747
- class TypedMaxentFeatureEncoding(MaxentFeatureEncodingI):
748
- """
749
- A feature encoding that generates vectors containing integer,
750
- float and binary joint-features of the form:
751
-
752
- Binary (for string and boolean features):
753
-
754
- | joint_feat(fs, l) = { 1 if (fs[fname] == fval) and (l == label)
755
- | {
756
- | { 0 otherwise
757
-
758
- Value (for integer and float features):
759
-
760
- | joint_feat(fs, l) = { fval if (fs[fname] == type(fval))
761
- | { and (l == label)
762
- | {
763
- | { not encoded otherwise
764
-
765
- Where ``fname`` is the name of an input-feature, ``fval`` is a value
766
- for that input-feature, and ``label`` is a label.
767
-
768
- Typically, these features are constructed based on a training
769
- corpus, using the ``train()`` method.
770
-
771
- For string and boolean features [type(fval) not in (int, float)]
772
- this method will create one feature for each combination of
773
- ``fname``, ``fval``, and ``label`` that occurs at least once in the
774
- training corpus.
775
-
776
- For integer and float features [type(fval) in (int, float)] this
777
- method will create one feature for each combination of ``fname``
778
- and ``label`` that occurs at least once in the training corpus.
779
-
780
- For binary features the ``unseen_features`` parameter can be used
781
- to add "unseen-value features", which are used whenever an input
782
- feature has a value that was not encountered in the training
783
- corpus. These features have the form:
784
-
785
- | joint_feat(fs, l) = { 1 if is_unseen(fname, fs[fname])
786
- | { and l == label
787
- | {
788
- | { 0 otherwise
789
-
790
- Where ``is_unseen(fname, fval)`` is true if the encoding does not
791
- contain any joint features that are true when ``fs[fname]==fval``.
792
-
793
- The ``alwayson_features`` parameter can be used to add "always-on
794
- features", which have the form:
795
-
796
- | joint_feat(fs, l) = { 1 if (l == label)
797
- | {
798
- | { 0 otherwise
799
-
800
- These always-on features allow the maxent model to directly model
801
- the prior probabilities of each label.
802
- """
803
- def __init__(self, labels, mapping, unseen_features=False,
804
- alwayson_features=False):
805
- """
806
- :param labels: A list of the \"known labels\" for this encoding.
807
-
808
- :param mapping: A dictionary mapping from ``(fname,fval,label)``
809
- tuples to corresponding joint-feature indexes. These
810
- indexes must be the set of integers from 0...len(mapping).
811
- If ``mapping[fname,fval,label]=id``, then
812
- ``self.encode({..., fname:fval, ...``, label)[id]} is 1;
813
- otherwise, it is 0.
814
-
815
- :param unseen_features: If true, then include unseen value
816
- features in the generated joint-feature vectors.
817
-
818
- :param alwayson_features: If true, then include always-on
819
- features in the generated joint-feature vectors.
820
- """
821
- if set(mapping.values()) != set(range(len(mapping))):
822
- raise ValueError('Mapping values must be exactly the '
823
- 'set of integers from 0...len(mapping)')
824
-
825
- self._labels = list(labels)
826
- """A list of attested labels."""
827
-
828
- self._mapping = mapping
829
- """dict mapping from (fname,fval,label) -> fid"""
830
-
831
- self._length = len(mapping)
832
- """The length of generated joint feature vectors."""
833
-
834
- self._alwayson = None
835
- """dict mapping from label -> fid"""
836
-
837
- self._unseen = None
838
- """dict mapping from fname -> fid"""
839
-
840
- if alwayson_features:
841
- self._alwayson = dict((label, i+self._length)
842
- for (i, label) in enumerate(labels))
843
- self._length += len(self._alwayson)
844
-
845
- if unseen_features:
846
- fnames = set(fname for (fname, fval, label) in mapping)
847
- self._unseen = dict((fname, i+self._length)
848
- for (i, fname) in enumerate(fnames))
849
- self._length += len(fnames)
850
-
851
- def encode(self, featureset, label):
852
- # Inherit docs.
853
- encoding = []
854
-
855
- # Convert input-features to joint-features:
856
- for fname, fval in featureset.items():
857
- if isinstance(fval, (compat.integer_types, float)):
858
- # Known feature name & value:
859
- if (fname, type(fval), label) in self._mapping:
860
- encoding.append((self._mapping[fname, type(fval),
861
- label], fval))
862
- else:
863
- # Known feature name & value:
864
- if (fname, fval, label) in self._mapping:
865
- encoding.append((self._mapping[fname, fval, label], 1))
866
-
867
- # Otherwise, we might want to fire an "unseen-value feature".
868
- elif self._unseen:
869
- # Have we seen this fname/fval combination with any label?
870
- for label2 in self._labels:
871
- if (fname, fval, label2) in self._mapping:
872
- break # we've seen this fname/fval combo
873
- # We haven't -- fire the unseen-value feature
874
- else:
875
- if fname in self._unseen:
876
- encoding.append((self._unseen[fname], 1))
877
-
878
-
879
- # Add always-on features:
880
- if self._alwayson and label in self._alwayson:
881
- encoding.append((self._alwayson[label], 1))
882
-
883
- return encoding
884
-
885
- def describe(self, f_id):
886
- # Inherit docs.
887
- if not isinstance(f_id, compat.integer_types):
888
- raise TypeError('describe() expected an int')
889
- try:
890
- self._inv_mapping
891
- except AttributeError:
892
- self._inv_mapping = [-1]*len(self._mapping)
893
- for (info, i) in self._mapping.items():
894
- self._inv_mapping[i] = info
895
-
896
- if f_id < len(self._mapping):
897
- (fname, fval, label) = self._inv_mapping[f_id]
898
- return '%s==%r and label is %r' % (fname, fval, label)
899
- elif self._alwayson and f_id in self._alwayson.values():
900
- for (label, f_id2) in self._alwayson.items():
901
- if f_id == f_id2:
902
- return 'label is %r' % label
903
- elif self._unseen and f_id in self._unseen.values():
904
- for (fname, f_id2) in self._unseen.items():
905
- if f_id == f_id2:
906
- return '%s is unseen' % fname
907
- else:
908
- raise ValueError('Bad feature id')
909
-
910
- def labels(self):
911
- # Inherit docs.
912
- return self._labels
913
-
914
- def length(self):
915
- # Inherit docs.
916
- return self._length
917
-
918
- @classmethod
919
- def train(cls, train_toks, count_cutoff=0, labels=None, **options):
920
- """
921
- Construct and return new feature encoding, based on a given
922
- training corpus ``train_toks``. See the class description
923
- ``TypedMaxentFeatureEncoding`` for a description of the
924
- joint-features that will be included in this encoding.
925
-
926
- Note: recognized feature values types are (int, float), over
927
- types are interpreted as regular binary features.
928
-
929
- :type train_toks: list(tuple(dict, str))
930
- :param train_toks: Training data, represented as a list of
931
- pairs, the first member of which is a feature dictionary,
932
- and the second of which is a classification label.
933
-
934
- :type count_cutoff: int
935
- :param count_cutoff: A cutoff value that is used to discard
936
- rare joint-features. If a joint-feature's value is 1
937
- fewer than ``count_cutoff`` times in the training corpus,
938
- then that joint-feature is not included in the generated
939
- encoding.
940
-
941
- :type labels: list
942
- :param labels: A list of labels that should be used by the
943
- classifier. If not specified, then the set of labels
944
- attested in ``train_toks`` will be used.
945
-
946
- :param options: Extra parameters for the constructor, such as
947
- ``unseen_features`` and ``alwayson_features``.
948
- """
949
- mapping = {} # maps (fname, fval, label) -> fid
950
- seen_labels = set() # The set of labels we've encountered
951
- count = defaultdict(int) # maps (fname, fval) -> count
952
-
953
- for (tok, label) in train_toks:
954
- if labels and label not in labels:
955
- raise ValueError('Unexpected label %s' % label)
956
- seen_labels.add(label)
957
-
958
- # Record each of the features.
959
- for (fname, fval) in tok.items():
960
- if type(fval) in (int, float):
961
- fval = type(fval)
962
- # If a count cutoff is given, then only add a joint
963
- # feature once the corresponding (fname, fval, label)
964
- # tuple exceeds that cutoff.
965
- count[fname, fval] += 1
966
- if count[fname, fval] >= count_cutoff:
967
- if (fname, fval, label) not in mapping:
968
- mapping[fname, fval, label] = len(mapping)
969
-
970
- if labels is None:
971
- labels = seen_labels
972
- return cls(labels, mapping, **options)
973
-
974
-
975
-
976
-
977
- ######################################################################
978
- #{ Classifier Trainer: Generalized Iterative Scaling
979
- ######################################################################
980
-
981
- def train_maxent_classifier_with_gis(train_toks, trace=3, encoding=None,
982
- labels=None, **cutoffs):
983
- """
984
- Train a new ``ConditionalExponentialClassifier``, using the given
985
- training samples, using the Generalized Iterative Scaling
986
- algorithm. This ``ConditionalExponentialClassifier`` will encode
987
- the model that maximizes entropy from all the models that are
988
- empirically consistent with ``train_toks``.
989
-
990
- :see: ``train_maxent_classifier()`` for parameter descriptions.
991
- """
992
- cutoffs.setdefault('max_iter', 100)
993
- cutoffchecker = CutoffChecker(cutoffs)
994
-
995
- # Construct an encoding from the training data.
996
- if encoding is None:
997
- encoding = GISEncoding.train(train_toks, labels=labels)
998
-
999
- if not hasattr(encoding, 'C'):
1000
- raise TypeError('The GIS algorithm requires an encoding that '
1001
- 'defines C (e.g., GISEncoding).')
1002
-
1003
- # Cinv is the inverse of the sum of each joint feature vector.
1004
- # This controls the learning rate: higher Cinv (or lower C) gives
1005
- # faster learning.
1006
- Cinv = 1.0/encoding.C
1007
-
1008
- # Count how many times each feature occurs in the training data.
1009
- empirical_fcount = calculate_empirical_fcount(train_toks, encoding)
1010
-
1011
- # Check for any features that are not attested in train_toks.
1012
- unattested = set(numpy.nonzero(empirical_fcount == 0)[0])
1013
-
1014
- # Build the classifier. Start with weight=0 for each attested
1015
- # feature, and weight=-infinity for each unattested feature.
1016
- weights = numpy.zeros(len(empirical_fcount), 'd')
1017
- for fid in unattested:
1018
- weights[fid] = numpy.NINF
1019
- classifier = ConditionalExponentialClassifier(encoding, weights)
1020
-
1021
- # Take the log of the empirical fcount.
1022
- log_empirical_fcount = numpy.log2(empirical_fcount)
1023
- del empirical_fcount
1024
-
1025
- if trace > 0:
1026
- print(' ==> Training (%d iterations)' % cutoffs['max_iter'])
1027
- if trace > 2:
1028
- print()
1029
- print(' Iteration Log Likelihood Accuracy')
1030
- print(' ---------------------------------------')
1031
-
1032
- # Train the classifier.
1033
- try:
1034
- while True:
1035
- if trace > 2:
1036
- ll = cutoffchecker.ll or log_likelihood(classifier, train_toks)
1037
- acc = cutoffchecker.acc or accuracy(classifier, train_toks)
1038
- iternum = cutoffchecker.iter
1039
- print(' %9d %14.5f %9.3f' % (iternum, ll, acc))
1040
-
1041
- # Use the model to estimate the number of times each
1042
- # feature should occur in the training data.
1043
- estimated_fcount = calculate_estimated_fcount(
1044
- classifier, train_toks, encoding)
1045
-
1046
- # Take the log of estimated fcount (avoid taking log(0).)
1047
- for fid in unattested:
1048
- estimated_fcount[fid] += 1
1049
- log_estimated_fcount = numpy.log2(estimated_fcount)
1050
- del estimated_fcount
1051
-
1052
- # Update the classifier weights
1053
- weights = classifier.weights()
1054
- weights += (log_empirical_fcount - log_estimated_fcount) * Cinv
1055
- classifier.set_weights(weights)
1056
-
1057
- # Check the log-likelihood & accuracy cutoffs.
1058
- if cutoffchecker.check(classifier, train_toks):
1059
- break
1060
-
1061
- except KeyboardInterrupt:
1062
- print(' Training stopped: keyboard interrupt')
1063
- except:
1064
- raise
1065
-
1066
- if trace > 2:
1067
- ll = log_likelihood(classifier, train_toks)
1068
- acc = accuracy(classifier, train_toks)
1069
- print(' Final %14.5f %9.3f' % (ll, acc))
1070
-
1071
- # Return the classifier.
1072
- return classifier
1073
-
1074
- def calculate_empirical_fcount(train_toks, encoding):
1075
- fcount = numpy.zeros(encoding.length(), 'd')
1076
-
1077
- for tok, label in train_toks:
1078
- for (index, val) in encoding.encode(tok, label):
1079
- fcount[index] += val
1080
-
1081
- return fcount
1082
-
1083
- def calculate_estimated_fcount(classifier, train_toks, encoding):
1084
- fcount = numpy.zeros(encoding.length(), 'd')
1085
-
1086
- for tok, label in train_toks:
1087
- pdist = classifier.prob_classify(tok)
1088
- for label in pdist.samples():
1089
- prob = pdist.prob(label)
1090
- for (fid, fval) in encoding.encode(tok, label):
1091
- fcount[fid] += prob*fval
1092
-
1093
- return fcount
1094
-
1095
-
1096
- ######################################################################
1097
- #{ Classifier Trainer: Improved Iterative Scaling
1098
- ######################################################################
1099
-
1100
- def train_maxent_classifier_with_iis(train_toks, trace=3, encoding=None,
1101
- labels=None, **cutoffs):
1102
- """
1103
- Train a new ``ConditionalExponentialClassifier``, using the given
1104
- training samples, using the Improved Iterative Scaling algorithm.
1105
- This ``ConditionalExponentialClassifier`` will encode the model
1106
- that maximizes entropy from all the models that are empirically
1107
- consistent with ``train_toks``.
1108
-
1109
- :see: ``train_maxent_classifier()`` for parameter descriptions.
1110
- """
1111
- cutoffs.setdefault('max_iter', 100)
1112
- cutoffchecker = CutoffChecker(cutoffs)
1113
-
1114
- # Construct an encoding from the training data.
1115
- if encoding is None:
1116
- encoding = BinaryMaxentFeatureEncoding.train(train_toks, labels=labels)
1117
-
1118
- # Count how many times each feature occurs in the training data.
1119
- empirical_ffreq = (calculate_empirical_fcount(train_toks, encoding) /
1120
- len(train_toks))
1121
-
1122
- # Find the nf map, and related variables nfarray and nfident.
1123
- # nf is the sum of the features for a given labeled text.
1124
- # nfmap compresses this sparse set of values to a dense list.
1125
- # nfarray performs the reverse operation. nfident is
1126
- # nfarray multiplied by an identity matrix.
1127
- nfmap = calculate_nfmap(train_toks, encoding)
1128
- nfarray = numpy.array(sorted(nfmap, key=nfmap.__getitem__), 'd')
1129
- nftranspose = numpy.reshape(nfarray, (len(nfarray), 1))
1130
-
1131
- # Check for any features that are not attested in train_toks.
1132
- unattested = set(numpy.nonzero(empirical_ffreq == 0)[0])
1133
-
1134
- # Build the classifier. Start with weight=0 for each attested
1135
- # feature, and weight=-infinity for each unattested feature.
1136
- weights = numpy.zeros(len(empirical_ffreq), 'd')
1137
- for fid in unattested:
1138
- weights[fid] = numpy.NINF
1139
- classifier = ConditionalExponentialClassifier(encoding, weights)
1140
-
1141
- if trace > 0:
1142
- print(' ==> Training (%d iterations)' % cutoffs['max_iter'])
1143
- if trace > 2:
1144
- print()
1145
- print(' Iteration Log Likelihood Accuracy')
1146
- print(' ---------------------------------------')
1147
-
1148
- # Train the classifier.
1149
- try:
1150
- while True:
1151
- if trace > 2:
1152
- ll = cutoffchecker.ll or log_likelihood(classifier, train_toks)
1153
- acc = cutoffchecker.acc or accuracy(classifier, train_toks)
1154
- iternum = cutoffchecker.iter
1155
- print(' %9d %14.5f %9.3f' % (iternum, ll, acc))
1156
-
1157
- # Calculate the deltas for this iteration, using Newton's method.
1158
- deltas = calculate_deltas(
1159
- train_toks, classifier, unattested, empirical_ffreq,
1160
- nfmap, nfarray, nftranspose, encoding)
1161
-
1162
- # Use the deltas to update our weights.
1163
- weights = classifier.weights()
1164
- weights += deltas
1165
- classifier.set_weights(weights)
1166
-
1167
- # Check the log-likelihood & accuracy cutoffs.
1168
- if cutoffchecker.check(classifier, train_toks):
1169
- break
1170
-
1171
- except KeyboardInterrupt:
1172
- print(' Training stopped: keyboard interrupt')
1173
- except:
1174
- raise
1175
-
1176
-
1177
- if trace > 2:
1178
- ll = log_likelihood(classifier, train_toks)
1179
- acc = accuracy(classifier, train_toks)
1180
- print(' Final %14.5f %9.3f' % (ll, acc))
1181
-
1182
- # Return the classifier.
1183
- return classifier
1184
-
1185
- def calculate_nfmap(train_toks, encoding):
1186
- """
1187
- Construct a map that can be used to compress ``nf`` (which is
1188
- typically sparse).
1189
-
1190
- *nf(feature_vector)* is the sum of the feature values for
1191
- *feature_vector*.
1192
-
1193
- This represents the number of features that are active for a
1194
- given labeled text. This method finds all values of *nf(t)*
1195
- that are attested for at least one token in the given list of
1196
- training tokens; and constructs a dictionary mapping these
1197
- attested values to a continuous range *0...N*. For example,
1198
- if the only values of *nf()* that were attested were 3, 5, and
1199
- 7, then ``_nfmap`` might return the dictionary ``{3:0, 5:1, 7:2}``.
1200
-
1201
- :return: A map that can be used to compress ``nf`` to a dense
1202
- vector.
1203
- :rtype: dict(int -> int)
1204
- """
1205
- # Map from nf to indices. This allows us to use smaller arrays.
1206
- nfset = set()
1207
- for tok, _ in train_toks:
1208
- for label in encoding.labels():
1209
- nfset.add(sum(val for (id, val) in encoding.encode(tok, label)))
1210
- return dict((nf, i) for (i, nf) in enumerate(nfset))
1211
-
1212
- def calculate_deltas(train_toks, classifier, unattested, ffreq_empirical,
1213
- nfmap, nfarray, nftranspose, encoding):
1214
- """
1215
- Calculate the update values for the classifier weights for
1216
- this iteration of IIS. These update weights are the value of
1217
- ``delta`` that solves the equation::
1218
-
1219
- ffreq_empirical[i]
1220
- =
1221
- SUM[fs,l] (classifier.prob_classify(fs).prob(l) *
1222
- feature_vector(fs,l)[i] *
1223
- exp(delta[i] * nf(feature_vector(fs,l))))
1224
-
1225
- Where:
1226
- - *(fs,l)* is a (featureset, label) tuple from ``train_toks``
1227
- - *feature_vector(fs,l)* = ``encoding.encode(fs,l)``
1228
- - *nf(vector)* = ``sum([val for (id,val) in vector])``
1229
-
1230
- This method uses Newton's method to solve this equation for
1231
- *delta[i]*. In particular, it starts with a guess of
1232
- ``delta[i]`` = 1; and iteratively updates ``delta`` with:
1233
-
1234
- | delta[i] -= (ffreq_empirical[i] - sum1[i])/(-sum2[i])
1235
-
1236
- until convergence, where *sum1* and *sum2* are defined as:
1237
-
1238
- | sum1[i](delta) = SUM[fs,l] f[i](fs,l,delta)
1239
- | sum2[i](delta) = SUM[fs,l] (f[i](fs,l,delta).nf(feature_vector(fs,l)))
1240
- | f[i](fs,l,delta) = (classifier.prob_classify(fs).prob(l) .
1241
- | feature_vector(fs,l)[i] .
1242
- | exp(delta[i] . nf(feature_vector(fs,l))))
1243
-
1244
- Note that *sum1* and *sum2* depend on ``delta``; so they need
1245
- to be re-computed each iteration.
1246
-
1247
- The variables ``nfmap``, ``nfarray``, and ``nftranspose`` are
1248
- used to generate a dense encoding for *nf(ltext)*. This
1249
- allows ``_deltas`` to calculate *sum1* and *sum2* using
1250
- matrices, which yields a significant performance improvement.
1251
-
1252
- :param train_toks: The set of training tokens.
1253
- :type train_toks: list(tuple(dict, str))
1254
- :param classifier: The current classifier.
1255
- :type classifier: ClassifierI
1256
- :param ffreq_empirical: An array containing the empirical
1257
- frequency for each feature. The *i*\ th element of this
1258
- array is the empirical frequency for feature *i*.
1259
- :type ffreq_empirical: sequence of float
1260
- :param unattested: An array that is 1 for features that are
1261
- not attested in the training data; and 0 for features that
1262
- are attested. In other words, ``unattested[i]==0`` iff
1263
- ``ffreq_empirical[i]==0``.
1264
- :type unattested: sequence of int
1265
- :param nfmap: A map that can be used to compress ``nf`` to a dense
1266
- vector.
1267
- :type nfmap: dict(int -> int)
1268
- :param nfarray: An array that can be used to uncompress ``nf``
1269
- from a dense vector.
1270
- :type nfarray: array(float)
1271
- :param nftranspose: The transpose of ``nfarray``
1272
- :type nftranspose: array(float)
1273
- """
1274
- # These parameters control when we decide that we've
1275
- # converged. It probably should be possible to set these
1276
- # manually, via keyword arguments to train.
1277
- NEWTON_CONVERGE = 1e-12
1278
- MAX_NEWTON = 300
1279
-
1280
- deltas = numpy.ones(encoding.length(), 'd')
1281
-
1282
- # Precompute the A matrix:
1283
- # A[nf][id] = sum ( p(fs) * p(label|fs) * f(fs,label) )
1284
- # over all label,fs s.t. num_features[label,fs]=nf
1285
- A = numpy.zeros((len(nfmap), encoding.length()), 'd')
1286
-
1287
- for tok, label in train_toks:
1288
- dist = classifier.prob_classify(tok)
1289
-
1290
- for label in encoding.labels():
1291
- # Generate the feature vector
1292
- feature_vector = encoding.encode(tok, label)
1293
- # Find the number of active features
1294
- nf = sum(val for (id, val) in feature_vector)
1295
- # Update the A matrix
1296
- for (id, val) in feature_vector:
1297
- A[nfmap[nf], id] += dist.prob(label) * val
1298
- A /= len(train_toks)
1299
-
1300
- # Iteratively solve for delta. Use the following variables:
1301
- # - nf_delta[x][y] = nfarray[x] * delta[y]
1302
- # - exp_nf_delta[x][y] = exp(nf[x] * delta[y])
1303
- # - nf_exp_nf_delta[x][y] = nf[x] * exp(nf[x] * delta[y])
1304
- # - sum1[i][nf] = sum p(fs)p(label|fs)f[i](label,fs)
1305
- # exp(delta[i]nf)
1306
- # - sum2[i][nf] = sum p(fs)p(label|fs)f[i](label,fs)
1307
- # nf exp(delta[i]nf)
1308
- for rangenum in range(MAX_NEWTON):
1309
- nf_delta = numpy.outer(nfarray, deltas)
1310
- exp_nf_delta = 2 ** nf_delta
1311
- nf_exp_nf_delta = nftranspose * exp_nf_delta
1312
- sum1 = numpy.sum(exp_nf_delta * A, axis=0)
1313
- sum2 = numpy.sum(nf_exp_nf_delta * A, axis=0)
1314
-
1315
- # Avoid division by zero.
1316
- for fid in unattested:
1317
- sum2[fid] += 1
1318
-
1319
- # Update the deltas.
1320
- deltas -= (ffreq_empirical - sum1) / -sum2
1321
-
1322
- # We can stop once we converge.
1323
- n_error = (numpy.sum(abs((ffreq_empirical-sum1)))/
1324
- numpy.sum(abs(deltas)))
1325
- if n_error < NEWTON_CONVERGE:
1326
- return deltas
1327
-
1328
- return deltas
1329
-
1330
- ######################################################################
1331
- #{ Classifier Trainer: megam
1332
- ######################################################################
1333
-
1334
- # [xx] possible extension: add support for using implicit file format;
1335
- # this would need to put requirements on what encoding is used. But
1336
- # we may need this for other maxent classifier trainers that require
1337
- # implicit formats anyway.
1338
- def train_maxent_classifier_with_megam(train_toks, trace=3, encoding=None,
1339
- labels=None, gaussian_prior_sigma=0,
1340
- **kwargs):
1341
- """
1342
- Train a new ``ConditionalExponentialClassifier``, using the given
1343
- training samples, using the external ``megam`` library. This
1344
- ``ConditionalExponentialClassifier`` will encode the model that
1345
- maximizes entropy from all the models that are empirically
1346
- consistent with ``train_toks``.
1347
-
1348
- :see: ``train_maxent_classifier()`` for parameter descriptions.
1349
- :see: ``nltk.classify.megam``
1350
- """
1351
-
1352
- explicit = True
1353
- bernoulli = True
1354
- if 'explicit' in kwargs:
1355
- explicit = kwargs['explicit']
1356
- if 'bernoulli' in kwargs:
1357
- bernoulli = kwargs['bernoulli']
1358
-
1359
- # Construct an encoding from the training data.
1360
- if encoding is None:
1361
- # Count cutoff can also be controlled by megam with the -minfc
1362
- # option. Not sure where the best place for it is.
1363
- count_cutoff = kwargs.get('count_cutoff', 0)
1364
- encoding = BinaryMaxentFeatureEncoding.train(train_toks, count_cutoff,
1365
- labels=labels,
1366
- alwayson_features=True)
1367
- elif labels is not None:
1368
- raise ValueError('Specify encoding or labels, not both')
1369
-
1370
- # Write a training file for megam.
1371
- try:
1372
- fd, trainfile_name = tempfile.mkstemp(prefix='nltk-')
1373
- with open(trainfile_name, 'w') as trainfile:
1374
- write_megam_file(train_toks, encoding, trainfile,
1375
- explicit=explicit, bernoulli=bernoulli)
1376
- os.close(fd)
1377
- except (OSError, IOError, ValueError) as e:
1378
- raise ValueError('Error while creating megam training file: %s' % e)
1379
-
1380
- # Run megam on the training file.
1381
- options = []
1382
- options += ['-nobias', '-repeat', '10']
1383
- if explicit:
1384
- options += ['-explicit']
1385
- if not bernoulli:
1386
- options += ['-fvals']
1387
- if gaussian_prior_sigma:
1388
- # Lambda is just the precision of the Gaussian prior, i.e. it's the
1389
- # inverse variance, so the parameter conversion is 1.0/sigma**2.
1390
- # See http://www.umiacs.umd.edu/~hal/docs/daume04cg-bfgs.pdf.
1391
- inv_variance = 1.0 / gaussian_prior_sigma**2
1392
- else:
1393
- inv_variance = 0
1394
- options += ['-lambda', '%.2f' % inv_variance, '-tune']
1395
- if trace < 3:
1396
- options += ['-quiet']
1397
- if 'max_iter' in kwargs:
1398
- options += ['-maxi', '%s' % kwargs['max_iter']]
1399
- if 'll_delta' in kwargs:
1400
- # [xx] this is actually a perplexity delta, not a log
1401
- # likelihood delta
1402
- options += ['-dpp', '%s' % abs(kwargs['ll_delta'])]
1403
- if hasattr(encoding, 'cost'):
1404
- options += ['-multilabel'] # each possible la
1405
- options += ['multiclass', trainfile_name]
1406
- stdout = call_megam(options)
1407
- # print './megam_i686.opt ', ' '.join(options)
1408
- # Delete the training file
1409
- try:
1410
- os.remove(trainfile_name)
1411
- except (OSError, IOError) as e:
1412
- print('Warning: unable to delete %s: %s' % (trainfile_name, e))
1413
-
1414
- # Parse the generated weight vector.
1415
- weights = parse_megam_weights(stdout, encoding.length(), explicit)
1416
-
1417
- # Convert from base-e to base-2 weights.
1418
- weights *= numpy.log2(numpy.e)
1419
-
1420
- # Build the classifier
1421
- return MaxentClassifier(encoding, weights)
1422
-
1423
- ######################################################################
1424
- #{ Classifier Trainer: tadm
1425
- ######################################################################
1426
-
1427
- class TadmMaxentClassifier(MaxentClassifier):
1428
- @classmethod
1429
- def train(cls, train_toks, **kwargs):
1430
- algorithm = kwargs.get('algorithm', 'tao_lmvm')
1431
- trace = kwargs.get('trace', 3)
1432
- encoding = kwargs.get('encoding', None)
1433
- labels = kwargs.get('labels', None)
1434
- sigma = kwargs.get('gaussian_prior_sigma', 0)
1435
- count_cutoff = kwargs.get('count_cutoff', 0)
1436
- max_iter = kwargs.get('max_iter')
1437
- ll_delta = kwargs.get('min_lldelta')
1438
-
1439
- # Construct an encoding from the training data.
1440
- if not encoding:
1441
- encoding = TadmEventMaxentFeatureEncoding.train(train_toks,
1442
- count_cutoff,
1443
- labels=labels)
1444
-
1445
- trainfile_fd, trainfile_name = \
1446
- tempfile.mkstemp(prefix='nltk-tadm-events-', suffix='.gz')
1447
- weightfile_fd, weightfile_name = \
1448
- tempfile.mkstemp(prefix='nltk-tadm-weights-')
1449
-
1450
- trainfile = gzip_open_unicode(trainfile_name, 'w')
1451
- write_tadm_file(train_toks, encoding, trainfile)
1452
- trainfile.close()
1453
-
1454
- options = []
1455
- options.extend(['-monitor'])
1456
- options.extend(['-method', algorithm])
1457
- if sigma:
1458
- options.extend(['-l2', '%.6f' % sigma**2])
1459
- if max_iter:
1460
- options.extend(['-max_it', '%d' % max_iter])
1461
- if ll_delta:
1462
- options.extend(['-fatol', '%.6f' % abs(ll_delta)])
1463
- options.extend(['-events_in', trainfile_name])
1464
- options.extend(['-params_out', weightfile_name])
1465
- if trace < 3:
1466
- options.extend(['2>&1'])
1467
- else:
1468
- options.extend(['-summary'])
1469
-
1470
- call_tadm(options)
1471
-
1472
- with open(weightfile_name, 'r') as weightfile:
1473
- weights = parse_tadm_weights(weightfile)
1474
-
1475
- os.remove(trainfile_name)
1476
- os.remove(weightfile_name)
1477
-
1478
- # Convert from base-e to base-2 weights.
1479
- weights *= numpy.log2(numpy.e)
1480
-
1481
- # Build the classifier
1482
- return cls(encoding, weights)
1483
-
1484
- ######################################################################
1485
- #{ Demo
1486
- ######################################################################
1487
- def demo():
1488
- from nltk.classify.util import names_demo
1489
- classifier = names_demo(MaxentClassifier.train)
1490
-
1491
- if __name__ == '__main__':
1492
- demo()
1
+ # Natural Language Toolkit: Maximum Entropy Classifiers
2
+ #
3
+ # Copyright (C) 2001-2018 NLTK Project
4
+ # Author: Edward Loper <edloper@gmail.com>
5
+ # Dmitry Chichkov <dchichkov@gmail.com> (TypedMaxentFeatureEncoding)
6
+ # URL: <http://nltk.org/>
7
+ # For license information, see LICENSE.TXT
8
+
9
+ """
10
+ A classifier model based on maximum entropy modeling framework. This
11
+ framework considers all of the probability distributions that are
12
+ empirically consistent with the training data; and chooses the
13
+ distribution with the highest entropy. A probability distribution is
14
+ "empirically consistent" with a set of training data if its estimated
15
+ frequency with which a class and a feature vector value co-occur is
16
+ equal to the actual frequency in the data.
17
+
18
+ Terminology: 'feature'
19
+ ======================
20
+ The term *feature* is usually used to refer to some property of an
21
+ unlabeled token. For example, when performing word sense
22
+ disambiguation, we might define a ``'prevword'`` feature whose value is
23
+ the word preceding the target word. However, in the context of
24
+ maxent modeling, the term *feature* is typically used to refer to a
25
+ property of a "labeled" token. In order to prevent confusion, we
26
+ will introduce two distinct terms to disambiguate these two different
27
+ concepts:
28
+
29
+ - An "input-feature" is a property of an unlabeled token.
30
+ - A "joint-feature" is a property of a labeled token.
31
+
32
+ In the rest of the ``nltk.classify`` module, the term "features" is
33
+ used to refer to what we will call "input-features" in this module.
34
+
35
+ In literature that describes and discusses maximum entropy models,
36
+ input-features are typically called "contexts", and joint-features
37
+ are simply referred to as "features".
38
+
39
+ Converting Input-Features to Joint-Features
40
+ -------------------------------------------
41
+ In maximum entropy models, joint-features are required to have numeric
42
+ values. Typically, each input-feature ``input_feat`` is mapped to a
43
+ set of joint-features of the form:
44
+
45
+ | joint_feat(token, label) = { 1 if input_feat(token) == feat_val
46
+ | { and label == some_label
47
+ | {
48
+ | { 0 otherwise
49
+
50
+ For all values of ``feat_val`` and ``some_label``. This mapping is
51
+ performed by classes that implement the ``MaxentFeatureEncodingI``
52
+ interface.
53
+ """
54
+ from __future__ import print_function, unicode_literals
55
+
56
+ try:
57
+ import numpy
58
+ except ImportError:
59
+ pass
60
+
61
+ import tempfile
62
+ import os
63
+ import re
64
+ from collections import defaultdict
65
+
66
+ from six import integer_types
67
+
68
+ from nltk import compat
69
+ from nltk.data import gzip_open_unicode
70
+ from nltk.util import OrderedDict
71
+ from nltk.probability import DictionaryProbDist
72
+
73
+ from nltk.classify.api import ClassifierI
74
+ from nltk.classify.util import CutoffChecker, accuracy, log_likelihood
75
+ from nltk.classify.megam import (call_megam,
76
+ write_megam_file, parse_megam_weights)
77
+ from nltk.classify.tadm import call_tadm, write_tadm_file, parse_tadm_weights
78
+
79
+ __docformat__ = 'epytext en'
80
+
81
+ ######################################################################
82
+ #{ Classifier Model
83
+ ######################################################################
84
+
85
+ @compat.python_2_unicode_compatible
86
+ class MaxentClassifier(ClassifierI):
87
+ """
88
+ A maximum entropy classifier (also known as a "conditional
89
+ exponential classifier"). This classifier is parameterized by a
90
+ set of "weights", which are used to combine the joint-features
91
+ that are generated from a featureset by an "encoding". In
92
+ particular, the encoding maps each ``(featureset, label)`` pair to
93
+ a vector. The probability of each label is then computed using
94
+ the following equation::
95
+
96
+ dotprod(weights, encode(fs,label))
97
+ prob(fs|label) = ---------------------------------------------------
98
+ sum(dotprod(weights, encode(fs,l)) for l in labels)
99
+
100
+ Where ``dotprod`` is the dot product::
101
+
102
+ dotprod(a,b) = sum(x*y for (x,y) in zip(a,b))
103
+ """
104
+ def __init__(self, encoding, weights, logarithmic=True):
105
+ """
106
+ Construct a new maxent classifier model. Typically, new
107
+ classifier models are created using the ``train()`` method.
108
+
109
+ :type encoding: MaxentFeatureEncodingI
110
+ :param encoding: An encoding that is used to convert the
111
+ featuresets that are given to the ``classify`` method into
112
+ joint-feature vectors, which are used by the maxent
113
+ classifier model.
114
+
115
+ :type weights: list of float
116
+ :param weights: The feature weight vector for this classifier.
117
+
118
+ :type logarithmic: bool
119
+ :param logarithmic: If false, then use non-logarithmic weights.
120
+ """
121
+ self._encoding = encoding
122
+ self._weights = weights
123
+ self._logarithmic = logarithmic
124
+ #self._logarithmic = False
125
+ assert encoding.length() == len(weights)
126
+
127
+ def labels(self):
128
+ return self._encoding.labels()
129
+
130
+ def set_weights(self, new_weights):
131
+ """
132
+ Set the feature weight vector for this classifier.
133
+ :param new_weights: The new feature weight vector.
134
+ :type new_weights: list of float
135
+ """
136
+ self._weights = new_weights
137
+ assert self._encoding.length() == len(new_weights)
138
+
139
+ def weights(self):
140
+ """
141
+ :return: The feature weight vector for this classifier.
142
+ :rtype: list of float
143
+ """
144
+ return self._weights
145
+
146
+ def classify(self, featureset):
147
+ return self.prob_classify(featureset).max()
148
+
149
+ def prob_classify(self, featureset):
150
+ prob_dict = {}
151
+ for label in self._encoding.labels():
152
+ feature_vector = self._encoding.encode(featureset, label)
153
+
154
+ if self._logarithmic:
155
+ total = 0.0
156
+ for (f_id, f_val) in feature_vector:
157
+ total += self._weights[f_id] * f_val
158
+ prob_dict[label] = total
159
+
160
+ else:
161
+ prod = 1.0
162
+ for (f_id, f_val) in feature_vector:
163
+ prod *= self._weights[f_id] ** f_val
164
+ prob_dict[label] = prod
165
+
166
+ # Normalize the dictionary to give a probability distribution
167
+ return DictionaryProbDist(prob_dict, log=self._logarithmic,
168
+ normalize=True)
169
+
170
+ def explain(self, featureset, columns=4):
171
+ """
172
+ Print a table showing the effect of each of the features in
173
+ the given feature set, and how they combine to determine the
174
+ probabilities of each label for that featureset.
175
+ """
176
+ descr_width = 50
177
+ TEMPLATE = ' %-'+str(descr_width-2)+'s%s%8.3f'
178
+
179
+ pdist = self.prob_classify(featureset)
180
+ labels = sorted(pdist.samples(), key=pdist.prob, reverse=True)
181
+ labels = labels[:columns]
182
+ print(' Feature'.ljust(descr_width)+''.join(
183
+ '%8s' % (("%s" % l)[:7]) for l in labels))
184
+ print(' '+'-'*(descr_width-2+8*len(labels)))
185
+ sums = defaultdict(int)
186
+ for i, label in enumerate(labels):
187
+ feature_vector = self._encoding.encode(featureset, label)
188
+ feature_vector.sort(key=lambda fid__: abs(self._weights[fid__[0]]),
189
+ reverse=True)
190
+ for (f_id, f_val) in feature_vector:
191
+ if self._logarithmic:
192
+ score = self._weights[f_id] * f_val
193
+ else: score = self._weights[f_id] ** f_val
194
+ descr = self._encoding.describe(f_id)
195
+ descr = descr.split(' and label is ')[0] # hack
196
+ descr += ' (%s)' % f_val # hack
197
+ if len(descr) > 47:
198
+ descr = descr[:44]+'...'
199
+ print(TEMPLATE % (descr, i*8*' ', score))
200
+ sums[label] += score
201
+ print(' '+'-'*(descr_width-1+8*len(labels)))
202
+ print(' TOTAL:'.ljust(descr_width)+''.join(
203
+ '%8.3f' % sums[l] for l in labels))
204
+ print(' PROBS:'.ljust(descr_width)+''.join(
205
+ '%8.3f' % pdist.prob(l) for l in labels))
206
+
207
+ def most_informative_features(self, n=10):
208
+ """
209
+ Generates the ranked list of informative features from most to least.
210
+ """
211
+ if hasattr(self, '_most_informative_features'):
212
+ return self._most_informative_features[:n]
213
+ else:
214
+ self._most_informative_features = sorted(list(range(len(self._weights))),
215
+ key=lambda fid: abs(self._weights[fid]),
216
+ reverse=True)
217
+ return self._most_informative_features[:n]
218
+
219
+ def show_most_informative_features(self, n=10, show='all'):
220
+ """
221
+ :param show: all, neg, or pos (for negative-only or positive-only)
222
+ :type show: str
223
+ :param n: The no. of top features
224
+ :type n: int
225
+ """
226
+ # Use None the full list of ranked features.
227
+ fids = self.most_informative_features(None)
228
+ if show == 'pos':
229
+ fids = [fid for fid in fids if self._weights[fid] > 0]
230
+ elif show == 'neg':
231
+ fids = [fid for fid in fids if self._weights[fid] < 0]
232
+ for fid in fids[:n]:
233
+ print('%8.3f %s' % (self._weights[fid],
234
+ self._encoding.describe(fid)))
235
+
236
+ def __repr__(self):
237
+ return ('<ConditionalExponentialClassifier: %d labels, %d features>' %
238
+ (len(self._encoding.labels()), self._encoding.length()))
239
+
240
+ #: A list of the algorithm names that are accepted for the
241
+ #: ``train()`` method's ``algorithm`` parameter.
242
+ ALGORITHMS = ['GIS', 'IIS', 'MEGAM', 'TADM']
243
+
244
+ @classmethod
245
+ def train(cls, train_toks, algorithm=None, trace=3, encoding=None,
246
+ labels=None, gaussian_prior_sigma=0, **cutoffs):
247
+ """
248
+ Train a new maxent classifier based on the given corpus of
249
+ training samples. This classifier will have its weights
250
+ chosen to maximize entropy while remaining empirically
251
+ consistent with the training corpus.
252
+
253
+ :rtype: MaxentClassifier
254
+ :return: The new maxent classifier
255
+
256
+ :type train_toks: list
257
+ :param train_toks: Training data, represented as a list of
258
+ pairs, the first member of which is a featureset,
259
+ and the second of which is a classification label.
260
+
261
+ :type algorithm: str
262
+ :param algorithm: A case-insensitive string, specifying which
263
+ algorithm should be used to train the classifier. The
264
+ following algorithms are currently available.
265
+
266
+ - Iterative Scaling Methods: Generalized Iterative Scaling (``'GIS'``),
267
+ Improved Iterative Scaling (``'IIS'``)
268
+ - External Libraries (requiring megam):
269
+ LM-BFGS algorithm, with training performed by Megam (``'megam'``)
270
+
271
+ The default algorithm is ``'IIS'``.
272
+
273
+ :type trace: int
274
+ :param trace: The level of diagnostic tracing output to produce.
275
+ Higher values produce more verbose output.
276
+ :type encoding: MaxentFeatureEncodingI
277
+ :param encoding: A feature encoding, used to convert featuresets
278
+ into feature vectors. If none is specified, then a
279
+ ``BinaryMaxentFeatureEncoding`` will be built based on the
280
+ features that are attested in the training corpus.
281
+ :type labels: list(str)
282
+ :param labels: The set of possible labels. If none is given, then
283
+ the set of all labels attested in the training data will be
284
+ used instead.
285
+ :param gaussian_prior_sigma: The sigma value for a gaussian
286
+ prior on model weights. Currently, this is supported by
287
+ ``megam``. For other algorithms, its value is ignored.
288
+ :param cutoffs: Arguments specifying various conditions under
289
+ which the training should be halted. (Some of the cutoff
290
+ conditions are not supported by some algorithms.)
291
+
292
+ - ``max_iter=v``: Terminate after ``v`` iterations.
293
+ - ``min_ll=v``: Terminate after the negative average
294
+ log-likelihood drops under ``v``.
295
+ - ``min_lldelta=v``: Terminate if a single iteration improves
296
+ log likelihood by less than ``v``.
297
+ """
298
+ if algorithm is None:
299
+ algorithm = 'iis'
300
+ for key in cutoffs:
301
+ if key not in ('max_iter', 'min_ll', 'min_lldelta',
302
+ 'max_acc', 'min_accdelta', 'count_cutoff',
303
+ 'norm', 'explicit', 'bernoulli'):
304
+ raise TypeError('Unexpected keyword arg %r' % key)
305
+ algorithm = algorithm.lower()
306
+ if algorithm == 'iis':
307
+ return train_maxent_classifier_with_iis(
308
+ train_toks, trace, encoding, labels, **cutoffs)
309
+ elif algorithm == 'gis':
310
+ return train_maxent_classifier_with_gis(
311
+ train_toks, trace, encoding, labels, **cutoffs)
312
+ elif algorithm == 'megam':
313
+ return train_maxent_classifier_with_megam(
314
+ train_toks, trace, encoding, labels,
315
+ gaussian_prior_sigma, **cutoffs)
316
+ elif algorithm == 'tadm':
317
+ kwargs = cutoffs
318
+ kwargs['trace'] = trace
319
+ kwargs['encoding'] = encoding
320
+ kwargs['labels'] = labels
321
+ kwargs['gaussian_prior_sigma'] = gaussian_prior_sigma
322
+ return TadmMaxentClassifier.train(train_toks, **kwargs)
323
+ else:
324
+ raise ValueError('Unknown algorithm %s' % algorithm)
325
+
326
+
327
+ #: Alias for MaxentClassifier.
328
+ ConditionalExponentialClassifier = MaxentClassifier
329
+
330
+
331
+ ######################################################################
332
+ #{ Feature Encodings
333
+ ######################################################################
334
+
335
+ class MaxentFeatureEncodingI(object):
336
+ """
337
+ A mapping that converts a set of input-feature values to a vector
338
+ of joint-feature values, given a label. This conversion is
339
+ necessary to translate featuresets into a format that can be used
340
+ by maximum entropy models.
341
+
342
+ The set of joint-features used by a given encoding is fixed, and
343
+ each index in the generated joint-feature vectors corresponds to a
344
+ single joint-feature. The length of the generated joint-feature
345
+ vectors is therefore constant (for a given encoding).
346
+
347
+ Because the joint-feature vectors generated by
348
+ ``MaxentFeatureEncodingI`` are typically very sparse, they are
349
+ represented as a list of ``(index, value)`` tuples, specifying the
350
+ value of each non-zero joint-feature.
351
+
352
+ Feature encodings are generally created using the ``train()``
353
+ method, which generates an appropriate encoding based on the
354
+ input-feature values and labels that are present in a given
355
+ corpus.
356
+ """
357
+ def encode(self, featureset, label):
358
+ """
359
+ Given a (featureset, label) pair, return the corresponding
360
+ vector of joint-feature values. This vector is represented as
361
+ a list of ``(index, value)`` tuples, specifying the value of
362
+ each non-zero joint-feature.
363
+
364
+ :type featureset: dict
365
+ :rtype: list(tuple(int, int))
366
+ """
367
+ raise NotImplementedError()
368
+
369
+ def length(self):
370
+ """
371
+ :return: The size of the fixed-length joint-feature vectors
372
+ that are generated by this encoding.
373
+ :rtype: int
374
+ """
375
+ raise NotImplementedError()
376
+
377
+ def labels(self):
378
+ """
379
+ :return: A list of the \"known labels\" -- i.e., all labels
380
+ ``l`` such that ``self.encode(fs,l)`` can be a nonzero
381
+ joint-feature vector for some value of ``fs``.
382
+ :rtype: list
383
+ """
384
+ raise NotImplementedError()
385
+
386
+ def describe(self, fid):
387
+ """
388
+ :return: A string describing the value of the joint-feature
389
+ whose index in the generated feature vectors is ``fid``.
390
+ :rtype: str
391
+ """
392
+ raise NotImplementedError()
393
+
394
+ def train(cls, train_toks):
395
+ """
396
+ Construct and return new feature encoding, based on a given
397
+ training corpus ``train_toks``.
398
+
399
+ :type train_toks: list(tuple(dict, str))
400
+ :param train_toks: Training data, represented as a list of
401
+ pairs, the first member of which is a feature dictionary,
402
+ and the second of which is a classification label.
403
+ """
404
+ raise NotImplementedError()
405
+
406
+ class FunctionBackedMaxentFeatureEncoding(MaxentFeatureEncodingI):
407
+ """
408
+ A feature encoding that calls a user-supplied function to map a
409
+ given featureset/label pair to a sparse joint-feature vector.
410
+ """
411
+ def __init__(self, func, length, labels):
412
+ """
413
+ Construct a new feature encoding based on the given function.
414
+
415
+ :type func: (callable)
416
+ :param func: A function that takes two arguments, a featureset
417
+ and a label, and returns the sparse joint feature vector
418
+ that encodes them::
419
+
420
+ func(featureset, label) -> feature_vector
421
+
422
+ This sparse joint feature vector (``feature_vector``) is a
423
+ list of ``(index,value)`` tuples.
424
+
425
+ :type length: int
426
+ :param length: The size of the fixed-length joint-feature
427
+ vectors that are generated by this encoding.
428
+
429
+ :type labels: list
430
+ :param labels: A list of the \"known labels\" for this
431
+ encoding -- i.e., all labels ``l`` such that
432
+ ``self.encode(fs,l)`` can be a nonzero joint-feature vector
433
+ for some value of ``fs``.
434
+ """
435
+ self._length = length
436
+ self._func = func
437
+ self._labels = labels
438
+
439
+ def encode(self, featureset, label):
440
+ return self._func(featureset, label)
441
+
442
+ def length(self):
443
+ return self._length
444
+
445
+ def labels(self):
446
+ return self._labels
447
+
448
+ def describe(self, fid):
449
+ return 'no description available'
450
+
451
+ class BinaryMaxentFeatureEncoding(MaxentFeatureEncodingI):
452
+ """
453
+ A feature encoding that generates vectors containing a binary
454
+ joint-features of the form:
455
+
456
+ | joint_feat(fs, l) = { 1 if (fs[fname] == fval) and (l == label)
457
+ | {
458
+ | { 0 otherwise
459
+
460
+ Where ``fname`` is the name of an input-feature, ``fval`` is a value
461
+ for that input-feature, and ``label`` is a label.
462
+
463
+ Typically, these features are constructed based on a training
464
+ corpus, using the ``train()`` method. This method will create one
465
+ feature for each combination of ``fname``, ``fval``, and ``label``
466
+ that occurs at least once in the training corpus.
467
+
468
+ The ``unseen_features`` parameter can be used to add "unseen-value
469
+ features", which are used whenever an input feature has a value
470
+ that was not encountered in the training corpus. These features
471
+ have the form:
472
+
473
+ | joint_feat(fs, l) = { 1 if is_unseen(fname, fs[fname])
474
+ | { and l == label
475
+ | {
476
+ | { 0 otherwise
477
+
478
+ Where ``is_unseen(fname, fval)`` is true if the encoding does not
479
+ contain any joint features that are true when ``fs[fname]==fval``.
480
+
481
+ The ``alwayson_features`` parameter can be used to add "always-on
482
+ features", which have the form::
483
+
484
+ | joint_feat(fs, l) = { 1 if (l == label)
485
+ | {
486
+ | { 0 otherwise
487
+
488
+ These always-on features allow the maxent model to directly model
489
+ the prior probabilities of each label.
490
+ """
491
+ def __init__(self, labels, mapping, unseen_features=False,
492
+ alwayson_features=False):
493
+ """
494
+ :param labels: A list of the \"known labels\" for this encoding.
495
+
496
+ :param mapping: A dictionary mapping from ``(fname,fval,label)``
497
+ tuples to corresponding joint-feature indexes. These
498
+ indexes must be the set of integers from 0...len(mapping).
499
+ If ``mapping[fname,fval,label]=id``, then
500
+ ``self.encode(..., fname:fval, ..., label)[id]`` is 1;
501
+ otherwise, it is 0.
502
+
503
+ :param unseen_features: If true, then include unseen value
504
+ features in the generated joint-feature vectors.
505
+
506
+ :param alwayson_features: If true, then include always-on
507
+ features in the generated joint-feature vectors.
508
+ """
509
+ if set(mapping.values()) != set(range(len(mapping))):
510
+ raise ValueError('Mapping values must be exactly the '
511
+ 'set of integers from 0...len(mapping)')
512
+
513
+ self._labels = list(labels)
514
+ """A list of attested labels."""
515
+
516
+ self._mapping = mapping
517
+ """dict mapping from (fname,fval,label) -> fid"""
518
+
519
+ self._length = len(mapping)
520
+ """The length of generated joint feature vectors."""
521
+
522
+ self._alwayson = None
523
+ """dict mapping from label -> fid"""
524
+
525
+ self._unseen = None
526
+ """dict mapping from fname -> fid"""
527
+
528
+ if alwayson_features:
529
+ self._alwayson = dict((label, i+self._length)
530
+ for (i, label) in enumerate(labels))
531
+ self._length += len(self._alwayson)
532
+
533
+ if unseen_features:
534
+ fnames = set(fname for (fname, fval, label) in mapping)
535
+ self._unseen = dict((fname, i+self._length)
536
+ for (i, fname) in enumerate(fnames))
537
+ self._length += len(fnames)
538
+
539
+ def encode(self, featureset, label):
540
+ # Inherit docs.
541
+ encoding = []
542
+
543
+ # Convert input-features to joint-features:
544
+ for fname, fval in featureset.items():
545
+ # Known feature name & value:
546
+ if (fname, fval, label) in self._mapping:
547
+ encoding.append((self._mapping[fname, fval, label], 1))
548
+
549
+ # Otherwise, we might want to fire an "unseen-value feature".
550
+ elif self._unseen:
551
+ # Have we seen this fname/fval combination with any label?
552
+ for label2 in self._labels:
553
+ if (fname, fval, label2) in self._mapping:
554
+ break # we've seen this fname/fval combo
555
+ # We haven't -- fire the unseen-value feature
556
+ else:
557
+ if fname in self._unseen:
558
+ encoding.append((self._unseen[fname], 1))
559
+
560
+ # Add always-on features:
561
+ if self._alwayson and label in self._alwayson:
562
+ encoding.append((self._alwayson[label], 1))
563
+
564
+ return encoding
565
+
566
+ def describe(self, f_id):
567
+ # Inherit docs.
568
+ if not isinstance(f_id, integer_types):
569
+ raise TypeError('describe() expected an int')
570
+ try:
571
+ self._inv_mapping
572
+ except AttributeError:
573
+ self._inv_mapping = [-1]*len(self._mapping)
574
+ for (info, i) in self._mapping.items():
575
+ self._inv_mapping[i] = info
576
+
577
+ if f_id < len(self._mapping):
578
+ (fname, fval, label) = self._inv_mapping[f_id]
579
+ return '%s==%r and label is %r' % (fname, fval, label)
580
+ elif self._alwayson and f_id in self._alwayson.values():
581
+ for (label, f_id2) in self._alwayson.items():
582
+ if f_id == f_id2:
583
+ return 'label is %r' % label
584
+ elif self._unseen and f_id in self._unseen.values():
585
+ for (fname, f_id2) in self._unseen.items():
586
+ if f_id == f_id2:
587
+ return '%s is unseen' % fname
588
+ else:
589
+ raise ValueError('Bad feature id')
590
+
591
+ def labels(self):
592
+ # Inherit docs.
593
+ return self._labels
594
+
595
+ def length(self):
596
+ # Inherit docs.
597
+ return self._length
598
+
599
+ @classmethod
600
+ def train(cls, train_toks, count_cutoff=0, labels=None, **options):
601
+ """
602
+ Construct and return new feature encoding, based on a given
603
+ training corpus ``train_toks``. See the class description
604
+ ``BinaryMaxentFeatureEncoding`` for a description of the
605
+ joint-features that will be included in this encoding.
606
+
607
+ :type train_toks: list(tuple(dict, str))
608
+ :param train_toks: Training data, represented as a list of
609
+ pairs, the first member of which is a feature dictionary,
610
+ and the second of which is a classification label.
611
+
612
+ :type count_cutoff: int
613
+ :param count_cutoff: A cutoff value that is used to discard
614
+ rare joint-features. If a joint-feature's value is 1
615
+ fewer than ``count_cutoff`` times in the training corpus,
616
+ then that joint-feature is not included in the generated
617
+ encoding.
618
+
619
+ :type labels: list
620
+ :param labels: A list of labels that should be used by the
621
+ classifier. If not specified, then the set of labels
622
+ attested in ``train_toks`` will be used.
623
+
624
+ :param options: Extra parameters for the constructor, such as
625
+ ``unseen_features`` and ``alwayson_features``.
626
+ """
627
+ mapping = {} # maps (fname, fval, label) -> fid
628
+ seen_labels = set() # The set of labels we've encountered
629
+ count = defaultdict(int) # maps (fname, fval) -> count
630
+
631
+ for (tok, label) in train_toks:
632
+ if labels and label not in labels:
633
+ raise ValueError('Unexpected label %s' % label)
634
+ seen_labels.add(label)
635
+
636
+ # Record each of the features.
637
+ for (fname, fval) in tok.items():
638
+
639
+ # If a count cutoff is given, then only add a joint
640
+ # feature once the corresponding (fname, fval, label)
641
+ # tuple exceeds that cutoff.
642
+ count[fname, fval] += 1
643
+ if count[fname, fval] >= count_cutoff:
644
+ if (fname, fval, label) not in mapping:
645
+ mapping[fname, fval, label] = len(mapping)
646
+
647
+ if labels is None:
648
+ labels = seen_labels
649
+ return cls(labels, mapping, **options)
650
+
651
+ class GISEncoding(BinaryMaxentFeatureEncoding):
652
+ """
653
+ A binary feature encoding which adds one new joint-feature to the
654
+ joint-features defined by ``BinaryMaxentFeatureEncoding``: a
655
+ correction feature, whose value is chosen to ensure that the
656
+ sparse vector always sums to a constant non-negative number. This
657
+ new feature is used to ensure two preconditions for the GIS
658
+ training algorithm:
659
+
660
+ - At least one feature vector index must be nonzero for every
661
+ token.
662
+ - The feature vector must sum to a constant non-negative number
663
+ for every token.
664
+ """
665
+ def __init__(self, labels, mapping, unseen_features=False,
666
+ alwayson_features=False, C=None):
667
+ """
668
+ :param C: The correction constant. The value of the correction
669
+ feature is based on this value. In particular, its value is
670
+ ``C - sum([v for (f,v) in encoding])``.
671
+ :seealso: ``BinaryMaxentFeatureEncoding.__init__``
672
+ """
673
+ BinaryMaxentFeatureEncoding.__init__(
674
+ self, labels, mapping, unseen_features, alwayson_features)
675
+ if C is None:
676
+ C = len(set(fname for (fname, fval, label) in mapping))+1
677
+ self._C = C
678
+
679
+ @property
680
+ def C(self):
681
+ """The non-negative constant that all encoded feature vectors
682
+ will sum to."""
683
+ return self._C
684
+
685
+ def encode(self, featureset, label):
686
+ # Get the basic encoding.
687
+ encoding = BinaryMaxentFeatureEncoding.encode(self, featureset, label)
688
+ base_length = BinaryMaxentFeatureEncoding.length(self)
689
+
690
+ # Add a correction feature.
691
+ total = sum(v for (f, v) in encoding)
692
+ if total >= self._C:
693
+ raise ValueError('Correction feature is not high enough!')
694
+ encoding.append((base_length, self._C-total))
695
+
696
+ # Return the result
697
+ return encoding
698
+
699
+ def length(self):
700
+ return BinaryMaxentFeatureEncoding.length(self) + 1
701
+
702
+ def describe(self, f_id):
703
+ if f_id == BinaryMaxentFeatureEncoding.length(self):
704
+ return 'Correction feature (%s)' % self._C
705
+ else:
706
+ return BinaryMaxentFeatureEncoding.describe(self, f_id)
707
+
708
+
709
+ class TadmEventMaxentFeatureEncoding(BinaryMaxentFeatureEncoding):
710
+ def __init__(self, labels, mapping, unseen_features=False,
711
+ alwayson_features=False):
712
+ self._mapping = OrderedDict(mapping)
713
+ self._label_mapping = OrderedDict()
714
+ BinaryMaxentFeatureEncoding.__init__(self, labels, self._mapping,
715
+ unseen_features,
716
+ alwayson_features)
717
+
718
+ def encode(self, featureset, label):
719
+ encoding = []
720
+ for feature, value in featureset.items():
721
+ if (feature, label) not in self._mapping:
722
+ self._mapping[(feature, label)] = len(self._mapping)
723
+ if value not in self._label_mapping:
724
+ if not isinstance(value, int):
725
+ self._label_mapping[value] = len(self._label_mapping)
726
+ else:
727
+ self._label_mapping[value] = value
728
+ encoding.append((self._mapping[(feature, label)],
729
+ self._label_mapping[value]))
730
+ return encoding
731
+
732
+ def labels(self):
733
+ return self._labels
734
+
735
+ def describe(self, fid):
736
+ for (feature, label) in self._mapping:
737
+ if self._mapping[(feature, label)] == fid:
738
+ return (feature, label)
739
+
740
+ def length(self):
741
+ return len(self._mapping)
742
+
743
+ @classmethod
744
+ def train(cls, train_toks, count_cutoff=0, labels=None, **options):
745
+ mapping = OrderedDict()
746
+ if not labels:
747
+ labels = []
748
+
749
+ # This gets read twice, so compute the values in case it's lazy.
750
+ train_toks = list(train_toks)
751
+
752
+ for (featureset, label) in train_toks:
753
+ if label not in labels:
754
+ labels.append(label)
755
+
756
+ for (featureset, label) in train_toks:
757
+ for label in labels:
758
+ for feature in featureset:
759
+ if (feature, label) not in mapping:
760
+ mapping[(feature, label)] = len(mapping)
761
+
762
+ return cls(labels, mapping, **options)
763
+
764
+
765
+ class TypedMaxentFeatureEncoding(MaxentFeatureEncodingI):
766
+ """
767
+ A feature encoding that generates vectors containing integer,
768
+ float and binary joint-features of the form:
769
+
770
+ Binary (for string and boolean features):
771
+
772
+ | joint_feat(fs, l) = { 1 if (fs[fname] == fval) and (l == label)
773
+ | {
774
+ | { 0 otherwise
775
+
776
+ Value (for integer and float features):
777
+
778
+ | joint_feat(fs, l) = { fval if (fs[fname] == type(fval))
779
+ | { and (l == label)
780
+ | {
781
+ | { not encoded otherwise
782
+
783
+ Where ``fname`` is the name of an input-feature, ``fval`` is a value
784
+ for that input-feature, and ``label`` is a label.
785
+
786
+ Typically, these features are constructed based on a training
787
+ corpus, using the ``train()`` method.
788
+
789
+ For string and boolean features [type(fval) not in (int, float)]
790
+ this method will create one feature for each combination of
791
+ ``fname``, ``fval``, and ``label`` that occurs at least once in the
792
+ training corpus.
793
+
794
+ For integer and float features [type(fval) in (int, float)] this
795
+ method will create one feature for each combination of ``fname``
796
+ and ``label`` that occurs at least once in the training corpus.
797
+
798
+ For binary features the ``unseen_features`` parameter can be used
799
+ to add "unseen-value features", which are used whenever an input
800
+ feature has a value that was not encountered in the training
801
+ corpus. These features have the form:
802
+
803
+ | joint_feat(fs, l) = { 1 if is_unseen(fname, fs[fname])
804
+ | { and l == label
805
+ | {
806
+ | { 0 otherwise
807
+
808
+ Where ``is_unseen(fname, fval)`` is true if the encoding does not
809
+ contain any joint features that are true when ``fs[fname]==fval``.
810
+
811
+ The ``alwayson_features`` parameter can be used to add "always-on
812
+ features", which have the form:
813
+
814
+ | joint_feat(fs, l) = { 1 if (l == label)
815
+ | {
816
+ | { 0 otherwise
817
+
818
+ These always-on features allow the maxent model to directly model
819
+ the prior probabilities of each label.
820
+ """
821
+ def __init__(self, labels, mapping, unseen_features=False,
822
+ alwayson_features=False):
823
+ """
824
+ :param labels: A list of the \"known labels\" for this encoding.
825
+
826
+ :param mapping: A dictionary mapping from ``(fname,fval,label)``
827
+ tuples to corresponding joint-feature indexes. These
828
+ indexes must be the set of integers from 0...len(mapping).
829
+ If ``mapping[fname,fval,label]=id``, then
830
+ ``self.encode({..., fname:fval, ...``, label)[id]} is 1;
831
+ otherwise, it is 0.
832
+
833
+ :param unseen_features: If true, then include unseen value
834
+ features in the generated joint-feature vectors.
835
+
836
+ :param alwayson_features: If true, then include always-on
837
+ features in the generated joint-feature vectors.
838
+ """
839
+ if set(mapping.values()) != set(range(len(mapping))):
840
+ raise ValueError('Mapping values must be exactly the '
841
+ 'set of integers from 0...len(mapping)')
842
+
843
+ self._labels = list(labels)
844
+ """A list of attested labels."""
845
+
846
+ self._mapping = mapping
847
+ """dict mapping from (fname,fval,label) -> fid"""
848
+
849
+ self._length = len(mapping)
850
+ """The length of generated joint feature vectors."""
851
+
852
+ self._alwayson = None
853
+ """dict mapping from label -> fid"""
854
+
855
+ self._unseen = None
856
+ """dict mapping from fname -> fid"""
857
+
858
+ if alwayson_features:
859
+ self._alwayson = dict((label, i+self._length)
860
+ for (i, label) in enumerate(labels))
861
+ self._length += len(self._alwayson)
862
+
863
+ if unseen_features:
864
+ fnames = set(fname for (fname, fval, label) in mapping)
865
+ self._unseen = dict((fname, i+self._length)
866
+ for (i, fname) in enumerate(fnames))
867
+ self._length += len(fnames)
868
+
869
+ def encode(self, featureset, label):
870
+ # Inherit docs.
871
+ encoding = []
872
+
873
+ # Convert input-features to joint-features:
874
+ for fname, fval in featureset.items():
875
+ if isinstance(fval, (integer_types, float)):
876
+ # Known feature name & value:
877
+ if (fname, type(fval), label) in self._mapping:
878
+ encoding.append((self._mapping[fname, type(fval),
879
+ label], fval))
880
+ else:
881
+ # Known feature name & value:
882
+ if (fname, fval, label) in self._mapping:
883
+ encoding.append((self._mapping[fname, fval, label], 1))
884
+
885
+ # Otherwise, we might want to fire an "unseen-value feature".
886
+ elif self._unseen:
887
+ # Have we seen this fname/fval combination with any label?
888
+ for label2 in self._labels:
889
+ if (fname, fval, label2) in self._mapping:
890
+ break # we've seen this fname/fval combo
891
+ # We haven't -- fire the unseen-value feature
892
+ else:
893
+ if fname in self._unseen:
894
+ encoding.append((self._unseen[fname], 1))
895
+
896
+
897
+ # Add always-on features:
898
+ if self._alwayson and label in self._alwayson:
899
+ encoding.append((self._alwayson[label], 1))
900
+
901
+ return encoding
902
+
903
+ def describe(self, f_id):
904
+ # Inherit docs.
905
+ if not isinstance(f_id, integer_types):
906
+ raise TypeError('describe() expected an int')
907
+ try:
908
+ self._inv_mapping
909
+ except AttributeError:
910
+ self._inv_mapping = [-1]*len(self._mapping)
911
+ for (info, i) in self._mapping.items():
912
+ self._inv_mapping[i] = info
913
+
914
+ if f_id < len(self._mapping):
915
+ (fname, fval, label) = self._inv_mapping[f_id]
916
+ return '%s==%r and label is %r' % (fname, fval, label)
917
+ elif self._alwayson and f_id in self._alwayson.values():
918
+ for (label, f_id2) in self._alwayson.items():
919
+ if f_id == f_id2:
920
+ return 'label is %r' % label
921
+ elif self._unseen and f_id in self._unseen.values():
922
+ for (fname, f_id2) in self._unseen.items():
923
+ if f_id == f_id2:
924
+ return '%s is unseen' % fname
925
+ else:
926
+ raise ValueError('Bad feature id')
927
+
928
+ def labels(self):
929
+ # Inherit docs.
930
+ return self._labels
931
+
932
+ def length(self):
933
+ # Inherit docs.
934
+ return self._length
935
+
936
+ @classmethod
937
+ def train(cls, train_toks, count_cutoff=0, labels=None, **options):
938
+ """
939
+ Construct and return new feature encoding, based on a given
940
+ training corpus ``train_toks``. See the class description
941
+ ``TypedMaxentFeatureEncoding`` for a description of the
942
+ joint-features that will be included in this encoding.
943
+
944
+ Note: recognized feature values types are (int, float), over
945
+ types are interpreted as regular binary features.
946
+
947
+ :type train_toks: list(tuple(dict, str))
948
+ :param train_toks: Training data, represented as a list of
949
+ pairs, the first member of which is a feature dictionary,
950
+ and the second of which is a classification label.
951
+
952
+ :type count_cutoff: int
953
+ :param count_cutoff: A cutoff value that is used to discard
954
+ rare joint-features. If a joint-feature's value is 1
955
+ fewer than ``count_cutoff`` times in the training corpus,
956
+ then that joint-feature is not included in the generated
957
+ encoding.
958
+
959
+ :type labels: list
960
+ :param labels: A list of labels that should be used by the
961
+ classifier. If not specified, then the set of labels
962
+ attested in ``train_toks`` will be used.
963
+
964
+ :param options: Extra parameters for the constructor, such as
965
+ ``unseen_features`` and ``alwayson_features``.
966
+ """
967
+ mapping = {} # maps (fname, fval, label) -> fid
968
+ seen_labels = set() # The set of labels we've encountered
969
+ count = defaultdict(int) # maps (fname, fval) -> count
970
+
971
+ for (tok, label) in train_toks:
972
+ if labels and label not in labels:
973
+ raise ValueError('Unexpected label %s' % label)
974
+ seen_labels.add(label)
975
+
976
+ # Record each of the features.
977
+ for (fname, fval) in tok.items():
978
+ if type(fval) in (int, float):
979
+ fval = type(fval)
980
+ # If a count cutoff is given, then only add a joint
981
+ # feature once the corresponding (fname, fval, label)
982
+ # tuple exceeds that cutoff.
983
+ count[fname, fval] += 1
984
+ if count[fname, fval] >= count_cutoff:
985
+ if (fname, fval, label) not in mapping:
986
+ mapping[fname, fval, label] = len(mapping)
987
+
988
+ if labels is None:
989
+ labels = seen_labels
990
+ return cls(labels, mapping, **options)
991
+
992
+
993
+
994
+
995
+ ######################################################################
996
+ #{ Classifier Trainer: Generalized Iterative Scaling
997
+ ######################################################################
998
+
999
+ def train_maxent_classifier_with_gis(train_toks, trace=3, encoding=None,
1000
+ labels=None, **cutoffs):
1001
+ """
1002
+ Train a new ``ConditionalExponentialClassifier``, using the given
1003
+ training samples, using the Generalized Iterative Scaling
1004
+ algorithm. This ``ConditionalExponentialClassifier`` will encode
1005
+ the model that maximizes entropy from all the models that are
1006
+ empirically consistent with ``train_toks``.
1007
+
1008
+ :see: ``train_maxent_classifier()`` for parameter descriptions.
1009
+ """
1010
+ cutoffs.setdefault('max_iter', 100)
1011
+ cutoffchecker = CutoffChecker(cutoffs)
1012
+
1013
+ # Construct an encoding from the training data.
1014
+ if encoding is None:
1015
+ encoding = GISEncoding.train(train_toks, labels=labels)
1016
+
1017
+ if not hasattr(encoding, 'C'):
1018
+ raise TypeError('The GIS algorithm requires an encoding that '
1019
+ 'defines C (e.g., GISEncoding).')
1020
+
1021
+ # Cinv is the inverse of the sum of each joint feature vector.
1022
+ # This controls the learning rate: higher Cinv (or lower C) gives
1023
+ # faster learning.
1024
+ Cinv = 1.0/encoding.C
1025
+
1026
+ # Count how many times each feature occurs in the training data.
1027
+ empirical_fcount = calculate_empirical_fcount(train_toks, encoding)
1028
+
1029
+ # Check for any features that are not attested in train_toks.
1030
+ unattested = set(numpy.nonzero(empirical_fcount == 0)[0])
1031
+
1032
+ # Build the classifier. Start with weight=0 for each attested
1033
+ # feature, and weight=-infinity for each unattested feature.
1034
+ weights = numpy.zeros(len(empirical_fcount), 'd')
1035
+ for fid in unattested:
1036
+ weights[fid] = numpy.NINF
1037
+ classifier = ConditionalExponentialClassifier(encoding, weights)
1038
+
1039
+ # Take the log of the empirical fcount.
1040
+ log_empirical_fcount = numpy.log2(empirical_fcount)
1041
+ del empirical_fcount
1042
+
1043
+ if trace > 0:
1044
+ print(' ==> Training (%d iterations)' % cutoffs['max_iter'])
1045
+ if trace > 2:
1046
+ print()
1047
+ print(' Iteration Log Likelihood Accuracy')
1048
+ print(' ---------------------------------------')
1049
+
1050
+ # Train the classifier.
1051
+ try:
1052
+ while True:
1053
+ if trace > 2:
1054
+ ll = cutoffchecker.ll or log_likelihood(classifier, train_toks)
1055
+ acc = cutoffchecker.acc or accuracy(classifier, train_toks)
1056
+ iternum = cutoffchecker.iter
1057
+ print(' %9d %14.5f %9.3f' % (iternum, ll, acc))
1058
+
1059
+ # Use the model to estimate the number of times each
1060
+ # feature should occur in the training data.
1061
+ estimated_fcount = calculate_estimated_fcount(
1062
+ classifier, train_toks, encoding)
1063
+
1064
+ # Take the log of estimated fcount (avoid taking log(0).)
1065
+ for fid in unattested:
1066
+ estimated_fcount[fid] += 1
1067
+ log_estimated_fcount = numpy.log2(estimated_fcount)
1068
+ del estimated_fcount
1069
+
1070
+ # Update the classifier weights
1071
+ weights = classifier.weights()
1072
+ weights += (log_empirical_fcount - log_estimated_fcount) * Cinv
1073
+ classifier.set_weights(weights)
1074
+
1075
+ # Check the log-likelihood & accuracy cutoffs.
1076
+ if cutoffchecker.check(classifier, train_toks):
1077
+ break
1078
+
1079
+ except KeyboardInterrupt:
1080
+ print(' Training stopped: keyboard interrupt')
1081
+ except:
1082
+ raise
1083
+
1084
+ if trace > 2:
1085
+ ll = log_likelihood(classifier, train_toks)
1086
+ acc = accuracy(classifier, train_toks)
1087
+ print(' Final %14.5f %9.3f' % (ll, acc))
1088
+
1089
+ # Return the classifier.
1090
+ return classifier
1091
+
1092
+ def calculate_empirical_fcount(train_toks, encoding):
1093
+ fcount = numpy.zeros(encoding.length(), 'd')
1094
+
1095
+ for tok, label in train_toks:
1096
+ for (index, val) in encoding.encode(tok, label):
1097
+ fcount[index] += val
1098
+
1099
+ return fcount
1100
+
1101
+ def calculate_estimated_fcount(classifier, train_toks, encoding):
1102
+ fcount = numpy.zeros(encoding.length(), 'd')
1103
+
1104
+ for tok, label in train_toks:
1105
+ pdist = classifier.prob_classify(tok)
1106
+ for label in pdist.samples():
1107
+ prob = pdist.prob(label)
1108
+ for (fid, fval) in encoding.encode(tok, label):
1109
+ fcount[fid] += prob*fval
1110
+
1111
+ return fcount
1112
+
1113
+
1114
+ ######################################################################
1115
+ #{ Classifier Trainer: Improved Iterative Scaling
1116
+ ######################################################################
1117
+
1118
+ def train_maxent_classifier_with_iis(train_toks, trace=3, encoding=None,
1119
+ labels=None, **cutoffs):
1120
+ """
1121
+ Train a new ``ConditionalExponentialClassifier``, using the given
1122
+ training samples, using the Improved Iterative Scaling algorithm.
1123
+ This ``ConditionalExponentialClassifier`` will encode the model
1124
+ that maximizes entropy from all the models that are empirically
1125
+ consistent with ``train_toks``.
1126
+
1127
+ :see: ``train_maxent_classifier()`` for parameter descriptions.
1128
+ """
1129
+ cutoffs.setdefault('max_iter', 100)
1130
+ cutoffchecker = CutoffChecker(cutoffs)
1131
+
1132
+ # Construct an encoding from the training data.
1133
+ if encoding is None:
1134
+ encoding = BinaryMaxentFeatureEncoding.train(train_toks, labels=labels)
1135
+
1136
+ # Count how many times each feature occurs in the training data.
1137
+ empirical_ffreq = (calculate_empirical_fcount(train_toks, encoding) /
1138
+ len(train_toks))
1139
+
1140
+ # Find the nf map, and related variables nfarray and nfident.
1141
+ # nf is the sum of the features for a given labeled text.
1142
+ # nfmap compresses this sparse set of values to a dense list.
1143
+ # nfarray performs the reverse operation. nfident is
1144
+ # nfarray multiplied by an identity matrix.
1145
+ nfmap = calculate_nfmap(train_toks, encoding)
1146
+ nfarray = numpy.array(sorted(nfmap, key=nfmap.__getitem__), 'd')
1147
+ nftranspose = numpy.reshape(nfarray, (len(nfarray), 1))
1148
+
1149
+ # Check for any features that are not attested in train_toks.
1150
+ unattested = set(numpy.nonzero(empirical_ffreq == 0)[0])
1151
+
1152
+ # Build the classifier. Start with weight=0 for each attested
1153
+ # feature, and weight=-infinity for each unattested feature.
1154
+ weights = numpy.zeros(len(empirical_ffreq), 'd')
1155
+ for fid in unattested:
1156
+ weights[fid] = numpy.NINF
1157
+ classifier = ConditionalExponentialClassifier(encoding, weights)
1158
+
1159
+ if trace > 0:
1160
+ print(' ==> Training (%d iterations)' % cutoffs['max_iter'])
1161
+ if trace > 2:
1162
+ print()
1163
+ print(' Iteration Log Likelihood Accuracy')
1164
+ print(' ---------------------------------------')
1165
+
1166
+ # Train the classifier.
1167
+ try:
1168
+ while True:
1169
+ if trace > 2:
1170
+ ll = cutoffchecker.ll or log_likelihood(classifier, train_toks)
1171
+ acc = cutoffchecker.acc or accuracy(classifier, train_toks)
1172
+ iternum = cutoffchecker.iter
1173
+ print(' %9d %14.5f %9.3f' % (iternum, ll, acc))
1174
+
1175
+ # Calculate the deltas for this iteration, using Newton's method.
1176
+ deltas = calculate_deltas(
1177
+ train_toks, classifier, unattested, empirical_ffreq,
1178
+ nfmap, nfarray, nftranspose, encoding)
1179
+
1180
+ # Use the deltas to update our weights.
1181
+ weights = classifier.weights()
1182
+ weights += deltas
1183
+ classifier.set_weights(weights)
1184
+
1185
+ # Check the log-likelihood & accuracy cutoffs.
1186
+ if cutoffchecker.check(classifier, train_toks):
1187
+ break
1188
+
1189
+ except KeyboardInterrupt:
1190
+ print(' Training stopped: keyboard interrupt')
1191
+ except:
1192
+ raise
1193
+
1194
+
1195
+ if trace > 2:
1196
+ ll = log_likelihood(classifier, train_toks)
1197
+ acc = accuracy(classifier, train_toks)
1198
+ print(' Final %14.5f %9.3f' % (ll, acc))
1199
+
1200
+ # Return the classifier.
1201
+ return classifier
1202
+
1203
+ def calculate_nfmap(train_toks, encoding):
1204
+ """
1205
+ Construct a map that can be used to compress ``nf`` (which is
1206
+ typically sparse).
1207
+
1208
+ *nf(feature_vector)* is the sum of the feature values for
1209
+ *feature_vector*.
1210
+
1211
+ This represents the number of features that are active for a
1212
+ given labeled text. This method finds all values of *nf(t)*
1213
+ that are attested for at least one token in the given list of
1214
+ training tokens; and constructs a dictionary mapping these
1215
+ attested values to a continuous range *0...N*. For example,
1216
+ if the only values of *nf()* that were attested were 3, 5, and
1217
+ 7, then ``_nfmap`` might return the dictionary ``{3:0, 5:1, 7:2}``.
1218
+
1219
+ :return: A map that can be used to compress ``nf`` to a dense
1220
+ vector.
1221
+ :rtype: dict(int -> int)
1222
+ """
1223
+ # Map from nf to indices. This allows us to use smaller arrays.
1224
+ nfset = set()
1225
+ for tok, _ in train_toks:
1226
+ for label in encoding.labels():
1227
+ nfset.add(sum(val for (id, val) in encoding.encode(tok, label)))
1228
+ return dict((nf, i) for (i, nf) in enumerate(nfset))
1229
+
1230
+ def calculate_deltas(train_toks, classifier, unattested, ffreq_empirical,
1231
+ nfmap, nfarray, nftranspose, encoding):
1232
+ """
1233
+ Calculate the update values for the classifier weights for
1234
+ this iteration of IIS. These update weights are the value of
1235
+ ``delta`` that solves the equation::
1236
+
1237
+ ffreq_empirical[i]
1238
+ =
1239
+ SUM[fs,l] (classifier.prob_classify(fs).prob(l) *
1240
+ feature_vector(fs,l)[i] *
1241
+ exp(delta[i] * nf(feature_vector(fs,l))))
1242
+
1243
+ Where:
1244
+ - *(fs,l)* is a (featureset, label) tuple from ``train_toks``
1245
+ - *feature_vector(fs,l)* = ``encoding.encode(fs,l)``
1246
+ - *nf(vector)* = ``sum([val for (id,val) in vector])``
1247
+
1248
+ This method uses Newton's method to solve this equation for
1249
+ *delta[i]*. In particular, it starts with a guess of
1250
+ ``delta[i]`` = 1; and iteratively updates ``delta`` with:
1251
+
1252
+ | delta[i] -= (ffreq_empirical[i] - sum1[i])/(-sum2[i])
1253
+
1254
+ until convergence, where *sum1* and *sum2* are defined as:
1255
+
1256
+ | sum1[i](delta) = SUM[fs,l] f[i](fs,l,delta)
1257
+ | sum2[i](delta) = SUM[fs,l] (f[i](fs,l,delta).nf(feature_vector(fs,l)))
1258
+ | f[i](fs,l,delta) = (classifier.prob_classify(fs).prob(l) .
1259
+ | feature_vector(fs,l)[i] .
1260
+ | exp(delta[i] . nf(feature_vector(fs,l))))
1261
+
1262
+ Note that *sum1* and *sum2* depend on ``delta``; so they need
1263
+ to be re-computed each iteration.
1264
+
1265
+ The variables ``nfmap``, ``nfarray``, and ``nftranspose`` are
1266
+ used to generate a dense encoding for *nf(ltext)*. This
1267
+ allows ``_deltas`` to calculate *sum1* and *sum2* using
1268
+ matrices, which yields a significant performance improvement.
1269
+
1270
+ :param train_toks: The set of training tokens.
1271
+ :type train_toks: list(tuple(dict, str))
1272
+ :param classifier: The current classifier.
1273
+ :type classifier: ClassifierI
1274
+ :param ffreq_empirical: An array containing the empirical
1275
+ frequency for each feature. The *i*\ th element of this
1276
+ array is the empirical frequency for feature *i*.
1277
+ :type ffreq_empirical: sequence of float
1278
+ :param unattested: An array that is 1 for features that are
1279
+ not attested in the training data; and 0 for features that
1280
+ are attested. In other words, ``unattested[i]==0`` iff
1281
+ ``ffreq_empirical[i]==0``.
1282
+ :type unattested: sequence of int
1283
+ :param nfmap: A map that can be used to compress ``nf`` to a dense
1284
+ vector.
1285
+ :type nfmap: dict(int -> int)
1286
+ :param nfarray: An array that can be used to uncompress ``nf``
1287
+ from a dense vector.
1288
+ :type nfarray: array(float)
1289
+ :param nftranspose: The transpose of ``nfarray``
1290
+ :type nftranspose: array(float)
1291
+ """
1292
+ # These parameters control when we decide that we've
1293
+ # converged. It probably should be possible to set these
1294
+ # manually, via keyword arguments to train.
1295
+ NEWTON_CONVERGE = 1e-12
1296
+ MAX_NEWTON = 300
1297
+
1298
+ deltas = numpy.ones(encoding.length(), 'd')
1299
+
1300
+ # Precompute the A matrix:
1301
+ # A[nf][id] = sum ( p(fs) * p(label|fs) * f(fs,label) )
1302
+ # over all label,fs s.t. num_features[label,fs]=nf
1303
+ A = numpy.zeros((len(nfmap), encoding.length()), 'd')
1304
+
1305
+ for tok, label in train_toks:
1306
+ dist = classifier.prob_classify(tok)
1307
+
1308
+ for label in encoding.labels():
1309
+ # Generate the feature vector
1310
+ feature_vector = encoding.encode(tok, label)
1311
+ # Find the number of active features
1312
+ nf = sum(val for (id, val) in feature_vector)
1313
+ # Update the A matrix
1314
+ for (id, val) in feature_vector:
1315
+ A[nfmap[nf], id] += dist.prob(label) * val
1316
+ A /= len(train_toks)
1317
+
1318
+ # Iteratively solve for delta. Use the following variables:
1319
+ # - nf_delta[x][y] = nfarray[x] * delta[y]
1320
+ # - exp_nf_delta[x][y] = exp(nf[x] * delta[y])
1321
+ # - nf_exp_nf_delta[x][y] = nf[x] * exp(nf[x] * delta[y])
1322
+ # - sum1[i][nf] = sum p(fs)p(label|fs)f[i](label,fs)
1323
+ # exp(delta[i]nf)
1324
+ # - sum2[i][nf] = sum p(fs)p(label|fs)f[i](label,fs)
1325
+ # nf exp(delta[i]nf)
1326
+ for rangenum in range(MAX_NEWTON):
1327
+ nf_delta = numpy.outer(nfarray, deltas)
1328
+ exp_nf_delta = 2 ** nf_delta
1329
+ nf_exp_nf_delta = nftranspose * exp_nf_delta
1330
+ sum1 = numpy.sum(exp_nf_delta * A, axis=0)
1331
+ sum2 = numpy.sum(nf_exp_nf_delta * A, axis=0)
1332
+
1333
+ # Avoid division by zero.
1334
+ for fid in unattested:
1335
+ sum2[fid] += 1
1336
+
1337
+ # Update the deltas.
1338
+ deltas -= (ffreq_empirical - sum1) / -sum2
1339
+
1340
+ # We can stop once we converge.
1341
+ n_error = (numpy.sum(abs((ffreq_empirical-sum1)))/
1342
+ numpy.sum(abs(deltas)))
1343
+ if n_error < NEWTON_CONVERGE:
1344
+ return deltas
1345
+
1346
+ return deltas
1347
+
1348
+ ######################################################################
1349
+ #{ Classifier Trainer: megam
1350
+ ######################################################################
1351
+
1352
+ # [xx] possible extension: add support for using implicit file format;
1353
+ # this would need to put requirements on what encoding is used. But
1354
+ # we may need this for other maxent classifier trainers that require
1355
+ # implicit formats anyway.
1356
+ def train_maxent_classifier_with_megam(train_toks, trace=3, encoding=None,
1357
+ labels=None, gaussian_prior_sigma=0,
1358
+ **kwargs):
1359
+ """
1360
+ Train a new ``ConditionalExponentialClassifier``, using the given
1361
+ training samples, using the external ``megam`` library. This
1362
+ ``ConditionalExponentialClassifier`` will encode the model that
1363
+ maximizes entropy from all the models that are empirically
1364
+ consistent with ``train_toks``.
1365
+
1366
+ :see: ``train_maxent_classifier()`` for parameter descriptions.
1367
+ :see: ``nltk.classify.megam``
1368
+ """
1369
+
1370
+ explicit = True
1371
+ bernoulli = True
1372
+ if 'explicit' in kwargs:
1373
+ explicit = kwargs['explicit']
1374
+ if 'bernoulli' in kwargs:
1375
+ bernoulli = kwargs['bernoulli']
1376
+
1377
+ # Construct an encoding from the training data.
1378
+ if encoding is None:
1379
+ # Count cutoff can also be controlled by megam with the -minfc
1380
+ # option. Not sure where the best place for it is.
1381
+ count_cutoff = kwargs.get('count_cutoff', 0)
1382
+ encoding = BinaryMaxentFeatureEncoding.train(train_toks, count_cutoff,
1383
+ labels=labels,
1384
+ alwayson_features=True)
1385
+ elif labels is not None:
1386
+ raise ValueError('Specify encoding or labels, not both')
1387
+
1388
+ # Write a training file for megam.
1389
+ try:
1390
+ fd, trainfile_name = tempfile.mkstemp(prefix='nltk-')
1391
+ with open(trainfile_name, 'w') as trainfile:
1392
+ write_megam_file(train_toks, encoding, trainfile,
1393
+ explicit=explicit, bernoulli=bernoulli)
1394
+ os.close(fd)
1395
+ except (OSError, IOError, ValueError) as e:
1396
+ raise ValueError('Error while creating megam training file: %s' % e)
1397
+
1398
+ # Run megam on the training file.
1399
+ options = []
1400
+ options += ['-nobias', '-repeat', '10']
1401
+ if explicit:
1402
+ options += ['-explicit']
1403
+ if not bernoulli:
1404
+ options += ['-fvals']
1405
+ if gaussian_prior_sigma:
1406
+ # Lambda is just the precision of the Gaussian prior, i.e. it's the
1407
+ # inverse variance, so the parameter conversion is 1.0/sigma**2.
1408
+ # See http://www.umiacs.umd.edu/~hal/docs/daume04cg-bfgs.pdf.
1409
+ inv_variance = 1.0 / gaussian_prior_sigma**2
1410
+ else:
1411
+ inv_variance = 0
1412
+ options += ['-lambda', '%.2f' % inv_variance, '-tune']
1413
+ if trace < 3:
1414
+ options += ['-quiet']
1415
+ if 'max_iter' in kwargs:
1416
+ options += ['-maxi', '%s' % kwargs['max_iter']]
1417
+ if 'll_delta' in kwargs:
1418
+ # [xx] this is actually a perplexity delta, not a log
1419
+ # likelihood delta
1420
+ options += ['-dpp', '%s' % abs(kwargs['ll_delta'])]
1421
+ if hasattr(encoding, 'cost'):
1422
+ options += ['-multilabel'] # each possible la
1423
+ options += ['multiclass', trainfile_name]
1424
+ stdout = call_megam(options)
1425
+ # print './megam_i686.opt ', ' '.join(options)
1426
+ # Delete the training file
1427
+ try:
1428
+ os.remove(trainfile_name)
1429
+ except (OSError, IOError) as e:
1430
+ print('Warning: unable to delete %s: %s' % (trainfile_name, e))
1431
+
1432
+ # Parse the generated weight vector.
1433
+ weights = parse_megam_weights(stdout, encoding.length(), explicit)
1434
+
1435
+ # Convert from base-e to base-2 weights.
1436
+ weights *= numpy.log2(numpy.e)
1437
+
1438
+ # Build the classifier
1439
+ return MaxentClassifier(encoding, weights)
1440
+
1441
+ ######################################################################
1442
+ #{ Classifier Trainer: tadm
1443
+ ######################################################################
1444
+
1445
+ class TadmMaxentClassifier(MaxentClassifier):
1446
+ @classmethod
1447
+ def train(cls, train_toks, **kwargs):
1448
+ algorithm = kwargs.get('algorithm', 'tao_lmvm')
1449
+ trace = kwargs.get('trace', 3)
1450
+ encoding = kwargs.get('encoding', None)
1451
+ labels = kwargs.get('labels', None)
1452
+ sigma = kwargs.get('gaussian_prior_sigma', 0)
1453
+ count_cutoff = kwargs.get('count_cutoff', 0)
1454
+ max_iter = kwargs.get('max_iter')
1455
+ ll_delta = kwargs.get('min_lldelta')
1456
+
1457
+ # Construct an encoding from the training data.
1458
+ if not encoding:
1459
+ encoding = TadmEventMaxentFeatureEncoding.train(train_toks,
1460
+ count_cutoff,
1461
+ labels=labels)
1462
+
1463
+ trainfile_fd, trainfile_name = \
1464
+ tempfile.mkstemp(prefix='nltk-tadm-events-', suffix='.gz')
1465
+ weightfile_fd, weightfile_name = \
1466
+ tempfile.mkstemp(prefix='nltk-tadm-weights-')
1467
+
1468
+ trainfile = gzip_open_unicode(trainfile_name, 'w')
1469
+ write_tadm_file(train_toks, encoding, trainfile)
1470
+ trainfile.close()
1471
+
1472
+ options = []
1473
+ options.extend(['-monitor'])
1474
+ options.extend(['-method', algorithm])
1475
+ if sigma:
1476
+ options.extend(['-l2', '%.6f' % sigma**2])
1477
+ if max_iter:
1478
+ options.extend(['-max_it', '%d' % max_iter])
1479
+ if ll_delta:
1480
+ options.extend(['-fatol', '%.6f' % abs(ll_delta)])
1481
+ options.extend(['-events_in', trainfile_name])
1482
+ options.extend(['-params_out', weightfile_name])
1483
+ if trace < 3:
1484
+ options.extend(['2>&1'])
1485
+ else:
1486
+ options.extend(['-summary'])
1487
+
1488
+ call_tadm(options)
1489
+
1490
+ with open(weightfile_name, 'r') as weightfile:
1491
+ weights = parse_tadm_weights(weightfile)
1492
+
1493
+ os.remove(trainfile_name)
1494
+ os.remove(weightfile_name)
1495
+
1496
+ # Convert from base-e to base-2 weights.
1497
+ weights *= numpy.log2(numpy.e)
1498
+
1499
+ # Build the classifier
1500
+ return cls(encoding, weights)
1501
+
1502
+ ######################################################################
1503
+ #{ Demo
1504
+ ######################################################################
1505
+ def demo():
1506
+ from nltk.classify.util import names_demo
1507
+ classifier = names_demo(MaxentClassifier.train)
1508
+
1509
+ if __name__ == '__main__':
1510
+ demo()