数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 MCMC 采样
- 机器学习方法概论
统计学习
深度学习
- 深度学习简介
- 深度前馈网络
- 反向传播算法
- 正则化
- 深度学习中的最优化问题
- 卷积神经网络
- CNN:图像分类
- 循环神经网络 RNN
- Transformer
- 一、Transformer [2017]
- 二、Universal Transformer [2018]
- 三、Transformer-XL [2019]
- 四、GPT1 [2018]
- 五、GPT2 [2019]
- 六、GPT3 [2020]
- 七、OPT [2022]
- 八、BERT [2018]
- 九、XLNet [2019]
- 十、RoBERTa [2019]
- 十一、ERNIE 1.0 [2019]
- 十二、ERNIE 2.0 [2019]
- 十三、ERNIE 3.0 [2021]
- 十四、ERNIE-Huawei [2019]
- 十五、MT-DNN [2019]
- 十六、BART [2019]
- 十七、mBART [2020]
- 十八、SpanBERT [2019]
- 十九、ALBERT [2019]
- 二十、UniLM [2019]
- 二十一、MASS [2019]
- 二十二、MacBERT [2019]
- 二十三、Fine-Tuning Language Models from Human Preferences [2019]
- 二十四 Learning to summarize from human feedback [2020]
- 二十五、InstructGPT [2022]
- 二十六、T5 [2020]
- 二十七、mT5 [2020]
- 二十八、ExT5 [2021]
- 二十九、Muppet [2021]
- 三十、Self-Attention with Relative Position Representations [2018]
- 三十一、USE [2018]
- 三十二、Sentence-BERT [2019]
- 三十三、SimCSE [2021]
- 三十四、BERT-Flow [2020]
- 三十五、BERT-Whitening [2021]
- 三十六、Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings [2019]
- 三十七、CERT [2020]
- 三十八、DeCLUTR [2020]
- 三十九、CLEAR [2020]
- 四十、ConSERT [2021]
- 四十一、Sentence-T5 [2021]
- 四十二、ULMFiT [2018]
- 四十三、Scaling Laws for Neural Language Models [2020]
- 四十四、Chinchilla [2022]
- 四十七、GLM-130B [2022]
- 四十八、GPT-NeoX-20B [2022]
- 四十九、Bloom [2022]
- 五十、PaLM [2022] (粗读)
- 五十一、PaLM2 [2023](粗读)
- 五十二、Self-Instruct [2022]
- 句子向量
- 词向量
- 传统CTR 预估模型
- CTR 预估模型
- 一、DSSM [2013]
- 二、FNN [2016]
- 三、PNN [2016]
- 四、DeepCrossing [2016]
- 五、Wide 和 Deep [2016]
- 六、DCN [2017]
- 七、DeepFM [2017]
- 八、NFM [2017]
- 九、AFM [2017]
- 十、xDeepFM [2018]
- 十一、ESMM [2018]
- 十二、DIN [2017]
- 十三、DIEN [2019]
- 十四、DSIN [2019]
- 十五、DICM [2017]
- 十六、DeepMCP [2019]
- 十七、MIMN [2019]
- 十八、DMR [2020]
- 十九、MiNet [2020]
- 二十、DSTN [2019]
- 二十一、BST [2019]
- 二十二、SIM [2020]
- 二十三、ESM2 [2019]
- 二十四、MV-DNN [2015]
- 二十五、CAN [2020]
- 二十六、AutoInt [2018]
- 二十七、Fi-GNN [2019]
- 二十八、FwFM [2018]
- 二十九、FM2 [2021]
- 三十、FiBiNET [2019]
- 三十一、AutoFIS [2020]
- 三十三、AFN [2020]
- 三十四、FGCNN [2019]
- 三十五、AutoCross [2019]
- 三十六、InterHAt [2020]
- 三十七、xDeepInt [2023]
- 三十九、AutoDis [2021]
- 四十、MDE [2020]
- 四十一、NIS [2020]
- 四十二、AutoEmb [2020]
- 四十三、AutoDim [2021]
- 四十四、PEP [2021]
- 四十五、DeepLight [2021]
- 图的表达
- 一、DeepWalk [2014]
- 二、LINE [2015]
- 三、GraRep [2015]
- 四、TADW [2015]
- 五、DNGR [2016]
- 六、Node2Vec [2016]
- 七、WALKLETS [2016]
- 八、SDNE [2016]
- 九、CANE [2017]
- 十、EOE [2017]
- 十一、metapath2vec [2017]
- 十二、GraphGAN [2018]
- 十三、struc2vec [2017]
- 十四、GraphWave [2018]
- 十五、NetMF [2017]
- 十六、NetSMF [2019]
- 十七、PTE [2015]
- 十八、HNE [2015]
- 十九、AANE [2017]
- 二十、LANE [2017]
- 二十一、MVE [2017]
- 二十二、PMNE [2017]
- 二十三、ANRL [2018]
- 二十四、DANE [2018]
- 二十五、HERec [2018]
- 二十六、GATNE [2019]
- 二十七、MNE [2018]
- 二十八、MVN2VEC [2018]
- 二十九、SNE [2018]
- 三十、ProNE [2019]
- Graph Embedding 综述
- 图神经网络
- 一、GNN [2009]
- 二、Spectral Networks 和 Deep Locally Connected Networks [2013]
- 三、Fast Localized Spectral Filtering On Graph [2016]
- 四、GCN [2016]
- 五、神经图指纹 [2015]
- 六、GGS-NN [2016]
- 七、PATCHY-SAN [2016]
- 八、GraphSAGE [2017]
- 九、GAT [2017]
- 十、R-GCN [2017]
- 十一、 AGCN [2018]
- 十二、FastGCN [2018]
- 十三、PinSage [2018]
- 十四、GCMC [2017]
- 十五、JK-Net [2018]
- 十六、PPNP [2018]
- 十七、VRGCN [2017]
- 十八、ClusterGCN [2019]
- 十九、LDS-GNN [2019]
- 二十、DIAL-GNN [2019]
- 二十一、HAN [2019]
- 二十二、HetGNN [2019]
- 二十三、HGT [2020]
- 二十四、GPT-GNN [2020]
- 二十五、Geom-GCN [2020]
- 二十六、Graph Network [2018]
- 二十七、GIN [2019]
- 二十八、MPNN [2017]
- 二十九、UniMP [2020]
- 三十、Correct and Smooth [2020]
- 三十一、LGCN [2018]
- 三十二、DGCNN [2018]
- 三十三、AS-GCN
- 三十四、DGI [2018]
- 三十五、DIFFPOLL [2018]
- 三十六、DCNN [2016]
- 三十七、IN [2016]
- 图神经网络 2
- 图神经网络 3
- 推荐算法(传统方法)
- 一、Tapestry [1992]
- 二、GroupLens [1994]
- 三、ItemBased CF [2001]
- 四、Amazon I-2-I CF [2003]
- 五、Slope One Rating-Based CF [2005]
- 六、Bipartite Network Projection [2007]
- 七、Implicit Feedback CF [2008]
- 八、PMF [2008]
- 九、SVD++ [2008]
- 十、MMMF 扩展 [2008]
- 十一、OCCF [2008]
- 十二、BPR [2009]
- 十三、MF for RS [2009]
- 十四、 Netflix BellKor Solution [2009]
- 推荐算法(神经网络方法 1)
- 一、MIND [2019](用于召回)
- 二、DNN For YouTube [2016]
- 三、Recommending What Video to Watch Next [2019]
- 四、ESAM [2020]
- 五、Facebook Embedding Based Retrieval [2020](用于检索)
- 六、Airbnb Search Ranking [2018]
- 七、MOBIUS [2019](用于召回)
- 八、TDM [2018](用于检索)
- 九、DR [2020](用于检索)
- 十、JTM [2019](用于检索)
- 十一、Pinterest Recommender System [2017]
- 十二、DLRM [2019]
- 十三、Applying Deep Learning To Airbnb Search [2018]
- 十四、Improving Deep Learning For Airbnb Search [2020]
- 十五、HOP-Rec [2018]
- 十六、NCF [2017]
- 十七、NGCF [2019]
- 十八、LightGCN [2020]
- 十九、Sampling-Bias-Corrected Neural Modeling [2019](检索)
- 二十、EGES [2018](Matching 阶段)
- 二十一、SDM [2019](Matching 阶段)
- 二十二、COLD [2020 ] (Pre-Ranking 模型)
- 二十三、ComiRec [2020](https://www.wenjiangs.com/doc/0b4e1736-ac78)
- 二十四、EdgeRec [2020]
- 二十五、DPSR [2020](检索)
- 二十六、PDN [2021](mathcing)
- 二十七、时空周期兴趣学习网络ST-PIL [2021]
- 推荐算法之序列推荐
- 一、FPMC [2010]
- 二、GRU4Rec [2015]
- 三、HRM [2015]
- 四、DREAM [2016]
- 五、Improved GRU4Rec [2016]
- 六、NARM [2017]
- 七、HRNN [2017]
- 八、RRN [2017]
- 九、Caser [2018]
- 十、p-RNN [2016]
- 十一、GRU4Rec Top-k Gains [2018]
- 十二、SASRec [2018]
- 十三、RUM [2018]
- 十四、SHAN [2018]
- 十五、Phased LSTM [2016]
- 十六、Time-LSTM [2017]
- 十七、STAMP [2018]
- 十八、Latent Cross [2018]
- 十九、CSRM [2019]
- 二十、SR-GNN [2019]
- 二十一、GC-SAN [2019]
- 二十二、BERT4Rec [2019]
- 二十三、MCPRN [2019]
- 二十四、RepeatNet [2019]
- 二十五、LINet(2019)
- 二十六、NextItNet [2019]
- 二十七、GCE-GNN [2020]
- 二十八、LESSR [2020]
- 二十九、HyperRec [2020]
- 三十、DHCN [2021]
- 三十一、TiSASRec [2020]
- 推荐算法(综述)
- 多任务学习
- 系统架构
- 实践方法论
- 深度强化学习 1
- 自动代码生成
工具
- CRF
- lightgbm
- xgboost
- scikit-learn
- spark
- numpy
- matplotlib
- pandas
- huggingface_transformer
- 一、Tokenizer
- 二、Datasets
- 三、Model
- 四、Trainer
- 五、Evaluator
- 六、Pipeline
- 七、Accelerate
- 八、Autoclass
- 九、应用
- 十、Gradio
Scala
- 环境搭建
- 基础知识
- 函数
- 类
- 样例类和模式匹配
- 测试和注解
- 集合 collection(一)
- 集合collection(二)
- 集成 Java
- 并发
二、微调 Masked Language Model
:
token classification
任务可以为文本中的单词或字符分配标签,如:实体命名识别 (
NER
):找出句子中的实体(如人物、地点或组织)。词性标注 (
POS
):将句子中的每个单词标记为对应的词性(如名词、动词、形容词等)。分块(
chunking
):找到属于同一实体的Token
(如,找出名词短语、动词短语等)。
1.1 加载数据集
查看数据集:
xxxxxxxxxx
from datasets import load_dataset raw_datasets = load_dataset("conll2003") print(raw_datasets) # 有三个数据集:train, validation, test # DatasetDict({ # train: Dataset({ # features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], # num_rows: 14041 # }) # validation: Dataset({ # features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], # num_rows: 3250 # }) # test: Dataset({ # features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], # num_rows: 3453 # }) # }) print(raw_datasets["train"][0]["tokens"]) # 训练集第零个样本的单词序列 # ['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] print(raw_datasets["train"][0]["ner_tags"]) # 训练集第零个样本的 label 序列 # [3, 0, 7, 0, 0, 0, 7, 0, 0] ner_feature = raw_datasets["train"].features["ner_tags"] print(ner_feature) # NER label 的名称 # Sequence(feature=ClassLabel(names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'],>print(ner_feature.feature.names) # label 编号从 0 ~ 8 对应的名称 # ['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] # O 表示这个词不对应任何实体。 # B-PER/I-PER意味着这个词对应于人名实体的开头/内部。 # B-ORG/I-ORG 的意思是这个词对应于组织名称实体的开头/内部。 # B-LOC/I-LOC 指的是是这个词对应于地名实体的开头/内部。 # B-MISC/I-MISC 表示该词对应于一个杂项实体的开头/内部。
1.2 数据处理
我们的文本需要转换为
Token ID
,然后模型才能理解它们。xxxxxxxxxx
from transformers import AutoTokenizer model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) print(tokenizer.is_fast) # True ## 注意,样本,如 raw_datasets["train"][0]["tokens"] 已经是完成了分词,因此这里无需分词步骤 (is_split_into_words 设为 True ) inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) # 执行 tokenization print(inputs) # { # 'input_ids': [101, 7270, 22961, 1528, 1840, 1106, 21423, 1418, 2495, 12913, 119, 102], # 'token_type_ids': [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] # } print(inputs.tokens()) # ['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]']注意:单词
"lamb"
被分为两个子词"la"
和"##mb"
。这导致了token
序列和标签序列之间的不匹配:标签序列只有9
个元素,而token
序列有12
个token
。我们需要扩展标签序列以匹配token
序列 。这里有两条规则:special token
(如,[CLS], [SEP]
)的标签为-100
。这是因为默认情况下-100
是一个在我们将使用的损失函数(交叉熵)中被忽略的index
。每个
token
都会获得与其所在单词相同的标签,因为它们是同一实体的一部分。对于单词内部但不在开头的token
,我们将B-
替换为I-
,因为单词内部的token
不是实体的开头。
xxxxxxxxxx
def align_labels_with_tokens(labels, word_ids): # labels : word_id -> label 的映射 # word_ids: 每个 token 对应的 word_id new_labels = [] current_word = None for word_id in word_ids: if word_id != current_word: # 遇到一个新的单词 current_word = word_id label = -100 if word_id is None else labels[word_id] new_labels.append(label) elif word_id is None: # special token new_labels.append(-100) else: # 同一个单词内部 label = labels[word_id] if label % 2 == 1: # 如果 label 是 B-XXX,那么修改为 I-XXX label += 1 new_labels.append(label) return new_labels为了预处理整个数据集,我们需要
tokenize
所有输入并在所有label
上应用align_labels_with_tokens()
。为了利用我们的快速分词器的速度优势,最好同时对大量文本进行tokenize
,因此我们将编写一个处理样本列表的函数并使用带batched = True
的Dataset.map()
方法。xxxxxxxxxx
def tokenize_and_align_labels(examples): # examples 是样本的列表 tokenized_inputs = tokenizer( examples["tokens"], truncation=True, is_split_into_words=True ) all_labels = examples["ner_tags"] new_labels = [] for i, labels in enumerate(all_labels): word_ids = tokenized_inputs.word_ids(i) # 获取第 i 个样本的 word_ids new_labels.append(align_labels_with_tokens(labels, word_ids)) tokenized_inputs["labels"] = new_labels # 调整标签 return tokenized_inputs tokenized_datasets = raw_datasets.map( tokenize_and_align_labels, batched=True, # 一次性处理 batch 的样本 remove_columns=raw_datasets["train"].column_names, ) print(tokenized_datasets) # DatasetDict({ # train: Dataset({ # features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'], # num_rows: 14041 # }) # validation: Dataset({ # features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'], # num_rows: 3250 # }) # test: Dataset({ # features: ['input_ids', 'token_type_ids', 'attention_mask', 'labels'], # num_rows: 3453 # }) # }) print(tokenized_datasets["train"][0]["input_ids"]) # 训练集第零个样本的 token id 序列 # [101, 7270, 22961, 1528, 1840, 1106, 21423, 1418, 2495, 12913, 119, 102] print(tokenized_datasets["train"][0]["labels"]) # 训练集第零个样本的 label id 序列 # [-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] print(tokenized_datasets["train"].features["labels"]) # Sequence(feature=Value(dtype='int64',>
1.3 使用 Trainer API 微调模型
a. 构建 mini-batch
前面的数据处理可以批量地将输入文本转换为
input_ids
(token id
序列),但是我们还需要将样本拼接成mini-batch
然而馈入模型。DataCollatorForTokenClassification
支持以相同的方式来填充输入文本和label
,使得input_ids
和labels
的长度保持相同。label
的padding
值为-100
,这样在损失计算中就可以忽略相应的预测。注意:
input_ids
的padding
值为0
、labels
的padding
值为-100
。xxxxxxxxxx
from transformers import DataCollatorForTokenClassification data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) sample_list = [tokenized_datasets["train"][i] for i in range(3)] # 3 个样本,每个样本的序列长度不同 print([len(i['labels']) for i in sample_list]) # [12, 4, 11] batch = data_collator(sample_list) print(batch) # {'input_ids': # tensor([[ 101, 7270, 22961, 1528, 1840, 1106, 21423, 1418, 2495, 12913, # 119, 102], # [ 101, 1943, 14428, 102, 0, 0, 0, 0, 0, 0, # 0, 0], # [ 101, 26660, 13329, 12649, 15928, 1820, 118, 4775, 118, 1659, # 102, 0]]), # 'token_type_ids': # tensor([[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': # tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], # [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], # [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]]), # 'labels': # tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], # [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100], # [-100, 5, 6, 6, 6, 0, 0, 0, 0, 0, -100, -100]])}
b. evaluation 指标
用于评估
Token
分类效果的传统框架是seqeval
。要使用此指标,我们首先需要安装seqeval
库:xxxxxxxxxx
pip install seqeval然后我们可以通过
load_metric()
函数加载它:xxxxxxxxxx
from datasets import load_metric metric = load_metric("seqeval")这个评估框架以字符串而不是整数的格式传入
label
,因此在将prediction
和label
传递给它之前需要对prediction/label
进行解码。让我们看看它是如何工作的:首先,我们获得第一个训练样本的标签:
xxxxxxxxxx
labels = raw_datasets["train"][0]["ner_tags"] print(labels) # [3, 0, 7, 0, 0, 0, 7, 0, 0] label_names = raw_datasets["train"].features["ner_tags"].feature.names labels = [label_names[i] for i in labels] print(labels) # ['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']然后我们可以通过更改
labels
来创建 ”虚拟” 的prediction
:xxxxxxxxxx
predictions = labels.copy() # 注意,需要执行深层拷贝使得 predictions 和 labels 不共享底层数据 predictions[2] = "O" metric.compute(predictions=[predictions], references=[labels])metric.compute
的输入是一组样本的prediction
(不仅仅是单个样本的prediction
)、以及一组样本的label
。输出为:xxxxxxxxxx
{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.6666666666666666, 'number': 2}, 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, 'overall_precision': 1.0, 'overall_recall': 0.6666666666666666, 'overall_f1': 0.8, 'overall_accuracy': 0.8888888888888888}可以看到它返回了每个单独实体(如
"MISC"、"ORG"
)、以及整体的精度、召回率、以及F1
分数。对于单独实体还返回出现的次数("number"
),对于整体还返回准确率。为了让
Trainer
在每个epoch
计算一个指标,我们需要定义一个compute_metrics()
函数,该函数接受prediction
和label
,并返回一个包含指标名称和值的字典。这个
compute_metrics()
首先对logits
执行argmax
从而得到prediction
(这里无需采用softmax
,因为我们只需要找到概率最大的那个token id
即可,也就是logit
最大的那个token id
)。然后我们将prediction
和label
从整数转换为字符串(注意删除label = -100
的label
及其对应位置的prediction
)。然后我们将prediction
字符串和label
字符串传递给metric.compute()
方法:xxxxxxxxxx
import numpy as np def compute_metrics(eval_preds): logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) # 移除 label = -100 位置的数据 (包括 token 和 label) true_labels = [[label_names[l] for l in label if l != -100] for label in labels] true_predictions = [ [label_names[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels) ] all_metrics = metric.compute(predictions=true_predictions, references=true_labels) return { "precision": all_metrics["overall_precision"], "recall": all_metrics["overall_recall"], "f1": all_metrics["overall_f1"], "accuracy": all_metrics["overall_accuracy"], }
c. 定义模型
由于我们正在研究
Token
分类问题,因此我们将使用AutoModelForTokenClassification
类。首先我们定义两个字典
id2label
和label2id
,其中包含从label id
到label name
的映射:xxxxxxxxxx
id2label = {i: label for i, label in enumerate(label_names)} print(id2label) # {0: 'O', 1: 'B-PER', 2: 'I-PER', 3: 'B-ORG', 4: 'I-ORG', 5: 'B-LOC', 6: 'I-LOC', 7: 'B-MISC', 8: 'I-MISC'} label2id = {v: k for k, v in id2label.items()} print(label2id) # {'O': 0, 'B-PER': 1, 'I-PER': 2, 'B-ORG': 3, 'I-ORG': 4, 'B-LOC': 5, 'I-LOC': 6, 'B-MISC': 7, 'I-MISC': 8}然后我们加载预训练好的模型:
xxxxxxxxxx
from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer model = AutoModelForTokenClassification.from_pretrained( "bert-base-cased", id2label=id2label, label2id=label2id ) print(model) # BertForTokenClassification( # (bert): BertModel( # (embeddings): BertEmbeddings( # ... # ) # (encoder): BertEncoder( # ... # ) # ) # (dropout): Dropout(p=0.1, inplace=False) # (classifier): Linear(in_features=768, out_features=9, bias=True) #)注意,预训练好的模型必须和
AutoTokenizer
相匹配,这里都是用的"bert-base-cased"
。创建模型会发出警告,提示某些权重未被使用(来自
pretrained-head
的权重),另外提示某些权重被随机初始化(来新分类头的权重),我们将要训练这个模型因此可以忽略这两种警告。我们可以查看模型配置:
xxxxxxxxxx
print(model.config) # BertConfig { # "_name_or_path": "bert-base-cased", # "architectures": [ # "BertForMaskedLM" # ], # "attention_probs_dropout_prob": 0.1, # "classifier_dropout": null, # "gradient_checkpointing": false, # "hidden_act": "gelu", # "hidden_dropout_prob": 0.1, # "hidden_size": 768, # "id2label": { # ... # }, # "initializer_range": 0.02, # "intermediate_size": 3072, # "label2id": { # ... # }, # "layer_norm_eps": 1e-12, # "max_position_embeddings": 512, # "model_type": "bert", # "num_attention_heads": 12, # "num_hidden_layers": 12, # "pad_token_id": 0, # "position_embedding_type": "absolute", # "transformers_version": "4.25.1", # "type_vocab_size": 2, # "use_cache": true, # "vocab_size": 28996 # }然后我们需要定义训练参数,以及登录
Hugging Face
(如果不需要把训练结果推送到HuggingFace
,则无需登录并且设置push_to_hub=False
)。登录:
xxxxxxxxxx
huggingface-cli login定义训练参数:
xxxxxxxxxx
from transformers import TrainingArguments args = TrainingArguments( "bert-finetuned-ner", # 输出模型的名称 evaluation_strategy="epoch", save_strategy="epoch", learning_rate=2e-5, num_train_epochs=3, weight_decay=0.01, push_to_hub=True, # 注意,如果你不想 push 到 HuggingFace,设置它为 False ) print(args) # 参数含义可以参考前面对应章节的内容 # TrainingArguments( # _n_gpu=0, # 如果机器上有 GPU,那么这里表示 GPU 数量 # adafactor=False, # adam_beta1=0.9, # adam_beta2=0.999, # adam_epsilon=1e-08, # auto_find_batch_size=False, # bf16=False, # bf16_full_eval=False, # data_seed=None, # dataloader_drop_last=False, # dataloader_num_workers=0, # dataloader_pin_memory=True, # ddp_bucket_cap_mb=None, # ddp_find_unused_parameters=None, # ddp_timeout=1800, # debug=[], # deepspeed=None, # disable_tqdm=False, # do_eval=True, # do_predict=False, # do_train=False, # eval_accumulation_steps=None, # eval_delay=0, # eval_steps=None, # evaluation_strategy=epoch, # fp16=False, # fp16_backend=auto, # fp16_full_eval=False, # fp16_opt_level=O1, # fsdp=[], # fsdp_min_num_params=0, # fsdp_transformer_layer_cls_to_wrap=None, # full_determinism=False, # gradient_accumulation_steps=1, # gradient_checkpointing=False, # greater_is_better=None, # group_by_length=False, # half_precision_backend=auto, # hub_model_id=None, # hub_private_repo=False, # hub_strategy=every_save, # hub_token=<HUB_TOKEN>, # ignore_data_skip=False, # include_inputs_for_metrics=False, # jit_mode_eval=False, # label_names=None, # label_smoothing_factor=0.0, # learning_rate=2e-05, # length_column_name=length, # load_best_model_at_end=False, # local_rank=-1, # log_level=passive, # log_level_replica=passive, # log_on_each_node=True, # logging_dir=bert-finetuned-ner/runs/Apr15_06-45-26_SHAUNHUA-MB0, # logging_first_step=False, # logging_nan_inf_filter=True, # logging_steps=500, # logging_strategy=steps, # lr_scheduler_type=linear, # max_grad_norm=1.0, # max_steps=-1, # metric_for_best_model=None, # mp_parameters=, # no_cuda=False, # num_train_epochs=3, # optim=adamw_hf, # optim_args=None, # output_dir=bert-finetuned-ner, # overwrite_output_dir=False, # past_index=-1, # per_device_eval_batch_size=8, # per_device_train_batch_size=8, # prediction_loss_only=False, # push_to_hub=True, # push_to_hub_model_id=None, # push_to_hub_organization=None, # push_to_hub_token=<PUSH_TO_HUB_TOKEN>, # ray_scope=last, # remove_unused_columns=True, # report_to=['tensorboard'], # resume_from_checkpoint=None, # run_name=bert-finetuned-ner, # save_on_each_node=False, # save_steps=500, # save_strategy=epoch, # save_total_limit=None, # seed=42, # sharded_ddp=[], # skip_memory_metrics=True, # tf32=None, # torchdynamo=None, # tpu_metrics_debug=False, # tpu_num_cores=None, # use_ipex=False, # use_legacy_prediction_loop=False, # use_mps_device=False, # warmup_ratio=0.0, # warmup_steps=0, # weight_decay=0.01, # xpu_backend=None, # )
接下来我们构建
Trainer
并启动训练:xxxxxxxxxx
from transformers import Trainer trainer = Trainer( model=model, args=args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, compute_metrics=compute_metrics, tokenizer=tokenizer, ) trainer.train() # TrainOutput(global_step=5268, training_loss=0.06639557918455896, # metrics={'train_runtime': 198.3512, 'train_samples_per_second': 212.366, 'train_steps_per_second': 26.559, 'total_flos': 923954298531210.0, 'train_loss': 0.06639557918455896, 'epoch': 3.0})建议使用
GPU
训练,CPU
速度太慢。在MacBook Pro 2018
(型号A1990
)上训练速度:0.38 iteration/s
,在RTX 4090
上训练速度26.48 iteration/s
。如果
push_to_hub=True
,那么训练期间每次保存模型时(这里是每个epooch
保存一次),trainer
都会在后台将checkpoint
上传到HuggingFace Model Hub
。这样,你就能够在另一台机器上继续训练。并且训练完成之后,可以上传模型的最终版本:xxxxxxxxxx
trainer.push_to_hub(commit_message="Training finish!")这
Trainer
还创建了一张包含所有评估结果的Model Card
并上传。在此阶段,你可以使用Hugging Face Model Hub
上的inference widget
来测试该模型,并分享给其它人。
1.4 使用微调后的模型
通过指定模型名称来使用微调后的模型:
xxxxxxxxxx
from transformers import pipeline token_classifier = pipeline( # model 参数指定模型名称(对应于 HuggingFace Model Hub、或者模型权重的位置(对应于本地) "token-classification", model="./bert-finetuned-ner/checkpoint-5268"", aggregation_strategy="simple" ) token_classifier("Wuhan is a beautiful city in China.") # [{'entity_group': 'LOC', 'score': 0.99749047, 'word': 'Wuhan', 'start': 0, 'end': 5}, # {'entity_group': 'LOC', 'score': 0.9990609, 'word': 'China', 'start': 29, 'end': 34}]
1.5 自定义训练过程
我们也可以自定义训练过程,从而代替
Trainer API
,这样可以对训练过程进行更精细的控制。xxxxxxxxxx
from torch.utils.data import DataLoader from datasets import load_dataset from transformers import AutoTokenizer from transformers import DataCollatorForTokenClassification from transformers import AutoModelForTokenClassification from datasets import load_metric from tqdm.auto import tqdm import torch ##****************** 创建数据集 ********************** model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) def align_labels_with_tokens(labels, word_ids): # labels : word_id -> label 的映射 # word_ids: 每个 token 对应的 word_id new_labels = [] current_word = None for word_id in word_ids: if word_id != current_word: # 遇到一个新的单词 current_word = word_id label = -100 if word_id is None else labels[word_id] new_labels.append(label) elif word_id is None: # special token new_labels.append(-100) else: # 同一个单词内部 label = labels[word_id] if label % 2 == 1: # 如果 label 是 B-XXX,那么修改为 I-XXX label += 1 new_labels.append(label) return new_labels def tokenize_and_align_labels(examples): # examples 是样本的列表 tokenized_inputs = tokenizer( examples["tokens"], truncation=True, is_split_into_words=True ) all_labels = examples["ner_tags"] new_labels = [] for i, labels in enumerate(all_labels): word_ids = tokenized_inputs.word_ids(i) # 获取第 i 个样本的 word_ids new_labels.append(align_labels_with_tokens(labels, word_ids)) tokenized_inputs["labels"] = new_labels # 调整标签 return tokenized_inputs raw_datasets = load_dataset("conll2003") tokenized_datasets = raw_datasets.map( tokenize_and_align_labels, batched=True, # 一次性处理 batch 的样本 remove_columns=raw_datasets["train"].column_names, ) train_dataloader = DataLoader( tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, ) eval_dataloader = DataLoader( tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 ) ##****************** 创建模型 ********************** label_names = raw_datasets["train"].features["ner_tags"].feature.names id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} model = AutoModelForTokenClassification.from_pretrained( model_checkpoint, id2label=id2label, label2id=label2id, ) ##****************** 创建优化器 ********************** from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ##****************** 调用 accelerator ********************** from accelerate import Accelerator accelerator = Accelerator() model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( model, optimizer, train_dataloader, eval_dataloader ) ##****************** 创建调度器 ********************** from transformers import get_scheduler num_train_epochs = 3 num_update_steps_per_epoch = len(train_dataloader) # 注意,必须在 accelerator.prepare() 之后在调用数据集的 len(...),因为 accelerator.prepare() 可能改变数据集的长度 num_training_steps = num_train_epochs * num_update_steps_per_epoch lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps, ) ##****************** 创建 Repo ********************** from huggingface_hub import Repository, get_full_repo_name # model_name = "bert-finetuned-ner-accelerate" # repo_name = get_full_repo_name(model_name) # print(repo_name) output_dir = "bert-finetuned-ner-accelerate" # repo = Repository(output_dir, clone_from=repo_name) # 也可以忽略这一步 ##****************** 定义 metric 、以及后处理函数 ********************** metric = load_metric("seqeval") def postprocess(predictions, labels): # 将模型输出和标签转换为 metric 可接受的格式 predictions = predictions.detach().cpu().clone().numpy() labels = labels.detach().cpu().clone().numpy() # 移除 label = -100 位置的数据 (包括 token 和 label) true_labels = [[label_names[l] for l in label if l != -100] for label in labels] true_predictions = [ [label_names[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels) ] return true_labels, true_predictions ##****************** 训练流程 ********************** progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): # Training model.train() 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) # Evaluation model.eval() for batch in eval_dataloader: with torch.no_grad(): outputs = model(**batch) predictions = outputs.logits.argmax(dim=-1) labels = batch["labels"] # 在多进程场景下,可能两个进程将 predictions/labels 在进程内部对齐,但是在进程间不一致,这里需要跨进程对齐 predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) predictions_gathered = accelerator.gather(predictions) # 跨进程收集 labels_gathered = accelerator.gather(labels) true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) metric.add_batch(predictions=true_predictions, references=true_labels) results = metric.compute() # 计算验证集的指标 print(f"epoch {epoch}:", { key: results[f"overall_{key}"] for key in ["precision", "recall", "f1", "accuracy"]}, ) # epoch 0: {'precision': 0.930, 'recall': 0.904, 'f1': 0.917, 'accuracy': 0.982} # epoch 1: {'precision': 0.944, 'recall': 0.912, 'f1': 0.928, 'accuracy': 0.985} # epoch 2: {'precision': 0.948, 'recall': 0.928, 'f1': 0.938, 'accuracy': 0.987} ##****************** 保存和上传模型 ********************** accelerator.wait_for_everyone() # 所有进程都到达这个阶段然后继续执行,如果任何一个进程未到达,则所有其他进程都要等待 unwrapped_model = accelerator.unwrap_model(model) # 获取底层的模型,因为 accelerator 可能在分布式环境中工作 unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) # 用 accelerator.save 来保存 if accelerator.is_main_process: # 只有主进程才执行下面的步骤 tokenizer.save_pretrained(output_dir) # 保存 tokenizer # repo.push_to_hub(commit_message=f"Training in progress epoch {epoch}", blocking=False) # blocking = False 表示异步推送
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
![扫码二维码加入Web技术交流群](/public/img/jiaqun_03.jpg)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论