1 huggingface
1.1 概述
Hugging Face
是一个知名的开源社区和公司,专注于自然语言处理(NLP)和机器学习(ML)领域。他们开发了许多流行的开源工具和库,使得构建和应用NLP模型更加便捷
Hugging face起初是一家总部位于纽约的聊天机器人初创服务商,他们本来打算创业做聊天机器人,然后在github上开源了一个Transformers库,虽然聊天机器人业务没搞起来,但是他们的这个库在机器学习社区迅速大火起来。目前已经共享了超100,000个预训练模型,10,000个数据集,变成了机器学习界的github
在这里主要有以下大家需要的资源
Datasets:数据集,以及数据集的下载地址
Models:包括各种处理CV和NLP等任务的模型,上面模型都是可以免费获得
主要包括计算机视觉、自然语言处理、语音处理、多模态、表格处理、强化学习
course:免费的nlp课程
docs:文档
展开细节
- Computer Vision(计算机视觉任务):包括lmage Classification(图像分类),lmage Segmentation(图像分割)、zero-Shot lmage Classification(零样本图像分类)、lmage-to-Image(图像到图像的任务)、Unconditional lmage Generation(无条件图像生成)、Object Detection(目标检测)、Video Classification(视频分类)、Depth Estimation(深度估计,估计拍摄者距离图像各处的距离)
- Natural Language Processing(自然语言处理):包括Translation(机器翻译)、Fill-Mask(填充掩码,预测句子中被遮掩的词)、Token Classification(词分类)、Sentence Similarity(句子相似度)、Question Answering(问答系统),Summarization(总结,缩句)、Zero-Shot Classification (零样本分类)、Text Classification(文本分类)、Text2Text(文本到文本的生成)、Text Generation(文本生成)、Conversational(聊天)、Table Question Answer(表问答,1.预测表格中被遮掩单词2.数字推理,判断句子是否被表格数据支持)
- Audio(语音):Automatic Speech Recognition(语音识别)、Audio Classification(语音分类)、Text-to-Speech(文本到语音的生成)、Audio-to-Audio(语音到语音的生成)、Voice Activity Detection(声音检测、检测识别出需要的声音部分)
- Multimodal(多模态):Feature Extraction(特征提取)、Text-to-Image(文本到图像)、Visual Question Answering(视觉问答)、Image2Text(图像到文本)、Document Question Answering(文档问答)
- Tabular(表格):Tabular Classification(表分类)、Tabular Regression(表回归)
- Reinforcement Learning(强化学习):Reinforcement Learning(强化学习)、Robotics(机器人)
1.2 安装
安装transformers库
pip install transformers
1.3 模型下载加速
1.3.1 git clone
官方提供了 git clone repo_url
的方式下载,这种方法相当简单,然而却是最不推荐直接用的方法,缺点有二:
- 不支持断点续传,断了重头再来
- clone 会下载历史版本占用磁盘空间,即使没有历史版本
1.3.2 huggingface-cli
huggingface-cli
隶属于 huggingface_hub
库,不仅可以下载模型、数据,还可以可以登录huggingface、上传模型、数据等
huggingface-cli 属于官方工具,其长期支持肯定是最好的。优先推荐!
安装依赖
pip install -U huggingface_hub
注意:huggingface_hub 依赖于 Python>=3.8,此外需要安装 0.17.0 及以上的版本,推荐0.19.0+
基本用法
huggingface-cli download --resume-download bigscience/bloom-560m --local-dir bloom-560m
下载数据集
huggingface-cli download --resume-download --repo-type dataset lavita/medical-qa-shared-task-v1-toy
值得注意的是,有个
--local-dir-use-symlinks False
参数可选,因为huggingface的工具链默认会使用符号链接来存储下载的文件,导致--local-dir
指定的目录中都是一些链接文件,真实模型则存储在~/.cache/huggingface
下,如果不喜欢这个可以用--local-dir-use-symlinks False
取消这个逻辑
1.3.3 多线程下载器
多线程加速是一种有效、显著提高下载速度的方法
经典多线程工具推荐两个:IDM、Aria2。 IDM 适用于 Windows、aria2 适用于 Linux。本文头图就是 IDM 工具。因此获取URL后,可以利用这些多线程工具来下载。以我的一次实测为例,单线程700KB/s,IDM 8线程 6MB/s。千兆宽带下,利用IDM能跑到80MB/s+
手动获取仓库中所有 URL 比较麻烦,作者写了一个命令行脚本 hdf.sh(Gitst链接),结合自动获取 url 以及 aria2
多线程下载,适合于 Linux
#!/usr/bin/env bash
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
trap 'printf "${YELLOW}\nDownload interrupted. If you re-run the command, you can resume the download from the breakpoint.\n${NC}"; exit 1' INT
display_help() {
cat << EOF
Usage:
hfd <model_id> [--include include_pattern] [--exclude exclude_pattern] [--hf_username username] [--hf_token token] [--tool wget|aria2c] [-x threads] [--dataset]
Description:
Downloads a model or dataset from Hugging Face using the provided model ID.
Parameters:
model_id The Hugging Face model ID in the format 'repo/model_name'.
--include (Optional) Flag to specify a string pattern to include files for downloading.
--exclude (Optional) Flag to specify a string pattern to exclude files from downloading.
exclude_pattern The pattern to match against filenames for exclusion.
--hf_username (Optional) Hugging Face username for authentication.
--hf_token (Optional) Hugging Face token for authentication.
--tool (Optional) Download tool to use. Can be wget (default) or aria2c.
-x (Optional) Number of download threads for aria2c.
--dataset (Optional) Flag to indicate downloading a dataset.
Example:
hfd bigscience/bloom-560m --exclude safetensors
hfd meta-llama/Llama-2-7b --hf_username myuser --hf_token mytoken --tool aria2c -x 8
hfd lavita/medical-qa-shared-task-v1-toy --dataset
EOF
exit 1
}
MODEL_ID=$1
shift
# Default values
TOOL="wget"
THREADS=1
HF_ENDPOINT=${HF_ENDPOINT:-"https://huggingface.co"}
while [[ $# -gt 0 ]]; do
case $1 in
--include) INCLUDE_PATTERN="$2"; shift 2 ;;
--exclude) EXCLUDE_PATTERN="$2"; shift 2 ;;
--hf_username) HF_USERNAME="$2"; shift 2 ;;
--hf_token) HF_TOKEN="$2"; shift 2 ;;
--tool) TOOL="$2"; shift 2 ;;
-x) THREADS="$2"; shift 2 ;;
--dataset) DATASET=1; shift ;;
*) shift ;;
esac
done
# Check if aria2, wget, curl, git, and git-lfs are installed
check_command() {
if ! command -v $1 &>/dev/null; then
echo -e "${RED}$1 is not installed. Please install it first.${NC}"
exit 1
fi
}
[[ "$TOOL" == "aria2c" ]] && check_command aria2c
[[ "$TOOL" == "wget" ]] && check_command wget
check_command curl; check_command git; check_command git-lfs
[[ -z "$MODEL_ID" || "$MODEL_ID" =~ ^-h ]] && display_help
MODEL_DIR="${MODEL_ID#*/}"
if [[ "$DATASET" == 1 ]]; then
MODEL_ID="datasets/$MODEL_ID"
fi
echo "Downloading to ./$MODEL_DIR"
if [ -d "$MODEL_DIR/.git" ]; then
printf "${YELLOW}%s exists, Skip Clone.\n${NC}" "$MODEL_DIR"
cd "$MODEL_DIR" && GIT_LFS_SKIP_SMUDGE=1 git pull || { printf "Git pull failed.\n"; exit 1; }
else
REPO_URL="$HF_ENDPOINT/$MODEL_ID"
GIT_REFS_URL="${REPO_URL}/info/refs?service=git-upload-pack"
echo "Test GIT_REFS_URL: $GIT_REFS_URL"
response=$(curl -s -o /dev/null -w "%{http_code}" "$GIT_REFS_URL")
if [ "$response" == "401" ] || [ "$response" == "403" ]; then
if [[ -z "$HF_USERNAME" || -z "$HF_TOKEN" ]]; then
printf "${RED}HTTP Status Code: $response.\nThe repository requires authentication, but --hf_username and --hf_token is not passed. Please get token from https://huggingface.co/settings/tokens.\nExiting.\n${NC}"
exit 1
fi
REPO_URL="https://$HF_USERNAME:$HF_TOKEN@${HF_ENDPOINT#https://}/$MODEL_ID"
elif [ "$response" != "200" ]; then
echo -e "${RED}Unexpected HTTP Status Code: $response.\nExiting.\n${NC}"; exit 1
fi
echo "git clone $REPO_URL"
GIT_LFS_SKIP_SMUDGE=1 git clone "$REPO_URL" && cd "$MODEL_DIR" || { printf "${RED}Git clone failed.\n${NC}"; exit 1; }
for file in $(git lfs ls-files | awk '{print $3}'); do
truncate -s 0 "$file"
done
fi
printf "\nStart Downloading lfs files, bash script:\n"
files=$(git lfs ls-files | awk '{print $3}')
declare -a urls
for file in $files; do
url="$HF_ENDPOINT/$MODEL_ID/resolve/main/$file"
file_dir=$(dirname "$file")
mkdir -p "$file_dir"
if [[ "$TOOL" == "wget" ]]; then
download_cmd="wget -c \"$url\" -O \"$file\""
[[ -n "$HF_TOKEN" ]] && download_cmd="wget --header=\"Authorization: Bearer ${HF_TOKEN}\" -c \"$url\" -O \"$file\""
else
download_cmd="aria2c -x $THREADS -s $THREADS -k 1M -c \"$url\" -d \"$file_dir\" -o \"$(basename "$file")\""
[[ -n "$HF_TOKEN" ]] && download_cmd="aria2c --header=\"Authorization: Bearer ${HF_TOKEN}\" -x $THREADS -s $THREADS -k 1M -c \"$url\" -d \"$file_dir\" -o \"$(basename "$file")\""
fi
[[ -n "$INCLUDE_PATTERN" && $file != *"$INCLUDE_PATTERN"* ]] && printf "# %s\n" "$download_cmd" && continue
[[ -n "$EXCLUDE_PATTERN" && $file == *"$EXCLUDE_PATTERN"* ]] && printf "# %s\n" "$download_cmd" && continue
printf "%s\n" "$download_cmd"
urls+=("$url|$file")
done
for url_file in "${urls[@]}"; do
IFS='|' read -r url file <<< "$url_file"
file_dir=$(dirname "$file")
if [[ "$TOOL" == "wget" ]]; then
[[ -n "$HF_TOKEN" ]] && wget --header="Authorization: Bearer ${HF_TOKEN}" -c "$url" -O "$file" || wget -c "$url" -O "$file"
else
[[ -n "$HF_TOKEN" ]] && aria2c --header="Authorization: Bearer ${HF_TOKEN}" -x $THREADS -s $THREADS -k 1M -c "$url" -d "$file_dir" -o "$(basename "$file")" || aria2c -x $THREADS -s $THREADS -k 1M -c "$url" -d "$file_dir" -o "$(basename "$file")"
fi
[[ $? -eq 0 ]] && printf "Downloaded %s successfully.\n" "$url" || { printf "${RED}Failed to download %s.\n${NC}" "$url"; exit 1; }
done
printf "${GREEN}Download completed successfully.\n${NC}"
该工具同样支持设置镜像端点的环境变量:
export HF_ENDPOINT="https://hf-mirror.com"
基本命令:
./hdf.sh bigscience/bloom-560m --tool aria2c -x 4
如果没有安装 aria2,则可以默认用 wget:
./hdf.sh bigscience/bloom-560m
1.3.4 镜像网站
可下载模型和数据集,解决Huggingface无法访问问题
使用以下py脚本可以快速生成下载模型等文件的sh脚本
#!/usr/bin/env Python
# -- coding: utf-8 --
"""
@version: v1.0
@author: huangyc
@file: download_hf_models.py
@Description:
@time: 2024/1/18 11:08
"""
import contextlib
import sys
from typing import List
from urllib.parse import unquote, urlparse
import requests
from basic_support.logger.logger_config import logger
@contextlib.contextmanager
def print_to_file(file: str, mode: str = 'w', encoding='utf-8', errors=None, newline=None, closefd=True):
"""
将print重定向输出到文件
:param file: 文件名
:param mode: 读写模式
:param encoding: 文件编码
:param errors:
:param newline:
:param closefd:
:return:
"""
f = open(file=file, mode=mode, encoding=encoding, errors=errors, newline=newline, closefd=closefd)
# 保存原来的sys.stdout
original_stdout = sys.stdout
# 将sys.stdout重定向到文件流
sys.stdout = f
yield
# 恢复原来的sys.stdout
sys.stdout = original_stdout
f.close()
def extract_main_domain(url):
parsed_url = urlparse(url)
main_domain = f"{parsed_url.scheme}://{parsed_url.hostname}"
return main_domain
def gen_download_hf_models_script(model_url: str, file_name: str, filter_types: List[str] = None):
"""
产生下载hf模型的脚本
:param model_url: 支持 https://hf-mirror.com 和 https://huggingface.co/models
如: https://hf-mirror.com/baichuan-inc/Baichuan2-13B-Chat/tree/v2.0
:param file_name: 输出文件名字, 如nohup_download_baichuan2.sh
:param filter_types: 需要过滤的文件类型[暂时没实现]
:return:
"""
from bs4 import BeautifulSoup
# 获取主要域名
main_domain = extract_main_domain(model_url)
# 输出主要域名
logger.info(f"解析到域名为:{main_domain}")
# 发送HTTP GET请求获取网页内容
logger.info("开始解析下载")
response = requests.get(model_url)
logger.info("网页下载完成, 准备解析下载地址")
html_content = response.text
# 使用BeautifulSoup对HTML内容进行解析
soup = BeautifulSoup(html_content, 'html.parser')
download_files = soup.findAll('a', {'title': 'Download file'})
with print_to_file(file_name):
print('echo "开始下载模型等文件"\n')
for idx, download_file in enumerate(download_files):
href = unquote(download_file.get('href'))
print('date +"当前时间为: %Y-%m-%d %H:%M:%S"')
url = f"{main_domain}{href}"
file_name =os.path.basename(href).split('?')[0]
print(f'wget -O "{file_name}" "{url}"')
print(f'echo "下载完成"\n')
print('date +"当前时间为: %Y-%m-%d %H:%M:%S"')
print('echo "Download completed successfully."')
logger.info("下载地址解析完毕")
logger.info(f"脚本输出路径为: {file_name}")
if __name__ == '__main__':
test_url = r"https://hf-mirror.com/baichuan-inc/Baichuan2-13B-Chat/tree/v2.0"
gen_download_hf_models_script(model_url=test_url, file_name='noup_download_baichuan2.sh')
另一种更方便的方式
pip install -U huggingface_hub
export HF_ENDPOINT=https://hf-mirror.com
# 获取token https://huggingface.co/settings/tokens
huggingface-cli download --token hf_*** --resume-download meta-llama/Llama-2-7b-hf --local-dir Llama-2-7b-hf
1.4 快速开始
下图是huggingface模块关系图
#!/usr/bin/env Python
# -- coding: utf-8 --
"""
@version: v1.0
@author: huangyc
@file: noup_huggingface.py
@Description:
@time: 2024/2/3 9:49
"""
from datasets import load_dataset
from transformers import AutoModelForSequenceClassification
from transformers import AutoTokenizer
from transformers import DataCollatorWithPadding
from transformers import Trainer
from transformers import TrainingArguments
def run():
model_name = "distilbert-base-uncased"
output_dir = "path/to/save/folder/"
# 加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained(model_name)
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 加载数据集
dataset = load_dataset("rotten_tomatoes")
def tokenize_dataset(p_dataset):
"""
定义数据处理函数
@param p_dataset:
@return:
"""
return tokenizer(p_dataset["text"])
# 对数据集调用处理函数(这里主要做分词)
dataset = dataset.map(tokenize_dataset, batched=True)
# 有些还需要做标签对齐
# label2id = {"contradiction": 0, "neutral": 1, "entailment": 2}
# mnli = load_dataset("glue", "mnli", split="train")
# mnli_aligned = mnli.align_labels_with_mapping(label2id, "label")
# 定义一个数据收集器
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
# 配置训练参数
training_args = TrainingArguments(output_dir, learning_rate=2e-5, per_device_train_batch_size=8,
per_device_eval_batch_size=8, num_train_epochs=2, )
# 定义一个trainer
trainer = Trainer(model=model, args=training_args, train_dataset=dataset["train"], eval_dataset=dataset["test"],
tokenizer=tokenizer, data_collator=data_collator, )
# 开始训练
trainer.train()
if __name__ == '__main__':
run()
对于使用序列到序列模型(Seq2Seq)的任务,如翻译或摘要,应该使用Seq2SeqTrainer和Seq2SeqTrainingArguments类
您可以通过继承Trainer内部的方法来自定义训练循环的行为。这使您能够自定义特征,如损失函数、优化器和调度器。查看Trainer参考以了解哪些方法可以被继承
自定义训练循环的另一种方式是使用回调(Callbacks)。您可以使用回调与其他库集成以及检查训练循环,以报告进度或提前停止训练
回调不会修改训练循环本身的任何内容。若要自定义像损失函数这样的内容,您需要继承Trainer
2 datasets
2.1 安装
下面三个命令都用于安装Hugging Face的datasets
库的不同配置
pip install datasets
:这个命令安装的是datasets
库的基本配置,它提供了对常见的自然语言处理(NLP)任务和数据集的支持,例如文本分类、命名实体识别、问答系统等。如果您只需要处理文本数据或进行常见的NLP任务,这个基本配置就足够了pip install datasets[audio]
:这个命令安装的是datasets
库的"audio"配置。它包含了对声音和音频数据集的支持,例如自动语音识别(ASR)和音频分类任务。如果您需要处理声音和音频数据,比如进行语音识别或音频分类,安装这个配置会提供相应的功能和数据集支持pip install datasets[vision]
:这个命令安装的是datasets
库的"vision"配置。它包含了对图像和计算机视觉任务的支持,例如图像分类、目标检测和分割等。如果您需要处理图像数据或进行计算机视觉任务,安装这个配置会提供相应的功能和数据集支持
通过安装不同的配置,您可以选择仅安装您需要的功能和支持的任务类型,以减少库的安装和存储空间。根据您的具体需求,选择适合的配置进行安装即可
# 安装基础版
pip install datasets
# 安装for声音
pip install datasets[audio]
# 安装for图像
pip install datasets[vision]
2.2 快速开始
2.2.1 视觉
from datasets import load_dataset, Image
from torch.utils.data import DataLoader
from torchvision.transforms import Compose, ColorJitter, ToTensor
# 加载数据集
dataset = load_dataset("beans", split="train")
jitter = Compose(
[ColorJitter(brightness=0.5, hue=0.5), ToTensor()]
)
def transforms(examples):
examples["pixel_values"] = [jitter(image.convert("RGB")) for image in examples["image"]]
return examples
dataset = dataset.with_transform(transforms)
def collate_fn(examples):
images = []
labels = []
for example in examples:
images.append((example["pixel_values"]))
labels.append(example["labels"])
pixel_values = torch.stack(images)
labels = torch.tensor(labels)
return {"pixel_values": pixel_values, "labels": labels}
# 定义DataLoader
dataloader = DataLoader(dataset, collate_fn=collate_fn, batch_size=4)
2.2.2 nlp
使用 Hugging Face 提供的datasets
库加载了GLUE(General Language Understanding Evaluation)数据集中的MRPC(Microsoft Research Paraphrase Corpus)部分的训练集。这个数据集用于句子对的相似性判断任务
from datasets import load_dataset
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
dataset = load_dataset("glue", "mrpc", split="test")
# load a pretrained BERT model and its corresponding tokenizer from the 🤗 Transformers library.
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
def encode(examples):
return tokenizer(examples["sentence1"], examples["sentence2"], truncation=True, padding="max_length")
dataset = dataset.map(encode, batched=True)
dataset[0]
{'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
'label': 1,
'idx': 0,
'input_ids': array([ 101, 7277, 2180, 5303, 4806, 1117, 1711, 117, 2292, 1119, 1270, 107, 1103, 7737, 107, 117, 1104, 9938, 4267, 12223, 21811, 1117, 2554, 119, 102, 11336, 6732, 3384, 1106, 1140, 1112, 1178, 107, 1103, 7737, 107, 117, 7277, 2180, 5303, 4806, 1117, 1711, 1104, 9938, 4267, 12223, 21811, 1117, 2554, 119, 102]),
'token_type_ids': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
'attention_mask': array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])}
# Rename the label column to labels, which is the expected input name in BertForSequenceClassification
dataset = dataset.map(lambda examples: {"labels": examples["label"]}, batched=True)
dataset.set_format(type="torch", columns=["input_ids", "token_type_ids", "attention_mask", "labels"])
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32)
2.3 概述
datasets
库中的Dataset
对象通常用来处理和存储数据。当数据需要载入模型进行训练或评估时,DataLoader
被用来创建数据的迭代器,允许批量处理和并行加载
DataCollator
则用于将这些批次的数据整理成模型需要的格式,进行适当的填充或其他预处理步骤
简单来说,你可以这样想象它们的工作流:
Dataset
负责数据的存储和预处理DataLoader
负责从Dataset
中抽取数据,组成批次,并可选择并行加载数据DataCollator
负责将DataLoader
提供的批次数据进行填充和整理,以确保模型可以正确处理
2.3.1 DataCollator类
huggingface的DataCollator和pytorch的collate_fn的关系
在PyTorch中,collate_fn
是 DataLoader
的一个参数,用于指定如何将多个数据样本组合成一个批次
这个函数接受一个样本列表作为输入,然后返回一个批次,通常是通过堆叠(stacking)或填充(padding)样本来实现
collate_fn
在处理长度不一致的数据时特别有用,例如文本数据或时间序列数据
Hugging Face的 DataCollator
基本上是 collate_fn
的一个扩展或包装器。它通常是一个类,实现了一个 __call__
方法,该方法的功能与 collate_fn
相同
在Hugging Face的Transformers库中,有预先定义的 DataCollator
类,它们被设计用来处理特定类型的数据和模型需求,如填充到相同长度,或者为了语言模型训练而进行数据掩蔽的任务
下面是一个示例,展示了在PyTorch和Hugging Face的Transformers中如何使用 collate_fn
和 DataCollator
:
在PyTorch中使用自定义 collate_fn
:
from torch.utils.data import DataLoader
def custom_collate_fn(batch):
# 自定义的堆叠、填充逻辑
pass
data_loader = DataLoader(dataset, batch_size=32, collate_fn=custom_collate_fn)
在Hugging Face的Transformers中使用 DataCollator
:
from transformers import DataCollatorWithPadding
from torch.utils.data import DataLoader
# 对于一些特定的任务,Transformers库提供了预定义的DataCollator
data_collator = DataCollatorWithPadding(tokenizer)
data_loader = DataLoader(dataset, batch_size=32, collate_fn=data_collator)
在这个例子中,DataCollatorWithPadding
是Hugging Face提供的一个类,它使用给定的tokenizer来自动处理批次的填充
当创建 DataLoader
实例时,你可以直接将 data_collator
作为 collate_fn
的值传入,这是因为 DataCollatorWithPadding
类的实例是可调用的,这样 DataLoader
在每个批次准备数据时会调用 data_collator
总的来说,Hugging Face的 DataCollator
提供了一个更高级别、更方便的接口,尤其是为了与 Transformers
库中的NLP模型和任务配合使用,而PyTorch的 collate_fn
是这个接口的底层机制,提供了自定义数据组合逻辑的基础功能
小结
可以查看huggingface的Trainer类,很容易发现他们之间的关系:Hugging Face的 DataCollator
基本上是 collate_fn
的一个扩展或包装器。它通常是一个类,实现了一个 __call__
方法,该方法的功能与 collate_fn
相同
from torch.utils.data import DataLoader
class Trainer:
def __init__(
self,
model: Union[PreTrainedModel, nn.Module] = None,
args: TrainingArguments = None,
data_collator: Optional[DataCollator] = None,
train_dataset: Optional[Dataset] = None,
eval_dataset: Optional[Union[Dataset, Dict[str, Dataset]]] = None,
tokenizer: Optional[PreTrainedTokenizerBase] = None,
model_init: Optional[Callable[[], PreTrainedModel]] = None,
compute_metrics: Optional[Callable[[EvalPrediction], Dict]] = None,
callbacks: Optional[List[TrainerCallback]] = None,
optimizers: Tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR] = (None, None),
preprocess_logits_for_metrics: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None,
):
...
default_collator = default_data_collator if tokenizer is None else DataCollatorWithPadding(tokenizer)
self.data_collator = data_collator if data_collator is not None else default_collator
...
def get_train_dataloader(self) -> DataLoader:
"""
Returns the training [`~torch.utils.data.DataLoader`].
Will use no sampler if `train_dataset` does not implement `__len__`, a random sampler (adapted to distributed
training if necessary) otherwise.
Subclass and override this method if you want to inject some custom behavior.
"""
if self.train_dataset is None:
raise ValueError("Trainer: training requires a train_dataset.")
train_dataset = self.train_dataset
data_collator = self.data_collator
if is_datasets_available() and isinstance(train_dataset, datasets.Dataset):
train_dataset = self._remove_unused_columns(train_dataset, description="training")
else:
data_collator = self._get_collator_with_removed_columns(data_collator, description="training")
if isinstance(train_dataset, torch.utils.data.IterableDataset):
if self.args.world_size > 1:
train_dataset = IterableDatasetShard(
train_dataset,
batch_size=self._train_batch_size,
drop_last=self.args.dataloader_drop_last,
num_processes=self.args.world_size,
process_index=self.args.process_index,
)
return DataLoader(
train_dataset,
batch_size=self._train_batch_size,
collate_fn=data_collator,
num_workers=self.args.dataloader_num_workers,
pin_memory=self.args.dataloader_pin_memory,
)
train_sampler = self._get_train_sampler()
return DataLoader(
train_dataset,
batch_size=self._train_batch_size,
sampler=train_sampler,
collate_fn=data_collator,
drop_last=self.args.dataloader_drop_last,
num_workers=self.args.dataloader_num_workers,
pin_memory=self.args.dataloader_pin_memory,
worker_init_fn=seed_worker,
)
2.4 加载数据集
查看数据集描述
from datasets import load_dataset_builder
ds_builder = load_dataset_builder("rotten_tomatoes")
ds_builder.info.description
Movie Review Dataset. This is a dataset of containing 5,331 positive and 5,331 negative processed sentences from Rotten Tomatoes movie reviews. This data was first used in Bo Pang and Lillian Lee, ``Seeing stars: Exploiting class relationships for sentiment categorization with respect to rating scales.'', Proceedings of the ACL, 2005.
ds_builder.info.features
{'label': ClassLabel(num_classes=2, names=['neg', 'pos'], id=None),
'text': Value(dtype='string', id=None)}
加载数据集
from datasets import load_dataset
dataset = load_dataset("rotten_tomatoes", split="train")
当一个数据集由多个文件(我们称之为分片)组成时,可以显著加快数据集的下载和准备步骤
您可以使用num_proc参数选择并行准备数据集时要使用的进程数。在这种情况下,每个进程被分配了一部分分片来进行准备
from datasets import load_dataset
oscar_afrikaans = load_dataset("oscar-corpus/OSCAR-2201", "af", num_proc=8)
imagenet = load_dataset("imagenet-1k", num_proc=8)
ml_librispeech_spanish = load_dataset("facebook/multilingual_librispeech", "spanish", num_proc=8)
查看数据集的分片名称,并加载指定的分片名称
from datasets import get_dataset_split_names
from datasets import load_dataset
get_dataset_split_names("rotten_tomatoes")
['train', 'validation', 'test']
# 加载指定分片
dataset = load_dataset("rotten_tomatoes", split="train")
Dataset({
features: ['text', 'label'],
num_rows: 8530
})
# 还可以这么写:
train_test_ds = datasets.load_dataset("bookcorpus", split="train+test")
train_10_20_ds = datasets.load_dataset("bookcorpus", split="train[10:20]")
train_10pct_ds = datasets.load_dataset("bookcorpus", split="train[:10%]")
train_10_80pct_ds = datasets.load_dataset("bookcorpus", split="train[:10%]+train[-80%:]")
val_ds = datasets.load_dataset("bookcorpus", split=[f"train[{k}%:{k+10}%]" for k in range(0, 100, 10)])
train_ds = datasets.load_dataset("bookcorpus", split=[f"train[:{k}%]+train[{k+10}%:]" for k in range(0, 100, 10)])
train_50_52_ds = datasets.load_dataset("bookcorpus", split="train[50%:52%]")
train_52_54_ds = datasets.load_dataset("bookcorpus", split="train[52%:54%]")
# 18 records, from 450 (included) to 468 (excluded).
train_50_52pct1_ds = datasets.load_dataset("bookcorpus", split=datasets.ReadInstruction("train", from_=50, to=52, unit="%", rounding="pct1_dropremainder"))
# 18 records, from 468 (included) to 486 (excluded).
train_52_54pct1_ds = datasets.load_dataset("bookcorpus", split=datasets.ReadInstruction("train",from_=52, to=54, unit="%", rounding="pct1_dropremainder"))
# Or equivalently:
train_50_52pct1_ds = datasets.load_dataset("bookcorpus", split="train[50%:52%](pct1_dropremainder)")
train_52_54pct1_ds = datasets.load_dataset("bookcorpus", split="train[52%:54%](pct1_dropremainder)")
# 加载全部数据
dataset = load_dataset("rotten_tomatoes")
DatasetDict({
train: Dataset({
features: ['text', 'label'],
num_rows: 8530
})
validation: Dataset({
features: ['text', 'label'],
num_rows: 1066
})
test: Dataset({
features: ['text', 'label'],
num_rows: 1066
})
})
查看数据集子集,一个数据下可能还有很多子数据集
from datasets import get_dataset_config_names
configs = get_dataset_config_names("PolyAI/minds14")
print(configs)
['cs-CZ', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-FR', 'it-IT', 'ko-KR', 'nl-NL', 'pl-PL', 'pt-PT', 'ru-RU', 'zh-CN', 'all']
加载指定子数据集
from datasets import load_dataset
mindsFR = load_dataset("PolyAI/minds14", "fr-FR", split="train") # 指定子数据集是fr-FR
指定数据集的文件, 避免load过多的数据
data_files = {"validation": "en/c4-validation.*.json.gz"}
c4_validation = load_dataset("allenai/c4", data_files=data_files, split="validation")
load本地的json、csv文件等,可以load远程文件、sql等
#{"version": "0.1.0",
# "data": [{"a": 1, "b": 2.0, "c": "foo", "d": false},
# {"a": 4, "b": -5.5, "c": null, "d": true}]
#}
from datasets import load_dataset
dataset = load_dataset("json", data_files="my_file.json", field="data")
通过python对象来创建dataset
from datasets import Dataset
import pandas as pd
# 字典方式
my_dict = {"a": [1, 2, 3]}
dataset = Dataset.from_dict(my_dict)
# list方式
my_list = [{"a": 1}, {"a": 2}, {"a": 3}]
dataset = Dataset.from_list(my_list)
# pandas方式
df = pd.DataFrame({"a": [1, 2, 3]})
dataset = Dataset.from_pandas(df)
load多个文本文件: 文本必须一行就是一条样本
from datasets import load_dataset
dataset = load_dataset("text", data_files={"train": ["my_text_1.txt", "my_text_2.txt"], "test": "my_test_file.txt"})
# Load from a directory
dataset = load_dataset("text", data_dir="path/to/text/dataset")
离线load: 将环境变量HF_DATASETS_OFFLINE
设置为1以启用完全离线模式
2.5 进阶加载数据集
从脚本加载数据集
您可能在本地计算机上有一个🤗Datasets的加载脚本。在这种情况下,通过将以下路径之一传递给load_dataset()来加载数据集:
加载脚本文件的本地路径。 包含加载脚本文件的目录的本地路径(仅当脚本文件与目录具有相同的名称时)
dataset = load_dataset("path/to/local/loading_script/loading_script.py", split="train")
# equivalent because the file has the same name as the directory
dataset = load_dataset("path/to/local/loading_script", split="train")
可以从Hub上下载加载脚本,并对其进行编辑以添加自己的修改。将数据集仓库下载到本地,以便加载脚本中相对路径引用的任何数据文件都可以被加载
git clone https://huggingface.co/datasets/eli5
在加载脚本上进行编辑后,通过将其本地路径传递给load_dataset()来加载它
from datasets import load_dataset
eli5 = load_dataset("path/to/local/eli5")
csv+json方式
数据集可以从存储在计算机上的本地文件和远程文件中加载。这些数据集很可能以csv、json、txt或parquet文件的形式存储。load_dataset()函数可以加载这些文件类型的数据集
from datasets import load_dataset
# csv方式
dataset = load_dataset("csv", data_files="my_file.csv")
# json方式
# {"a": 1, "b": 2.0, "c": "foo", "d": false}
# {"a": 4, "b": -5.5, "c": null, "d": true}
dataset = load_dataset("json", data_files="my_file.json")
# {"version": "0.1.0",
# "data": [{"a": 1, "b": 2.0, "c": "foo", "d": false},
# {"a": 4, "b": -5.5, "c": null, "d": true}]
# }
dataset = load_dataset("json", data_files="my_file.json", field="data")
# 从http方式加载csv
base_url = "https://rajpurkar.github.io/SQuAD-explorer/dataset/"
dataset = load_dataset("json", data_files={"train": base_url + "train-v1.1.json", "validation": base_url + "dev-v1.1.json"}, field="data")
# Parquet方式
dataset = load_dataset("parquet", data_files={'train': 'train.parquet', 'test': 'test.parquet'})
base_url = "https://storage.googleapis.com/huggingface-nlp/cache/datasets/wikipedia/20200501.en/1.0.0/"
data_files = {"train": base_url + "wikipedia-train.parquet"}
wiki = load_dataset("parquet", data_files=data_files, split="train")
sql方式
使用from_sql()方法可以通过指定连接到数据库的URI来读取数据库内容。您可以读取表名或执行查询操作
from datasets import Dataset
dataset = Dataset.from_sql("data_table_name", con="sqlite:///sqlite_file.db")
dataset = Dataset.from_sql("SELECT text FROM table WHERE length(text) > 100 LIMIT 10", con="sqlite:///sqlite_file.db")
For more details, check out the how to load tabular datasets from SQL databases guide.
2.6 探索数据集
下标
# 第一个样本
dataset[0]
#{'label': 1,
# 'text': 'the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .'}
# 最后一个样本
dataset[-1]
# 只取text列
dataset["text"] # 返回a list of 样本列
# 第一个样本text列
dataset[0]["text"] # 性能:dataset[0]['text']比dataset['text'][0]快2倍。
数据切片
# Get the first three rows
dataset[:3]
# Get rows between three and six
dataset[3:6]
迭代方式,streaming=True
from datasets import load_dataset
iterable_dataset = load_dataset("food101", split="train", streaming=True)
for example in iterable_dataset:
print(example)
break
{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=384x512 at 0x7F0681F5C520>, 'label': 6}
# Get first three examples
list(iterable_dataset.take(3))
[{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=384x512 at 0x7F7479DEE9D0>,
'label': 6},
{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=512x512 at 0x7F7479DE8190>,
'label': 6},
{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=512x383 at 0x7F7479DE8310>,
'label': 6}]
排序+shuffle+选择+filter+切分数据集+分片
# sort: 按某一列排序
dataset.sort("label")
# 打乱
shuffled_dataset = sorted_dataset.shuffle(seed=42)
# 选择
small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
# 匹配查找
start_with_ar = dataset.filter(lambda example: example["sentence1"].startswith("Ar"))
len(start_with_ar)
start_with_ar["sentence1"]
# 匹配查找:根据下标
even_dataset = dataset.filter(lambda example, idx: idx % 2 == 0, with_indices=True)
# 切分
dataset.train_test_split(test_size=0.1)
# 分片
# 数据集支持分片,将非常大的数据集划分为预定义数量的块。 在 shard() 中指定 num_shards 参数以确定要将数据集拆分成的分片数。 您还需要使用 index 参数提供要返回的分片。
from datasets import load_dataset
datasets = load_dataset("imdb", split="train")
print(dataset)
dataset.shard(num_shards=4, index=0) # 四分之一
列重命名+移除列+转换格式+flatten
from datasets import ClassLabel, Value
from datasets import load_dataset
# 列重命名
dataset = dataset.rename_column("sentence1", "sentenceA")
# 去掉某一列
dataset = dataset.remove_columns(["sentence1", "sentence2"])
# 转换格式:一列或者多列
new_features = dataset.features.copy()
new_features["label"] = ClassLabel(names=["negative", "positive"])
new_features["idx"] = Value("int64")
dataset = dataset.cast(new_features)
# 转换格式:一列
dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))
# 将某一列的key\value拉平
dataset = load_dataset("squad", split="train") # ???
map转换
from multiprocess import set_start_method
from datasets import load_dataset
import torch
import os
set_start_method("spawn")
# remove_columns 转换的同时去掉某一列
updated_dataset = dataset.map(lambda example: {"new_sentence": example["sentence1"]}, remove_columns=["sentence1"])
updated_dataset.column_names
# with_indices: 对下标处理
updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True)
updated_dataset["sentence2"][:5]
#如果您设置with_rank=True,map()也适用于进程的等级。 这类似于with_indices参数。 映射函数中的with_rank参数位于索引1之后(如果它已经存在)
def gpu_computation(example, rank):
os.environ["CUDA_VISIBLE_DEVICES"] = str(rank % torch.cuda.device_count())
# Your big GPU call goes here
return examples
updated_dataset = dataset.map(gpu_computation, with_rank=True)
# 多线程
updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, num_proc=4)
# batched
chunked_dataset = dataset.map(chunk_examples, batched=True, remove_columns=dataset.column_names)
# 数据增强
def augment_data(examples):
outputs = []
for sentence in examples["sentence1"]:
words = sentence.split(' ')
K = randint(1, len(words)-1)
masked_sentence = " ".join(words[:K] + [mask_token] + words[K+1:])
predictions = fillmask(masked_sentence)
augmented_sequences = [predictions[i]["sequence"] for i in range(3)]
outputs += [sentence] + augmented_sequences
return {"data": outputs}
augmented_dataset = smaller_dataset.map(augment_data, batched=True, remove_columns=dataset.column_names, batch_size=8)
augmented_dataset[:9]["data"]
# 处理多split
dataset = load_dataset('glue', 'mrpc')
encoded_dataset = dataset.map(lambda examples: tokenizer(examples["sentence1"]), batched=True)
encoded_dataset["train"][0]
合并+拼接数据集
from datasets import concatenate_datasets, load_dataset
from datasets import Dataset
# 加载数据集
bookcorpus = load_dataset("bookcorpus", split="train")
wiki = load_dataset("wikipedia", "20220301.en", split="train")
wiki = wiki.remove_columns([col for col in wiki.column_names if col != "text"]) # only keep the 'text' column
assert bookcorpus.features.type == wiki.features.type
bert_dataset = concatenate_datasets([bookcorpus, wiki])
# 可以换concate的方向
bookcorpus_ids = Dataset.from_dict({"ids": list(range(len(bookcorpus)))})
bookcorpus_with_ids = concatenate_datasets([bookcorpus, bookcorpus_ids], axis=1)
相互穿插
import torch
# 按概率穿插
seed = 42
probabilities = [0.3, 0.5, 0.2]
d1 = Dataset.from_dict({"a": [0, 1, 2]})
d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
d3 = Dataset.from_dict({"a": [20, 21, 22]})
dataset = interleave_datasets([d1, d2, d3], probabilities=probabilities, seed=seed)
dataset["a"]
# 按所有的样本都出现过一次后,马上停止
d1 = Dataset.from_dict({"a": [0, 1, 2]})
d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
d3 = Dataset.from_dict({"a": [20, 21, 22]})
dataset = interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")
dataset["a"]
format
dataset.set_format(type="torch", columns=["input_ids", "token_type_ids", "attention_mask", "label"])
# 返回一个新dataset
dataset = dataset.with_format(type="torch", columns=["input_ids", "token_type_ids", "attention_mask", "label"])
# 查看
dataset.format
保存
from datasets import load_from_disk
encoded_dataset.save_to_disk("path/of/my/dataset/directory")
# 从本地load上来
reloaded_dataset = load_from_disk("path/of/my/dataset/directory")
encoded_dataset.to_csv("path/of/my/dataset.csv")
Dataset.to_json()
2.7 Preprocess处理
文本处理:用transformers的tokenizer
from transformers import AutoTokenizer
from datasets import load_dataset
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
dataset = load_dataset("rotten_tomatoes", split="train")
tokenizer(dataset[0]["text"])
{'input_ids': [101, 1103, 2067, 1110, 17348, 1106, 1129, 1103, 6880, 1432, 112, 188, 1207, 107, 14255, 1389, 107, 1105, 1115, 1119, 112, 188, 1280, 1106, 1294, 170, 24194, 1256, 3407, 1190, 170, 11791, 5253, 188, 1732, 7200, 10947, 12606, 2895, 117, 179, 7766, 118, 172, 15554, 1181, 3498, 6961, 3263, 1137, 188, 1566, 7912, 14516, 6997, 119, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
分词器返回一个包含三个项目的字典:
- input_ids:表示文本中各个标记的数字
- token_type_ids:如果有多个序列,指示一个标记属于哪个序列
- attention_mask:指示一个标记是否应该被掩盖(masked)
dataset.set_format(type="torch", columns=["input_ids", "token_type_ids", "attention_mask", "labels"])
音频信号:重新采样音频信号
from transformers import AutoFeatureExtractor
from datasets import load_dataset, Audio
feature_extractor = AutoFeatureExtractor.from_pretrained("facebook/wav2vec2-base-960h")
dataset = load_dataset("PolyAI/minds14", "en-US", split="train")
dataset[0]["audio"]
{'array': array([ 0. , 0.00024414, -0.00024414, ..., -0.00024414,
0. , 0. ], dtype=float32),
'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav',
'sampling_rate': 8000}
MInDS-14数据集卡会告诉您采样率为8kHz
Wav2Vec2模型卡说它是在16kHz语音音频上采样的。 这意味着您需要对MInDS-14数据集进行上采样以匹配模型的采样率
使用cast_column()函数并在Audio功能中设置sampling_rate参数以对音频信号进行上采样。 当您现在调用音频列时,它会被解码并重新采样到16kHz:
dataset = dataset.cast_column("audio", Audio(sampling_rate=16_000))
dataset[0]["audio"]
# 加速:使用 map() 函数将整个数据集重新采样到16kHz
def preprocess_function(examples):
audio_arrays = [x["array"] for x in examples["audio"]]
inputs = feature_extractor(
audio_arrays, sampling_rate=feature_extractor.sampling_rate, max_length=16000, truncation=True
)
return inputs
dataset = dataset.map(preprocess_function, batched=True)
图像增强
在图像数据集中,最常见的预处理操作之一是数据增强
(data augmentation),这是一种在不改变数据含义的情况下对图像引入随机变化的过程
这可以包括改变图像的颜色属性或随机裁剪图像。您可以自由选择任何数据增强库,并且🤗Datasets将帮助您将数据增强应用到您的数据集中
from transformers import AutoFeatureExtractor
from datasets import load_dataset, Image
from torchvision.transforms import RandomRotation
feature_extractor = AutoFeatureExtractor.from_pretrained("google/vit-base-patch16-224-in21k")
dataset = load_dataset("beans", split="train")
rotate = RandomRotation(degrees=(0, 90))
def transforms(examples):
examples["pixel_values"] = [rotate(image.convert("RGB")) for image in examples["image"]]
return examples
# 应用图像转换
dataset.set_transform(transforms)
dataset[0]["pixel_values"]
label id对齐
在Transformers库中,label id对齐(label ID alignment)通常指的是将标签与模型输出的预测结果对齐。当使用预训练模型进行分类或回归等任务时,通常需要将标签映射为模型期望的标签ID
具体来说,对于分类任务,常见的做法是将标签映射为整数标签ID。例如,如果有三个类别["cat", "dog", "bird"],可以将它们映射为[0, 1, 2],并将模型的输出标签预测结果与这些标签ID进行对齐
对于回归任务,可能需要将连续值的标签进行离散化或归一化,并将其映射为标签ID。例如,将一个连续的目标值范围映射为一组离散的标签ID
在使用Transformers库进行训练或评估时,您需要确保标签与模型的输出结果具有相同的标签ID对齐,以便正确计算损失、评估指标和解码预测结果
需要注意的是,标签ID对齐的具体实现方式可能因任务和库的使用而有所不同。在具体的代码实现中,您可能需要根据您的数据集和模型设置进行相应的标签ID对齐操作
from datasets import load_dataset
label2id = {"contradiction": 0, "neutral": 1, "entailment": 2}
mnli = load_dataset("glue", "mnli", split="train")
mnli_aligned = mnli.align_labels_with_mapping(label2id, "label")
2.8 构建数据集
如果您使用自己的数据,可能需要创建一个数据集。使用🤗Datasets创建数据集可以享受到该库的所有优势:快速加载和处理数据、流式处理大型数据集、内存映射等等。您可以使用🤗Datasets的低代码方法轻松快速地创建数据集,减少启动训练模型所需的时间。在许多情况下,只需将数据文件拖放到Hub上的数据集仓库中,就可以轻松完成
在本教程中,您将学习如何使用🤗Datasets的低代码方法创建各种类型的数据集:
- 基于文件夹的构建器(Folder-based builders),用于快速创建图像或音频数据集
- 使用from_方法从本地文件创建数据集
基于文件夹的构建器
有两个基于文件夹的构建器:ImageFolder(图像文件夹构建器)
和AudioFolder(音频文件夹构建器)
它们是低代码方法,可以快速创建包含数千个示例的图像、语音和音频数据集。它们非常适用于在扩展到更大的数据集之前,快速原型化计算机视觉和语音模型
基于文件夹的构建器会使用您的数据,并自动生成数据集的特征、划分和标签。具体来说:
- ImageFolder使用Image特征来解码图像文件。它支持许多图像扩展格式,例如jpg和png,还支持其他格式。您可以查看支持的图像扩展格式的完整列表
- AudioFolder使用Audio特征来解码音频文件。它支持音频扩展格式,如wav和mp3,您可以查看支持的音频扩展格式的完整列表
例如,如果您的图像数据集(对于音频数据集也是一样)存储如下所示:
pokemon/train/grass/bulbasaur.png
pokemon/train/fire/charmander.png
pokemon/train/water/squirtle.png
pokemon/test/grass/ivysaur.png
pokemon/test/fire/charmeleon.png
pokemon/test/water/wartortle.png
from datasets import ImageFolder
from datasets import AudioFolder
dataset = load_dataset("imagefolder", data_dir="/path/to/pokemon")
dataset = load_dataset("audiofolder", data_dir="/path/to/folder")
数据集中可以包含有关数据集的其他信息,例如文本标题或转录,可以使用包含在数据集文件夹中的metadata.csv文件来进行存储
metadata文件需要有一个file_name列,将图像或音频文件与其相应的元数据进行关联
file_name, text
bulbasaur.png, There is a plant seed on its back right from the day this Pokémon is born.
charmander.png, It has a preference for hot things.
squirtle.png, When it retracts its long neck into its shell, it squirts out water with vigorous force.
To learn more about each of these folder-based builders, check out the and ImageFolder or AudioFolder guides.
基于文件的构建器
使用 from_generator() 方法是从生成器创建数据集的最节省内存的方式,这是由于生成器的迭代行为。这在处理非常大的数据集时特别有用,因为数据集是逐步在磁盘上生成的,然后进行内存映射,这样可以避免将整个数据集加载到内存中
from datasets import Dataset
def gen():
yield {"pokemon": "bulbasaur", "type": "grass"}
yield {"pokemon": "squirtle", "type": "water"}
ds = Dataset.from_generator(gen)
ds[0]
{"pokemon": "bulbasaur", "type": "grass"}
基于生成器的IterableDataset需要使用for循环进行迭代,例如:
from datasets import IterableDataset
ds = IterableDataset.from_generator(gen)
for example in ds:
print(example)
{"pokemon": "bulbasaur", "type": "grass"}
{"pokemon": "squirtle", "type": "water"}
使用from_dict()方法是从字典创建数据集的简单直接的方式:
from datasets import Dataset
ds = Dataset.from_dict({"pokemon": ["bulbasaur", "squirtle"], "type": ["grass", "water"]})
ds[0]
{"pokemon": "bulbasaur", "type": "grass"}
2.9 分享数据集
点击您的个人资料并选择新的数据集以创建一个新的数据集仓库。 为您的数据集选择一个名称,并选择它是一个公共数据集还是私有数据集。公共数据集对任何人可见,而私有数据集只能由您或您组织的成员查看
一旦您的数据集存储在Hub上,任何人都可以使用load_dataset()函数加载它:
from datasets import load_dataset
dataset = load_dataset("stevhliu/demo")
使用Python进行上传
喜欢以编程方式上传数据集的用户可以使用huggingface_hub库。该库允许用户从Python中与Hub进行交互
首先安装该库:
pip install huggingface_hub
要在Hub上使用Python上传数据集,您需要登录到您的Hugging Face账户:
huggingface-cli login
使用push_to_hub()函数帮助您将文件添加、提交和推送到您的仓库:
from datasets import load_dataset
dataset = load_dataset("stevhliu/demo")
# dataset = dataset.map(...) # 在这里进行所有的数据处理
dataset.push_to_hub("stevhliu/processed_demo")
如果要将数据集设置为私有,请将private参数设置为True。该参数仅在首次创建仓库时有效
dataset.push_to_hub("stevhliu/private_processed_demo", private=True)
3 评估指标
3.1 安装
一种用于轻松评估机器学习模型和数据集的库
只需一行代码,您就可以访问数十种不同领域(自然语言处理、计算机视觉、强化学习等)的评估方法
无论是在本地机器上还是在分布式训练环境中,您都可以以一种一致且可重复的方式评估您的模型
安装
pip install evaluate
测试
python -c "import evaluate; print(evaluate.load('exact_match').compute(references=['hello'], predictions=['hello']))"
{'exact_match': 1.0}
3.2 快速开始
3.2.1 指标种类
🤗Evaluate提供了广泛的评估工具。它涵盖了文本、计算机视觉、音频等多种形式,并提供了用于评估模型或数据集的工具。这些工具分为三个类别
评估类型 典型的机器学习流程涉及到不同方面的评估,对于每个方面,🤗 Evaluate都提供了相应的工具:
- 指标(Metric):用于评估模型的性能,通常涉及模型的预测结果和一些真实标签。您可以在evaluate-metric中找到所有集成的指标
- 比较(Comparison):用于比较两个模型。可以通过将它们的预测结果与真实标签进行比较并计算它们的一致性来进行比较。您可以在evaluate-comparison中找到所有集成的比较方法
- 测量(Measurement):数据集和训练在其上的模型同样重要。通过测量可以研究数据集的特性。您可以在evaluate-measurement中找到所有集成的测量方法
每个评估模块都作为一个Space存储在Hugging Face Hub上。它们提供了一个交互式小部件和一个文档卡片,用于记录其使用方法和限制
评估工具之间的关系和区别
Evaluate库中的Metric(指标)
、Comparison(比较)
和Measurement(测量)
是三种不同的评估工具,用于评估机器学习模型和数据集。它们之间的关系和区别如下:
- Metric(指标):
- 用途:用于评估模型的性能
- 具体含义:指标通过将模型的预测结果与真实标签进行比较来衡量模型的表现
- 示例:准确率、精确率、召回率、F1分数等
- 目的:提供了对模型性能的定量评估,帮助衡量模型在特定任务上的表现
- Comparison(比较):
- 用途:用于比较两个模型之间的差异
- 具体含义:比较工具将两个模型的预测结果与真实标签进行对比,计算它们之间的一致性或差异程度
- 示例:一致性指标、相对误差等
- 目的:帮助评估不同模型之间的性能差异,找到更好的模型或进行模型选择
- Measurement(测量):
- 用途:用于研究数据集的属性和特性
- 具体含义:测量工具用于对数据集进行分析,探索数据集的结构、分布、偏差等方面的信息
- 示例:数据集大小、样本分布、类别不平衡度等
- 目的:提供对数据集的详细了解,帮助了解数据集的特点和潜在问题
这三种评估工具在Evaluate库中各自独立,用于不同的评估目的。Metric用于衡量模型性能,Comparison用于比较不同模型之间的性能差异,Measurement用于研究和了解数据集的特性。通过使用这些工具,可以全面评估和理解机器学习模型和数据集的表现和特点
3.2.2 指标加载
官方+社区 指标
在使用Hugging Face的Evaluate库加载评估工具时,可以通过显式指定评估的类型来确保加载正确的工具。这可以防止名称冲突或混淆,确保您使用的是期望的评估工具
import evaluate
accuracy = evaluate.load("accuracy")
# 显式指定评估的类型
word_length = evaluate.load("word_length", module_type="measurement")
# 社区指标
element_count = evaluate.load("lvwerra/element_count", module_type="measurement")
查看可用的模块方法
evaluate.list_evaluation_modules(
module_type="comparison",
include_community=False,
with_details=True)
[{'name': 'mcnemar', 'type': 'comparison', 'community': False, 'likes': 1},
{'name': 'exact_match', 'type': 'comparison', 'community': False, 'likes': 0}]
3.2.3 指标计算
计算指标
当涉及到计算实际得分时,有两种主要的方法:
一体式计算(All-in-one):通过一次性将所有必要的输入传递给compute()方法来计算得分
accuracy.compute(references=[0,1,0,1], predictions=[1,0,0,1]) {'accuracy': 0.5}
逐步计算(Incremental):通过使用EvaluationModule.add()或EvaluationModule.add_batch()将必要的输入逐步添加到模块中,然后在最后使用 EvaluationModule.compute()计算得分
# add的方式 for ref, pred in zip([0,1,0,1], [1,0,0,1]): accuracy.add(references=ref, predictions=pred) accuracy.compute() {'accuracy': 0.5} # add_batch的方式 for refs, preds in zip([[0,1],[0,1]], [[1,0],[0,1]]): accuracy.add_batch(references=refs, predictions=preds) accuracy.compute() {'accuracy': 0.5}
在你需要以批量方式从模型中获取预测结果时特别有用:
for model_inputs, gold_standards in evaluation_dataset:
predictions = model(model_inputs)
metric.add_batch(references=gold_standards, predictions=predictions)
metric.compute()
分布式指标
在分布式环境中计算指标可能会有些棘手。指标评估是在不同的数据子集上的单独Python进程或节点中执行的
通常情况下,当一个指标得分是可加的()时,你可以使用分布式的reduce操作来收集每个数据子集的得分。但是当指标是非可加的()时,情况就不那么简单了。例如,你不能将每个数据子集的F1分数相加作为最终的指标
克服这个问题的常见方法是回退到单进程评估,但指标在单个GPU上进行评估,这会导致效率降低
- 🤗Evaluate通过仅在第一个节点上计算最终的指标来解决了这个问题
- 预测结果和参考结果被分别计算并提供给每个节点的指标,这些结果暂时存储在Apache Arrow表中,避免了GPU或CPU内存的混乱
- 当你准备使用compute()计算最终指标时,第一个节点能够访问所有其他节点上存储的预测结果和参考结果。一旦它收集到所有的预测结果和参考结果,compute()将进行最终的指标评估
这个解决方案使得🤗Evaluate能够在分布式设置中执行分布式预测,这对于提高评估速度非常重要。同时,你还可以使用复杂的非可加指标,而不浪费宝贵的GPU或CPU内存
组合评估
通常情况下,我们不仅想评估单个指标,而是想评估一系列不同的指标,以捕捉模型性能的不同方面
例如,对于分类问题,除了准确度外,通常还会计算F1分数、召回率和精确度,以便更好地了解模型的性能。当然,你可以加载一系列指标并依次调用它们。然而,一种更方便的方法是使用combine()函数将它们捆绑在一起:
clf_metrics = evaluate.combine(["accuracy", "f1", "precision", "recall"])
clf_metrics.compute(predictions=[0, 1, 0], references=[0, 1, 1])
{
'accuracy': 0.667,
'f1': 0.667,
'precision': 1.0,
'recall': 0.5
}
自动化评估
使用evaluate.evaluator()提供了自动化的评估功能,只需要一个模型、数据集和度量指标,与EvaluationModules中的度量指标相比,它不需要模型的预测结果。因此,使用给定的度量指标在数据集上评估模型更容易,因为推理过程是在内部处理的
为了实现这一点,它使用了transformers库中的pipeline抽象。然而,只要符合pipeline接口,你也可以使用自己的框架
from transformers import pipeline
from datasets import load_dataset
from evaluate import evaluator
import evaluate
为了使用evaluator进行评估,让我们加载一个基于IMDb训练的transformers pipeline(但你也可以传递自己的自定义推理类来适应任何遵循pipeline调用API的框架),并使用IMDb的测试集和准确度度量指标进行评估
pipe = pipeline("text-classification", model="lvwerra/distilbert-imdb", device=0)
data = load_dataset("imdb", split="test").shuffle().select(range(1000))
metric = evaluate.load("accuracy")
task_evaluator = evaluator("text-classification")
results = task_evaluator.compute(model_or_pipeline=pipe, data=data, metric=metric,
label_mapping={"NEGATIVE": 0, "POSITIVE": 1},)
{'accuracy': 0.934}
仅仅计算度量指标的值通常还不足以知道一个模型是否显著优于另一个模型。通过使用自助法(bootstrapping)
,evaluate计算置信区间和标准误差,这有助于估计分数的稳定性
results = eval.compute(model_or_pipeline=pipe, data=data, metric=metric,
label_mapping={"NEGATIVE": 0, "POSITIVE": 1},
strategy="bootstrap", n_resamples=200)
{'accuracy':
{
'confidence_interval': (0.906, 0.9406749892841922),
'standard_error': 0.00865213251082787,
'score': 0.923
}
}
评估器期望数据输入具有"text"和"label"列。如果您的数据集不同,可以使用关键字参数input_column="text"和label_column="label"来提供列名
目前只支持"text-classification"任务,将来可能会添加更多的任务类型
3.2.4 结果存储
评估结果save和push
保存和分享评估结果是一个重要的步骤。我们提供evaluate.save()函数来方便地保存指标结果。你可以传递一个特定的文件名或目录。在后一种情况下,结果将保存在一个带有自动创建的文件名的文件中
除了目录或文件名,该函数还接受任意的键值对作为输入,并将它们存储在一个JSON文件中
result = accuracy.compute(references=[0,1,0,1], predictions=[1,0,0,1])
hyperparams = {"model": "bert-base-uncased"}
evaluate.save("./results/", experiment="run 42", **result, **hyperparams)
PosixPath('results/result-2022_05_30-22_09_11.json')
# result-2022_05_30-22_09_11.json
{
"experiment": "run 42",
"accuracy": 0.5,
"model": "bert-base-uncased",
"_timestamp": "2022-05-30T22:09:11.959469",
"_git_commit_hash": "123456789abcdefghijkl",
"_evaluate_version": "0.1.0",
"_python_version": "3.9.12 (main, Mar 26 2022, 15:51:15) \n[Clang 13.1.6 (clang-1316.0.21.2)]",
"_interpreter_path": "/Users/leandro/git/evaluate/env/bin/python"
}
除了指定的字段,它还包含有用的系统信息,用于重现结果,你还应该将它们报告到模型在Hub上的存储库中
evaluate.push_to_hub(
model_id="huggingface/gpt2-wikitext2", # model repository on hub
metric_value=0.5, # metric value
metric_type="bleu", # metric name, e.g. accuracy.name
metric_name="BLEU", # pretty name which is displayed
dataset_type="wikitext", # dataset name on the hub
dataset_name="WikiText", # pretty name
dataset_split="test", # dataset split used
task_type="text-generation", # task id, see https://github.com/huggingface/datasets/blob/master/src/datasets/utils/resources/tasks.json
task_name="Text Generation" # pretty name for task
)
3.2.5 可视化
当比较多个模型时,仅通过查看它们的得分往往很难发现它们之间的差异。而且通常情况下,并没有一个单一的最佳模型,而是在准确性和延迟等方面存在着权衡,因为较大的模型可能具有更好的性能但也更慢。我们正在逐步添加不同的可视化方法,例如绘图,以便更轻松地选择适合特定用例的最佳模型。
例如,如果您有多个模型的结果列表(以字典形式),您可以将它们传递给radar_plot()函数进行可视化:
import evaluate
from evaluate.visualization import radar_plot
data = [
{"accuracy": 0.99, "precision": 0.8, "f1": 0.95, "latency_in_seconds": 33.6},
{"accuracy": 0.98, "precision": 0.87, "f1": 0.91, "latency_in_seconds": 11.2},
{"accuracy": 0.98, "precision": 0.78, "f1": 0.88, "latency_in_seconds": 87.6},
{"accuracy": 0.88, "precision": 0.78, "f1": 0.81, "latency_in_seconds": 101.6}
]
model_names = ["Model 1", "Model 2", "Model 3", "Model 4"]
plot = radar_plot(data=data, model_names=model_names)
plot.show()
3.2.6 选择合适指标
评估指标可以分为三个高级类别:
通用指标:适用于各种情况和数据集的指标,例如精确度和准确度
precision_metric = evaluate.load("precision") results = precision_metric.compute(references=[0, 1], predictions=[0, 1]) print(results) {'precision': 1.0}
任务特定指标:仅适用于特定任务的指标,例如机器翻译(通常使用BLEU或ROUGE指标进行评估)或命名实体识别(通常使用seqeval进行评估)
数据集特定指标:旨在衡量模型在特定基准数据集上的性能,例如GLUE基准测试具有专门的评估指标
4 transformers
4.1 概述
🤗 Transformers提供了API和工具,可轻松下载和训练最先进的预训练模型。使用预训练模型可以减少计算成本、碳足迹,并节省从头开始训练模型所需的时间和资源。这些模型支持不同领域的常见任务,包括:
- 📝 自然语言处理:文本分类、命名实体识别、问答系统、语言建模、摘要生成、翻译、多项选择和文本生成
- 🖼️ 计算机视觉:图像分类、目标检测和分割
- 🗣️ 音频:自动语音识别和音频分类
- 🐙 多模态:表格问答、光学字符识别、从扫描文档中提取信息、视频分类和视觉问答
🤗 Transformers支持在PyTorch、TensorFlow和JAX之间进行框架互操作。这提供了在模型的不同阶段使用不同框架的灵活性;可以在一个框架中用三行代码训练模型,然后在另一个框架中加载模型进行推理。模型还可以导出为ONNX和TorchScript等格式,以便在生产环境中进行部署
4.2 安装
pip install transformers datasets
4.3 快速开始
4.3.1 Pipeline
pipeline()是使用预训练模型进行推理的最简单和最快捷的方法。您可以直接使用pipeline()进行许多任务的推理,涵盖了不同的模态,下表列出了其中一些任务
Task | Description | Modality | Pipeline identifier |
---|---|---|---|
Text classification | assign a label to a given sequence of text | NLP | pipeline(task=“sentiment-analysis”) |
Text generation | generate text given a prompt | NLP | pipeline(task=“text-generation”) |
Summarization | generate a summary of a sequence of text or document | NLP | pipeline(task=“summarization”) |
Image classification | assign a label to an image | CV | pipeline(task=“image-classification”) |
Image segmentation | assign a label to each individual pixel of an image (supports semantic, panoptic, and instance segmentation) | CV | pipeline(task=“image-segmentation”) |
Object detection | predict the bounding boxes and classes of objects in an image | CV | pipeline(task=“object-detection”) |
Audio classification | assign a label to some audio data | Audio | pipeline(task=“audio-classification”) |
Automatic speech recognition | transcribe speech into text | Audio | pipeline(task=“automatic-speech-recognition”) |
Visual question answering | answer a question about the image, given an image and a question | Multimodal | pipeline(task=“vqa”) |
Document question answering | answer a question about a document, given an image and a question | Multimodal | pipeline(task=“document-question-answering”) |
Image captioning | generate a caption for a given image | Multimodal | pipeline(task=“image-to-text”) |
基本使用
首先,通过创建pipeline()的实例并指定要使用的任务,开始使用它。在本指南中,我们以情感分析的pipeline()为例:
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
pipeline()会下载并缓存用于情感分析的默认预训练模型和分词器。现在,您可以在目标文本上使用分类器了:
classifier("We are very happy to show you the 🤗 Transformers library.")
[{'label': 'POSITIVE', 'score': 0.9998}]
如果您有多个输入,请将输入作为列表传递给pipeline(),以返回一个字典列表
results = classifier(["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."])
for result in results:
print(f"label: {result['label']}, with score: {round(result['score'], 4)}")
label: POSITIVE, with score: 0.9998
label: NEGATIVE, with score: 0.5309
pipeline()还可以对任何您喜欢的任务迭代整个数据集。在这个例子中,让我们选择自动语音识别作为我们的任务
import torch
from transformers import pipeline
speech_recognizer = pipeline("automatic-speech-recognition", model="facebook/wav2vec2-base-960h")
加载您想要迭代的音频数据集(有关更多详细信息,请参阅🤗 Datasets快速入门)。例如,加载MInDS-14数据集:
from datasets import load_dataset, Audio
dataset = load_dataset("PolyAI/minds14", name="en-US", split="train")
您需要确保数据集的采样率与facebook/wav2vec2-base-960h 训练时使用的采样率相匹配
dataset = dataset.cast_column("audio", Audio(sampling_rate=speech_recognizer.feature_extractor.sampling_rate))
调用"audio"列时,音频文件会自动加载和重新采样。从前四个样本中提取原始波形数组,并将其作为列表传递给pipeline:
result = speech_recognizer(dataset[:4]["audio"])
print([d["text"] for d in result])
['I WOULD LIKE TO SET UP A JOINT ACCOUNT WITH MY PARTNER HOW DO I PROCEED WITH DOING THAT', "FONDERING HOW I'D SET UP A JOIN TO HELL T WITH MY WIFE AND WHERE THE AP MIGHT BE", "I I'D LIKE TOY SET UP A JOINT ACCOUNT WITH MY PARTNER I'M NOT SEEING THE OPTION TO DO IT ON THE APSO I CALLED IN TO GET SOME HELP CAN I JUST DO IT OVER THE PHONE WITH YOU AND GIVE YOU THE INFORMATION OR SHOULD I DO IT IN THE AP AN I'M MISSING SOMETHING UQUETTE HAD PREFERRED TO JUST DO IT OVER THE PHONE OF POSSIBLE THINGS", 'HOW DO I FURN A JOINA COUT']
对于输入较大的更大数据集(如语音或视觉数据),您可以将生成器传递给pipeline,而不是将其作为列表加载到内存中
在pipeline中使用其他模型和分词器pipeline()可以适应Hub中的任何模型,这使得对pipeline()进行其他用途的调整变得容易
例如,如果您想要一个能够处理法语文本的模型,请使用Hub上的标签来过滤合适的模型。通过对过滤结果进行排序,您可以获得一个针对法语文本进行情感分析的多语言BERT模型
在pipeline中使用另一个模型和分词器
pipeline()可以适应Hub中的任何模型,这使得将pipeline()适应其他用例变得容易
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
classifier = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
classifier("Nous sommes très heureux de vous présenter la bibliothèque 🤗 Transformers.")
4.3.2 AutoClass
AutoClass是一种快捷方式,它可以根据模型的名称或路径自动获取预训练模型的架构。您只需要选择与您的任务相匹配的AutoClass和相应的预处理类
AutoTokenizer
AutoTokenizer分词器负责将文本预处理为模型输入的数字数组。有多个规则来规定分词的过程,包括如何拆分一个单词以及以何种级别拆分单词
最重要的是,您需要使用相同的模型名称来实例化一个分词器,以确保您使用了与预训练模型相同的分词规则
使用AutoTokenizer加载一个分词器
将return_tensors
参数设置为pt
以返回适用于PyTorch的张量,或者设置为tf
以返回适用于TensorFlow的张量
from transformers import AutoTokenizer
model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
tokenizer = AutoTokenizer.from_pretrained(model_name)
encoding = tokenizer("We are very happy to show you the 🤗 Transformers library.", return_tensors="pt")
{'input_ids': [101, 11312, 10320, 12495, 19308, 10114, 11391, 10855, 10103, 100, 58263, 13299, 119, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
tokenizer.decode(encoding["input_ids"])
"We are very happy to show you the 🤗 Transformers library."
分词器返回一个包含三个项目的字典:
- input_ids:表示文本中各个标记的数字
- token_type_ids:如果有多个序列,指示一个标记属于哪个序列
- attention_mask:指示一个标记是否应该被掩盖(masked)
分词器还可以接受一个输入列表,并对文本进行填充和截断,以返回具有统一长度的批处理数据
pt_batch = tokenizer(
["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."],
padding=True,
truncation=True,
max_length=512,
return_tensors="pt")
pad + truncation
# padding
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
# truncation 将truncation参数设置为True,可以将序列截断为模型所能接受的最大长度
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
AutoModel
🤗Transformers提供了一种简单而统一的方法来加载预训练模型实例。这意味着您可以像加载AutoTokenizer一样加载AutoModel
唯一的区别是选择正确的AutoModel来适应任务。对于文本(或序列)分类,您应该加载AutoModelForSequenceClassification
from transformers import AutoModelForSequenceClassification
model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
pt_model = AutoModelForSequenceClassification.from_pretrained(model_name)
pt_outputs = pt_model(**pt_batch)
模型将最终的激活值存储在logits属性中。应用softmax函数到logits上以获取概率值
from torch import nn
pt_predictions = nn.functional.softmax(pt_outputs.logits, dim=-1)
tensor([[0.0021, 0.0018, 0.0115, 0.2121, 0.7725],
[0.2084, 0.1826, 0.1969, 0.1755, 0.2365]], grad_fn=<SoftmaxBackward0>)
在huggingface库中,AutoModel类可以根据给定的checkpoint自动选择并加载适合的模型。它支持各种不同的模型架构,包括:
- AutoModel: 用于通用的模型加载,根据checkpoint自动选择适合的模型架构
- AutoModelForSequenceClassification: 用于序列分类任务的模型,如文本分类
- AutoModelForQuestionAnswering: 用于问答任务的模型,如阅读理解
- AutoModelForTokenClassification: 用于标记分类任务的模型,如命名实体识别
- AutoModelForMaskedLM: 用于遮蔽语言建模任务的模型,如BERT
- AutoModelForCausalLM: 用于有因果关系的语言建模任务的模型,如GPT
- AutoModelForImageClassification: 用于图像分类任务的模型,如ResNet
- AutoModelForImageSegmentation: 用于图像分割任务的模型,如Mask R-CNN
这些仅是AutoModel类的一些示例,实际上还有更多可用的模型架构。您可以根据具体的任务需求选择适合的AutoModel类进行加载和使用
其他的Auto类
AutoImageProcessor
对于视觉任务,图像处理器将图像处理为正确的输入格式
from transformers import AutoImageProcessor
image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
AutoFeatureExtractor
对于音频任务,特征提取器将音频信号处理为正确的输入格式
from transformers import AutoFeatureExtractor
feature_extractor = AutoFeatureExtractor.from_pretrained(
"ehcalabres/wav2vec2-lg-xlsr-en-speech-emotion-recognition"
)
AutoProcessor
多模态任务需要一个处理器来结合两种类型的预处理工具。例如,LayoutLMV2模型需要一个图像处理器来处理图像,还需要一个分词器来处理文本;处理器将两者结合起来
from transformers import AutoProcessor
processor = AutoProcessor.from_pretrained("microsoft/layoutlmv2-base-uncased")
模型保存
一旦您的模型经过微调,您可以使用PreTrainedModel.save_pretrained()将其与其标记器一起保存起来:
# 模型+分词器 保存
pt_save_directory = "./pt_save_pretrained"
tokenizer.save_pretrained(pt_save_directory)
pt_model.save_pretrained(pt_save_directory)
# 加载
pt_model = AutoModelForSequenceClassification.from_pretrained(pt_save_directory)
4.3.3 AutoConfig
您可以修改模型的配置类来更改模型的构建方式。配置类指定了模型的属性,例如隐藏层的数量或注意力头数
当您从自定义配置类初始化模型时,您将从头开始。模型的属性将被随机初始化,您需要在使用模型之前对其进行训练以获得有意义的结果
首先导入AutoConfig,然后加载要修改的预训练模型。在AutoConfig.from_pretrained()中,您可以指定要更改的属性,例如注意力头的数量:
from transformers import AutoConfig
from transformers import AutoModel
my_config = AutoConfig.from_pretrained("distilbert-base-uncased", n_heads=12)
my_model = AutoModel.from_config(my_config)
4.3.4 tokenizer
4.3.5 Trainer
对于PyTorch,所有模型都是标准的torch.nn.Module,因此您可以在任何典型的训练循环中使用它们。虽然您可以编写自己的训练循环,但🤗Transformers提供了Trainer
类,其中包含基本的训练循环,并添加了其他功能,如分布式训练、混合精度等
根据您的任务,通常会向Trainer传递以下参数:
PreTrainedModel或
torch.nn.Module
对象from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased")
TrainingArguments包含了可以修改的模型超参数,比如学习率、批大小和训练的轮数。如果你不指定任何训练参数,将使用默认值
from transformers import TrainingArguments training_args = TrainingArguments( output_dir="path/to/save/folder/", learning_rate=2e-5, per_device_train_batch_size=8, per_device_eval_batch_size=8, num_train_epochs=2, )
Preprocessing类,例如tokenizer(标记器)、image processor(图像处理器)、feature extractor(特征提取器)或processor(处理器)
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
加载数据集
from datasets import load_dataset dataset = load_dataset("rotten_tomatoes") # doctest: +IGNORE_RESULT
创建一个函数来对数据集进行标记化处理,然后使用
map
函数将其应用于整个数据集def tokenize_dataset(dataset): return tokenizer(dataset["text"]) dataset = dataset.map(tokenize_dataset, batched=True)
使用
DataCollatorWithPadding
来从数据集中创建一个批次的示例from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
DataCollatorWithPadding
是Hugging Face的transformers
库中的一个类,用于在训练过程中创建批次数据。它的作用是将不同长度的样本填充到相同长度,以便能够同时进行批处理具体来说,
DataCollatorWithPadding
会根据给定的数据集,找到其中最长的样本,并将其他样本填充到相同的长度。填充通常使用特定的填充令牌(token)来完成,这样模型在处理时可以识别出填充部分,并进行相应的处理使用
DataCollatorWithPadding
可以确保批次数据的长度一致,从而提高训练效率,并避免由于不同长度样本导致的错误
现在将所有这些类组合在Trainer中
from transformers import Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
tokenizer=tokenizer,
data_collator=data_collator,
) # doctest: +SKIP
trainer.train()
Trainer类提供了自定义训练循环行为的方法,你可以通过继承Trainer类并重写其中的方法来实现自定义行为。这样你就可以定制诸如损失函数、优化器和学习率调度器等功能。你可以参考Trainer类的文档了解可以重写的方法
另一种定制训练循环的方式是使用回调函数(Callbacks)。你可以使用回调函数与其他库进行集成,监视训练过程并报告进展,或在必要时提前停止训练。回调函数不会修改训练循环本身的行为。如果你需要定制损失函数等内容,你需要继承Trainer类来实现
5 教程
5.1 模型训练
使用预训练模型有很多好处。它可以减少计算成本和碳足迹,并且可以让您使用最先进的模型,而无需从头开始训练
🤗Transformers提供了对各种任务的数千个预训练模型的访问。当您使用预训练模型时,您可以在特定于您任务的数据集上进行微调训练。这被称为微调
,是一种非常强大的训练技术
数据准备
from datasets import load_dataset
from transformers import AutoTokenizer
# 1. 加载数据集
dataset = load_dataset("yelp_review_full")
dataset["train"][100]
{'label': 0,
'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...that takes something special!\\nThe cashier took my friends\'s order, then promptly ignored me. I had to force myself in front of a cashier who opened his register to wait on the person BEHIND me. I waited over five minutes for a gigantic order that included precisely one kid\'s meal. After watching two people who ordered after me be handed their food, I asked where mine was. The manager started yelling at the cashiers for \\"serving off their orders\\" when they didn\'t have their food. But neither cashier was anywhere near those controls, and the manager was the one serving food to customers and clearing the boards.\\nThe manager was rude when giving me my order. She didn\'t make sure that I had everything ON MY RECEIPT, and never even had the decency to apologize that I felt I was getting poor service.\\nI\'ve eaten at various McDonalds restaurants for over 30 years. I\'ve worked at more than one location. I expect bad days, bad moods, and the occasional mistake. But I have yet to have a decent experience at this store. It will remain a place I avoid unless someone in my party needs to avoid illness from low blood sugar. Perhaps I should go back to the racially biased service of Steak n Shake instead!'}
# 可以创建一个较小的数据集子集,用于微调,以减少所需的时间
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
# 2. 分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
Train with PyTorch Trainer
from transformers import AutoModelForSequenceClassification
from transformers import TrainingArguments
import numpy as np
import evaluate
# 1. 加载模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)
# 2. 定义训练参数 在训练参数中指定evaluation_strategy参数,以在每个epoch结束时报告评估指标
training_args = TrainingArguments(output_dir="test_trainer", evaluation_strategy="epoch")
# 3. 加载评估器
metric = evaluate.load("accuracy")
# 在计算度量标准的时候调用compute,以计算您的预测的准确率。在将预测结果传递给compute之前,您需要将预测结果转换为logits
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
# 4. 定义Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics,
)
# 5. 开始训练
trainer.train()
Train in native PyTorch
Trainer负责训练循环,并允许您通过一行代码对模型进行微调。对于喜欢编写自己的训练循环的用户,您也可以在原生PyTorch中对🤗Transformers模型进行微调
from torch.utils.data import DataLoader
from transformers import AutoModelForSequenceClassification
from torch.optim import AdamW
from transformers import get_scheduler
import torch
from tqdm.auto import tqdm
import evaluate
# 1. 数据集预处理
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
# 2. 定义DataLoader
train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)
# 3. 加载模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)
# 4. 定义优化器
optimizer = AdamW(model.parameters(), lr=5e-5)
# 5. 定义scheduler
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps
)
# 6. 移动模型到指定设备
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
# 7. 开始训练
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
# 8. 验证集评估
metric = evaluate.load("accuracy")
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)
metric.add_batch(predictions=predictions, references=batch["labels"])
metric.compute()
5.2 分布式加速
🤗Accelerate是Hugging Face提供的用于简化分布式训练的库。它旨在使分布式训练更加容易和高效,支持多种深度学习框架,包括PyTorch和TensorFlow
Accelerate提供了以下功能:
- 数据并行:Accelerate使用
accelerator.DataParallel
类来实现数据并行,可以在多个GPU上同时训练模型 - 混合精度训练:Accelerate支持自动混合精度训练,通过将模型参数和梯度转换为半精度浮点数来减少内存占用和计算量
- 分布式训练:Accelerate使用
accelerator.DistributedDataParallel
类来实现分布式训练,可以在多个机器上并行训练模型 - 训练循环的自动管理:Accelerate提供了一个
accelerator.Trainer
类,它封装了训练循环,自动处理数据加载、前向传播、反向传播、优化器更新等过程
使用Accelerate可以简化分布式训练的配置和管理,使用户能够更轻松地利用多个GPU或多台机器进行训练,并获得更高的训练效率
安装
pip install accelerate
示例代码,以下代码只列出改变的部分代码
只需要在训练循环中添加四行额外的代码即可启用分布式训练
from accelerate import Accelerator
# 1. 定义加速器
accelerator = Accelerator()
# 2. dataloader包装
train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
train_dataloader, eval_dataloader, model, optimizer
)
# 3. 反向传播
for epoch in range(num_epochs):
for batch in train_dataloader:
outputs = model(**batch)
loss = outputs.loss
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
完整代码如下
+ from accelerate import Accelerator
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler
+ accelerator = Accelerator()
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)
+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+ train_dataloader, eval_dataloader, model, optimizer
+ )
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps
)
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
- batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
- loss.backward()
+ accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
5.3 示例代码
6 PEFT模块
🤗PEFT
,即Parameter-Efficient Fine-Tuning(参数高效微调),是一个用于高效地将预训练语言模型(PLM)适应于各种下游应用的库,而无需对所有模型参数进行微调
PEFT方法只微调少量的(额外的)模型参数,显著降低了计算和存储成本,因为对大规模PLM进行完整微调代价过高。最近的最先进的PEFT技术达到了与完整微调相当的性能
PEFT与🤗Accelerate库无缝集成,用于利用DeepSpeed和Big Model Inference进行大规模模型微调
Supported methods (截至23-06-15)
- LoRA: LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS
- Prefix Tuning: Prefix-Tuning: Optimizing Continuous Prompts for Generation, P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks
- P-Tuning: GPT Understands, Too
- Prompt Tuning: The Power of Scale for Parameter-Efficient Prompt Tuning
- AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning
- LLaMA-Adapter: Efficient Fine-tuning of Language Models with Zero-init Attention
6.1 基本使用
6.1.1 加载PEFT adapter
从🤗Transformers加载并使用PEFT adapter模型时,请确保Hub仓库或本地目录包含一个adapter_config.json
文件和adapter
权重,如上面的示例图片所示
然后,你可以使用AutoModelFor类加载PEFT adapter模型。例如,要加载用于因果语言建模的PEFT adapter模型:
- 指定PEFT模型的id
- 将其传递给AutoModelForCausalLM类
from transformers import AutoModelForCausalLM, AutoTokenizer
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id)
你可以使用AutoModelFor类或基础模型类(如OPTForCausalLM或LlamaForCausalLM)加载PEFT adapter
你也可以通过调用load_adapter
方法加载PEFT adapter:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "facebook/opt-350m"
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(model_id)
model.load_adapter(peft_model_id)
6.1.2 以8或4位加载
bitsandbytes集成支持8位和4位精度数据类型,这对于加载大型模型很有用,因为它可以节省内存
添加load_in_8bit
或load_in_4bit
参数到from_pretrained()
并设置device_map="auto"
以有效地将模型分配到你的硬件:
from transformers import AutoModelForCausalLM, AutoTokenizer
peft_model_id = "ybelkada/opt-350m-lora"
model = AutoModelForCausalLM.from_pretrained(peft_model_id, device_map="auto", load_in_8bit=True)
6.1.3 添加新adapter
你可以使用~peft.PeftModel.add_adapter
向具有现有adapter的模型添加一个新的adapter,只要新的adapter是与当前的adapter类型相同的
例如,如果你的模型已经附加了一个LoRA adapter:
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import LoraConfig
model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config = LoraConfig(
target_modules=["q_proj", "k_proj"],
init_lora_weights=False
)
model.add_adapter(lora_config, adapter_name="adapter_1")
要添加新的adapter:
# 使用相同配置附加新的 adapter
model.add_adapter(lora_config, adapter_name="adapter_2")
现在你可以使用~peft.PeftModel.set_adapter
设置使用哪个adapter:
# 使用 adapter_1
model.set_adapter("adapter_1")
output = model.generate(**inputs)
print(tokenizer.decode(output_disabled[0], skip_special_tokens=True))
# 使用 adapter_2
model.set_adapter("adapter_2")
output_enabled = model.generate(**inputs)
print(tokenizer.decode(output_enabled[0], skip_special_tokens=True))
6.1.4 启用和禁用adapters
一旦你向模型中添加了一个adapter,你可以启用或禁用adapter模块。要启用adapter模块:
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import PeftConfig
model_id = "facebook/opt-350m"
adapter_model_id = "ybelkada/opt-350m-lora"
tokenizer = AutoTokenizer.from_pretrained(model_id)
text = "Hello"
inputs = tokenizer(text, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(model_id)
peft_config = PeftConfig.from_pretrained(adapter_model_id)
# 初始化为随机权重
peft_config.init_lora_weights = False
model.add_adapter(peft_config)
model.enable_adapters()
output = model.generate(**inputs)
要禁用adapter模块:
model.disable_adapters()
output = model.generate(**inputs)
6.1.5 训练PEFT adapter
Trainer类支持PEFT adapters,因此你可以为你的特定用例训练adapter,只需要添加几行代码
例如,训练LoRA adapter,如果你不熟悉使用Trainer细调模型,请Trainer查看教程
使用任务类型和超参数定义adapter配置(查看~peft.LoraConfig
以了解更多关于超参数的信息)
from peft import LoraConfig
peft_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.1,
r=64,
bias="none",
task_type="CAUSAL_LM",
)
将adapter添加到模型中
model.add_adapter(peft_config)
现在你可以将模型传递给Trainer
trainer = Trainer(model=model, ...)
trainer.train()
要保存你训练的adapter并重新加载它:
model.save_pretrained(save_dir)
model = AutoModelForCausalLM.from_pretrained(save_dir)
向PEFT adapter添加额外的可训练层
你也可以在附有adapter的模型上通过在你的PEFT配置中传递modules_to_save
来调整额外的可训练adapter
例如,如果你想在带有LoRA adapter的模型上同时微调lm_head
:
from transformers import AutoModelForCausalLM, OPTForCausalLM, AutoTokenizer
from peft import LoraConfig
model_id = "facebook/opt-350m"
model = AutoModelForCausalLM.from_pretrained(model_id)
lora_config = LoraConfig(
target_modules=["q_proj", "k_proj"],
modules_to_save=["lm_head"],
)
model.add_adapter(lora_config)
6.2 训练推理优化
几个方面的加速
基于deepspeed的加速
基于flash-attention的加速(flash-attention是一个),大概有1.3-1.4倍加速(基于qwen-14b),flash-attn原理可以参考LLM模型部署调试推理-Flash_Atten(转)
git clone -b v2.0.8 https://github.com/Dao-AILab/flash-attention cd flash-attention && pip install . # 下方安装可选,安装可能比较缓慢。 # pip install csrc/layer_norm # If the version of flash-attn is higher than 2.1.1, the following is not needed. # pip install csrc/rotary
实践数据记录(A40*48G),训练样本为7746(合并之后的,原来是6.6w),max_seq_length取1024,即大概约793w token
模型 | 卡数 | 显存(48G/卡) | deepspeed | per_device_bs | max_seq_length | flash-attn | 速度 |
---|---|---|---|---|---|---|---|
qwen-14b | 4 | no | 8 | 1024 | 500:00:00 | ||
qwen-14b | 4 | stage-2 | 8 | 1024 | 394:47:05, 189.52s/it | ||
qwen-14b | 4 | stage-2 | 12 | 1024 | 390:25:53, 281.17s/it | ||
qwen-14b | 4 | 37616MiB | stage-2 | 16 | 1024 | 350:07:48, 360.24s/it | |
qwen-14b | 4 | stage-2 | 20 | 1024 | 370:07:35, 444.30s/it | ||
qwen-14b | 4 | 40808MiB | stage-1 | 12 | 1024 | 389:40:00, 280.62s/it | |
qwen-14b | 4 | 34502MiB | stage-1 | 16 | 1024 | 341:45:02, 351.62s/it | |
qwen-14b | 4 | 46480MiB | stage-1 | 20 | 1024 | 364:50:39, 437.96s/it | |
qwen-14b | 4 | 显存超出 | stage-1 | 24 | 1024 | ||
qwen-14b | 4 | 45156MiB | stage-1 | 36 | 512 | 306:29:36, 367.91s/it | |
qwen-14b | 4 | 46606MiB | stage-1 | 40 | 512 | 339:45:26, 407.84s/it | |
qwen-14b | 4 | 37516MiB | stage-2 | 16 | 1024 | v2 | 263:12:16, 270.80s/it |
qwen-14b | 4 | 43896MiB | stage-2 | 20 | 1024 | v2 | 281:20:40, 337.73s/it |
qwen-14b | 4 | 37314MiB | stage-2 | 12 | 1024 | v2 | 304:49:43, 219.52s/it |
qwen-14b | 5 | 37314MiB | stage-2 | 16 | 1024 | v2 | 226:26:14, 271.52s/it |
几个结论:
- 修改
max_seq_length
对速度影响不大,原因应该是总的训练的token量没什么变化 - 增加
per_device_bs
不一定会加速,应该不断尝试不一样的per_device_bs
值 - torchrun转deepspeed大概提速1.25倍,如果同时将bs从8增加到16,可以提速1.43倍
- 每加一张卡,大概提升速度1.16倍
- 使用flash-attn-v2大概提速1.33倍
训练参数为
{
"output_dir": "output/firefly-qwen-14b-pretrain-qlora-law-one-item-doc-2000-statge-0",
"model_name_or_path": "/root/autodl-tmp/pretrained_model/tokenizer/Qwen-14B",
"train_file": "/root/autodl-tmp/hyc/Qwen/datas/base_law_one_item_and_docs_2000_0327_1340",
"deepspeed": "./train_args/ds_z2_config_qwen.json",
"train_mode": "qlora",
"task_type": "pretrain",
"num_train_epochs": 500,
"tokenize_num_workers": 10,
"per_device_train_batch_size": 16,
"gradient_accumulation_steps": 16,
"learning_rate": 1e-5,
"max_seq_length": 1024,
"logging_steps": 2,
"save_steps": 100,
"save_total_limit": 50,
"lr_scheduler_type": "cosine",
"warmup_ratio": 0.01,
"gradient_checkpointing": true,
"logging_first_step": false,
"disable_tqdm": false,
"optim": "adamw_hf",
"seed": 42,
"fp16": true,
"report_to": "tensorboard",
"dataloader_num_workers": 0,
"save_strategy": "steps",
"weight_decay": 0,
"max_grad_norm": 1.0,
"remove_unused_columns": false
}
ds_z2_config_qwen.json
{
"fp16": {
"enabled": "auto",
"loss_scale": 0,
"loss_scale_window": 1000,
"initial_scale_power": 16,
"hysteresis": 2,
"min_loss_scale": 1
},
"bf16": {
"enabled": "auto"
},
"optimizer": {
"type": "AdamW",
"params": {
"lr": "auto",
"betas": "auto",
"eps": "auto",
"weight_decay": "auto"
}
},
"scheduler": {
"type": "WarmupLR",
"params": {
"warmup_min_lr": "auto",
"warmup_max_lr": "auto",
"warmup_num_steps": "auto"
}
},
"zero_optimization": {
"stage": 2,
"offload_optimizer": {
"device": "none",
"pin_memory": true
},
"allgather_partitions": true,
"allgather_bucket_size": 2e8,
"overlap_comm": true,
"reduce_scatter": true,
"reduce_bucket_size": 2e8,
"contiguous_gradients": true,
"stage3_gather_16bit_weights_on_model_save": false,
"ignore_unused_parameters": true
},
"gradient_accumulation_steps": "auto",
"gradient_clipping": "auto",
"steps_per_print": 100,
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"wall_clock_breakdown": false
}
7 其他模块
7.1 核心类
LLM 入门笔记-transformers库简单介绍](https://mp.weixin.qq.com/s/svJMXj9UwI2sd_SAtFExig))
7.1.1 ModelOutput
ModelOutput
(transformers.utils.ModelOutput)是所有模型输出的基类。简单理解它就是一个字典,在模型的 forward
函数里把原本的输出做了一下封装而已,方便用户能直观地知道输出是什么
例如CausalLMOutput
顾名思义就是用于像 GPT 这样自回归模型的输出,ModelOutput
是所有模型输出的基类
class ModelOutput(OrderedDict):
def __init_subclass__(cls) -> None:
"""
这个方法允许对 ModelOutput 的子类进行定制,使得子类在被创建时能够执行特定的操作或注册到某个系统中。
"""
...
def __init__(self, *args, **kwargs):
"""
初始化 ModelOutput 类的实例。
"""
super().__init__(*args, **kwargs)
def __post_init__(self):
"""
在初始化 ModelOutput 类的实例之后执行的操作,允许进一步对实例进行处理或设置属性。子类需要用 dataclass 装饰器
"""
...
基于 ModelOutput
,hf 预先定义了 40 多种不同的 sub-class,这些类是 Hugging Face Transformers 库中用于表示不同类型模型输出的基础类,每个类都提供了特定类型模型输出的结构和信息,以便于在实际任务中对模型输出进行处理和使用
每个 sub-class 都需要用装饰器 @dataclass
,我们以CausalLMOutputWithPast
为例看一下源码
@dataclass
class CausalLMOutputWithPast(ModelOutput):
loss: Optional[torch.FloatTensor] = None
logits: torch.FloatTensor = None
past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None
hidden_states: Optional[Tuple[torch.FloatTensor]] = None
attentions: Optional[Tuple[torch.FloatTensor]] = None
为了保持代码规范,我们需要在模型的forward
函数中对输出结果进行封装,示例如下:
class MyModel(PretrainedModel):
def __init__(self):
self.model = ...
def forward(self, inputs, labels):
output = self.model(**inputs)
hidden_states = ...
loss = loss_fn(outputs, labels)
return CausalLMOutputWithPast(
loss=loss,
logits=logits,
past_key_values=outputs.past_key_values,
hidden_states=outputs.hidden_states,
attentions=outputs.attentions,
)
这里简单介绍以下几种,更多的可以查看官方文档和源码:
BaseModelOutput
: 该类是许多基本模型输出的基础,包含模型的一般输出,如 logits、hidden_states 等BaseModelOutputWithNoAttention
: 在模型输出中不包含注意力(attention)信息BaseModelOutputWithPast
: 包含过去隐藏状态的模型输出,适用于能够迭代生成文本的模型,例如语言模型BaseModelOutputWithCrossAttentions
: 在模型输出中包含交叉注意力(cross attentions)信息通常用于特定任务中需要跨注意力的情况,比如机器翻译
BaseModelOutputWithPastAndCrossAttentions
: 同时包含过去隐藏状态和交叉注意力的模型输出MoEModelOutput
: 包含混合专家模型(Mixture of Experts)输出的模型MoECausalLMOutputWithPast
: 混合专家语言模型的输出,包括过去隐藏状态Seq2SeqModelOutput
: 序列到序列模型输出的基类,适用于需要生成序列的模型CausalLMOutput
: 用于生成式语言模型输出的基础类,提供生成文本的基本信息CausalLMOutputWithPast
: 生成式语言模型输出的类,包含过去隐藏状态,用于连续生成文本的模型
7.1.2 PreTrainedModel
PreTrainedModel
(transformers.modeling_utils.PretrainedModel) 是所有模型的基类
所以你如果看到一个模型取名为LlamaForCausalLM
,那你就可以知道这个模型的输出格式大概率就是自回归输出,即前面提到的CausalLMOutput
PreTrainedModel
是 Hugging Face Transformers 库中定义预训练模型的基类
它继承了 nn.Module
,同时混合了几个不同的 mixin 类,如 ModuleUtilsMixin
、GenerationMixin
、PushToHubMixin
和 PeftAdapterMixin
这个基类提供了创建和定义预训练模型所需的核心功能和属性
class PreTrainedModel(nn.Module, ModuleUtilsMixin, GenerationMixin, PushToHubMixin, PeftAdapterMixin):
config_class = None
base_model_prefix = ""
main_input_name = "input_ids"
_auto_class = None
_no_split_modules = None
_skip_keys_device_placement = None
_keep_in_fp32_modules = None
...
def __init__(self, config: PretrainedConfig, *inputs, **kwargs):
super().__init__()
...
在这个基类中,我们可以看到一些重要的属性和方法:
config_class
:指向特定预训练模型类的配置文件,用于定义模型的配置base_model_prefix
:基本模型前缀,在模型的命名中使用,例如在加载预训练模型的权重时使用main_input_name
:指定模型的主要输入名称,通常是 input_ids_init_weights
方法:用于初始化模型权重的方法
在这个基类中,大多数属性都被定义为 None 或空字符串,这些属性在具体的预训练模型类中会被重写或填充
LLama例子
接下来我们将看到如何使用 PretrainedModel 类定义 llama 模型
class LlamaPreTrainedModel(PreTrainedModel):
config_class = LlamaConfig
base_model_prefix = "model"
supports_gradient_checkpointing = True
_no_split_modules = ["LlamaDecoderLayer"]
_skip_keys_device_placement = "past_key_values"
_supports_flash_attn_2 = True
def _init_weights(self, module):
std = self.config.initializer_range
if isinstance(module, nn.Linear):
module.weight.data.normal_(mean=0.0, std=std)
if module.bias is not None:
module.bias.data.zero_()
elif isinstance(module, nn.Embedding):
module.weight.data.normal_(mean=0.0, std=std)
if module.padding_idx is not None:
module.weight.data[module.padding_idx].zero_()
在这个例子中,首先定义了 LlamaPreTrainedModel
类作为 llama 模型的基类,它继承自 PreTrainedModel
。在这个基类中,我们指定了一些 llama 模型特有的属性,比如配置类 LlamaConfig
、模型前缀 model、支持梯度检查点(gradient checkpointing)、跳过的模块列表 _no_split_modules 等等。
然后,我们基于这个基类分别定义了 LlamaModel
、LlamaForCausalLM
和 LlamaForSequenceClassification
。这些模型的逻辑关系如下图所示
LlamaModel
是 llama 模型的主体定义类,也就是我们最常见的普pytorch 定义模型的方法、默认的输出格式为BaseModelOutputWithPast
class LlamaModel(LlamaPreTrainedModel):
def __init__(self, config: LlamaConfig):
super().__init__(config)
self.padding_idx = config.pad_token_id
self.vocab_size = config.vocab_size
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
self.layers = nn.ModuleList([LlamaDecoderLayer(config) for _ in range(config.num_hidden_layers)])
self.norm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
...
def forward(self, ...):
...
return BaseModelOutputWithPast(...)
LlamaForCausalLM
适用于生成式语言模型的 llama 模型,可以看到 backbone 就是 LlamaModel
,增加了lm_head
作为分类器,输出长度为词汇表达大小,用来预测下一个单词。输出格式为CausalLMOutputWithPast
class LlamaForCausalLM(LlamaPreTrainedModel):
# 适用于生成式语言模型的 Llama 模型定义
def __init__(self, config):
super().__init__(config)
self.model = LlamaModel(config)
self.vocab_size = config.vocab_size
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
...
def forward(self, ...):
outputs = self.model(...)
... # 后处理 outputs,以满足输出格式要求
return CausalLMOutputWithPast(...)
LlamaForSequenceClassification
适用于序列分类任务的 llama 模型,同样把 LlamaModel
作为 backbone, 不过增加了score
作为分类器,输出长度为 label 的数量,用来预测类别。输出格式为SequenceClassifierOutputWithPast
class LlamaForSequenceClassification(LlamaPreTrainedModel):
# 适用于序列分类任务的 Llama 模型定义
def __init__(self, config):
super().__init__(config)
self.num_labels = config.num_labels
self.model = LlamaModel(config)
self.score = nn.Linear(config.hidden_size, self.num_labels, bias=False)
...
def forward(self, ...):
outputs = self.model(...)
... # 后处理 outputs,以满足输出格式要求
return SequenceClassifierOutputWithPast(...)
每个子类根据特定的任务或应用场景进行了定制,以满足不同任务的需求
另外可以看到 hf 定义的模型都是由传入的 config
参数定义的,所以不同模型对应不同的配置啦,这也是为什么我们经常能看到有像 BertConfig
,GPTConfig
这些预先定义好的类
例如我们可以很方便地通过指定的字符串或者文件获取和修改不同的参数配置
config = BertConfig.from_pretrained("bert-base-uncased") # Download configuration from huggingface.co and cache.
config = BertConfig.from_pretrained("./test/saved_model/") # E.g. config (or model) was saved using *save_pretrained('./test/saved_model/')*
config = BertConfig.from_pretrained("./test/saved_model/my_configuration.json")
config = BertConfig.from_pretrained("bert-base-uncased", output_attentions=True, foo=False)
hf 为了造福懒人,提供了更加简便的 API,即 Auto 系列 API。至于有多简便,看看下面的 demo 就知道了
from transformers import AutoConfig, AutoModel
# Download configuration from huggingface.co and cache.
config = AutoConfig.from_pretrained("bert-base-cased")
model = AutoModel.from_config(config)
7.2 AutoTrain
AutoTrain
是一个用于自动化训练的库,旨在简化模型训练的过程。它提供了一种简单的方法来定义和训练深度学习模型,自动处理数据加载、批处理、优化器、损失函数等训练过程中的细节。通过使用AutoTrain,你可以更快速地搭建和训练模型,减少样板代码的编写,并且能够轻松地进行超参数搜索和模型选择
7.3 Gradio
Gradio
是一个用于构建交互式界面的库,使你能够轻松地为你的深度学习模型创建Web应用程序。Gradio提供了一个简单而强大的API,可以将模型与用户界面组件(如文本框、滑块、图像上传器等)相连接,从而实现模型的实时推理和可视化。通过Gradio,你可以快速构建一个交互式的演示或部署你的模型到Web上,无需编写复杂的前端代码
7.4 Diffusers
Diffusers
是一个用于生成图像、音频甚至分子的三维结构的最新预训练扩散模型的库。无论您是寻找一个简单的推理解决方案,还是想要训练自己的扩散模型,🤗Diffusers都是一个支持两者的模块化工具箱。我们的库着重于易用性而非性能,简洁而非复杂,可定制性而非抽象性,该库主要包含以下三个组件:
- 最新的扩散推理流程,只需几行代码即可实现
- 可互换的噪声调度器,用于在生成速度和质量之间平衡权衡
- 可用作构建块的预训练模型,可以与调度器结合使用,创建您自己的端到端扩散系统
7.5 Accelerate
Hugging Face的Accelerate
是一个旨在简化和加速深度学习模型训练和推理过程的库
它提供了一个高级API,抽象了分布式训练、混合精度和梯度累积等复杂性,使用户能够轻松地充分利用硬件资源的潜力
Accelerate兼容PyTorch和TensorFlow,并提供了一套工具和实用程序,以实现跨多个GPU或多台机器的高效分布式训练。它包括以下功能:
- 分布式训练:Accelerate提供了简单易用的接口,使用户能够将训练过程分布到多个GPU或多台机器上。它支持常见的分布式训练策略,如数据并行和模型并行,并自动处理数据的分发和梯度的聚合,使用户无需手动编写复杂的分布式训练代码
- 混合精度训练:Accelerate支持混合精度训练,通过同时使用浮点16位和浮点32位精度来加快模型的训练速度。它自动处理数据类型转换和梯度缩放,用户只需简单地指定使用混合精度训练即可
- 梯度累积:Accelerate支持梯度累积,这在GPU显存有限的情况下特别有用。梯度累积允许在多个小批次上累积梯度,然后进行一次大批次的参数更新,从而减少显存占用并提高训练效率
- 自动调节批次大小:Accelerate可以自动调整批次大小以适应可用的GPU内存。它会动态调整批次大小,以达到最佳的GPU利用率和训练性能
总之,Hugging Face的Accelerate是一个功能强大的库,旨在简化和加速深度学习模型的训练和推理过程。它提供了高级API和一系列工具,使用户能够轻松地实现分布式训练、混合精度训练和梯度累积等高效训练策略