Ⅰ. Hanlp
HanLP是一系列模型與算法組成的NLP工具包,目前HanLP 2.0版本正處于alpha測試階段。我們可以使用該工具包快速構(gòu)建分詞、詞性標注、命名實體識別、依存句法分析、語義依存分析等功能。
Hanlp 2.0 是直接支持 Java 和 Python接口的,這點與 Hanlp 1.x版本不同。1.x版本主要支持 java,使用python調(diào)用實際調(diào)用的是 pyhanlp相關(guān)接口。具體項目詳情可以登錄相關(guān)項目主頁:
https://github.com/hankcs/HanLP
由于Hanlp 2.0基于 TensorFlow 2.1,因此需要先安裝最新版 TensorFlow:
使用虛擬環(huán)境
python3 -m venv .venv
source .venv/bin/activate
pip install tensorflow==2.1
我用的是云服務(wù)器,沒有安裝GPU版本,直接安裝會安裝 TensorFlow 1.x 的版本,可以使用以下命令升級:
pip install tensorflow --upgrade
完成后可以測試一下是否安裝成功:
import tensorflow as tf
print(tf.__version__)
'2.1.0'
顯示了正確的版本號,就可以繼續(xù)安裝 Hanlp:
pip install hanlp
Hanlp會自動根據(jù)已經(jīng)安裝的tensorflow版本進行安裝,安裝完成后同樣測試一下:
import hanlp
print(hanlp.__version__)
'2.0.0-alpha.38'
打印 hanlp.pretrained.ALL 可以列出HanLP中的所有預(yù)訓練模型,一共有41個:
print(hanlp.pretrained.ALL)
{'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. 中文分詞
引入模型:
import hanlp
tokenizer = hanlp.load('PKU_NAME_MERGED_SIX_MONTHS_CONVSEG')
單句分詞:
tokenizer('商品和服務(wù)')
['商品', '和', '服務(wù)']
批量并行分詞:
tokenizer(['薩哈夫說,伊拉克將同聯(lián)合國銷毀伊拉克大規(guī)模殺傷性武器特別委員會繼續(xù)保持合作。',
'上海華安工業(yè)(集團)公司董事長譚旭光和秘書張晚霞來到美國紐約現(xiàn)代藝術(shù)博物館參觀。',
'HanLP支援臺灣正體、香港繁體,具有新詞辨識能力的中文斷詞系統(tǒng)'])
[['薩哈夫', '說', ',', '伊拉克', '將', '同', '聯(lián)合國', '銷毀', '伊拉克', '大', '規(guī)模', '殺傷性', '武器', '特別', '委員會', '繼續(xù)', '保持', '合作', '。'],
['上海', '華安', '工業(yè)', '(', '集團', ')', '公司', '董事長', '譚旭光', '和', '秘書', '張晚霞', '來到', '美國', '紐約', '現(xiàn)代', '藝術(shù)', '博物館', '參觀', '。'],
['HanLP', '支援', '臺灣', '正體', '、', '香港', '繁體', ',', '具有', '新詞', '辨識', '能力', '的', '中文', '斷詞', '系統(tǒng)']]
2. 英文分詞
英文本身是不需要分詞的,因此這里直接使用基于規(guī)則的普通函數(shù):
tokenizer = hanlp.utils.rules.tokenize_english
tokenizer("Don't go gentle into that good night.")
['Do', "n't", 'go', 'gentle', 'into', 'that', 'good', 'night', '.']
3.中文命名實體識別
中文命名實體識別是字符級模型,輸入使用 list將字符串轉(zhuǎn)換為字符列表。輸出格式為 (entity, type, begin, end)。
recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH)
recognizer([list('上海華安工業(yè)(集團)公司董事長譚旭光和秘書張晚霞來到美國紐約現(xiàn)代藝術(shù)博物館參觀。'),
list('薩哈夫說,伊拉克將同聯(lián)合國銷毀伊拉克大規(guī)模殺傷性武器特別委員會繼續(xù)保持合作。')])
[[('上海華安工業(yè)(集團)公司', 'NT', 0, 12),
('譚旭光', 'NR', 15, 18),
('張晚霞', 'NR', 21, 24),
('美國', 'NS', 26, 28),
('紐約現(xiàn)代藝術(shù)博物館', 'NS', 28, 37)],
[('薩哈夫', 'NR', 0, 3),
('伊拉克', 'NS', 5, 8),
('聯(lián)合國銷毀伊拉克大規(guī)模殺傷性武器特別委員會', 'NT', 10, 31)]]
4.中文依存句法分析
句法分析器的輸入是單詞列表及詞性列表,輸出是 CoNLL-X 格式 [^conllx] 的句法樹。
syntactic_parser = hanlp.load(hanlp.pretrained.dep.CTB7_BIAFFINE_DEP_ZH)
print(syntactic_parser([('蠟燭', 'NN'), ('兩', 'CD'), ('頭', 'NN'), ('燒', 'VV')]))
1 蠟燭 _ NN _ _ 4 nsubj _ _
2 兩 _ CD _ _ 3 nummod _ _
3 頭 _ NN _ _ 4 dep _ _
4 燒 _ VV _ _ 0 root _ _
由于預(yù)訓練模型都較大,需要預(yù)留好足夠的存儲空間。
個人經(jīng)驗是最好在云服務(wù)器上下載,如果是下載到本地最好提前下載,并且晚上下載速度會快一些。
下面介紹 FastAPI 以及對 NLP 功能的接口封裝。
Ⅱ. FastAPI
2005年就有人說過:“Python是一門web框架比關(guān)鍵字還多的語言“。近幾年主要流行的Python Web框架是Django和Flask。不過Django較為繁重,F(xiàn)lask常常被人詬病其異步性能。
自從 Python3.5 中正式將協(xié)程做為底層技術(shù)引入之后,關(guān)于如何構(gòu)建標準異步web框架的討論就源源不絕。之后便誕生了一些基于 ASGI 的 web 框架,如:API Star、Uvicorn、Starlette、FastAPI等等。
其中FastAPI是個比較有趣的項目:它并非一個從零開始的項目,而是基于Starlette和Pydantic,其中Starlette又是基于Uvicorn的。
這里我們可以看一下FastAPI的源碼,在dependencies文件夾下的 utils.py 引用部分:
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來對數(shù)據(jù)進行數(shù)據(jù)驗證和設(shè)置管理。
單從性能上來說應(yīng)該是 Uvicorn > Starlette > FastAPI:


項目官網(wǎng)列出了FastAPI的主要優(yōu)點:
- 性能快:高性能,可以和NodeJS和Go相提并論;
- 快速開發(fā):開發(fā)功能速度提高約200%至300%;
- 更少的Bug:減少40%開發(fā)人員容易引發(fā)的錯誤;
- 直觀:完美的編輯支持,補全功能縮減了debugging的時間;
- 簡單: 易于使用和學習,減少閱讀文檔的時間;
- 代碼簡潔:很大程度上減少代碼重復(fù)。每個參數(shù)可以聲明多個功能,減少bug的發(fā)生;
-
標準化:基于并完全兼容API的開發(fā)標準:
OpenAPI(以前稱為Swagger)和JSON Schema。
以下簡單介紹以下FastAPI的使用。
1. 安裝
直接使用 pip安裝:
pip install fastapi
如果用于生產(chǎn),那么你還需要一個ASGI服務(wù)器,如Uvicorn或Hypercorn:
pip install uvicorn
2. 創(chuàng)建FastAPI項目
創(chuàng)建main.py文件
from fastapi import FastAPI
# 實例化
app = FastAPI()
# 創(chuàng)建一個get請求
@app.get("/")
def first_fuc():
return {"Hello": "World"}
# 再創(chuàng)建一個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,如下所示:
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. 運行項目
運行服務(wù)器:
uvicorn main:app --reload
命令
uvicorn main:app --reload指的是:
- main:main.py文件
- app:app = FastAPI() 在main.py內(nèi)創(chuàng)建的對象。
- --reload:在代碼更改后重新啟動服務(wù)器。只有在開發(fā)時才使用這個參數(shù)。
在Terminal中啟動成功后便會顯示如下:

4. 檢查項目
在瀏覽器中打開網(wǎng)址:http://127.0.0.1:8000
可以看見json格式的響應(yīng)數(shù)據(jù):


這樣便創(chuàng)建了一個API:
- url:/ 和 / any / {item_id},兩個url都可以接收HTTP請求。
- / 和 / any / {item_id} 都采用GET方式的HTTP請求方法
- / any / {item_id}包含路徑參數(shù)item_id,格式為int
- / any / {item_id}還包含一個可選的參數(shù)q,格式為str
5. 交互的API文檔
現(xiàn)在進入 http://127.0.0.1:8000/docs
便會得到自動的交互式API文檔,該文檔由 Swagger UI 提供。

6. 備用API文檔
現(xiàn)在,轉(zhuǎn)到 http://127.0.0.1:8000/redoc
這里是備用自動文檔(由ReDoc提供)。

7. 下載API接口文檔
如果需要提供API接口,那么只需要一行命令,即可下載api文件,一般保存為api.json
curl -o api.json http://127.0.0.1:8000/openapi.json
8. 對hanlp進行接口封裝
我們需要調(diào)用 Hanl p的四個模型功能中,兩個模型的輸入是字符串,兩個模型的輸入是列表,此處統(tǒng)一為列表。
設(shè)計接口的輸入為以下格式的 JSON 字符串:
{
"model_name": str,
"input": list,
}
接口的返回輸出為:
{
"success": bool,
"rlt": list
}
下面直接貼出全部的后端代碼:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from fastapi import FastAPI
from pydantic import BaseModel
import hanlp
# 應(yīng)用實例化
app = FastAPI()
# 導入Hanlp相關(guān)模型
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)
# 定義數(shù)據(jù)格式
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}
如果希望程序支持異步執(zhí)行,只需要將以上代碼中 def 修改為 async def 即可。
9. 在服務(wù)器上啟動項目
由于使用了云服務(wù)器,所以在運行時要設(shè)置 ip 以及端口號:
uvicorn main:app --reload --host 0.0.0.0 --port 80
其中 --host 是設(shè)置ip地址,0.0.0.0意思就是使用本機的公網(wǎng) ip, 然后 --port:80 是指將端口號設(shè)置為80。
根據(jù)云服務(wù)器的安全策略,使用其他端口需要進入控制臺,開放相應(yīng)端口的外網(wǎng)訪問權(quán)限。
更多的設(shè)置可以通過 uvicorn --help 進行查詢。項目啟動后如果顯示以下內(nèi)容則表示啟動成功。

然后在瀏覽器中輸入 ip 地址/docs 進入到如下接口頁面便可以對接口進行調(diào)試:

從圖中可以看出一共有四個接口,都是 POST 類型的,后面有相對路徑和函數(shù)名。具體測試方法與上文相同,不再贅述。
Ⅲ. Streamlit
要說2019年開源社區(qū)的寶藏項目,Streamlit絕對排的上號。官方宣稱它是:The fastest way to build custom ML tools。
具體有多塊呢?可見下圖展示的例子,使用兩百多行代碼就制作了一個用于自動駕駛的車輛檢測應(yīng)用:

當然 Streamlit 相對于傳統(tǒng)前端還是有很多的問題,比如:控件數(shù)量較少;可修改的自由度有限;沒有傳統(tǒng)的遞歸,每次請求都相當于重新運行整個腳本或者要使用 cache 改進性能。
不過將其作為快速應(yīng)用的開發(fā),以及機器學習工具的配置式前端,都是十分不錯的。畢竟相比花費一、兩周制作一個完美的頁面,一個小時就能展示模型能力更加吸引 AI 開發(fā)者。
同樣直接貼出使用Streamlit搭建前端的代碼:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import streamlit as st
import requests
# 定義接口查詢函數(shù)
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('請輸入待分析的內(nèi)容:')
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
調(diào)用后臺接口的是Python的HTTP庫 requests。如果后臺使用了 async def異步函數(shù),那么建議將 requests 替換為同為異步的 aiohttp。
將以上代碼保存為文件 front.py,在終端中運行:
streamlit run front.py
運行成功后終端便會顯示:

因為我是在本地電腦上運行,默認端口是8501,如果在服務(wù)器上運行,需要配置 ip 地址和端口號。詳見官方文檔:https://docs.streamlit.io/
在瀏覽器中輸入:http://localhost:8501/
便可看到我們NLP應(yīng)用的界面:

然后我們測試一下:

Streamlit的另外一個優(yōu)點是:開發(fā)出來的應(yīng)用可以基于設(shè)備自適應(yīng),因此在手機端瀏覽也會有較好的體驗效果。有興趣的小伙伴可以自行嘗試。
Ⅳ. 進階
以上簡單介紹了使用 FastAPI以及 Streamlit開發(fā)一款基于Hanlp的自然語言處理應(yīng)用的方法。
完整的開發(fā)流程還應(yīng)包括反向代理設(shè)置,以及使用容器工具打包。這些在本文中不是重點,就不一一展開了。
本文提到的三種框架中:
- Hanlp 基于Tensorflow;
- FastAPI 基于 Scarlett和P ydantic;
- Streamlit 依賴于 tornado。
這揭示了一種APP開發(fā)的趨勢:使用更高級的框架,盡可能地忽略掉底層的細節(jié),以此達到快速開發(fā)和迭代的目的。
不過這并不意味著以后開發(fā)人員就不用弄懂web的各種細節(jié)。
相反,只有明白底層細節(jié)才能快速上手高級框架,并且不至于跌入高度抽象的陷阱當中。想要去個性化地定制系統(tǒng)或者提高性能,就必須深入了解其底層架構(gòu)。
這里只是對這種思路的一種嘗試,后面我會繼續(xù)探索將更多新的框架納入生產(chǎn)中的 pipeline。比方說 AllenNLp、FastAI以及流計算框架Flink等等。