圍繞 transformers 構建現代 NLP 開發環境
這是2023年的第85篇文章
( 本文閱讀時間:15分鐘 )
01
Intro
02
樣本處理
核心思路:函式式,流式,組合式,batch 做多路融合,對 datasets 相容
MaxCompute(ODPS) :無法透過行號快速讀取資料,但是有 Tunnel 介面支援從某個下標開始順序讀取資料。
檔案系統:包括本地檔案,HDFS,以及OSS等物件儲存。本地檔案雖然能用 lseek() 等函式快速跳轉到某個位置(且該操作通常為 O(1)),但是如果每條樣本位元組數不一樣,封裝為隨機讀取還是非常複雜,但是做成流式讀取就很容易。其他雲上的儲存介質更是如此。
訊息佇列:例如 MetaQ,天然流式的資料,可以主動拉取,也可以以訂閱的方式封裝流式讀取的介面。
positive = Threaded(Map(func, ODPS(access_id, access_key, project, positive_sample_table_name, read_once=False)))negative = Threaded(Map(func, ODPS(access_id, access_key, project, negative_sample_table_name, read_once=False)))combined = Combine([positive, negative], sample_weight=[1.0, 1.0])
# 直接讀取資料
for data in combined:
print(data)
# 使用 huggingface datasets 模組
# 之後可以直接用在 transformers.Trainer 類中參與訓練
import datasets
train_dataset = datasets.IterableDataset.from_generator(combined,
gen_kwargs={"ranks": [0,1,2,3], "world_size": 4} # 支援分散式訓練
)
2.2 技術問題:對分散式訓練的支援
def _ODPS(access_id, access_key, project, table_name, partition_spec, read_once, retry,
endpoint, ranks=None, world_size=None):
# 載入 ranks + world_size 對應分片資料,實現略(計算讀取 range 後,使用 PyODPS 載入資料)
def ODPS(access_id, access_key, project, table_name, partition_spec=None, read_once=True, retry=True, endpoint="):
return partial(_ODPS, access_id, access_key, project, table_name, partition_spec, read_once, retry, endpoint)
03
模型開發
核心思路:
繼承 PreTrainedModel,PreTrainedCofig,PreTrainedTokenizer 基類,與 transformers 體系打通。 透過 mixin / monkey patching 方式,擴充現有框架功能,例如對 OSS 模型載入/儲存的支援。
# 載入我們專案組開發的分類模型(多工層次分類)
model = BertForMultiTaskHierarchicalClassification.from_pretrained("./local_dir")
# or 從 OSS 直接載入
model = BertForMultiTaskHierarchicalClassification.from_pretrained("oss://model_remote_dir")
# 儲存模型
model.save_pretrained("./local_dir")
model.save_pretrained("oss://model_remote_dir")
# 載入我們使用 C++ 開發的 tokenizer
tokenizer = ShieldTokenizer.from_pretrained("oss://model_remote_dir")
# 使用 AutoClass 實現相同功能,不需要指定特定的模型類名,由框架自動推斷
model = AutoModel.from_pretrained("oss://model_remote_dir")
tokenizer = AutoTokenizer.from_pretrained("oss://model_remote_dir")
# 擴充套件 transformers 預設的 pipeline
# 增加 multitask-hierarchical-classification 任務
pipe = pipeline("multitask-hierarchical-classification", model=model, tokenizer=tokenizer)
print(pipe("測試文字"))
3.1 如何支援集團內的儲存介質
class OSSRemoteModelMixin(object):
"""
支援使用者在 from_pretrained 和 save_pretrained 時按照 oss://path 的格式指定路徑 (bucket, ak, sk 需要在環境變數中指定, 見 util.oss_util 類)
可以用於所有包含 from_pretrained 和 save_pretrained 方法的類 (config or tokenizer or model)
"""
def from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.PathLike]], *model_args, **kwargs):
pretrained_model_name_or_path = convert_oss_to_local_path(cls, pretrained_model_name_or_path,
kwargs.get('cache_dir', risk_shield.CACHE_DIR))
return super(OSSRemoteModelMixin, cls).from_pretrained(
pretrained_model_name_or_path, *model_args, **kwargs)
def save_pretrained(
self,
save_directory: Union[str, os.PathLike],
*args,
**kwargs
):
prefix = "oss://"
oss_path = save_directory
if save_directory.startswith(prefix):
# save to a temp dir
# .......
# 將檔案複製到 OSS,實現略
return res
else:
res = super(OSSRemoteModelMixin, self).save_pretrained(save_directory, *args, **kwargs)
# 讓模型繼承自 OSSRemoteModelMixin 就會自動獲得 OSS 存取的能力
class BertForMultiTaskHierarchicalClassification(
OSSRemoteModelMixin, BertPreTrainedModel):
config_class = BertForMultiTaskHierarchicalClassificationConfig
def __init__(self, config:BertForMultiTaskHierarchicalClassificationConfig):
# .....
# 對於 AutoModel,則直接覆蓋他們的 from_pretrained 方法。
def patch_auto_class(cls):
"""
讓 AutoClass 支援 OSS 路徑
"""
old_from_pretrained = cls.from_pretrained
def new_from_pretrained(cls, pretrained_model_name_or_path: Optional[Union[str, os.PathLike]], *model_args, **kwargs):
pretrained_model_name_or_path = \
convert_oss_to_local_path(cls, pretrained_model_name_or_path,
kwargs.get('cache_dir', risk_shield.CACHE_DIR))
return old_from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
cls.from_pretrained = classmethod(new_from_pretrained)
類變數 vocab_files_names__getstate__, __setstate__,解決 C++ 物件的 pickle 問題_convert_token_to_id_convert_id_to_tokenconvert_ids_to_tokens__setattr__vocab_sizeget_vocabtokenizesave_vocabulary_pad 對 tokenize 後的結果進行 pad,主要用在 trainer 裡的 collator_encode_plus 處理單條文字_batch_encode_plus 處理 batch 文字
3.3 訓練程式碼
def compute_metrics(model, eval_pred):
logits, labels = eval_pred
# 針對層次分類設計的評估指標
metrics = evaluate_multitask_hierarchical_classifier(model, labels)
return metrics
# 定義模型
tokenizer = ShieldTokenizer.from_pretrained("oss://backbone")
config = BertForMultiTaskHierarchicalClassificationConfig.from_pretrained("oss://backbone")
config.multi_task_config = {
# 這裡的分類樹僅是例子
"main": {
"hierarchical_tree":
["父類別1", "父類別2",
["父類別3",
["父類別3-子類別1", "父類別3-子類別2", "父類別3-子類別3", "父類別3-子類別4"]
]
]
}
}
model = BertForMultiTaskHierarchicalClassification.from_pretrained("./backbone")
# 定義訓練資料載入策略
positive = Threaded(Map(func, ODPS(access_id, access_key, project,
positive_sample_table_name,
read_once=False)))
negative = Threaded(Map(func, ODPS(access_id, access_key, project,
negative_sample_table_name,
read_once=False)))
combined = Combine([positive, negative], sample_weight=[1.0, 1.0])
train_ds = datasets.IterableDataset.from_generator(combined)
training_arg = TrainingArguments(
output_dir="./output",
overwrite_output_dir=True,
num_train_epochs=4,
# ...
# 其他訓練引數
# ...
dataloader_num_workers=2,
)
trainer = Trainer(
model=model,
args=training_arg,
train_dataset=train_ds,
tokenizer=tokenizer,
eval_dataset=val_ds,
compute_metrics=partial(compute_metrics, model),
# 針對層次分類開發的 collator
data_collator=MultiTaskHierarchicalClassifierCollator(
tokenizer=tokenizer, model=model, max_length=max_length,
task_label_smooth=task_label_smooth
)
)
# 將實驗指標寫入 tensorboard 並上傳到 OSS
trainer.add_callback(OSSTensorboardWriterCallback("experiment/v1/"))
trainer.train()
第一個元素是 loss,trainer 自動最佳化該值。 後續的元素只能是 python dict / list / tuple / tensor,tensor 第一維的大小必須和 batch size 一致。最理想的情況就是一個二維 logits 矩陣。
3.4 模型部署
import risk_shield
from transformers import AutoTokenizer, AutoModel
model = AutoModel.from_pretrained("oss://model.tar.gz")
tokenizer = AutoTokenizer.from_pretrained("oss://model.tar.gz")
# 匯出 ONNX
model.export_onnx(output_dir)
# 或匯出 TensorRT
model.export_tensorrt(output_dir)
# 或匯出 Tensorflow(ODPS 部署)
model.export_tf_saved_model(output_dir)
# 匯出切詞器到同一目錄
tokenizer.save_pretrained(output_dir)
#########################
# 部署時載入對應 pipeline
from risk_shield import ONNXHierarchicalClassifierPipeline
from risk_shield import TensorRTHierarchicalClassifierPipeline
from risk_shield import TFSavedModelHierarchicalClassifierPipeline
pipe = TensorRTHierarchicalClassifierPipeline(output_dir)
result= pipe("測試文字")
3.5 最小化依賴
04
實驗管理
05
工具鏈及視覺化
06
“軟體2.0”
-- 團隊 A 同學釋出模型到 OSS
shield_publish ~/checkpoint_dir
WARNING:root:從本地目錄上傳:~/checkpoint_dir
xxxx.tar.gz: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100/100 [01:50<00:00, 1.11s/it]
WARNING:root:已釋出OSS(url):
-- 團隊 B 同學使用 OSS 上“內部開源”的模型
AutoModel.from_pretrained("")
相關資料
[02]
[03]
[04]
[05]
[06]
[07]
[08]
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2996997/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 用 Docker 構建 PHP 開發環境DockerPHP開發環境
- 基於 Vagrant 構建 PHP 開發環境PHP開發環境
- Docker 構建統一的前端開發環境Docker前端開發環境
- 如何構建基於 docker 的開發環境Docker開發環境
- 徒手用 Docker 構建自己的 PHP 開發環境DockerPHP開發環境
- 構建你的Office 365開發環境 - IOS版開發環境iOS
- 用Vagrant構建統一的golang開發環境Golang開發環境
- 構建你的Office 365開發環境 - 其他版本開發環境
- 構建一個基於容器的開發環境開發環境
- 基於 Docker 構建統一的開發環境Docker開發環境
- Python開發篇——構建虛擬Python開發環境(Conda+Poetry)Python開發環境
- 【進階系列】前端開發環境構建(一)CSS -- Sass前端開發環境CSS
- dockerfile構建flask環境DockerFlask
- docker構建php環境DockerPHP
- 酷!一鍵構建我自己的PHP框架的開發環境PHP框架開發環境
- webpack4.0 入門篇 - 構建前端開發的基本環境Web前端
- 建立嵌入式軟體開發的自動構建環境
- Docker 構建PHP 映象環境DockerPHP
- 重構之路:webpack區分生產環境和開發環境Web開發環境
- Vue 3與ESLint、Prettier:構建規範化的前端開發環境VueEsLint前端開發環境
- 利用 Docker 構建一個簡單的 java 開發編譯環境DockerJava編譯
- Jenkins +nginx 搭建前端構建環境JenkinsNginx前端
- Docker構建redis叢集環境DockerRedis
- vagrant構建centos虛擬環境CentOS
- emacs開發環境配置(4)——rust開發環境Mac開發環境Rust
- 使用VSCode和CMake構建跨平臺的C/C++開發環境VSCodeC++開發環境
- 配置開發環境、生成環境、測試環境開發環境
- TGDC | 用生活邏輯去遊戲——圍繞現代社會的開放世界設計遊戲
- Web 前端開發日誌(四):構建現代化 Node 應用Web前端
- 在umi中實現一次構建多環境部署
- 用 Docker 構建 MySQL 主從環境DockerMySql
- vue 構建環境切換指令碼Vue指令碼
- docker 靈活的構建 PHP 環境DockerPHP
- 開發者體驗:現代企業架構的關鍵一環架構
- 什麼是Hyperledger?Linux如何圍繞英特爾的區塊鏈專案構建開放平臺?Linux區塊鏈
- 語言結構的深層處理是NLP繞不開的坎
- 一、開發環境開發環境
- 開發環境配置開發環境