Ⅰ. Hanlp
HanLP是一系列模型与算法组成的NLP工具包,目前HanLP 2.0版本正处于alpha测试阶段。我们可以使用该工具包快速构建分词、词性标注、命名实体识别、依存句法分析、语义依存分析等功能。
Hanlp 2.0 是直接支持 Java 和 Python接口的,这点与 Hanlp 1.x版本不同。1.x版本主要支持 java,使用python调用实际调用的是 pyhanlp相关接口。具体项目详情可以登录相关项目主页:
https://github.com/hankcs/HanLP
由于Hanlp 2.0基于 TensorFlow 2.1,因此需要先安装最新版 TensorFlow:
使用虚拟环境
1 2 | python3 -m venv .venv source .venv/bin/activate |
1 | pip install tensorflow==2.1 |
我用的是云服务器,没有安装GPU版本,直接安装会安装 TensorFlow 1.x 的版本,可以使用以下命令升级:
1 | pip install tensorflow --upgrade |
完成后可以测试一下是否安装成功:
1 2 | import tensorflow as tf print(tf.__version__) |
1 | '2.1.0' |
显示了正确的版本号,就可以继续安装 Hanlp:
1 | pip install hanlp |
Hanlp会自动根据已经安装的tensorflow版本进行安装,安装完成后同样测试一下:
1 2 | import hanlp print(hanlp.__version__) |
1 | '2.0.0-alpha.38' |
打印
1 | print(hanlp.pretrained.ALL) |
1 2 3 | {'CHNSENTICORP_BERT_BASE_ZH': 'https://file.hankcs.com/hanlp/classification/chnsenticorp_bert_base_20200104_164655.zip', ... 'TENCENT_AI_LAB_EMBEDDING': 'https://ai.tencent.com/ailab/nlp/data/Tencent_AILab_ChineseEmbedding.tar.gz#Tencent_AILab_ChineseEmbedding.txt'} |
这里只列出本文中要用到的四个模型:
| 模型名称 | 类型 |
|---|---|
| PKU_NAME_MERGED_SIX_MONTHS_CONVSEG | 中文分词 |
| rules.tokenize_english | 英文分词 |
| MSRA_NER_BERT_BASE_ZH | 中文命名实体识别 |
| CTB7_BIAFFINE_DEP_ZH | 中文依存句法分析 |
以下逐一说明:
1. 中文分词
引入模型:
1 2 | import hanlp tokenizer = hanlp.load('PKU_NAME_MERGED_SIX_MONTHS_CONVSEG') |
单句分词:
1 | tokenizer('商品和服务') |
1 | ['商品', '和', '服务'] |
批量并行分词:
1 2 3 | tokenizer(['萨哈夫说,伊拉克将同联合国销毁伊拉克大规模杀伤性武器特别委员会继续保持合作。', '上海华安工业(集团)公司董事长谭旭光和秘书张晚霞来到美国纽约现代艺术博物馆参观。', 'HanLP支援臺灣正體、香港繁體,具有新詞辨識能力的中文斷詞系統']) |
1 2 3 | [['萨哈夫', '说', ',', '伊拉克', '将', '同', '联合国', '销毁', '伊拉克', '大', '规模', '杀伤性', '武器', '特别', '委员会', '继续', '保持', '合作', '。'], ['上海', '华安', '工业', '(', '集团', ')', '公司', '董事长', '谭旭光', '和', '秘书', '张晚霞', '来到', '美国', '纽约', '现代', '艺术', '博物馆', '参观', '。'], ['HanLP', '支援', '臺灣', '正體', '、', '香港', '繁體', ',', '具有', '新詞', '辨識', '能力', '的', '中文', '斷詞', '系統']] |
2. 英文分词
英文本身是不需要分词的,因此这里直接使用基于规则的普通函数:
1 2 | tokenizer = hanlp.utils.rules.tokenize_english tokenizer("Don't go gentle into that good night.") |
1 | ['Do', "n't", 'go', 'gentle', 'into', 'that', 'good', 'night', '.'] |
3.中文命名实体识别
中文命名实体识别是字符级模型,输入使用
1 2 3 | recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH) recognizer([list('上海华安工业(集团)公司董事长谭旭光和秘书张晚霞来到美国纽约现代艺术博物馆参观。'), list('萨哈夫说,伊拉克将同联合国销毁伊拉克大规模杀伤性武器特别委员会继续保持合作。')]) |
1 2 3 4 5 6 7 8 | [[('上海华安工业(集团)公司', 'NT', 0, 12), ('谭旭光', 'NR', 15, 18), ('张晚霞', 'NR', 21, 24), ('美国', 'NS', 26, 28), ('纽约现代艺术博物馆', 'NS', 28, 37)], [('萨哈夫', 'NR', 0, 3), ('伊拉克', 'NS', 5, 8), ('联合国销毁伊拉克大规模杀伤性武器特别委员会', 'NT', 10, 31)]] |
4.中文依存句法分析
句法分析器的输入是单词列表及词性列表,输出是 CoNLL-X 格式
1 2 | syntactic_parser = hanlp.load(hanlp.pretrained.dep.CTB7_BIAFFINE_DEP_ZH) print(syntactic_parser([('蜡烛', 'NN'), ('两', 'CD'), ('头', 'NN'), ('烧', 'VV')])) |
1 2 3 4 | 1 蜡烛 _ NN _ _ 4 nsubj _ _ 2 两 _ CD _ _ 3 nummod _ _ 3 头 _ NN _ _ 4 dep _ _ 4 烧 _ VV _ _ 0 root _ _ |
由于预训练模型都较大,需要预留好足够的存储空间。
个人经验是最好在云服务器上下载,如果是下载到本地最好提前下载,并且晚上下载速度会快一些。
下面介绍 FastAPI 以及对 NLP 功能的接口封装。
Ⅱ. FastAPI
2005年就有人说过:“Python是一门web框架比关键字还多的语言“。近几年主要流行的Python Web框架是Django和Flask。不过Django较为繁重,Flask常常被人诟病其异步性能。
自从 Python3.5 中正式将协程做为底层技术引入之后,关于如何构建标准异步web框架的讨论就源源不绝。之后便诞生了一些基于 ASGI 的 web 框架,如:API Star、Uvicorn、Starlette、FastAPI等等。
其中FastAPI是个比较有趣的项目:它并非一个从零开始的项目,而是基于Starlette和Pydantic,其中Starlette又是基于Uvicorn的。
这里我们可以看一下FastAPI的源码,在dependencies文件夹下的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from fastapi.dependencies.models import Dependant, SecurityRequirement from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.utils import PYDANTIC_1, get_field_info, get_path_param_names from pydantic import BaseConfig, BaseModel, create_model from pydantic.error_wrappers import ErrorWrapper from pydantic.errors import MissingError from pydantic.utils import lenient_issubclass from starlette.background import BackgroundTasks from starlette.concurrency import run_in_threadpool from starlette.datastructures import FormData, Headers, QueryParams, UploadFile from starlette.requests import Request from starlette.responses import Response from starlette.websockets import WebSocket |
因此可以将FastAPI视做对Starlette的高级封装,并引入了Pydantic来对数据进行数据验证和设置管理。
单从性能上来说应该是 Uvicorn > Starlette > FastAPI:


项目官网列出了FastAPI的主要优点:
- 性能快:高性能,可以和NodeJS和Go相提并论;
- 快速开发:开发功能速度提高约200%至300%;
- 更少的Bug:减少40%开发人员容易引发的错误;
- 直观:完美的编辑支持,补全功能缩减了debugging的时间;
- 简单: 易于使用和学习,减少阅读文档的时间;
- 代码简洁:很大程度上减少代码重复。每个参数可以声明多个功能,减少bug的发生;
-
标准化:基于并完全兼容API的开发标准:
OpenAPI (以前称为Swagger )和JSON Schema 。
以下简单介绍以下FastAPI的使用。
1. 安装
直接使用
1 | pip install fastapi |
如果用于生产,那么你还需要一个ASGI服务器,如Uvicorn或Hypercorn:
1 | pip install uvicorn |
2. 创建FastAPI项目
创建main.py文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from fastapi import FastAPI # 实例化 app = FastAPI() # 创建一个get请求 @app.get("/") def first_fuc(): return {"Hello": "World"} # 再创建一个get请求 @app.get("/any/{item_id}") def second_fuc(item_id: int, q: str = None): return {"item_id": item_id, "q": q} |
如果代码需要用到异步async/await,使用async def,如下所示:
1 2 3 4 5 6 7 8 9 10 11 | from fastapi import FastAPI app = FastAPI() @app.get("/") async def first_fuc(): return {"Hello": "World"} @app.get("/any/{item_id}") async def second_fuc(item_id: int, q: str = None): return {"item_id": item_id, "q": q} |
3. 运行项目
运行服务器:
1 | uvicorn main:app --reload |
命令
uvicorn main:app --reload 指的是:
- main:main.py文件
- app:app = FastAPI() 在main.py内创建的对象。
- --reload:在代码更改后重新启动服务器。只有在开发时才使用这个参数。
在Terminal中启动成功后便会显示如下:

4. 检查项目
在浏览器中打开网址:http://127.0.0.1:8000
可以看见json格式的响应数据:


这样便创建了一个API:
- url:/ 和 / any / {item_id},两个url都可以接收HTTP请求。
- / 和 / any / {item_id} 都采用GET方式的HTTP请求方法
- / any / {item_id}包含路径参数item_id,格式为int
- / any / {item_id}还包含一个可选的参数q,格式为str
5. 交互的API文档
现在进入 http://127.0.0.1:8000/docs
便会得到自动的交互式API文档,该文档由 Swagger UI 提供。

6. 备用API文档
现在,转到 http://127.0.0.1:8000/redoc
这里是备用自动文档(由ReDoc提供)。

7. 下载API接口文档
如果需要提供API接口,那么只需要一行命令,即可下载api文件,一般保存为api.json
1 | curl -o api.json http://127.0.0.1:8000/openapi.json |
8. 对hanlp进行接口封装
我们需要调用 Hanl p的四个模型功能中,两个模型的输入是字符串,两个模型的输入是列表,此处统一为列表。
设计接口的输入为以下格式的 JSON 字符串:
1 2 3 4 | { "model_name": str, "input": list, } |
接口的返回输出为:
1 2 3 4 | { "success": bool, "rlt": list } |
下面直接贴出全部的后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #!/usr/bin/python # -*- coding: UTF-8 -*- from fastapi import FastAPI from pydantic import BaseModel import hanlp # 应用实例化 app = FastAPI() # 导入Hanlp相关模型 tokenizer_zh = hanlp.load('PKU_NAME_MERGED_SIX_MONTHS_CONVSEG') tokenizer_en = hanlp.utils.rules.tokenize_english recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH) syntactic_parser = hanlp.load(hanlp.pretrained.dep.CTB7_BIAFFINE_DEP_ZH) # 定义数据格式 class Data(BaseModel): model_name: str input: list # 中文分词接口 @app.post('/tok_zh') def split_cn(data_zh: Data): msg = data_zh.input rlt = [tokenizer_zh(x) for x in msg] return {'success': True, 'rlt': rlt} # 英文分词接口 @app.post('/tok_en') def split_en(data_en: Data): msg = data_en.input rlt = [tokenizer_en(x) for x in msg] return {'success': True, 'rlt': rlt} # 中文命名实体识别 @app.post('/ner') def ner(data_zh: Data): msg = data_zh.input rlt = [recognizer(x) for x in msg] return {'success': True, 'rlt': rlt} # 中文依存句法分析接口 @app.post('/parser') def parser(data_zh: Data): msg = data_zh.input rlt = syntactic_parser(msg) return {'success': True, 'rlt': rlt} |
如果希望程序支持异步执行,只需要将以上代码中
9. 在服务器上启动项目
由于使用了云服务器,所以在运行时要设置 ip 以及端口号:
1 | uvicorn main:app --reload --host 0.0.0.0 --port 80 |
其中
根据云服务器的安全策略,使用其他端口需要进入控制台,开放相应端口的外网访问权限。
更多的设置可以通过

然后在浏览器中输入

从图中可以看出一共有四个接口,都是
Ⅲ. Streamlit
要说2019年开源社区的宝藏项目,Streamlit绝对排的上号。官方宣称它是:The fastest way to build custom ML tools。
具体有多块呢?可见下图展示的例子,使用两百多行代码就制作了一个用于自动驾驶的车辆检测应用:

当然 Streamlit 相对于传统前端还是有很多的问题,比如:控件数量较少;可修改的自由度有限;没有传统的递归,每次请求都相当于重新运行整个脚本或者要使用
不过将其作为快速应用的开发,以及机器学习工具的配置式前端,都是十分不错的。毕竟相比花费一、两周制作一个完美的页面,一个小时就能展示模型能力更加吸引 AI 开发者。
同样直接贴出使用Streamlit搭建前端的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #!/usr/bin/python # -*- coding: UTF-8 -*- import streamlit as st import requests # 定义接口查询函数 def send2back(data_bin): rlt = requests.post('http://111.229.217.153:80/ner', json=data_bin).json() st.title("自然语言处理APP") html_tmp = """ <div style="background-color:tomato;padding:10px"> <h2 style="color:white;text-align:center;">基于Hanlp与FastAPI制作</h2> </div> """ st.markdown(html_tmp, unsafe_allow_html=True) st.markdown('---') option = st.selectbox('请选择你想要使用的功能:', ('', '中文分词', '英文分词', '中文命名实体识别', '中文依存句法分析')) content = st.text_input('请输入待分析的内容:') if option == '中文分词': if st.button("中文分词") & (content != ''): data_bin = {'model_name': 'tok_zh', 'input': [content]} rlt = requests.post('http://111.229.217.153:80/tok_zh', json=data_bin).json() st.text(rlt['rlt'][0]) elif option == '英文分词': if st.button("英文分词") & (content != ''): data_bin = {'model_name': 'tok_en', 'input': [content]} rlt = requests.post('http://111.229.217.153:80/tok_en', json=data_bin).json() st.text(rlt['rlt'][0]) elif option == '中文命名实体识别': if st.button("命名实体识别") & (content != ''): data_bin = {'model_name': 'ner', 'input': [list(content)]} rlt = requests.post('http://111.229.217.153:80/ner', json=data_bin).json() st.write(rlt) elif option == '中文依存句法分析': if st.button("依存句法分析") & (content != ''): data_bin = {'model_name': 'parser', 'input': [content]} rlt = requests.post('http://111.229.217.153:80/parser', json=data_bin).json() st.write(rlt) else: pass |
调用后台接口的是Python的HTTP库
将以上代码保存为文件
1 | streamlit run front.py |
运行成功后终端便会显示:

因为我是在本地电脑上运行,默认端口是8501,如果在服务器上运行,需要配置 ip 地址和端口号。详见官方文档:https://docs.streamlit.io/
在浏览器中输入:http://localhost:8501/
便可看到我们NLP应用的界面:

然后我们测试一下:

Streamlit的另外一个优点是:开发出来的应用可以基于设备自适应,因此在手机端浏览也会有较好的体验效果。有兴趣的小伙伴可以自行尝试。
Ⅳ. 进阶
以上简单介绍了使用 FastAPI以及 Streamlit开发一款基于Hanlp的自然语言处理应用的方法。
完整的开发流程还应包括反向代理设置,以及使用容器工具打包。这些在本文中不是重点,就不一一展开了。
本文提到的三种框架中:
- Hanlp 基于Tensorflow;
- FastAPI 基于 Scarlett和P ydantic;
- Streamlit 依赖于 tornado。
这揭示了一种APP开发的趋势:使用更高级的框架,尽可能地忽略掉底层的细节,以此达到快速开发和迭代的目的。
不过这并不意味着以后开发人员就不用弄懂web的各种细节。
相反,只有明白底层细节才能快速上手高级框架,并且不至于跌入高度抽象的陷阱当中。想要去个性化地定制系统或者提高性能,就必须深入了解其底层架构。
这里只是对这种思路的一种尝试,后面我会继续探索将更多新的框架纳入生产中的 pipeline。比方说 AllenNLp、FastAI以及流计算框架Flink等等。