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,1277 +1,1277 @@
1
- # Natural Language Toolkit: Hidden Markov Model
2
- #
3
- # Copyright (C) 2001-2015 NLTK Project
4
- # Author: Trevor Cohn <tacohn@csse.unimelb.edu.au>
5
- # Philip Blunsom <pcbl@csse.unimelb.edu.au>
6
- # Tiago Tresoldi <tiago@tresoldi.pro.br> (fixes)
7
- # Steven Bird <stevenbird1@gmail.com> (fixes)
8
- # Joseph Frazee <jfrazee@mail.utexas.edu> (fixes)
9
- # Steven Xu <xxu@student.unimelb.edu.au> (fixes)
10
- # URL: <http://nltk.org/>
11
- # For license information, see LICENSE.TXT
12
-
13
- """
14
- Hidden Markov Models (HMMs) largely used to assign the correct label sequence
15
- to sequential data or assess the probability of a given label and data
16
- sequence. These models are finite state machines characterised by a number of
17
- states, transitions between these states, and output symbols emitted while in
18
- each state. The HMM is an extension to the Markov chain, where each state
19
- corresponds deterministically to a given event. In the HMM the observation is
20
- a probabilistic function of the state. HMMs share the Markov chain's
21
- assumption, being that the probability of transition from one state to another
22
- only depends on the current state - i.e. the series of states that led to the
23
- current state are not used. They are also time invariant.
24
-
25
- The HMM is a directed graph, with probability weighted edges (representing the
26
- probability of a transition between the source and sink states) where each
27
- vertex emits an output symbol when entered. The symbol (or observation) is
28
- non-deterministically generated. For this reason, knowing that a sequence of
29
- output observations was generated by a given HMM does not mean that the
30
- corresponding sequence of states (and what the current state is) is known.
31
- This is the 'hidden' in the hidden markov model.
32
-
33
- Formally, a HMM can be characterised by:
34
-
35
- - the output observation alphabet. This is the set of symbols which may be
36
- observed as output of the system.
37
- - the set of states.
38
- - the transition probabilities *a_{ij} = P(s_t = j | s_{t-1} = i)*. These
39
- represent the probability of transition to each state from a given state.
40
- - the output probability matrix *b_i(k) = P(X_t = o_k | s_t = i)*. These
41
- represent the probability of observing each symbol in a given state.
42
- - the initial state distribution. This gives the probability of starting
43
- in each state.
44
-
45
- To ground this discussion, take a common NLP application, part-of-speech (POS)
46
- tagging. An HMM is desirable for this task as the highest probability tag
47
- sequence can be calculated for a given sequence of word forms. This differs
48
- from other tagging techniques which often tag each word individually, seeking
49
- to optimise each individual tagging greedily without regard to the optimal
50
- combination of tags for a larger unit, such as a sentence. The HMM does this
51
- with the Viterbi algorithm, which efficiently computes the optimal path
52
- through the graph given the sequence of words forms.
53
-
54
- In POS tagging the states usually have a 1:1 correspondence with the tag
55
- alphabet - i.e. each state represents a single tag. The output observation
56
- alphabet is the set of word forms (the lexicon), and the remaining three
57
- parameters are derived by a training regime. With this information the
58
- probability of a given sentence can be easily derived, by simply summing the
59
- probability of each distinct path through the model. Similarly, the highest
60
- probability tagging sequence can be derived with the Viterbi algorithm,
61
- yielding a state sequence which can be mapped into a tag sequence.
62
-
63
- This discussion assumes that the HMM has been trained. This is probably the
64
- most difficult task with the model, and requires either MLE estimates of the
65
- parameters or unsupervised learning using the Baum-Welch algorithm, a variant
66
- of EM.
67
-
68
- For more information, please consult the source code for this module,
69
- which includes extensive demonstration code.
70
- """
71
- from __future__ import print_function, unicode_literals, division
72
-
73
- import re
74
- import itertools
75
-
76
- try:
77
- import numpy as np
78
- except ImportError:
79
- pass
80
-
81
- from nltk.probability import (FreqDist, ConditionalFreqDist,
82
- ConditionalProbDist, DictionaryProbDist,
83
- DictionaryConditionalProbDist,
84
- LidstoneProbDist, MutableProbDist,
85
- MLEProbDist, RandomProbDist)
86
- from nltk.metrics import accuracy
87
- from nltk.util import LazyMap, unique_list
88
- from nltk.compat import python_2_unicode_compatible, izip, imap
89
- from nltk.tag.api import TaggerI
90
-
91
-
92
- _TEXT = 0 # index of text in a tuple
93
- _TAG = 1 # index of tag in a tuple
94
-
95
- def _identity(labeled_symbols):
96
- return labeled_symbols
97
-
98
- @python_2_unicode_compatible
99
- class HiddenMarkovModelTagger(TaggerI):
100
- """
101
- Hidden Markov model class, a generative model for labelling sequence data.
102
- These models define the joint probability of a sequence of symbols and
103
- their labels (state transitions) as the product of the starting state
104
- probability, the probability of each state transition, and the probability
105
- of each observation being generated from each state. This is described in
106
- more detail in the module documentation.
107
-
108
- This implementation is based on the HMM description in Chapter 8, Huang,
109
- Acero and Hon, Spoken Language Processing and includes an extension for
110
- training shallow HMM parsers or specialized HMMs as in Molina et.
111
- al, 2002. A specialized HMM modifies training data by applying a
112
- specialization function to create a new training set that is more
113
- appropriate for sequential tagging with an HMM. A typical use case is
114
- chunking.
115
-
116
- :param symbols: the set of output symbols (alphabet)
117
- :type symbols: seq of any
118
- :param states: a set of states representing state space
119
- :type states: seq of any
120
- :param transitions: transition probabilities; Pr(s_i | s_j) is the
121
- probability of transition from state i given the model is in
122
- state_j
123
- :type transitions: ConditionalProbDistI
124
- :param outputs: output probabilities; Pr(o_k | s_i) is the probability
125
- of emitting symbol k when entering state i
126
- :type outputs: ConditionalProbDistI
127
- :param priors: initial state distribution; Pr(s_i) is the probability
128
- of starting in state i
129
- :type priors: ProbDistI
130
- :param transform: an optional function for transforming training
131
- instances, defaults to the identity function.
132
- :type transform: callable
133
- """
134
- def __init__(self, symbols, states, transitions, outputs, priors,
135
- transform=_identity):
136
- self._symbols = unique_list(symbols)
137
- self._states = unique_list(states)
138
- self._transitions = transitions
139
- self._outputs = outputs
140
- self._priors = priors
141
- self._cache = None
142
- self._transform = transform
143
-
144
- @classmethod
145
- def _train(cls, labeled_sequence, test_sequence=None,
146
- unlabeled_sequence=None, transform=_identity,
147
- estimator=None, **kwargs):
148
-
149
- if estimator is None:
150
- def estimator(fd, bins):
151
- return LidstoneProbDist(fd, 0.1, bins)
152
-
153
- labeled_sequence = LazyMap(transform, labeled_sequence)
154
- symbols = unique_list(word for sent in labeled_sequence
155
- for word, tag in sent)
156
- tag_set = unique_list(tag for sent in labeled_sequence
157
- for word, tag in sent)
158
-
159
- trainer = HiddenMarkovModelTrainer(tag_set, symbols)
160
- hmm = trainer.train_supervised(labeled_sequence, estimator=estimator)
161
- hmm = cls(hmm._symbols, hmm._states, hmm._transitions, hmm._outputs,
162
- hmm._priors, transform=transform)
163
-
164
- if test_sequence:
165
- hmm.test(test_sequence, verbose=kwargs.get('verbose', False))
166
-
167
- if unlabeled_sequence:
168
- max_iterations = kwargs.get('max_iterations', 5)
169
- hmm = trainer.train_unsupervised(unlabeled_sequence, model=hmm,
170
- max_iterations=max_iterations)
171
- if test_sequence:
172
- hmm.test(test_sequence, verbose=kwargs.get('verbose', False))
173
-
174
- return hmm
175
-
176
- @classmethod
177
- def train(cls, labeled_sequence, test_sequence=None,
178
- unlabeled_sequence=None, **kwargs):
179
- """
180
- Train a new HiddenMarkovModelTagger using the given labeled and
181
- unlabeled training instances. Testing will be performed if test
182
- instances are provided.
183
-
184
- :return: a hidden markov model tagger
185
- :rtype: HiddenMarkovModelTagger
186
- :param labeled_sequence: a sequence of labeled training instances,
187
- i.e. a list of sentences represented as tuples
188
- :type labeled_sequence: list(list)
189
- :param test_sequence: a sequence of labeled test instances
190
- :type test_sequence: list(list)
191
- :param unlabeled_sequence: a sequence of unlabeled training instances,
192
- i.e. a list of sentences represented as words
193
- :type unlabeled_sequence: list(list)
194
- :param transform: an optional function for transforming training
195
- instances, defaults to the identity function, see ``transform()``
196
- :type transform: function
197
- :param estimator: an optional function or class that maps a
198
- condition's frequency distribution to its probability
199
- distribution, defaults to a Lidstone distribution with gamma = 0.1
200
- :type estimator: class or function
201
- :param verbose: boolean flag indicating whether training should be
202
- verbose or include printed output
203
- :type verbose: bool
204
- :param max_iterations: number of Baum-Welch interations to perform
205
- :type max_iterations: int
206
- """
207
- return cls._train(labeled_sequence, test_sequence,
208
- unlabeled_sequence, **kwargs)
209
-
210
- def probability(self, sequence):
211
- """
212
- Returns the probability of the given symbol sequence. If the sequence
213
- is labelled, then returns the joint probability of the symbol, state
214
- sequence. Otherwise, uses the forward algorithm to find the
215
- probability over all label sequences.
216
-
217
- :return: the probability of the sequence
218
- :rtype: float
219
- :param sequence: the sequence of symbols which must contain the TEXT
220
- property, and optionally the TAG property
221
- :type sequence: Token
222
- """
223
- return 2**(self.log_probability(self._transform(sequence)))
224
-
225
- def log_probability(self, sequence):
226
- """
227
- Returns the log-probability of the given symbol sequence. If the
228
- sequence is labelled, then returns the joint log-probability of the
229
- symbol, state sequence. Otherwise, uses the forward algorithm to find
230
- the log-probability over all label sequences.
231
-
232
- :return: the log-probability of the sequence
233
- :rtype: float
234
- :param sequence: the sequence of symbols which must contain the TEXT
235
- property, and optionally the TAG property
236
- :type sequence: Token
237
- """
238
- sequence = self._transform(sequence)
239
-
240
- T = len(sequence)
241
-
242
- if T > 0 and sequence[0][_TAG]:
243
- last_state = sequence[0][_TAG]
244
- p = self._priors.logprob(last_state) + \
245
- self._output_logprob(last_state, sequence[0][_TEXT])
246
- for t in range(1, T):
247
- state = sequence[t][_TAG]
248
- p += self._transitions[last_state].logprob(state) + \
249
- self._output_logprob(state, sequence[t][_TEXT])
250
- last_state = state
251
- return p
252
- else:
253
- alpha = self._forward_probability(sequence)
254
- p = logsumexp2(alpha[T-1])
255
- return p
256
-
257
- def tag(self, unlabeled_sequence):
258
- """
259
- Tags the sequence with the highest probability state sequence. This
260
- uses the best_path method to find the Viterbi path.
261
-
262
- :return: a labelled sequence of symbols
263
- :rtype: list
264
- :param unlabeled_sequence: the sequence of unlabeled symbols
265
- :type unlabeled_sequence: list
266
- """
267
- unlabeled_sequence = self._transform(unlabeled_sequence)
268
- return self._tag(unlabeled_sequence)
269
-
270
- def _tag(self, unlabeled_sequence):
271
- path = self._best_path(unlabeled_sequence)
272
- return list(izip(unlabeled_sequence, path))
273
-
274
- def _output_logprob(self, state, symbol):
275
- """
276
- :return: the log probability of the symbol being observed in the given
277
- state
278
- :rtype: float
279
- """
280
- return self._outputs[state].logprob(symbol)
281
-
282
- def _create_cache(self):
283
- """
284
- The cache is a tuple (P, O, X, S) where:
285
-
286
- - S maps symbols to integers. I.e., it is the inverse
287
- mapping from self._symbols; for each symbol s in
288
- self._symbols, the following is true::
289
-
290
- self._symbols[S[s]] == s
291
-
292
- - O is the log output probabilities::
293
-
294
- O[i,k] = log( P(token[t]=sym[k]|tag[t]=state[i]) )
295
-
296
- - X is the log transition probabilities::
297
-
298
- X[i,j] = log( P(tag[t]=state[j]|tag[t-1]=state[i]) )
299
-
300
- - P is the log prior probabilities::
301
-
302
- P[i] = log( P(tag[0]=state[i]) )
303
- """
304
- if not self._cache:
305
- N = len(self._states)
306
- M = len(self._symbols)
307
- P = np.zeros(N, np.float32)
308
- X = np.zeros((N, N), np.float32)
309
- O = np.zeros((N, M), np.float32)
310
- for i in range(N):
311
- si = self._states[i]
312
- P[i] = self._priors.logprob(si)
313
- for j in range(N):
314
- X[i, j] = self._transitions[si].logprob(self._states[j])
315
- for k in range(M):
316
- O[i, k] = self._output_logprob(si, self._symbols[k])
317
- S = {}
318
- for k in range(M):
319
- S[self._symbols[k]] = k
320
- self._cache = (P, O, X, S)
321
-
322
- def _update_cache(self, symbols):
323
- # add new symbols to the symbol table and repopulate the output
324
- # probabilities and symbol table mapping
325
- if symbols:
326
- self._create_cache()
327
- P, O, X, S = self._cache
328
- for symbol in symbols:
329
- if symbol not in self._symbols:
330
- self._cache = None
331
- self._symbols.append(symbol)
332
- # don't bother with the work if there aren't any new symbols
333
- if not self._cache:
334
- N = len(self._states)
335
- M = len(self._symbols)
336
- Q = O.shape[1]
337
- # add new columns to the output probability table without
338
- # destroying the old probabilities
339
- O = np.hstack([O, np.zeros((N, M - Q), np.float32)])
340
- for i in range(N):
341
- si = self._states[i]
342
- # only calculate probabilities for new symbols
343
- for k in range(Q, M):
344
- O[i, k] = self._output_logprob(si, self._symbols[k])
345
- # only create symbol mappings for new symbols
346
- for k in range(Q, M):
347
- S[self._symbols[k]] = k
348
- self._cache = (P, O, X, S)
349
-
350
- def reset_cache(self):
351
- self._cache = None
352
-
353
- def best_path(self, unlabeled_sequence):
354
- """
355
- Returns the state sequence of the optimal (most probable) path through
356
- the HMM. Uses the Viterbi algorithm to calculate this part by dynamic
357
- programming.
358
-
359
- :return: the state sequence
360
- :rtype: sequence of any
361
- :param unlabeled_sequence: the sequence of unlabeled symbols
362
- :type unlabeled_sequence: list
363
- """
364
- unlabeled_sequence = self._transform(unlabeled_sequence)
365
- return self._best_path(unlabeled_sequence)
366
-
367
- def _best_path(self, unlabeled_sequence):
368
- T = len(unlabeled_sequence)
369
- N = len(self._states)
370
- self._create_cache()
371
- self._update_cache(unlabeled_sequence)
372
- P, O, X, S = self._cache
373
-
374
- V = np.zeros((T, N), np.float32)
375
- B = -np.ones((T, N), np.int)
376
-
377
- V[0] = P + O[:, S[unlabeled_sequence[0]]]
378
- for t in range(1, T):
379
- for j in range(N):
380
- vs = V[t-1, :] + X[:, j]
381
- best = np.argmax(vs)
382
- V[t, j] = vs[best] + O[j, S[unlabeled_sequence[t]]]
383
- B[t, j] = best
384
-
385
- current = np.argmax(V[T-1,:])
386
- sequence = [current]
387
- for t in range(T-1, 0, -1):
388
- last = B[t, current]
389
- sequence.append(last)
390
- current = last
391
-
392
- sequence.reverse()
393
- return list(map(self._states.__getitem__, sequence))
394
-
395
- def best_path_simple(self, unlabeled_sequence):
396
- """
397
- Returns the state sequence of the optimal (most probable) path through
398
- the HMM. Uses the Viterbi algorithm to calculate this part by dynamic
399
- programming. This uses a simple, direct method, and is included for
400
- teaching purposes.
401
-
402
- :return: the state sequence
403
- :rtype: sequence of any
404
- :param unlabeled_sequence: the sequence of unlabeled symbols
405
- :type unlabeled_sequence: list
406
- """
407
- unlabeled_sequence = self._transform(unlabeled_sequence)
408
- return self._best_path_simple(unlabeled_sequence)
409
-
410
- def _best_path_simple(self, unlabeled_sequence):
411
- T = len(unlabeled_sequence)
412
- N = len(self._states)
413
- V = np.zeros((T, N), np.float64)
414
- B = {}
415
-
416
- # find the starting log probabilities for each state
417
- symbol = unlabeled_sequence[0]
418
- for i, state in enumerate(self._states):
419
- V[0, i] = self._priors.logprob(state) + \
420
- self._output_logprob(state, symbol)
421
- B[0, state] = None
422
-
423
- # find the maximum log probabilities for reaching each state at time t
424
- for t in range(1, T):
425
- symbol = unlabeled_sequence[t]
426
- for j in range(N):
427
- sj = self._states[j]
428
- best = None
429
- for i in range(N):
430
- si = self._states[i]
431
- va = V[t-1, i] + self._transitions[si].logprob(sj)
432
- if not best or va > best[0]:
433
- best = (va, si)
434
- V[t, j] = best[0] + self._output_logprob(sj, symbol)
435
- B[t, sj] = best[1]
436
-
437
- # find the highest probability final state
438
- best = None
439
- for i in range(N):
440
- val = V[T-1, i]
441
- if not best or val > best[0]:
442
- best = (val, self._states[i])
443
-
444
- # traverse the back-pointers B to find the state sequence
445
- current = best[1]
446
- sequence = [current]
447
- for t in range(T-1, 0, -1):
448
- last = B[t, current]
449
- sequence.append(last)
450
- current = last
451
-
452
- sequence.reverse()
453
- return sequence
454
-
455
- def random_sample(self, rng, length):
456
- """
457
- Randomly sample the HMM to generate a sentence of a given length. This
458
- samples the prior distribution then the observation distribution and
459
- transition distribution for each subsequent observation and state.
460
- This will mostly generate unintelligible garbage, but can provide some
461
- amusement.
462
-
463
- :return: the randomly created state/observation sequence,
464
- generated according to the HMM's probability
465
- distributions. The SUBTOKENS have TEXT and TAG
466
- properties containing the observation and state
467
- respectively.
468
- :rtype: list
469
- :param rng: random number generator
470
- :type rng: Random (or any object with a random() method)
471
- :param length: desired output length
472
- :type length: int
473
- """
474
-
475
- # sample the starting state and symbol prob dists
476
- tokens = []
477
- state = self._sample_probdist(self._priors, rng.random(), self._states)
478
- symbol = self._sample_probdist(self._outputs[state],
479
- rng.random(), self._symbols)
480
- tokens.append((symbol, state))
481
-
482
- for i in range(1, length):
483
- # sample the state transition and symbol prob dists
484
- state = self._sample_probdist(self._transitions[state],
485
- rng.random(), self._states)
486
- symbol = self._sample_probdist(self._outputs[state],
487
- rng.random(), self._symbols)
488
- tokens.append((symbol, state))
489
-
490
- return tokens
491
-
492
- def _sample_probdist(self, probdist, p, samples):
493
- cum_p = 0
494
- for sample in samples:
495
- add_p = probdist.prob(sample)
496
- if cum_p <= p <= cum_p + add_p:
497
- return sample
498
- cum_p += add_p
499
- raise Exception('Invalid probability distribution - '
500
- 'does not sum to one')
501
-
502
- def entropy(self, unlabeled_sequence):
503
- """
504
- Returns the entropy over labellings of the given sequence. This is
505
- given by::
506
-
507
- H(O) = - sum_S Pr(S | O) log Pr(S | O)
508
-
509
- where the summation ranges over all state sequences, S. Let
510
- *Z = Pr(O) = sum_S Pr(S, O)}* where the summation ranges over all state
511
- sequences and O is the observation sequence. As such the entropy can
512
- be re-expressed as::
513
-
514
- H = - sum_S Pr(S | O) log [ Pr(S, O) / Z ]
515
- = log Z - sum_S Pr(S | O) log Pr(S, 0)
516
- = log Z - sum_S Pr(S | O) [ log Pr(S_0) + sum_t Pr(S_t | S_{t-1}) + sum_t Pr(O_t | S_t) ]
517
-
518
- The order of summation for the log terms can be flipped, allowing
519
- dynamic programming to be used to calculate the entropy. Specifically,
520
- we use the forward and backward probabilities (alpha, beta) giving::
521
-
522
- H = log Z - sum_s0 alpha_0(s0) beta_0(s0) / Z * log Pr(s0)
523
- + sum_t,si,sj alpha_t(si) Pr(sj | si) Pr(O_t+1 | sj) beta_t(sj) / Z * log Pr(sj | si)
524
- + sum_t,st alpha_t(st) beta_t(st) / Z * log Pr(O_t | st)
525
-
526
- This simply uses alpha and beta to find the probabilities of partial
527
- sequences, constrained to include the given state(s) at some point in
528
- time.
529
- """
530
- unlabeled_sequence = self._transform(unlabeled_sequence)
531
-
532
- T = len(unlabeled_sequence)
533
- N = len(self._states)
534
-
535
- alpha = self._forward_probability(unlabeled_sequence)
536
- beta = self._backward_probability(unlabeled_sequence)
537
- normalisation = logsumexp2(alpha[T-1])
538
-
539
- entropy = normalisation
540
-
541
- # starting state, t = 0
542
- for i, state in enumerate(self._states):
543
- p = 2**(alpha[0, i] + beta[0, i] - normalisation)
544
- entropy -= p * self._priors.logprob(state)
545
- #print 'p(s_0 = %s) =' % state, p
546
-
547
- # state transitions
548
- for t0 in range(T - 1):
549
- t1 = t0 + 1
550
- for i0, s0 in enumerate(self._states):
551
- for i1, s1 in enumerate(self._states):
552
- p = 2**(alpha[t0, i0] + self._transitions[s0].logprob(s1) +
553
- self._outputs[s1].logprob(
554
- unlabeled_sequence[t1][_TEXT]) +
555
- beta[t1, i1] - normalisation)
556
- entropy -= p * self._transitions[s0].logprob(s1)
557
- #print 'p(s_%d = %s, s_%d = %s) =' % (t0, s0, t1, s1), p
558
-
559
- # symbol emissions
560
- for t in range(T):
561
- for i, state in enumerate(self._states):
562
- p = 2**(alpha[t, i] + beta[t, i] - normalisation)
563
- entropy -= p * self._outputs[state].logprob(
564
- unlabeled_sequence[t][_TEXT])
565
- #print 'p(s_%d = %s) =' % (t, state), p
566
-
567
- return entropy
568
-
569
- def point_entropy(self, unlabeled_sequence):
570
- """
571
- Returns the pointwise entropy over the possible states at each
572
- position in the chain, given the observation sequence.
573
- """
574
- unlabeled_sequence = self._transform(unlabeled_sequence)
575
-
576
- T = len(unlabeled_sequence)
577
- N = len(self._states)
578
-
579
- alpha = self._forward_probability(unlabeled_sequence)
580
- beta = self._backward_probability(unlabeled_sequence)
581
- normalisation = logsumexp2(alpha[T-1])
582
-
583
- entropies = np.zeros(T, np.float64)
584
- probs = np.zeros(N, np.float64)
585
- for t in range(T):
586
- for s in range(N):
587
- probs[s] = alpha[t, s] + beta[t, s] - normalisation
588
-
589
- for s in range(N):
590
- entropies[t] -= 2**(probs[s]) * probs[s]
591
-
592
- return entropies
593
-
594
- def _exhaustive_entropy(self, unlabeled_sequence):
595
- unlabeled_sequence = self._transform(unlabeled_sequence)
596
-
597
- T = len(unlabeled_sequence)
598
- N = len(self._states)
599
-
600
- labellings = [[state] for state in self._states]
601
- for t in range(T - 1):
602
- current = labellings
603
- labellings = []
604
- for labelling in current:
605
- for state in self._states:
606
- labellings.append(labelling + [state])
607
-
608
- log_probs = []
609
- for labelling in labellings:
610
- labeled_sequence = unlabeled_sequence[:]
611
- for t, label in enumerate(labelling):
612
- labeled_sequence[t] = (labeled_sequence[t][_TEXT], label)
613
- lp = self.log_probability(labeled_sequence)
614
- log_probs.append(lp)
615
- normalisation = _log_add(*log_probs)
616
-
617
- #ps = zeros((T, N), float64)
618
- #for labelling, lp in zip(labellings, log_probs):
619
- #for t in range(T):
620
- #ps[t, self._states.index(labelling[t])] += \
621
- # 2**(lp - normalisation)
622
-
623
- #for t in range(T):
624
- #print 'prob[%d] =' % t, ps[t]
625
-
626
- entropy = 0
627
- for lp in log_probs:
628
- lp -= normalisation
629
- entropy -= 2**(lp) * lp
630
-
631
- return entropy
632
-
633
- def _exhaustive_point_entropy(self, unlabeled_sequence):
634
- unlabeled_sequence = self._transform(unlabeled_sequence)
635
-
636
- T = len(unlabeled_sequence)
637
- N = len(self._states)
638
-
639
- labellings = [[state] for state in self._states]
640
- for t in range(T - 1):
641
- current = labellings
642
- labellings = []
643
- for labelling in current:
644
- for state in self._states:
645
- labellings.append(labelling + [state])
646
-
647
- log_probs = []
648
- for labelling in labellings:
649
- labelled_sequence = unlabeled_sequence[:]
650
- for t, label in enumerate(labelling):
651
- labelled_sequence[t] = (labelled_sequence[t][_TEXT], label)
652
- lp = self.log_probability(labelled_sequence)
653
- log_probs.append(lp)
654
-
655
- normalisation = _log_add(*log_probs)
656
-
657
- probabilities = _ninf_array((T,N))
658
-
659
- for labelling, lp in zip(labellings, log_probs):
660
- lp -= normalisation
661
- for t, label in enumerate(labelling):
662
- index = self._states.index(label)
663
- probabilities[t, index] = _log_add(probabilities[t, index], lp)
664
-
665
- entropies = np.zeros(T, np.float64)
666
- for t in range(T):
667
- for s in range(N):
668
- entropies[t] -= 2**(probabilities[t, s]) * probabilities[t, s]
669
-
670
- return entropies
671
-
672
- def _transitions_matrix(self):
673
- """ Return a matrix of transition log probabilities. """
674
- trans_iter = (self._transitions[sj].logprob(si)
675
- for sj in self._states
676
- for si in self._states)
677
-
678
- transitions_logprob = np.fromiter(trans_iter, dtype=np.float64)
679
- N = len(self._states)
680
- return transitions_logprob.reshape((N, N)).T
681
-
682
- def _outputs_vector(self, symbol):
683
- """
684
- Return a vector with log probabilities of emitting a symbol
685
- when entering states.
686
- """
687
- out_iter = (self._output_logprob(sj, symbol) for sj in self._states)
688
- return np.fromiter(out_iter, dtype=np.float64)
689
-
690
- def _forward_probability(self, unlabeled_sequence):
691
- """
692
- Return the forward probability matrix, a T by N array of
693
- log-probabilities, where T is the length of the sequence and N is the
694
- number of states. Each entry (t, s) gives the probability of being in
695
- state s at time t after observing the partial symbol sequence up to
696
- and including t.
697
-
698
- :param unlabeled_sequence: the sequence of unlabeled symbols
699
- :type unlabeled_sequence: list
700
- :return: the forward log probability matrix
701
- :rtype: array
702
- """
703
- T = len(unlabeled_sequence)
704
- N = len(self._states)
705
- alpha = _ninf_array((T, N))
706
-
707
- transitions_logprob = self._transitions_matrix()
708
-
709
- # Initialization
710
- symbol = unlabeled_sequence[0][_TEXT]
711
- for i, state in enumerate(self._states):
712
- alpha[0, i] = self._priors.logprob(state) + \
713
- self._output_logprob(state, symbol)
714
-
715
- # Induction
716
- for t in range(1, T):
717
- symbol = unlabeled_sequence[t][_TEXT]
718
- output_logprob = self._outputs_vector(symbol)
719
-
720
- for i in range(N):
721
- summand = alpha[t-1] + transitions_logprob[i]
722
- alpha[t, i] = logsumexp2(summand) + output_logprob[i]
723
-
724
- return alpha
725
-
726
- def _backward_probability(self, unlabeled_sequence):
727
- """
728
- Return the backward probability matrix, a T by N array of
729
- log-probabilities, where T is the length of the sequence and N is the
730
- number of states. Each entry (t, s) gives the probability of being in
731
- state s at time t after observing the partial symbol sequence from t
732
- .. T.
733
-
734
- :return: the backward log probability matrix
735
- :rtype: array
736
- :param unlabeled_sequence: the sequence of unlabeled symbols
737
- :type unlabeled_sequence: list
738
- """
739
- T = len(unlabeled_sequence)
740
- N = len(self._states)
741
- beta = _ninf_array((T, N))
742
-
743
- transitions_logprob = self._transitions_matrix().T
744
-
745
- # initialise the backward values;
746
- # "1" is an arbitrarily chosen value from Rabiner tutorial
747
- beta[T-1, :] = np.log2(1)
748
-
749
- # inductively calculate remaining backward values
750
- for t in range(T-2, -1, -1):
751
- symbol = unlabeled_sequence[t+1][_TEXT]
752
- outputs = self._outputs_vector(symbol)
753
-
754
- for i in range(N):
755
- summand = transitions_logprob[i] + beta[t+1] + outputs
756
- beta[t, i] = logsumexp2(summand)
757
-
758
- return beta
759
-
760
- def test(self, test_sequence, verbose=False, **kwargs):
761
- """
762
- Tests the HiddenMarkovModelTagger instance.
763
-
764
- :param test_sequence: a sequence of labeled test instances
765
- :type test_sequence: list(list)
766
- :param verbose: boolean flag indicating whether training should be
767
- verbose or include printed output
768
- :type verbose: bool
769
- """
770
-
771
- def words(sent):
772
- return [word for (word, tag) in sent]
773
-
774
- def tags(sent):
775
- return [tag for (word, tag) in sent]
776
-
777
- def flatten(seq):
778
- return list(itertools.chain(*seq))
779
-
780
- test_sequence = self._transform(test_sequence)
781
- predicted_sequence = list(imap(self._tag, imap(words, test_sequence)))
782
-
783
- if verbose:
784
- for test_sent, predicted_sent in izip(test_sequence, predicted_sequence):
785
- print('Test:',
786
- ' '.join('%s/%s' % (token, tag)
787
- for (token, tag) in test_sent))
788
- print()
789
- print('Untagged:',
790
- ' '.join("%s" % token for (token, tag) in test_sent))
791
- print()
792
- print('HMM-tagged:',
793
- ' '.join('%s/%s' % (token, tag)
794
- for (token, tag) in predicted_sent))
795
- print()
796
- print('Entropy:',
797
- self.entropy([(token, None) for
798
- (token, tag) in predicted_sent]))
799
- print()
800
- print('-' * 60)
801
-
802
- test_tags = flatten(imap(tags, test_sequence))
803
- predicted_tags = flatten(imap(tags, predicted_sequence))
804
-
805
- acc = accuracy(test_tags, predicted_tags)
806
- count = sum(len(sent) for sent in test_sequence)
807
- print('accuracy over %d tokens: %.2f' % (count, acc * 100))
808
-
809
- def __repr__(self):
810
- return ('<HiddenMarkovModelTagger %d states and %d output symbols>'
811
- % (len(self._states), len(self._symbols)))
812
-
813
-
814
- class HiddenMarkovModelTrainer(object):
815
- """
816
- Algorithms for learning HMM parameters from training data. These include
817
- both supervised learning (MLE) and unsupervised learning (Baum-Welch).
818
-
819
- Creates an HMM trainer to induce an HMM with the given states and
820
- output symbol alphabet. A supervised and unsupervised training
821
- method may be used. If either of the states or symbols are not given,
822
- these may be derived from supervised training.
823
-
824
- :param states: the set of state labels
825
- :type states: sequence of any
826
- :param symbols: the set of observation symbols
827
- :type symbols: sequence of any
828
- """
829
- def __init__(self, states=None, symbols=None):
830
- self._states = (states if states else [])
831
- self._symbols = (symbols if symbols else [])
832
-
833
- def train(self, labeled_sequences=None, unlabeled_sequences=None,
834
- **kwargs):
835
- """
836
- Trains the HMM using both (or either of) supervised and unsupervised
837
- techniques.
838
-
839
- :return: the trained model
840
- :rtype: HiddenMarkovModelTagger
841
- :param labelled_sequences: the supervised training data, a set of
842
- labelled sequences of observations
843
- :type labelled_sequences: list
844
- :param unlabeled_sequences: the unsupervised training data, a set of
845
- sequences of observations
846
- :type unlabeled_sequences: list
847
- :param kwargs: additional arguments to pass to the training methods
848
- """
849
- assert labeled_sequences or unlabeled_sequences
850
- model = None
851
- if labeled_sequences:
852
- model = self.train_supervised(labeled_sequences, **kwargs)
853
- if unlabeled_sequences:
854
- if model: kwargs['model'] = model
855
- model = self.train_unsupervised(unlabeled_sequences, **kwargs)
856
- return model
857
-
858
-
859
- def _baum_welch_step(self, sequence, model, symbol_to_number):
860
-
861
- N = len(model._states)
862
- M = len(model._symbols)
863
- T = len(sequence)
864
-
865
- # compute forward and backward probabilities
866
- alpha = model._forward_probability(sequence)
867
- beta = model._backward_probability(sequence)
868
-
869
- # find the log probability of the sequence
870
- lpk = logsumexp2(alpha[T-1])
871
-
872
- A_numer = _ninf_array((N, N))
873
- B_numer = _ninf_array((N, M))
874
- A_denom = _ninf_array(N)
875
- B_denom = _ninf_array(N)
876
-
877
- transitions_logprob = model._transitions_matrix().T
878
-
879
- for t in range(T):
880
- symbol = sequence[t][_TEXT] # not found? FIXME
881
- next_symbol = None
882
- if t < T - 1:
883
- next_symbol = sequence[t+1][_TEXT] # not found? FIXME
884
- xi = symbol_to_number[symbol]
885
-
886
- next_outputs_logprob = model._outputs_vector(next_symbol)
887
- alpha_plus_beta = alpha[t] + beta[t]
888
-
889
- if t < T - 1:
890
- numer_add = transitions_logprob + next_outputs_logprob + \
891
- beta[t+1] + alpha[t].reshape(N, 1)
892
- A_numer = np.logaddexp2(A_numer, numer_add)
893
- A_denom = np.logaddexp2(A_denom, alpha_plus_beta)
894
- else:
895
- B_denom = np.logaddexp2(A_denom, alpha_plus_beta)
896
-
897
- B_numer[:,xi] = np.logaddexp2(B_numer[:,xi], alpha_plus_beta)
898
-
899
- return lpk, A_numer, A_denom, B_numer, B_denom
900
-
901
- def train_unsupervised(self, unlabeled_sequences, update_outputs=True,
902
- **kwargs):
903
- """
904
- Trains the HMM using the Baum-Welch algorithm to maximise the
905
- probability of the data sequence. This is a variant of the EM
906
- algorithm, and is unsupervised in that it doesn't need the state
907
- sequences for the symbols. The code is based on 'A Tutorial on Hidden
908
- Markov Models and Selected Applications in Speech Recognition',
909
- Lawrence Rabiner, IEEE, 1989.
910
-
911
- :return: the trained model
912
- :rtype: HiddenMarkovModelTagger
913
- :param unlabeled_sequences: the training data, a set of
914
- sequences of observations
915
- :type unlabeled_sequences: list
916
-
917
- kwargs may include following parameters:
918
-
919
- :param model: a HiddenMarkovModelTagger instance used to begin
920
- the Baum-Welch algorithm
921
- :param max_iterations: the maximum number of EM iterations
922
- :param convergence_logprob: the maximum change in log probability to
923
- allow convergence
924
- """
925
-
926
- # create a uniform HMM, which will be iteratively refined, unless
927
- # given an existing model
928
- model = kwargs.get('model')
929
- if not model:
930
- priors = RandomProbDist(self._states)
931
- transitions = DictionaryConditionalProbDist(
932
- dict((state, RandomProbDist(self._states))
933
- for state in self._states))
934
- outputs = DictionaryConditionalProbDist(
935
- dict((state, RandomProbDist(self._symbols))
936
- for state in self._states))
937
- model = HiddenMarkovModelTagger(self._symbols, self._states,
938
- transitions, outputs, priors)
939
-
940
- self._states = model._states
941
- self._symbols = model._symbols
942
-
943
- N = len(self._states)
944
- M = len(self._symbols)
945
- symbol_numbers = dict((sym, i) for i, sym in enumerate(self._symbols))
946
-
947
- # update model prob dists so that they can be modified
948
- # model._priors = MutableProbDist(model._priors, self._states)
949
-
950
- model._transitions = DictionaryConditionalProbDist(
951
- dict((s, MutableProbDist(model._transitions[s], self._states))
952
- for s in self._states))
953
-
954
- if update_outputs:
955
- model._outputs = DictionaryConditionalProbDist(
956
- dict((s, MutableProbDist(model._outputs[s], self._symbols))
957
- for s in self._states))
958
-
959
- model.reset_cache()
960
-
961
- # iterate until convergence
962
- converged = False
963
- last_logprob = None
964
- iteration = 0
965
- max_iterations = kwargs.get('max_iterations', 1000)
966
- epsilon = kwargs.get('convergence_logprob', 1e-6)
967
-
968
- while not converged and iteration < max_iterations:
969
- A_numer = _ninf_array((N, N))
970
- B_numer = _ninf_array((N, M))
971
- A_denom = _ninf_array(N)
972
- B_denom = _ninf_array(N)
973
-
974
- logprob = 0
975
- for sequence in unlabeled_sequences:
976
- sequence = list(sequence)
977
- if not sequence:
978
- continue
979
-
980
- (lpk, seq_A_numer, seq_A_denom,
981
- seq_B_numer, seq_B_denom) = self._baum_welch_step(sequence, model, symbol_numbers)
982
-
983
- # add these sums to the global A and B values
984
- for i in range(N):
985
- A_numer[i] = np.logaddexp2(A_numer[i], seq_A_numer[i]-lpk)
986
- B_numer[i] = np.logaddexp2(B_numer[i], seq_B_numer[i]-lpk)
987
-
988
- A_denom = np.logaddexp2(A_denom, seq_A_denom-lpk)
989
- B_denom = np.logaddexp2(B_denom, seq_B_denom-lpk)
990
-
991
- logprob += lpk
992
-
993
- # use the calculated values to update the transition and output
994
- # probability values
995
- for i in range(N):
996
- logprob_Ai = A_numer[i] - A_denom[i]
997
- logprob_Bi = B_numer[i] - B_denom[i]
998
-
999
- # We should normalize all probabilities (see p.391 Huang et al)
1000
- # Let sum(P) be K.
1001
- # We can divide each Pi by K to make sum(P) == 1.
1002
- # Pi' = Pi/K
1003
- # log2(Pi') = log2(Pi) - log2(K)
1004
- logprob_Ai -= logsumexp2(logprob_Ai)
1005
- logprob_Bi -= logsumexp2(logprob_Bi)
1006
-
1007
- # update output and transition probabilities
1008
- si = self._states[i]
1009
-
1010
- for j in range(N):
1011
- sj = self._states[j]
1012
- model._transitions[si].update(sj, logprob_Ai[j])
1013
-
1014
- if update_outputs:
1015
- for k in range(M):
1016
- ok = self._symbols[k]
1017
- model._outputs[si].update(ok, logprob_Bi[k])
1018
-
1019
- # Rabiner says the priors don't need to be updated. I don't
1020
- # believe him. FIXME
1021
-
1022
- # test for convergence
1023
- if iteration > 0 and abs(logprob - last_logprob) < epsilon:
1024
- converged = True
1025
-
1026
- print('iteration', iteration, 'logprob', logprob)
1027
- iteration += 1
1028
- last_logprob = logprob
1029
-
1030
- return model
1031
-
1032
- def train_supervised(self, labelled_sequences, estimator=None):
1033
- """
1034
- Supervised training maximising the joint probability of the symbol and
1035
- state sequences. This is done via collecting frequencies of
1036
- transitions between states, symbol observations while within each
1037
- state and which states start a sentence. These frequency distributions
1038
- are then normalised into probability estimates, which can be
1039
- smoothed if desired.
1040
-
1041
- :return: the trained model
1042
- :rtype: HiddenMarkovModelTagger
1043
- :param labelled_sequences: the training data, a set of
1044
- labelled sequences of observations
1045
- :type labelled_sequences: list
1046
- :param estimator: a function taking
1047
- a FreqDist and a number of bins and returning a CProbDistI;
1048
- otherwise a MLE estimate is used
1049
- """
1050
-
1051
- # default to the MLE estimate
1052
- if estimator is None:
1053
- estimator = lambda fdist, bins: MLEProbDist(fdist)
1054
-
1055
- # count occurrences of starting states, transitions out of each state
1056
- # and output symbols observed in each state
1057
- known_symbols = set(self._symbols)
1058
- known_states = set(self._states)
1059
-
1060
- starting = FreqDist()
1061
- transitions = ConditionalFreqDist()
1062
- outputs = ConditionalFreqDist()
1063
- for sequence in labelled_sequences:
1064
- lasts = None
1065
- for token in sequence:
1066
- state = token[_TAG]
1067
- symbol = token[_TEXT]
1068
- if lasts is None:
1069
- starting[state] += 1
1070
- else:
1071
- transitions[lasts][state] += 1
1072
- outputs[state][symbol] += 1
1073
- lasts = state
1074
-
1075
- # update the state and symbol lists
1076
- if state not in known_states:
1077
- self._states.append(state)
1078
- known_states.add(state)
1079
-
1080
- if symbol not in known_symbols:
1081
- self._symbols.append(symbol)
1082
- known_symbols.add(symbol)
1083
-
1084
- # create probability distributions (with smoothing)
1085
- N = len(self._states)
1086
- pi = estimator(starting, N)
1087
- A = ConditionalProbDist(transitions, estimator, N)
1088
- B = ConditionalProbDist(outputs, estimator, len(self._symbols))
1089
-
1090
- return HiddenMarkovModelTagger(self._symbols, self._states, A, B, pi)
1091
-
1092
-
1093
- def _ninf_array(shape):
1094
- res = np.empty(shape, np.float64)
1095
- res.fill(-np.inf)
1096
- return res
1097
-
1098
-
1099
- def logsumexp2(arr):
1100
- max_ = arr.max()
1101
- return np.log2(np.sum(2**(arr - max_))) + max_
1102
-
1103
-
1104
- def _log_add(*values):
1105
- """
1106
- Adds the logged values, returning the logarithm of the addition.
1107
- """
1108
- x = max(values)
1109
- if x > -np.inf:
1110
- sum_diffs = 0
1111
- for value in values:
1112
- sum_diffs += 2**(value - x)
1113
- return x + np.log2(sum_diffs)
1114
- else:
1115
- return x
1116
-
1117
-
1118
- def _create_hmm_tagger(states, symbols, A, B, pi):
1119
- def pd(values, samples):
1120
- d = dict(zip(samples, values))
1121
- return DictionaryProbDist(d)
1122
-
1123
- def cpd(array, conditions, samples):
1124
- d = {}
1125
- for values, condition in zip(array, conditions):
1126
- d[condition] = pd(values, samples)
1127
- return DictionaryConditionalProbDist(d)
1128
-
1129
- A = cpd(A, states, states)
1130
- B = cpd(B, states, symbols)
1131
- pi = pd(pi, states)
1132
- return HiddenMarkovModelTagger(symbols=symbols, states=states,
1133
- transitions=A, outputs=B, priors=pi)
1134
-
1135
-
1136
- def _market_hmm_example():
1137
- """
1138
- Return an example HMM (described at page 381, Huang et al)
1139
- """
1140
- states = ['bull', 'bear', 'static']
1141
- symbols = ['up', 'down', 'unchanged']
1142
- A = np.array([[0.6, 0.2, 0.2], [0.5, 0.3, 0.2], [0.4, 0.1, 0.5]], np.float64)
1143
- B = np.array([[0.7, 0.1, 0.2], [0.1, 0.6, 0.3], [0.3, 0.3, 0.4]], np.float64)
1144
- pi = np.array([0.5, 0.2, 0.3], np.float64)
1145
-
1146
- model = _create_hmm_tagger(states, symbols, A, B, pi)
1147
- return model, states, symbols
1148
-
1149
-
1150
- def demo():
1151
- # demonstrates HMM probability calculation
1152
-
1153
- print()
1154
- print("HMM probability calculation demo")
1155
- print()
1156
-
1157
- model, states, symbols = _market_hmm_example()
1158
-
1159
- print('Testing', model)
1160
-
1161
- for test in [['up', 'up'], ['up', 'down', 'up'],
1162
- ['down'] * 5, ['unchanged'] * 5 + ['up']]:
1163
-
1164
- sequence = [(t, None) for t in test]
1165
-
1166
- print('Testing with state sequence', test)
1167
- print('probability =', model.probability(sequence))
1168
- print('tagging = ', model.tag([word for (word,tag) in sequence]))
1169
- print('p(tagged) = ', model.probability(sequence))
1170
- print('H = ', model.entropy(sequence))
1171
- print('H_exh = ', model._exhaustive_entropy(sequence))
1172
- print('H(point) = ', model.point_entropy(sequence))
1173
- print('H_exh(point)=', model._exhaustive_point_entropy(sequence))
1174
- print()
1175
-
1176
- def load_pos(num_sents):
1177
- from nltk.corpus import brown
1178
-
1179
- sentences = brown.tagged_sents(categories='news')[:num_sents]
1180
-
1181
- tag_re = re.compile(r'[*]|--|[^+*-]+')
1182
- tag_set = set()
1183
- symbols = set()
1184
-
1185
- cleaned_sentences = []
1186
- for sentence in sentences:
1187
- for i in range(len(sentence)):
1188
- word, tag = sentence[i]
1189
- word = word.lower() # normalize
1190
- symbols.add(word) # log this word
1191
- # Clean up the tag.
1192
- tag = tag_re.match(tag).group()
1193
- tag_set.add(tag)
1194
- sentence[i] = (word, tag) # store cleaned-up tagged token
1195
- cleaned_sentences += [sentence]
1196
-
1197
- return cleaned_sentences, list(tag_set), list(symbols)
1198
-
1199
- def demo_pos():
1200
- # demonstrates POS tagging using supervised training
1201
-
1202
- print()
1203
- print("HMM POS tagging demo")
1204
- print()
1205
-
1206
- print('Training HMM...')
1207
- labelled_sequences, tag_set, symbols = load_pos(20000)
1208
- trainer = HiddenMarkovModelTrainer(tag_set, symbols)
1209
- hmm = trainer.train_supervised(labelled_sequences[10:],
1210
- estimator=lambda fd, bins: LidstoneProbDist(fd, 0.1, bins))
1211
-
1212
- print('Testing...')
1213
- hmm.test(labelled_sequences[:10], verbose=True)
1214
-
1215
- def _untag(sentences):
1216
- unlabeled = []
1217
- for sentence in sentences:
1218
- unlabeled.append([(token[_TEXT], None) for token in sentence])
1219
- return unlabeled
1220
-
1221
- def demo_pos_bw(test=10, supervised=20, unsupervised=10, verbose=True,
1222
- max_iterations=5):
1223
- # demonstrates the Baum-Welch algorithm in POS tagging
1224
-
1225
- print()
1226
- print("Baum-Welch demo for POS tagging")
1227
- print()
1228
-
1229
- print('Training HMM (supervised, %d sentences)...' % supervised)
1230
-
1231
- sentences, tag_set, symbols = load_pos(test + supervised + unsupervised)
1232
-
1233
- symbols = set()
1234
- for sentence in sentences:
1235
- for token in sentence:
1236
- symbols.add(token[_TEXT])
1237
-
1238
- trainer = HiddenMarkovModelTrainer(tag_set, list(symbols))
1239
- hmm = trainer.train_supervised(sentences[test:test+supervised],
1240
- estimator=lambda fd, bins: LidstoneProbDist(fd, 0.1, bins))
1241
-
1242
- hmm.test(sentences[:test], verbose=verbose)
1243
-
1244
- print('Training (unsupervised, %d sentences)...' % unsupervised)
1245
- # it's rather slow - so only use 10 samples by default
1246
- unlabeled = _untag(sentences[test+supervised:])
1247
- hmm = trainer.train_unsupervised(unlabeled, model=hmm,
1248
- max_iterations=max_iterations)
1249
- hmm.test(sentences[:test], verbose=verbose)
1250
-
1251
- def demo_bw():
1252
- # demo Baum Welch by generating some sequences and then performing
1253
- # unsupervised training on them
1254
-
1255
- print()
1256
- print("Baum-Welch demo for market example")
1257
- print()
1258
-
1259
- model, states, symbols = _market_hmm_example()
1260
-
1261
- # generate some random sequences
1262
- training = []
1263
- import random
1264
- rng = random.Random()
1265
- rng.seed(0)
1266
- for i in range(10):
1267
- item = model.random_sample(rng, 5)
1268
- training.append([(i[0], None) for i in item])
1269
-
1270
- # train on those examples, starting with the model that generated them
1271
- trainer = HiddenMarkovModelTrainer(states, symbols)
1272
- hmm = trainer.train_unsupervised(training, model=model,
1273
- max_iterations=1000)
1274
-
1275
-
1276
-
1277
-
1
+ # Natural Language Toolkit: Hidden Markov Model
2
+ #
3
+ # Copyright (C) 2001-2018 NLTK Project
4
+ # Author: Trevor Cohn <tacohn@csse.unimelb.edu.au>
5
+ # Philip Blunsom <pcbl@csse.unimelb.edu.au>
6
+ # Tiago Tresoldi <tiago@tresoldi.pro.br> (fixes)
7
+ # Steven Bird <stevenbird1@gmail.com> (fixes)
8
+ # Joseph Frazee <jfrazee@mail.utexas.edu> (fixes)
9
+ # Steven Xu <xxu@student.unimelb.edu.au> (fixes)
10
+ # URL: <http://nltk.org/>
11
+ # For license information, see LICENSE.TXT
12
+
13
+ """
14
+ Hidden Markov Models (HMMs) largely used to assign the correct label sequence
15
+ to sequential data or assess the probability of a given label and data
16
+ sequence. These models are finite state machines characterised by a number of
17
+ states, transitions between these states, and output symbols emitted while in
18
+ each state. The HMM is an extension to the Markov chain, where each state
19
+ corresponds deterministically to a given event. In the HMM the observation is
20
+ a probabilistic function of the state. HMMs share the Markov chain's
21
+ assumption, being that the probability of transition from one state to another
22
+ only depends on the current state - i.e. the series of states that led to the
23
+ current state are not used. They are also time invariant.
24
+
25
+ The HMM is a directed graph, with probability weighted edges (representing the
26
+ probability of a transition between the source and sink states) where each
27
+ vertex emits an output symbol when entered. The symbol (or observation) is
28
+ non-deterministically generated. For this reason, knowing that a sequence of
29
+ output observations was generated by a given HMM does not mean that the
30
+ corresponding sequence of states (and what the current state is) is known.
31
+ This is the 'hidden' in the hidden markov model.
32
+
33
+ Formally, a HMM can be characterised by:
34
+
35
+ - the output observation alphabet. This is the set of symbols which may be
36
+ observed as output of the system.
37
+ - the set of states.
38
+ - the transition probabilities *a_{ij} = P(s_t = j | s_{t-1} = i)*. These
39
+ represent the probability of transition to each state from a given state.
40
+ - the output probability matrix *b_i(k) = P(X_t = o_k | s_t = i)*. These
41
+ represent the probability of observing each symbol in a given state.
42
+ - the initial state distribution. This gives the probability of starting
43
+ in each state.
44
+
45
+ To ground this discussion, take a common NLP application, part-of-speech (POS)
46
+ tagging. An HMM is desirable for this task as the highest probability tag
47
+ sequence can be calculated for a given sequence of word forms. This differs
48
+ from other tagging techniques which often tag each word individually, seeking
49
+ to optimise each individual tagging greedily without regard to the optimal
50
+ combination of tags for a larger unit, such as a sentence. The HMM does this
51
+ with the Viterbi algorithm, which efficiently computes the optimal path
52
+ through the graph given the sequence of words forms.
53
+
54
+ In POS tagging the states usually have a 1:1 correspondence with the tag
55
+ alphabet - i.e. each state represents a single tag. The output observation
56
+ alphabet is the set of word forms (the lexicon), and the remaining three
57
+ parameters are derived by a training regime. With this information the
58
+ probability of a given sentence can be easily derived, by simply summing the
59
+ probability of each distinct path through the model. Similarly, the highest
60
+ probability tagging sequence can be derived with the Viterbi algorithm,
61
+ yielding a state sequence which can be mapped into a tag sequence.
62
+
63
+ This discussion assumes that the HMM has been trained. This is probably the
64
+ most difficult task with the model, and requires either MLE estimates of the
65
+ parameters or unsupervised learning using the Baum-Welch algorithm, a variant
66
+ of EM.
67
+
68
+ For more information, please consult the source code for this module,
69
+ which includes extensive demonstration code.
70
+ """
71
+ from __future__ import print_function, unicode_literals, division
72
+
73
+ import re
74
+ import itertools
75
+
76
+ from six.moves import map, zip
77
+
78
+ try:
79
+ import numpy as np
80
+ except ImportError:
81
+ pass
82
+
83
+ from nltk.probability import (FreqDist, ConditionalFreqDist,
84
+ ConditionalProbDist, DictionaryProbDist,
85
+ DictionaryConditionalProbDist,
86
+ LidstoneProbDist, MutableProbDist,
87
+ MLEProbDist, RandomProbDist)
88
+ from nltk.metrics import accuracy
89
+ from nltk.util import LazyMap, unique_list
90
+ from nltk.compat import python_2_unicode_compatible
91
+ from nltk.tag.api import TaggerI
92
+
93
+
94
+ _TEXT = 0 # index of text in a tuple
95
+ _TAG = 1 # index of tag in a tuple
96
+
97
+ def _identity(labeled_symbols):
98
+ return labeled_symbols
99
+
100
+ @python_2_unicode_compatible
101
+ class HiddenMarkovModelTagger(TaggerI):
102
+ """
103
+ Hidden Markov model class, a generative model for labelling sequence data.
104
+ These models define the joint probability of a sequence of symbols and
105
+ their labels (state transitions) as the product of the starting state
106
+ probability, the probability of each state transition, and the probability
107
+ of each observation being generated from each state. This is described in
108
+ more detail in the module documentation.
109
+
110
+ This implementation is based on the HMM description in Chapter 8, Huang,
111
+ Acero and Hon, Spoken Language Processing and includes an extension for
112
+ training shallow HMM parsers or specialized HMMs as in Molina et.
113
+ al, 2002. A specialized HMM modifies training data by applying a
114
+ specialization function to create a new training set that is more
115
+ appropriate for sequential tagging with an HMM. A typical use case is
116
+ chunking.
117
+
118
+ :param symbols: the set of output symbols (alphabet)
119
+ :type symbols: seq of any
120
+ :param states: a set of states representing state space
121
+ :type states: seq of any
122
+ :param transitions: transition probabilities; Pr(s_i | s_j) is the
123
+ probability of transition from state i given the model is in
124
+ state_j
125
+ :type transitions: ConditionalProbDistI
126
+ :param outputs: output probabilities; Pr(o_k | s_i) is the probability
127
+ of emitting symbol k when entering state i
128
+ :type outputs: ConditionalProbDistI
129
+ :param priors: initial state distribution; Pr(s_i) is the probability
130
+ of starting in state i
131
+ :type priors: ProbDistI
132
+ :param transform: an optional function for transforming training
133
+ instances, defaults to the identity function.
134
+ :type transform: callable
135
+ """
136
+ def __init__(self, symbols, states, transitions, outputs, priors,
137
+ transform=_identity):
138
+ self._symbols = unique_list(symbols)
139
+ self._states = unique_list(states)
140
+ self._transitions = transitions
141
+ self._outputs = outputs
142
+ self._priors = priors
143
+ self._cache = None
144
+ self._transform = transform
145
+
146
+ @classmethod
147
+ def _train(cls, labeled_sequence, test_sequence=None,
148
+ unlabeled_sequence=None, transform=_identity,
149
+ estimator=None, **kwargs):
150
+
151
+ if estimator is None:
152
+ def estimator(fd, bins):
153
+ return LidstoneProbDist(fd, 0.1, bins)
154
+
155
+ labeled_sequence = LazyMap(transform, labeled_sequence)
156
+ symbols = unique_list(word for sent in labeled_sequence
157
+ for word, tag in sent)
158
+ tag_set = unique_list(tag for sent in labeled_sequence
159
+ for word, tag in sent)
160
+
161
+ trainer = HiddenMarkovModelTrainer(tag_set, symbols)
162
+ hmm = trainer.train_supervised(labeled_sequence, estimator=estimator)
163
+ hmm = cls(hmm._symbols, hmm._states, hmm._transitions, hmm._outputs,
164
+ hmm._priors, transform=transform)
165
+
166
+ if test_sequence:
167
+ hmm.test(test_sequence, verbose=kwargs.get('verbose', False))
168
+
169
+ if unlabeled_sequence:
170
+ max_iterations = kwargs.get('max_iterations', 5)
171
+ hmm = trainer.train_unsupervised(unlabeled_sequence, model=hmm,
172
+ max_iterations=max_iterations)
173
+ if test_sequence:
174
+ hmm.test(test_sequence, verbose=kwargs.get('verbose', False))
175
+
176
+ return hmm
177
+
178
+ @classmethod
179
+ def train(cls, labeled_sequence, test_sequence=None,
180
+ unlabeled_sequence=None, **kwargs):
181
+ """
182
+ Train a new HiddenMarkovModelTagger using the given labeled and
183
+ unlabeled training instances. Testing will be performed if test
184
+ instances are provided.
185
+
186
+ :return: a hidden markov model tagger
187
+ :rtype: HiddenMarkovModelTagger
188
+ :param labeled_sequence: a sequence of labeled training instances,
189
+ i.e. a list of sentences represented as tuples
190
+ :type labeled_sequence: list(list)
191
+ :param test_sequence: a sequence of labeled test instances
192
+ :type test_sequence: list(list)
193
+ :param unlabeled_sequence: a sequence of unlabeled training instances,
194
+ i.e. a list of sentences represented as words
195
+ :type unlabeled_sequence: list(list)
196
+ :param transform: an optional function for transforming training
197
+ instances, defaults to the identity function, see ``transform()``
198
+ :type transform: function
199
+ :param estimator: an optional function or class that maps a
200
+ condition's frequency distribution to its probability
201
+ distribution, defaults to a Lidstone distribution with gamma = 0.1
202
+ :type estimator: class or function
203
+ :param verbose: boolean flag indicating whether training should be
204
+ verbose or include printed output
205
+ :type verbose: bool
206
+ :param max_iterations: number of Baum-Welch interations to perform
207
+ :type max_iterations: int
208
+ """
209
+ return cls._train(labeled_sequence, test_sequence,
210
+ unlabeled_sequence, **kwargs)
211
+
212
+ def probability(self, sequence):
213
+ """
214
+ Returns the probability of the given symbol sequence. If the sequence
215
+ is labelled, then returns the joint probability of the symbol, state
216
+ sequence. Otherwise, uses the forward algorithm to find the
217
+ probability over all label sequences.
218
+
219
+ :return: the probability of the sequence
220
+ :rtype: float
221
+ :param sequence: the sequence of symbols which must contain the TEXT
222
+ property, and optionally the TAG property
223
+ :type sequence: Token
224
+ """
225
+ return 2**(self.log_probability(self._transform(sequence)))
226
+
227
+ def log_probability(self, sequence):
228
+ """
229
+ Returns the log-probability of the given symbol sequence. If the
230
+ sequence is labelled, then returns the joint log-probability of the
231
+ symbol, state sequence. Otherwise, uses the forward algorithm to find
232
+ the log-probability over all label sequences.
233
+
234
+ :return: the log-probability of the sequence
235
+ :rtype: float
236
+ :param sequence: the sequence of symbols which must contain the TEXT
237
+ property, and optionally the TAG property
238
+ :type sequence: Token
239
+ """
240
+ sequence = self._transform(sequence)
241
+
242
+ T = len(sequence)
243
+
244
+ if T > 0 and sequence[0][_TAG]:
245
+ last_state = sequence[0][_TAG]
246
+ p = self._priors.logprob(last_state) + \
247
+ self._output_logprob(last_state, sequence[0][_TEXT])
248
+ for t in range(1, T):
249
+ state = sequence[t][_TAG]
250
+ p += self._transitions[last_state].logprob(state) + \
251
+ self._output_logprob(state, sequence[t][_TEXT])
252
+ last_state = state
253
+ return p
254
+ else:
255
+ alpha = self._forward_probability(sequence)
256
+ p = logsumexp2(alpha[T-1])
257
+ return p
258
+
259
+ def tag(self, unlabeled_sequence):
260
+ """
261
+ Tags the sequence with the highest probability state sequence. This
262
+ uses the best_path method to find the Viterbi path.
263
+
264
+ :return: a labelled sequence of symbols
265
+ :rtype: list
266
+ :param unlabeled_sequence: the sequence of unlabeled symbols
267
+ :type unlabeled_sequence: list
268
+ """
269
+ unlabeled_sequence = self._transform(unlabeled_sequence)
270
+ return self._tag(unlabeled_sequence)
271
+
272
+ def _tag(self, unlabeled_sequence):
273
+ path = self._best_path(unlabeled_sequence)
274
+ return list(zip(unlabeled_sequence, path))
275
+
276
+ def _output_logprob(self, state, symbol):
277
+ """
278
+ :return: the log probability of the symbol being observed in the given
279
+ state
280
+ :rtype: float
281
+ """
282
+ return self._outputs[state].logprob(symbol)
283
+
284
+ def _create_cache(self):
285
+ """
286
+ The cache is a tuple (P, O, X, S) where:
287
+
288
+ - S maps symbols to integers. I.e., it is the inverse
289
+ mapping from self._symbols; for each symbol s in
290
+ self._symbols, the following is true::
291
+
292
+ self._symbols[S[s]] == s
293
+
294
+ - O is the log output probabilities::
295
+
296
+ O[i,k] = log( P(token[t]=sym[k]|tag[t]=state[i]) )
297
+
298
+ - X is the log transition probabilities::
299
+
300
+ X[i,j] = log( P(tag[t]=state[j]|tag[t-1]=state[i]) )
301
+
302
+ - P is the log prior probabilities::
303
+
304
+ P[i] = log( P(tag[0]=state[i]) )
305
+ """
306
+ if not self._cache:
307
+ N = len(self._states)
308
+ M = len(self._symbols)
309
+ P = np.zeros(N, np.float32)
310
+ X = np.zeros((N, N), np.float32)
311
+ O = np.zeros((N, M), np.float32)
312
+ for i in range(N):
313
+ si = self._states[i]
314
+ P[i] = self._priors.logprob(si)
315
+ for j in range(N):
316
+ X[i, j] = self._transitions[si].logprob(self._states[j])
317
+ for k in range(M):
318
+ O[i, k] = self._output_logprob(si, self._symbols[k])
319
+ S = {}
320
+ for k in range(M):
321
+ S[self._symbols[k]] = k
322
+ self._cache = (P, O, X, S)
323
+
324
+ def _update_cache(self, symbols):
325
+ # add new symbols to the symbol table and repopulate the output
326
+ # probabilities and symbol table mapping
327
+ if symbols:
328
+ self._create_cache()
329
+ P, O, X, S = self._cache
330
+ for symbol in symbols:
331
+ if symbol not in self._symbols:
332
+ self._cache = None
333
+ self._symbols.append(symbol)
334
+ # don't bother with the work if there aren't any new symbols
335
+ if not self._cache:
336
+ N = len(self._states)
337
+ M = len(self._symbols)
338
+ Q = O.shape[1]
339
+ # add new columns to the output probability table without
340
+ # destroying the old probabilities
341
+ O = np.hstack([O, np.zeros((N, M - Q), np.float32)])
342
+ for i in range(N):
343
+ si = self._states[i]
344
+ # only calculate probabilities for new symbols
345
+ for k in range(Q, M):
346
+ O[i, k] = self._output_logprob(si, self._symbols[k])
347
+ # only create symbol mappings for new symbols
348
+ for k in range(Q, M):
349
+ S[self._symbols[k]] = k
350
+ self._cache = (P, O, X, S)
351
+
352
+ def reset_cache(self):
353
+ self._cache = None
354
+
355
+ def best_path(self, unlabeled_sequence):
356
+ """
357
+ Returns the state sequence of the optimal (most probable) path through
358
+ the HMM. Uses the Viterbi algorithm to calculate this part by dynamic
359
+ programming.
360
+
361
+ :return: the state sequence
362
+ :rtype: sequence of any
363
+ :param unlabeled_sequence: the sequence of unlabeled symbols
364
+ :type unlabeled_sequence: list
365
+ """
366
+ unlabeled_sequence = self._transform(unlabeled_sequence)
367
+ return self._best_path(unlabeled_sequence)
368
+
369
+ def _best_path(self, unlabeled_sequence):
370
+ T = len(unlabeled_sequence)
371
+ N = len(self._states)
372
+ self._create_cache()
373
+ self._update_cache(unlabeled_sequence)
374
+ P, O, X, S = self._cache
375
+
376
+ V = np.zeros((T, N), np.float32)
377
+ B = -np.ones((T, N), np.int)
378
+
379
+ V[0] = P + O[:, S[unlabeled_sequence[0]]]
380
+ for t in range(1, T):
381
+ for j in range(N):
382
+ vs = V[t-1, :] + X[:, j]
383
+ best = np.argmax(vs)
384
+ V[t, j] = vs[best] + O[j, S[unlabeled_sequence[t]]]
385
+ B[t, j] = best
386
+
387
+ current = np.argmax(V[T-1,:])
388
+ sequence = [current]
389
+ for t in range(T-1, 0, -1):
390
+ last = B[t, current]
391
+ sequence.append(last)
392
+ current = last
393
+
394
+ sequence.reverse()
395
+ return list(map(self._states.__getitem__, sequence))
396
+
397
+ def best_path_simple(self, unlabeled_sequence):
398
+ """
399
+ Returns the state sequence of the optimal (most probable) path through
400
+ the HMM. Uses the Viterbi algorithm to calculate this part by dynamic
401
+ programming. This uses a simple, direct method, and is included for
402
+ teaching purposes.
403
+
404
+ :return: the state sequence
405
+ :rtype: sequence of any
406
+ :param unlabeled_sequence: the sequence of unlabeled symbols
407
+ :type unlabeled_sequence: list
408
+ """
409
+ unlabeled_sequence = self._transform(unlabeled_sequence)
410
+ return self._best_path_simple(unlabeled_sequence)
411
+
412
+ def _best_path_simple(self, unlabeled_sequence):
413
+ T = len(unlabeled_sequence)
414
+ N = len(self._states)
415
+ V = np.zeros((T, N), np.float64)
416
+ B = {}
417
+
418
+ # find the starting log probabilities for each state
419
+ symbol = unlabeled_sequence[0]
420
+ for i, state in enumerate(self._states):
421
+ V[0, i] = self._priors.logprob(state) + \
422
+ self._output_logprob(state, symbol)
423
+ B[0, state] = None
424
+
425
+ # find the maximum log probabilities for reaching each state at time t
426
+ for t in range(1, T):
427
+ symbol = unlabeled_sequence[t]
428
+ for j in range(N):
429
+ sj = self._states[j]
430
+ best = None
431
+ for i in range(N):
432
+ si = self._states[i]
433
+ va = V[t-1, i] + self._transitions[si].logprob(sj)
434
+ if not best or va > best[0]:
435
+ best = (va, si)
436
+ V[t, j] = best[0] + self._output_logprob(sj, symbol)
437
+ B[t, sj] = best[1]
438
+
439
+ # find the highest probability final state
440
+ best = None
441
+ for i in range(N):
442
+ val = V[T-1, i]
443
+ if not best or val > best[0]:
444
+ best = (val, self._states[i])
445
+
446
+ # traverse the back-pointers B to find the state sequence
447
+ current = best[1]
448
+ sequence = [current]
449
+ for t in range(T-1, 0, -1):
450
+ last = B[t, current]
451
+ sequence.append(last)
452
+ current = last
453
+
454
+ sequence.reverse()
455
+ return sequence
456
+
457
+ def random_sample(self, rng, length):
458
+ """
459
+ Randomly sample the HMM to generate a sentence of a given length. This
460
+ samples the prior distribution then the observation distribution and
461
+ transition distribution for each subsequent observation and state.
462
+ This will mostly generate unintelligible garbage, but can provide some
463
+ amusement.
464
+
465
+ :return: the randomly created state/observation sequence,
466
+ generated according to the HMM's probability
467
+ distributions. The SUBTOKENS have TEXT and TAG
468
+ properties containing the observation and state
469
+ respectively.
470
+ :rtype: list
471
+ :param rng: random number generator
472
+ :type rng: Random (or any object with a random() method)
473
+ :param length: desired output length
474
+ :type length: int
475
+ """
476
+
477
+ # sample the starting state and symbol prob dists
478
+ tokens = []
479
+ state = self._sample_probdist(self._priors, rng.random(), self._states)
480
+ symbol = self._sample_probdist(self._outputs[state],
481
+ rng.random(), self._symbols)
482
+ tokens.append((symbol, state))
483
+
484
+ for i in range(1, length):
485
+ # sample the state transition and symbol prob dists
486
+ state = self._sample_probdist(self._transitions[state],
487
+ rng.random(), self._states)
488
+ symbol = self._sample_probdist(self._outputs[state],
489
+ rng.random(), self._symbols)
490
+ tokens.append((symbol, state))
491
+
492
+ return tokens
493
+
494
+ def _sample_probdist(self, probdist, p, samples):
495
+ cum_p = 0
496
+ for sample in samples:
497
+ add_p = probdist.prob(sample)
498
+ if cum_p <= p <= cum_p + add_p:
499
+ return sample
500
+ cum_p += add_p
501
+ raise Exception('Invalid probability distribution - '
502
+ 'does not sum to one')
503
+
504
+ def entropy(self, unlabeled_sequence):
505
+ """
506
+ Returns the entropy over labellings of the given sequence. This is
507
+ given by::
508
+
509
+ H(O) = - sum_S Pr(S | O) log Pr(S | O)
510
+
511
+ where the summation ranges over all state sequences, S. Let
512
+ *Z = Pr(O) = sum_S Pr(S, O)}* where the summation ranges over all state
513
+ sequences and O is the observation sequence. As such the entropy can
514
+ be re-expressed as::
515
+
516
+ H = - sum_S Pr(S | O) log [ Pr(S, O) / Z ]
517
+ = log Z - sum_S Pr(S | O) log Pr(S, 0)
518
+ = log Z - sum_S Pr(S | O) [ log Pr(S_0) + sum_t Pr(S_t | S_{t-1}) + sum_t Pr(O_t | S_t) ]
519
+
520
+ The order of summation for the log terms can be flipped, allowing
521
+ dynamic programming to be used to calculate the entropy. Specifically,
522
+ we use the forward and backward probabilities (alpha, beta) giving::
523
+
524
+ H = log Z - sum_s0 alpha_0(s0) beta_0(s0) / Z * log Pr(s0)
525
+ + sum_t,si,sj alpha_t(si) Pr(sj | si) Pr(O_t+1 | sj) beta_t(sj) / Z * log Pr(sj | si)
526
+ + sum_t,st alpha_t(st) beta_t(st) / Z * log Pr(O_t | st)
527
+
528
+ This simply uses alpha and beta to find the probabilities of partial
529
+ sequences, constrained to include the given state(s) at some point in
530
+ time.
531
+ """
532
+ unlabeled_sequence = self._transform(unlabeled_sequence)
533
+
534
+ T = len(unlabeled_sequence)
535
+ N = len(self._states)
536
+
537
+ alpha = self._forward_probability(unlabeled_sequence)
538
+ beta = self._backward_probability(unlabeled_sequence)
539
+ normalisation = logsumexp2(alpha[T-1])
540
+
541
+ entropy = normalisation
542
+
543
+ # starting state, t = 0
544
+ for i, state in enumerate(self._states):
545
+ p = 2**(alpha[0, i] + beta[0, i] - normalisation)
546
+ entropy -= p * self._priors.logprob(state)
547
+ #print 'p(s_0 = %s) =' % state, p
548
+
549
+ # state transitions
550
+ for t0 in range(T - 1):
551
+ t1 = t0 + 1
552
+ for i0, s0 in enumerate(self._states):
553
+ for i1, s1 in enumerate(self._states):
554
+ p = 2**(alpha[t0, i0] + self._transitions[s0].logprob(s1) +
555
+ self._outputs[s1].logprob(
556
+ unlabeled_sequence[t1][_TEXT]) +
557
+ beta[t1, i1] - normalisation)
558
+ entropy -= p * self._transitions[s0].logprob(s1)
559
+ #print 'p(s_%d = %s, s_%d = %s) =' % (t0, s0, t1, s1), p
560
+
561
+ # symbol emissions
562
+ for t in range(T):
563
+ for i, state in enumerate(self._states):
564
+ p = 2**(alpha[t, i] + beta[t, i] - normalisation)
565
+ entropy -= p * self._outputs[state].logprob(
566
+ unlabeled_sequence[t][_TEXT])
567
+ #print 'p(s_%d = %s) =' % (t, state), p
568
+
569
+ return entropy
570
+
571
+ def point_entropy(self, unlabeled_sequence):
572
+ """
573
+ Returns the pointwise entropy over the possible states at each
574
+ position in the chain, given the observation sequence.
575
+ """
576
+ unlabeled_sequence = self._transform(unlabeled_sequence)
577
+
578
+ T = len(unlabeled_sequence)
579
+ N = len(self._states)
580
+
581
+ alpha = self._forward_probability(unlabeled_sequence)
582
+ beta = self._backward_probability(unlabeled_sequence)
583
+ normalisation = logsumexp2(alpha[T-1])
584
+
585
+ entropies = np.zeros(T, np.float64)
586
+ probs = np.zeros(N, np.float64)
587
+ for t in range(T):
588
+ for s in range(N):
589
+ probs[s] = alpha[t, s] + beta[t, s] - normalisation
590
+
591
+ for s in range(N):
592
+ entropies[t] -= 2**(probs[s]) * probs[s]
593
+
594
+ return entropies
595
+
596
+ def _exhaustive_entropy(self, unlabeled_sequence):
597
+ unlabeled_sequence = self._transform(unlabeled_sequence)
598
+
599
+ T = len(unlabeled_sequence)
600
+ N = len(self._states)
601
+
602
+ labellings = [[state] for state in self._states]
603
+ for t in range(T - 1):
604
+ current = labellings
605
+ labellings = []
606
+ for labelling in current:
607
+ for state in self._states:
608
+ labellings.append(labelling + [state])
609
+
610
+ log_probs = []
611
+ for labelling in labellings:
612
+ labeled_sequence = unlabeled_sequence[:]
613
+ for t, label in enumerate(labelling):
614
+ labeled_sequence[t] = (labeled_sequence[t][_TEXT], label)
615
+ lp = self.log_probability(labeled_sequence)
616
+ log_probs.append(lp)
617
+ normalisation = _log_add(*log_probs)
618
+
619
+ #ps = zeros((T, N), float64)
620
+ #for labelling, lp in zip(labellings, log_probs):
621
+ #for t in range(T):
622
+ #ps[t, self._states.index(labelling[t])] += \
623
+ # 2**(lp - normalisation)
624
+
625
+ #for t in range(T):
626
+ #print 'prob[%d] =' % t, ps[t]
627
+
628
+ entropy = 0
629
+ for lp in log_probs:
630
+ lp -= normalisation
631
+ entropy -= 2**(lp) * lp
632
+
633
+ return entropy
634
+
635
+ def _exhaustive_point_entropy(self, unlabeled_sequence):
636
+ unlabeled_sequence = self._transform(unlabeled_sequence)
637
+
638
+ T = len(unlabeled_sequence)
639
+ N = len(self._states)
640
+
641
+ labellings = [[state] for state in self._states]
642
+ for t in range(T - 1):
643
+ current = labellings
644
+ labellings = []
645
+ for labelling in current:
646
+ for state in self._states:
647
+ labellings.append(labelling + [state])
648
+
649
+ log_probs = []
650
+ for labelling in labellings:
651
+ labelled_sequence = unlabeled_sequence[:]
652
+ for t, label in enumerate(labelling):
653
+ labelled_sequence[t] = (labelled_sequence[t][_TEXT], label)
654
+ lp = self.log_probability(labelled_sequence)
655
+ log_probs.append(lp)
656
+
657
+ normalisation = _log_add(*log_probs)
658
+
659
+ probabilities = _ninf_array((T,N))
660
+
661
+ for labelling, lp in zip(labellings, log_probs):
662
+ lp -= normalisation
663
+ for t, label in enumerate(labelling):
664
+ index = self._states.index(label)
665
+ probabilities[t, index] = _log_add(probabilities[t, index], lp)
666
+
667
+ entropies = np.zeros(T, np.float64)
668
+ for t in range(T):
669
+ for s in range(N):
670
+ entropies[t] -= 2**(probabilities[t, s]) * probabilities[t, s]
671
+
672
+ return entropies
673
+
674
+ def _transitions_matrix(self):
675
+ """ Return a matrix of transition log probabilities. """
676
+ trans_iter = (self._transitions[sj].logprob(si)
677
+ for sj in self._states
678
+ for si in self._states)
679
+
680
+ transitions_logprob = np.fromiter(trans_iter, dtype=np.float64)
681
+ N = len(self._states)
682
+ return transitions_logprob.reshape((N, N)).T
683
+
684
+ def _outputs_vector(self, symbol):
685
+ """
686
+ Return a vector with log probabilities of emitting a symbol
687
+ when entering states.
688
+ """
689
+ out_iter = (self._output_logprob(sj, symbol) for sj in self._states)
690
+ return np.fromiter(out_iter, dtype=np.float64)
691
+
692
+ def _forward_probability(self, unlabeled_sequence):
693
+ """
694
+ Return the forward probability matrix, a T by N array of
695
+ log-probabilities, where T is the length of the sequence and N is the
696
+ number of states. Each entry (t, s) gives the probability of being in
697
+ state s at time t after observing the partial symbol sequence up to
698
+ and including t.
699
+
700
+ :param unlabeled_sequence: the sequence of unlabeled symbols
701
+ :type unlabeled_sequence: list
702
+ :return: the forward log probability matrix
703
+ :rtype: array
704
+ """
705
+ T = len(unlabeled_sequence)
706
+ N = len(self._states)
707
+ alpha = _ninf_array((T, N))
708
+
709
+ transitions_logprob = self._transitions_matrix()
710
+
711
+ # Initialization
712
+ symbol = unlabeled_sequence[0][_TEXT]
713
+ for i, state in enumerate(self._states):
714
+ alpha[0, i] = self._priors.logprob(state) + \
715
+ self._output_logprob(state, symbol)
716
+
717
+ # Induction
718
+ for t in range(1, T):
719
+ symbol = unlabeled_sequence[t][_TEXT]
720
+ output_logprob = self._outputs_vector(symbol)
721
+
722
+ for i in range(N):
723
+ summand = alpha[t-1] + transitions_logprob[i]
724
+ alpha[t, i] = logsumexp2(summand) + output_logprob[i]
725
+
726
+ return alpha
727
+
728
+ def _backward_probability(self, unlabeled_sequence):
729
+ """
730
+ Return the backward probability matrix, a T by N array of
731
+ log-probabilities, where T is the length of the sequence and N is the
732
+ number of states. Each entry (t, s) gives the probability of being in
733
+ state s at time t after observing the partial symbol sequence from t
734
+ .. T.
735
+
736
+ :return: the backward log probability matrix
737
+ :rtype: array
738
+ :param unlabeled_sequence: the sequence of unlabeled symbols
739
+ :type unlabeled_sequence: list
740
+ """
741
+ T = len(unlabeled_sequence)
742
+ N = len(self._states)
743
+ beta = _ninf_array((T, N))
744
+
745
+ transitions_logprob = self._transitions_matrix().T
746
+
747
+ # initialise the backward values;
748
+ # "1" is an arbitrarily chosen value from Rabiner tutorial
749
+ beta[T-1, :] = np.log2(1)
750
+
751
+ # inductively calculate remaining backward values
752
+ for t in range(T-2, -1, -1):
753
+ symbol = unlabeled_sequence[t+1][_TEXT]
754
+ outputs = self._outputs_vector(symbol)
755
+
756
+ for i in range(N):
757
+ summand = transitions_logprob[i] + beta[t+1] + outputs
758
+ beta[t, i] = logsumexp2(summand)
759
+
760
+ return beta
761
+
762
+ def test(self, test_sequence, verbose=False, **kwargs):
763
+ """
764
+ Tests the HiddenMarkovModelTagger instance.
765
+
766
+ :param test_sequence: a sequence of labeled test instances
767
+ :type test_sequence: list(list)
768
+ :param verbose: boolean flag indicating whether training should be
769
+ verbose or include printed output
770
+ :type verbose: bool
771
+ """
772
+
773
+ def words(sent):
774
+ return [word for (word, tag) in sent]
775
+
776
+ def tags(sent):
777
+ return [tag for (word, tag) in sent]
778
+
779
+ def flatten(seq):
780
+ return list(itertools.chain(*seq))
781
+
782
+ test_sequence = self._transform(test_sequence)
783
+ predicted_sequence = list(map(self._tag, map(words, test_sequence)))
784
+
785
+ if verbose:
786
+ for test_sent, predicted_sent in zip(test_sequence, predicted_sequence):
787
+ print('Test:',
788
+ ' '.join('%s/%s' % (token, tag)
789
+ for (token, tag) in test_sent))
790
+ print()
791
+ print('Untagged:',
792
+ ' '.join("%s" % token for (token, tag) in test_sent))
793
+ print()
794
+ print('HMM-tagged:',
795
+ ' '.join('%s/%s' % (token, tag)
796
+ for (token, tag) in predicted_sent))
797
+ print()
798
+ print('Entropy:',
799
+ self.entropy([(token, None) for
800
+ (token, tag) in predicted_sent]))
801
+ print()
802
+ print('-' * 60)
803
+
804
+ test_tags = flatten(map(tags, test_sequence))
805
+ predicted_tags = flatten(map(tags, predicted_sequence))
806
+
807
+ acc = accuracy(test_tags, predicted_tags)
808
+ count = sum(len(sent) for sent in test_sequence)
809
+ print('accuracy over %d tokens: %.2f' % (count, acc * 100))
810
+
811
+ def __repr__(self):
812
+ return ('<HiddenMarkovModelTagger %d states and %d output symbols>'
813
+ % (len(self._states), len(self._symbols)))
814
+
815
+
816
+ class HiddenMarkovModelTrainer(object):
817
+ """
818
+ Algorithms for learning HMM parameters from training data. These include
819
+ both supervised learning (MLE) and unsupervised learning (Baum-Welch).
820
+
821
+ Creates an HMM trainer to induce an HMM with the given states and
822
+ output symbol alphabet. A supervised and unsupervised training
823
+ method may be used. If either of the states or symbols are not given,
824
+ these may be derived from supervised training.
825
+
826
+ :param states: the set of state labels
827
+ :type states: sequence of any
828
+ :param symbols: the set of observation symbols
829
+ :type symbols: sequence of any
830
+ """
831
+ def __init__(self, states=None, symbols=None):
832
+ self._states = (states if states else [])
833
+ self._symbols = (symbols if symbols else [])
834
+
835
+ def train(self, labeled_sequences=None, unlabeled_sequences=None,
836
+ **kwargs):
837
+ """
838
+ Trains the HMM using both (or either of) supervised and unsupervised
839
+ techniques.
840
+
841
+ :return: the trained model
842
+ :rtype: HiddenMarkovModelTagger
843
+ :param labelled_sequences: the supervised training data, a set of
844
+ labelled sequences of observations
845
+ ex: [ (word_1, tag_1),...,(word_n,tag_n) ]
846
+ :type labelled_sequences: list
847
+ :param unlabeled_sequences: the unsupervised training data, a set of
848
+ sequences of observations
849
+ ex: [ word_1, ..., word_n ]
850
+ :type unlabeled_sequences: list
851
+ :param kwargs: additional arguments to pass to the training methods
852
+ """
853
+ assert labeled_sequences or unlabeled_sequences
854
+ model = None
855
+ if labeled_sequences:
856
+ model = self.train_supervised(labeled_sequences, **kwargs)
857
+ if unlabeled_sequences:
858
+ if model: kwargs['model'] = model
859
+ model = self.train_unsupervised(unlabeled_sequences, **kwargs)
860
+ return model
861
+
862
+
863
+ def _baum_welch_step(self, sequence, model, symbol_to_number):
864
+
865
+ N = len(model._states)
866
+ M = len(model._symbols)
867
+ T = len(sequence)
868
+
869
+ # compute forward and backward probabilities
870
+ alpha = model._forward_probability(sequence)
871
+ beta = model._backward_probability(sequence)
872
+
873
+ # find the log probability of the sequence
874
+ lpk = logsumexp2(alpha[T-1])
875
+
876
+ A_numer = _ninf_array((N, N))
877
+ B_numer = _ninf_array((N, M))
878
+ A_denom = _ninf_array(N)
879
+ B_denom = _ninf_array(N)
880
+
881
+ transitions_logprob = model._transitions_matrix().T
882
+
883
+ for t in range(T):
884
+ symbol = sequence[t][_TEXT] # not found? FIXME
885
+ next_symbol = None
886
+ if t < T - 1:
887
+ next_symbol = sequence[t+1][_TEXT] # not found? FIXME
888
+ xi = symbol_to_number[symbol]
889
+
890
+ next_outputs_logprob = model._outputs_vector(next_symbol)
891
+ alpha_plus_beta = alpha[t] + beta[t]
892
+
893
+ if t < T - 1:
894
+ numer_add = transitions_logprob + next_outputs_logprob + \
895
+ beta[t+1] + alpha[t].reshape(N, 1)
896
+ A_numer = np.logaddexp2(A_numer, numer_add)
897
+ A_denom = np.logaddexp2(A_denom, alpha_plus_beta)
898
+ else:
899
+ B_denom = np.logaddexp2(A_denom, alpha_plus_beta)
900
+
901
+ B_numer[:,xi] = np.logaddexp2(B_numer[:,xi], alpha_plus_beta)
902
+
903
+ return lpk, A_numer, A_denom, B_numer, B_denom
904
+
905
+ def train_unsupervised(self, unlabeled_sequences, update_outputs=True,
906
+ **kwargs):
907
+ """
908
+ Trains the HMM using the Baum-Welch algorithm to maximise the
909
+ probability of the data sequence. This is a variant of the EM
910
+ algorithm, and is unsupervised in that it doesn't need the state
911
+ sequences for the symbols. The code is based on 'A Tutorial on Hidden
912
+ Markov Models and Selected Applications in Speech Recognition',
913
+ Lawrence Rabiner, IEEE, 1989.
914
+
915
+ :return: the trained model
916
+ :rtype: HiddenMarkovModelTagger
917
+ :param unlabeled_sequences: the training data, a set of
918
+ sequences of observations
919
+ :type unlabeled_sequences: list
920
+
921
+ kwargs may include following parameters:
922
+
923
+ :param model: a HiddenMarkovModelTagger instance used to begin
924
+ the Baum-Welch algorithm
925
+ :param max_iterations: the maximum number of EM iterations
926
+ :param convergence_logprob: the maximum change in log probability to
927
+ allow convergence
928
+ """
929
+
930
+ # create a uniform HMM, which will be iteratively refined, unless
931
+ # given an existing model
932
+ model = kwargs.get('model')
933
+ if not model:
934
+ priors = RandomProbDist(self._states)
935
+ transitions = DictionaryConditionalProbDist(
936
+ dict((state, RandomProbDist(self._states))
937
+ for state in self._states))
938
+ outputs = DictionaryConditionalProbDist(
939
+ dict((state, RandomProbDist(self._symbols))
940
+ for state in self._states))
941
+ model = HiddenMarkovModelTagger(self._symbols, self._states,
942
+ transitions, outputs, priors)
943
+
944
+ self._states = model._states
945
+ self._symbols = model._symbols
946
+
947
+ N = len(self._states)
948
+ M = len(self._symbols)
949
+ symbol_numbers = dict((sym, i) for i, sym in enumerate(self._symbols))
950
+
951
+ # update model prob dists so that they can be modified
952
+ # model._priors = MutableProbDist(model._priors, self._states)
953
+
954
+ model._transitions = DictionaryConditionalProbDist(
955
+ dict((s, MutableProbDist(model._transitions[s], self._states))
956
+ for s in self._states))
957
+
958
+ if update_outputs:
959
+ model._outputs = DictionaryConditionalProbDist(
960
+ dict((s, MutableProbDist(model._outputs[s], self._symbols))
961
+ for s in self._states))
962
+
963
+ model.reset_cache()
964
+
965
+ # iterate until convergence
966
+ converged = False
967
+ last_logprob = None
968
+ iteration = 0
969
+ max_iterations = kwargs.get('max_iterations', 1000)
970
+ epsilon = kwargs.get('convergence_logprob', 1e-6)
971
+
972
+ while not converged and iteration < max_iterations:
973
+ A_numer = _ninf_array((N, N))
974
+ B_numer = _ninf_array((N, M))
975
+ A_denom = _ninf_array(N)
976
+ B_denom = _ninf_array(N)
977
+
978
+ logprob = 0
979
+ for sequence in unlabeled_sequences:
980
+ sequence = list(sequence)
981
+ if not sequence:
982
+ continue
983
+
984
+ (lpk, seq_A_numer, seq_A_denom,
985
+ seq_B_numer, seq_B_denom) = self._baum_welch_step(sequence, model, symbol_numbers)
986
+
987
+ # add these sums to the global A and B values
988
+ for i in range(N):
989
+ A_numer[i] = np.logaddexp2(A_numer[i], seq_A_numer[i]-lpk)
990
+ B_numer[i] = np.logaddexp2(B_numer[i], seq_B_numer[i]-lpk)
991
+
992
+ A_denom = np.logaddexp2(A_denom, seq_A_denom-lpk)
993
+ B_denom = np.logaddexp2(B_denom, seq_B_denom-lpk)
994
+
995
+ logprob += lpk
996
+
997
+ # use the calculated values to update the transition and output
998
+ # probability values
999
+ for i in range(N):
1000
+ logprob_Ai = A_numer[i] - A_denom[i]
1001
+ logprob_Bi = B_numer[i] - B_denom[i]
1002
+
1003
+ # We should normalize all probabilities (see p.391 Huang et al)
1004
+ # Let sum(P) be K.
1005
+ # We can divide each Pi by K to make sum(P) == 1.
1006
+ # Pi' = Pi/K
1007
+ # log2(Pi') = log2(Pi) - log2(K)
1008
+ logprob_Ai -= logsumexp2(logprob_Ai)
1009
+ logprob_Bi -= logsumexp2(logprob_Bi)
1010
+
1011
+ # update output and transition probabilities
1012
+ si = self._states[i]
1013
+
1014
+ for j in range(N):
1015
+ sj = self._states[j]
1016
+ model._transitions[si].update(sj, logprob_Ai[j])
1017
+
1018
+ if update_outputs:
1019
+ for k in range(M):
1020
+ ok = self._symbols[k]
1021
+ model._outputs[si].update(ok, logprob_Bi[k])
1022
+
1023
+ # Rabiner says the priors don't need to be updated. I don't
1024
+ # believe him. FIXME
1025
+
1026
+ # test for convergence
1027
+ if iteration > 0 and abs(logprob - last_logprob) < epsilon:
1028
+ converged = True
1029
+
1030
+ print('iteration', iteration, 'logprob', logprob)
1031
+ iteration += 1
1032
+ last_logprob = logprob
1033
+
1034
+ return model
1035
+
1036
+ def train_supervised(self, labelled_sequences, estimator=None):
1037
+ """
1038
+ Supervised training maximising the joint probability of the symbol and
1039
+ state sequences. This is done via collecting frequencies of
1040
+ transitions between states, symbol observations while within each
1041
+ state and which states start a sentence. These frequency distributions
1042
+ are then normalised into probability estimates, which can be
1043
+ smoothed if desired.
1044
+
1045
+ :return: the trained model
1046
+ :rtype: HiddenMarkovModelTagger
1047
+ :param labelled_sequences: the training data, a set of
1048
+ labelled sequences of observations
1049
+ :type labelled_sequences: list
1050
+ :param estimator: a function taking
1051
+ a FreqDist and a number of bins and returning a CProbDistI;
1052
+ otherwise a MLE estimate is used
1053
+ """
1054
+
1055
+ # default to the MLE estimate
1056
+ if estimator is None:
1057
+ estimator = lambda fdist, bins: MLEProbDist(fdist)
1058
+
1059
+ # count occurrences of starting states, transitions out of each state
1060
+ # and output symbols observed in each state
1061
+ known_symbols = set(self._symbols)
1062
+ known_states = set(self._states)
1063
+
1064
+ starting = FreqDist()
1065
+ transitions = ConditionalFreqDist()
1066
+ outputs = ConditionalFreqDist()
1067
+ for sequence in labelled_sequences:
1068
+ lasts = None
1069
+ for token in sequence:
1070
+ state = token[_TAG]
1071
+ symbol = token[_TEXT]
1072
+ if lasts is None:
1073
+ starting[state] += 1
1074
+ else:
1075
+ transitions[lasts][state] += 1
1076
+ outputs[state][symbol] += 1
1077
+ lasts = state
1078
+
1079
+ # update the state and symbol lists
1080
+ if state not in known_states:
1081
+ self._states.append(state)
1082
+ known_states.add(state)
1083
+
1084
+ if symbol not in known_symbols:
1085
+ self._symbols.append(symbol)
1086
+ known_symbols.add(symbol)
1087
+
1088
+ # create probability distributions (with smoothing)
1089
+ N = len(self._states)
1090
+ pi = estimator(starting, N)
1091
+ A = ConditionalProbDist(transitions, estimator, N)
1092
+ B = ConditionalProbDist(outputs, estimator, len(self._symbols))
1093
+
1094
+ return HiddenMarkovModelTagger(self._symbols, self._states, A, B, pi)
1095
+
1096
+
1097
+ def _ninf_array(shape):
1098
+ res = np.empty(shape, np.float64)
1099
+ res.fill(-np.inf)
1100
+ return res
1101
+
1102
+
1103
+ def logsumexp2(arr):
1104
+ max_ = arr.max()
1105
+ return np.log2(np.sum(2**(arr - max_))) + max_
1106
+
1107
+
1108
+ def _log_add(*values):
1109
+ """
1110
+ Adds the logged values, returning the logarithm of the addition.
1111
+ """
1112
+ x = max(values)
1113
+ if x > -np.inf:
1114
+ sum_diffs = 0
1115
+ for value in values:
1116
+ sum_diffs += 2**(value - x)
1117
+ return x + np.log2(sum_diffs)
1118
+ else:
1119
+ return x
1120
+
1121
+
1122
+ def _create_hmm_tagger(states, symbols, A, B, pi):
1123
+ def pd(values, samples):
1124
+ d = dict(zip(samples, values))
1125
+ return DictionaryProbDist(d)
1126
+
1127
+ def cpd(array, conditions, samples):
1128
+ d = {}
1129
+ for values, condition in zip(array, conditions):
1130
+ d[condition] = pd(values, samples)
1131
+ return DictionaryConditionalProbDist(d)
1132
+
1133
+ A = cpd(A, states, states)
1134
+ B = cpd(B, states, symbols)
1135
+ pi = pd(pi, states)
1136
+ return HiddenMarkovModelTagger(symbols=symbols, states=states,
1137
+ transitions=A, outputs=B, priors=pi)
1138
+
1139
+
1140
+ def _market_hmm_example():
1141
+ """
1142
+ Return an example HMM (described at page 381, Huang et al)
1143
+ """
1144
+ states = ['bull', 'bear', 'static']
1145
+ symbols = ['up', 'down', 'unchanged']
1146
+ A = np.array([[0.6, 0.2, 0.2], [0.5, 0.3, 0.2], [0.4, 0.1, 0.5]], np.float64)
1147
+ B = np.array([[0.7, 0.1, 0.2], [0.1, 0.6, 0.3], [0.3, 0.3, 0.4]], np.float64)
1148
+ pi = np.array([0.5, 0.2, 0.3], np.float64)
1149
+
1150
+ model = _create_hmm_tagger(states, symbols, A, B, pi)
1151
+ return model, states, symbols
1152
+
1153
+
1154
+ def demo():
1155
+ # demonstrates HMM probability calculation
1156
+
1157
+ print()
1158
+ print("HMM probability calculation demo")
1159
+ print()
1160
+
1161
+ model, states, symbols = _market_hmm_example()
1162
+
1163
+ print('Testing', model)
1164
+
1165
+ for test in [['up', 'up'], ['up', 'down', 'up'],
1166
+ ['down'] * 5, ['unchanged'] * 5 + ['up']]:
1167
+
1168
+ sequence = [(t, None) for t in test]
1169
+
1170
+ print('Testing with state sequence', test)
1171
+ print('probability =', model.probability(sequence))
1172
+ print('tagging = ', model.tag([word for (word,tag) in sequence]))
1173
+ print('p(tagged) = ', model.probability(sequence))
1174
+ print('H = ', model.entropy(sequence))
1175
+ print('H_exh = ', model._exhaustive_entropy(sequence))
1176
+ print('H(point) = ', model.point_entropy(sequence))
1177
+ print('H_exh(point)=', model._exhaustive_point_entropy(sequence))
1178
+ print()
1179
+
1180
+ def load_pos(num_sents):
1181
+ from nltk.corpus import brown
1182
+
1183
+ sentences = brown.tagged_sents(categories='news')[:num_sents]
1184
+
1185
+ tag_re = re.compile(r'[*]|--|[^+*-]+')
1186
+ tag_set = set()
1187
+ symbols = set()
1188
+
1189
+ cleaned_sentences = []
1190
+ for sentence in sentences:
1191
+ for i in range(len(sentence)):
1192
+ word, tag = sentence[i]
1193
+ word = word.lower() # normalize
1194
+ symbols.add(word) # log this word
1195
+ # Clean up the tag.
1196
+ tag = tag_re.match(tag).group()
1197
+ tag_set.add(tag)
1198
+ sentence[i] = (word, tag) # store cleaned-up tagged token
1199
+ cleaned_sentences += [sentence]
1200
+
1201
+ return cleaned_sentences, list(tag_set), list(symbols)
1202
+
1203
+ def demo_pos():
1204
+ # demonstrates POS tagging using supervised training
1205
+
1206
+ print()
1207
+ print("HMM POS tagging demo")
1208
+ print()
1209
+
1210
+ print('Training HMM...')
1211
+ labelled_sequences, tag_set, symbols = load_pos(20000)
1212
+ trainer = HiddenMarkovModelTrainer(tag_set, symbols)
1213
+ hmm = trainer.train_supervised(labelled_sequences[10:],
1214
+ estimator=lambda fd, bins: LidstoneProbDist(fd, 0.1, bins))
1215
+
1216
+ print('Testing...')
1217
+ hmm.test(labelled_sequences[:10], verbose=True)
1218
+
1219
+ def _untag(sentences):
1220
+ unlabeled = []
1221
+ for sentence in sentences:
1222
+ unlabeled.append([(token[_TEXT], None) for token in sentence])
1223
+ return unlabeled
1224
+
1225
+ def demo_pos_bw(test=10, supervised=20, unsupervised=10, verbose=True,
1226
+ max_iterations=5):
1227
+ # demonstrates the Baum-Welch algorithm in POS tagging
1228
+
1229
+ print()
1230
+ print("Baum-Welch demo for POS tagging")
1231
+ print()
1232
+
1233
+ print('Training HMM (supervised, %d sentences)...' % supervised)
1234
+
1235
+ sentences, tag_set, symbols = load_pos(test + supervised + unsupervised)
1236
+
1237
+ symbols = set()
1238
+ for sentence in sentences:
1239
+ for token in sentence:
1240
+ symbols.add(token[_TEXT])
1241
+
1242
+ trainer = HiddenMarkovModelTrainer(tag_set, list(symbols))
1243
+ hmm = trainer.train_supervised(sentences[test:test+supervised],
1244
+ estimator=lambda fd, bins: LidstoneProbDist(fd, 0.1, bins))
1245
+
1246
+ hmm.test(sentences[:test], verbose=verbose)
1247
+
1248
+ print('Training (unsupervised, %d sentences)...' % unsupervised)
1249
+ # it's rather slow - so only use 10 samples by default
1250
+ unlabeled = _untag(sentences[test+supervised:])
1251
+ hmm = trainer.train_unsupervised(unlabeled, model=hmm,
1252
+ max_iterations=max_iterations)
1253
+ hmm.test(sentences[:test], verbose=verbose)
1254
+
1255
+ def demo_bw():
1256
+ # demo Baum Welch by generating some sequences and then performing
1257
+ # unsupervised training on them
1258
+
1259
+ print()
1260
+ print("Baum-Welch demo for market example")
1261
+ print()
1262
+
1263
+ model, states, symbols = _market_hmm_example()
1264
+
1265
+ # generate some random sequences
1266
+ training = []
1267
+ import random
1268
+ rng = random.Random()
1269
+ rng.seed(0)
1270
+ for i in range(10):
1271
+ item = model.random_sample(rng, 5)
1272
+ training.append([(i[0], None) for i in item])
1273
+
1274
+ # train on those examples, starting with the model that generated them
1275
+ trainer = HiddenMarkovModelTrainer(states, symbols)
1276
+ hmm = trainer.train_unsupervised(training, model=model,
1277
+ max_iterations=1000)