Hugging Face实战(NLP实战/Transformer实战/预训练模型/分词器/模型微调/模型自动选择/PyTorch版本/代码逐行解析)下篇之模型训练
模型訓練的流程代碼是不是特別特別多啊?有的童鞋看過Bert那個源碼寫的特別特別詳細,參數賊多,運行一個模型百八十個參數的。
Transformer對NLP的理解是一個大道至簡的感覺,Hugging Face的老板接受采訪的時候講過他想給算法人提供一個非常簡單實用的模板,因為NLP本身做的就是一個非常簡單的事情。但是由于一些開源項目的門檻過高,所以大家用起來特別麻煩。Hugging Face的老板只用了30個做兼職的人就把NLP的江山打下來了,微軟、谷歌、openAI、Facebook真的直接哭死。
有任何問題歡迎在下面留言
本篇文章的代碼運行界面演示結果均在notebook中進行
本篇文章配套的代碼資源已經上傳
本文章是下篇的內容,主要解析模型訓練,上篇內容解析如何進行模型調用:
Hugging Face實戰(NLP實戰/Transformer實戰/預訓練模型/分詞器/模型微調/模型自動選擇/PyTorch版本/代碼逐行解析)上篇之模型調用_會害羞的楊卓越的博客-CSDN博客
目錄
1、數據集與模型
1.1背景
1.2 數據集構建
1.3 HuggingFace官網的數據集
2、處理數據
2.1 單條數據處理
2.2 對所有數據進行處理
2.3數據封裝
3 模型訓練
3.1模型參數
3.2模型導入
3.3模型訓練
4 模型測試
4.1模型測試
4.2訓練評估函數
1、數據集與模型
- 這些數據集都可以直接用:點我直達數據集鏈接
- 咱們今天玩這個(GLUE):點我直達GLUE數據集
1.1背景
Hugging Face不僅僅是Transformer,你點開可以看到非常重要的四個大模塊。
首先是提供了Transformers本身的工具包源碼
首先是tokenizers,一個高效的分詞器,對文本數據進行預處理,你別管是用什么語言做的,用就完了。實際上最核心的東西就是tokenizers,對于我們來說是一個開箱即用的過程,但是開發出這個工具確實需要特別大的成本和代價。
然后是數據集,你別一個模型一個數據集了,全部用統一格式的數據集吧。實際上人家在下一盤大棋,不僅給你提供API,還要提供數據集,統一了數據格式,以后真競爭不過它。與此同時想想,國內還在天天“遙遙領先!遙遙領先!遙遙領先!”
?如果你用非常高端的GPU來訓練,提供一個加速包讓你更夠有更高的效率去訓練,幫你發揮出高端設備的能力。
1.2 數據集構建
首先是dataset這個安裝包,真的很簡單,直接pip install就行了:
pip install datasetsglue數據集是NLP中Hello World級別的數據集,但是它卻包含來各大經典任務,執行下面的代碼后,就會進行下載:
import warnings warnings.filterwarnings("ignore") from datasets import load_dataset#https://github.com/huggingface/datasetsraw_datasets = load_dataset("glue", "mrpc") raw_datasets執行后的提示:
Using the latest cached version of the module from C:\Users\Administrator\.cache\huggingface\modules\datasets_modules\datasets\glue\dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad (last modified on Sun May 1 15:59:37 2022) since it couldn't be found locally at glue., or remotely on the Hugging Face Hub. Reusing dataset glue (C:\Users\Administrator\.cache\huggingface\datasets\glue\mrpc\1.0.0\dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad)所以梯子,該整還是得整,代碼執行后的輸出:
A Jupyter Widget DatasetDict({train: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 3668})validation: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 408})test: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 1725}) })解釋一下上面的數據,看看公開數據集到底長啥樣子
train:(?train: Dataset、validation: Dataset、test: Dataset)這三個分別表示訓練、驗證、測試集,很簡單不用講
sentence1 & sentence2:分別是兩個句子
label:表示的是標簽,標簽意義是這兩個句子之間有沒有相關性
idx:這個樣本的索引,然后后面的
num_rows:表示在訓練集中一共有3668行數據,每行數據都包含了兩個句子和一個標簽,一個索引
選擇訓練集的第100號樣本,打印出來看看,代碼:
raw_train_dataset = raw_datasets["train"] raw_train_dataset[100]打印:
{'idx': 114,'label': 0,'sentence1': 'The Nasdaq composite index inched up 1.28 , or 0.1 percent , to 1,766.60 , following a weekly win of 3.7 percent .','sentence2': 'The technology-laced Nasdaq Composite Index .IXIC was off 24.44 points , or 1.39 percent , at 1,739.87 .'}idx索引不用去管,label為0,是二分類的,然后是兩個句子,這就是一個實際的數據集,公開數據集。
1.3 HuggingFace官網的數據集
在官網的datasets中搜索glue,打開后可以看到很多數據集,其中的mrpc就是剛剛加載的數據集
?可以看到很多預覽的東西,后續可以自己慢慢去看。
2、處理數據
2.1 單條數據處理
使用AutoTokenizer來處理數據
from transformers import AutoTokenizercheckpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint)從Transformer中導入AutoTokenizer,將一個經典模型讀進來,現成的分詞器去分這兩句話:
inputs = tokenizer("This is the first sentence.", "This is the second one.") inputs打印結果:
{'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}這個分詞器有沒有什么不一樣呢?
input_ids 和attention_mask之前提到過,這個token_type_ids呢?
現在的輸入是兩句話,但是即便你輸入的是兩句話,也會將你的兩句話合為一體(你仔細觀察input_ids 就知道了)。但是為什么是會將兩句話合成一句話呢?是跟你選擇得模型有關的!
但是我怎么知道怎么選模型呢?
你再看看Hugging Face的官網,點開Models,在左邊可以根據任務類型進行選擇分類,然后在右邊選擇模型
你們真是趕上好時候了,保姆級教程直接給你了。在三四年前,你做一個NLP任務,跑通一個模型賊費勁,該一個源碼真給你干吐血來。
說回前面的token_type_ids,0就表示的第一句話,1表示第二句話。
把ID解碼出來看看,代碼:
tokenizer.convert_ids_to_tokens(inputs["input_ids"])打印結果:
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]','this', 'is', 'the', 'second', 'one', '.', '[SEP]']2.2 對所有數據進行處理
當我們對數據進行預處理的時候,兩種方法做,一種是使用pandas工具包,清洗一遍數據。第二種就是官方強烈推薦的map方法去做的所有的數據預處理。
我們的任務有兩句話,是要一起做一個tokenizer。先定義一個函數:
def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)在執行map這個函數的時候,會對每一個樣本都執行這個函數的操作,數據集的所有數據都會映射為向量:
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets提示:
Loading cached processed dataset at C:\Users\Administrator\.cache\huggingface\datasets\glue\mrpc\1.0.0\dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad\cache-fcf7d43fefa50575.arrow Loading cached processed dataset at C:\Users\Administrator\.cache\huggingface\datasets\glue\mrpc\1.0.0\dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad\cache-6c207e4b2226e7c9.arrow打印結果:
A Jupyter Widget DatasetDict({train: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],num_rows: 3668})validation: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],num_rows: 408})test: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask'],num_rows: 1725}) })map函數有一個好處就是指定batched=True,分布式的東西自己研究多麻煩。
看一下上面的輸出,映射完后有什么,看看前面我們有幾個字段:
features: ['sentence1', 'sentence2', 'label', 'idx']映射完之后有幾個字段:
features: ['sentence1', 'sentence2', 'label', 'idx', 'input_ids', 'token_type_ids', 'attention_mask']多了后面幾個,都是tokenizer生成的。當我們有了tokenizer生成的input_ids、token_type_ids、attention_mask前面的還需要嗎?
前面的我們都不需要了,都需要刪掉,后面會講怎么刪掉哈。
2.3數據封裝
在NLP中怎么做數據的DataLoader呢?還是調用PyTorch的DataLoader包嗎?不是的!
我們的數據已經做來分詞處理,但是還沒有進行封裝,是不能進入模型處理的。
from transformers import DataCollatorWithPaddingdata_collator = DataCollatorWithPadding(tokenizer=tokenizer)這里為什么會有一個withPadding呢?在數據打包的時候保證64個樣本數據長度都是一致的
將結果打印一下:
tokenized_datasets["train"][0]notebook打印結果:
{'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],'idx': 0,'input_ids': [101, 2572, 3217, 5831, 5496, 2010, 2567, 1010, 3183, 2002, 2170, 1000, 1996, 7409, 1000, 1010, 1997, 9969, 4487, 23809, 3436, 2010, 3350, 1012, 102, 7727, 2000, 2032, 2004, 2069, 1000, 1996, 7409, 1000, 1010, 2572, 3217, 5831, 5496, 2010, 2567, 1997, 9969, 4487, 23809, 3436, 2010, 3350, 1012, 102],'label': 1,'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 .','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, 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]}將數據取出來看看唄:
samples = tokenized_datasets["train"][:8]#取到所有的列 samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}#不需要這些列 [len(x) for x in samples["input_ids"]]#每一個樣本的長度第一行:取出數據集,取訓練集,取前八個樣本,sample即為訓練集的前八個樣本
第二行:一個列表推導式,將["idx", "sentence1", "sentence2"]這三個列過濾掉,剩下的做成字典,k為列名,v為列值
第三行:依次打印出每個樣本的長度
打印結果:
[50, 59, 47, 67, 59, 50, 62, 32]8個樣本長度都不同?在我們任務中樣本長度必須得相同啊。
經過data_collator處理之后,所有的樣本長度都是固定的,再用k:v的形式打印一下
batch = data_collator(samples) {k: v.shape for k, v in batch.items()}打印結果:
{'attention_mask': torch.Size([8, 67]),'input_ids': torch.Size([8, 67]),'labels': torch.Size([8]),'token_type_ids': torch.Size([8, 67])}結果就是按照最大值取,全是8*67了
3 模型訓練
咱玩一個東西,要帶著問題去玩兒,有的人特別擅長做筆記,拿本拿筆記下來?能把所有參數都記下來,真沒什么卵用。什么叫學習,多查,多練,遇到問題了,然后要去解決一個問題的一個過程,這才叫學習。
3.1模型參數
先打開這個API文檔:
API文檔,實際用的時候一定對應著來
API文檔就是說明書,你得認真的看,有你想知道的一切答案
首先第一步,從Transformers中導進來訓練參數
from transformers import TrainingArgumentstraining_args = TrainingArguments("test-trainer")設置好后再打印出來看看:
print(training_args?)
TrainingArguments( _n_gpu=0, adafactor=False, adam_beta1=0.9, adam_beta2=0.999, adam_epsilon=1e-08, bf16=False, bf16_full_eval=False, dataloader_drop_last=False, dataloader_num_workers=0, dataloader_pin_memory=True, ddp_bucket_cap_mb=None, ddp_find_unused_parameters=None, debug=[], deepspeed=None, disable_tqdm=False, do_eval=False, do_predict=False, do_train=False, eval_accumulation_steps=None, eval_steps=None, evaluation_strategy=IntervalStrategy.NO, fp16=False, fp16_backend=auto, fp16_full_eval=False, fp16_opt_level=O1, gradient_accumulation_steps=1, gradient_checkpointing=False, greater_is_better=None, group_by_length=False, half_precision_backend=auto, hub_model_id=None, hub_strategy=HubStrategy.EVERY_SAVE, hub_token=<HUB_TOKEN>, ignore_data_skip=False, label_names=None, label_smoothing_factor=0.0, learning_rate=5e-05, length_column_name=length, load_best_model_at_end=False, local_rank=-1, log_level=-1, log_level_replica=-1, log_on_each_node=True, logging_dir=test-trainer\runs\May26_10-08-48_WIN-BM410VRSBIO, logging_first_step=False, logging_nan_inf_filter=True, logging_steps=500, logging_strategy=IntervalStrategy.STEPS, lr_scheduler_type=SchedulerType.LINEAR, max_grad_norm=1.0, max_steps=-1, metric_for_best_model=None, mp_parameters=, no_cuda=False, num_train_epochs=3.0, optim=OptimizerNames.ADAMW_HF, output_dir=test-trainer, 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=False, push_to_hub_model_id=None, push_to_hub_organization=None, push_to_hub_token=<PUSH_TO_HUB_TOKEN>, remove_unused_columns=True, report_to=['tensorboard', 'wandb'], resume_from_checkpoint=None, run_name=test-trainer, save_on_each_node=False, save_steps=500, save_strategy=IntervalStrategy.STEPS, save_total_limit=None, seed=42, sharded_ddp=[], skip_memory_metrics=True, tf32=None, tpu_metrics_debug=False, tpu_num_cores=None, use_legacy_prediction_loop=False, warmup_ratio=0.0, warmup_steps=0, weight_decay=0.0, xpu_backend=None, )我的天哪,這么多參數,這些參數都能改嗎?
你都能改,要訓練模型的時候,這些參數都要指定的
就算你背下來了,你還是要忘,就是要邊查邊用
比如說我要指定batch怎么指定呢?指定epochs怎么指定呢?
你打開API文檔,看看人家API文檔做的多漂亮。
鼠標停在第一個參數上:
?第一個就是輸出路徑,自己讀一遍,模型保存的位置對不對?后面的也是這樣一個一個看的。
前面我們打印出來的都是默認的參數
3.2模型導入
接下來導一下模型:
from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)模型有一些提示:
Some weights of the model checkpoint at bert-base-uncased were not used when initializing BertForSequenceClassification: ['cls.predictions.bias', 'cls.predictions.transform.dense.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.dense.weight', 'cls.predictions.decoder.weight', 'cls.seq_relationship.bias', 'cls.seq_relationship.weight', 'cls.predictions.transform.LayerNorm.bias'] - This IS expected if you are initializing BertForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model). - This IS NOT expected if you are initializing BertForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model). Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['classifier.weight', 'classifier.bias'] You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.首先確定你任務是什么,比如對序列進行分類,就導入AutoModelForSequenceClassification,選擇模型checkpoint,num_labels=2是什么意思?我們要改輸出層,輸出層不用預訓練模型了,輸出層自己訓練。
所以上面的提示告訴你,很多分類層的權重參數沒有指定到,就是分類的輸出層被自己初始化了,無法加載預訓練模型了,當然了正合我們意。
3.3模型訓練
模型咋訓練?哎呀,太簡單了,真的嗷嗷簡單:
from transformers import Trainertrainer = Trainer(model,training_args,train_dataset=tokenized_datasets["train"],eval_dataset=tokenized_datasets["validation"],data_collator=data_collator,tokenizer=tokenizer, )無論訓練什么都把Trainer導進來,看看參數
- model:我們在上面已經定義了
- training_args:配置參數,前面打印過,現在全是默認的,但是可以改,后續再教怎么改
- train_dataset:訓練集,自己指定,根據前面定義的字典
- eval_dataset:驗證集,自己指定,根據前面定義的字典
- data_collator:這是前面提到的batch
- tokenizer:前面也定義了
不懂沒關系,再次點開前面提到的API,搜一下Trainer,要等個幾秒鐘才會出現:
?不懂就去API里面查:
?看看人家這在線API做的,多招人稀罕啊,鼠標放上面就有解釋了。
指定好參數,直接.train一下就開始訓練了:
trainer.train()訓練過程中會給你打印出損失:
再看?training_args參數中,有一個叫logging_steps=500,就是說500次打印一次損失
還會告訴你一些已經指定的參數:
The following columns in the training set don't have a corresponding argument in `BertForSequenceClassification.forward` and have been ignored: sentence2, idx, sentence1. ***** Running training *****Num examples = 3668Num Epochs = 3Instantaneous batch size per device = 8Total train batch size (w. parallel, distributed & accumulation) = 8Gradient Accumulation steps = 1Total optimization steps = 1377其實這個任務CPU也能跑,但是比較慢,但是最好還是有GPU這個東西哈。
跑完之后還有提示:
Saving model checkpoint to test-trainer\checkpoint-500 Configuration saved in test-trainer\checkpoint-500\config.json Model weights saved in test-trainer\checkpoint-500\pytorch_model.bin tokenizer config file saved in test-trainer\checkpoint-500\tokenizer_config.json Special tokens file saved in test-trainer\checkpoint-500\special_tokens_map.json Saving model checkpoint to test-trainer\checkpoint-1000 Configuration saved in test-trainer\checkpoint-1000\config.json Model weights saved in test-trainer\checkpoint-1000\pytorch_model.bin tokenizer config file saved in test-trainer\checkpoint-1000\tokenizer_config.json Special tokens file saved in test-trainer\checkpoint-1000\special_tokens_map.jsonTraining completed. Do not forget to share your model on huggingface.co/models =)就是你的模型都保存在哪兒了,訓練完成后,就可以得到模型了:
?這分別是500打印一次損失的結果,1000打印一次損失的結果,點進去看,pytorch_model.bin這個文件,就是你訓練的模型
這就是一個訓練過程
4 模型測試
4.1模型測試
模型訓練好了,用驗證集進行一下驗證:
predictions = trainer.predict(tokenized_datasets["validation"]) print(predictions.predictions.shape, predictions.label_ids.shape)?打印的結果:(408, 2) (408,),當然這是打印的維度
前面給到的都是損失值,能不能給出具體的評估呢?datasets 模塊專門提供了評估子模塊load_metric
from datasets import load_metricmetric = load_metric("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids)打印結果:
A Jupyter Widget {'accuracy': 0.8186274509803921, 'f1': 0.8754208754208753}在評估的參數中,只需要傳入兩個值,一個是predictions,一個是references,預測和標簽嘛
4.2訓練評估函數
我們在訓練過程中能不能指定評估參數呢,那就需要將它封裝成一個函數了:
def compute_metrics(eval_preds):metric = load_metric("glue", "mrpc")logits, labels = eval_predspredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)逐行解釋:
最后在訓練參數中將上面的函數指定進去:
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)trainer = Trainer(model,training_args,train_dataset=tokenized_datasets["train"],eval_dataset=tokenized_datasets["validation"],data_collator=data_collator,tokenizer=tokenizer,compute_metrics=compute_metrics, )compute_metrics=compute_metrics,這是一個固定的寫法
再訓練看一下:
trainer.train()這回打印的指標就變多了:
這就完了,源碼點我直達。
這就完了,這簡直就是,簡單TM給簡單開門,簡單到家了?
總結
以上是生活随笔為你收集整理的Hugging Face实战(NLP实战/Transformer实战/预训练模型/分词器/模型微调/模型自动选择/PyTorch版本/代码逐行解析)下篇之模型训练的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你若离去,我自飘零
- 下一篇: 网络空间安全---常见网络漏洞