Python開發(fā)NLP應(yīng)用新思路:Streamlit與FastAPI雙劍合璧

Ⅰ. 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)點:

  • 性能快:高性能,可以和NodeJSGo相提并論;
  • 快速開發(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等等。

學習來源

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 常用概念: 自然語言處理(NLP) 數(shù)據(jù)挖掘 推薦算法 用戶畫像 知識圖譜 信息檢索 文本分類 常用技術(shù): 詞級別...
    御風之星閱讀 10,042評論 1 25
  • 前言 從本文開始,我們進入實戰(zhàn)部分。首先,我們按照中文自然語言處理流程的第一步獲取語料,然后重點進行中文分詞的學習...
    Element靜婷閱讀 962評論 0 0
  • 古時有俠客, 胸中裝著正義, 一劍刺穿喉嚨, 一劍割下頭顱, 電光火石閃耀, 劃出奪目的弧。 江河涌流, 黃沙掀土...
    山河一夢閱讀 508評論 11 14
  • Dear Ryan dear, 大約是在8月底的某一天。 今天麻麻用嬰兒車推著你,走在黃昏的小區(qū)里面。即將下線的落...
    susanolys閱讀 119評論 0 0

友情鏈接更多精彩內(nèi)容