阿裡達摩院魔塔ModelScope模型訓練Train

2024年2月6日 24点热度 0人点赞

模型訓練介紹

ModelScope提供了很多模型,這些模型可以直接在推理中使用,也可以根據用戶數據集重新生成模型的參數,這個過程叫做訓練。特別地,基於預訓練backbone進行訓練的過程叫做微調(finetune)。

一般來說,一次完整的模型訓練包含了訓練(train)和評估(evaluate)兩個過程。訓練過程使用訓練數據集,將數據輸入模型計算出loss後更新模型參數。評估過程使用評估數據集,將數據輸入模型後評估模型效果。

ModelScope提供了完整的訓練組件,其中的主要組件被稱為trainer(訓練器),這些組件可以在預訓練或普通訓練場景下使用。

PyTorch訓練流程

ModelScope的模型訓練步驟如下:

  1. 使用MsDataset加載數據集
  2. 編寫cfg_modify_fn方法,按需修改部分參數
  3. 構造trainer,開始訓練
  4. 【訓練後步驟】進行模型評估
  5. 【訓練後步驟】使用訓練後的模型進行推理

PyTorch模型的訓練使用EpochBasedTrainer(及其子類),該類會根據配置文件實例化模型、預處理器、優化器、指標等模塊。因此訓練模型的重點在於修改出合理的配置,其中用到的各組件都是ModelScope的標準模塊。

trainer的重要構造參數

model: 模型id、模型本地路徑或模型實例,必填
cfg_file: 額外的配置文件,可選。如果填寫,trainer會使用這個配置進行訓練
cfg_modify_fn: 讀取配置後trainer調用這個回調方法修改配置項,可選。如果不傳就使用原始配置
train_dataset: 訓練用的數據集,調用訓練時必傳
eval_dataset: 評估用的數據集,調用評估時必傳
optimizers: 自定義的(optimizer、lr_scheduler),可選,如果傳入就不會使用配置文件中的
seed: 隨機種子
launcher: 支持使用pytorch/mpi/slurm開啟分佈式訓練
device: 訓練用設備。可選,值為cpu, gpu, gpu:0, cuda:0等,默認gpu

一個簡單的例子:文本分類

下面以一個簡單的文本分類任務為例,演示如何通過十幾行代碼,就可以端到端執行一個finetune任務。假設待訓練模型為:

# structbert的backbone,該模型沒有有效分類器,因此使用前需要finetune(微調)
model_id = 'damo/nlp_structbert_backbone_base_std'

使用MsDataset加載數據集

MsDataset提供了加載數據集的能力,包括用戶的數據和ModelScope生態數據集。下面的示例加載了ModelScope提供的afqmc(Ant Financial Question Matching Corpus,雙句相似度任務)數據集:

from modelscope.msdatasets import MsDataset
# 載入訓練數據,數據格式類似於{'sentence1': 'some content here', 'sentence2': 'other content here', 'label': 0}
train_dataset = MsDataset.load('clue',  subset_name='afqmc', split='train')
# 載入評估數據
eval_dataset = MsDataset.load('clue',  subset_name='afqmc', split='validation')

或者,也可以加載用戶自己的數據集:

from modelscope.msdatasets import MsDataset
# 載入訓練數據
train_dataset = MsDataset.load('/path/to/my_train_file.txt')
# 載入評估數據
eval_dataset = MsDataset.load('/path/to/my_eval_file.txt')

編寫cfg_modify_fn方法,按需修改部分參數

建議首先查看模型的配置文件,並查看需要額外修改的參數:

from modelscope.utils.hub import read_config
# 上面的model_id
config = read_config(model_id)
print(config.pretty_text)

一般的配置文件中,在訓練時需要修改的參數一般分為:

1. 預處理器參數

# 使用該模型適配的預處理器sen-sim-tokenizer
cfg.preprocessor.type='sen-sim-tokenizer'
# 預處理器輸入的dict中,句子1的key,參考上文加載數據集中的afqmc的格式
cfg.preprocessor.first_sequence = 'sentence1'
# 預處理器輸入的dict中,句子2的key
cfg.preprocessor.second_sequence = 'sentence2'
# 預處理器輸入的dict中,label的key
cfg.preprocessor.label = 'label'
# 預處理器需要的label和id的mapping
cfg.preprocessor.label2id = {'0': 0, '1': 1}

某些模態中,預處理的參數需要根據數據集修改(比如NLP一般需要修改,而CV一般不需要修改),後續可以查看ModelCard或各任務最佳實踐中各任務訓練的詳細描述。

2. 模型參數

# num_labels是該模型分類數
cfg.model.num_labels = 2

3. 任務參數

# 修改task類型為'text-classification'
cfg.task = 'text-classification'
# 修改pipeline名稱,用於後續推理
cfg.pipeline = {'type': 'text-classification'}

4. 訓練參數

一般訓練超參數的調節都在這裡進行:

# 設置訓練epoch
cfg.train.max_epochs = 5
# 工作目錄
cfg.train.work_dir = '/tmp'
# 設置batch_size
cfg.train.dataloader.batch_size_per_gpu = 32
cfg.evaluation.dataloader.batch_size_per_gpu = 32
# 設置learning rate
cfg.train.optimizer.lr = 2e-5
# 設置LinearLR的total_iters,這項和數據集大小相關
cfg.train.lr_scheduler.total_iters = int(len(train_dataset) / cfg.train.dataloader.batch_size_per_gpu) * cfg.train.max_epochs
# 設置評估metric類
cfg.evaluation.metrics = 'seq-cls-metric'

使用cfg_modify_fn將上述配置修改應用起來:

# 這個方法在trainer讀取configuration.json後立即執行,先於構造模型、預處理器等組件
def cfg_modify_fn(cfg):
  cfg.preprocessor.type='sen-sim-tokenizer'
  cfg.preprocessor.first_sequence = 'sentence1'
  cfg.preprocessor.second_sequence = 'sentence2'
  cfg.preprocessor.label = 'label'
  cfg.preprocessor.label2id = {'0': 0, '1': 1}
  cfg.model.num_labels = 2
  cfg.task = 'text-classification'
  cfg.pipeline = {'type': 'text-classification'}
  cfg.train.max_epochs = 5
  cfg.train.work_dir = '/tmp'
  cfg.train.dataloader.batch_size_per_gpu = 32
  cfg.evaluation.dataloader.batch_size_per_gpu = 32
  cfg.train.dataloader.workers_per_gpu = 0
  cfg.evaluation.dataloader.workers_per_gpu = 0
  cfg.train.optimizer.lr = 2e-5
  cfg.train.lr_scheduler.total_iters = int(len(train_dataset) / cfg.train.dataloader.batch_size_per_gpu) * cfg.train.max_epochs
  cfg.evaluation.metrics = 'seq-cls-metric'
  # 註意這裡需要返回修改後的cfg
  return cfg

構造trainer,開始訓練

首先,配置訓練所需參數:

from modelscope.trainers import build_trainer

# 配置參數
kwargs = dict(
        model=model_id,
        train_dataset=train_dataset,
        eval_dataset=eval_dataset,
        cfg_modify_fn=cfg_modify_fn)
trainer = build_trainer(default_args=kwargs)
trainer.train()

需要註意,數據由trainer從dataloader取數據的時候調用預處理器進行處理。

恭喜,你完成了一次模型訓練。

進行模型評估

可選地,在訓練後可以進行額外數據集的評估。用戶可以單獨調用evaluate方法對模型進行評估:

from modelscope.msdatasets import MsDataset
# 載入評估數據
eval_dataset = MsDataset.load('clue',  subset_name='afqmc', split='validation')
from modelscope.trainers import build_trainer
# 配置參數
kwargs = dict(
        # 由於使用的模型訓練後的目錄,因此不需要傳入cfg_modify_fn
        model='/tmp/output',
        eval_dataset=eval_dataset)
trainer = build_trainer(default_args=kwargs)
trainer.evaluate()

或者,也可以調用predict方法將預測結果保存下來,以供後續打榜:

from modelscope.msdatasets import MsDataset
import numpy as np
# 載入評估數據
eval_dataset = MsDataset.load('clue', subset_name='afqmc', split='test').to_hf_dataset()
from modelscope.trainers import build_trainer
def cfg_modify_fn(cfg):
    # 預處理器在mini-batch中留存冗餘字段
    cfg.preprocessor.val.keep_original_columns = ['sentence1', 'sentence2']
    # 預測數據集沒有label,將對應key置空
    cfg.preprocessor.val.label = None
    return cfg
kwargs = dict(
    model='damo/nlp_structbert_sentence-similarity_chinese-tiny',
    work_dir='/tmp',
    cfg_modify_fn=cfg_modify_fn,
    # remove_unused_data會將上述keep_original_columns的列轉為attributes
    remove_unused_data=True)
trainer = build_trainer(default_args=kwargs)
def saving_fn(inputs, outputs):
    with open(f'/tmp/predicts.txt', 'a') as f:
        # 通過attribute取冗餘值
        sentence1 = inputs.sentence1
        sentence2 = inputs.sentence2
        predictions = np.argmax(outputs['logits'].cpu().numpy(), axis=1)
        for sent1, sent2, pred in zip(sentence1, sentence2, predictions):
            f.writelines(f'{sent1}, {sent2}, {pred}\n')
trainer.predict(predict_datasets=eval_dataset,
                saving_fn=saving_fn)

使用訓練後的模型進行推理

訓練完成以後,文件夾中會生成推理用的模型配置,可以直接用於pipeline:

  • {work_dir}/output:訓練完成後,存儲模型配置文件,及最後一個epoch/iter的模型參數(配置中需要指定CheckpointHook)
  • {work_dir}/output_best:最佳模型參數時,存儲模型配置文件,及最佳的模型參數(配置中需要指定BestCkptSaverHook)
from modelscope.pipelines import pipeline
pipeline_ins = pipeline('text-classification', model='/tmp/output')
pipeline_ins(('這個功能可用嗎', '這個功能現在可用嗎'))

此外,ModelScope也會存儲*.pth文件,用於後續繼續訓練、訓練後驗證、訓練後推理。一般一次存儲會存儲兩個pth文件:

  • epoch_*.pth 存儲模型的state_dictoutput/output_best的bin文件是此文件的硬鏈接
  • epoch_*_trainer_state.pth,存儲trainerstate_dict

在繼續訓練場景時,隻需要加載模型的pth文件,trainer的pth文件會被同時讀取。用戶也可以手動link某個pth文件到output/output_best,實現使用任意一個存儲節點的推理.

pth的文件名格式如下:

  • epoch_{n}/iter_{n}.pth(如epoch_3.pth): 每interval個epoch/iter周期存儲(配置中需要指定CheckpointHook)
  • best_epoch{n}_{metricname}{m}.pth(如best_iter13_accuracy22.pth):取得最佳模型參數時存儲(配置中需要指定BestCkptSaverHook)
# 用於繼續訓練
trainer.train(checkpoint_path=os.path.join(self.tmp_dir, 'iter_3.pth'))
# 用於訓練後評估
trainer.evaluate(checkpoint_path=os.path.join(self.tmp_dir, 'iter_3.pth'))
# 用於訓練後推理並通過saving_fn存儲預測的label為文件
trainer.predict(checkpoint_path=os.path.join(self.tmp_dir, 'iter_3.pth'), 
                predict_datasets=some_dataset,
                saving_fn=some-saving-fn)