├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── chapter ├── 1. 新手上路.md ├── 10.文本聚类.md ├── 11.文本分类.md ├── 12.依存句法分析.md ├── 13.深度学习与自然语言处理.md ├── 2.词典分词.md ├── 3.二元语法与中文分词.md ├── 4.隐马尔可夫模型与序列标注.md ├── 5.感知机分类与序列标注.md ├── 6.条件随机场与序列标注.md ├── 7.词性标注.md ├── 8.命名实体识别.md └── 9.信息抽取.md ├── code ├── ch02 │ ├── ngram_segment.py │ └── trie.py ├── ch03 │ └── ngram_segment.py ├── ch04 │ └── hmm_cws.py ├── ch05 │ ├── classify_name.py │ └── perceptron_cws.py ├── ch06 │ └── evaluate_crf_cws.py ├── ch07 │ ├── crf_pos.py │ ├── hmm_pos.py │ └── perceptron_pos.py ├── ch08 │ ├── crf_ner.py │ ├── hmm_ner.py │ └── perceptron_ner.py ├── ch09 │ └── extract_word.py ├── ch11 │ ├── load_text_classification_corpus.py │ ├── sentiment_analysis.py │ ├── svm_text_classification.py │ ├── text_classification.py │ └── text_classification_evaluation.py └── ch12 │ └── train_parser.py ├── data └── dictionnary │ ├── CoreNatureDictionary.mini.txt │ ├── my_cws_corpus.txt │ └── stopwords.txt └── img ├── 2020-2-14_16-23-23.png ├── 1.png ├── 2020-2-10_10-33-28.png ├── 2020-2-10_10-35-32.png ├── 2020-2-10_11-27-16.png ├── 2020-2-10_16-51-16.gif ├── 2020-2-10_16-52-37.gif ├── 2020-2-10_16-53-34.gif ├── 2020-2-10_16-54-37.gif ├── 2020-2-10_16-55-36.gif ├── 2020-2-10_16-56-26.gif ├── 2020-2-10_16-57-29.gif ├── 2020-2-10_16-58-24.gif ├── 2020-2-10_16-59-19.gif ├── 2020-2-10_17-0-23.gif ├── 2020-2-10_17-1-14.gif ├── 2020-2-10_17-10-56.gif ├── 2020-2-10_17-11-56.gif ├── 2020-2-10_17-12-45.gif ├── 2020-2-10_17-13-35.gif ├── 2020-2-10_17-14-25.gif ├── 2020-2-10_17-2-4.gif ├── 2020-2-12_11-33-51.png ├── 2020-2-12_11-53-59.png ├── 2020-2-12_17-18-47.gif ├── 2020-2-12_17-21-2.gif ├── 2020-2-12_17-22-2.gif ├── 2020-2-12_17-24-59.gif ├── 2020-2-12_17-26-59.gif ├── 2020-2-12_17-26-7.gif ├── 2020-2-12_22-32-31.png ├── 2020-2-12_22-36-21.png ├── 2020-2-13_14-26-31.gif ├── 2020-2-13_14-28-33.gif ├── 2020-2-13_14-29-43.gif ├── 2020-2-13_14-30-58.gif ├── 2020-2-13_14-32-1.gif ├── 2020-2-13_14-33-24.gif ├── 2020-2-13_14-47-11.gif ├── 2020-2-13_15-49-16.gif ├── 2020-2-13_15-51-18.gif ├── 2020-2-13_15-52-26.gif ├── 2020-2-13_15-54-25.gif ├── 2020-2-13_15-55-22.gif ├── 2020-2-13_15-56-25.gif ├── 2020-2-13_15-57-21.gif ├── 2020-2-13_15-58-29.gif ├── 2020-2-14_16-27-56.png ├── 2020-2-14_17-55-50.png ├── 2020-2-14_17-58-48.png ├── 2020-2-14_18-37-41.png ├── 2020-2-18_16-43-14.png ├── 2020-2-18_17-10-42.png ├── 2020-2-18_17-12-27.gif ├── 2020-2-19_21-57-32.gif ├── 2020-2-19_21-58-57.gif ├── 2020-2-3_10-50-30.png ├── 2020-2-3_11-17-38.png ├── 2020-2-3_12-26-11.png ├── 2020-2-3_12-8-55.png ├── 2020-2-3_13-40-56.png ├── 2020-2-3_13-52-12.png ├── 2020-2-3_16-0-25.png ├── 2020-2-3_19-41-15.png ├── 2020-2-4_12-44-46.png ├── 2020-2-4_12-55-18.png ├── 2020-2-4_14-12-47.png ├── 2020-2-4_14-15-53.png ├── 2020-2-4_14-45-35.png ├── 2020-2-4_14-46-52.png ├── 2020-2-4_16-49-15.png ├── 2020-2-5_10-32-10.png ├── 2020-2-5_10-57-30.png ├── 2020-2-5_11-17-22.png ├── 2020-2-5_11-21-45.png ├── 2020-2-5_15-13-17.png ├── 2020-2-5_17-49-0.png ├── 2020-2-5_17-51-33.png ├── 2020-2-5_17-53-12.png ├── 2020-2-5_17-55-12.png ├── 2020-2-5_17-56-39.png ├── 2020-2-5_17-57-48.png ├── 2020-2-5_17-59-12.png ├── 2020-2-5_18-1-47.png ├── 2020-2-5_18-11-13.png ├── 2020-2-5_18-22-57.png ├── 2020-2-6_10-36-13.png ├── 2020-2-6_10-55-28.png ├── 2020-2-6_11-11-0.png ├── 2020-2-6_11-23-10.png ├── 2020-2-6_11-4-22.png ├── 2020-2-6_11-43-39.png ├── 2020-2-6_11-53-47.png ├── 2020-2-6_11-57-24.png ├── 2020-2-6_13-19-18.png ├── 2020-2-6_13-3-50.png ├── 2020-2-6_14-9-11.png ├── 2020-2-7_13-2-10.png ├── 2020-2-7_13-2-39.png ├── 2020-2-7_17-57-2.png ├── 2020-2-7_18-4-34.png ├── 2020-2-8_0-41-12.gif ├── 2020-2-8_0-43-48.gif ├── 2020-2-8_0-45-19.gif ├── 2020-2-8_0-47-45.gif ├── 2020-2-8_0-49-50.gif ├── 2020-2-8_0-51-27.gif ├── 2020-2-8_0-52-50.gif ├── 2020-2-8_0-55-17.gif ├── 2020-2-8_0-56-28.gif ├── 2020-2-8_0-57-47.gif ├── 2020-2-8_0-59-16.gif ├── 2020-2-8_1-0-30.gif ├── 2020-2-8_1-1-42.gif ├── 2020-2-8_1-3-7.gif ├── 2020-2-8_1-4-40.gif ├── 2020-2-8_1-6-54.gif ├── 2020-2-8_1-8-16.gif ├── 2020-2-8_10-27-54.png ├── 2020-2-8_10-54-50.png ├── 2020-2-8_11-1-3.png ├── 2020-2-8_11-28-24.png ├── 2020-2-8_11-43-39.png ├── 2020-2-9_10-51-43.gif ├── 2020-2-9_10-53-25.gif ├── 2020-2-9_10-55-36.gif ├── 2020-2-9_10-56-36.gif ├── 2020-2-9_10-57-27.gif ├── 2020-2-9_10-58-23.gif ├── 2020-2-9_10-59-14.gif ├── 2020-2-9_11-0-5.gif ├── 2020-2-9_11-1-14.gif ├── 2020-2-9_11-2-11.gif ├── 2020-2-9_11-3-4.gif ├── 2020-2-9_11-3-55.gif ├── 2020-2-9_15-14-17.png ├── 2020-2-9_15-46-25.png ├── 2020-2-9_16-7-16.png └── s /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=python 2 | *.png linguist-language=python 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction-NLP 2 | HanLP作者何晗老师的新书《自然语言处理入门》详细笔记!业界良心之作,书中不是枯燥无味的公式罗列,而是用白话阐述的通俗易懂的算法模型。从基本概念出发,逐步介绍**中文分词、词性标注、命名实体识别、信息抽取、文本聚类、文本分类、句法分析这几个热门问题的算法原理与工程实现。** 3 | 4 | 本项目旨在帮助更多同路人能够快速的掌握 NLP 的专业知识,理清知识要点,在工作中发挥更大的作用。以书本为主,记录本人学习此书的心路历程、总结和笔记。 5 | 6 | - 机器学习与深度学习请转至本人项目:[ML-NLP](https://github.com/NLP-LOVE/ML-NLP) 7 | 8 | - HanLP项目:[HanLP](https://github.com/hankcs/HanLP) 9 | 10 | - 思维导图,**请关注 AIArea 公众号并回复:NLP思维导图** ,即能下载高清大图。 11 | 12 | - ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_16-0-25.png?raw=true) 13 | 14 | 15 | 16 | 17 | ## 目录 18 | 19 | ---- 20 | 21 | | 章节 | 22 | | ------------------------------------------------------------ | 23 | | [第 1 章:新手上路](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/1.%20%E6%96%B0%E6%89%8B%E4%B8%8A%E8%B7%AF.md) | 24 | | [第 2 章:词典分词](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/2.%E8%AF%8D%E5%85%B8%E5%88%86%E8%AF%8D.md) | 25 | | [第 3 章:二元语法与中文分词](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/3.%E4%BA%8C%E5%85%83%E8%AF%AD%E6%B3%95%E4%B8%8E%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D.md) | 26 | | [第 4 章:隐马尔可夫模型与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/4.%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md ) | 27 | | [第 5 章:感知机分类与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/5.%E6%84%9F%E7%9F%A5%E6%9C%BA%E5%88%86%E7%B1%BB%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) | 28 | | [第 6 章:条件随机场与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/6.%E6%9D%A1%E4%BB%B6%E9%9A%8F%E6%9C%BA%E5%9C%BA%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) | 29 | | [第 7 章:词性标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/7.%E8%AF%8D%E6%80%A7%E6%A0%87%E6%B3%A8.md) | 30 | | [第 8 章:命名实体识别](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/8.%E5%91%BD%E5%90%8D%E5%AE%9E%E4%BD%93%E8%AF%86%E5%88%AB.md) | 31 | | [第 9 章:信息抽取](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/9.%E4%BF%A1%E6%81%AF%E6%8A%BD%E5%8F%96.md) | 32 | | [第 10 章:文本聚类](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/10.%E6%96%87%E6%9C%AC%E8%81%9A%E7%B1%BB.md) | 33 | | [第 11 章:文本分类](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/11.%E6%96%87%E6%9C%AC%E5%88%86%E7%B1%BB.md) | 34 | | [第 12 章:依存句法分析](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/12.%E4%BE%9D%E5%AD%98%E5%8F%A5%E6%B3%95%E5%88%86%E6%9E%90.md) | 35 | | [第 13 章:深度学习与自然语言处理](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/13.%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%8E%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86.md) | 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter/1. 新手上路.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | [1. 新手上路](#1-新手上路) 3 | - [1.1 自然语言与编程语言的比较](#11-自然语言与编程语言的比较) 4 | - [1.2 自然语言处理的层次](#12-自然语言处理的层次) 5 | - [1.3 自然语言处理的流派](#13-自然语言处理的流派) 6 | - [1.4 机器学习](#14-机器学习) 7 | - [1.5 语料库](#15-语料库) 8 | - [1.6 开源工具](#16-开源工具) 9 | - [1.7 总结](#17-总结) 10 | 11 | ## 1. 新手上路 12 | 13 | **自然语言处理**(Natural Language Processing,NLP)是一门融合了计算机科学、人工智能及语言学的交叉学科,它们的关系如下图所示。这门学科研究的是如何通过机器学习等技术,让计算机学会处理人类语言,乃至实现终极目标--理解人类语言或人工智能。 14 | 15 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_10-50-30.png) 16 | 17 | 美国计算机科学家Bill Manaris在《计算机进展》( Advances in Computers)第47卷的《从人机交互的角度看自然语言处理》一文中曾经给**自然语言处理提出了如下的定义**: 18 | 19 | > “自然语言处理可以定义为研究在人与人交际中以及在人与计算机交际中的语言问题的一门学科。自然语言处理要研制表示语言能力和语言应用的模型,建立计算框架来实现这样的语言模型,提出相应的方法来不断地完善这样的语言模型,根据这样的语言模型设计各种实用系统,并探讨这些实用系统的评测技术。” 20 | 21 | 22 | 23 | ### 1.1 自然语言与编程语言的比较 24 | 25 | | 比较 | 不同 | 例子 | 26 | | ---------- | ------------------------------------------------------------ | ------------------- | 27 | | **词汇量** | 自然语言中的词汇比编程语言中的关键词丰富,我们还可以随时创造各种类型的新词 | 蓝瘦、香菇 | 28 | | **结构化** | 自然语言是非结构化的,而编程语言是结构化的 | | 29 | | **歧义性** | 自然语言含有大量歧义,而编程语言是确定性的 | 这人真有意思:没意思 | 30 | | **容错性** | 自然语言错误随处可见,而编程语言错误会导致编译不通过 | 的、地的用法错误 | 31 | | **易变性** | 自然语言变化相对迅速嘈杂一些,而编程语言的变化要缓慢得多 | 新时代词汇 | 32 | | **简略性** | 自然语言往往简洁、干练,而编程语言就要明确定义 | “老地方”不必指出 | 33 | 34 | 35 | 36 | ### 1.2 自然语言处理的层次 37 | 38 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_11-17-38.png) 39 | 40 | 1. **语音、图像和文本** 41 | 42 | 自然语言处理系统的输入源一共有3个,即语音、图像与文本。语音和图像这两种形式一般经过识别后转化为文字,转化后就可以进行后续的NLP任务了。 43 | 44 | 2. **中文分词、词性标注和命名实体识别** 45 | 46 | 这3个任务都是围绕词语进行的分析,所以统称**词法分析**。词法分析的主要任务是将文本分隔为有意义的词语(**中文分词**),确定每个词语的类别和浅层的歧义消除(**词性标注**),并且识别出一些较长的专有名词(**命名实体识别**)。对中文而言,词法分析常常是后续高级任务的基础。 47 | 48 | 3. **信息抽取** 49 | 50 | 词法分析之后,文本已经呈现出部分结构化的趋势,根据分析出来的每个单词和附有自己词性及其他标签的数据,抽取出一部分有用的信息,关键词、专业术语等,也可以根据统计学信息抽取出更大颗粒度的文本。 51 | 52 | 4. **文本分类与文本聚类** 53 | 54 | 将文本拆分为一系列词语之后,就可以对文本进行分类和聚类操作,找出相类似的文本。 55 | 56 | 5. **句法分析** 57 | 58 | 词法分析只能得到零散的词汇信息,计算机不知道词语之间的关系。在一些问答系统中,需要得到句子的主谓宾结构,这就是句法分析得到的结果,如下图所示: 59 | 60 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_12-8-55.png) 61 | 62 | 不仅是问答系统或搜索引擎,句法分析还经常应用有基于短语的机器翻译,给译文的词语重新排序。 63 | 64 | 6. **语义分析与篇章分析** 65 | 66 | 相较于句法分析,语义分析侧重语义而非语法。它包括**词义消歧**(确定一个词在语境中的含义,而不是简单的词性)、**语义角色标注**(标注句子中的谓语与其他成分的关系)乃至**语义依存分析**(分析句子中词语之间的语义关系)。 67 | 68 | 7. **其他高级任务** 69 | 70 | 自动问答、自动摘要、机器翻译 71 | 72 | 注意,一般认为信息检索(Information Retrieve,IR)是区别于自然语言处理的独立学科,IR的目标是查询信息,而NLP的目标是理解语言。 73 | 74 | 75 | 76 | ### 1.3 自然语言处理的流派 77 | 78 | 1. **基于规则的专家系统** 79 | 80 | 规则,指的是由专家手工制定的确定性流程。专家系统要求设计者对所处理的问题具备深入的理解,并且尽量以人力全面考虑所有可能的情况。它最大的弱点是难以拓展。当规则数量增加或者多个专家维护同一个系统时,就容易出现冲突。 81 | 82 | 2. **基于统计的学习方法** 83 | 84 | 人们使用统计方法让计算机自动学习语言。所谓“**统计**”,指的是在语料库上进行的统计。所谓“**语料库**”,指的是人工标注的结构化文本。 85 | 86 | 统计学习方法其实是机器学习的别称,而机器学习则是当代实现人工智能的主要途径。 87 | 88 | 3. **历史** 89 | 90 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_12-26-11.png) 91 | 92 | 93 | 94 | ### 1.4 机器学习 95 | 96 | 1. **什么是机器学习** 97 | 98 | 美国工程院院士 Tom Mitchell 给过一个更明确的定义,**机器学习**指的是计算机通过某项任务的经验数据提高了在该项任务上的能力。 99 | 100 | 2. **模型** 101 | 102 | 模型是对现实问题的数学抽象,由一个假设函数以及一系列参数构成。以下就是最简单的模型公式: 103 | 104 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_19-41-15.png) 105 | 106 | 其中,w 和 b 是函数的参数,而 x 是函数的自变量。不过模型并不包括具体的自变量x,因为自变量是由用户输入的。自变量 x 是一个特征向量,用来表示一个对象的特征。 107 | 108 | 3. **特征** 109 | 110 | - **特征**指的是事物的特点转化的数值。 111 | - 如何挑选特征,如何设计特征模板,这称作**特征工程**。特征越多,参数就越多;参数越多,模型就越复杂。 112 | 113 | 4. **数据集** 114 | 115 | 样本的集合在机器学习领域称作**数据集**,在自然语言处理领域称作**语料库**。 116 | 117 | 5. **监督学习** 118 | 119 | 如果数据集附带标准答案 y,则此时的学习算法称作**监督学习**。学习一遍误差还不够小,需要反复学习、反复调整。此时的算法是一种迭代式的算法,每一遍学习称作**一次迭代**。这种在有标签的数据集上迭代学习的过程称作**训练**。 120 | 121 | 6. **无监督学习** 122 | 123 | 如果我们只给机器做题,却不告诉它参考答案,机器仍然可以学到知识吗?可以,此时的学习称作**无监督学习**,而不含标准答案的数据集被称作**无标注的数据集**。无监督学习一般用于聚类和降维,**降维**指的是将样本点从高维空间变换成低维空间的过程。 124 | 125 | 7. **其他类型的机器学习算法** 126 | 127 | - **半监督学习**:如果我们训练多个模型,然后对同一个实例执行预测,会得到多个结果。如果这些结果多数一致,则可以将该实例和结果放到一起作为新的训练样本,用力啊扩充训练集。这样的算法被称为半监督学习。 128 | - **强化学习**:现实世界中的事物之间往往有很长的因果链:我们要正确地执行一系列彼此关联的决策,才能得到最终的成果。这类问题往往需要一边预测,一边根据环境的反馈规划下次决策。这类算法被称为强化学习。 129 | 130 | 131 | 132 | ### 1.5 语料库 133 | 134 | 1. **中文分词语料库** 135 | 136 | 中文分词语料库指的是,由人工正确切分的句子集合。以著名的1998年《人民日报》语料库为例: 137 | 138 | > 先 有 通货膨胀 干扰,后 有 通货 紧缩 叫板。 139 | 140 | 2. **词性标注语料库** 141 | 142 | 它指的是切分并为每个词语制定一个词性的语料。依然以《人民日报》语料库为例: 143 | 144 | > 迈向/v 充满/v 希望/n 的/u 新/a 世纪/n --/w 一九九八年/t 新年/t 讲话/n 145 | 146 | 这里每个单词后面用斜杠隔开的就是词性标签。 147 | 148 | 3. **命名实体识别语料库** 149 | 150 | 这种语料库人工标注了文本内部制作者关心的实体名词以及实体类别。比如《人民日报》语料库中-共含有人名、地名和机构名3种命名实体: 151 | 152 | > **萨哈夫/nr** 说/v ,/w **伊拉克/ns** 将/d 同/p **[联合国/nt 销毁/v 伊拉克/ns 大规模/b 杀伤性/n 武器/n 特别/a 委员会/n] /nt** 继续/v 保持/v 合作/v 。/w 153 | 154 | 这个句子中的加粗词语分别是人名、地名和机构名。中括号括起来的是复合词,我们可以观察到:有时候机构名和地名复合起来会构成更长的机构名,这种构词法上的嵌套现象增加了命名实体识别的难度。 155 | 156 | 4. **句法分析语料库** 157 | 158 | 汉语中常用的句法分析语料库有CTB(Chinese Treebank,中文树库),其中一个句子可视化后如下图所示: 159 | 160 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_13-40-56.png) 161 | 162 | 中文单词上面的英文标签标示词性,而箭头表示有语法联系的两个单词,具体是何种联系由箭头上的标签标示。 163 | 164 | 5. **文本分类语料库** 165 | 166 | 它指的是人工标注了所属分类的文章构成的语料库。 167 | 168 | 6. **语料库的建设** 169 | 170 | 语料库建设指的是构建一份语料库的过程,分为规范制定、人员培训与人工标注这三个阶段。针对不同类型的任务,人们开发出许多标注软件,其中比较成熟的一款是brat,它支持词性标注、命名实体识别和句法分析等任务。 171 | 172 | 173 | 174 | ### 1.6 开源工具 175 | 176 | 1. **主流NLP工具比较** 177 | 178 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-3_13-52-12.png) 179 | 180 | 另外,也研究过其他开源项目的原理,借鉴了其中优秀的设计。但毕竟还是自己写的代码讲得最清楚,所以综合以上各种考虑,最后选取了**HanLP作为本书的实现**。 181 | 182 | 2. **Python接口** 183 | 184 | HanLP 的 Python 接口由 pyhanlp 包提供,其安装只需一句命令: 185 | 186 | ```shell 187 | $ pip install pyhanlp 188 | ``` 189 | 190 | 191 | 192 | ### 1.7 总结 193 | 194 | 本章给出了人工智能、机器学习与自然语言处理的宏观缩略图与发展时间线。机器学习是人工智能的子集,而自然语言处理则是人工智能与语言学、计算机科学的交集。这个交集虽然小,它的难度却很大。为了实现理解自然语言这个宏伟目标,人们尝试了规则系统,并最终发展到基于大规模语料库的统计学习系统。 195 | 196 | 在接下来的章节中,就让我们按照这种由易到难的发展规律去解决第一个NLP问题一中文分词。我们将先从规则系统人手,介绍一些快而不准的算法,然后逐步进化到更加准确的统计模型。 197 | -------------------------------------------------------------------------------- /chapter/10.文本聚类.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [10. 文本聚类](#10-文本聚类) 3 | - [10.1 概述](#101-概述) 4 | - [10.2 文档的特征提取](#102-文档的特征提取) 5 | - [10.3 k均值算法](#103-k均值算法) 6 | - [10.4 重复二分聚类算法](#104-重复二分聚类算法) 7 | - [10.5 标准化评测](#105-标准化评测) 8 | 9 | ## 10. 文本聚类 10 | 11 | 正所谓物以类聚,人以群分。人们在获取数据时需要整理,将相似的数据归档到一起,自动发现大量样本之间的相似性,这种根据相似性归档的任务称为聚类。 12 | 13 | 14 | 15 | ### 10.1 概述 16 | 17 | 1. **聚类** 18 | 19 | **聚类**(cluster analysis )指的是将给定对象的集合划分为不同子集的过程,目标是使得每个子集内部的元素尽量相似,不同子集间的元素尽量不相似。这些子集又被称为**簇**(cluster),一般没有交集。 20 | 21 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_22-32-31.png) 22 | 23 | 一般将聚类时簇的数量视作由使用者指定的超参数,虽然存在许多自动判断的算法,但它们往往需要人工指定其他超参数。 24 | 25 | 根据聚类结果的结构,聚类算法也可以分为**划分式**(partitional )和**层次化**(hierarchieal两种。划分聚类的结果是一系列不相交的子集,而层次聚类的结果是一棵树, 叶子节点是元素,父节点是簇。本章主要介绍划分聚类。 26 | 27 | 28 | 29 | 2. **文本聚类** 30 | 31 | **文本聚类**指的是对文档进行聚类分析,被广泛用于文本挖掘和信息检索领域。 32 | 33 | 文本聚类的基本流程分为特征提取和向量聚类两步, 如果能将文档表示为向量,就可以对其应用聚类算法。这种表示过程称为**特征提取**,而一旦将文档表示为向量,剩下的算法就与文档无关了。这种抽象思维无论是从软件工程的角度,还是从数学应用的角度都十分简洁有效。 34 | 35 | 36 | 37 | ### 10.2 文档的特征提取 38 | 39 | 1. **词袋模型** 40 | 41 | **词袋**(bag-of-words )是信息检索与自然语言处理中最常用的文档表示模型,它将文档想象为一个装有词语的袋子, 通过袋子中每种词语的计数等统计量将文档表示为向量。比如下面的例子: 42 | 43 | ``` 44 | 人 吃 鱼。 45 | 美味 好 吃! 46 | ``` 47 | 48 | 统计词频后如下: 49 | 50 | ``` 51 | 人=1 52 | 吃=2 53 | 鱼=1 54 | 美味=1 55 | 好=1 56 | ``` 57 | 58 | 文档经过该词袋模型得到的向量表示为[1,2,1,1,1],这 5 个维度分别表示这 5 种词语的词频。 59 | 60 | 一般选取训练集文档的所有词语构成一个词表,词表之外的词语称为 oov,不予考虑。一旦词表固定下来,假设大小为 N。则任何一个文档都可以通过这种方法转换为一个N维向量。词袋模型不考虑词序,也正因为这个原因,词袋模型损失了词序中蕴含的语义,比如,对于词袋模型来讲,“人吃鱼”和“鱼吃人”是一样的,这就不对了。 61 | 62 | 不过目前工业界已经发展出很好的词向量表示方法了: word2vec/bert 模型等。 63 | 64 | 65 | 66 | 2. **词袋中的统计指标** 67 | 68 | 词袋模型并非只是选取词频作为统计指标,而是存在许多选项。常见的统计指标如下: 69 | 70 | - 布尔词频: 词频非零的话截取为1,否则为0,适合长度较短的数据集 71 | - TF-IDF: 适合主题较少的数据集 72 | - 词向量: 如果词语本身也是某种向量的话,则可以将所有词语的词向量求和作为文档向量。适合处理 OOV 问题严重的数据集。 73 | - 词频向量: 适合主题较多的数据集 74 | 75 | 定义由 n 个文档组成的集合为 S,定义其中第 i 个文档 di 的特征向量为 di,其公式如下: 76 | 77 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-26-31.gif) 78 | 79 | 其中 tj表示词表中第 j 种单词,m 为词表大小, TF(tj, di) 表示单词 tj 在文档 di 中的出现次数。为了处理长度不同的文档,通常将文档向量处理为单位向量,即缩放向量使得 ||d||=1。 80 | 81 | 82 | 83 | ### 10.3 k均值算法 84 | 85 | 一种简单实用的聚类算法是k均值算法(k-means),由Stuart Lloyd于1957年提出。该算法虽然无法保证一定能够得到最优聚类结果,但实践效果非常好。基于k均值算法衍生出许多改进算法,先介绍 k均值算法,然后推导它的一个变种。 86 | 87 | 1. **基本原理** 88 | 89 | 形式化啊定义 k均值算法所解决的问题,给定 n 个向量 d1 到 dn,以及一个整数 k,要求找出 k 个簇 S1 到 Sk 以及各自的质心 C1 到 Ck,使得下式最小: 90 | 91 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-28-33.gif) 92 | 93 | 其中 ||di - Cr|| 是向量与质心的欧拉距离,I(Euclidean) 称作聚类的**准则函数**。也就是说,k均值以最小化每个向量到质心的欧拉距离的平方和为准则进行聚类,所以该准则函数有时也称作**平方误差和**函数。而质心的计算就是簇内数据点的几何平均: 94 | 95 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-29-43.gif) 96 | 97 | 其中,si 是簇 Si 内所有向量之和,称作**合成向量**。 98 | 99 | 生成 k 个簇的 k均值算法是一种迭代式算法,每次迭代都在上一步的基础上优化聚类结果,步骤如下: 100 | 101 | - 选取 k 个点作为 k 个簇的初始质心。 102 | - 将所有点分别分配给最近的质心所在的簇。 103 | - 重新计算每个簇的质心。 104 | - 重复步骤 2 和步骤 3 直到质心不再发生变化。 105 | 106 | k均值算法虽然无法保证收敛到全局最优,但能够有效地收敛到一个局部最优点。对于该算法,初级读者重点需要关注两个问题,即初始质心的选取和两点距离的度量。 107 | 108 | 109 | 110 | 2. **初始质心的选取** 111 | 112 | 由于 k均值不保证收敏到全局最优,所以初始质心的选取对k均值的运行结果影响非常大,如果选取不当,则可能收敛到一个较差的局部最优点。 113 | 114 | 一种更高效的方法是, 将质心的选取也视作准则函数进行迭代式优化的过程。其具体做法是,先随机选择第一个数据点作为质心,视作只有一个簇计算准则函数。同时维护每个点到最近质心的距离的平方,作为一个映射数组 M。接着,随机取准则函数值的一部分记作。遍历剩下的所有数据点,若该点到最近质心的距离的平方小于0,则选取该点添加到质心列表,同时更新准则函数与 M。如此循环多次,直至凑足 k 个初始质心。这种方法可行的原理在于,每新增一个质心,都保证了准则函数的值下降一个随机比率。 而朴素实现相当于每次新增的质心都是完全随机的,准则函数的增减无法控制。孰优孰劣,一目了然。 115 | 116 | 考虑到 k均值是一种迭代式的算法, 需要反复计算质心与两点距离,这部分计算通常是效瓶颈。为了改进朴素 k均值算法的运行效率,HanLP利用种更快的准则函数实现了k均值的变种。 117 | 118 | 119 | 120 | 3. **更快的准则函数** 121 | 122 | 除了欧拉准则函数,还存在一种基于余弦距离的准则函数: 123 | 124 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-30-58.gif) 125 | 126 | 该函数使用余弦函数衡量点与质心的相似度,目标是最大化同簇内点与质心的相似度。将向量夹角计算公式代人,该准则函数变换为: 127 | 128 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-32-1.gif) 129 | 130 | 代入后变换为: 131 | 132 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-33-24.gif) 133 | 134 | 也就是说,余弦准则函数等于 k 个簇各自合成向量的长度之和。比较之前的准则函数会发现在数据点从原簇移动到新簇时,I(Euclidean) 需要重新计算质心,以及两个簇内所有点到新质心的距离。而对于I(cos),由于发生改变的只有原簇和新簇两个合成向量,只需求两者的长度即可,计算量一下子减小不少。 135 | 136 | 基于新准则函数 I(cos),k均值变种算法流程如下: 137 | 138 | - 选取 k 个点作为 k 个簇的初始质心。 139 | - 将所有点分别分配给最近的质心所在的簇。 140 | - 对每个点,计算将其移入另一个簇时 I(cos) 的增大量,找出最大增大量,并完成移动。 141 | - 重复步骤 3 直到达到最大迭代次数,或簇的划分不再变化。 142 | 143 | 144 | 145 | 4. **实现** 146 | 147 | 在 HanLP 中,聚类算法实现为 ClusterAnalyzer,用户可以将其想象为一个文档 id 到文档向量的映射容器。 148 | 149 | 此处以某音乐网站中的用户聚类为案例讲解聚类模块的用法。假设该音乐网站将 6 位用户点播的歌曲的流派记录下来,并且分别拼接为 6 段文本。给定用户名称与这 6 段播放历史,要求将这 6 位用户划分为 3 个簇。实现代码如下: 150 | 151 | ```python 152 | from pyhanlp import * 153 | 154 | ClusterAnalyzer = JClass('com.hankcs.hanlp.mining.cluster.ClusterAnalyzer') 155 | 156 | if __name__ == '__main__': 157 | analyzer = ClusterAnalyzer() 158 | analyzer.addDocument("赵一", "流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 摇滚, 摇滚, 摇滚, 摇滚") 159 | analyzer.addDocument("钱二", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲") 160 | analyzer.addDocument("张三", "古典, 古典, 古典, 古典, 民谣, 民谣, 民谣, 民谣") 161 | analyzer.addDocument("李四", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 金属, 金属, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲") 162 | analyzer.addDocument("王五", "流行, 流行, 流行, 流行, 摇滚, 摇滚, 摇滚, 嘻哈, 嘻哈, 嘻哈") 163 | analyzer.addDocument("马六", "古典, 古典, 古典, 古典, 古典, 古典, 古典, 古典, 摇滚") 164 | print(analyzer.kmeans(3)) 165 | ``` 166 | 167 | 结果如下: 168 | 169 | ``` 170 | [[李四, 钱二], [王五, 赵一], [张三, 马六]] 171 | ``` 172 | 173 | 通过 k均值聚类算法,我们成功的将用户按兴趣分组,获得了“人以群分”的效果。 174 | 175 | 聚类结果中簇的顺序是随机的,每个簇中的元素也是无序的,由于 k均值是个随机算法,有小概率得到不同的结果。 176 | 177 | 该聚类模块可以接受任意文本作为文档,而不需要用特殊分隔符隔开单词。 178 | 179 | 180 | 181 | ### 10.4 重复二分聚类算法 182 | 183 | 1. **基本原理** 184 | 185 | **重复二分聚类**(repeated bisection clustering) 是 k均值算法的效率加强版,其名称中的bisection是“二分”的意思,指的是反复对子集进行二分。该算法的步骤如下: 186 | 187 | - 挑选一个簇进行划分。 188 | - 利用 k均值算法将该簇划分为 2 个子集。 189 | - 重复步骤 1 和步骤 2,直到产生足够舒朗的簇。 190 | 191 | 每次产生的簇由上到下形成了一颗二叉树结构。 192 | 193 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_22-36-21.png) 194 | 195 | 196 | 197 | 正是由于这个性质,重复二分聚类算得上一种基于划分的层次聚类算法。如果我们把算法运行的中间结果存储起来,就能输出一棵具有层级关系的树。树上每个节点都是一个簇,父子节点对应的簇满足包含关系。虽然每次划分都基于 k均值,由于每次二分都仅仅在一个子集上进行,输人数据少,算法自然更快。 198 | 199 | 在步骤1中,HanLP采用二分后准则函数的增幅最大为策略,每产生一个新簇,都试着将其二分并计算准则函数的增幅。然后对增幅最大的簇执行二分,重复多次直到满足算法停止条件。 200 | 201 | 202 | 203 | 2. **自动判断聚类个数k** 204 | 205 | 读者可能觉得聚类个数 k 这个超参数很难准确估计。在重复二分聚类算法中,有一种变通的方法,那就是通过给准则函数的增幅设定阈值 β 来自动判断 k。此时算法的停止条件为,当一个簇的二分增幅小于 β 时不再对该簇进行划分,即认为这个簇已经达到最终状态,不可再分。当所有簇都不可再分时,算法终止,最终产生的聚类数量就不再需要人工指定了。 206 | 207 | 208 | 209 | 3. **实现** 210 | 211 | ```python 212 | from pyhanlp import * 213 | 214 | ClusterAnalyzer = JClass('com.hankcs.hanlp.mining.cluster.ClusterAnalyzer') 215 | 216 | if __name__ == '__main__': 217 | analyzer = ClusterAnalyzer() 218 | analyzer.addDocument("赵一", "流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 摇滚, 摇滚, 摇滚, 摇滚") 219 | analyzer.addDocument("钱二", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲") 220 | analyzer.addDocument("张三", "古典, 古典, 古典, 古典, 民谣, 民谣, 民谣, 民谣") 221 | analyzer.addDocument("李四", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 金属, 金属, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲") 222 | analyzer.addDocument("王五", "流行, 流行, 流行, 流行, 摇滚, 摇滚, 摇滚, 嘻哈, 嘻哈, 嘻哈") 223 | analyzer.addDocument("马六", "古典, 古典, 古典, 古典, 古典, 古典, 古典, 古典, 摇滚") 224 | 225 | print(analyzer.repeatedBisection(3)) # 重复二分聚类 226 | print(analyzer.repeatedBisection(1.0)) # 自动判断聚类数量k 227 | ``` 228 | 229 | 运行结果如下: 230 | 231 | ``` 232 | [[李四, 钱二], [王五, 赵一], [张三, 马六]] 233 | [[李四, 钱二], [王五, 赵一], [张三, 马六]] 234 | ``` 235 | 236 | 与上面音乐案例得出的结果一致,但运行速度要快不少。 237 | 238 | 239 | 240 | ### 10.5 标准化评测 241 | 242 | 本次评测选择搜狗实验室提供的文本分类语料的一个子集,我称它为“搜狗文本分类语料库迷你版”。该迷你版语料库分为5个类目,每个类目下1000 篇文章,共计5000篇文章。运行代码如下: 243 | 244 | ```python 245 | from pyhanlp import * 246 | 247 | import zipfile 248 | import os 249 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 250 | 251 | def test_data_path(): 252 | """ 253 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 254 | :return: 255 | """ 256 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 257 | if not os.path.isdir(data_path): 258 | os.mkdir(data_path) 259 | return data_path 260 | 261 | 262 | 263 | ## 验证是否存在 MSR语料库,如果没有自动下载 264 | def ensure_data(data_name, data_url): 265 | root_path = test_data_path() 266 | dest_path = os.path.join(root_path, data_name) 267 | if os.path.exists(dest_path): 268 | return dest_path 269 | 270 | if data_url.endswith('.zip'): 271 | dest_path += '.zip' 272 | download(data_url, dest_path) 273 | if data_url.endswith('.zip'): 274 | with zipfile.ZipFile(dest_path, "r") as archive: 275 | archive.extractall(root_path) 276 | remove_file(dest_path) 277 | dest_path = dest_path[:-len('.zip')] 278 | return dest_path 279 | 280 | 281 | sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip') 282 | 283 | 284 | ## =============================================== 285 | ## 以下开始聚类 286 | 287 | ClusterAnalyzer = JClass('com.hankcs.hanlp.mining.cluster.ClusterAnalyzer') 288 | 289 | if __name__ == '__main__': 290 | for algorithm in "kmeans", "repeated bisection": 291 | print("%s F1=%.2f\n" % (algorithm, ClusterAnalyzer.evaluate(sogou_corpus_path, algorithm) * 100)) 292 | ``` 293 | 294 | 运行结果如下: 295 | 296 | ``` 297 | kmeans F1=83.74 298 | 299 | repeated bisection F1=85.58 300 | ``` 301 | 302 | 评测结果如下表: 303 | 304 | | 算法 | F1 | 耗时 | 305 | | ------------ | ----- | ---- | 306 | | k均值 | 83.74 | 67秒 | 307 | | 重复二分聚类 | 85.58 | 24秒 | 308 | 309 | 对比两种算法,重复二分聚类不仅准确率比 k均值更高,而且速度是 k均值的 3 倍。然而重复二分聚类成绩波动较大,需要多运行几次才可能得出这样的结果。 310 | 311 | 无监督聚类算法无法学习人类的偏好对文档进行划分,也无法学习每个簇在人类那里究竟叫什么。 312 | -------------------------------------------------------------------------------- /chapter/11.文本分类.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [11. 文本分类](#11-文本分类) 3 | - [11.1 文本分类的概念](#111-文本分类的概念) 4 | - [11.2 文本分类语料库](#112-文本分类语料库) 5 | - [11.3 文本分类的特征提取](#113-文本分类的特征提取) 6 | - [11.4 朴素贝叶斯分类器](#114-朴素贝叶斯分类器) 7 | - [11.5 支持向量机](#115-支持向量机) 8 | - [11.6 标准化评测](#116-标准化评测) 9 | - [11.7 情感分析](#117-情感分析) 10 | 11 | ## 11. 文本分类 12 | 13 | 上一章我们学习了 文本聚类,体验了无须标注语料库的便利性。然而无监督学习总归无法按照我们的意志预测出文档的类别,限制了文本聚类的应用场景。有许多场景需要将文档分门别类地归人具体的类别中,比如垃圾邮件过滤和社交媒体的自动标签推荐。在这一章中, 我们将介绍如何实现这些需求。 14 | 15 | 16 | 17 | ### 11.1 文本分类的概念 18 | 19 | **文本分类**( text classification),又称**文档分类**( document classification),指的是将一个文档归类到一个或多个类别中的自然语言处理任务。文本分类的应用场景非常广泛,涵盖垃圾邮件过滤、垃圾评论过滤、自动标签、情感分析等任何需要自动归档文本的场合。 20 | 21 | 文本的类别有时又称作**标签**,所有类别组成了标注集,文本分类输出结果一定属于标注集。 22 | 23 | 文本分类是一个典型的监督学习任务,其流程离不开人工指导: 人工标注文档的类别,利用语料训练模型,利用模型预测文档的类别。 24 | 25 | 26 | 27 | ### 11.2 文本分类语料库 28 | 29 | 文本分类语料库的标注过程相对简单,只需收集一些文档, 人工指定每篇文档的类别即可。另外,许多新闻网站的栏目是由编辑人工整理的,如果栏目设置符合要求,也可以用爬虫爬取下来作语料库使用。其中,搜狗实验室就提供了这样一份语料库 ,详情见代码(**自动下载语料库**): load_text_classification_corpus.py 30 | 31 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/load_text_classification_corpus.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/load_text_classification_corpus.py) 32 | 33 | 运行结果如下: 34 | 35 | ``` 36 | 标注集:[教育, 汽车, 健康, 军事, 体育, 自然语言处理] 37 | 第一篇文档的类别:教育 38 | ``` 39 | 40 | 当语料库就绪时,文本分类的流程一般分为特征提取和分类器处理两大步。 41 | 42 | 43 | 44 | ### 11.3 文本分类的特征提取 45 | 46 | 在机器学习中,我们需要对具体对象提取出有助于分类的特征,才能交给某个分类器进行分类。这些特征数值化后为一个定长的向量(数据点),用来作为分类器的输入。在训练时,分类器根据数据集中的数据点学习出决策边界。在预测时,分类器根据输人的效据点落在决策边界的位置来决定类别。 47 | 48 | 我们依然使用词袋向量作为特征向量,词袋向量是词语颗粒度上的频次或 TF-IDF 向量,先进行分词处理 49 | 50 | 1. **分词** 51 | 52 | HanLP 允许为数据集的构造函数指定一个分词器 ITokenizer,用来实现包括分词在内的预处理逻辑。 53 | 54 | | 实现 | 应用场景 | 55 | | --------------- | ----------------------------------------------- | 56 | | HanLPTokenizer | 中文文本,使用NotionalTokenizer分词并过滤停用词 | 57 | | BlankTokenizer | 英文文本,使用空格分词 | 58 | | BigramTokenizer | 中文文本,将相邻字符作为二元语法输出 | 59 | 60 | 61 | 62 | 2. **卡方特征选择** 63 | 64 | 在文本分类时会有这样一个问题,比如汉语中的虚词“的”,这些词在所有类别的文档中均匀出现,为了消除这些单词的影响,一方面可以用停用词表,另一方面可以用**卡方非参数检验**来过滤掉与类别相关程度不高的词语。 65 | 66 | 在统计学上,卡方检验常用于检验两个事件的独立性,如果两个随机事件 A 和 B 相互独立,则两者同时发生的概率P(AB)= P(A)P(B)。如果将词语的出现与类别的出现作为两个随机事件则类别独立性越高的词语越不适合作为特征。如果将某个事件的期望记作 E,实际出现(观测)的频次记作 N,则卡方检验衡量期望与观测的相似程度。卡方检验值越高,则期望和观测的计数越相化也更大程度地否定了独立性。 67 | 68 | 一旦确定了哪些特征有用,接下来就可以将文档转化为向量了。 69 | 70 | 71 | 72 | 3. **词袋向量** 73 | 74 | 我们提取的是 TF 特征,统计出每个特征及其频次。以特征的 id 作为下标,频次作为数值,假设一共有 n 个特征,一篇文档就转化为 n 维的词袋向量。沿用机器学习文献的习惯,将词袋向量记作 x,向量的第 i 维记作 X1。将类别记 75 | 作 y,其中 K 为类别总数。则语料库(训练数据集) T 可以表示为词袋向量 x 和类别 y 所构成的二元组的集合: 76 | 77 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_14-47-11.gif) 78 | 79 | 在不进行特征选择的前提下,如果以词语作为特征,则 n 大约在 10 万量级;如果以字符二元语法作为特征,则 n 大约在 50 万量级。数十万维的向量运算开销不容小觑,一般利用卡方特征选择,可以将特征数量减小到10% ~ 20%左右。 80 | 81 | 当文档被转化为向量后,就可以利用机器学习进行训练了。 82 | 83 | 84 | 85 | ### 11.4 朴素贝叶斯分类器 86 | 87 | 在各种各样的分类器中,**朴素贝叶斯法**( naive Bayes)可算是最简单常用的一种生成式模型。朴素贝叶斯法基于贝叶斯定理将联合概率转化为条件概率,然后利用特征条件独立假设简化条件概率的计算。 88 | 89 | 1. **朴素贝叶斯法原理** 90 | 91 | 朴素贝叶斯法的目标是通过训练集学习联合概率分布 P(X,Y),由贝叶斯定理可以将联合概率转换为先验概率分布与条件概率分布之积: 92 | 93 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-49-16.gif) 94 | 95 | - **首先**计算先验概率分布 P(Y=Ck),通过统计每个类别下的样本数: 96 | 97 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-51-18.gif) 98 | 99 | - **然后**计算 P(X=x|Y=Ck),这个难以估计,因为 x 的量级非常大,可以从下式看出来: 100 | 101 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-52-26.gif) 102 | 103 | 该条件概率分布的参数数量是指数级的,难以估计。 104 | 105 | 为此朴素贝叶斯法“朴素”的假设了所有特征是条件独立的: 106 | 107 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-54-25.gif) 108 | 109 | 于是,又可以利用极大似然来进行估计: 110 | 111 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-55-22.gif) 112 | 113 | - **预测**时,朴素贝叶斯法依然利用贝叶斯公式找出后验概率 P(Y=Ck|X=x) 最大的类别 Ck 作为输出 y: 114 | 115 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-56-25.gif) 116 | 117 | 将贝叶斯公式带入上式得: 118 | 119 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-57-21.gif) 120 | 121 | - **最终**,由于分母与 Ck 无关,可以省略掉,然后将独立性假设带入,得到最终的分类预测函数: 122 | 123 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-13_15-58-29.gif) 124 | 2. **朴素贝叶斯分类器实现** 125 | 126 | 实现代码详见: text_classification.py 127 | 128 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/text_classification.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/text_classification.py) 129 | 130 | 运行结果如下: 131 | 132 | ``` 133 | 《C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练》 属于分类 【体育】 134 | 《英国造航母耗时8年仍未服役 被中国速度远远甩在身后》 属于分类 【军事】 135 | 《 研究生考录模式亟待进一步专业化》 属于分类 【教育】 136 | 《如果真想用食物解压,建议可以食用燕麦》 属于分类 【健康】 137 | 《通用及其部分竞争对手目前正在考虑解决库存问题》 属于分类 【汽车】 138 | ``` 139 | 140 | 朴素贝叶斯法实现简单,但由于特征独立性假设过于强烈,有时会影响准确性,下面开始介绍更加健壮的支持向量机分类器。 141 | 142 | 143 | 144 | ### 11.5 支持向量机 145 | 146 | **支持向量机**( Support Vector Machine, SVM)是一种二分类模型,其学习策略在于如何找出一个决策边界,使得边界到正负样本的最小距离都最远。这种策略使得支持向量机有别于感知机,能够找到一个更加稳健的决策边界。支持向量机最简单的形式为线性支持向量机,其决策边界为一个超平面,适用于线性可分数据集。 147 | 148 | 有关**支持向量机(SVM)的原理**详见我的博客,这里不加详细介绍: 149 | 150 | [http://mantchs.com/2019/07/11/ML/SVM/](http://mantchs.com/2019/07/11/ML/SVM/) 151 | 152 | **线性支持向量机文本分类器实现** 153 | 154 | 实现代码详见: svm_text_classification.py 155 | 156 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/svm_text_classification.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/svm_text_classification.py) 157 | 158 | 可能第一次运行失败,java类没有加载,重启环境运行就可以了,运行结果如下: 159 | 160 | ``` 161 | 《C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练》 属于分类 【体育】 162 | 《潜艇具有很强的战略威慑能力与实战能力》 属于分类 【军事】 163 | 《 研究生考录模式亟待进一步专业化》 属于分类 【汽车】 164 | 《如果真想用食物解压,建议可以食用燕麦》 属于分类 【健康】 165 | 《通用及其部分竞争对手目前正在考虑解决库存问题》 属于分类 【汽车】 166 | ``` 167 | 168 | 169 | 170 | ### 11.6 标准化评测 171 | 172 | 本次评测采用两种分类器和两种分词器搭配进行评估,所有试验采用的数据集皆为搜狗文本分类语料库,特征剪裁算法皆为卡方检验。 173 | 174 | 我们在搜狗文本分类语料库上对{朴素贝叶斯,支持向量机} * {中文分词(HanLPTokenizer),二元语法(BigramTokenizer)}的 4 种搭配组合做评测。 175 | 176 | 评测代码详见: text_classification_evaluation.py 177 | 178 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/text_classification_evaluation.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/text_classification_evaluation.py) 179 | 180 | 评测结果如下表: 181 | 182 | | 算法+分词 | P | R | F1 | 文档/秒 | 183 | | ------------------- | ----- | ---- | ----- | ------- | 184 | | 朴素贝叶斯+中文分词 | 96.16 | 96 | 96.08 | 6172 | 185 | | 朴素贝叶斯+二元语法 | 96.36 | 96.2 | 96.28 | 3378 | 186 | | SVM + 中文分词 | 97.24 | 97.2 | 97.22 | 27777 | 187 | | SVM + 二元语法 | 97.83 | 97.8 | 97.81 | 12195 | 188 | 189 | - 中文文本分类的确不需要分词,不分词直接用元语法反而能够取得更高的准确率。只不过由于二元语法数量比单词多,导致参与运算的特征更多,相应的分类速度减半。 190 | - 线性支持向量机的分类准确率更高,而且分类速度更快,推荐使用。 191 | 192 | 193 | 194 | ### 11.7 情感分析 195 | 196 | 文本**情感分析**指的是提取文本中的主观信息的一种 NLP 任务,其具体目标通常是找出文本对应的正负情感态度。情感分析可以在实体、句子、段落乃至文档上进行。本文介绍文档级别的情感分析,当然也适用于段落和句子。 197 | 198 | 对于情感分析而言,只需要准备标注了正负情感的大量文档,就能将其视作普通的文本分类任务来解决。 199 | 200 | 1. **ChnsentiCorp情感分析语料库** 201 | 202 | 该语料库由谭松波博士整理发布,包含酒店、电脑与书籍三个行业的评论与相应情感极性。文档内容为数十字的简短评论。 203 | 204 | 205 | 206 | 2. **训练情感分析模型** 207 | 208 | 实现代码详见: sentiment_analysis.py 209 | 210 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/sentiment_analysis.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch11/sentiment_analysis.py) 211 | 212 | 运行结果如下: 213 | 214 | ``` 215 | 《前台客房服务态度非常好!早餐很丰富,房价很干净。再接再厉!》 情感极性是 【正面】 216 | 《结果大失所望,灯光昏暗,空间极其狭小,床垫质量恶劣,房间还伴着一股霉味。》 情感极性是 【负面】 217 | 《可利用文本分类实现情感分析,效果不是不行》 情感极性是 【负面】 218 | ``` 219 | 220 | 值得注意的是,最后一个测试案例“可利用文本分类实现情感分析,效果不是不行”虽然不属于酒店评论,但结果依然是正确地,这说明该统计模型有一定的泛化能力,能处理一些其他行业的文本。 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /chapter/12.依存句法分析.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [12. 依存句法分析](#12-依存句法分析) 3 | - [12.1 短语结构树](#121-短语结构树) 4 | - [12.2 依存句法树](#122-依存句法树) 5 | - [12.3 依存句法分析](#123-依存句法分析) 6 | - [12.4 基于转移的依存句法分析](#124-基于转移的依存句法分析) 7 | - [12.5 依存句法分析 API](#125-依存句法分析-api) 8 | - [12.6 案例: 基于依存句法分析的意见抽取](#126-案例-基于依存句法分析的意见抽取) 9 | 10 | ## 12. 依存句法分析 11 | 12 | **语法分析**(syntactic parsing )是自然语言处理中一个重要的任务,其目标是分析句子的语法结构并将其表示为容易理解的结构(通常是树形结构)。同时,语法分析也是所有工具性NLP任务中较为高级、较为复杂的一种任务。 通过掌握语法分析的原理、实现和应用,我们将在NLP工程师之路上跨越一道分水岭。 本章将会介绍**短语结构树**和**依存句法树**两种语法形式,并且着重介绍依存句法分析的原理和实现。 13 | 14 | 15 | 16 | ### 12.1 短语结构树 17 | 18 | 语言其实具备自顶而下的层级关系,固定数量的语法结构能够生成无数句子。比如,仅仅利用下列两个语法规律,我们就能够生成所有名词短语。 19 | 20 | - 名词短语可以由名词和名词短语组成。 21 | - 名词短语还可以由名词和名词组成。 22 | 23 | 例如,“上海+浦东+机场+航站楼”,所以,汉语中大部分句子都可以通过这样的语法来生成。 24 | 25 | 在语言学中,这样的语法被称为**上下文无关文法**,它由如下组件构成: 26 | 27 | - 终结符结合 Σ,比如汉语的一个词表。 28 | - 非终结符集合 V,比如“名词短语”“动词短语”等短语结构组成的集合。V 中至少包含一个特殊的非终结符,即句子符或初始符,计作 S。 29 | - 推到规则 R,即推到非终结符的一系列规则: V -> V U Σ。 30 | 31 | 基于上下文无关文法理论,我们可以从 S 出发,逐步推导非终结符。一个非终结符至少产生一个下级符号,如此一层一层地递推下去,我们就得到了一棵语法树。但在NLP中,我们称其为短语结构树。也就是说,计算机科学中的术语“上下文无关文法”在语言学中被称作“短语结构语法”。 32 | 33 | 1. **短语结构树** 34 | 35 | 短语结构语法描述了如何自顶而下的生成一个句子,反过来,句子也可以用短语结构语法来递归的分解。层级结构其实是一种树形结构,例如这句话“上海 浦东 开发 与 法制 建设 同步”,分解成如下图的短语结构树: 36 | 37 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-14_16-27-56.png) 38 | 39 | 40 | 41 | 这样的树形结构称为**短语结构树**,相应的语法称为*短语结构语法**或上下文无关文法。至于树中的字母下面开始介绍。 42 | 43 | 44 | 45 | 2. **宾州树库和中文树库** 46 | 47 | 语言学家制定短语结构语法规范,将大量句子人工分解为树形结构,形成了一种语料库,称为**树库**( treebank )。常见的英文树库有宾州树库,相应地,中文领域有CTB。上图中叶子节点(词语)的上级节点为词性,词性是非终结符的一种,满足“词性生成词语”的推导规则。 48 | 49 | 常见的标记如下: 50 | 51 | | 标记 | 释义 | 52 | | ------ | ------------- | 53 | | IP-HLN | 单句-标题 | 54 | | NP-SBJ | 名词短语-主语 | 55 | | NP-PN | 名词短语-代词 | 56 | | NP | 名词短语 | 57 | | VP | 动词短语 | 58 | 59 | 但是由于短语结构语法比较复杂,相应句法分析器的准确率并不高,现在研究者绝大部分转向了另一种语法形式。 60 | 61 | 62 | 63 | ### 12.2 依存句法树 64 | 65 | 不同于短语结构树,依存句法树并不关注如何生成句子这种宏大的命题。依存句法树关注的是句子中词语之间的语法联系,并且将其约束为树形结构。 66 | 67 | 1. **依存句法理论** 68 | 69 | 依存语法理论认为词与词之间存在主从关系,这是一种二元不等价的关系。在句子中,如果一个词修饰另一个词,则称修饰词为**从属词**( dependent ),被修饰的词语称为**支配词**(head),两者之间的语法关系称为依存关系( dependency relation)。比如句子“大梦想”中形容词“大”与名词“梦想"之间的依存关系如图所示: 70 | 71 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-14_17-55-50.png) 72 | 73 | 图中的箭头方向由支配词指向从属词,这是可视化时的习惯。将一个句子中所有词语的依存关系以有向边的形式表示出来,就会得到一棵树,称为**依存句法树**( dependency parse tree)。比如句子“弱小的我也有大梦想”的依存句法树如图所示。 74 | 75 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-14_17-58-48.png) 76 | 77 | 78 | 79 | 现代依存语法中,语言学家 Robinson 对依存句法树提了 4 个约束性的公理。 80 | 81 | - 有且只有一个词语(ROOT,虚拟根节点,简称虚根)不依存于其他词语。 82 | - 除此之外所有单词必须依存于其他单词。 83 | - 每个单词不能依存于多个单词。 84 | - 如果单词 A 依存于 B,那么位置处于 A 和 B 之间的单词 C 只能依存于 A、B 或 AB 之间的单词。 85 | 86 | 这 4 条公理分别约束了依存句法树(图的特例)的根节点唯一性、 连通、无环和投射性( projective )。这些约束对语料库的标注以及依存句法分析器的设计奠定了基础。 87 | 88 | 89 | 90 | 2. **中文依存句法树库** 91 | 92 | 目前最有名的开源自由的依存树库当属UD ( Universal Dependencies),它以“署名-非商业性使用-相同方式共享4.0”等类似协议免费向公众授权。UD是个跨语种的语法标注项目,一共有 200 多名贡献者为 70 多种语言标注了 100 多个树库。具体到中文,存在4个不同领域的树库。本章选取其中规模最大的 UD_ Chinese GSD 作为示例。该树库的语种为繁体中文,将其转换为简体中文后,供大家下载使用。 93 | 94 | [http://file.hankcs.com/corpus/chs-gsd-ud.zip](http://file.hankcs.com/corpus/chs-gsd-ud.zip) 95 | 96 | 该树库的格式为 CoNLL-U,这是一种以制表符分隔的表格格式。CoNLL-U 文件有10列,每行都是一个单词, 空白行表示句子结束。单元中的下划线 _ 表示空白, 结合其中一句样例,解释如表所示。 97 | 98 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-14_18-37-41.png) 99 | 100 | 101 | 102 | 词性标注集合依存关系标注集请参考 UD 的官方网站: 103 | 104 | [http://niversaldependencies.org/guidelines.html](http://niversaldependencies.org/guidelines.html) 105 | 106 | 107 | 108 | 另一份著名的语料库依然是 CTB,只不过需要额外利用一些工具将短语结构树转换为依存句法树。读者可以直接下载转换后的 CTB 依存句法树库,其格式是类似于 CoNLl-U 的 CoNLL。 109 | 110 | 111 | 112 | 3. **依存句法树的可视化** 113 | 114 | 工具如下: 115 | 116 | - 南京大学汤光超开发的 Dependency Viewer。导入 .conll 扩展名的树库文件即可。 117 | - brat 标注工具。 118 | 119 | 可视化工具可以帮助我们理解句法树的结构,比较句子之间的不同。 120 | 121 | 122 | 123 | ### 12.3 依存句法分析 124 | 125 | **依存句法分析**( dependency parsing )指的是分析句子的依存语法的一种中高级 NLP任务,其输人通常是词语和词性,输出则是一棵依存句法树。 本节介绍实现依存句法分析的两种宏观方法,以及依存句法分析的评价指标。 126 | 127 | 1. **基于图的依存句法分析** 128 | 129 | 正如树是图的特例一样,依存句法树其实是**完全图**的一个子图。如果为完全图中的每条边是否属于句法树的可能性打分,然后就可以利用 Prim 之类的算法找出最大生成树( MST )作为依存句法树了。这样将整棵树的分数分解( factorize )为每条边上的分数之和,然后在图上搜索最优解的方法统称为基于图的算法。 130 | 131 | 在传统机器学习时代,基于图的依存句法分析器往往面临运行开销大的问题。这是由于传统机器学习所依赖的特征过于稀疏,训练算法需要在整个图上进行全局的结构化预测等。考虑到这些问题,另一种基于转移的路线在传统机器学习框架下显得更加实用。 132 | 133 | 134 | 135 | 2. **基于转移的依存句法分析** 136 | 137 | 我们以“人 吃 鱼”这个句子为例子,手动构建依存句法树。 138 | 139 | - 从“吃”连线到“人”建立依存关系,主谓关系。 140 | - 从“吃”连线到“鱼”建立依存关系,动宾关系。 141 | 142 | 如此,我们将一棵依存句法树的构建过程表示为两个动作。如果机器学习模型能够根据句子的某些特征准确地预测这些动作,那么计算机就能够根据这些动作拼装出正确的依存句法树了。这种拼装动作称为**转移**( transition),而这类算法统称为**基于转移的依存句法分析**。 143 | 144 | 145 | 146 | ### 12.4 基于转移的依存句法分析 147 | 148 | 1. **Arc-Eager 转移系统** 149 | 150 | 一个转移系统 S 由 4 个部件构成: S = (C,T,Cs,Ct),其中: 151 | 152 | - C 是系统状态的集合 153 | - T 是所有可执行的转移动作的集合。 154 | - Cs 是一个初始化函数 155 | - Ct 为一系列终止状态,系统进入该状态后即可停机输出最终的动作序列。 156 | 157 | 而系统状态又由 3 元祖构成: C = (σ,β,A) 其中: 158 | 159 | - σ 为一个存储单词的栈。 160 | - β 为存储单词的队列 161 | - A 为已确定的依存弧的集合。 162 | 163 | Arc-Eager 转移系统的转移动作集合详见下表: 164 | 165 | | 动作名称 | 条件 | 解释 | 166 | | -------- | --------------------- | --------------------------------------------------------- | 167 | | Shift | 队列 β 非空 | 将队首单词 i 压栈 | 168 | | LeftArc | 栈顶单词 i 没有支配词 | 将栈顶单词 i 的支配词设为队首单词 j,即 i 作为 j 的子节点 | 169 | | RightArc | 队首单词 j 没有支配词 | 将队首单词 j 的支配词设为栈顶单词 i,即 j 作为 i 的子节点 | 170 | | Reduce | 栈顶单词 i 已有支配词 | 将栈顶单词 i 出栈 | 171 | 172 | 173 | 174 | 对于上面的“人 吃 鱼”案例,Arc-Eager 的执行步骤如下: 175 | 176 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-18_17-10-42.png) 177 | 178 | 179 | 180 | 此时集合 A 中的依存弧为一颗依存句法树。 181 | 182 | 183 | 184 | 2. **训练原理** 185 | 186 | 对基于转移的依存句法分析器而言,它学习和预测的对象是一系列转移动作。然而依存句法树库是一棵树,并不是现成的转移动作序列。这时候就需要一个算法将语料库中的依存句法树转移为正确地转移动作序列。 187 | 188 | 这里可以使用感知机进行训练得到转移动作序列,原理详见: 189 | 190 | [5. 感知机分类与序列标注](https://mp.weixin.qq.com/s/HXkeXDS9krQ8z0XDL6f-9A) 191 | 192 | 训练句法分析器时,结构化感知机算法迭代式的优化线性模型,目标是使其将最高的分值赋予可抵达正确句法树的转移序列。 193 | 194 | **训练分为以下几个步骤**: 195 | 196 | - 读入一个训练样本,提取特征,创建 ArcEager 的初始状态 c。 197 | - 若 c 不是终止状态,反复进行转移序列,修正参数。 198 | - 算法终止,返回返回模型参数 w。 199 | 200 | 201 | 202 | ### 12.5 依存句法分析 API 203 | 204 | 205 | 206 | 1. **训练模型** 207 | 208 | 本节使用的语料库是 CTB8.0,**运行代码的时候会自动下载语料库**: train_parser.py 209 | 210 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch12/train_parser.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch12/train_parser.py) 211 | 212 | 训练时间比较长,结果如下: 213 | 214 | ``` 215 | 1 人 人 N NN _ 2 nsubj _ _ 216 | 2 吃 吃 V VV _ 0 ROOT _ _ 217 | 3 鱼 鱼 N NN _ 2 dobj _ _ 218 | UAS=83.3% LAS=81.0% 219 | ``` 220 | 221 | 222 | 223 | 2. **标准化评测** 224 | 225 | 给定两棵树,一棵树为标准答案(来自测试集),一棵树为预测结果,评测的目标是衡量这两棵树的差异。如果将树的节点编号,拆解为依存弧并分别存入两个集合 A ( 标准答案)和 B (预测结果),则可以利用分类任务的 F1 评价指标。 226 | 227 | 依存句法分析任务采用的评测指标为 UAS (unlabeled atachment score) 和 LAS (labeled attachment score ),分别对应忽略标签和包括标签的 F1 值。以 LAS 为例,具体计算方式如下: 228 | 229 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-18_17-12-27.gif) 230 | 231 | UAS 的计算也是同理,只不过将每条依存弧上的标签去掉后放人集合参与运算即可。相较于 LAS, UAS 仅仅衡量支配词的预测准确率,不衡量依存关系的准确率,一般分数更高。 232 | 233 | 在上面的训练模型中已经做了评测 234 | 235 | ``` 236 | UAS=83.3% LAS=81.0% 237 | ``` 238 | 239 | 这个分数说明,在测试集上有 83% 的支配词被准确预测,有 81% 的依存弧被准确预测。 240 | 241 | 242 | 243 | ### 12.6 案例: 基于依存句法分析的意见抽取 244 | 245 | 其实许多人都有一个疑问,依存句法分析究竟可以用来干什么。本节就来利用依存句法分析实现一个意见抽取的例子,提取下列商品评论中的属性和买家评价。 246 | 247 | > 电池非常棒,机身不长,长的是待机,但是屏幕分辨率不高。 248 | 249 | 为了提取“电池”“机身”“待机”和“分辨率”所对应的意见,朴素的处理方式是在分司和词性标注之后编写正则表达式,提取名词后面的形容词。然而正则表达式无法处理“长的是待机”这样句式灵活的例子。 250 | 251 | 这时就可以对这句话进行依存句法分析,分析代码如下: 252 | 253 | ```python 254 | from pyhanlp import * 255 | 256 | CoNLLSentence = JClass('com.hankcs.hanlp.corpus.dependency.CoNll.CoNLLSentence') 257 | CoNLLWord = JClass('com.hankcs.hanlp.corpus.dependency.CoNll.CoNLLWord') 258 | IDependencyParser = JClass('com.hankcs.hanlp.dependency.IDependencyParser') 259 | KBeamArcEagerDependencyParser = JClass('com.hankcs.hanlp.dependency.perceptron.parser.KBeamArcEagerDependencyParser') 260 | 261 | parser = KBeamArcEagerDependencyParser() 262 | tree = parser.parse("电池非常棒,机身不长,长的是待机,但是屏幕分辨率不高。") 263 | print(tree) 264 | ``` 265 | 266 | 运行结果如下: 267 | 268 | ``` 269 | 1 电池 电池 N NN _ 3 nsubj _ _ 270 | 2 非常 非常 A AD _ 3 advmod _ _ 271 | 3 棒 棒 V VA _ 0 ROOT _ _ 272 | 4 , , P PU _ 3 punct _ _ 273 | 5 机身 机身 N NN _ 7 nsubj _ _ 274 | 6 不 不 A AD _ 7 neg _ _ 275 | 7 长 长 V VA _ 3 conj _ _ 276 | 8 , , P PU _ 7 punct _ _ 277 | 9 长 长 V VA _ 11 top _ _ 278 | 10 的 的 D DEC _ 9 cpm _ _ 279 | 11 是 是 V VC _ 7 conj _ _ 280 | 12 待机 待机 N NN _ 11 attr _ _ 281 | 13 , , P PU _ 3 punct _ _ 282 | 14 但是 但是 A AD _ 18 advmod _ _ 283 | 15 屏幕 屏幕 N NN _ 16 nn _ _ 284 | 16 分辨率 分辨率 N NN _ 18 nsubj _ _ 285 | 17 不 不 A AD _ 18 neg _ _ 286 | 18 高 高 V VA _ 3 conj _ _ 287 | 19 。 。 P PU _ 3 punct _ _ 288 | ``` 289 | 290 | 291 | 292 | 进行可视化后: 293 | 294 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-18_16-43-14.png) 295 | 296 | 297 | 298 | 仔细观察,不难发现“电池”与“棒”、“机身”与“长”、“分辨率”与“高”之间的依存关系都是 nsubj (名词性主语)。 299 | 300 | 1. 利用这一规律, 不难写出第一版遍历算法, 也就是用个for 循环去遍历树中的每个节点。对于算法遍历树中的每一个词语, 如果其词性为名词且作为某个形容词的名词性主语,则认为该名词是属性,而形容词是意见。运行代码如下: 301 | 302 | ```python 303 | def extactOpinion1(tree): 304 | for word in tree.iterator(): 305 | if word.POSTAG == "NN" and word.DEPREL == "nsubj": 306 | print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA)) 307 | 308 | print("第一版") 309 | extactOpinion1(tree) 310 | ``` 311 | 312 | 结果如下: 313 | 314 | ``` 315 | 第一版 316 | 电池 = 棒 317 | 机身 = 长 318 | 分辨率 = 高 319 | ``` 320 | 321 | 322 | 323 | 2. 虽然的确提取出了一些意见,然而后两个都是错误的。这一版算法存在的问题之一是没有考虑到“机身不长””“分辨率不高"等否定修饰关系。否定修饰关系在依存句法中的标记为 neg,于是我们只需检查形容词是否存在否定修饰的支配词即可。于是得出第二版算法: 324 | 325 | ```python 326 | def extactOpinion2(tree): 327 | for word in tree.iterator(): 328 | if word.POSTAG == "NN" and word.DEPREL == "nsubj": 329 | if tree.findChildren(word.HEAD, "neg").isEmpty(): 330 | print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA)) 331 | else: 332 | print("%s = 不%s" % (word.LEMMA, word.HEAD.LEMMA)) 333 | 334 | print("第二版") 335 | extactOpinion2(tree) 336 | ``` 337 | 338 | 结果如下: 339 | 340 | ``` 341 | 第二版 342 | 电池 = 棒 343 | 机身 = 不长 344 | 分辨率 = 不高 345 | ``` 346 | 347 | 348 | 349 | 3. 接下来思考如何提取“待机”的意见,“待机”与“长”之间的公共父节点为“是”,于是我们得到第三版算法如下: 350 | 351 | ```python 352 | def extactOpinion3(tree): 353 | for word in tree.iterator(): 354 | if word.POSTAG == "NN": 355 | 356 | # 检测名词词语的依存弧是否是“属性关系”, 357 | # 如果是,则寻找支配词的子节点中的主题词 358 | # 以该主题词作为名词的意见。 359 | if word.DEPREL == "nsubj": # ①属性 360 | 361 | if tree.findChildren(word.HEAD, "neg").isEmpty(): 362 | print("%s = %s" % (word.LEMMA, word.HEAD.LEMMA)) 363 | else: 364 | print("%s = 不%s" % (word.LEMMA, word.HEAD.LEMMA)) 365 | elif word.DEPREL == "attr": 366 | top = tree.findChildren(word.HEAD, "top") # ②主题 367 | 368 | if not top.isEmpty(): 369 | print("%s = %s" % (word.LEMMA, top.get(0).LEMMA)) 370 | 371 | print("第三版") 372 | extactOpinion3(tree) 373 | ``` 374 | 375 | 结果如下: 376 | 377 | ``` 378 | 第三版 379 | 电池 = 棒 380 | 机身 = 不长 381 | 待机 = 长 382 | 分辨率 = 不高 383 | ``` 384 | 385 | 386 | 387 | 至此,4 个属性被完整正确地提取出来了,读者可以尝试搜集更多的句子,通过分析句法结构总结更多的提取规则。 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | -------------------------------------------------------------------------------- /chapter/2.词典分词.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | [2. 词典分词](#2-词典分词) 3 | - [2.1 什么是词](#21-什么是词) 4 | - [2.2 词典](#22-词典) 5 | - [2.3 切分算法](#23-切分算法) 6 | - [2.4 字典树](#24-字典树) 7 | - [2.5 基于字典树的其它算法](#25-基于字典树的其它算法) 8 | - [2.6 HanLP的词典分词实现](#26-hanlp的词典分词实现) 9 | 10 | ## 2. 词典分词 11 | 12 | - **中文分词**:指的是将一段文本拆分为一系列单词的过程,这些单词顺序拼接后等于原文本。 13 | - 中文分词算法大致分为**基于词典规则**与**基于机器学习**这两大派。 14 | 15 | 16 | 17 | ### 2.1 什么是词 18 | 19 | - 在基于词典的中文分词中,词的定义要现实得多:**词典中的字符串就是词**。 20 | 21 | - 词的性质--**齐夫定律**:一个单词的词频与它的词频排名成反比。 22 | 23 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-4_12-44-46.png) 24 | 25 | 26 | 27 | ### 2.2 词典 28 | 29 | 互联网词库(SogouW, 15万个词条)、清华大学开放中文词库(THUOCL)、HanLP词库(千万级词条) 30 | 31 | 这里以HanLP附带的迷你核心词典为例(本项目路径):[data/dictionnary/CoreNatureDictionary.mini.txt](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/data/dictionnary/CoreNatureDictionary.mini.txt) 32 | 33 | ``` 34 | 上升 v 98 vn 18 35 | 上升期 n 1 36 | 上升股 n 1 37 | 上午 t 147 38 | 上半叶 t 3 39 | 上半场 n 2 40 | 上半夜 t 1 41 | ``` 42 | 43 | HanLP中的词典格式是一种以空格分隔的表格形式,第一列是单词本身,之后每两列分别表示词性与相应的词频。 44 | 45 | 46 | 47 | ### 2.3 切分算法 48 | 49 | 首先,加载词典: 50 | 51 | ```python 52 | def load_dictionary(): 53 | dic = set() 54 | 55 | # 按行读取字典文件,每行第一个空格之前的字符串提取出来。 56 | for line in open("CoreNatureDictionary.mini.txt","r"): 57 | dic.add(line[0:line.find(' ')]) 58 | 59 | return dic 60 | ``` 61 | 62 | 1. **完全切分** 63 | 64 | 指的是,找出一段文本中的所有单词。 65 | 66 | ```python 67 | def fully_segment(text, dic): 68 | word_list = [] 69 | for i in range(len(text)): # i 从 0 到text的最后一个字的下标遍历 70 | for j in range(i + 1, len(text) + 1): # j 遍历[i + 1, len(text)]区间 71 | word = text[i:j] # 取出连续区间[i, j]对应的字符串 72 | if word in dic: # 如果在词典中,则认为是一个词 73 | word_list.append(word) 74 | return word_list 75 | 76 | dic = load_dictionary() 77 | print(fully_segment('就读北京大学', dic)) 78 | ``` 79 | 80 | 输出: 81 | 82 | ``` 83 | ['就', '就读', '读', '北', '北京', '北京大学', '京', '大', '大学', '学'] 84 | ``` 85 | 86 | 输出了所有可能的单词。由于词库中含有单字,所以结果中也出现了一些单字。 87 | 88 | 2. **正向最长匹配** 89 | 90 | 上面的输出并不是中文分词,我们更需要那种有意义的词语序列,而不是所有出现在词典中的单词所构成的链表。比如,我们希望“北京大学”成为一整个词,而不是“北京 + 大学”之类的碎片。具体来说,就是在以某个下标为起点递增查词的过程中,优先输出更长的单词,这种规则被称为**最长匹配算法**。从前往后匹配则称为**正向最长匹配**,反之则称为**逆向最长匹配**。 91 | 92 | ```python 93 | def forward_segment(text, dic): 94 | word_list = [] 95 | i = 0 96 | while i < len(text): 97 | longest_word = text[i] # 当前扫描位置的单字 98 | for j in range(i + 1, len(text) + 1): # 所有可能的结尾 99 | word = text[i:j] # 从当前位置到结尾的连续字符串 100 | if word in dic: # 在词典中 101 | if len(word) > len(longest_word): # 并且更长 102 | longest_word = word # 则更优先输出 103 | word_list.append(longest_word) # 输出最长词 104 | i += len(longest_word) # 正向扫描 105 | return word_list 106 | 107 | dic = load_dictionary() 108 | print(forward_segment('就读北京大学', dic)) 109 | print(forward_segment('研究生命起源', dic)) 110 | ``` 111 | 112 | 输出: 113 | 114 | ``` 115 | ['就读', '北京大学'] 116 | ['研究生', '命', '起源'] 117 | ``` 118 | 119 | 第二句话就会产生误差了,我们是需要把“研究”提取出来,结果按照正向最长匹配算法就提取出了“研究生”,所以人们就想出了逆向最长匹配。 120 | 121 | 3. **逆向最长匹配** 122 | 123 | ```python 124 | def backward_segment(text, dic): 125 | word_list = [] 126 | i = len(text) - 1 127 | while i >= 0: # 扫描位置作为终点 128 | longest_word = text[i] # 扫描位置的单字 129 | for j in range(0, i): # 遍历[0, i]区间作为待查询词语的起点 130 | word = text[j: i + 1] # 取出[j, i]区间作为待查询单词 131 | if word in dic: 132 | if len(word) > len(longest_word): # 越长优先级越高 133 | longest_word = word 134 | break 135 | word_list.insert(0, longest_word) # 逆向扫描,所以越先查出的单词在位置上越靠后 136 | i -= len(longest_word) 137 | return word_list 138 | 139 | dic = load_dictionary() 140 | print(backward_segment('研究生命起源', dic)) 141 | print(backward_segment('项目的研究', dic)) 142 | ``` 143 | 144 | 输出: 145 | 146 | ``` 147 | ['研究', '生命', '起源'] 148 | ['项', '目的', '研究'] 149 | ``` 150 | 151 | 第一句正确了,但下一句又出错了,可谓拆东墙补西墙。另一些人提出综合两种规则,期待它们取长补短,称为双向最长匹配。 152 | 153 | 4. **双向最长匹配** 154 | 155 | 这是一种融合两种匹配方法的复杂规则集,流程如下: 156 | 157 | - 同时执行正向和逆向最长匹配,若两者的词数不同,则返回词数更少的那一个。 158 | - 否则,返回两者中单字更少的那一个。当单字数也相同时,优先返回逆向最长匹配的结果。 159 | 160 | ```python 161 | def count_single_char(word_list: list): # 统计单字成词的个数 162 | return sum(1 for word in word_list if len(word) == 1) 163 | 164 | 165 | def bidirectional_segment(text, dic): 166 | f = forward_segment(text, dic) 167 | b = backward_segment(text, dic) 168 | if len(f) < len(b): # 词数更少优先级更高 169 | return f 170 | elif len(f) > len(b): 171 | return b 172 | else: 173 | if count_single_char(f) < count_single_char(b): # 单字更少优先级更高 174 | return f 175 | else: 176 | return b # 都相等时逆向匹配优先级更高 177 | 178 | 179 | print(bidirectional_segment('研究生命起源', dic)) 180 | print(bidirectional_segment('项目的研究', dic)) 181 | ``` 182 | 183 | 输出: 184 | 185 | ``` 186 | ['研究', '生命', '起源'] 187 | ['项', '目的', '研究'] 188 | ``` 189 | 190 | 191 | 192 | 通过以上几种切分算法,我们可以做一个对比: 193 | 194 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-4_14-15-53.png) 195 | 196 | 上图显示,双向最长匹配的确在2、3、5这3种情况下选择出了最好的结果,但在4号句子上选择了错误的结果,使得最终正确率 3/6 反而小于逆向最长匹配的 4/6 , 由此,规则系统的脆弱可见一斑。规则集的维护有时是拆东墙补西墙,有时是帮倒忙。 197 | 198 | 199 | 200 | ### 2.4 字典树 201 | 202 | 匹配算法的瓶颈之一在于如何判断集合(词典)中是否含有字符串。如果用有序集合TreeMap)的话,复杂度是o(logn) ( n是词典大小);如果用散列表( Java的HashMap. Python的dict )的话,账面上的时间复杂度虽然下降了,但内存复杂度却上去了。有没有速度又快、内存又省的数据结构呢?这就是**字典树**。 203 | 204 | 1. **什么是字典树** 205 | 206 | 字符串集合常用宇典树(trie树、前缀树)存储,这是一种字符串上的树形数据结构。字典树中每条边都对应一个字, 从根节点往下的路径构成一个个字符串。字典树并不直接在节点上存储字符串, 而是将词语视作根节点到某节点之间的一条路径,并在终点节点(蓝色) 上做个标记“该节点对应词语的结尾”。字符串就是一 条路径,要查询一个单词,只需顺着这条路径从根节点往下走。如果能走到特殊标记的节点,则说明该字符串在集合中,否则说明不存在。一个典型的字典树如下图所示所示。 207 | 208 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-4_14-46-52.png) 209 | 210 | 其中,蓝色标记着该节点是一个词的结尾,数字是人为的编号。按照路径我们可以得到如下表所示: 211 | 212 | | 词语 | 路径 | 213 | | -------- | --------- | 214 | | 入门 | 0-1-2 | 215 | | 自然 | 0-3-4 | 216 | | 自然人 | 0-3-4-5 | 217 | | 自然语言 | 0-3-4-6-7 | 218 | | 自语 | 0-3-8 | 219 | 220 | 当词典大小为 n 时,虽然最坏情况下字典树的复杂度依然是O(logn) (假设子节点用对数复杂度的数据结构存储,所有词语都是单字),但它的实际速度比二分查找快。这是因为随着路径的深入,前缀匹配是递进的过程,算法不必比较字符串的前缀。 221 | 222 | 2. **字典树的实现** 223 | 224 | 由上图可知,每个节点都应该至少知道自己的子节点与对应的边,以及自己是否对应一个词。如果要实现映射而不是集合的话,还需要知道自己对应的值。我们约定用值为None表示节点不对应词语,虽然这样就不能插人值为None的键了,但实现起来更简洁。那么字典树的实现参见项目路径(与书上略有不同,我写的比较简洁):**code/ch02/trie.py** 225 | 226 | 通过**debug运行 trie.py 代码**,可以观察到 trie 类的字典树结构: 227 | 228 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-4_16-49-15.png) 229 | 230 | 231 | 232 | ### 2.5 基于字典树的其它算法 233 | 234 | 字典树的数据结构在以上的切分算法中已经很快了,但厉害的是作者通过自己的努力改进了基于字典树的算法,把分词速度推向了千万字每秒的级别,这里不一一详细介绍,详情见书,主要按照以下递进关系优化: 235 | 236 | - 首字散列其余二分的字典树 237 | - 双数组字典树 238 | - AC自动机(多模式匹配) 239 | - 基于双数组字典树的AC自动机 240 | 241 | 242 | 243 | ### 2.6 HanLP的词典分词实现 244 | 245 | 1. **DoubleArrayTrieSegment** 246 | 247 | DoubleArrayTrieSegment分词器是对DAT最长匹配的封装,默认加载hanlp.properties中CoreDictionaryPath制定的词典。 248 | 249 | ```python 250 | from pyhanlp import * 251 | 252 | # 不显示词性 253 | HanLP.Config.ShowTermNature = False 254 | 255 | # 可传入自定义字典 [dir1, dir2] 256 | segment = DoubleArrayTrieSegment() 257 | # 激活数字和英文识别 258 | segment.enablePartOfSpeechTagging(True) 259 | 260 | print(segment.seg("江西鄱阳湖干枯,中国最大淡水湖变成大草原")) 261 | print(segment.seg("上海市虹口区大连西路550号SISU")) 262 | ``` 263 | 264 | 输出: 265 | 266 | ``` 267 | [江西, 鄱阳湖, 干枯, ,, 中国, 最大, 淡水湖, 变成, 大草原] 268 | [上海市, 虹口区, 大连, 西路, 550, 号, SISU] 269 | ``` 270 | 271 | 2. **去掉停用词** 272 | 273 | 停用词词典文件:[data/dictionnary/stopwords.txt](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/data/dictionnary/stopwords.txt) 274 | 275 | 该词典收录了常见的中英文无意义词汇(不含敏感词),每行一个词。 276 | 277 | ```python 278 | def load_from_file(path): 279 | """ 280 | 从词典文件加载DoubleArrayTrie 281 | :param path: 词典路径 282 | :return: 双数组trie树 283 | """ 284 | map = JClass('java.util.TreeMap')() # 创建TreeMap实例 285 | with open(path) as src: 286 | for word in src: 287 | word = word.strip() # 去掉Python读入的\n 288 | map[word] = word 289 | return JClass('com.hankcs.hanlp.collection.trie.DoubleArrayTrie')(map) 290 | 291 | 292 | ## 去掉停用词 293 | def remove_stopwords_termlist(termlist, trie): 294 | return [term.word for term in termlist if not trie.containsKey(term.word)] 295 | 296 | 297 | trie = load_from_file('stopwords.txt') 298 | termlist = segment.seg("江西鄱阳湖干枯了,中国最大的淡水湖变成了大草原") 299 | print('去掉停用词前:', termlist) 300 | 301 | print('去掉停用词后:', remove_stopwords_termlist(termlist, trie)) 302 | ``` 303 | 304 | 输出: 305 | 306 | ``` 307 | 去掉停用词前: [江西, 鄱阳湖, 干枯, 了, ,, 中国, 最大, 的, 淡水湖, 变成, 了, 大草原] 308 | 去掉停用词后: ['江西', '鄱阳湖', '干枯', '中国', '最大', '淡水湖', '变成', '大草原'] 309 | ``` 310 | 311 | 312 | -------------------------------------------------------------------------------- /chapter/3.二元语法与中文分词.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | [3. 二元语法与中文分词](#3-二元语法与中文分词) 3 | - [3.1 语言模型](#31-语言模型) 4 | - [3.2 中文分词语料库](#32-中文分词语料库) 5 | - [3.3 训练与预测](#33-训练与预测) 6 | - [3.4 HanLP分词与用户词典的集成](#34-hanlp分词与用户词典的集成) 7 | - [3.5 二元语法与词典分词比较](#35-二元语法与词典分词比较) 8 | 9 | ## 3. 二元语法与中文分词 10 | 11 | 上一章中我们实现了块儿不准的词典分词,词典分词无法消歧。给定两种分词结果“商品 和服 务”以及“商品 和 服务”,词典分词不知道哪种更加合理。 12 | 13 | 我们人类确知道第二种更加合理,只因为我们从小到大接触的都是第二种分词,出现的次数多,所以我们判定第二种是正确地选择。这就是利用了**统计自然语言处理**。统计自然语言处理的核心话题之一,就是如何利用统计手法对语言建模,这一章讲的就是二元语法的统计语言模型。 14 | 15 | 16 | 17 | ### 3.1 语言模型 18 | 19 | 1. **什么是语言模型** 20 | 21 | **模型**指的是对事物的数学抽象,那么**语言模型**指的就是对语言现象的数学抽象。准确的讲,给定一个句子 w,语言模型就是计算句子的出现概率 p(w) 的模型,而统计的对象就是人工标注而成的语料库。 22 | 23 | 假设构建如下的小型语料库: 24 | 25 | ``` 26 | 商品 和 服务 27 | 商品 和服 物美价廉 28 | 服务 和 货币 29 | ``` 30 | 31 | 每个句子出现的概率都是 1/3,这就是语言模型。然而 p(w) 的计算非常难:句子数量无穷无尽,无法枚举。即便是大型语料库,也只能“枚举”有限的数百万个句子。实际遇到的句子大部分都在语料库之外,意味着它们的概率都被当作0,这种现象被称为**数据稀疏**。 32 | 33 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_18-22-57.png) 34 | 35 | 然而随着句子长度的增大,语言模型会遇到如下两个问题。 36 | 37 | - **数据稀疏**,指的是长度越大的句子越难出现,可能统计不到频次,导致 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_17-57-48.png),比如 p(商品 和 货币)=0。 38 | - **计算代价大**,k 越大,需要存储的 p 就越多,即便用上字典树索引,依然代价不菲。 39 | 40 | 41 | 42 | 2. **马尔可夫链与二元语法** 43 | 44 | 为了解决以上两个问题,需要使用**马尔可夫假设**来简化语言模型,给定时间线上有一串事件顺序发生,假设每个事件的发生概率只取决于前一个事件,那么这串事件构成的因果链被称作**马尔可夫链**。 45 | 46 | 在语言模型中,第 t 个事件指的是 Wt 作为第 t 个单词出现。也就是说,每个单词出现的概率只取决于前一个单词: 47 | 48 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_17-59-12.png) 49 | 50 | 51 | 52 | 基于此假设,式子一下子变短了不少,此时的语言模型称为**二元语法模型**: 53 | 54 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_10-57-30.png) 55 | 56 | 57 | 58 | 由于语料库中二元连续的重复程度要高于整个句子的重要程度,所以缓解了数据稀疏的问题,另外二元连续的总数量远远小于句子的数量,存储和查询也得到了解决。 59 | 60 | 61 | 62 | 3. **n元语法** 63 | 64 | 利用类似的思路,可以得到**n元语法**的定义:每个单词的概率仅取决于该单词之前的 n 个单词: 65 | 66 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_18-1-47.png) 67 | 68 | 69 | 70 | 特别地,当 n=1 时的 n 元语法称为**一元语法** ( unigram);当 n=3 时的 n 元语法称为**三元语法**(tigam); n≥4时数据稀疏和计算代价又变得显著起来,实际工程中几乎不使用。 71 | 72 | 73 | 74 | 4. **数据稀疏与平滑策略** 75 | 76 | 对于 n 元语法模型,n 越大,数据稀疏问题越严峻。比如上述语料库中“商品 货币”的频次就为0。一个自然而然的解决方案就是利用低阶 n 元语法平滑高阶 n 元语法,所谓**平滑**,就是字面上的意思:使 n 元语法频次的折线平滑为曲线。最简单的一种是线性插值法: 77 | 78 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_11-17-22.png) 79 | 80 | 81 | 82 | 其中,λ∈(0,1)为常数平滑因子。通俗理解,线性插值就是劫富济贫的税赋制度,其中的 λ 就是个人所得税的税率。![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_18-11-13.png) 是税前所得,P(Wt) 是社会福利。 通过缴税,高收人(高概率)二元语法的一部分收人 (概率)被移动到社会福利中。而零收入(语料库统计不到频次)的一元语法能够从社会福利中取得点低保金, 不至于饿死。低保金的额度与二元语法挣钱潜力成正比:二元语法中第二个词词频越高,它未来被统计到的概率也应该越高,因此它应该多拿一点。 83 | 84 | 类似地,一元语法也可以通过线性插值来平滑: 85 | 86 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_11-21-45.png) 87 | 88 | 89 | 90 | 其中,N 是语料库总词频。 91 | 92 | 93 | 94 | ### 3.2 中文分词语料库 95 | 96 | 语言模型只是一个函数的骨架,函数的参数需要在语料库上统计才能得到。为了满足实际工程需要,一个质量高、分量足的语料库必不可少。以下是常用的语料库: 97 | 98 | - 《人民日报》语料库 PKU 99 | - 微软亚洲研究院语料库 MSR 100 | - 香港城市大学 CITYU(繁体) 101 | - 台湾中央研究院 AS(繁体) 102 | 103 | | 语料库 | 字符数 | 词语种数 | 总词频 | 平均词长 | 104 | | ------ | ------ | -------- | ------ | -------- | 105 | | PKU | 183万 | 6万 | 111万 | 1.6 | 106 | | MSR | 405万 | 9万 | 237万 | 1.7 | 107 | | AS | 837万 | 14万 | 545万 | 1.5 | 108 | | CITYU | 240万 | 7万 | 146万 | 1.7 | 109 | 110 | 一般采用MSR作为分词语料的首选,有以下原因: 111 | 112 | - 标注一致性上MSR要优于PKU。 113 | - 切分颗粒度上MSR要优于PKU,MSR的机构名称不予切分,而PKU拆开。 114 | - MSR中姓名作为一个整体,更符合习惯。 115 | - MSR量级是PKU的两倍。 116 | 117 | 118 | 119 | ### 3.3 训练与预测 120 | 121 | 训练指的是统计二元语法频次以及一元语法频次,有了频次,通过极大似然估计以及平滑策略,我们就可以估计任意句子的概率分布,即得到了语言模型。这里以二元语法为例: 122 | 123 | 这里我们选用上面自己构造的小型语料库:[data/dictionnary/my_cws_corpus.txt](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/data/dictionnary/my_cws_corpus.txt) 124 | 125 | 代码请见:[code/ch03/ngram_segment.py](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/code/ch03/my_cws_corpus.txt) 126 | 127 | 步骤如下: 128 | 129 | - 加载语料库文件并进行词频统计。 130 | 131 | - 对词频文件生成**词网** 132 | 133 | 词网指的是句子中所有一元语法构成的网状结构,是HanLP工程上的概念。比如“商品和服务”这个句子,我们将句子中所有单词找出来,起始位置(offset)相同的单词写作一行: 134 | 135 | ``` 136 | 0:[ ] 137 | 1:[商品] 138 | 2:[] 139 | 3:[和,和服] 140 | 4:[服务] 141 | 5:[务] 142 | 6:[ ] 143 | ``` 144 | 145 | 其中收尾(行0和行6)分别对应起始和末尾。词网必须保证从起点出发的所有路径都会连通到钟点房。 146 | 147 | 词网有一个极佳的**性质**:那就是第 i 行的词语 w 与第 i+len(w) 行的所有词语相连都能构成二元语法。 148 | 149 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-5_15-13-17.png) 150 | 151 | - 词图上的**维特比算法** 152 | 153 | 上述词图每条边以二元语法的概率作为距离,那么中文分词任务转换为有向无环图上的最长路径问题。再通过将浮点数乘法转化为负对数之间的加法,相应的最长路径转化为负对数的最短路径。使用维特比算法求解。 154 | 155 | 这里仅作一下简述,详细过程参考书本第三章。 156 | 157 | 该模型代码输入是句子“货币和服务”,得到结果如下: 158 | 159 | ``` 160 | [' ', '货币', '和', '服务', ' '] 161 | ``` 162 | 163 | 结果正确,可见我们的二元语法模型具备一定的泛化能力。 164 | 165 | 166 | 167 | ### 3.4 HanLP分词与用户词典的集成 168 | 169 | 词典往往廉价易得,资源丰富,利用统计模型的消歧能力,辅以用户词典处理新词,是提高分词器准确率的有效方式。HanLP支持 2 档用户词典优先级: 170 | 171 | - **低优先级**:分词器首先在不考虑用户词典的情况下由统计模型预测分词结果,最后将该结果按照用户词典合并。默认低优先级。 172 | - **高优先级**:分词器优先考虑用户词典,但具体实现由分词器子类自行决定。 173 | 174 | **HanLP分词器简洁版**: 175 | 176 | ```python 177 | from pyhanlp import * 178 | 179 | ViterbiSegment = SafeJClass('com.hankcs.hanlp.seg.Viterbi.ViterbiSegment') 180 | 181 | segment = ViterbiSegment() 182 | sentence = "社会摇摆简称社会摇" 183 | segment.enableCustomDictionary(False) 184 | print("不挂载词典:", segment.seg(sentence)) 185 | CustomDictionary.insert("社会摇", "nz 100") 186 | segment.enableCustomDictionary(True) 187 | print("低优先级词典:", segment.seg(sentence)) 188 | segment.enableCustomDictionaryForcing(True) 189 | print("高优先级词典:", segment.seg(sentence)) 190 | ``` 191 | 192 | 输出: 193 | 194 | ``` 195 | 不挂载词典: [社会/n, 摇摆/v, 简称/v, 社会/n, 摇/v] 196 | 低优先级词典: [社会/n, 摇摆/v, 简称/v, 社会摇/nz] 197 | 高优先级词典: [社会摇/nz, 摆/v, 简称/v, 社会摇/nz] 198 | ``` 199 | 200 | 可见,用户词典的高优先级未必是件好事,HanLP中的用户词典默认低优先级,做项目时请读者在理解上述说明的情况下根据实际需求自行开启高优先级。 201 | 202 | 203 | 204 | ### 3.5 二元语法与词典分词比较 205 | 206 | 按照NLP任务的一般流程,我们已经完成了语料标注和模型训练,现在来比较一下二元语法和词典分词的评测: 207 | 208 | | 算法 | P | R | F1 | R(oov) | R(IV) | 209 | | -------- | ----- | ----- | ----- | ------ | ----- | 210 | | 最长匹配 | 89.41 | 94.64 | 91.95 | 2.58 | 97.14 | 211 | | 二元语法 | 92.38 | 96.70 | 94.49 | 2.58 | 99.26 | 212 | 213 | 相较于词典分词,二元语法在精确度、召回率及IV召回率上全面胜出,最终F1值提高了 2.5%,成绩的提高主要受惠于消歧能力的提高。然而 OOV 召回依然是 n 元语法模型的硬伤,我们需要更强大的统计模型。 214 | -------------------------------------------------------------------------------- /chapter/4.隐马尔可夫模型与序列标注.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | [4. 隐马尔可夫模型与序列标注](#4-隐马尔可夫模型与序列标注) 3 | - [4.1 序列标注问题](#41-序列标注问题) 4 | - [4.2 隐马尔可夫模型](#42-隐马尔可夫模型) 5 | - [4.3 隐马尔可夫模型的训练](#43-隐马尔可夫模型的训练) 6 | - [4.4 隐马尔可夫模型的预测](#45-隐马尔可夫模型的预测) 7 | - [4.5 隐马尔可夫模型应用于中文分词](#46-隐马尔可夫模型应用于中文分词) 8 | - [4.6 性能评测](#47-性能评测) 9 | - [4.7 总结](#48-总结) 10 | 11 | ## 4. 隐马尔可夫模型与序列标注 12 | 13 | 第3章的n元语法模型从词语接续的流畅度出发,为全切分词网中的二元接续打分,进而利用维特比算法求解似然概率最大的路径。这种词语级别的模型无法应对 **OOV(Out of Vocabulary,即未登录词) 问题**: 00V在最初的全切分阶段就已经不可能进人词网了,更何谈召回。 14 | 15 | 例如下面一句: 16 | 17 | > 头上戴着**束发嵌宝紫金冠**,齐眉勒着**二龙抢珠金抹额** 18 | 19 | 加粗的就是相对陌生的新词,之前的分词算法识别不出,但人类确可以,是因为读者能够识别“戴着”,这些构词法能让人类拥有动态组词的能力。我们需要更细粒度的模型,比词语更细粒度的就是字符。 20 | 21 | 具体说来,只要将每个汉字组词时所处的位置(首尾等)作为标签,则中文分词就转化为给定汉字序列找出标签序列的问题。一般而言,由字构词是**序列标注模型**的一种应用。 在所有“序列标注”模型中,隐马尔可夫模型是最基础的一种。 22 | 23 | 24 | 25 | ### 4.1 序列标注问题 26 | 27 | **序列标注**指的是给定一个序列 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-41-12.gif),找出序列中每个元素对应标签 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-43-48.gif) 的问题。其中,y 所有可能的取值集合称为**标注集**。比如,输入一个自然数序列,输出它们的奇偶性。 28 | 29 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_10-36-13.png) 30 | 31 | 求解序列标注问题的模型一般称为**序列标注器**,通常由模型从一个标注数据集 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-45-19.gif) 中学习相关知识后再进行预测。再NLP问题中,x 通常是字符或词语,而 y 则是待预测的组词角色或词性等标签。中文分词、词性标注以及命名实体识别,都可以转化为序列标注问题。 32 | 33 | 1. **序列标注与中文分词** 34 | 35 | 考虑一个字符序列(字符串) x,想象切词器真的是在拿刀切割字符串,如此,中文分词转化为标注集{切,过}的序列标注问题。 36 | 37 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_10-55-28.png) 38 | 39 | 40 | 41 | **分词标注集**并非只有一种,为了捕捉汉字分别作为词语收尾(**B**egin、**E**nd)、词中(**M**iddle)以及单字成词(**S**ingle)时不同的成词概率,人们提出了{B,M,E,S}这种最流行的标注集。 42 | 43 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_11-4-22.png) 44 | 45 | 46 | 47 | 2. **序列标注与词性标注** 48 | 49 | 词性标注任务是一个天然的序列标注问题:x 是单词序列,y 是相应的词性序列。需要综合考虑前后的单词与词性才能决定当前单词的词性。![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_11-11-0.png) 50 | 51 | 52 | 53 | 3. **序列标注与命名实体识别** 54 | 55 | 所谓**命名实体**,指的是现实存在的实体,比如人名、地名和机构名,命名实体是 OOV 的主要组成部分。 56 | 57 | 考虑到字符级别中文分词和词语级别命名实体识别有着类似的特点,都是组合短单位形成长单位的问题。所以命名实体识别可以复用BMES标注集,并沿用中文分词的逻辑,只不过标注的对象由字符变为单词而已。唯一不同的是,命名实体识别还需要确定实体所属的类别。这个额外的要求依然是个标注问题,可以通过将命名实体类别附着到BMES标签来达到目的。比如,构成地名的单词标注为“B/M/E/S-地名”,以此类推。对于那些不构成命名实体的单词,则统-标注为O ( Outside), 即复合词之外。 58 | 59 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_11-23-10.png) 60 | 61 | 62 | 63 | 总之,序列标注问题是NLP中最常见的问题之一。许多应用任务都可以变换思路,转化为序列标注来解决。所以一个准确的序列标注模型非常重要,直接关系到NLP系统的准确率。机器学习领域为NLP提供了许多标注模型,本着循序渐进的原则,本章介绍其中最基础的一个隐马尔可夫模型。 64 | 65 | 66 | 67 | ### 4.2 隐马尔可夫模型 68 | 69 | **隐马尔可夫模型**( Hidden Markov Model, HMM)是描述两个时序序列联合分布 p(x,y) 的概率模型: x 序列外界可见(外界指的是观测者),称为**观测序列**(obsevation sequence); y 序列外界不可见,称为**状态序列**(state sequence)。比如观测 x 为单词,状态 y 为词性,我们需要根据单词序列去猜测它们的词性。隐马尔可夫模型之所以称为“隐”,是因为从外界来看,状 70 | 态序列(例如词性)隐藏不可见,是待求的因变量。从这个角度来讲,人们也称状态为隐状态(hidden state),而称观测为显状态( visible state)。隐马尔可夫模型之所以称为“马尔可夫模型”,”是因为它满足**马尔可夫假设**。 71 | 72 | 1. **从马尔可夫假设到隐马尔可夫模型** 73 | 74 | **马尔可夫假设**:每个事件的发生概率只取决于前一个事件。 75 | 76 | **马尔可夫链**:将满足马尔可夫假设的连续多个事件串联起来,就构成了马尔可夫链。 77 | 78 | 如果把事件具象为单词,那么马尔可夫模型就具象为二元语法模型。 79 | 80 | 81 | 82 | **隐马尔可夫模型**:它的马尔可夫假设作用于状态序列, 83 | 84 | 假设 ① 当前状态 Yt 仅仅依赖于前一个状态 Yt-1, 连续多个状态构成隐**马尔可夫链 y**。有了隐马尔可夫链,如何与观测序列 x 建立联系呢? 85 | 86 | 隐马尔可夫模型做了第二个假设: ② 任意时刻的观测 x 只依赖于该时刻的状态 Yt,与其他时刻的状态或观测独立无关。如果用箭头表示事件的依赖关系(箭头终点是结果,依赖于起点的因缘),则隐马尔可夫模型可以表示为下图所示 87 | 88 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_11-43-39.png) 89 | 90 | 91 | 92 | 状态与观测之间的依赖关系确定之后,隐马尔可夫模型利用三个要素来模拟时序序列的发生过程----即**初始状态概率向量、状态转移概率矩阵和发射概率矩阵**。 93 | 94 | 95 | 96 | 2. **初始状态概率向量** 97 | 98 | 系统启动时进入的第一个状态 Y1 称为**初始状态**,假设 y 有 N 种可能的取值,那么 Y1 就是一个独立的离散型随机变量,由 P(y1 | π) 描述。其中 99 | 100 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-47-45.gif) 101 | 102 | 是概率分布的参数向量,称为**初始状态概率向量**。 103 | 104 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_11-57-24.png) 105 | 106 | 107 | 108 | 给定 π ,初始状态 Y1 的取值分布就确定了,比如采用{B,M,E,S}标注集时概率如下: 109 | 110 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-49-50.gif) 111 | 112 | 那么此时隐马尔可夫模型的初始状态概率向量为 π=[0.7,0,0,0.3],注意,句子第一个词是单字的可能性要小一些。 113 | 114 | 115 | 116 | 3. **状态转移矩阵** 117 | 118 | Yt 如何转移到 Yt+1 呢?根据马尔可夫假设,t+1 时的状态仅仅取决于 t 时的状态,既然一共有 N 种状态,那么从状态 Si 到状态 Sj 的概率就构成了一个 N*N 的方阵,称为**状态转移矩阵 A**: 119 | 120 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-51-27.gif) 121 | 122 | 其中下标 i、j 分别表示状态的第 i、j 种取值。状态转移概率的存在有其实际意义,在中文分词中,标签 B 的后面不可能是 S,于是就有 P(Yt+1 = S | Yt = B) = 0。同样,词性标注中的“形容词->名词”“副词->动词”也可通过状态转移概率来模拟,这些**概率分布参数不需要手动设置,而是通过语料库上的统计自动学习**。 123 | 124 | 125 | 126 | 4. **发射概率矩阵** 127 | 128 | 有了状态 Yt 之后,如何确定观测 Xt 的概率分布呢?根据隐马尔可夫假设②,当前观测 Xt 仅仅取决于当前状态 Yt。也就是说,给定每种 y,x 都是一个独立的离散型随机变量,其参数对应一个向量。 假设观测 x 一共有 M 种可能的取值,则 x 的概率分布参数向量维度为 M。由于 y 共有 N 种,所以这些参数向量构成了 N*M 的矩阵,称为**发射概率矩阵B**。 129 | 130 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-52-50.gif) 131 | 132 | 其中,第 i 行 j 列的元素下标 i 和 j 分别代表观测和状态的第 i 种和第 j 种取值。 133 | 134 | 135 | 136 | 5. **隐马尔可夫模型的三个基本用法** 137 | 138 | - **样本生成问题**:给定模型,如何有效计算产生观测序列的概率?换言之,如何评估模型与观测序列之间的匹配程度? 139 | 140 | - **序列预测问题**:给定模型和观测序列,如何找到与此观测序列最匹配的状态序列?换言之,如何根据观测序列推断出隐藏的模型状态? 141 | 142 | - **模型训练问题**:给定观测序列,如何调整模型参数使得该序列出现的概率最大?换言之,如何训练模型使其能最好地描述观测数据? 143 | 144 | 前两个问题是模式识别的问题:1) 根据隐马尔科夫模型得到一个可观察状态序列的概率(**评价**);2) 找到一个隐藏状态的序列使得这个序列产生一个可观察状态序列的概率最大(**解码**)。第三个问题就是根据一个可以观察到的状态序列集产生一个隐马尔科夫模型(**学习**)。 145 | 146 | 147 | 148 | ### 4.3 隐马尔可夫模型的训练 149 | 150 | 1. **案例假设和模型构造** 151 | 152 | 设想如下案例:某医院招标开发“智能”医疗诊断系统,用来辅助感冒诊断。已知①来诊者只有两种状态:要么健康,要么发烧。②来诊者不确定自己到底是哪种状态,只能回答感觉头晕、体寒或正常。医院认为,③感冒这种病,只跟病人前一天的状态有关,并且,④当天的病情决定当天的身体感觉。有位来诊者的病历卡上完整地记录了最近 T 天的身体感受(头晕、体寒或正常),请预测这 T 天的身体状态(健康或发烧)。由于医疗数据属于机密隐私,医院无法提供训练数据,但根据医生经验,感冒发病的规律如下图所示(**箭头上的数值表示概率**): 153 | 154 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-6_14-9-11.png) 155 | 156 | 157 | 158 | 根据已知条件①②,病情状态(健康、发烧)可作为隐马尔可夫模型的隐状态(上图蓝色状态),而身体感受(头晕、体寒或正常)可作为隐马尔可夫模型的显状态(图中白色状态)。条件③符合隐马尔可夫模型假设一,条件④符 合隐马尔可夫模型假设二。这个案例其实描述了一个隐马尔可夫模型, 并且参数已经给定。构造模型代码见: 159 | 160 | ```python 161 | import numpy as np 162 | from pyhanlp import * 163 | from jpype import JArray, JFloat, JInt 164 | 165 | to_str = JClass('java.util.Arrays').toString 166 | 167 | ## 隐马尔可夫模型描述 168 | states = ('Healthy', 'Fever') 169 | start_probability = {'Healthy': 0.6, 'Fever': 0.4} 170 | transition_probability = { 171 | 'Healthy': {'Healthy': 0.7, 'Fever': 0.3}, 172 | 'Fever': {'Healthy': 0.4, 'Fever': 0.6}, 173 | } 174 | emission_probability = { 175 | 'Healthy': {'normal': 0.5, 'cold': 0.4, 'dizzy': 0.1}, 176 | 'Fever': {'normal': 0.1, 'cold': 0.3, 'dizzy': 0.6}, 177 | } 178 | observations = ('normal', 'cold', 'dizzy') 179 | 180 | 181 | def generate_index_map(lables): 182 | index_label = {} 183 | label_index = {} 184 | i = 0 185 | for l in lables: 186 | index_label[i] = l 187 | label_index[l] = i 188 | i += 1 189 | return label_index, index_label 190 | 191 | 192 | states_label_index, states_index_label = generate_index_map(states) 193 | observations_label_index, observations_index_label = generate_index_map(observations) 194 | 195 | 196 | 197 | def convert_map_to_matrix(map, label_index1, label_index2): 198 | m = np.empty((len(label_index1), len(label_index2)), dtype=float) 199 | for line in map: 200 | for col in map[line]: 201 | m[label_index1[line]][label_index2[col]] = map[line][col] 202 | return JArray(JFloat, m.ndim)(m.tolist()) 203 | 204 | def convert_observations_to_index(observations, label_index): 205 | list = [] 206 | for o in observations: 207 | list.append(label_index[o]) 208 | return list 209 | 210 | def convert_map_to_vector(map, label_index): 211 | v = np.empty(len(map), dtype=float) 212 | for e in map: 213 | v[label_index[e]] = map[e] 214 | return JArray(JFloat, v.ndim)(v.tolist()) # 将numpy数组转为Java数组 215 | 216 | 217 | ## pi:初始状态概率向量 218 | ## A:状态转移概率矩阵 219 | ## B:发射概率矩阵 220 | A = convert_map_to_matrix(transition_probability, states_label_index, states_label_index) 221 | B = convert_map_to_matrix(emission_probability, states_label_index, observations_label_index) 222 | observations_index = convert_observations_to_index(observations, observations_label_index) 223 | pi = convert_map_to_vector(start_probability, states_label_index) 224 | 225 | FirstOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.FirstOrderHiddenMarkovModel') 226 | given_model = FirstOrderHiddenMarkovModel(pi, A, B) 227 | ``` 228 | 229 | 230 | 231 | 2. **样本生成算法** 232 | 233 | 它的生成过程就是沿着隐马尔可夫链走 T 步: 234 | 235 | - 根据初始状态概率向量采样第一个时刻的状态 Y1 = Si,即 Y1 ~ π。 236 | - Yt 采样结束得到 Si 后,根据状态转移概率矩s阵第 i 行的概率向量,采样下一时刻的状态 Yt+1。 237 | - 对每个 Yt = Si,根据发射概率矩阵的第 i 行采样 Xt。 238 | - 重复步骤 2 共计 T-1 次,重复步骤 3 共计 T 次,输出序列 x 与 y。 239 | 240 | 代码如下(接上),直接通过模型进行生成: 241 | 242 | ```python 243 | ## 第一个参数:序列最低长度 244 | ## 第二个参数:序列最高长度 245 | ## 第三个参数:需要生成的样本数 246 | for O, S in given_model.generate(3, 5, 2): 247 | print(" ".join((observations_index_label[o] + '/' + states_index_label[s]) for o, s in zip(O, S))) 248 | ``` 249 | 250 | 251 | 252 | 3. **隐马尔可夫模型的训练** 253 | 254 | 样本生成后,我们就可以利用生成的数据重新训练,通过极大似然法来估计隐马尔可夫模型的参数。参数指的是三元组(π,A,B)。 255 | 256 | 利用给定的隐马尔可夫模型 P生成十万个样本,在这十万个样本上训练新模型Q,比较新旧模型参数是否一致。 257 | 258 | ```python 259 | trained_model = FirstOrderHiddenMarkovModel() 260 | 261 | ## 第一个参数:序列最低长度 262 | ## 第二个参数:序列最高长度 263 | ## 第三个参数:需要生成的样本数 264 | trained_model.train(given_model.generate(3, 10, 100000)) 265 | print('新模型与旧模型是否相同:', trained_model.similar(given_model)) 266 | ``` 267 | 268 | 输出: 269 | 270 | ``` 271 | 新模型与旧模型是否相同: True 272 | ``` 273 | 274 | 运行后一般都成立,由于随机数,仅有小概率发生失败。 275 | 276 | 277 | 278 | ### 4.4 **隐马尔可夫模型的预测** 279 | 280 | 隐马尔可夫模型最具实际意义的问题当属序列标注了:给定观测序列,求解最可能的状态序列及其概率。 281 | 282 | 1. **概率计算的前向算法** 283 | 284 | 给定观测序列 x 和一个状态序列 y,就可以估计两者的联合概率 P(x,y),联合概率就是一种结果的概率,在这些结果当中找到最大的联合概率就是找到最有可能的结果预测。联合概率:**P(x,y) = P(y) P(x|y)**,下面我们来分别求出P(y)和P(x|y) 285 | 286 | 287 | 288 | - 顺着隐马尔可夫链走,首先 t=1 时初始状态没有前驱状态,发生概率由 π 决定: 289 | 290 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-55-17.gif) 291 | 292 | - 接着对 t >= 2,状态 Yt 由前驱状态 Yt-1 转移而来,转移矩阵由矩阵 A 决定: 293 | 294 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-56-28.gif) 295 | 296 | 所以状态序列的概率为上面式子的乘积: 297 | 298 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-57-47.gif) 299 | 300 | - P(y) 我们已经求出来了,下面要求 P(x|y) 301 | 302 | 对于每个 Yt = Si,都会“发射”一个 Xt = Oj,发射概率由矩阵 B 决定: 303 | 304 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_0-59-16.gif) 305 | 306 | - 那么给定一个状态序列 Y,对应的 X 的概率累积形式: 307 | 308 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-0-30.gif) 309 | 310 | - 最后带入联合概率公式得: 311 | 312 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-1-42.gif) 313 | 314 | 315 | 316 | 将其中的每个 Xt、Yt 对应上实际发生序列的 Si、Oj,就能带入(**π,A,B**)中的相应元素,从而计算出任意序列的概率,最后找出这些概率的最大值就得到预测结果。找出概率最大值要用到**维特比算法**。 317 | 318 | 319 | 320 | 2. **搜索状态序列的维特比算法** 321 | 322 | 理解了前向算法之后,找寻最大概率所对应的状态序列无非是一个搜索问题。具体说来,将每个状态作为有向图中的一个节点, 节点间的距离由转移概率决定,节点本身的花费由发射概率决定。那么所有备选状态构成一幅有 向无环图,待求的概率最大的状态序列就是图中的最长路径,此时的搜索算法称为**维特比算法**,如图下图所示: 323 | 324 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-7_17-57-2.png) 325 | 326 | 327 | 328 | 上图从左往右时序递增,虚线由初始状态概率决定,实线则是转移概率。由于受到观测序列的约束,不同状态发射观测的概率不同,所以每个节点本身也必须计算自己的花费,由发射概率决定。又由于 Yt+1 仅依赖于 Yt,所以网状图可以**动态规划的搜索**,也就是**维特比算法**: 329 | 330 | - **初始化**,t=1 时初始最优路径的备选由 N 个状态组成,它们的前驱为空。 331 | 332 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-3-7.gif) 333 | 334 | 其中,**δ** 存储在时刻 t 以 Si 结尾的所有局部路径的最大概率。**ψ** 存储局部最优路径末状态 Yt 的前驱状态。 335 | 336 | - **递推**,t >= 2 时每条备选路径像贪吃蛇一样吃入一个新状态,长度增加一个单位,根据转移概率和发射概率计算花费。找出新的局部最优路径,更新 δ、ψ 两个数组。 337 | 338 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-4-40.gif) 339 | 340 | - **终止**,找出最终时刻 δt,i 数组中的最大概率 P*,以及相应的结尾状态下标 i\*t。 341 | 342 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-6-54.gif) 343 | 344 | - **回溯**,根据前驱数组 **ψ** 回溯前驱状态,取得最优路径状态下标。 345 | 346 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_1-8-16.gif) 347 | 348 | 349 | 350 | 预测代码如下(接上面代码): 351 | 352 | ``` 353 | pred = JArray(JInt, 1)([0, 0, 0]) 354 | prob = given_model.predict(observations_index, pred) 355 | print(" ".join((observations_index_label[o] + '/' + states_index_label[s]) for o, s in 356 | zip(observations_index, pred)) + " {:.3f}".format(np.math.exp(prob))) 357 | ``` 358 | 359 | 输出: 360 | 361 | ``` 362 | normal/Healthy cold/Healthy dizzy/Fever 0.015 363 | ``` 364 | 365 | 观察该结果,“/”隔开观测和状态,最后的 0.015 是序列的联合概率。 366 | 367 | 368 | 369 | ### 4.5 隐马尔可夫模型应用于中文分词 370 | 371 | HanLP 已经实现了基于隐马尔可夫模型的中文分词器 HMMSegmenter,并且实现了训练接口。代码详见: 372 | 373 | hmm_cws.py:[https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch04/hmm_cws.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch04/hmm_cws.py) 374 | 375 | 376 | 377 | ### 4.6 性能评测 378 | 379 | 如果隐马尔可夫模型中每个状态仅依赖于前一个状态, 则称为**一阶**;如果依赖于前两个状态,则称为**二阶**。既然一阶隐马尔可夫模型过于简单,是否可以切换到二阶来提高分数呢? 380 | 381 | 答案是可以的,跟一阶类似,这里不再详细介绍二阶隐马尔可夫模型,详细请看原书。 382 | 383 | 这里我们使用 MSR语料库进行评测,结果如下表所示: 384 | 385 | | 算法 | P | R | F1 | R(oov) | R(IV) | 386 | | ------------------ | ----- | ----- | ----- | ------ | ----- | 387 | | 最长匹配 | 89.41 | 94.64 | 91.95 | 2.58 | 97.14 | 388 | | 二元语法 | 92.38 | 96.70 | 94.49 | 2.58 | 99.26 | 389 | | 一阶隐马尔可夫模型 | 78.49 | 80.38 | 79.42 | 41.11 | 81.44 | 390 | | 二阶隐马尔可夫模型 | 78.34 | 80.01 | 79.16 | 42.06 | 81.04 | 391 | 392 | 可以看到,二阶隐马尔可夫模型的 Roov 有少许提升,但综合 F1 反而下降了。这说明增加隐马尔可夫模型的阶数并不能提高分词器的准确率,单靠提高转移概率矩阵的复杂度并不能提高模型的拟合能力,我们需要从别的方面想办法。目前市面上一些开源分词器仍然停留在一阶隐马尔可夫模型的水平,比如著名的结巴分词,它们的准确率也只能达到80%左右。 393 | 394 | 395 | 396 | ### 4.7 总结 397 | 398 | 这一章我们想解决的问题是新词识别,为此从词语级模型切换到字符级模型,将中文分词任务转换为序列标注问题。作为新手起步,我们尝试了最简单的序列标注模型----隐马尔可夫模型。隐马尔可夫模型的基本问题有三个:**样本生成、参数估计、序列预测**。 399 | 400 | 然而隐马尔可夫模型用于中文分词的效果并不理想,虽然召回了一半的 OOV,但综合 F1 甚至低于词典分词。哪怕升级到二阶隐马尔可夫模型, F1 值依然没有提升。 看来朴素的隐马尔可夫模型不适合中文分词,我们需要更高级的模型。 401 | 402 | 话说回来,隐马尔可夫模型作为入门模型,比较容易上手,同时也是许多高级模型的基础。打好基础,我们才能挑战高级模型。 403 | 404 | 405 | 406 | 407 | 408 | 409 | -------------------------------------------------------------------------------- /chapter/5.感知机分类与序列标注.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [5. 感知机分类与序列标注](#5-感知机分类与序列标注) 3 | - [5.1 分类问题](#51-分类问题) 4 | - [5.2 线性分类模型](#52-线性分类模型) 5 | - [5.3 感知机算法](#53-感知机算法) 6 | - [5.4 基于感知机的人名性别分类](#53-基于感知机的人名性别分类) 7 | - [5.5 结构化预测问题](#54-结构化预测问题) 8 | - [5.6 线性模型的结构化感知机算法](#55-线性模型的结构化感知机算法) 9 | - [5.7 基于结构化感知机的中文分词](#56-基于结构化感知机的中文分词) 10 | 11 | ## 5. 感知机分类与序列标注 12 | 13 | 第4章我们利用隐马尔可夫模型实现了第一个基于序列标注的中文分词器,然而效果并不理想。事实上,隐马尔可夫模型假设人们说的话仅仅取决于一个隐藏的{B.M,E,S序列,这个假设太单纯了,不符合语言规律。语言不是由这么简单的标签序列生成,语言含有更多特征,而隐马弥可夫模型没有捕捉到。**隐马弥可夫模型能捕捉的特征仅限于两种: 其一,前一个标签是什么;其二,当前字符是什么**。 14 | 15 | 为了利用更多的特征,线性模型( linear model )应运而生。线性模型由两部分构成: 一系列用来提取特征的特征函数 φ,以及相应的权重向量 w。 16 | 17 | 本章将深人讲解感知机算法的原理,以及在分类和序列标注上的应用。在序列标注应用部分,我们将实现基于感知机的中文分词器。由于感知机序列标注基于分类,并且分类问题更简单,所以我们先学习分类问题。 18 | 19 | 20 | 21 | ### 5.1 分类问题 22 | 23 | 1. **定义** 24 | 25 | **分类**指的是预测样本所属类别的一类问题。二分类也可以解决任意类别数的多分类问题(one vs rest)。 26 | 27 | - 将类型class1看作正样本,其他类型全部看作负样本,然后我们就可以得到样本标记类型为该类型的概率 p1。 28 | 29 | - 然后再将另外类型class2看作正样本,其他类型全部看作负样本,同理得到 p2。 30 | 31 | - 以此循环,我们可以得到该待预测样本的标记类型分别为类型 class i 时的概率 pi,最后我们取 pi 中最大的那个概率对应的样本标记类型作为我们的待预测样本类型。 32 | - 总之还是以二分类来依次划分,并求出最大概率结果。 33 | 34 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_10-27-54.png) 35 | 36 | 37 | 38 | 2. **应用** 39 | 40 | 在NLP领域,绝大多数任务可以用分类来解决。文本分类天然就是一个分类问题。关键词提取时,对文章中的每个单词判断是否属于关键词,于是转化为二分类问题。在指代消解问题中,对每个代词和每个实体判断是否存在指代关系,又是一个二分类问题。在语言模型中,将词表中每个单词作为一种类别,给定上文预测接下来要出现的单词。 41 | 42 | 43 | 44 | ### 5.2 线性分类模型 45 | 46 | **线性模型**是传统机器学习方法中最简单最常用的分类模型,用一条线性的直线或高维平面将数据一分为二。 47 | 48 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_10-54-50.png) 49 | 50 | 直线将平面分割为两部分,分别对应男女。对于任何姓名,计算它落入哪个区域,就能预测它的性别。这样的区域称为**决策区域**,它们的边界称为**决策边界**。二维空间中,如果决策边界是直线,则称为**线性分类模型**: Y = Wx + b。 51 | 52 | 如果是任意维度空间中的线性决策边界统称为**分离超平面** 53 | 54 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_11-1-3.png) 55 | 56 | 推广到 D 维空间,分离超平面的方程为: 57 | 58 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-51-43.gif) 59 | 60 | 其中,w 是权重,b 偏置(截距),可以写成向量的形式: 61 | 62 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-53-25.gif) 63 | 64 | 65 | 66 | 67 | ### 5.3 感知机算法 68 | 69 | 找出这个分离超平面其实就是感知机算法。**感知机算法**则是一种迭代式的算法:在训练集上运行多个迭代,每次读入一个样本,执行预测,将预测结果与正确答案进行对比,计算误差,根据误差更新模型参数,再次进行训练,直到误差最小为止。 70 | 71 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_11-28-24.png) 72 | 73 | - **损失函数**: 从数值优化的角度来讲,迭代式机器学习算法都在优化(减小)一个损失函数( loss function )。损失函数 J(w) 用来衡量模型在训练集上的错误程度,自变量是模型参数 w,因变量是一个标量,表示模型在训练集上的损失的大小。 74 | - **梯度下降**: 给定样本,其特征向量 x 只是常数,对 J(w) 求导,得到一个梯度向量 Δw,它的反方向一定是当前位置损失函数减小速度最快的方向。如果参数点 w 反方向移动就会使损失函数减小,叫梯度下降。 75 | - **学习率**: 梯度下降的步长叫做学习率。 76 | - **随机梯度下降**(SGD): 如果算法每次迭代随机选取部分样本计算损失函数的梯度,则称为随机梯度下降。 77 | 78 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-8_11-43-39.png) 79 | 80 | 这时候问题来了,假如数据本身线性不可分,感知机损失函数不会收敛,每次迭代分离超平面都会剧烈振荡。这时可以对感知机算法打补丁,使用投票感知机或平均感知机。 81 | 82 | 1. **投票感知机和平均感知机** 83 | 84 | **投票感知机**:每次迭代的模型都保留,准确率也保留,预测时,每个模型都给出自己的结果,乘以它的准确率加权平均值作为最终结果。 85 | 86 | 投票感知机要求存储多个模型及加权,计算开销较大,更实际的做法是取多个模型的权重的平均,这就是**平均感知机**。 87 | 88 | 89 | 90 | ### 5.4 基于感知机的人名性别分类 91 | 92 | 解决人名性别分类的监督学习流程: 93 | 94 | - 标注人名分类语料库 95 | - 利用感知机算法训练线性模型 96 | - 利用线性模型给人名分类,评估准确率。 97 | 98 | 1. **人名性别语料库** 99 | 100 | 笔者整理了一份人名性别语料库 cnname 101 | 102 | 运行下面代码后会自动下载。 103 | 104 | 预料格式为逗号分隔的 .csv,第一列为姓名,第二列为性别: 105 | 106 | ``` 107 | 赵伏琴,女 108 | 钱沐杨,男 109 | 孙竹珍,女 110 | 李潮阳,男 111 | ``` 112 | 113 | 2. **训练** 114 | 115 | 代码详见:classify_name.py 116 | 117 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch05/classify_name.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch05/classify_name.py) 118 | 119 | 运行结果如下: 120 | 121 | ``` 122 | 下载 http://file.hankcs.com/corpus/cnname.zip 到 /usr/local/lib/python3.7/site-packages/pyhanlp/static/data/test/cnname.zip 123 | 100.00%, 1 MB, 256 KB/s, 还有 0 分 0 秒 124 | =====朴素感知机算法===== 125 | 训练集准确率: P=85.44 R=85.06 F1=85.25 126 | 特征数量: 9089 127 | 赵建军=男 128 | 沈雁冰=男 129 | 陆雪琪=女 130 | 李冰冰=女 131 | 测试集准确率: P=82.85 R=82.90 F1=82.88 132 | =====平均感知机算法===== 133 | 训练集准确率: P=93.62 R=83.06 F1=88.02 134 | 特征数量: 9089 135 | 赵建军=男 136 | 沈雁冰=男 137 | 陆雪琪=女 138 | 李冰冰=女 139 | 测试集准确率: P=90.92 R=80.39 F1=85.33 140 | ``` 141 | 142 | 143 | 144 | ### 5.5 结构化预测问题 145 | 146 | 自然语言处理问题大致可分为两类,一种是分类问题,另一种就是结构化预测问题,序列标注只是结构化预测的一个特例,对感知机稍作拓展,分类器就能支持结构化预测。 147 | 148 | 1. **定义** 149 | 150 | 信息的层次结构特点称作结构化。那么**结构化预测**(structhre,prediction)则是预测对象结构的一类监督学习问题。相应的模型训练过程称作**结构化学习**(stutured laming )。分类问题的预测结果是一个决策边界, 回归问题的预测结果是一个实数标量,而结构化预测的结果则是一个完整的结构。 151 | 152 | 自然语言处理中有许多任务是结构化预测,比如序列标注预测结构是一整个序列,句法分析预测结构是一棵句法树,机器翻译预测结构是一段完整的译文。这些结构由许多部分构成,最小的部分虽然也是分类问题(比如中文分词时每个字符分类为{B,M,E,S} ),但必须考虑结构整体的合理程度。 153 | 154 | 2. **结构化预测与学习流程** 155 | 156 | 结构化预测的过程就是给定一个模型 λ 及打分函数 score,利用打分函数给一些备选结构打分,选择分数最高的结构作为预测输出,公式如下: 157 | 158 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-55-36.gif) 159 | 160 | 其中,Y 是备选结构的集合。既然结构化预测就是搜索得分最高的结构 y,那么结构化学习的目标就是想方设法让正确答案 y 的得分最高。不同的模型有不同的算法,对于线性模型,训练算法为结构化感知机。 161 | 162 | 163 | 164 | ### 5.6 线性模型的结构化感知机算法 165 | 166 | 1. **结构化感知机算法** 167 | 168 | 要让线性模型支持结构化预测,必须先设计打分函数。打分函数的输入有两个缺一不可的参数: 特征 x 和结构 y。但之前介绍的线性模型的“打分函数”只接受一个自变量 x。 169 | 170 | 做法是定义新的特征函数 ϕ(x,y),把结构 y 也作为一种特征,输出新的“结构化特征向量”。新特征向量与权重向量做点积后,就得到一个标量,将其作为分数: 171 | 172 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-56-36.gif) 173 | 174 | 打分函数有了,取分值最大的结构作为预测结果,得到结构化预测函数: 175 | 176 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-57-27.gif) 177 | 178 | 预测函数与线性分类器的决策函数很像,都是权重向量点积特征向量。那么感知机算法也可以拓展复用,得到线性模型的结构化学习算法。 179 | 180 | - 读入样本 (x,y),进行结构化预测 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-58-23.gif) 181 | 182 | - 与正确答案相比,若不相等,则更新参数: 奖励正确答案触发的特征函数的权重,否则进行惩罚: 183 | 184 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_10-59-14.gif) 185 | 186 | - 还可以调整学习率: 187 | 188 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_11-0-5.gif) 189 | 190 | 191 | 192 | 2. **与感知机算法比较** 193 | 194 | - 结构化感知机修改了特征向量。 195 | - 结构化感知机的参数更新赏罚分明。 196 | 197 | 198 | 199 | 3. **结构化感知机与序列标注** 200 | 201 | 上面已经讲了结构化感知机的模型公式,看如何运用到序列标注上,我们知道序列标注最大的结构特点就是标签相互之间的依赖性,这种依赖性利用初始状态概率想俩狗和状态转移概率矩阵体系那,那么对于结构化感知机,就可以使用**转移特征**来表示: 202 | 203 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_11-1-14.gif) 204 | 205 | 206 | 207 | 其中,Yt 为序列第 t 个标签,Si 为标注集第 i 种标签,N 为标注集大小。 208 | 209 | 210 | **状态特征**如下,类似于隐马尔可夫模型的发射概率矩阵,状态特征只与当前的状态有关,与之前的状态无关: 211 | 212 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_11-2-11.gif) 213 | 214 | 215 | 216 | 于是,结构化感知机的特征函数就是转移特征和状态特征的合集: 217 | 218 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_11-3-4.gif) 219 | 220 | 221 | 222 | 基于以上公式,我们统一用打分函数来表示: 223 | 224 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_11-3-55.gif) 225 | 226 | 227 | 228 | 有了打分公式,就可以利用维特比算法求解得分最高的序列。 229 | 230 | 231 | 232 | ### 5.7 基于结构化感知机的中文分词 233 | 234 | 代码详见(注释写得很清楚): **perceptron_cws.py** 235 | 236 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch05/perceptron_cws.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch05/perceptron_cws.py) 237 | 238 | 运行以上代码结果如下: 239 | 240 | ``` 241 | P:96.68 R:96.51 F1:96.59 OOV-R:71.54 IV-R:97.18 242 | [王思斌, ,, 男, ,, 1949年10月, 生, 。] 243 | [山东, 桓台县, 起凤镇, 穆寨村, 妇女, 穆玲英] 244 | [现, 为, 中国艺术研究院中国文化研究所, 研究员, 。] 245 | [我们, 的, 父母, 重, 男, 轻, 女] 246 | [北京输气管道, 工程] 247 | ``` 248 | 249 | **准确性与性能的比较** 250 | 251 | | 算法 | P | R | F1 | R(oov) | R(IV) | 252 | | ------------ | ----- | ----- | ----- | ------ | ----- | 253 | | 最长匹配 | 89.41 | 94.64 | 91.95 | 2.58 | 97.14 | 254 | | 二元语法 | 92.38 | 96.70 | 94.49 | 2.58 | 99.26 | 255 | | 一阶HHM | 78.49 | 80.38 | 79.42 | 41.11 | 81.44 | 256 | | 二阶HHM | 78.34 | 80.01 | 79.16 | 42.06 | 81.04 | 257 | | 平均感知机 | 96.69 | 96.45 | 96.57 | 70.34 | 97.16 | 258 | | 结构化感知机 | 96.67 | 96.64 | 96.65 | 70.52 | 97.35 | 259 | 260 | 对比各项指标,我们终于将 OOV 提高到了 70% 以上,并且综合 F1 也提高了 96.7%,感知机是截止到这章最好用的算法,完全达到了实用水平,在实际项目中,无非还需要挂载一些领域词库。 261 | -------------------------------------------------------------------------------- /chapter/6.条件随机场与序列标注.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [6. 条件随机场与序列标注](#6-条件随机场与序列标注) 3 | - [6.1 机器学习的模型谱系](#61-机器学习的模型谱系) 4 | - [6.2 条件随机场](#62-条件随机场) 5 | - [6.3 条件随机场工具包](#63-条件随机场工具包) 6 | - [6.4 HanLP中的CRF++ API](#64-hanlp中的crf-api) 7 | 8 | ## 6. 条件随机场与序列标注 9 | 10 | 本章介绍一种新的序列标注模型条件随机场。这种模型与感知机同属结构化学习大家族,但性能比感知机还要强大。为了厘清该模型的来龙去脉,我们先对机器学习模型做番柿理。然后结合代码介绍条件随机场理论,探究它与结构化感知机的异同。 11 | 12 | 13 | 14 | ### 6.1 机器学习的模型谱系 15 | 16 | 机器学习的模型谱系图如下图所示: 17 | 18 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_15-14-17.png) 19 | 20 | 根据建模的究竟是联合概率分布 P(x,y) 还是条件概率分布 P(y|x)。派生出生成式模型与判别式模型。 21 | 22 | 1. **生成式模型** 23 | 24 | **生成式模型**:模拟数据的生成过程,两类随机变量存在因果先后关系,先有因素 y,后有结果 x,这种因果关系由联合分布模拟: 25 | 26 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-51-16.gif) 27 | 28 | 通过联合分布 P(x,y),生成式模型其实间接建模了 P(x): 29 | 30 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-52-37.gif) 31 | 32 | 这里有两个缺陷: 33 | 34 | - P(x) 很难准确估计,因为特征之间并非相互独立,而是存在错综复杂的依赖关系。 35 | - P(x) 在分类中也没有直接作用。 36 | 37 | 为了克服这两个问题,判别式模型出现。 38 | 39 | 2. **判别式模型** 40 | 41 | 判别式模型直接跳过了 P(x),直接对条件概率 P(y|x) 建模。不管 x 内部存在多复杂的关系,也不影响判别式模型对 y 的判断,于是就能够**放心大胆的利用各种各样丰富的、有关联的特征**。 所以我们会看到感知机分词的准确率高于隐马尔可夫模型。 42 | 43 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-53-34.gif) 44 | 45 | 其中,exp 为指数函数。随机变量关系错综复杂,为了分析这些关系,使用概率图模型。 46 | 47 | 3. **有向概率图模型** 48 | 49 | **概率图模型**( Probabilistic Graphical Model, PGM)是用来表示与推断多维随机变量联合分布 p(x,y) 的强大框架,被广泛用于计算机视觉、知识表达、贝叶斯统计与自然语言处理。它利用节点 V 来表示随机变量,用边 E 连接有关联的随机变量,将多维随机变量分布表示为图 G=(V,E)。这样就带来了一个好处,那就是整个图可以分解为子图再进行分析.子图中的随机变量更少,建模更加简单。具体如何分解,据此派生出有向图模型和无向图模型。 50 | 51 | **有向图模型**按事件的先后因果顺序将节点连接为有向图。如果事件 A 导致事件 B,则用箭头连接两个事件 A-->B。 52 | 53 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_15-46-25.png) 54 | 55 | 有向图模型都将概率有向图分解为一系列条件概率之积,有向图模型经常用生成式模型来实现。定义 π(v) 表示节点 v 的所有前驱节点,则分布为: 56 | 57 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-54-37.gif) 58 | 59 | 60 | 61 | 4. **无向概率图模型** 62 | 63 | 相反,**无向图模型**则不探究每个事件的因果关系,也就是说不涉及条件概率分解。无向图模型的边没有方向,仅仅代表两个事件有关联。 64 | 65 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-9_16-7-16.png) 66 | 67 | 无向图模型将概率分解为所有最大团上的某种函数之积。 68 | 69 | 在图论中,**最大团**指的是满足所有节点相互连接的最大子图。因为最大团需要考虑所有变量,为此,无向图模型定义了一些虚拟的**因子节点**,每个因子节点只连接部分节点,组成更小的最大团。 70 | 71 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_10-35-32.png) 72 | 73 | 74 | 75 | 蓝色虚线表示最大团,黑色方块表因子节点,圆圈则表示变量节点,无向图模型将多维随机变量的联合分布分解为一系列最大团中的因子之积: 76 | 77 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-55-36.gif) 78 | 79 | 其中,a 是因子节点,Ψa 则是一个因子节点对应的函数,参数 Xa,Ya 是与因子节点相连的所有变量节点。为了将式子约束为概率分布,定义常数 Z 为如下归一化因子: 80 | 81 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-56-26.gif) 82 | 83 | 在机器学习中,常用指数家族的因子函数: 84 | 85 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-57-29.gif) 86 | 87 | 其中,k 为特征的编号,Fak 是特征函数,Wak 为相应的特征权重。 88 | 89 | 判别式模型经常用无向图来表示,只需要在归一化时,对每种 x 都求一个归一化因子: 90 | 91 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-58-24.gif) 92 | 93 | 然后 P(x,y) 就转化为判别式模型所需的条件概率分布: 94 | 95 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_16-59-19.gif) 96 | 97 | 到这里,最后一个公式就是条件随机场的一般形式。 98 | 99 | 100 | 101 | ### 6.2 条件随机场 102 | 103 | **条件随机场**( Conditional Random Field, CRF)是一种给定输入随机变量 x,求解条件概率 p(y| x) 的概率无向图模型。用于序列标注时,特例化为线性链( linear chain )条件随机场。此时,输人输出随机变量为等长的两个序列。 104 | 105 | 1. **线性链条件随机场** 106 | 107 | 线性链条件随机场如下图所示: 108 | 109 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_11-27-16.png) 110 | 111 | 112 | 113 | 每个 Xt 上方有 3 个灰色节点,代表 Xt 的 3 个特征,当然还可以是任意数量的特征,体现了特征的丰富性,黑色方块是因子节点,可以理解为一个特征函数 ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-0-23.gif)。其中仅仅利用了 Xt 和 Yt 的特征称作**状态特征**,利用了 Yt-1 的特征则称作**转移特征**,与感知机的特征函数相同。 114 | 115 | **线性链条件随机场**的定义如下: 116 | 117 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-1-14.gif) 118 | 119 | 其中,Z(x)为归一化函数: 120 | 121 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-2-4.gif) 122 | 123 | 上式定义在所有可能的标注序列上。如果将所有特征函数与权重分别写作向量形式,则线性链条件随机场的定义可简化为: 124 | 125 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-10-56.gif) 126 | 127 | 对比结构化感知机的打分函数: 128 | 129 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-11-56.gif) 130 | 131 | 可以发现结构化感知机打分函数与条件随机场的指数部分完全相同,由于给定实例 x,Z(x) 就是一个常数 c,所以有: 132 | 133 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-12-45.gif) 134 | 135 | 于是,条件随机场就和结构化感知机有以下联系: 136 | 137 | - 条件随机场和结构化感知机的特征函数完全一致。 138 | - 结构化感知机预测打分越高,条件随机场给予该预测的概率也越大。 139 | 140 | 这种相似性使得我们能够复用结构化感知机的预测算法,也就是维特比算法。 141 | 142 | 143 | 条件随机场的训练过程详见《自然语言处理入门》第6章。 144 | 145 | 146 | 147 | 2. **对比结构化感知机** 148 | 149 | 结构化感知机和条件随机场的**相同点**: 150 | 151 | - 特征函数相同 152 | - 权重向量相同 153 | - 打分函数相同 154 | - 预测算法相同 155 | - 同属结构化学习 156 | 157 | **不同点** 158 | 159 | - 感知机更新参数时,只使用一个训练实例,没有考虑整个数据集,难免顾此失彼;而条件随机场对数似然函数及其梯度则使用了整个数据集。 160 | 161 | - 条件随机场更新参数更加合理,条件随机场更新参数如下: 162 | 163 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-13-35.gif) 164 | 165 | 对比感知机的更新参数表达式: 166 | 167 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-10_17-14-25.gif) 168 | 169 | 两者的差距一目了然,感知机奖励正确答案对应的特征函数 ϕ,但仅惩罚错误最厉害的那个 y,而条件随机场同时惩罚所有答案 y,分摊惩罚总量。 170 | 171 | 172 | 173 | ### 6.3 条件随机场工具包 174 | 175 | 谈到条件随机场工具包,最著名的就是 CRF++,有各大平台的安装方法,HanLP已经集成了。 176 | 177 | 1. **CRF++ 语料格式** 178 | 179 | CRF++ 接受纯文本语料,约定为一种空格或制表符分隔的表格格式。每个序列作为一个表格,每行为序列的一个时刻 Xt,Yt,除了最后一列为输出变量 y 之外,其它列都是输入变量 x,如下所示: 180 | 181 | ``` 182 | 商 s 中 B 183 | 品 p 中 E 184 | 和 h 中 S 185 | 服 f 中 B 186 | 务 w 中 E 187 | 188 | A a 英 B 189 | K k 英 M 190 | B b 英 M 191 | 4 s 数 M 192 | 8 b 数 E 193 | ``` 194 | 195 | 196 | 197 | ### 6.4 HanLP中的CRF++ API 198 | 199 | 详细代码请见: evaluate_crf_cws.py 200 | 201 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch06/evaluate_crf_cws.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch06/evaluate_crf_cws.py) 202 | 203 | 训练耗时很长。 204 | 205 | **标准化评测** 206 | 207 | | 算法 | P | R | F1 | R(oov) | R(IV) | 208 | | ------------ | ----- | ----- | ----- | ------ | ----- | 209 | | 最长匹配 | 89.41 | 94.64 | 91.95 | 2.58 | 97.14 | 210 | | 二元语法 | 92.38 | 96.70 | 94.49 | 2.58 | 99.26 | 211 | | 一阶HHM | 78.49 | 80.38 | 79.42 | 41.11 | 81.44 | 212 | | 二阶HHM | 78.34 | 80.01 | 79.16 | 42.06 | 81.04 | 213 | | 平均感知机 | 96.69 | 96.45 | 96.57 | 70.34 | 97.16 | 214 | | 结构化感知机 | 96.67 | 96.64 | 96.65 | 70.52 | 97.35 | 215 | | 条件随机场 | 96.86 | 96.64 | 96.75 | 71.54 | 97.33 | 216 | 217 | 条件随机场的各项指标全面胜过了结构化感知机,综合 F1 更达到 96.8%, 是传统方法中最准确的分词模型。 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /chapter/7.词性标注.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [7. 词性标注](#7-词性标注) 3 | - [7.1 词性标注概述](#71-词性标注概述) 4 | - [7.2 词性标注语料库与标注集](#72-词性标注语料库与标注集) 5 | - [7.3 基于隐马尔可夫模型的词性标注](#73-基于隐马尔可夫模型的词性标注) 6 | - [7.4 基于感知机的词性标注](#74-基于感知机的词性标注) 7 | - [7.5 基于条件随机场的词性标注](#75-基于条件随机场的词性标注) 8 | - [7.6 词性标注评测](#76-词性标注评测) 9 | - [7.7 自定义词性](#77-自定义词性) 10 | 11 | ## 7. 词性标注 12 | 13 | ### 7.1 词性标注概述 14 | 15 | 1. **什么是词性** 16 | 17 | 在语言学上,**词性**(Par-Of-Speech, Pos )指的是单词的语法分类,也称为词类。同一个类别的词语具有相似的语法性质,所有词性的集合称为词性标注集。不同的语料库采用了不同的词性标注集,一般都含有形容词、动词、名词等常见词性。下图就是HanLP输出的一个含有词性的结构化句子。 18 | 19 | ``` 20 | 我/r 的/u 希望/n 是/v 希望/v 张晚霞/nr 的/u 背影/n 被/p 晚霞/n 映/v 红/a 21 | ``` 22 | 23 | 每个单词的后边跟的就是词性标签: 24 | 25 | | 词性标签 | 词性 | 26 | | -------- | ------ | 27 | | r | 代词 | 28 | | u | 动词 | 29 | | n | 名词 | 30 | | v | 动词 | 31 | | nr | 人名 | 32 | | p | 介词 | 33 | | a | 形容词 | 34 | 35 | 36 | 37 | 2. **词性的用处** 38 | 39 | 词性的作用是提供词语的抽象表示,词的数量是无穷的,但词性的数量是有限的。词性支撑着许多高级应用,当下游应用遇到 OOV 时,可以通过 OOV 的词性猜测用法,比如上面的句子“林晚霞”就识别为人名进行处理,而不会拆开。 40 | 41 | 词性也可以直接用于抽取一些信息,比如抽取所有描述特定商品的形容词等。 42 | 43 | 44 | 45 | 3. **词性标注** 46 | 47 | **词性标注**指的是为句子中每个单词预测一个词性标签的任务。它有以下两个难点: 48 | 49 | - 汉语中一个单词多个词性的现象很常见,但在具体语境下一定是唯一词性。 50 | 51 | - OOV 是任何自然语言处理任务的难题。 52 | 53 | 54 | 55 | 4. **词性标注模型** 56 | 57 | 统计方法为这两个难点提供了解决方案,那就是我们熟悉的**序列标注模型**。只需将中文分词中的汉字替换为词语,{B,M,E,S} 替换为“名词、动词、形容词等”,序列标注模型马上就可以用来做词性标注。 58 | 59 | 词性标注既可以看作中文分词的后续任务,也可以与中文分词集成为同一个任务。其中就可以把分词语料库加上词性标签就可以了,这样同时进行多个任务的模型称为**联合模型**。由于综合考虑了多种监督信号,联合模型在几乎所有问题上都要优于独立模型。 60 | 61 | 然而工业界就没有那么理想,同时具有分词和词性标注的语料库非常少,需要大量的人力进行标注。 62 | 63 | 64 | 65 | ### 7.2 词性标注语料库与标注集 66 | 67 | 同中文分词一样,语言学界在标注规范上存在分歧,导致目前还没有一个被广泛接受的汉语词性划分标准。无论是词性划分的颗粒度,还是词性标签都不统一。一方面,各研究机构各持己见、派系林立,标注了大量互不兼容的语料库。另一方面,部分语料库受到严格版权控制,成为内部材料,得不到充分共享利用。 68 | 69 | 本节选取其中一些授权宽松, 容易获得的语料库作为案例。 70 | 71 | 以下示例我们选取 PKU 标注的《人民日报》语料库的标注集。 72 | 73 | 74 | 75 | ### 7.3 基于隐马尔可夫模型的词性标注 76 | 77 | 之前我们就介绍过隐马尔可夫模型,详细见: [4.隐马尔可夫模型与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/4.%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 78 | 79 | 隐马尔可夫模型词性标注代码见(**程序会自动下载 PKU 语料库**): hmm_pos.py 80 | 81 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/hmm_pos.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/hmm_pos.py) 82 | 83 | 运行代码后结果如下: 84 | 85 | ``` 86 | 一阶隐马尔可夫模型: 87 | r, u, n, v, v, v 88 | 他/r 的/u 希望/n 是/v 希望/v 上学/v 89 | 他/代词 的/助词 希望/名词 是/动词 希望/动词 上学/动词 90 | 李狗蛋/动词 的/动词 希望/动词 是/动词 希望/动词 上学/动词 91 | 92 | 二阶隐马尔可夫模型: 93 | r, u, n, v, v, v 94 | 他/r 的/u 希望/n 是/v 希望/v 上学/v 95 | 他/代词 的/助词 希望/名词 是/动词 希望/动词 上学/动词 96 | 李狗蛋/动词 的/动词 希望/动词 是/动词 希望/动词 上学/动词 97 | ``` 98 | 99 | 可见隐马尔可夫模型成功的辨别出“希望”的两种词性 n 和 v。但 OOV问题就出现了,无法把“李狗蛋”识别成人名,隐马尔可夫模型一步走错满盘皆输,其根本原因在于隐马尔可夫模型只能利用单词这一个状态特征,无法通过姓氏“李”来推测“李狗蛋”是人名。 100 | 101 | 102 | 103 | ### 7.4 基于感知机的词性标注 104 | 105 | 之前我们就介绍过感知机模型,详细见: [5.感知机分类与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/5.%E6%84%9F%E7%9F%A5%E6%9C%BA%E5%88%86%E7%B1%BB%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 106 | 107 | 按照中文分词时的经验,感知机能够利用丰富的上下文特征,是优于隐马尔可夫模型的选择,对于词性标注也是如此。 108 | 109 | 感知机模型词性标注代码见(**程序会自动下载 PKU 语料库**): perceptron_pos.py 110 | 111 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/perceptron_pos.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/perceptron_pos.py) 112 | 113 | 运行会有些慢,结果如下: 114 | 115 | ``` 116 | 李狗蛋/nr 的/u 希望/n 是/v 希望/v 上学/v 117 | 李狗蛋/人名 的/助词 希望/名词 是/动词 希望/动词 上学/动词 118 | ``` 119 | 120 | 这次的运行结果完全正确,感知机成功的识别出 OOV “李狗蛋”的词性。 121 | 122 | 123 | 124 | ### 7.5 基于条件随机场的词性标注 125 | 126 | 之前我们就介绍过条件随机场模型,详细见: [6.条件随机场与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/6.%E6%9D%A1%E4%BB%B6%E9%9A%8F%E6%9C%BA%E5%9C%BA%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 127 | 128 | 条件随机场模型词性标注代码见(**程序会自动下载 PKU 语料库**): crf_pos.py 129 | 130 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/crf_pos.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch07/crf_pos.py) 131 | 132 | 运行时间会比较长,结果如下: 133 | 134 | ``` 135 | 李狗蛋/nr 的/u 希望/n 是/v 希望/v 上学/v 136 | 李狗蛋/人名 的/助词 希望/名词 是/动词 希望/动词 上学/动词 137 | ``` 138 | 139 | 依然可以成功识别 OOV “李狗蛋”的词性。 140 | 141 | 142 | 143 | ### 7.6 词性标注评测 144 | 145 | 将 PKU 语料库按 9:1 分隔为训练集和测试集,分别用以上三种模型来训练,准确率如下: 146 | 147 | | 算法 | 准确率 | 148 | | ------------------ | ------ | 149 | | 一阶隐马尔可夫模型 | 44.99% | 150 | | 二阶隐马尔可夫模型 | 40.53% | 151 | | 结构化感知机 | 83.07% | 152 | | 条件随机场 | 82.12% | 153 | 154 | 从上图可知,结构化感知机和条件随机场都要优于隐马尔可夫模型,判别式模型能够利用更多的特征来进行训练,从而提高更多的精度。 155 | 156 | 157 | 158 | ### 7.7 自定义词性 159 | 160 | 在工程上,许多用户希望将特定的一些词语打上自定义的标签,称为**自定义词性**。比如,电商领域的用户希望将一些手机品牌打上相应标签,以便后续分析。HanLP 提供了自定义词性功能。具体有两种实现。 161 | 162 | 1. **朴素实现** 163 | 164 | 可以使用HanLP挂载的方式实现: 165 | 166 | ```python 167 | from pyhanlp import * 168 | 169 | CustomDictionary.insert("苹果", "手机品牌 1") 170 | CustomDictionary.insert("iPhone X", "手机型号 1") 171 | analyzer = PerceptronLexicalAnalyzer() 172 | analyzer.enableCustomDictionaryForcing(True) 173 | print(analyzer.analyze("你们苹果iPhone X保修吗?")) 174 | print(analyzer.analyze("多吃苹果有益健康")) 175 | ``` 176 | 177 | 当然,此处以代码的方式插入自定义词语,在实际项目中也可以用词典文件的方式,运行效果如下: 178 | 179 | ``` 180 | 你们/r 苹果/手机品牌 iPhone X/手机型号 保修/v 吗/y ?/w 181 | 多/ad 吃/v 苹果/手机品牌 有益健康/i 182 | ``` 183 | 184 | 从结果来看,词典只是机械的匹配,将“吃苹果”也当成了手机品牌,犯了所有规则系统的通病,看来词典同样解决不了词性标注,词性标注还是应当交给统计方法。 185 | 186 | 187 | 188 | 2. **标注语料** 189 | 190 | 词性的确定需要根据上下文语境,这恰好是统计模型所擅长的。为了实现自定义词性,最佳实践是标注一份语料库,然后训练一个统计模型。 191 | 192 | 至于语料库规模,与所有机器学习问题一样,数据越多,模型越准。 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /chapter/8.命名实体识别.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [8. 命名实体识别](#8-命名实体识别) 3 | - [8.1 概述](#81-概述) 4 | - [8.2 基于隐马尔可夫模型序列标注的命名实体识别](#82-基于隐马尔可夫模型序列标注的命名实体识别) 5 | - [8.3 基于感知机序列标注的命名实体识别](#83-基于感知机序列标注的命名实体识别) 6 | - [8.4 基于条件随机场序列标注的命名实体识别](#84-基于条件随机场序列标注的命名实体识别) 7 | - [8.5 命名实体识别标准化评测](#85-命名实体识别标准化评测) 8 | - [8.6 自定义领域命名实体识别](#86-自定义领域命名实体识别) 9 | 10 | ## 8. 命名实体识别 11 | 12 | ### 8.1 概述 13 | 14 | 1. **命名实体** 15 | 16 | 文本中有一些描述实体的词汇。比如人名、地名、组织机构名、股票基金、医学术语等,称为**命名实体**。具有以下共性: 17 | 18 | - 数量无穷。比如宇宙中的恒星命名、新生儿的命名不断出现新组合。 19 | - 构词灵活。比如中国工商银行,既可以称为工商银行,也可以简称工行。 20 | - 类别模糊。有一些地名本身就是机构名,比如“国家博物馆” 21 | 22 | 2. **命名实体识别** 23 | 24 | 识别出句子中命名实体的边界与类别的任务称为**命名实体识别**。由于上述难点,命名实体识别也是一个统计为主、规则为辅的任务。 25 | 26 | 对于规则性较强的命名实体,比如网址、E-mail、IBSN、商品编号等,完全可以通过正则表达式处理,未匹配上的片段交给统计模型处理。 27 | 28 | 命名实体识别也可以转化为一个序列标注问题。具体做法是将命名实体识别附着到{B,M,E,S}标签,比如, 构成地名的单词标注为“B/ME/S- 地名”,以此类推。对于那些命名实体边界之外的单词,则统一标注为0 ( Outside )。具体实施时,HanLP做了一个简化,即所有非复合词的命名实体都标注为S,不再附着类别。这样标注集更精简,模型更小巧。 29 | 30 | 命名实体识别实际上可以看作分词与词性标注任务的集成: 命名实体的边界可以通过{B,M,E,S}确定,其类别可以通过 B-nt 等附加类别的标签来确定。 31 | 32 | HanLP内部提供了语料库转换工序,用户无需关心,只需要传入 PKU 格式的语料库路径即可。 33 | 34 | 35 | 36 | ### 8.2 基于隐马尔可夫模型序列标注的命名实体识别 37 | 38 | 之前我们就介绍过隐马尔可夫模型,详细见: [4.隐马尔可夫模型与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/4.%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 39 | 40 | 隐马尔可夫模型命名实体识别代码见(**自动下载 PKU 语料库**): hmm_ner.py 41 | 42 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/hmm_ner.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/hmm_ner.py) 43 | 44 | 运行代码后结果如下: 45 | 46 | ``` 47 | 华北电力公司/nt 董事长/n 谭旭光/nr 和/c 秘书/n 胡花蕊/nr 来到/v 美国纽约/ns 现代/ntc 艺术/n 博物馆/n 参观/v 48 | ``` 49 | 50 | 其中机构名“华北电力公司”、人名“谭旭光”“胡花蕊”全部识别正确。但是地名“美国纽约现代艺术博物馆”则无法识别。有以下两个原因: 51 | 52 | - PKU 语料库中没有出现过这个样本。 53 | - 隐马尔可夫模型无法利用词性特征。 54 | 55 | 对于第一个原因,只能额外标注一些语料。对于第二个原因可以通过切换到更强大的模型来解决。 56 | 57 | 58 | 59 | ### 8.3 基于感知机序列标注的命名实体识别 60 | 61 | 之前我们就介绍过感知机模型,详细见: [5.感知机分类与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/5.%E6%84%9F%E7%9F%A5%E6%9C%BA%E5%88%86%E7%B1%BB%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 62 | 63 | 感知机模型词性标注代码见(**自动下载 PKU 语料库**): perceptron_ner.py 64 | 65 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/perceptron_ner.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/perceptron_ner.py) 66 | 67 | 运行会有些慢,结果如下: 68 | 69 | ``` 70 | 华北电力公司/nt 董事长/n 谭旭光/nr 和/c 秘书/n 胡花蕊/nr 来到/v [美国纽约/ns 现代/ntc 艺术/n 博物馆/n]/ns 参观/v 71 | ``` 72 | 73 | 与隐马尔可夫模型相比,已经能够正确识别地名了。 74 | 75 | 76 | 77 | ### 8.4 基于条件随机场序列标注的命名实体识别 78 | 79 | 之前我们就介绍过条件随机场模型,详细见: [6.条件随机场与序列标注](https://github.com/NLP-LOVE/Introduction-NLP/blob/master/chapter/6.%E6%9D%A1%E4%BB%B6%E9%9A%8F%E6%9C%BA%E5%9C%BA%E4%B8%8E%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.md) 80 | 81 | 条件随机场模型词性标注代码见(**自动下载 PKU 语料库**): crf_ner.py 82 | 83 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/crf_ner.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/crf_ner.py) 84 | 85 | 运行时间会比较长,结果如下: 86 | 87 | ``` 88 | 华北电力公司/nt 董事长/n 谭旭光/nr 和/c 秘书/n 胡花蕊/nr 来到/v [美国纽约/ns 现代/ntc 艺术/n 博物馆/n]/ns 参观/v 89 | ``` 90 | 91 | 得到了结果是一样的。 92 | 93 | 94 | 95 | ### 8.5 命名实体识别标准化评测 96 | 97 | 各个命名实体识别模块的准确率如何,并非只能通过几个句子主观感受。任何监督学习任务都有一套标准化评测方案,对于命名实体识别,按照惯例引入P、R 和 F1 评测指标。 98 | 99 | 在1998年1月《人民日报》语料库上的标准化评测结果如下: 100 | 101 | | 模型 | P | R | F1 | 102 | | -------------- | ----- | ----- | ----- | 103 | | 隐马尔可夫模型 | 79.01 | 30.14 | 43.64 | 104 | | 感知机 | 87.33 | 78.98 | 82.94 | 105 | | 条件随机场 | 87.93 | 73.75 | 80.22 | 106 | 107 | 值得一提的是,准确率与评测策略、特征模板、语料库规模息息相关。通常而言,当语料库较小时,应当使用简单的特征模板,以防止模型过拟合;当语料库较大时,则建议使用更多特征,以期更高的准确率。当特征模板固定时,往往是语料库越大,准确率越高。 108 | 109 | 110 | 111 | ### 8.6 自定义领域命名实体识别 112 | 113 | 以上我们接触的都是通用领域上的语料库,所含的命名实体仅限于人名、地名、机构名等。假设我们想要识别专门领域中的命名实体,这时,我们就要自定义领域的语料库了。 114 | 115 | 1. **标注领域命名实体识别语料库** 116 | 117 | 首先我们需要收集一些文本, 作为标注语料库的原料,称为**生语料**。由于我们的目标是识别文本中的战斗机名称或型号,所以生语料的来源应当是些军事网站的报道。在实际工程中,求由客户提出,则应当由该客户提供生语料。语料的量级越大越好,一般最低不少于数千个句子。 118 | 119 | 生语料准备就绪后,就可以开始标注了。对于命名实体识别语料库,若以词语和词性为特征的话,还需要标注分词边界和词性。不过我们不必从零开始标注,而可以在HanLP的标注基础上进行校正,这样工作量更小。 120 | 121 | 样本标注了数千个之后,生语料就被标注成了**熟语料**。下面代码自动下载语料库。 122 | 123 | 2. **训练领域模型** 124 | 125 | 选择感知机作为训练算法(**自动下载 战斗机 语料库**): plane_ner.py 126 | 127 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/plane_ner.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch08/plane_ner.py) 128 | 129 | 运行结果如下: 130 | 131 | ``` 132 | 下载 http://file.hankcs.com/corpus/plane-re.zip 到 /usr/local/lib/python3.7/site-packages/pyhanlp/static/data/test/plane-re.zip 133 | 100.00%, 0 MB, 552 KB/s, 还有 0 分 0 秒 134 | 米高扬/nrf 设计/v [米格/nr -/w 17/m PF/nx]/np :/w [米格/nr -/w 17/m]/np PF/n 型/k 战斗机/n 比/p [米格/nr -/w 17/m P/nx]/np 性能/n 更好/l 。/w 135 | [米格/nr -/w 阿帕奇/nrf -/w 666/m S/q]/np 横空出世/l 。/w 136 | ``` 137 | 138 | 这句话已经在语料库中出现过,能被正常识别并不意外。我们可以伪造一款“米格-阿帕奇-666S”战斗机,试试模型的繁华能力,发现依然能够正确识别。 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /chapter/9.信息抽取.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | - [9. 信息抽取](#9-信息抽取) 3 | - [9.1 新词提取](#91-新词提取) 4 | - [9.2 关键词提取](#92-关键词提取) 5 | - [9.3 短语提取](#93-短语提取) 6 | - [9.4 关键句提取](#94-关键句提取) 7 | - [9.5 总结](#95-总结) 8 | 9 | ## 9. 信息抽取 10 | 11 | 信息抽取是一个宽泛的概念,指的是从非结构化文本中提取结构化信息的一类技术。这类技术依然分为基于规则的正则匹配、有监督学习和无监督学习等各种实现方法。我们将使用一些简单实用的无监督学习方法。由于不需要标注语料库,所以可以利用海量的非结构化文本。 12 | 13 | 本章按照颗粒度从小到大的顺序,介绍抽取新词、关键词、关键短语和关键句的无监督学习方法。 14 | 15 | 16 | 17 | ### 9.1 新词提取 18 | 19 | 1. **概述** 20 | 21 | 新词是一个相对的概念,每个人的标准都不一样,所以我们这里定义: 词典之外的词语(OOV)称作**新词**。 22 | 23 | 新词的提取对中文分词而言具有重要的意义,因为语料库的标注成本很高。那么如何修订领域词典呢,此时,无监督的新词提取算法就体现了现实意义。 24 | 25 | 26 | 27 | 2. **基本原理** 28 | 29 | - 提取出大量文本(生语料)中的词语,无论新旧。 30 | - 用词典过滤掉已有的词语,于是得到新词。 31 | 32 | 步骤 2 很容易,关键是步骤 1,如何无监督的提取出文本中的单词。给定一段文本,随机取一个片段,如果这个片段左右的搭配很丰富,并且片段内部成分搭配很固定,则可以认为这是一个词。将这样的片段筛选出来,按照频次由高到低排序,排在前面的有很高概率是词。 33 | 34 | 如果文本足够大,再用通用的词典过滤掉“旧词”,就可以得到“新词”。 35 | 36 | 片段外部左右搭配的丰富程度,可以用**信息熵**来衡量,而片段内部搭配的固定程度可以用子序列的**互信息**来衡量。 37 | 38 | 39 | 40 | 3. **信息熵** 41 | 42 | 在信息论中,**信息熵**( entropy )指的是某条消息所含的信息量。它反映的是听说某个消息之后,关于该事件的不确定性的减少量。比如抛硬币之前,我们不知道“硬币正反”这个事件的结果。但是一旦有人告诉我们“硬币是正面”这条消息,我们对该次抛硬币事件的不确定性立即降为零,这种不确定性的减小量就是信息熵。公式如下: 43 | 44 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-18-47.gif) 45 | 46 | 给定字符串 S 作为词语备选,X 定义为该字符串左边可能出现的字符(左邻字),则称 H(X) 为 S 的左信息熵,类似的,定义右信息熵 H(Y),例如下列句子: 47 | 48 | > 两只**蝴蝶**飞啊飞 49 | > 50 | > 这些**蝴蝶**飞走了 51 | 52 | 那么对于字符串蝴蝶,它的左信息熵为1,而右信息熵为0。因为生语料库中蝴蝶的右邻字一定是飞。假如我们再收集一些句子,比如“蝴蝶效应”“蝴蝶蜕变”之类,就会观察到右信息熵会增大不少。 53 | 54 | 左右信息熵越大,说明字符串可能的搭配就越丰富,该字符串就是一个词的可能性就越大。 55 | 56 | 光考虑左右信息熵是不够的,比如“吃了一顿”“看了一遍”“睡了一晚”“去了一趟”中的了一的左右搭配也很丰富。为了更好的效果,我们还必须考虑词语内部片段的凝聚程度,这种凝聚程度由互信息衡量。 57 | 58 | 59 | 60 | 4. **互信息** 61 | 62 | **互信息**指的是两个离散型随机变量 X 与 Y 相关程度的度量,定义如下: 63 | 64 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-21-2.gif) 65 | 66 | 互信息的定义可以用韦恩图直观表达: 67 | 68 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_11-33-51.png) 69 | 70 | 71 | 72 | 其中,左圆圈表示H(X),右圆圈表示H(Y)。它们的并集是联合分布的信息熵H(X,Y),差集有多件嫡,交集就是互信息。可见互信息越大,两个随机变量的关联就越密切,或者说同时发生的可能性越大。 73 | 74 | 片段可能有多种组合方式,计算上可以选取所有组合方式中互信息最小的那一种为代表。有了左右信息熵和互信息之后,将两个指标低于一定阈值的片段过滤掉,剩下的片段按频次降序排序,截取最高频次的 N 个片段即完成了词语提取流程。 75 | 76 | 77 | 78 | 5. **实现** 79 | 80 | 我们用四大名著来提起100个高频词。 81 | 82 | 代码请见(**语料库自动下载**): extract_word.py 83 | 84 | [https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch09/extract_word.py](https://github.com/NLP-LOVE/Introduction-NLP/tree/master/code/ch09/extract_word.py) 85 | 86 | 运行结果如下: 87 | 88 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_11-53-59.png) 89 | 90 | 91 | 92 | 虽然我们没有在古典文学语料库上进行训练,但新词识别模块成功的识别出了麝月、高太尉等生僻词语,该模块也适用于微博等社交媒体的不规范文本。 93 | 94 | 95 | 96 | ### 9.2 关键词提取 97 | 98 | 词语颗粒度的信息抽取还存在另一个需求,即提取文章中重要的单词,称为**关键词提起**。关键词也是一个没有定量的标准,无法统一语料库,所以就可以利用无监督学习来完成。 99 | 100 | 分别介绍词频、TF-IDF和TextRank算法,单文档提起可以用词频和TextRank,多文档可以使用TF-IDF来提取关键词。 101 | 102 | 1. **词频统计** 103 | 104 | 关键词通常在文章中反复出现,为了解释关键词,作者通常会反复提及它们。通过统计文章中每种词语的词频并排序,可以初步获取部分关键词。 105 | 106 | 不过文章中反复出现的词语却不一定是关键词,例如“的”。所以在统计词频之前需要去掉停用词。 107 | 108 | 词频统计的流程一般是分词、停用词过滤、按词频取前 n 个。其中,求 m 个元素中前 n (n<=m) 大元素的问题通常通过最大堆解决,复杂度为 O(mlogn)。HanLP代码如下: 109 | 110 | ```python 111 | from pyhanlp import * 112 | 113 | TermFrequency = JClass('com.hankcs.hanlp.corpus.occurrence.TermFrequency') 114 | TermFrequencyCounter = JClass('com.hankcs.hanlp.mining.word.TermFrequencyCounter') 115 | 116 | if __name__ == '__main__': 117 | counter = TermFrequencyCounter() 118 | counter.add("加油加油中国队!") # 第一个文档 119 | counter.add("中国观众高呼加油中国") # 第二个文档 120 | for termFrequency in counter: # 遍历每个词与词频 121 | print("%s=%d" % (termFrequency.getTerm(), termFrequency.getFrequency())) 122 | print(counter.top(2)) # 取 top N 123 | 124 | # 根据词频提取关键词 125 | print('') 126 | print(TermFrequencyCounter.getKeywordList("女排夺冠,观众欢呼女排女排女排!", 3)) 127 | ``` 128 | 129 | 运行结果如下: 130 | 131 | ``` 132 | 中国=2 133 | 中国队=1 134 | 加油=3 135 | 观众=1 136 | 高呼=1 137 | [加油=3, 中国=2] 138 | 139 | [女排, 观众, 欢呼] 140 | ``` 141 | 142 | 用词频来提取关键词有一个缺陷,那就是高频词并不等价于关键词。比如在一个体育网站中,所有文章都是奥运会报道,导致“奥运会”词频最高,用户希望通过关键词看到每篇文章的特色。此时,TF-IDF 就派上用场了。 143 | 144 | 145 | 146 | 2. **TF-IDF** 147 | 148 | TF-IDF (Term Frequency-lnverse Document Frequency,词频-倒排文档频次)是信息检索中衡量一个词语重要程度的统计指标,被广泛用于Lucene、Solr、Elasticsearch 等搜索引擎。 149 | 150 | 相较于词频,TF-IDF 还综合考虑词语的稀有程度。在TF-IDF计算方法中,一个词语的重要程度不光正比于它在文档中的频次,还反比于有多少文档包含它。包含该词语的文档趣多,就说明它越宽泛, 越不能体现文档的特色。 正是因为需要考虑整个语料库或文档集合,所以TF-IDF在关键词提取时属于多文档方法。 151 | 152 | 计算公式如下: 153 | 154 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-22-2.gif) 155 | 156 | 其中,t 代表单词,d 代表文档,TF(t,d) 代表 t 在 d 中出现频次,DF(t) 代表有多少篇文档包含 t。DF 的导数称为IDF,这也是 TF-IDF 得名的由来。 157 | 158 | 当然,实际应用时做一些扩展,比如加一平滑、IDF取对数以防止浮点数下溢出。HanLP的示例如下: 159 | 160 | ```python 161 | from pyhanlp import * 162 | 163 | TfIdfCounter = JClass('com.hankcs.hanlp.mining.word.TfIdfCounter') 164 | 165 | if __name__ == '__main__': 166 | counter = TfIdfCounter() 167 | counter.add("《女排夺冠》", "女排北京奥运会夺冠") # 输入多篇文档 168 | counter.add("《羽毛球男单》", "北京奥运会的羽毛球男单决赛") 169 | counter.add("《女排》", "中国队女排夺北京奥运会金牌重返巅峰,观众欢呼女排女排女排!") 170 | counter.compute() # 输入完毕 171 | for id in counter.documents(): 172 | print(id + " : " + counter.getKeywordsOf(id, 3).toString()) # 根据每篇文档的TF-IDF提取关键词 173 | # 根据语料库已有的IDF信息为语料库之外的新文档提取关键词 174 | 175 | print('') 176 | print(counter.getKeywords("奥运会反兴奋剂", 2)) 177 | ``` 178 | 179 | 运行后如下: 180 | 181 | ``` 182 | 《女排》 : [女排=5.150728289807123, 重返=1.6931471805599454, 巅峰=1.6931471805599454] 183 | 《女排夺冠》 : [夺冠=1.6931471805599454, 女排=1.2876820724517808, 奥运会=1.0] 184 | 《羽毛球男单》 : [决赛=1.6931471805599454, 羽毛球=1.6931471805599454, 男单=1.6931471805599454] 185 | 186 | [反, 兴奋剂] 187 | ``` 188 | 189 | 观察输出结果,可以看出 TF-IDF 有效的避免了给予“奥运会”这个宽泛的词语过高的权重。 190 | 191 | TF-IDF在大型语料库上的统计类似于一种学习过程,假如我们没有这么大型的语料库或者存储IDF的内存,同时又想改善词频统计的效果该怎么办呢?此时可以使用TextRank算法。 192 | 193 | 194 | 195 | 3. **TextRank** 196 | 197 | TextRank 是 PageRank 在文本中的应用,PageRank是一种用于排序网页的随机算法,它的工作原理是将互联网看作有向图,互联网上的网页视作节点,节点 Vi 到节点 Vj 的超链接视作有向边,初始化时每个节点的权重 S(Vi) 都是1,以迭代的方式更新每个节点的权重。每次迭代权重的更新表达式如下: 198 | 199 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-24-59.gif) 200 | 201 | 其中 d 是一个介于 (0,1) 之间的常数因子,在PagRank中模拟用户点击链接从而跳出当前网站的概率,In(Vi) 表示链接到 Vi 的节点集合,Out(Vj) 表示从 Vj 出发链接到的节点集合。可见,开不是外链越多,网站的PageRank就越高。网站给别的网站做外链越多,每条外链的权重就越低。如果一个网站的外链都是这种权重很低的外链,那么PageRank也会下降,造成不良反应。正所谓物以类聚,垃圾网站推荐的链接往往也是垃圾网站。因此PageRank能够比较公正的反映网站的排名。 202 | 203 | 将 PageRank 应用到关键词提取,无非是将单词视作节点而已,另外,每个单词的外链来自自身前后固定大小的窗口内的所有单词。 204 | 205 | HanLP实现的代码如下: 206 | 207 | ```python 208 | from pyhanlp import * 209 | 210 | """ 关键词提取""" 211 | content = ( 212 | "程序员(英文Programmer)是从事程序开发、维护的专业人员。" 213 | "一般将程序员分为程序设计人员和程序编码人员," 214 | "但两者的界限并不非常清楚,特别是在中国。" 215 | "软件从业人员分为初级程序员、高级程序员、系统" 216 | "分析员和项目经理四大类。") 217 | 218 | TextRankKeyword = JClass("com.hankcs.hanlp.summary.TextRankKeyword") 219 | keyword_list = HanLP.extractKeyword(content, 5) 220 | print(keyword_list) 221 | ``` 222 | 223 | 运行结果如下: 224 | 225 | ``` 226 | [程序员, 程序, 分为, 人员, 软件] 227 | ``` 228 | 229 | 230 | 231 | ### 9.3 短语提取 232 | 233 | 在信息抽取领域,另一项重要的任务就是提取中文短语,也即固定多字词表达串的识别。短语提取经常用于搜索引擎的自动推荐,文档的简介生成等。 234 | 235 | 利用互信息和左右信息熵,我们可以轻松地将新词提取算法拓展到短语提取。只需将新词提取时的字符替换为单词, 字符串替换为单词列表即可。为了得到单词,我们依然需要进行中文分词。 大多数时候, 停用词对短语含义表达帮助不大,所以通常在分词后过滤掉。 236 | 237 | 代码如下: 238 | 239 | ```python 240 | from pyhanlp import * 241 | 242 | """ 短语提取""" 243 | text = ''' 244 | 算法工程师 245 | 算法(Algorithm)是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。 246 | 如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、 247 | 空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。算法工程师就是利用算法处理事物的人。 248 | 249 | 1职位简介 250 | 算法工程师是一个非常高端的职位; 251 | 专业要求:计算机、电子、通信、数学等相关专业; 252 | 学历要求:本科及其以上的学历,大多数是硕士学历及其以上; 253 | 语言要求:英语要求是熟练,基本上能阅读国外专业书刊; 254 | 必须掌握计算机相关知识,熟练使用仿真工具MATLAB等,必须会一门编程语言。 255 | 256 | 2研究方向 257 | 视频算法工程师、图像处理算法工程师、音频算法工程师 通信基带算法工程师 258 | 259 | 3目前国内外状况 260 | 目前国内从事算法研究的工程师不少,但是高级算法工程师却很少,是一个非常紧缺的专业工程师。 261 | 算法工程师根据研究领域来分主要有音频/视频算法处理、图像技术方面的二维信息算法处理和通信物理层、 262 | 雷达信号处理、生物医学信号处理等领域的一维信息算法处理。 263 | 在计算机音视频和图形图像技术等二维信息算法处理方面目前比较先进的视频处理算法:机器视觉成为此类算法研究的核心; 264 | 另外还有2D转3D算法(2D-to-3D conversion),去隔行算法(de-interlacing),运动估计运动补偿算法 265 | (Motion estimation/Motion Compensation),去噪算法(Noise Reduction),缩放算法(scaling), 266 | 锐化处理算法(Sharpness),超分辨率算法(Super Resolution) 手势识别(gesture recognition) 人脸识别(face recognition)。 267 | 在通信物理层等一维信息领域目前常用的算法:无线领域的RRM、RTT,传送领域的调制解调、信道均衡、信号检测、网络优化、信号分解等。 268 | 另外数据挖掘、互联网搜索算法也成为当今的热门方向。 269 | 算法工程师逐渐往人工智能方向发展。''' 270 | 271 | phrase_list = HanLP.extractPhrase(text, 5) 272 | print(phrase_list) 273 | 274 | ``` 275 | 276 | 运行结果如下: 277 | 278 | ``` 279 | [算法工程师, 算法处理, 一维信息, 算法研究, 信号处理] 280 | ``` 281 | 282 | 目前该模块只支持提取二元语法短语。在另一些场合,关键词或关键短语依然显得碎片化,不足以表达完整的主题。这时通常提取中心句子作为文章的简短摘要,而关键句的提取依然是基于 PageRank 的拓展。 283 | 284 | 285 | 286 | ### 9.4 关键句提取 287 | 288 | 由于一篇文章中几乎不可能出现相同的两个句子,所以朴素的 PageRank 在句子颗粒度上行不通。为了将 PageRank 利用到句子颗粒度上去,我们引人 BM25 算法衡量句子的相似度,改进链接的权重计算。这样窗口的中心句与相邻的句子间的链接变得有强有弱,相似的句子将得到更高的投票。而文章的中心句往往与其他解释说明的句子存在较高的相似性,这恰好为算法提供了落脚点。本节将先介绍BM25算法,后介绍TextRank在关键句提取中的应用。 289 | 290 | 1. **BM25** 291 | 292 | 在信息检索领域中,BM25 是TF-IDF的一种改进变种。TF-IDF衡量的是单个词语在文档中的重要程度,而在搜索引擎中,查询串(query)往往是由多个词语构成的。如何衡量多个词语与文档的关联程度,就是BM25所解决的问题。 293 | 294 | 形式化的定义 Q 为查询语句,由关键字 q1 到 qn 组成,D 为一个被检索的文档,BM25度量如下: 295 | 296 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-26-7.gif) 297 | 298 | 2. **TextRank** 299 | 300 | 有了BM25算法之后,将一个句子视作查询语句,相邻的句子视作待查询的文档,就能得到它们之间的相似度。以此相似度作为 PageRank 中的链接的权重,于是得到一种改进算法,称为TextRank。它的形式化计算方法如下: 301 | 302 | ![](https://github.com/NLP-LOVE/Introduction-NLP/raw/master/img/2020-2-12_17-26-59.gif) 303 | 304 | 其中,WS(Vi) 就是文档中第 i 个句子的得分,重复迭代该表达式若干次之后得到最终的分值,排序后输出前 N 个即得到关键句。代码如下: 305 | 306 | ```python 307 | from pyhanlp import * 308 | 309 | """自动摘要""" 310 | document = '''水利部水资源司司长陈明忠9月29日在国务院新闻办举行的新闻发布会上透露, 311 | 根据刚刚完成了水资源管理制度的考核,有部分省接近了红线的指标, 312 | 有部分省超过红线的指标。对一些超过红线的地方,陈明忠表示,对一些取用水项目进行区域的限批, 313 | 严格地进行水资源论证和取水许可的批准。''' 314 | 315 | TextRankSentence = JClass("com.hankcs.hanlp.summary.TextRankSentence") 316 | sentence_list = HanLP.extractSummary(document, 3) 317 | print(sentence_list) 318 | ``` 319 | 320 | 结果如下: 321 | 322 | ``` 323 | [严格地进行水资源论证和取水许可的批准, 水利部水资源司司长陈明忠9月29日在国务院新闻办举行的新闻发布会上透露, 有部分省超过红线的指标] 324 | ``` 325 | 326 | 327 | 328 | ### 9.5 总结 329 | 330 | 我们看到,新词提取与短语提取,关键词与关键句的提取,在原理上都是同一种算法在不同文本颗粒度上的应用。值得一提的是, 这些算法都不需要标注语料的参与,满足了人们“不劳而获”的欲望。然而必须指出的是,这些算法的效果非常有限。**对于同一个任务,监督学习方法的效果通常远远领先于无监督学习方法。** 331 | 332 | 333 | -------------------------------------------------------------------------------- /code/ch02/ngram_segment.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | from jpype import JString 3 | 4 | ## 加载 JAVA 类 5 | CorpusLoader = SafeJClass('com.hankcs.hanlp.corpus.document.CorpusLoader') 6 | NatureDictionaryMaker = SafeJClass('com.hankcs.hanlp.corpus.dictionary.NatureDictionaryMaker') 7 | CoreDictionary = LazyLoadingJClass('com.hankcs.hanlp.dictionary.CoreDictionary') 8 | WordNet = JClass('com.hankcs.hanlp.seg.common.WordNet') 9 | Vertex = JClass('com.hankcs.hanlp.seg.common.Vertex') 10 | 11 | 12 | def generate_wordnet(sent, trie): 13 | """ 14 | 生成词网 15 | :param sent: 句子 16 | :param trie: 词典(unigram) 17 | :return: 词网 18 | """ 19 | searcher = trie.getSearcher(JString(sent), 0) 20 | wordnet = WordNet(sent) 21 | while searcher.next(): 22 | wordnet.add(searcher.begin + 1, 23 | Vertex(sent[searcher.begin:searcher.begin + searcher.length], searcher.value, searcher.index)) 24 | # 原子分词,保证图连通 25 | vertexes = wordnet.getVertexes() 26 | i = 0 27 | while i < len(vertexes): 28 | if len(vertexes[i]) == 0: # 空白行 29 | j = i + 1 30 | for j in range(i + 1, len(vertexes) - 1): # 寻找第一个非空行 j 31 | if len(vertexes[j]): 32 | break 33 | wordnet.add(i, Vertex.newPunctuationInstance(sent[i - 1: j - 1])) # 填充[i, j)之间的空白行 34 | i = j 35 | else: 36 | i += len(vertexes[i][-1].realWord) 37 | 38 | return wordnet 39 | 40 | 41 | ## 维特比算法 42 | def viterbi(wordnet): 43 | nodes = wordnet.getVertexes() 44 | # 前向遍历 45 | for i in range(0, len(nodes) - 1): 46 | for node in nodes[i]: 47 | for to in nodes[i + len(node.realWord)]: 48 | to.updateFrom(node) # 根据距离公式计算节点距离,并维护最短路径上的前驱指针from 49 | # 后向回溯 50 | path = [] # 最短路径 51 | f = nodes[len(nodes) - 1].getFirst() # 从终点回溯 52 | while f: 53 | path.insert(0, f) 54 | f = f.getFrom() # 按前驱指针from回溯 55 | return [v.realWord for v in path] 56 | 57 | 58 | ## 训练n元语法模型 59 | def train_bigram(corpus_path, model_path): 60 | sents = CorpusLoader.convert2SentenceList(corpus_path) 61 | for sent in sents: 62 | for word in sent: 63 | word.setLabel("n") 64 | 65 | maker = NatureDictionaryMaker() 66 | maker.compute(sents) 67 | maker.saveTxtTo(model_path) # 会生成两个统计词频文件 68 | 69 | ## 加载 n元语法模型 70 | def load_bigram(model_path, sent): 71 | HanLP.Config.CoreDictionaryPath = model_path + ".txt" # unigram 72 | HanLP.Config.BiGramDictionaryPath = model_path + ".ngram.txt" # bigram 73 | 74 | wordnet = generate_wordnet(sent, CoreDictionary.trie) 75 | print(viterbi(wordnet)) 76 | 77 | 78 | 79 | corpus_path = 'my_cws_corpus.txt' # 语料库文件 80 | model_path = 'my_cws_model' # 模型保存路径 81 | sent = '货币和服务' # 需要分词的语句 82 | 83 | train_bigram(corpus_path, model_path) 84 | load_bigram(model_path, sent) 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /code/ch02/trie.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 节点类 4 | class Node(): 5 | def __init__(self) -> None: 6 | self.children = {} 7 | self.value = None 8 | 9 | # 增加节点 10 | def add_child(self, char, value, overwrite=False): 11 | child = self.children.get(char) 12 | if child is None: 13 | child = Node() # 创建子节点 14 | self.children[char] = child # 子节点赋值,字 -> 节点的映射 15 | 16 | if value is not None or overwrite: 17 | child.value = value # 节点上对应的词 18 | 19 | return child 20 | 21 | ## 字典树 继承节点类 22 | class Trie(Node): 23 | 24 | def __contains__(self, key): 25 | return self[key] is not None 26 | 27 | # 查询方法 28 | def __getitem__(self, key): 29 | state = self 30 | for char in key: 31 | state = state.children.get(char) 32 | if state is None: 33 | return None 34 | 35 | return state.value 36 | 37 | # 重载方法,使得类可以像对待dict那样操作字典树 38 | # 构建一个词的字典树 39 | def __setitem__(self, key, value): 40 | state = self 41 | for i, char in enumerate(key): 42 | if i < len(key) - 1: 43 | state = state.add_child(char, None) 44 | else: 45 | state = state.add_child(char, value, True) 46 | 47 | 48 | if __name__ == '__main__': 49 | trie = Trie() 50 | # 增 51 | trie['自然'] = 'nature' 52 | trie['自然人'] = 'human' 53 | trie['自然语言'] = 'language' 54 | trie['自语'] = 'talk to oneself' 55 | trie['入门'] = 'introduction' 56 | assert '自然' in trie 57 | # 删 58 | trie['自然'] = None 59 | assert '自然' not in trie 60 | # 改 61 | trie['自然语言'] = 'human language' 62 | assert trie['自然语言'] == 'human language' 63 | # 查 64 | assert trie['入门'] == 'introduction' 65 | print() 66 | -------------------------------------------------------------------------------- /code/ch03/ngram_segment.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | from jpype import JString 3 | 4 | ## 加载 JAVA 类 5 | CorpusLoader = SafeJClass('com.hankcs.hanlp.corpus.document.CorpusLoader') 6 | NatureDictionaryMaker = SafeJClass('com.hankcs.hanlp.corpus.dictionary.NatureDictionaryMaker') 7 | CoreDictionary = LazyLoadingJClass('com.hankcs.hanlp.dictionary.CoreDictionary') 8 | WordNet = JClass('com.hankcs.hanlp.seg.common.WordNet') 9 | Vertex = JClass('com.hankcs.hanlp.seg.common.Vertex') 10 | 11 | 12 | def generate_wordnet(sent, trie): 13 | """ 14 | 生成词网 15 | :param sent: 句子 16 | :param trie: 词典(unigram) 17 | :return: 词网 18 | """ 19 | searcher = trie.getSearcher(JString(sent), 0) 20 | wordnet = WordNet(sent) 21 | while searcher.next(): 22 | wordnet.add(searcher.begin + 1, 23 | Vertex(sent[searcher.begin:searcher.begin + searcher.length], searcher.value, searcher.index)) 24 | # 原子分词,保证图连通 25 | vertexes = wordnet.getVertexes() 26 | i = 0 27 | while i < len(vertexes): 28 | if len(vertexes[i]) == 0: # 空白行 29 | j = i + 1 30 | for j in range(i + 1, len(vertexes) - 1): # 寻找第一个非空行 j 31 | if len(vertexes[j]): 32 | break 33 | wordnet.add(i, Vertex.newPunctuationInstance(sent[i - 1: j - 1])) # 填充[i, j)之间的空白行 34 | i = j 35 | else: 36 | i += len(vertexes[i][-1].realWord) 37 | 38 | return wordnet 39 | 40 | 41 | ## 维特比算法 42 | def viterbi(wordnet): 43 | nodes = wordnet.getVertexes() 44 | # 前向遍历 45 | for i in range(0, len(nodes) - 1): 46 | for node in nodes[i]: 47 | for to in nodes[i + len(node.realWord)]: 48 | to.updateFrom(node) # 根据距离公式计算节点距离,并维护最短路径上的前驱指针from 49 | # 后向回溯 50 | path = [] # 最短路径 51 | f = nodes[len(nodes) - 1].getFirst() # 从终点回溯 52 | while f: 53 | path.insert(0, f) 54 | f = f.getFrom() # 按前驱指针from回溯 55 | return [v.realWord for v in path] 56 | 57 | 58 | ## 训练n元语法模型 59 | def train_bigram(corpus_path, model_path): 60 | sents = CorpusLoader.convert2SentenceList(corpus_path) 61 | for sent in sents: 62 | for word in sent: 63 | word.setLabel("n") 64 | 65 | maker = NatureDictionaryMaker() 66 | maker.compute(sents) 67 | maker.saveTxtTo(model_path) # 会生成两个统计词频文件 68 | 69 | ## 加载 n元语法模型 70 | def load_bigram(model_path, sent): 71 | HanLP.Config.CoreDictionaryPath = model_path + ".txt" # unigram 72 | HanLP.Config.BiGramDictionaryPath = model_path + ".ngram.txt" # bigram 73 | 74 | wordnet = generate_wordnet(sent, CoreDictionary.trie) 75 | print(viterbi(wordnet)) 76 | 77 | 78 | 79 | corpus_path = 'my_cws_corpus.txt' # 语料库文件 80 | model_path = 'my_cws_model' # 模型保存路径 81 | sent = '货币和服务' # 需要分词的语句 82 | 83 | train_bigram(corpus_path, model_path) 84 | load_bigram(model_path, sent) 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /code/ch04/hmm_cws.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import os 3 | 4 | from pyhanlp import * 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | ## 验证是否存在 MSR语料库,如果没有自动下载 19 | def ensure_data(data_name, data_url): 20 | root_path = test_data_path() 21 | dest_path = os.path.join(root_path, data_name) 22 | if os.path.exists(dest_path): 23 | return dest_path 24 | 25 | if data_url.endswith('.zip'): 26 | dest_path += '.zip' 27 | download(data_url, dest_path) 28 | if data_url.endswith('.zip'): 29 | with zipfile.ZipFile(dest_path, "r") as archive: 30 | archive.extractall(root_path) 31 | remove_file(dest_path) 32 | dest_path = dest_path[:-len('.zip')] 33 | return dest_path 34 | 35 | 36 | sighan05 = ensure_data('icwb2-data', 'http://sighan.cs.uchicago.edu/bakeoff2005/data/icwb2-data.zip') 37 | msr_train = os.path.join(sighan05, 'training', 'msr_training.utf8') 38 | 39 | ## =============================================== 40 | ## 以下开始中文分词 41 | 42 | FirstOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.FirstOrderHiddenMarkovModel') 43 | SecondOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.SecondOrderHiddenMarkovModel') 44 | HMMSegmenter = JClass('com.hankcs.hanlp.model.hmm.HMMSegmenter') 45 | 46 | 47 | def train(corpus, model): 48 | segmenter = HMMSegmenter(model) 49 | segmenter.train(corpus) 50 | return segmenter.toSegment() 51 | 52 | 53 | def evaluate(segment): 54 | result = CWSEvaluator.evaluate(segment, msr_test, msr_output, msr_gold, msr_dict) 55 | print(result) 56 | 57 | 58 | if __name__ == '__main__': 59 | segment = train(msr_train, FirstOrderHiddenMarkovModel()) 60 | HanLP.Config.ShowTermNature = False # 关闭现实词性 61 | 62 | print(segment.seg('商品和服务')) 63 | print(segment.seg('商品和货币')) 64 | -------------------------------------------------------------------------------- /code/ch05/classify_name.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 3 | 4 | import zipfile 5 | import os 6 | 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | ## 验证是否存在 cnname 人名性别语料库,如果没有自动下载 19 | def ensure_data(data_name, data_url): 20 | root_path = test_data_path() 21 | dest_path = os.path.join(root_path, data_name) 22 | if os.path.exists(dest_path): 23 | return dest_path 24 | 25 | if data_url.endswith('.zip'): 26 | dest_path += '.zip' 27 | download(data_url, dest_path) 28 | if data_url.endswith('.zip'): 29 | with zipfile.ZipFile(dest_path, "r") as archive: 30 | archive.extractall(root_path) 31 | remove_file(dest_path) 32 | dest_path = dest_path[:-len('.zip')] 33 | return dest_path 34 | 35 | 36 | ## =============================================== 37 | ## 以下开始中文分词 38 | 39 | 40 | PerceptronNameGenderClassifier = JClass('com.hankcs.hanlp.model.perceptron.PerceptronNameGenderClassifier') 41 | cnname = ensure_data('cnname', 'http://file.hankcs.com/corpus/cnname.zip') 42 | TRAINING_SET = os.path.join(cnname, 'train.csv') 43 | TESTING_SET = os.path.join(cnname, 'test.csv') 44 | MODEL = cnname + ".bin" 45 | 46 | 47 | def run_classifier(averaged_perceptron): 48 | print('=====%s=====' % ('平均感知机算法' if averaged_perceptron else '朴素感知机算法')) 49 | 50 | # 感知机模型 51 | classifier = PerceptronNameGenderClassifier() 52 | print('训练集准确率:', classifier.train(TRAINING_SET, 10, averaged_perceptron)) 53 | model = classifier.getModel() 54 | print('特征数量:', len(model.parameter)) 55 | # model.save(MODEL, model.featureMap.entrySet(), 0, True) 56 | # classifier = PerceptronNameGenderClassifier(MODEL) 57 | for name in "赵建军", "沈雁冰", "陆雪琪", "李冰冰": 58 | print('%s=%s' % (name, classifier.predict(name))) 59 | print('测试集准确率:', classifier.evaluate(TESTING_SET)) 60 | 61 | 62 | if __name__ == '__main__': 63 | run_classifier(False) 64 | run_classifier(True) 65 | -------------------------------------------------------------------------------- /code/ch05/perceptron_cws.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import os 3 | 4 | from pyhanlp import * 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | ## 验证是否存在 MSR语料库,如果没有自动下载 19 | def ensure_data(data_name, data_url): 20 | root_path = test_data_path() 21 | dest_path = os.path.join(root_path, data_name) 22 | if os.path.exists(dest_path): 23 | return dest_path 24 | 25 | if data_url.endswith('.zip'): 26 | dest_path += '.zip' 27 | download(data_url, dest_path) 28 | if data_url.endswith('.zip'): 29 | with zipfile.ZipFile(dest_path, "r") as archive: 30 | archive.extractall(root_path) 31 | remove_file(dest_path) 32 | dest_path = dest_path[:-len('.zip')] 33 | return dest_path 34 | 35 | 36 | sighan05 = ensure_data('icwb2-data', 'http://sighan.cs.uchicago.edu/bakeoff2005/data/icwb2-data.zip') 37 | msr_train = os.path.join(sighan05, 'training', 'msr_training.utf8') 38 | msr_model = os.path.join(test_data_path(), 'msr_cws') 39 | msr_test = os.path.join(sighan05, 'testing', 'msr_test.utf8') 40 | msr_output = os.path.join(sighan05, 'testing', 'msr_bigram_output.txt') 41 | msr_gold = os.path.join(sighan05, 'gold', 'msr_test_gold.utf8') 42 | msr_dict = os.path.join(sighan05, 'gold', 'msr_training_words.utf8') 43 | 44 | ## =============================================== 45 | ## 以下开始中文分词 46 | 47 | 48 | 49 | CWSTrainer = JClass('com.hankcs.hanlp.model.perceptron.CWSTrainer') 50 | CWSEvaluator = SafeJClass('com.hankcs.hanlp.seg.common.CWSEvaluator') 51 | HanLP.Config.ShowTermNature = False # 关闭显示词性 52 | 53 | 54 | def train_uncompressed_model(): 55 | model = CWSTrainer().train(msr_train, msr_train, msr_model, 0., 10, 8).getModel() # 训练模型 56 | model.save(msr_train, model.featureMap.entrySet(), 0, True) # 最后一个参数指定导出txt 57 | 58 | 59 | def train(): 60 | model = CWSTrainer().train(msr_train, msr_model).getModel() # 训练感知机模型 61 | segment = PerceptronLexicalAnalyzer(model).enableCustomDictionary(False) # 创建感知机分词器 62 | print(CWSEvaluator.evaluate(segment, msr_test, msr_output, msr_gold, msr_dict)) # 标准化评测 63 | return segment 64 | 65 | 66 | 67 | segment = train() 68 | sents = [ 69 | "王思斌,男,1949年10月生。", 70 | "山东桓台县起凤镇穆寨村妇女穆玲英", 71 | "现为中国艺术研究院中国文化研究所研究员。", 72 | "我们的父母重男轻女", 73 | "北京输气管道工程", 74 | ] 75 | for sent in sents: 76 | print(segment.seg(sent)) 77 | # train_uncompressed_model() -------------------------------------------------------------------------------- /code/ch06/evaluate_crf_cws.py: -------------------------------------------------------------------------------- 1 | 2 | from pyhanlp import * 3 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 4 | import os 5 | 6 | CRFSegmenter = JClass('com.hankcs.hanlp.model.crf.CRFSegmenter') 7 | CRFLexicalAnalyzer = JClass('com.hankcs.hanlp.model.crf.CRFLexicalAnalyzer') 8 | CWSEvaluator = SafeJClass('com.hankcs.hanlp.seg.common.CWSEvaluator') 9 | 10 | 11 | 12 | def test_data_path(): 13 | """ 14 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 15 | :return: 16 | """ 17 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 18 | if not os.path.isdir(data_path): 19 | os.mkdir(data_path) 20 | return data_path 21 | 22 | 23 | 24 | ## 验证是否存在 MSR语料库,如果没有自动下载 25 | def ensure_data(data_name, data_url): 26 | root_path = test_data_path() 27 | dest_path = os.path.join(root_path, data_name) 28 | if os.path.exists(dest_path): 29 | return dest_path 30 | 31 | if data_url.endswith('.zip'): 32 | dest_path += '.zip' 33 | download(data_url, dest_path) 34 | if data_url.endswith('.zip'): 35 | with zipfile.ZipFile(dest_path, "r") as archive: 36 | archive.extractall(root_path) 37 | remove_file(dest_path) 38 | dest_path = dest_path[:-len('.zip')] 39 | return dest_path 40 | 41 | sighan05 = ensure_data('icwb2-data', 'http://sighan.cs.uchicago.edu/bakeoff2005/data/icwb2-data.zip') 42 | msr_dict = os.path.join(sighan05, 'gold', 'msr_training_words.utf8') 43 | msr_train = os.path.join(sighan05, 'training', 'msr_training.utf8') 44 | msr_model = os.path.join(test_data_path(), 'msr_cws') 45 | msr_test = os.path.join(sighan05, 'testing', 'msr_test.utf8') 46 | msr_output = os.path.join(sighan05, 'testing', 'msr_bigram_output.txt') 47 | msr_gold = os.path.join(sighan05, 'gold', 'msr_test_gold.utf8') 48 | 49 | 50 | CRF_MODEL_PATH = test_data_path() + "/crf-cws-model" 51 | CRF_MODEL_TXT_PATH = test_data_path() + "/crf-cws-model.txt" 52 | 53 | 54 | ## =============================================== 55 | ## 以下开始 CRF 中文分词 56 | 57 | 58 | def train(corpus): 59 | segmenter = CRFSegmenter(None) # 创建 CRF 分词器 60 | segmenter.train(corpus, CRF_MODEL_PATH) 61 | return CRFLexicalAnalyzer(segmenter) 62 | # 训练完毕时,可传入txt格式的模型(不可传入CRF++的二进制模型,不兼容!) 63 | # return CRFLexicalAnalyzer(CRF_MODEL_TXT_PATH).enableCustomDictionary(False) 64 | 65 | 66 | segment = train(msr_train) 67 | print(CWSEvaluator.evaluate(segment, msr_test, msr_output, msr_gold, msr_dict)) # 标准化评测 68 | 69 | -------------------------------------------------------------------------------- /code/ch07/crf_pos.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyhanlp import * 4 | import zipfile 5 | import os 6 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | 19 | 20 | ## 验证是否存在 MSR语料库,如果没有自动下载 21 | def ensure_data(data_name, data_url): 22 | root_path = test_data_path() 23 | dest_path = os.path.join(root_path, data_name) 24 | if os.path.exists(dest_path): 25 | return dest_path 26 | 27 | if data_url.endswith('.zip'): 28 | dest_path += '.zip' 29 | download(data_url, dest_path) 30 | if data_url.endswith('.zip'): 31 | with zipfile.ZipFile(dest_path, "r") as archive: 32 | archive.extractall(root_path) 33 | remove_file(dest_path) 34 | dest_path = dest_path[:-len('.zip')] 35 | return dest_path 36 | 37 | 38 | ## 指定 PKU 语料库 39 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 40 | PKU199801 = os.path.join(PKU98, '199801.txt') 41 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 42 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 43 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 44 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 45 | 46 | 47 | ## =============================================== 48 | ## 以下开始 CRF 词性标注 49 | 50 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 51 | PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter') 52 | CRFPOSTagger = JClass('com.hankcs.hanlp.model.crf.CRFPOSTagger') 53 | 54 | 55 | 56 | def train_crf_pos(corpus): 57 | # 选项1.使用HanLP的Java API训练,慢 58 | tagger = CRFPOSTagger(None) # 创建空白标注器 59 | tagger.train(corpus, POS_MODEL) # 训练 60 | tagger = CRFPOSTagger(POS_MODEL) # 加载 61 | # 选项2.使用CRF++训练,HanLP加载。(训练命令由选项1给出) 62 | # tagger = CRFPOSTagger(POS_MODEL + ".txt") 63 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger) # 构造词法分析器,与感知机分词器结合,能同时进行分词和词性标注。 64 | print(analyzer.analyze("李狗蛋的希望是希望上学")) # 分词+词性标注 65 | print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels()) # 对词性进行翻译 66 | return tagger 67 | 68 | 69 | if __name__ == '__main__': 70 | tagger = train_crf_pos(PKU199801_TRAIN) 71 | -------------------------------------------------------------------------------- /code/ch07/hmm_pos.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyhanlp import * 4 | import zipfile 5 | import os 6 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | 19 | 20 | ## 验证是否存在 MSR语料库,如果没有自动下载 21 | def ensure_data(data_name, data_url): 22 | root_path = test_data_path() 23 | dest_path = os.path.join(root_path, data_name) 24 | if os.path.exists(dest_path): 25 | return dest_path 26 | 27 | if data_url.endswith('.zip'): 28 | dest_path += '.zip' 29 | download(data_url, dest_path) 30 | if data_url.endswith('.zip'): 31 | with zipfile.ZipFile(dest_path, "r") as archive: 32 | archive.extractall(root_path) 33 | remove_file(dest_path) 34 | dest_path = dest_path[:-len('.zip')] 35 | return dest_path 36 | 37 | 38 | ## 指定 PKU 语料库 39 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 40 | PKU199801 = os.path.join(PKU98, '199801.txt') 41 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 42 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 43 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 44 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 45 | 46 | 47 | ## =============================================== 48 | ## 以下开始 HMM 词性标注 49 | 50 | 51 | 52 | HMMPOSTagger = JClass('com.hankcs.hanlp.model.hmm.HMMPOSTagger') 53 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 54 | PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter') 55 | FirstOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.FirstOrderHiddenMarkovModel') 56 | SecondOrderHiddenMarkovModel = JClass('com.hankcs.hanlp.model.hmm.SecondOrderHiddenMarkovModel') 57 | 58 | def train_hmm_pos(corpus, model): 59 | tagger = HMMPOSTagger(model) # 创建词性标注器 60 | tagger.train(corpus) # 训练 61 | print(', '.join(tagger.tag("他", "的", "希望", "是", "希望", "上学"))) # 预测 62 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger) # 构造词法分析器,与感知机分词器结合,能同时进行分词和词性标注。 63 | print(analyzer.analyze("他的希望是希望上学")) # 分词+词性标注 64 | print(analyzer.analyze("他的希望是希望上学").translateLabels()) # 对词性进行翻译 65 | print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels()) # 对词性进行翻译 66 | return tagger 67 | 68 | 69 | if __name__ == '__main__': 70 | print('一阶隐马尔可夫模型:') 71 | tagger1 = train_hmm_pos(PKU199801_TRAIN, FirstOrderHiddenMarkovModel()) # 一阶隐马 72 | print('') 73 | print('二阶隐马尔可夫模型:') 74 | tagger = train_hmm_pos(PKU199801_TRAIN, SecondOrderHiddenMarkovModel()) # 二阶隐马 75 | -------------------------------------------------------------------------------- /code/ch07/perceptron_pos.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyhanlp import * 4 | import zipfile 5 | import os 6 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | 19 | 20 | ## 验证是否存在 MSR语料库,如果没有自动下载 21 | def ensure_data(data_name, data_url): 22 | root_path = test_data_path() 23 | dest_path = os.path.join(root_path, data_name) 24 | if os.path.exists(dest_path): 25 | return dest_path 26 | 27 | if data_url.endswith('.zip'): 28 | dest_path += '.zip' 29 | download(data_url, dest_path) 30 | if data_url.endswith('.zip'): 31 | with zipfile.ZipFile(dest_path, "r") as archive: 32 | archive.extractall(root_path) 33 | remove_file(dest_path) 34 | dest_path = dest_path[:-len('.zip')] 35 | return dest_path 36 | 37 | 38 | ## 指定 PKU 语料库 39 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 40 | PKU199801 = os.path.join(PKU98, '199801.txt') 41 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 42 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 43 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 44 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 45 | 46 | 47 | ## =============================================== 48 | ## 以下开始 感知机 词性标注 49 | 50 | 51 | 52 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 53 | PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter') 54 | POSTrainer = JClass('com.hankcs.hanlp.model.perceptron.POSTrainer') 55 | PerceptronPOSTagger = JClass('com.hankcs.hanlp.model.perceptron.PerceptronPOSTagger') 56 | 57 | def train_perceptron_pos(corpus): 58 | trainer = POSTrainer() 59 | trainer.train(corpus, POS_MODEL) # 训练感知机模型 60 | tagger = PerceptronPOSTagger(POS_MODEL) # 加载 61 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), tagger) # 构造词法分析器,与感知机分词器结合,能同时进行分词和词性标注。 62 | print(analyzer.analyze("李狗蛋的希望是希望上学")) # 分词+词性标注 63 | print(analyzer.analyze("李狗蛋的希望是希望上学").translateLabels()) # 对词性进行翻译 64 | return tagger 65 | 66 | 67 | if __name__ == '__main__': 68 | train_perceptron_pos(PKU199801_TRAIN) 69 | -------------------------------------------------------------------------------- /code/ch08/crf_ner.py: -------------------------------------------------------------------------------- 1 | 2 | from pyhanlp import * 3 | import zipfile 4 | import os 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | def test_data_path(): 8 | """ 9 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 10 | :return: 11 | """ 12 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 13 | if not os.path.isdir(data_path): 14 | os.mkdir(data_path) 15 | return data_path 16 | 17 | 18 | 19 | ## 验证是否存在 MSR语料库,如果没有自动下载 20 | def ensure_data(data_name, data_url): 21 | root_path = test_data_path() 22 | dest_path = os.path.join(root_path, data_name) 23 | if os.path.exists(dest_path): 24 | return dest_path 25 | 26 | if data_url.endswith('.zip'): 27 | dest_path += '.zip' 28 | download(data_url, dest_path) 29 | if data_url.endswith('.zip'): 30 | with zipfile.ZipFile(dest_path, "r") as archive: 31 | archive.extractall(root_path) 32 | remove_file(dest_path) 33 | dest_path = dest_path[:-len('.zip')] 34 | return dest_path 35 | 36 | 37 | ## 指定 PKU 语料库 38 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 39 | PKU199801 = os.path.join(PKU98, '199801.txt') 40 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 41 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 42 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 43 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 44 | 45 | 46 | ## =============================================== 47 | ## 以下开始 CRF 命名实体识别 48 | 49 | CRFNERecognizer = JClass('com.hankcs.hanlp.model.crf.CRFNERecognizer') 50 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 51 | Utility = JClass('com.hankcs.hanlp.model.perceptron.utility.Utility') 52 | 53 | 54 | 55 | def train(corpus, model): 56 | # 零参数的构造函数代表加载配置文件默认的模型,必须用null None 与之区分。 57 | recognizer = CRFNERecognizer(None) # 空白 58 | recognizer.train(corpus, model) 59 | return recognizer 60 | 61 | 62 | def test(recognizer): 63 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), PerceptronPOSTagger(), recognizer) 64 | print(analyzer.analyze("华北电力公司董事长谭旭光和秘书胡花蕊来到美国纽约现代艺术博物馆参观")) 65 | scores = Utility.evaluateNER(recognizer, PKU199801_TEST) 66 | Utility.printNERScore(scores) 67 | 68 | 69 | if __name__ == '__main__': 70 | recognizer = train(PKU199801_TRAIN, NER_MODEL) 71 | test(recognizer) 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /code/ch08/hmm_ner.py: -------------------------------------------------------------------------------- 1 | 2 | from pyhanlp import * 3 | import zipfile 4 | import os 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | def test_data_path(): 8 | """ 9 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 10 | :return: 11 | """ 12 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 13 | if not os.path.isdir(data_path): 14 | os.mkdir(data_path) 15 | return data_path 16 | 17 | 18 | 19 | ## 验证是否存在 MSR语料库,如果没有自动下载 20 | def ensure_data(data_name, data_url): 21 | root_path = test_data_path() 22 | dest_path = os.path.join(root_path, data_name) 23 | if os.path.exists(dest_path): 24 | return dest_path 25 | 26 | if data_url.endswith('.zip'): 27 | dest_path += '.zip' 28 | download(data_url, dest_path) 29 | if data_url.endswith('.zip'): 30 | with zipfile.ZipFile(dest_path, "r") as archive: 31 | archive.extractall(root_path) 32 | remove_file(dest_path) 33 | dest_path = dest_path[:-len('.zip')] 34 | return dest_path 35 | 36 | 37 | ## 指定 PKU 语料库 38 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 39 | PKU199801 = os.path.join(PKU98, '199801.txt') 40 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 41 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 42 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 43 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 44 | 45 | 46 | ## =============================================== 47 | ## 以下开始 HMM 命名实体识别 48 | 49 | HMMNERecognizer = JClass('com.hankcs.hanlp.model.hmm.HMMNERecognizer') 50 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 51 | PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter') 52 | PerceptronPOSTagger = JClass('com.hankcs.hanlp.model.perceptron.PerceptronPOSTagger') 53 | Utility = JClass('com.hankcs.hanlp.model.perceptron.utility.Utility') 54 | 55 | 56 | 57 | def train(corpus): 58 | recognizer = HMMNERecognizer() 59 | recognizer.train(corpus) # data/test/pku98/199801-train.txt 60 | return recognizer 61 | 62 | 63 | def test(recognizer): 64 | # 包装了感知机分词器和词性标注器的词法分析器 65 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), PerceptronPOSTagger(), recognizer) 66 | print(analyzer.analyze("华北电力公司董事长谭旭光和秘书胡花蕊来到美国纽约现代艺术博物馆参观")) 67 | scores = Utility.evaluateNER(recognizer, PKU199801_TEST) 68 | Utility.printNERScore(scores) 69 | 70 | 71 | if __name__ == '__main__': 72 | recognizer = train(PKU199801_TRAIN) 73 | test(recognizer) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /code/ch08/perceptron_ner.py: -------------------------------------------------------------------------------- 1 | 2 | from pyhanlp import * 3 | import zipfile 4 | import os 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | def test_data_path(): 8 | """ 9 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 10 | :return: 11 | """ 12 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 13 | if not os.path.isdir(data_path): 14 | os.mkdir(data_path) 15 | return data_path 16 | 17 | 18 | 19 | ## 验证是否存在 MSR语料库,如果没有自动下载 20 | def ensure_data(data_name, data_url): 21 | root_path = test_data_path() 22 | dest_path = os.path.join(root_path, data_name) 23 | if os.path.exists(dest_path): 24 | return dest_path 25 | 26 | if data_url.endswith('.zip'): 27 | dest_path += '.zip' 28 | download(data_url, dest_path) 29 | if data_url.endswith('.zip'): 30 | with zipfile.ZipFile(dest_path, "r") as archive: 31 | archive.extractall(root_path) 32 | remove_file(dest_path) 33 | dest_path = dest_path[:-len('.zip')] 34 | return dest_path 35 | 36 | 37 | ## 指定 PKU 语料库 38 | PKU98 = ensure_data("pku98", "http://file.hankcs.com/corpus/pku98.zip") 39 | PKU199801 = os.path.join(PKU98, '199801.txt') 40 | PKU199801_TRAIN = os.path.join(PKU98, '199801-train.txt') 41 | PKU199801_TEST = os.path.join(PKU98, '199801-test.txt') 42 | POS_MODEL = os.path.join(PKU98, 'pos.bin') 43 | NER_MODEL = os.path.join(PKU98, 'ner.bin') 44 | 45 | 46 | ## =============================================== 47 | ## 以下开始 感知机 命名实体识别 48 | 49 | NERTrainer = JClass('com.hankcs.hanlp.model.perceptron.NERTrainer') 50 | PerceptronNERecognizer = JClass('com.hankcs.hanlp.model.perceptron.PerceptronNERecognizer') 51 | PerceptronSegmenter = JClass('com.hankcs.hanlp.model.perceptron.PerceptronSegmenter') 52 | PerceptronPOSTagger = JClass('com.hankcs.hanlp.model.perceptron.PerceptronPOSTagger') 53 | Sentence = JClass('com.hankcs.hanlp.corpus.document.sentence.Sentence') 54 | AbstractLexicalAnalyzer = JClass('com.hankcs.hanlp.tokenizer.lexical.AbstractLexicalAnalyzer') 55 | Utility = JClass('com.hankcs.hanlp.model.perceptron.utility.Utility') 56 | 57 | 58 | 59 | def train(corpus, model): 60 | trainer = NERTrainer() 61 | return PerceptronNERecognizer(trainer.train(corpus, model).getModel()) 62 | 63 | 64 | def test(recognizer): 65 | # 包装了感知机分词器和词性标注器的词法分析器 66 | analyzer = AbstractLexicalAnalyzer(PerceptronSegmenter(), PerceptronPOSTagger(), recognizer) 67 | print(analyzer.analyze("华北电力公司董事长谭旭光和秘书胡花蕊来到美国纽约现代艺术博物馆参观")) 68 | scores = Utility.evaluateNER(recognizer, PKU199801_TEST) 69 | Utility.printNERScore(scores) 70 | 71 | 72 | if __name__ == '__main__': 73 | recognizer = train(PKU199801_TRAIN, NER_MODEL) 74 | test(recognizer) 75 | 76 | ## 支持在线学习 77 | # 创建了感知机词法分析器 78 | analyzer = PerceptronLexicalAnalyzer(PerceptronSegmenter(), PerceptronPOSTagger(), recognizer) # ① 79 | # 根据标注样本的字符串形式创建等价的 Sentence对象 80 | sentence = Sentence.create("与/c 特朗普/nr 通/v 电话/n 讨论/v [太空/s 探索/vn 技术/n 公司/n]/nt") # ② 81 | # 测试词法分析器对样本的分析结果是否与标注一致,若不一致重复在线学习,直到两者一致。 82 | while not analyzer.analyze(sentence.text()).equals(sentence): # ③ 83 | analyzer.learn(sentence) 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /code/ch09/extract_word.py: -------------------------------------------------------------------------------- 1 | 2 | from pyhanlp import * 3 | 4 | 5 | 6 | import zipfile 7 | import os 8 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 9 | 10 | def test_data_path(): 11 | """ 12 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 13 | :return: 14 | """ 15 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 16 | if not os.path.isdir(data_path): 17 | os.mkdir(data_path) 18 | return data_path 19 | 20 | 21 | 22 | ## 验证是否存在 MSR语料库,如果没有自动下载 23 | def ensure_data(data_name, data_url): 24 | root_path = test_data_path() 25 | dest_path = os.path.join(root_path, data_name) 26 | if os.path.exists(dest_path): 27 | return dest_path 28 | 29 | if data_url.endswith('.zip'): 30 | dest_path += '.zip' 31 | download(data_url, dest_path) 32 | if data_url.endswith('.zip'): 33 | with zipfile.ZipFile(dest_path, "r") as archive: 34 | archive.extractall(root_path) 35 | remove_file(dest_path) 36 | dest_path = dest_path[:-len('.zip')] 37 | return dest_path 38 | 39 | 40 | 41 | 42 | HLM_PATH = ensure_data("红楼梦.txt", "http://file.hankcs.com/corpus/红楼梦.zip") 43 | XYJ_PATH = ensure_data("西游记.txt", "http://file.hankcs.com/corpus/西游记.zip") 44 | SHZ_PATH = ensure_data("水浒传.txt", "http://file.hankcs.com/corpus/水浒传.zip") 45 | SAN_PATH = ensure_data("三国演义.txt", "http://file.hankcs.com/corpus/三国演义.zip") 46 | WEIBO_PATH = ensure_data("weibo-classification", "http://file.hankcs.com/corpus/weibo-classification.zip") 47 | 48 | 49 | def test_weibo(): 50 | for folder in os.listdir(WEIBO_PATH): 51 | print(folder) 52 | big_text = "" 53 | for file in os.listdir(os.path.join(WEIBO_PATH, folder)): 54 | with open(os.path.join(WEIBO_PATH, folder, file)) as src: 55 | big_text += "".join(src.readlines()) 56 | word_info_list = HanLP.extractWords(big_text, 100) 57 | print(word_info_list) 58 | 59 | 60 | def extract(corpus): 61 | print("%s 热词" % corpus) 62 | 63 | ## 参数如下 64 | # reader: 文本数据源 65 | # size: 控制返回多少个词 66 | # newWordsOnly: 为真时,程序将使用内部词库过滤掉“旧词”。 67 | # max_word_len: 控制识别结果中最长的词语长度 68 | # min_freq: 控制结果中词语的最低频率 69 | # min_entropy: 控制结果中词语的最低信息熵的值,一般取 0.5 左右,值越大,越短的词语就越容易提取 70 | # min_aggregation: 控制结果中词语的最低互信息值,一般取 50 到 200.值越大,越长的词语越容易提取 71 | word_info_list = HanLP.extractWords(IOUtil.newBufferedReader(corpus), 100) 72 | print(word_info_list) 73 | # print("%s 新词" % corpus) 74 | # word_info_list = HanLP.extractWords(IOUtil.newBufferedReader(corpus), 100, True) 75 | # print(word_info_list) 76 | 77 | 78 | if __name__ == '__main__': 79 | extract(HLM_PATH) 80 | extract(XYJ_PATH) 81 | extract(SHZ_PATH) 82 | extract(SAN_PATH) 83 | test_weibo() 84 | 85 | # 更多参数 86 | word_info_list = HanLP.extractWords(IOUtil.newBufferedReader(HLM_PATH), 100, True, 4, 0.0, .5, 100) 87 | print(word_info_list) 88 | 89 | -------------------------------------------------------------------------------- /code/ch11/load_text_classification_corpus.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | 3 | import zipfile 4 | import os 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | def test_data_path(): 8 | """ 9 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 10 | :return: 11 | """ 12 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 13 | if not os.path.isdir(data_path): 14 | os.mkdir(data_path) 15 | return data_path 16 | 17 | 18 | 19 | ## 验证是否存在 MSR语料库,如果没有自动下载 20 | def ensure_data(data_name, data_url): 21 | root_path = test_data_path() 22 | dest_path = os.path.join(root_path, data_name) 23 | if os.path.exists(dest_path): 24 | return dest_path 25 | 26 | if data_url.endswith('.zip'): 27 | dest_path += '.zip' 28 | download(data_url, dest_path) 29 | if data_url.endswith('.zip'): 30 | with zipfile.ZipFile(dest_path, "r") as archive: 31 | archive.extractall(root_path) 32 | remove_file(dest_path) 33 | dest_path = dest_path[:-len('.zip')] 34 | return dest_path 35 | 36 | 37 | sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip') 38 | 39 | 40 | 41 | 42 | AbstractDataSet = JClass('com.hankcs.hanlp.classification.corpus.AbstractDataSet') 43 | Document = JClass('com.hankcs.hanlp.classification.corpus.Document') 44 | FileDataSet = JClass('com.hankcs.hanlp.classification.corpus.FileDataSet') 45 | MemoryDataSet = JClass('com.hankcs.hanlp.classification.corpus.MemoryDataSet') 46 | 47 | # 演示加载文本分类语料库 48 | if __name__ == '__main__': 49 | dataSet = MemoryDataSet() # ①将数据集加载到内存中 50 | dataSet.load(sogou_corpus_path) # ②加载data/test/搜狗文本分类语料库迷你版 51 | dataSet.add("自然语言处理", "自然语言处理很有趣") # ③新增样本 52 | allClasses = dataSet.getCatalog().getCategories() # ④获取标注集 53 | print("标注集:%s" % (allClasses)) 54 | for document in dataSet.iterator(): 55 | print("第一篇文档的类别:" + allClasses.get(document.category)) 56 | break -------------------------------------------------------------------------------- /code/ch11/sentiment_analysis.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | 3 | import zipfile 4 | import os 5 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 6 | 7 | def test_data_path(): 8 | """ 9 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 10 | :return: 11 | """ 12 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 13 | if not os.path.isdir(data_path): 14 | os.mkdir(data_path) 15 | return data_path 16 | 17 | 18 | 19 | ## 验证是否存在语料库,如果没有自动下载 20 | def ensure_data(data_name, data_url): 21 | root_path = test_data_path() 22 | dest_path = os.path.join(root_path, data_name) 23 | if os.path.exists(dest_path): 24 | return dest_path 25 | 26 | if data_url.endswith('.zip'): 27 | dest_path += '.zip' 28 | download(data_url, dest_path) 29 | if data_url.endswith('.zip'): 30 | with zipfile.ZipFile(dest_path, "r") as archive: 31 | archive.extractall(root_path) 32 | remove_file(dest_path) 33 | dest_path = dest_path[:-len('.zip')] 34 | return dest_path 35 | 36 | 37 | # 中文情感挖掘语料-ChnSentiCorp 谭松波 38 | chn_senti_corp = ensure_data("ChnSentiCorp情感分析酒店评论", "http://file.hankcs.com/corpus/ChnSentiCorp.zip") 39 | 40 | 41 | ## =============================================== 42 | ## 以下开始 情感分析 43 | 44 | 45 | IClassifier = JClass('com.hankcs.hanlp.classification.classifiers.IClassifier') 46 | NaiveBayesClassifier = JClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier') 47 | 48 | 49 | 50 | def predict(classifier, text): 51 | print("《%s》 情感极性是 【%s】" % (text, classifier.classify(text))) 52 | 53 | 54 | if __name__ == '__main__': 55 | classifier = NaiveBayesClassifier() 56 | # 创建分类器,更高级的功能请参考IClassifier的接口定义 57 | classifier.train(chn_senti_corp) 58 | # 训练后的模型支持持久化,下次就不必训练了 59 | predict(classifier, "前台客房服务态度非常好!早餐很丰富,房价很干净。再接再厉!") 60 | predict(classifier, "结果大失所望,灯光昏暗,空间极其狭小,床垫质量恶劣,房间还伴着一股霉味。") 61 | predict(classifier, "可利用文本分类实现情感分析,效果不是不行") -------------------------------------------------------------------------------- /code/ch11/svm_text_classification.py: -------------------------------------------------------------------------------- 1 | from pyhanlp.static import STATIC_ROOT, download 2 | 3 | 4 | import zipfile 5 | import os 6 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 7 | 8 | def test_data_path(): 9 | """ 10 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 11 | :return: 12 | """ 13 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 14 | if not os.path.isdir(data_path): 15 | os.mkdir(data_path) 16 | return data_path 17 | 18 | 19 | 20 | ## 验证是否存在 MSR语料库,如果没有自动下载 21 | def ensure_data(data_name, data_url): 22 | root_path = test_data_path() 23 | dest_path = os.path.join(root_path, data_name) 24 | if os.path.exists(dest_path): 25 | return dest_path 26 | 27 | if data_url.endswith('.zip'): 28 | dest_path += '.zip' 29 | download(data_url, dest_path) 30 | if data_url.endswith('.zip'): 31 | with zipfile.ZipFile(dest_path, "r") as archive: 32 | archive.extractall(root_path) 33 | remove_file(dest_path) 34 | dest_path = dest_path[:-len('.zip')] 35 | return dest_path 36 | 37 | 38 | sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip') 39 | 40 | 41 | ## =============================================== 42 | ## 以下开始 支持向量机SVM 43 | 44 | 45 | 46 | def install_jar(name, url): 47 | dst = os.path.join(STATIC_ROOT, name) 48 | if os.path.isfile(dst): 49 | return dst 50 | download(url, dst) 51 | return dst 52 | 53 | 54 | install_jar('text-classification-svm-1.0.2.jar', 'http://file.hankcs.com/bin/text-classification-svm-1.0.2.jar') 55 | install_jar('liblinear-1.95.jar', 'http://file.hankcs.com/bin/liblinear-1.95.jar') 56 | from pyhanlp import * 57 | 58 | LinearSVMClassifier = SafeJClass('com.hankcs.hanlp.classification.classifiers.LinearSVMClassifier') 59 | IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil') 60 | 61 | 62 | def train_or_load_classifier(): 63 | model_path = sogou_corpus_path + '.svm.ser' 64 | if os.path.isfile(model_path): 65 | return LinearSVMClassifier(IOUtil.readObjectFrom(model_path)) 66 | classifier = LinearSVMClassifier() 67 | classifier.train(sogou_corpus_path) 68 | model = classifier.getModel() 69 | IOUtil.saveObjectTo(model, model_path) 70 | return LinearSVMClassifier(model) 71 | 72 | 73 | def predict(classifier, text): 74 | print("《%16s》\t属于分类\t【%s】" % (text, classifier.classify(text))) 75 | # 如需获取离散型随机变量的分布,请使用predict接口 76 | # print("《%16s》\t属于分类\t【%s】" % (text, classifier.predict(text))) 77 | 78 | 79 | if __name__ == '__main__': 80 | classifier = train_or_load_classifier() 81 | predict(classifier, "C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练") 82 | predict(classifier, "潜艇具有很强的战略威慑能力与实战能力") 83 | predict(classifier, "研究生考录模式亟待进一步专业化") 84 | predict(classifier, "如果真想用食物解压,建议可以食用燕麦") 85 | predict(classifier, "通用及其部分竞争对手目前正在考虑解决库存问题") -------------------------------------------------------------------------------- /code/ch11/text_classification.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pyhanlp import SafeJClass 4 | 5 | 6 | import zipfile 7 | import os 8 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 9 | 10 | def test_data_path(): 11 | """ 12 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 13 | :return: 14 | """ 15 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 16 | if not os.path.isdir(data_path): 17 | os.mkdir(data_path) 18 | return data_path 19 | 20 | 21 | 22 | ## 验证是否存在 MSR语料库,如果没有自动下载 23 | def ensure_data(data_name, data_url): 24 | root_path = test_data_path() 25 | dest_path = os.path.join(root_path, data_name) 26 | if os.path.exists(dest_path): 27 | return dest_path 28 | 29 | if data_url.endswith('.zip'): 30 | dest_path += '.zip' 31 | download(data_url, dest_path) 32 | if data_url.endswith('.zip'): 33 | with zipfile.ZipFile(dest_path, "r") as archive: 34 | archive.extractall(root_path) 35 | remove_file(dest_path) 36 | dest_path = dest_path[:-len('.zip')] 37 | return dest_path 38 | 39 | 40 | sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip') 41 | 42 | 43 | ## =============================================== 44 | ## 以下开始朴素贝叶斯分类 45 | 46 | 47 | 48 | NaiveBayesClassifier = SafeJClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier') 49 | IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil') 50 | 51 | 52 | def train_or_load_classifier(): 53 | model_path = sogou_corpus_path + '.ser' 54 | if os.path.isfile(model_path): 55 | return NaiveBayesClassifier(IOUtil.readObjectFrom(model_path)) 56 | classifier = NaiveBayesClassifier() # 朴素贝叶斯分类器 57 | classifier.train(sogou_corpus_path) 58 | model = classifier.getModel() 59 | IOUtil.saveObjectTo(model, model_path) 60 | return NaiveBayesClassifier(model) 61 | 62 | 63 | def predict(classifier, text): 64 | print("《%16s》\t属于分类\t【%s】" % (text, classifier.classify(text))) 65 | # 如需获取离散型随机变量的分布,请使用predict接口 66 | # print("《%16s》\t属于分类\t【%s】" % (text, classifier.predict(text))) 67 | 68 | 69 | if __name__ == '__main__': 70 | classifier = train_or_load_classifier() 71 | predict(classifier, "C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练") 72 | predict(classifier, "英国造航母耗时8年仍未服役 被中国速度远远甩在身后") 73 | predict(classifier, "研究生考录模式亟待进一步专业化") 74 | predict(classifier, "如果真想用食物解压,建议可以食用燕麦") 75 | predict(classifier, "通用及其部分竞争对手目前正在考虑解决库存问题") 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /code/ch11/text_classification_evaluation.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | import zipfile 3 | import os 4 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 5 | 6 | def test_data_path(): 7 | """ 8 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 9 | :return: 10 | """ 11 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 12 | if not os.path.isdir(data_path): 13 | os.mkdir(data_path) 14 | return data_path 15 | 16 | 17 | 18 | ## 验证是否存在语料库,如果没有自动下载 19 | def ensure_data(data_name, data_url): 20 | root_path = test_data_path() 21 | dest_path = os.path.join(root_path, data_name) 22 | if os.path.exists(dest_path): 23 | return dest_path 24 | 25 | if data_url.endswith('.zip'): 26 | dest_path += '.zip' 27 | download(data_url, dest_path) 28 | if data_url.endswith('.zip'): 29 | with zipfile.ZipFile(dest_path, "r") as archive: 30 | archive.extractall(root_path) 31 | remove_file(dest_path) 32 | dest_path = dest_path[:-len('.zip')] 33 | return dest_path 34 | 35 | 36 | sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip') 37 | 38 | 39 | ## =============================================== 40 | ## 以下开始 标准化评测 朴素贝叶斯 和 SVM 分类器 41 | 42 | IClassifier = JClass('com.hankcs.hanlp.classification.classifiers.IClassifier') 43 | NaiveBayesClassifier = JClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier') 44 | LinearSVMClassifier = JClass('com.hankcs.hanlp.classification.classifiers.LinearSVMClassifier') 45 | FileDataSet = JClass('com.hankcs.hanlp.classification.corpus.FileDataSet') 46 | IDataSet = JClass('com.hankcs.hanlp.classification.corpus.IDataSet') 47 | MemoryDataSet = JClass('com.hankcs.hanlp.classification.corpus.MemoryDataSet') 48 | Evaluator = JClass('com.hankcs.hanlp.classification.statistics.evaluations.Evaluator') 49 | FMeasure = JClass('com.hankcs.hanlp.classification.statistics.evaluations.FMeasure') 50 | BigramTokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.BigramTokenizer') 51 | HanLPTokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.HanLPTokenizer') 52 | ITokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.ITokenizer') 53 | 54 | 55 | def evaluate(classifier, tokenizer): 56 | training_corpus = FileDataSet().setTokenizer(tokenizer).load(sogou_corpus_path, "UTF-8", 0.9) 57 | classifier.train(training_corpus) 58 | testing_corpus = MemoryDataSet(classifier.getModel()).load(sogou_corpus_path, "UTF-8", -0.1) 59 | result = Evaluator.evaluate(classifier, testing_corpus) 60 | print(classifier.getClass().getSimpleName() + "+" + tokenizer.getClass().getSimpleName()) 61 | print(result) 62 | 63 | 64 | if __name__ == '__main__': 65 | evaluate(NaiveBayesClassifier(), HanLPTokenizer()) 66 | evaluate(NaiveBayesClassifier(), BigramTokenizer()) 67 | evaluate(LinearSVMClassifier(), HanLPTokenizer()) 68 | evaluate(LinearSVMClassifier(), BigramTokenizer()) -------------------------------------------------------------------------------- /code/ch12/train_parser.py: -------------------------------------------------------------------------------- 1 | from pyhanlp import * 2 | import zipfile 3 | import os 4 | from pyhanlp.static import download, remove_file, HANLP_DATA_PATH 5 | 6 | def test_data_path(): 7 | """ 8 | 获取测试数据路径,位于$root/data/test,根目录由配置文件指定。 9 | :return: 10 | """ 11 | data_path = os.path.join(HANLP_DATA_PATH, 'test') 12 | if not os.path.isdir(data_path): 13 | os.mkdir(data_path) 14 | return data_path 15 | 16 | 17 | 18 | ## 验证是否存在语料库,如果没有自动下载 19 | def ensure_data(data_name, data_url): 20 | root_path = test_data_path() 21 | dest_path = os.path.join(root_path, data_name) 22 | if os.path.exists(dest_path): 23 | return dest_path 24 | 25 | if data_url.endswith('.zip'): 26 | dest_path += '.zip' 27 | download(data_url, dest_path) 28 | if data_url.endswith('.zip'): 29 | with zipfile.ZipFile(dest_path, "r") as archive: 30 | archive.extractall(root_path) 31 | remove_file(dest_path) 32 | dest_path = dest_path[:-len('.zip')] 33 | return dest_path 34 | 35 | 36 | 37 | ## =============================================== 38 | ## 以下开始 依存句法分析 39 | 40 | KBeamArcEagerDependencyParser = JClass('com.hankcs.hanlp.dependency.perceptron.parser.KBeamArcEagerDependencyParser') 41 | CTB_ROOT = ensure_data("ctb8.0-dep", "http://file.hankcs.com/corpus/ctb8.0-dep.zip") 42 | CTB_TRAIN = CTB_ROOT + "/train.conll" 43 | CTB_DEV = CTB_ROOT + "/dev.conll" 44 | CTB_TEST = CTB_ROOT + "/test.conll" 45 | CTB_MODEL = CTB_ROOT + "/ctb.bin" 46 | BROWN_CLUSTER = ensure_data("wiki-cn-cluster.txt", "http://file.hankcs.com/corpus/wiki-cn-cluster.zip") 47 | 48 | 49 | parser = KBeamArcEagerDependencyParser.train(CTB_TRAIN, CTB_DEV, BROWN_CLUSTER, CTB_MODEL) 50 | print(parser.parse("人吃鱼")) 51 | score = parser.evaluate(CTB_TEST) 52 | print("UAS=%.1f LAS=%.1f\n" % (score[0], score[1])) 53 | -------------------------------------------------------------------------------- /data/dictionnary/my_cws_corpus.txt: -------------------------------------------------------------------------------- 1 | 商品 和 服务 2 | 商品 和服 物美价廉 3 | 服务 和 货币 -------------------------------------------------------------------------------- /data/dictionnary/stopwords.txt: -------------------------------------------------------------------------------- 1 |   2 |   3 | aboard 4 | about 5 | above 6 | according 7 | according to 8 | across 9 | afore 10 | after 11 | afterwards 12 | again 13 | against 14 | agin 15 | all 16 | almost 17 | alone 18 | along 19 | alongside 20 | already 21 | also 22 | although 23 | always 24 | am 25 | amid 26 | amidst 27 | among 28 | amongst 29 | amoungst 30 | amount 31 | an 32 | and 33 | anent 34 | another 35 | any 36 | anyhow 37 | anyone 38 | anything 39 | anyway 40 | anywhere 41 | approximately 42 | are 43 | around 44 | as 45 | asked 46 | aslant 47 | astride 48 | at 49 | athwart 50 | back 51 | bar 52 | be 53 | became 54 | because 55 | because of 56 | become 57 | becomes 58 | becoming 59 | been 60 | before 61 | beforehand 62 | behind 63 | being 64 | below 65 | beneath 66 | beside 67 | besides 68 | between 69 | betwixt 70 | beyond 71 | bill 72 | both 73 | bottom 74 | but 75 | by 76 | call 77 | called 78 | can 79 | cannot 80 | cant 81 | circa 82 | co 83 | computer 84 | con 85 | could 86 | couldnt 87 | cry 88 | currently 89 | dare 90 | de 91 | describe 92 | despite 93 | detail 94 | did 95 | do 96 | does 97 | done 98 | down 99 | dr 100 | due 101 | due to 102 | during 103 | e.g., 104 | each 105 | earlier 106 | eg 107 | eight 108 | either 109 | eleven 110 | else 111 | elsewhere 112 | empty 113 | enough 114 | ere 115 | etc 116 | even 117 | eventually 118 | ever 119 | every 120 | everyone 121 | everything 122 | everywhere 123 | except 124 | few 125 | fifteen 126 | fify 127 | fill 128 | find 129 | fire 130 | first 131 | five 132 | for 133 | former 134 | formerly 135 | forty 136 | found 137 | four 138 | from 139 | front 140 | full 141 | further 142 | get 143 | give 144 | go 145 | had 146 | has 147 | hasnt 148 | have 149 | he 150 | hence 151 | her 152 | here 153 | hereafter 154 | hereby 155 | herein 156 | hereupon 157 | hers 158 | herself 159 | him 160 | himself 161 | his 162 | how 163 | however 164 | hundred 165 | i 166 | ie 167 | if 168 | in 169 | inc 170 | indeed 171 | inside 172 | instead 173 | interest 174 | into 175 | is 176 | it 177 | its 178 | itself 179 | just 180 | keep 181 | last 182 | latter 183 | latterly 184 | least 185 | less 186 | like 187 | ltd 188 | made 189 | major 190 | many 191 | may 192 | maybe 193 | me 194 | meanwhile 195 | mid 196 | midst 197 | might 198 | mill 199 | mine 200 | minus 201 | more 202 | moreover 203 | most 204 | mostly 205 | move 206 | mr 207 | mrs 208 | ms 209 | much 210 | must 211 | my 212 | myself 213 | name 214 | namely 215 | near 216 | need 217 | neither 218 | net 219 | never 220 | nevertheless 221 | next 222 | nigh 223 | nigher 224 | nighest 225 | nine 226 | no 227 | nobody 228 | none 229 | noone 230 | nor 231 | not 232 | nothing 233 | notwithstanding 234 | now 235 | nowhere 236 | of 237 | off 238 | often 239 | on 240 | on to 241 | once 242 | one 243 | only 244 | onto 245 | or 246 | other 247 | others 248 | otherwise 249 | ought 250 | our 251 | ours 252 | ourselves 253 | out 254 | out of 255 | outside 256 | over 257 | own 258 | part 259 | partly 260 | past 261 | pending 262 | per 263 | perhaps 264 | please 265 | plus 266 | prior 267 | put 268 | qua 269 | rather 270 | re 271 | regarding 272 | round 273 | same 274 | sans 275 | save 276 | see 277 | seem 278 | seemed 279 | seeming 280 | seems 281 | separately 282 | serious 283 | seven 284 | several 285 | shall 286 | she 287 | should 288 | show 289 | side 290 | similarly 291 | since 292 | sincere 293 | six 294 | sixty 295 | so 296 | some 297 | somehow 298 | someone 299 | something 300 | sometime 301 | sometimes 302 | somewhere 303 | still 304 | such 305 | system 306 | take 307 | ten 308 | than 309 | that 310 | the 311 | their 312 | theirs 313 | them 314 | themselves 315 | then 316 | thence 317 | there 318 | thereafter 319 | thereby 320 | therefore 321 | therein 322 | thereupon 323 | these 324 | they 325 | thick 326 | thin 327 | third 328 | this 329 | those 330 | though 331 | three 332 | through 333 | throughout 334 | thru 335 | thus 336 | till 337 | to 338 | together 339 | too 340 | top 341 | toward 342 | towards 343 | twelve 344 | twenty 345 | two 346 | un 347 | under 348 | underneath 349 | unless 350 | unlike 351 | until 352 | unto 353 | up 354 | upon 355 | us 356 | versus 357 | very 358 | via 359 | vice 360 | volume 361 | was 362 | we 363 | well 364 | were 365 | what 366 | whatever 367 | whats 368 | when 369 | whence 370 | whenever 371 | where 372 | whereafter 373 | whereas 374 | whereby 375 | wherein 376 | whereupon 377 | wherever 378 | whether 379 | which 380 | while 381 | whither 382 | who 383 | whoever 384 | whole 385 | whom 386 | whose 387 | why 388 | will 389 | with 390 | within 391 | without 392 | would 393 | yesterday 394 | yet 395 | you 396 | your 397 | yours 398 | yourself 399 | yourselves 400 | { 401 | | 402 | } 403 | ~ 404 | ¡ 405 | ¦ 406 | « 407 | ­ 408 | ¯ 409 | ´ 410 | ¸ 411 | » 412 | ¿ 413 | ˇ 414 | ˉ 415 | ˊ 416 | ˋ 417 | ˜ 418 | ‐ 419 | —  420 | ― 421 | ‖ 422 | ‘ 423 | ’ 424 | “ 425 | ” 426 | • 427 | … 428 | ‹ 429 | › 430 | ∕ 431 | 、 432 | 。 433 | 〈 434 | 〉 435 | 《 436 | 》 437 | 「 438 | 」 439 | 『 440 | 』 441 | 【 442 | 】 443 | 〔 444 | 〕 445 | 〖 446 | 〗 447 | 〝 448 | 〞 449 | 一 450 | 一些 451 | 一何 452 | 一切 453 | 一则 454 | 一方面 455 | 一旦 456 | 一来 457 | 一样 458 | 一般 459 | 一转眼 460 | 万一 461 | 上 462 | 上下 463 | 下 464 | 不 465 | 不仅 466 | 不但 467 | 不光 468 | 不单 469 | 不只 470 | 不外乎 471 | 不如 472 | 不妨 473 | 不尽 474 | 不尽然 475 | 不得 476 | 不怕 477 | 不惟 478 | 不成 479 | 不拘 480 | 不料 481 | 不是 482 | 不比 483 | 不然 484 | 不特 485 | 不独 486 | 不管 487 | 不至于 488 | 不若 489 | 不论 490 | 不过 491 | 不问 492 | 与 493 | 与其 494 | 与其说 495 | 与否 496 | 与此同时 497 | 且 498 | 且不说 499 | 且说 500 | 两者 501 | 个 502 | 个别 503 | 临 504 | 为 505 | 为了 506 | 为止 507 | 为此 508 | 为着 509 | 乃 510 | 乃至 511 | 乃至于 512 | 么 513 | 之 514 | 之一 515 | 之所以 516 | 之类 517 | 乌乎 518 | 乎 519 | 乘 520 | 也 521 | 也好 522 | 也罢 523 | 了 524 | 二来 525 | 于 526 | 于是 527 | 于是乎 528 | 云云 529 | 云尔 530 | 些 531 | 亦 532 | 人 533 | 人们 534 | 人家 535 | 今 536 | 介于 537 | 仍 538 | 仍旧 539 | 从 540 | 从此 541 | 从而 542 | 他 543 | 他人 544 | 他们 545 | 以 546 | 以上 547 | 以为 548 | 以便 549 | 以免 550 | 以及 551 | 以故 552 | 以期 553 | 以来 554 | 以至 555 | 以至于 556 | 以致 557 | 们 558 | 任 559 | 任何 560 | 任凭 561 | 似的 562 | 但 563 | 但凡 564 | 但是 565 | 何 566 | 何以 567 | 何况 568 | 何处 569 | 何时 570 | 余外 571 | 作为 572 | 你 573 | 你们 574 | 使 575 | 使得 576 | 例如 577 | 依 578 | 依据 579 | 依照 580 | 便于 581 | 俺 582 | 俺们 583 | 倘 584 | 倘使 585 | 倘或 586 | 倘然 587 | 倘若 588 | 借 589 | 假使 590 | 假如 591 | 假若 592 | 傥然 593 | 像 594 | 儿 595 | 先不先 596 | 光是 597 | 全体 598 | 全部 599 | 兮 600 | 关于 601 | 其 602 | 其一 603 | 其中 604 | 其二 605 | 其他 606 | 其余 607 | 其它 608 | 其次 609 | 具体地说 610 | 具体说来 611 | 兼之 612 | 内 613 | 再其次 614 | 再则 615 | 再有 616 | 再者 617 | 再者说 618 | 再说 619 | 冒 620 | 冲 621 | 况且 622 | 几 623 | 几时 624 | 凡 625 | 凡是 626 | 凭 627 | 凭借 628 | 出于 629 | 出来 630 | 分别 631 | 则 632 | 则甚 633 | 别 634 | 别人 635 | 别处 636 | 别是 637 | 别的 638 | 别管 639 | 别说 640 | 到 641 | 前后 642 | 前此 643 | 前者 644 | 加之 645 | 加以 646 | 即 647 | 即令 648 | 即使 649 | 即便 650 | 即如 651 | 即或 652 | 即若 653 | 却 654 | 去 655 | 又 656 | 又及 657 | 及 658 | 及其 659 | 及至 660 | 反之 661 | 反而 662 | 反过来 663 | 反过来说 664 | 受到 665 | 另 666 | 另一方面 667 | 另外 668 | 另悉 669 | 只 670 | 只当 671 | 只怕 672 | 只是 673 | 只有 674 | 只消 675 | 只要 676 | 只限 677 | 叫 678 | 叮咚 679 | 可 680 | 可以 681 | 可是 682 | 可见 683 | 各 684 | 各个 685 | 各位 686 | 各种 687 | 各自 688 | 同 689 | 同时 690 | 后 691 | 后者 692 | 向 693 | 向使 694 | 向着 695 | 吓 696 | 吗 697 | 否则 698 | 吧 699 | 吧哒 700 | 吱 701 | 呀 702 | 呃 703 | 呕 704 | 呗 705 | 呜 706 | 呜呼 707 | 呢 708 | 呵 709 | 呵呵 710 | 呸 711 | 呼哧 712 | 咋 713 | 和 714 | 咚 715 | 咦 716 | 咧 717 | 咱 718 | 咱们 719 | 咳 720 | 哇 721 | 哈 722 | 哈哈 723 | 哉 724 | 哎 725 | 哎呀 726 | 哎哟 727 | 哗 728 | 哟 729 | 哦 730 | 哩 731 | 哪 732 | 哪些 733 | 哪怕 734 | 哼 735 | 哼唷 736 | 唉 737 | 唯有 738 | 啊 739 | 啐 740 | 啥 741 | 啦 742 | 啪达 743 | 啷当 744 | 喂 745 | 喏 746 | 喔唷 747 | 喽 748 | 嗡 749 | 嗡嗡 750 | 嗬 751 | 嗯 752 | 嗳 753 | 嘎 754 | 嘎登 755 | 嘘 756 | 嘛 757 | 嘻 758 | 嘿 759 | 嘿嘿 760 | 因 761 | 因为 762 | 因了 763 | 因此 764 | 因着 765 | 因而 766 | 固然 767 | 在 768 | 在下 769 | 在于 770 | 地 771 | 基于 772 | 处在 773 | 多 774 | 多么 775 | 多少 776 | 大 777 | 大家 778 | 她 779 | 她们 780 | 好 781 | 如 782 | 如上 783 | 如上所述 784 | 如下 785 | 如何 786 | 如其 787 | 如同 788 | 如是 789 | 如果 790 | 如此 791 | 如若 792 | 始而 793 | 孰料 794 | 孰知 795 | 宁 796 | 宁可 797 | 宁愿 798 | 宁肯 799 | 它 800 | 它们 801 | 对 802 | 对于 803 | 对待 804 | 对方 805 | 对比 806 | 将 807 | 小 808 | 尔 809 | 尔后 810 | 尔尔 811 | 尚且 812 | 就 813 | 就是 814 | 就是了 815 | 就是说 816 | 就算 817 | 就要 818 | 尽 819 | 尽管 820 | 尽管如此 821 | 岂但 822 | 己 823 | 已 824 | 已矣 825 | 巴 826 | 巴巴 827 | 并 828 | 并且 829 | 并非 830 | 庶乎 831 | 庶几 832 | 开外 833 | 开始 834 | 归 835 | 归齐 836 | 当 837 | 当地 838 | 当然 839 | 当着 840 | 彼 841 | 彼时 842 | 彼此 843 | 往 844 | 待 845 | 很 846 | 得 847 | 得了 848 | 怎 849 | 怎奈 850 | 总之 851 | 总的来看 852 | 总的来说 853 | 总的说来 854 | 总而言之 855 | 恰恰相反 856 | 您 857 | 惟其 858 | 慢说 859 | 我 860 | 我们 861 | 或 862 | 或则 863 | 或是 864 | 或曰 865 | 或者 866 | 截至 867 | 所 868 | 所以 869 | 所在 870 | 所幸 871 | 所有 872 | 才 873 | 才能 874 | 打 875 | 打从 876 | 把 877 | 抑或 878 | 拿 879 | 按 880 | 按照 881 | 换句话说 882 | 换言之 883 | 据 884 | 据此 885 | 接着 886 | 故 887 | 故此 888 | 故而 889 | 旁人 890 | 无 891 | 无宁 892 | 无论 893 | 既 894 | 既往 895 | 既是 896 | 既然 897 | 时候 898 | 是 899 | 是以 900 | 是的 901 | 曾 902 | 替 903 | 替代 904 | 最 905 | 有 906 | 有些 907 | 有关 908 | 有及 909 | 有时 910 | 有的 911 | 望 912 | 朝 913 | 朝着 914 | 本 915 | 本人 916 | 本地 917 | 本着 918 | 本身 919 | 来 920 | 来着 921 | 来自 922 | 来说 923 | 极了 924 | 果然 925 | 果真 926 | 某 927 | 某个 928 | 某些 929 | 某某 930 | 根据 931 | 欤 932 | 正值 933 | 正如 934 | 正巧 935 | 正是 936 | 此 937 | 此地 938 | 此处 939 | 此外 940 | 此时 941 | 此次 942 | 此间 943 | 毋宁 944 | 每 945 | 每当 946 | 比 947 | 比及 948 | 比如 949 | 比方 950 | 没奈何 951 | 沿 952 | 沿着 953 | 漫说 954 | 焉 955 | 然则 956 | 然后 957 | 然而 958 | 照 959 | 照着 960 | 犹且 961 | 犹自 962 | 甚且 963 | 甚么 964 | 甚或 965 | 甚而 966 | 甚至 967 | 甚至于 968 | 用 969 | 用来 970 | 由 971 | 由于 972 | 由是 973 | 由此 974 | 由此可见 975 | 的 976 | 的确 977 | 的话 978 | 直到 979 | 相对而言 980 | 省得 981 | 看 982 | 眨眼 983 | 着 984 | 着呢 985 | 矣 986 | 矣乎 987 | 矣哉 988 | 离 989 | 竟而 990 | 第 991 | 等 992 | 等到 993 | 等等 994 | 简言之 995 | 管 996 | 类如 997 | 紧接着 998 | 纵 999 | 纵令 1000 | 纵使 1001 | 纵然 1002 | 经 1003 | 经过 1004 | 结果 1005 | 给 1006 | 继之 1007 | 继后 1008 | 继而 1009 | 综上所述 1010 | 罢了 1011 | 者 1012 | 而 1013 | 而且 1014 | 而况 1015 | 而后 1016 | 而外 1017 | 而已 1018 | 而是 1019 | 而言 1020 | 能 1021 | 能否 1022 | 腾 1023 | 自 1024 | 自个儿 1025 | 自从 1026 | 自各儿 1027 | 自后 1028 | 自家 1029 | 自己 1030 | 自打 1031 | 自身 1032 | 至 1033 | 至于 1034 | 至今 1035 | 至若 1036 | 致 1037 | 般的 1038 | 若 1039 | 若夫 1040 | 若是 1041 | 若果 1042 | 若非 1043 | 莫不然 1044 | 莫如 1045 | 莫若 1046 | 虽 1047 | 虽则 1048 | 虽然 1049 | 虽说 1050 | 被 1051 | 要 1052 | 要不 1053 | 要不是 1054 | 要不然 1055 | 要么 1056 | 要是 1057 | 譬喻 1058 | 譬如 1059 | 让 1060 | 许多 1061 | 论 1062 | 设使 1063 | 设或 1064 | 设若 1065 | 诚如 1066 | 诚然 1067 | 该 1068 | 说来 1069 | 诸 1070 | 诸位 1071 | 诸如 1072 | 谁 1073 | 谁人 1074 | 谁料 1075 | 谁知 1076 | 贼死 1077 | 赖以 1078 | 赶 1079 | 起 1080 | 起见 1081 | 趁 1082 | 趁着 1083 | 越是 1084 | 距 1085 | 跟 1086 | 较 1087 | 较之 1088 | 边 1089 | 过 1090 | 还 1091 | 还是 1092 | 还有 1093 | 还要 1094 | 这 1095 | 这一来 1096 | 这个 1097 | 这么 1098 | 这么些 1099 | 这么样 1100 | 这么点儿 1101 | 这些 1102 | 这会儿 1103 | 这儿 1104 | 这就是说 1105 | 这时 1106 | 这样 1107 | 这次 1108 | 这般 1109 | 这边 1110 | 这里 1111 | 进而 1112 | 连 1113 | 连同 1114 | 逐步 1115 | 通过 1116 | 遵循 1117 | 遵照 1118 | 那 1119 | 那个 1120 | 那么 1121 | 那么些 1122 | 那么样 1123 | 那些 1124 | 那会儿 1125 | 那儿 1126 | 那时 1127 | 那样 1128 | 那般 1129 | 那边 1130 | 那里 1131 | 都 1132 | 鄙人 1133 | 鉴于 1134 | 针对 1135 | 阿 1136 | 除 1137 | 除了 1138 | 除外 1139 | 除开 1140 | 除此之外 1141 | 除非 1142 | 随 1143 | 随后 1144 | 随时 1145 | 随着 1146 | 难道说 1147 | 非但 1148 | 非徒 1149 | 非特 1150 | 非独 1151 | 靠 1152 | 顺 1153 | 顺着 1154 | 首先 1155 | ︰ 1156 | ︳ 1157 | ︴ 1158 | ︵ 1159 | ︶ 1160 | ︷ 1161 | ︸ 1162 | ︹ 1163 | ︺ 1164 | ︻ 1165 | ︼ 1166 | ︽ 1167 | ︾ 1168 | ︿ 1169 | ﹀ 1170 | ﹁ 1171 | ﹂ 1172 | ﹃ 1173 | ﹄ 1174 | ﹉ 1175 | ﹊ 1176 | ﹋ 1177 | ﹌ 1178 | ﹍ 1179 | ﹎ 1180 | ﹏ 1181 | ﹐ 1182 | ﹑ 1183 | ﹔ 1184 | ﹕ 1185 | ﹖ 1186 | ﹝ 1187 | ﹞ 1188 | ﹟ 1189 | ﹠ 1190 | ﹡ 1191 | ﹢ 1192 | ﹤ 1193 | ﹦ 1194 | ﹨ 1195 | ﹩ 1196 | ﹪ 1197 | ﹫ 1198 | ! 1199 | " 1200 | ' 1201 | ( 1202 | ) 1203 | , 1204 | : 1205 | ; 1206 | ? 1207 | _ 1208 |  ̄ -------------------------------------------------------------------------------- /img/ 2020-2-14_16-23-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/ 2020-2-14_16-23-23.png -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/1.png -------------------------------------------------------------------------------- /img/2020-2-10_10-33-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_10-33-28.png -------------------------------------------------------------------------------- /img/2020-2-10_10-35-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_10-35-32.png -------------------------------------------------------------------------------- /img/2020-2-10_11-27-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_11-27-16.png -------------------------------------------------------------------------------- /img/2020-2-10_16-51-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-51-16.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-52-37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-52-37.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-53-34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-53-34.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-54-37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-54-37.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-55-36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-55-36.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-56-26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-56-26.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-57-29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-57-29.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-58-24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-58-24.gif -------------------------------------------------------------------------------- /img/2020-2-10_16-59-19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_16-59-19.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-0-23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-0-23.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-1-14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-1-14.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-10-56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-10-56.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-11-56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-11-56.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-12-45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-12-45.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-13-35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-13-35.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-14-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-14-25.gif -------------------------------------------------------------------------------- /img/2020-2-10_17-2-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-10_17-2-4.gif -------------------------------------------------------------------------------- /img/2020-2-12_11-33-51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_11-33-51.png -------------------------------------------------------------------------------- /img/2020-2-12_11-53-59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_11-53-59.png -------------------------------------------------------------------------------- /img/2020-2-12_17-18-47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-18-47.gif -------------------------------------------------------------------------------- /img/2020-2-12_17-21-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-21-2.gif -------------------------------------------------------------------------------- /img/2020-2-12_17-22-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-22-2.gif -------------------------------------------------------------------------------- /img/2020-2-12_17-24-59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-24-59.gif -------------------------------------------------------------------------------- /img/2020-2-12_17-26-59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-26-59.gif -------------------------------------------------------------------------------- /img/2020-2-12_17-26-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_17-26-7.gif -------------------------------------------------------------------------------- /img/2020-2-12_22-32-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_22-32-31.png -------------------------------------------------------------------------------- /img/2020-2-12_22-36-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-12_22-36-21.png -------------------------------------------------------------------------------- /img/2020-2-13_14-26-31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-26-31.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-28-33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-28-33.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-29-43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-29-43.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-30-58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-30-58.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-32-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-32-1.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-33-24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-33-24.gif -------------------------------------------------------------------------------- /img/2020-2-13_14-47-11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_14-47-11.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-49-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-49-16.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-51-18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-51-18.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-52-26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-52-26.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-54-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-54-25.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-55-22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-55-22.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-56-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-56-25.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-57-21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-57-21.gif -------------------------------------------------------------------------------- /img/2020-2-13_15-58-29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-13_15-58-29.gif -------------------------------------------------------------------------------- /img/2020-2-14_16-27-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-14_16-27-56.png -------------------------------------------------------------------------------- /img/2020-2-14_17-55-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-14_17-55-50.png -------------------------------------------------------------------------------- /img/2020-2-14_17-58-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-14_17-58-48.png -------------------------------------------------------------------------------- /img/2020-2-14_18-37-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-14_18-37-41.png -------------------------------------------------------------------------------- /img/2020-2-18_16-43-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-18_16-43-14.png -------------------------------------------------------------------------------- /img/2020-2-18_17-10-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-18_17-10-42.png -------------------------------------------------------------------------------- /img/2020-2-18_17-12-27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-18_17-12-27.gif -------------------------------------------------------------------------------- /img/2020-2-19_21-57-32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-19_21-57-32.gif -------------------------------------------------------------------------------- /img/2020-2-19_21-58-57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-19_21-58-57.gif -------------------------------------------------------------------------------- /img/2020-2-3_10-50-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_10-50-30.png -------------------------------------------------------------------------------- /img/2020-2-3_11-17-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_11-17-38.png -------------------------------------------------------------------------------- /img/2020-2-3_12-26-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_12-26-11.png -------------------------------------------------------------------------------- /img/2020-2-3_12-8-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_12-8-55.png -------------------------------------------------------------------------------- /img/2020-2-3_13-40-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_13-40-56.png -------------------------------------------------------------------------------- /img/2020-2-3_13-52-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_13-52-12.png -------------------------------------------------------------------------------- /img/2020-2-3_16-0-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_16-0-25.png -------------------------------------------------------------------------------- /img/2020-2-3_19-41-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-3_19-41-15.png -------------------------------------------------------------------------------- /img/2020-2-4_12-44-46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_12-44-46.png -------------------------------------------------------------------------------- /img/2020-2-4_12-55-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_12-55-18.png -------------------------------------------------------------------------------- /img/2020-2-4_14-12-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_14-12-47.png -------------------------------------------------------------------------------- /img/2020-2-4_14-15-53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_14-15-53.png -------------------------------------------------------------------------------- /img/2020-2-4_14-45-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_14-45-35.png -------------------------------------------------------------------------------- /img/2020-2-4_14-46-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_14-46-52.png -------------------------------------------------------------------------------- /img/2020-2-4_16-49-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-4_16-49-15.png -------------------------------------------------------------------------------- /img/2020-2-5_10-32-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_10-32-10.png -------------------------------------------------------------------------------- /img/2020-2-5_10-57-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_10-57-30.png -------------------------------------------------------------------------------- /img/2020-2-5_11-17-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_11-17-22.png -------------------------------------------------------------------------------- /img/2020-2-5_11-21-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_11-21-45.png -------------------------------------------------------------------------------- /img/2020-2-5_15-13-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_15-13-17.png -------------------------------------------------------------------------------- /img/2020-2-5_17-49-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-49-0.png -------------------------------------------------------------------------------- /img/2020-2-5_17-51-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-51-33.png -------------------------------------------------------------------------------- /img/2020-2-5_17-53-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-53-12.png -------------------------------------------------------------------------------- /img/2020-2-5_17-55-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-55-12.png -------------------------------------------------------------------------------- /img/2020-2-5_17-56-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-56-39.png -------------------------------------------------------------------------------- /img/2020-2-5_17-57-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-57-48.png -------------------------------------------------------------------------------- /img/2020-2-5_17-59-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_17-59-12.png -------------------------------------------------------------------------------- /img/2020-2-5_18-1-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_18-1-47.png -------------------------------------------------------------------------------- /img/2020-2-5_18-11-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_18-11-13.png -------------------------------------------------------------------------------- /img/2020-2-5_18-22-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-5_18-22-57.png -------------------------------------------------------------------------------- /img/2020-2-6_10-36-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_10-36-13.png -------------------------------------------------------------------------------- /img/2020-2-6_10-55-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_10-55-28.png -------------------------------------------------------------------------------- /img/2020-2-6_11-11-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-11-0.png -------------------------------------------------------------------------------- /img/2020-2-6_11-23-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-23-10.png -------------------------------------------------------------------------------- /img/2020-2-6_11-4-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-4-22.png -------------------------------------------------------------------------------- /img/2020-2-6_11-43-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-43-39.png -------------------------------------------------------------------------------- /img/2020-2-6_11-53-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-53-47.png -------------------------------------------------------------------------------- /img/2020-2-6_11-57-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_11-57-24.png -------------------------------------------------------------------------------- /img/2020-2-6_13-19-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_13-19-18.png -------------------------------------------------------------------------------- /img/2020-2-6_13-3-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_13-3-50.png -------------------------------------------------------------------------------- /img/2020-2-6_14-9-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-6_14-9-11.png -------------------------------------------------------------------------------- /img/2020-2-7_13-2-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-7_13-2-10.png -------------------------------------------------------------------------------- /img/2020-2-7_13-2-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-7_13-2-39.png -------------------------------------------------------------------------------- /img/2020-2-7_17-57-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-7_17-57-2.png -------------------------------------------------------------------------------- /img/2020-2-7_18-4-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-7_18-4-34.png -------------------------------------------------------------------------------- /img/2020-2-8_0-41-12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-41-12.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-43-48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-43-48.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-45-19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-45-19.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-47-45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-47-45.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-49-50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-49-50.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-51-27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-51-27.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-52-50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-52-50.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-55-17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-55-17.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-56-28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-56-28.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-57-47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-57-47.gif -------------------------------------------------------------------------------- /img/2020-2-8_0-59-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_0-59-16.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-0-30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-0-30.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-1-42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-1-42.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-3-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-3-7.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-4-40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-4-40.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-6-54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-6-54.gif -------------------------------------------------------------------------------- /img/2020-2-8_1-8-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_1-8-16.gif -------------------------------------------------------------------------------- /img/2020-2-8_10-27-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_10-27-54.png -------------------------------------------------------------------------------- /img/2020-2-8_10-54-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_10-54-50.png -------------------------------------------------------------------------------- /img/2020-2-8_11-1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_11-1-3.png -------------------------------------------------------------------------------- /img/2020-2-8_11-28-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_11-28-24.png -------------------------------------------------------------------------------- /img/2020-2-8_11-43-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-8_11-43-39.png -------------------------------------------------------------------------------- /img/2020-2-9_10-51-43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-51-43.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-53-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-53-25.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-55-36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-55-36.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-56-36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-56-36.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-57-27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-57-27.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-58-23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-58-23.gif -------------------------------------------------------------------------------- /img/2020-2-9_10-59-14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_10-59-14.gif -------------------------------------------------------------------------------- /img/2020-2-9_11-0-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_11-0-5.gif -------------------------------------------------------------------------------- /img/2020-2-9_11-1-14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_11-1-14.gif -------------------------------------------------------------------------------- /img/2020-2-9_11-2-11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_11-2-11.gif -------------------------------------------------------------------------------- /img/2020-2-9_11-3-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_11-3-4.gif -------------------------------------------------------------------------------- /img/2020-2-9_11-3-55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_11-3-55.gif -------------------------------------------------------------------------------- /img/2020-2-9_15-14-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_15-14-17.png -------------------------------------------------------------------------------- /img/2020-2-9_15-46-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_15-46-25.png -------------------------------------------------------------------------------- /img/2020-2-9_16-7-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NLP-LOVE/Introduction-NLP/3a5c2c3fc9444a734b86fc820e8b50084756479d/img/2020-2-9_16-7-16.png -------------------------------------------------------------------------------- /img/s: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------