更新时间:2024-04-11 GMT+08:00
分享

自定义模型规范

AI Gallery除了支持托管文本生成和文本问答任务类型的模型,还支持托管其他任务类型的模型,其他任务类型的模型被称为自定义模型。但是托管的自定义模型要满足规范才支持使用AI Gallery工具链服务(微调大师、在线推理服务)。

自定义模型的使用流程

  1. 托管模型资产

    • 模型基础设置里的“任务类型”选择除“文本问答”“文本生成”之外的类型。
    • 上传模型文件时需要确认待上传的文件是否满足自定义模型规范。如果模型要支持训练,则需要满足自定义模型规范(训练);如果模型要支持推理,则需要满足自定义模型规范(推理)

  2. 资产上架
  3. 模型微调模型部署为推理服务

    • 如果进行模型微调,则“训练任务类型”选择“自定义”
    • 如果部署为推理服务,则“推理任务类型”选择“自定义”

自定义模型规范(训练)

当托管自定义模型到AI Gallery时,如果模型要支持AI Gallery的模型微调,则需要在“模型文件”添加gallery_train文件夹,文件夹内容参考表1

  • gallery_train文件夹必须是一级目录直接上传,否则会被判定不符合自定义模型规范,无法使用模型微调。
  • 如果自定义模型的模型文件不符合gallery_train文件列表要求或文件内容为空,都将不能正常进行模型微调。
表1 gallery_train文件列表

文件类型

文件说明

“train.py”

必选文件,训练脚本文件,定义了自定义模型的训练处理方式。代码示例请参见train.py示例

如果训练脚本里使用了其他脚本文件,则必须一起打包在gallery_train文件夹里上传,否则会导致微调失败。

“train_params.json”

必选文件,训练参数文件,定义了模型训练的必要参数,例如训练方式、超参信息。该参数会显示在微调工作流的“作业设置”页面的算法配置和超参数设置里面。代码示例请参见train_params.json示例

“dataset_readme.md”

必选文件,数据集要求说明,定义了模型训练时对数据集的要求,会显示在微调工作流的“准备数据”页面。

“requirements.txt”

非必选文件,环境配置文件,定义了项目依赖的python包。AI Gallery提供了基础镜像的依赖环境,如果要添加自定义依赖项,可通过requirements.txt文件实现。基础镜像包含python、PyTorch、cuda(GPU)、CANN(NPU)。

自定义模型规范(推理)

当托管自定义模型到AI Gallery时,如果模型要支持AI Gallery的推理服务,则需要在“模型文件”添加gallery_inference文件夹,文件夹内容参考表2

  • gallery_inference文件夹必须是一级目录直接上传,否则会被判定不符合自定义模型规范,无法使用模型微调。
  • 如果自定义模型的模型文件不符合gallery_inference文件列表要求或文件内容为空,都将不能正常部署在线推理服务。
表2 gallery_inference文件列表

文件类型

文件说明

“inference.py”

必选文件,推理脚本文件,定义了自定义模型的推理处理方式,包含初始化推理(init)和输入输出(call函数)。代码示例请参见inference.py示例

如果推理脚本里使用了其他脚本文件,则必须一起打包在gallery_inference文件夹里上传,否则会导致推理失败。

“requirements.txt”

非必选文件,环境配置文件,定义了项目依赖的python包。AI Gallery提供了基础镜像的依赖环境,如果要添加自定义依赖项,可通过requirements.txt文件实现。基础镜像包含python、PyTorch、cuda(GPU)、CANN(NPU)。

自定义模型使用的预置镜像

AI Gallery提供了PyTorch基础镜像,镜像里已经安装好了运行任务所需的软件,供自定义模型直接使用,快速进行训练、推理。预置镜像的版本信息请参见表3

表3 AI Gallery预置镜像列表

引擎类型

资源类型

版本名称

PyTorch

NPU

pytorch_2.0.1-cann_6.3.2-py_3.9-euler_2.10.7-aarch64

GPU

pytorch_2.0.0-cuda_11.7-py_3.9.11-ubuntu_20.04-x86_64

“train.py”示例

表4 环境变量说明

变量名称

说明

示例

ENV_AG_MODEL_DIR

模型存放路径,AI Gallery的模型仓库地址,包含模型仓库的所有文件。

“/home/ma-user/.cache/gallery/model/ur12345--gpt2”

ENV_AG_DATASET_DIR

数据集存放路径,AI Gallery的数据集仓库地址,包含数据集仓库的所有文件。

“/home/ma-user/.cache/gallery/dataset/ur12345--data_demo”

ENV_AG_USER_PARAMS

配置的训练超参json字符串。创建训练任务时在算法配置页面设置的超参,用json字符串表示。

{"per_device_eval_batch_size":"32","lr":"0.001","logging_steps":"24"}

ENV_AG_TRAIN_OUTPUT_DIR

训练产物文件存放路径。训练产物将被保存到该路径。训练任务结束后,由AI Gallery平台将该目录上传到新模型的仓库中。

“/home/ma-user/.cache/gallery/output”

ENV_AG_USER_METRICS_LOG_PATH

训练数据的日志文件存放路径。训练过程中的迭代次数、LOSS和吞吐数据按照“迭代次数|loss|吞吐”格式记录在日志中,AI Gallery通过环境变量找到日志,从中获取实际数据绘制成“吞吐”“训练LOSS”曲线,呈现在训练的“指标效果”中。具体请参见查看训练效果

说明:

日志文件中的迭代次数、LOSS和吞吐数据必须按照“迭代次数|loss|吞吐”格式存放,否则AI Gallery会数据解析失败,导致“吞吐”“训练LOSS”曲线异常。

“/var/logs/user_metrics.log”

import json
import os

from datasets import load_dataset
from transformers import AutoImageProcessor
from torchvision.transforms import RandomResizedCrop, Compose, Normalize, ToTensor, RandomHorizontalFlip
import numpy as np
from transformers import AutoModelForImageClassification, TrainingArguments, Trainer
from transformers import DefaultDataCollator
from sklearn import metrics

# 环境变量
# 工作目录
ENV_AG_WORK_DIR = 'ENV_AG_WORK_DIR'
# 模型存放路径
ENV_AG_MODEL_DIR = 'ENV_AG_MODEL_DIR'
# 数据集存放路径
ENV_AG_DATASET_DIR = 'ENV_AG_DATASET_DIR'
# 配置的训练超参json字符串
ENV_AG_USER_PARAMS = 'ENV_AG_USER_PARAMS'
# 训练产物存放路径
ENV_AG_TRAIN_OUTPUT_DIR = 'ENV_AG_TRAIN_OUTPUT_DIR'

_transforms = None


def _multi_class_classification_metrics(pred):
    raw_predictions, labels = pred
    predictions = np.argmax(raw_predictions, axis=1)
    results = {
        "f1_macro": metrics.f1_score(labels, predictions, average="macro"),
        "f1_micro": metrics.f1_score(labels, predictions, average="micro"),
        "f1_weighted": metrics.f1_score(labels, predictions, average="weighted"),
        "precision_macro": metrics.precision_score(labels, predictions, average="macro"),
        "precision_micro": metrics.precision_score(labels, predictions, average="micro"),
        "precision_weighted": metrics.precision_score(labels, predictions, average="weighted"),
        "recall_macro": metrics.recall_score(labels, predictions, average="macro"),
        "recall_micro": metrics.recall_score(labels, predictions, average="micro"),
        "recall_weighted": metrics.recall_score(labels, predictions, average="weighted"),
        "accuracy": metrics.accuracy_score(labels, predictions),
    }
    return results


def parse_args():
    """ 从AIGallery环境变量中获取用户配置的超参json """
    return json.loads(os.getenv(ENV_AG_USER_PARAMS))


def _process_input_data(image_processor):
    # 加载数据集
    dataset_path = os.getenv(ENV_AG_DATASET_DIR)
    dataset = load_dataset("imagefolder", data_dir=dataset_path)

    # 数据增强
    normalize = Normalize(mean=image_processor.image_mean, std=image_processor.image_std)
    size = (image_processor.size["shortest_edge"] if "shortest_edge" in image_processor.size else (
        image_processor.size["height"], image_processor.size["width"]))
    global _transforms
    _transforms = Compose([RandomResizedCrop(size), RandomHorizontalFlip(), ToTensor(), normalize])
    ret = dataset.with_transform(_format_transforms)
    return ret


# 转换函数
def _format_transforms(examples):
    examples["pixel_values"] = [_transforms(img.convert("RGB")) for img in examples["image"]]
    del examples["image"]
    return examples


def train(user_args):
    print('Start to process dataset')
    model_path = os.getenv(ENV_AG_MODEL_DIR)
    image_processor = AutoImageProcessor.from_pretrained(model_path)

    dataset = _process_input_data(image_processor)
    print(f"Dataset: {dataset}")
    # label和id映射
    classes = dataset["train"].features["label"].names
    label2id = {c: i for i, c in enumerate(classes)}
    id2label = {i: c for i, c in enumerate(classes)}

    print('Start to load model')
    # 加载模型
    model = AutoModelForImageClassification.from_pretrained(
        model_path,
        num_labels=len(classes),
        id2label=id2label,
        label2id=label2id,
        ignore_mismatched_sizes=True
    )

    print('Start to set training args')
    # 训练参数
    training_args = TrainingArguments(
        output_dir=os.getenv(ENV_AG_TRAIN_OUTPUT_DIR),
        remove_unused_columns=False,
        evaluation_strategy="epoch",
        save_strategy=user_args['save_strategy'],
        learning_rate=float(user_args['lr']),
        save_total_limit=3,
        per_device_train_batch_size=32,
        gradient_accumulation_steps=1,
        per_device_eval_batch_size=int(user_args['per_device_eval_batch_size']),
        num_train_epochs=int(user_args['num_train_epochs']),
        warmup_ratio=float(user_args['warmup_ratio']),
        logging_steps=int(user_args['logging_steps']),
        load_best_model_at_end=True,
        metric_for_best_model="accuracy",
        push_to_hub=False,
    )

    print('Start to train')
    # 训练参数
    trainer = Trainer(
        model=model,
        args=training_args,
        data_collator=DefaultDataCollator(),
        train_dataset=dataset["train"],
        eval_dataset=dataset["test"],
        tokenizer=image_processor,
        compute_metrics=_multi_class_classification_metrics,
    )

    # 开始训练
    train_results = trainer.train()
    print('Start to save model')
    # 保存模型
    trainer.save_model()
    trainer.log_metrics("train", train_results.metrics)
    trainer.save_metrics("train", train_results.metrics)
    trainer.save_state()

    print('Start to evaluate')
    # 在验证集上做准确性评估
    eva_metrics = trainer.evaluate()
    trainer.log_metrics("eval", eva_metrics)
    trainer.save_metrics("eval", eva_metrics)

    print('All Done')


if __name__ == '__main__':
    args = parse_args()
    train(args)

“train_params.json”示例

表5 training_methods参数说明

参数名称

说明

name

自定义的训练方式。

hyperparameters

训练方式包含的超参。具体参数说明请参见表6

表6 hyperparameters参数说明

参数名称

说明

name

超参的名称,只能包含英文、数字、下划线。

type

支持的超参类型,支持float、int、str或bool。

required

超参是否必选,支持true、false。必选不可删除,非必选可删除。

default

超参的默认值,如果无默认值,则填写空双引号。

help

超参的说明,不能超过20个字符。

{
    "training_methods": [
        {
            "name": "全参微调",
            "hyperparameters": [
                {
                    "name": "lr",
                    "type": "float",
                    "required": true,
                    "default": 0.001,
                    "help": "学习率"
                },
                {
                    "name": "per_device_eval_batch_size",
                    "type": "int",
                    "required": false,
                    "default": 32,
                    "help": "批大小"
                },
                {
                    "name": "logging_steps",
                    "type": "int",
                    "required": false,
                    "default": 24,
                    "help": "每多少步记录一次步骤"
                },
                {
                    "name": "save_strategy",
                    "type": "str",
                    "required": true,
                    "default": "epoch",
                    "help": "训练过程中保存checkpoint的策略"
                },
                {
                    "name": "num_train_epochs",
                    "type": "int",
                    "required": true,
                    "default": 20,
                    "help": "训练的总epochs数"
                },
                {
                    "name": "warmup_ratio",
                    "type": "float",
                    "required": true,
                    "default": 0.1,
                    "help": "用于指定线性热身占总训练步骤的比例"
                }
            ]
        }
    ]
}

“inference.py”示例

from typing import Dict, List, Any
from transformers import pipeline
import os


class EndpointHandler:
    def __init__(self, path=""):
        # Use a pipeline as a high-level helper
        self.pipe = pipeline("question-answering", model=path)

    def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """
         data args:
              inputs (:obj: `str`)
        Return:
              A :obj:`list` | `dict`: will be serialized and returned
        """
        # get inputs
        inputs = data.pop("inputs", data)
        question = inputs["question"]
        context = inputs["context"]
        resp = self.pipe(question=question, context=context)
        return resp
分享:

    相关文档

    相关产品