更多技術細節參考上一篇專案,本篇主要側重本地端鏈路走通教學,提速提效:
基於Labelstudio的UIE半監督深度學習的智慧標註方案(雲端版),提效
更多內容參考文末碼源
自然語言處理資訊抽取智慧標註方案包括以下幾種:
基於規則的標註方案:通過編寫一系列規則來識別文字中的實體、關係等資訊,並將其標註。
基於機器學習的標註方案:通過訓練模型來自動識別文字中的實體、關係等資訊,並將其標註。
基於深度學習的標註方案:通過使用深度學習模型來自動識別文字中的實體、關係等資訊,並將其標註。
基於半監督學習的標註方案:通過使用少量的手工標註資料和大量的未標註資料來訓練模型,從而實現自動標註。
基於遠端監督的標註方案:利用已知的知識庫來自動標註文字中的實體、關係等資訊,從而減少手工標註的工作量。
本次專案主要講解的是基於半監督深度學習的標註方案。
安裝label-studio:
#建立名為label_studio的虛擬環境(範例的Python版本為3.8)
conda create -n labelstudio python=3.8
#啟用虛擬環境
conda activate labelstudio
#pip安裝label-studio (version=1.7.2)
pip install label-studio==1.7.2
在終端中依次執行下列命令:
#安裝label-studio機器學習後端,dirname為放程式碼的資料夾路徑
cd dirname
git clone https://github.com/heartexlabs/label-studio-ml-backend
#安裝label-studio及其依賴
cd label-studio-ml-backend
pip install -U -e .
#(可選) 安裝label-studio中examples執行所需的requirements
pip install -r label_studio_ml/examples/requirements.txt
建立與啟動模型:定義模型
在使用label-studio後端之前,要先定義好自己的訓練模型,模型的定義需要繼承自label-studio指定的類,具體可參考第四節。
建立後端模型:按照要求建立好的模型檔案的路徑假設為/Users/kyrol/Desktop/my_ml_backend.py,終端中執行以下命令:
# 初始化自定義機器學習後端
label-studio-ml init my_ml_backend --script /Users/kyrol/Desktop/my_ml_backend.py
#命令執行完畢會在當前資料夾下建立名為 my_ml_backend 的資料夾, 裡面放有 my_ml_backend.py, _wsgi.py 等內容。
#其中,_wsgi.py是要執行的python 主檔案,可以檢視裡面內容。注意:同時需要把依賴檔案放入my_ml_backend.py資料夾。
# 開啟機器學習後端服務
label-studio-ml start my_ml_backend
成功啟動後,在終端中可以看到 ML 後端的 URL。
開啟視覺化視窗,再開啟一個終端視窗,首先,啟用conda對應的環境;然後,cd 到label-studio程式碼所在路徑;然後,執行以下終端命令,啟動視覺化的視窗:
在啟動自定義機器學習後端之後,就可以將其新增到 Label Studio 專案中。
具體步驟如下:
設定訓練資料檔案
訓練模型
訓練後的模型會儲存在 my_ml_backend 資料夾中以數位命名的資料夾內。
具體步驟如下所示:
選擇 Use for interactive preannotations 開啟互動式預註釋功能(可選)
點選 Validate and Save
若要使用互動式預註釋功能,需在新增 ML Backend 時開啟 Use for interactive preannotations 選項。如未開啟,可點選 Edit 進行編輯。然後隨便點選一個資料,label studio 就會悄悄執行剛才的 ml backend 生成新的標註了。
檢視預標註好的資料,如有必要,對標註進行修改。
本例中,預標註的結果中『NBA』沒有被識別出來,手動新增實體將其標註為『組織』。
本例中,預標註的結果中將『人名』實體『三月』錯標註為『時間』實體,手動進行修改。
修改完成後,或預標註的結果已經符合預期,點選 Submit 提交標註結果。
在標註了至少一項任務之後,就可以開始訓練模型了。
點選 Settings - Machine Learning - Start Training 開始訓練。
動態圖為參照方便展示這個流程。
**然後返回啟動 label-studio-ml-backend 的視窗可以看到訓練的流程啟動了。 **
from pprint import pprint
from paddlenlp import Taskflow
schema = ['地名', '人名', '組織', '時間', '產品', '價格', '天氣']
ie = Taskflow('information_extraction', schema=schema)
pprint(ie("2K 與 Gearbox Software 宣佈,《小緹娜的奇幻之地》將於 6 月 24 日凌晨 1 點登入 Steam,此前 PC 平臺為 Epic 限時獨佔。在限定期間內,Steam 玩家可以在 Steam 入手《小緹娜的奇幻之地》,並在 2022 年 7 月 8 日前享有獲得黃金英雄鎧甲包。"))
[{'產品': [{'end': 35,
'probability': 0.8595664902550801,
'start': 25,
'text': '《小緹娜的奇幻之地》'}],
'地名': [{'end': 34,
'probability': 0.30077351606695757,
'start': 26,
'text': '小緹娜的奇幻之地'},
{'end': 117,
'probability': 0.5250433327469182,
'start': 109,
'text': '小緹娜的奇幻之地'}],
'時間': [{'end': 52,
'probability': 0.8796518890642702,
'start': 38,
'text': '6 月 24 日凌晨 1 點'}],
'組織': [{'end': 2,
'probability': 0.6914450625760651,
'start': 0,
'text': '2K'},
{'end': 93,
'probability': 0.5971815528872604,
'start': 88,
'text': 'Steam'},
{'end': 75,
'probability': 0.5844303540013343,
'start': 71,
'text': 'Epic'},
{'end': 105,
'probability': 0.45620707081511114,
'start': 100,
'text': 'Steam'},
{'end': 60,
'probability': 0.5683007420326334,
'start': 55,
'text': 'Steam'},
{'end': 21,
'probability': 0.6797917390407271,
'start': 5,
'text': 'Gearbox Software'}]}]
pprint(ie("近日,量子計算專家、ACM計算獎得主Scott Aaronson通過部落格宣佈,將於本週離開得克薩斯大學奧斯汀分校(UT Austin)一年,並加盟人工智慧研究公司OpenAI。"))
[{'人名': [{'end': 23,
'probability': 0.664236391748247,
'start': 18,
'text': 'Scott'},
{'end': 32,
'probability': 0.479811241610971,
'start': 24,
'text': 'Aaronson'}],
'時間': [{'end': 43,
'probability': 0.8424644728072508,
'start': 41,
'text': '本週'}],
'組織': [{'end': 87,
'probability': 0.5550909248934985,
'start': 81,
'text': 'OpenAI'}]}]
使用預設模型 uie-base 進行命名實體識別,效果還不錯,大多數的命名實體被識別出來了,但依然存在部分實體未被識別出,部分文字被誤識別等問題。比如 "Scott Aaronson" 被識別為了兩個人名,比如 "得克薩斯大學奧斯汀分校" 沒有被識別出來。為提升識別效果,將通過標註少量資料對模型進行微調。
在終端中執行以下指令碼,將 label studio 匯出的資料檔案格式轉換成 doccano 匯出的資料檔案格式。
python labelstudio2doccano.py --labelstudio_file dataset/label-studio.json
引數說明:
!python doccano.py \
--doccano_file dataset/doccano_ext.jsonl \
--task_type "ext" \
--save_dir ./data \
--splits 0.8 0.2 0
引數說明:
注:
在終端中執行以下指令碼進行模型微調。
# 然後在終端中執行以下指令碼,對 doccano 格式的資料檔案進行處理,執行後會在 /home/data 目錄下生成訓練/驗證/測試集檔案。
!python finetune.py \
--train_path "./data/train.txt" \
--dev_path "./data/dev.txt" \
--save_dir "./checkpoint" \
--learning_rate 1e-5 \
--batch_size 32 \
--max_seq_len 512 \
--num_epochs 100 \
--model "uie-base" \
--seed 1000 \
--logging_steps 100 \
--valid_steps 100 \
--device "gpu"
[2023-03-31 16:14:53,465] [ INFO] - global step 600, epoch: 67, loss: 0.00012, speed: 3.76 step/s
[2023-03-31 16:14:53,908] [ INFO] - Evaluation precision: 0.93478, recall: 0.79630, F1: 0.86000
[2023-03-31 16:15:20,328] [ INFO] - global step 700, epoch: 78, loss: 0.00010, speed: 3.79 step/s
[2023-03-31 16:15:20,777] [ INFO] - Evaluation precision: 0.93750, recall: 0.83333, F1: 0.88235
[2023-03-31 16:15:46,992] [ INFO] - global step 800, epoch: 89, loss: 0.00009, speed: 3.81 step/s
[2023-03-31 16:15:47,439] [ INFO] - Evaluation precision: 0.91667, recall: 0.81481, F1: 0.86275
[2023-03-31 16:16:13,316] [ INFO] - global step 900, epoch: 100, loss: 0.00008, speed: 3.86 step/s
[2023-03-31 16:16:13,758] [ INFO] - Evaluation precision: 0.95833, recall: 0.85185, F1: 0.90196
結果展示:
引數說明:
在終端中執行以下指令碼進行模型評估。
輸出範例:
引數說明:
debug
模式輸出範例:
!python evaluate.py \
--model_path ./checkpoint/model_best \
--test_path ./data/dev.txt \
--batch_size 16 \
--max_seq_len 512
[2023-03-31 16:16:18,503] [ INFO] - We are using <class 'paddlenlp.transformers.ernie.tokenizer.ErnieTokenizer'> to load './checkpoint/model_best'.
W0331 16:16:18.530714 1666 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W0331 16:16:18.533171 1666 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
[2023-03-31 16:16:24,551] [ INFO] - -----------------------------
[2023-03-31 16:16:24,551] [ INFO] - Class Name: all_classes
[2023-03-31 16:16:24,551] [ INFO] - Evaluation Precision: 0.95918 | Recall: 0.87037 | F1: 0.91262
!python evaluate.py \
--model_path ./checkpoint/model_best \
--test_path ./data/dev.txt \
--debug
[2023-03-31 16:16:29,246] [ INFO] - We are using <class 'paddlenlp.transformers.ernie.tokenizer.ErnieTokenizer'> to load './checkpoint/model_best'.
W0331 16:16:29.278601 1707 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W0331 16:16:29.281224 1707 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
[2023-03-31 16:16:34,944] [ INFO] - -----------------------------
[2023-03-31 16:16:34,944] [ INFO] - Class Name: 時間
[2023-03-31 16:16:34,944] [ INFO] - Evaluation Precision: 1.00000 | Recall: 0.90000 | F1: 0.94737
[2023-03-31 16:16:34,998] [ INFO] - -----------------------------
[2023-03-31 16:16:34,998] [ INFO] - Class Name: 地名
[2023-03-31 16:16:34,998] [ INFO] - Evaluation Precision: 0.95833 | Recall: 0.85185 | F1: 0.90196
[2023-03-31 16:16:35,022] [ INFO] - -----------------------------
[2023-03-31 16:16:35,022] [ INFO] - Class Name: 產品
[2023-03-31 16:16:35,022] [ INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2023-03-31 16:16:35,048] [ INFO] - -----------------------------
[2023-03-31 16:16:35,048] [ INFO] - Class Name: 組織
[2023-03-31 16:16:35,049] [ INFO] - Evaluation Precision: 1.00000 | Recall: 0.50000 | F1: 0.66667
[2023-03-31 16:16:35,071] [ INFO] - -----------------------------
[2023-03-31 16:16:35,071] [ INFO] - Class Name: 人名
[2023-03-31 16:16:35,071] [ INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2023-03-31 16:16:35,092] [ INFO] - -----------------------------
[2023-03-31 16:16:35,092] [ INFO] - Class Name: 天氣
[2023-03-31 16:16:35,092] [ INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
[2023-03-31 16:16:35,109] [ INFO] - -----------------------------
[2023-03-31 16:16:35,109] [ INFO] - Class Name: 價格
[2023-03-31 16:16:35,109] [ INFO] - Evaluation Precision: 1.00000 | Recall: 1.00000 | F1: 1.00000
my_ie = Taskflow("information_extraction", schema=schema, task_path='./checkpoint/model_best') # task_path 指定模型權重檔案的路徑
pprint(my_ie("2K 與 Gearbox Software 宣佈,《小緹娜的奇幻之地》將於 6 月 24 日凌晨 1 點登入 Steam,此前 PC 平臺為 Epic 限時獨佔。在限定期間內,Steam 玩家可以在 Steam 入手《小緹娜的奇幻之地》,並在 2022 年 7 月 8 日前享有獲得黃金英雄鎧甲包。"))
[2023-03-31 16:16:39,383] [ INFO] - Converting to the inference model cost a little time.
[2023-03-31 16:16:46,661] [ INFO] - The inference model save in the path:./checkpoint/model_best/static/inference
[2023-03-31 16:16:48,783] [ INFO] - We are using <class 'paddlenlp.transformers.ernie.tokenizer.ErnieTokenizer'> to load './checkpoint/model_best'.
[{'產品': [{'end': 118,
'probability': 0.9860396834664122,
'start': 108,
'text': '《小緹娜的奇幻之地》'},
{'end': 35,
'probability': 0.9870830377819004,
'start': 25,
'text': '《小緹娜的奇幻之地》'},
{'end': 148,
'probability': 0.9075236400717301,
'start': 141,
'text': '黃金英雄鎧甲包'}],
'時間': [{'end': 52,
'probability': 0.9998017644462607,
'start': 38,
'text': '6 月 24 日凌晨 1 點'},
{'end': 137,
'probability': 0.9875673117430104,
'start': 122,
'text': '2022 年 7 月 8 日前'}],
'組織': [{'end': 2,
'probability': 0.9888051241547942,
'start': 0,
'text': '2K'},
{'end': 93,
'probability': 0.9503029387182096,
'start': 88,
'text': 'Steam'},
{'end': 75,
'probability': 0.9819544449787045,
'start': 71,
'text': 'Epic'},
{'end': 105,
'probability': 0.7914398215948992,
'start': 100,
'text': 'Steam'},
{'end': 60,
'probability': 0.982935890915897,
'start': 55,
'text': 'Steam'},
{'end': 21,
'probability': 0.9994608274841141,
'start': 5,
'text': 'Gearbox Software'}]}]
pprint(my_ie("近日,量子計算專家、ACM計算獎得主Scott Aaronson通過部落格宣佈,將於本週離開得克薩斯大學奧斯汀分校(UT Austin)一年,並加盟人工智慧研究公司OpenAI。"))
[{'人名': [{'end': 32,
'probability': 0.9990193078443497,
'start': 18,
'text': 'Scott Aaronson'}],
'時間': [{'end': 2,
'probability': 0.9998481327061199,
'start': 0,
'text': '近日'},
{'end': 43,
'probability': 0.9995744486620453,
'start': 41,
'text': '本週'}],
'組織': [{'end': 66,
'probability': 0.9900117066000078,
'start': 57,
'text': 'UT Austin'},
{'end': 87,
'probability': 0.9993381402363184,
'start': 81,
'text': 'OpenAI'},
{'end': 56,
'probability': 0.9968616126324434,
'start': 45,
'text': '得克薩斯大學奧斯汀分校'},
{'end': 13,
'probability': 0.8434502340745098,
'start': 10,
'text': 'ACM'}]}]
基於 50 條標註資料進行模型微調後,效果有所提升。
在基於UIE的命名實體識別的基礎上,進一步通過整合 Label Studio 的 Machine Learning Backend 實現互動式預註釋和模型訓練等功能。
環境安裝:
pip install label_studio_ml
pip uninstall attr
完整的 Machine Learning Backend 見 my_ml_backend.py
檔案。更多有關自定義機器學習後端編寫的內容可參考 Write your own ML backend。
簡單來講,my_ml_backend.py 內主要包含一個繼承自 LabelStudioMLBase 的類,其內容可以分為以下三個主要部分:
import numpy as np
import os
import json
from paddlenlp import Taskflow
from label_studio_ml.model import LabelStudioMLBase
首先建立一個類宣告,通過繼承 LabelStudioMLBase
建立一個與 Label Studio 相容的 ML 後端伺服器。
class MyModel(LabelStudioMLBase):
然後,在 __init__
方法中定義和初始化需要的變數。LabelStudioMLBase
類提供了以下
fit()
方法的輸出相同。如本教學的例子中,標籤設定為:
<View>
<Labels name="label" toName="text">
<Label value="地名" background="#FFA39E"/>
<Label value="人名" background="#D4380D"/>
<Label value="組織" background="#FFC069"/>
<Label value="時間" background="#AD8B00"/>
<Label value="產品" background="#D3F261"/>
<Label value="價格" background="#389E0D"/>
<Label value="天氣" background="#5CDBD3"/>
</Labels>
<Text name="text" value="$text"/>
</View>
相對應的 parsed_label_config
如下所示:
{
'label': {
'type': 'Labels',
'to_name': ['text'],
'inputs': [{
'type': 'Text',
'value': 'text'
}],
'labels': ['地名', '人名', '組織', '時間', '產品', '價格', '天氣'],
'labels_attrs': {
'地名': {
'value': '地名',
'background': '#FFA39E'
},
'人名': {
'value': '人名',
'background': '#D4380D'
},
'組織': {
'value': '組織',
'background': '#FFC069'
},
'時間': {
'value': '時間',
'background': '#AD8B00'
},
'產品': {
'value': '產品',
'background': '#D3F261'
},
'價格': {
'value': '價格',
'background': '#389E0D'
},
'天氣': {
'value': '天氣',
'background': '#5CDBD3'
}
}
}
}
根據需要從 self.parsed_label_config
變數中提取需要的資訊,並通過 PaddleNLP 的 Taskflow 載入用於預標註的模型。
def __init__(self, **kwargs):
# don't forget to initialize base class...
super(MyModel, self).__init__(**kwargs)
# print("parsed_label_config:", self.parsed_label_config)
self.from_name, self.info = list(self.parsed_label_config.items())[0]
assert self.info['type'] == 'Labels'
assert self.info['inputs'][0]['type'] == 'Text'
self.to_name = self.info['to_name'][0]
self.value = self.info['inputs'][0]['value']
self.labels = list(self.info['labels'])
self.model = Taskflow("information_extraction", schema=self.labels, task_path= './checkpoint/model_best')
編寫程式碼覆蓋 predict(tasks, **kwargs)
方法。predict()
方法接受 [JSON 格式的 Label Studio 任務]返回預測。此外,還可以包含和自定義可用於主動學習迴圈的預測分數。
tasks
引數包含了有關要進行預註釋的任務的詳細資訊。具體的 task 格式如下所示:
{
'id': 16,
'data': {
'text': '新華社都柏林6月28日電(記者張琪)第二屆「漢語橋」世界小學生中文秀愛爾蘭賽區比賽結果日前揭曉,來自都柏林市的小學五年級學生埃拉·戈爾曼獲得一等獎。'
},
'meta': {},
'created_at': '2022-07-12T07:05:06.793411Z',
'updated_at': '2022-07-12T07:05:06.793424Z',
'is_labeled': False,
'overlap': 1,
'inner_id': 6,
'total_annotations': 0,
'cancelled_annotations': 0,
'total_predictions': 0,
'project': 2,
'updated_by': None,
'file_upload': 2,
'annotations': [],
'predictions': []
}
通過 Taskflow 進行預測需要從 ['data']['text']
欄位提取出原始文字,返回的 uie 預測結果格式如下所示:
{
'地名': [{
'text': '愛爾蘭',
'start': 34,
'end': 37,
'probability': 0.9999107139090313
}, {
'text': '都柏林市',
'start': 50,
'end': 54,
'probability': 0.9997840536235998
}, {
'text': '都柏林',
'start': 3,
'end': 6,
'probability': 0.9999684097596173
}],
'人名': [{
'text': '埃拉·戈爾曼',
'start': 62,
'end': 68,
'probability': 0.9999879598978225
}, {
'text': '張琪',
'start': 15,
'end': 17,
'probability': 0.9999905824882092
}],
'組織': [{
'text': '新華社',
'start': 0,
'end': 3,
'probability': 0.999975681447097
}],
'時間': [{
'text': '6月28日',
'start': 6,
'end': 11,
'probability': 0.9997071721989244
}, {
'text': '日前',
'start': 43,
'end': 45,
'probability': 0.9999804497706464
}]
}
從 uie 預測結果中提取相應的欄位,構成 Label Studio 接受的預註釋格式。命名實體識別任務的具體預註釋範例可參考 [Import span pre-annotations for text]
更多其他型別任務的具體預註釋範例可參考 [Specific examples for pre-annotations]
def predict(self, tasks, **kwargs):
from_name = self.from_name
to_name = self.to_name
model = self.model
predictions = []
for task in tasks:
# print("predict task:", task)
text = task['data'][self.value]
uie = model(text)[0]
# print("uie:", uie)
result = []
scores = []
for key in uie:
for item in uie[key]:
result.append({
'from_name': from_name,
'to_name': to_name,
'type': 'labels',
'value': {
'start': item['start'],
'end': item['end'],
'score': item['probability'],
'text': item['text'],
'labels': [key]
}
})
scores.append(item['probability'])
result = sorted(result, key=lambda k: k["value"]["start"])
mean_score = np.mean(scores) if len(scores) > 0 else 0
predictions.append({
'result': result,
# optionally you can include prediction scores that you can use to sort the tasks and do active learning
'score': float(mean_score),
'model_version': 'uie-ner'
})
return predictions
基於新註釋更新模型。
編寫程式碼覆蓋 fit()
方法。fit()
方法接受 [JSON 格式的 Label Studio 註釋]並返回任意一個可以儲存模型相關資訊的 JSON 字典。
def fit(self, annotations, workdir=None, **kwargs):
""" This is where training happens: train your model given list of annotations,
then returns dict with created links and resources
"""
# print("annotations:", annotations)
dataset = convert(annotations)
with open("./doccano_ext.jsonl", "w", encoding="utf-8") as outfile:
for item in dataset:
outline = json.dumps(item, ensure_ascii=False)
outfile.write(outline + "\n")
os.system('python doccano.py \
--doccano_file ./doccano_ext.jsonl \
--task_type "ext" \
--save_dir ./data \
--splits 0.8 0.2 0')
os.system('python finetune.py \
--train_path "./data/train.txt" \
--dev_path "./data/dev.txt" \
--save_dir "./checkpoint" \
--learning_rate 1e-5 \
--batch_size 4 \
--max_seq_len 512 \
--num_epochs 50 \
--model "uie-base" \
--init_from_ckpt "./checkpoint/model_best/model_state.pdparams" \
--seed 1000 \
--logging_steps 10 \
--valid_steps 100 \
--device "gpu"')
return {
'path': workdir
}
人工標註的缺點主要有以下幾點:
相比之下,智慧標註的優勢主要包括:
總之,智慧標註相對於人工標註有著更高的效率、更高的精度、更強的靈活性和更好的適用性,可以更好地滿足使用者的需求。
智慧標註:基於Labelstudio的UIE半監督深度學習的智慧標註方案碼源
更多細節參考:人工智慧知識圖譜之資訊抽取:基於Labelstudio的UIE半監督深度學習的智慧標註方案(雲端版),提效。