使用自動模型

2023-09-04 06:01:09

本文通過文字分類任務演示了HuggingFace自動模型使用方法,既不需要手動計算loss,也不需要手動定義下游任務模型,通過閱讀自動模型實現原始碼,提高NLP建模能力。

一.任務和資料集介紹
1.任務介紹
前面章節通過手動方式定義下游任務模型,HuggingFace也提供了一些常見的預定義下游任務模型,如下所示: 說明:包括預測下一個詞,文字填空,問答任務,文字摘要,文字分類,命名實體識別,翻譯等。

2.資料集介紹
本文使用ChnSentiCorp資料集,不清楚的可以參考中文情感分類介紹。一些樣例如下所示:

二.準備資料集
1.使用編碼工具

def load_encode_tool(pretrained_model_name_or_path):
    """
    載入編碼工具
    "
""
    tokenizer = BertTokenizer.from_pretrained(Path(f'{pretrained_model_name_or_path}'))
    return tokenizer
if __name__ == '__main__':
    # 測試編碼工具
    pretrained_model_name_or_path = r'L:/20230713_HuggingFaceModel/bert-base-chinese'
    tokenizer = load_encode_tool(pretrained_model_name_or_path)
    print(tokenizer)

輸出結果如下所示:

BertTokenizer(name_or_path='L:\20230713_HuggingFaceModel\bert-base-chinese', vocab_size=21128, model_max_length=1000000000000000019884624838656, is_fast=False, padding_side='right', truncation_side='right', special_tokens={'unk_token''[UNK]''sep_token''[SEP]''pad_token''[PAD]''cls_token''[CLS]''mask_token''[MASK]'}, clean_up_tokenization_spaces=True)

2.定義資料集
直接使用HuggingFace資料集物件,如下所示:

def load_dataset_from_disk():
    pretrained_model_name_or_path = r'L:\20230713_HuggingFaceModel\ChnSentiCorp'
    dataset = load_from_disk(pretrained_model_name_or_path)
    return dataset
if __name__ == '__main__':
    # 載入資料集
    dataset = load_dataset_from_disk()
    print(dataset)

輸出結果如下所示:

DatasetDict({
    train: Dataset({
        features: ['text''label'],
        num_rows: 9600
    })
    validation: Dataset({
        features: ['text''label'],
        num_rows: 1200
    })
    test: Dataset({
        features: ['text''label'],
        num_rows: 1200
    })
})

3.定義計算裝置

# 定義計算裝置
device = 'cpu'
if torch.cuda.is_available():
    device = 'cuda'
# print(device)

4.定義資料整理函數

def collate_fn(data):
    sents = [i['text'for i in data]
    labels = [i['label'for i in data]
    #編碼
    data = tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents, # 輸入文字
            truncation=True, # 是否截斷
            padding=True, # 是否填充
            max_length=512, # 最大長度
            return_tensors='pt'# 返回的型別
    #轉移到計算裝置
    for k, v in data.items():
        data[k] = v.to(device)
    data['labels'] = torch.LongTensor(labels).to(device)
    return data

5.定義資料集載入器

# 資料集載入器
loader = torch.utils.data.DataLoader(dataset=dataset['train'], batch_size=16, collate_fn=collate_fn, shuffle=True, drop_last=True)
print(len(loader))

# 檢視資料樣例
for i, data in enumerate(loader):
    break
for k, v in data.items():
    print(k, v.shape)

輸出結果如下所示:

600
input_ids torch.Size([16, 200])
token_type_ids torch.Size([16, 200])
attention_mask torch.Size([16, 200])
labels torch.Size([16])

三.載入自動模型
使用HuggingFace的AutoModelForSequenceClassification工具類載入自動模型,來實現文字分類任務,程式碼如下:

# 載入預訓練模型
model = AutoModelForSequenceClassification.from_pretrained(Path(f'{pretrained_model_name_or_path}'), num_labels=2)
model.to(device)
print(sum(i.numel() for i in model.parameters()) / 10000)

四.訓練和測試
1.訓練
需要說明自動模型本身包括loss計算,因此在train()中就不再需要手工計算loss,如下所示:

def train():
    # 定義優化器
    optimizer = AdamW(model.parameters(), lr=5e-4)
    # 定義學習率調節器
    scheduler = get_scheduler(name='linear'# 調節器名稱
                              num_warmup_steps=0, # 預熱步數
                              num_training_steps=len(loader), # 訓練步數
                              optimizer=optimizer) # 優化器
    # 將模型切換到訓練模式
    model.train()
    # 按批次遍歷訓練集中的資料
    for i, data in enumerate(loader):
        # print(i, data)
        # 模型計算
        out = model(**data)
        # 計算1oss並使用梯度下降法優化模型引數
        out['loss'].backward() # 反向傳播
        optimizer.step() # 優化器更新
        scheduler.step() # 學習率調節器更新
        optimizer.zero_grad() # 梯度清零
        model.zero_grad() # 梯度清零
        # 輸出各項資料的情況,便於觀察
        if i % 10 == 0:
            out_result = out['logits'].argmax(dim=1)
            accuracy = (out_result == data.labels).sum().item() / len(data.labels)
            lr = optimizer.state_dict()['param_groups'][0]['lr']
            print(i, out['loss'].item(), lr, accuracy)

其中,out資料結構如下所示:

2.測試

def test():
    # 定義測試資料集載入器
    loader_test = torch.utils.data.DataLoader(dataset=dataset['test'],
                                              batch_size=32,
                                              collate_fn=collate_fn,
                                              shuffle=True,
                                              drop_last=True)
    # 將下游任務模型切換到執行模式
    model.eval()
    correct = 0
    total = 0
    # 按批次遍歷測試集中的資料
    for i, data in enumerate(loader_test):
        # 計算5個批次即可,不需要全部遍歷
        if i == 5:
            break
        print(i)
        # 計算
        with torch.no_grad():
            out = model(**data)
        # 統計正確率
        out = out['logits'].argmax(dim=1)
        correct += (out == data.labels).sum().item()
        total += len(data.labels)
    print(correct / total)

五.深入自動模型原始碼
1.載入組態檔過程
在執行AutoModelForSequenceClassification.from_pretrained(Path(f'{pretrained_model_name_or_path}'), num_labels=2)時,實際上呼叫了AutoConfig.from_pretrained(),該函數返回的config物件內容如下所示: config物件如下所示:

BertConfig {
  "_name_or_path""L:\\20230713_HuggingFaceModel\\bert-base-chinese",
  "architectures": [
    "BertForMaskedLM"
  ],
  "attention_probs_dropout_prob": 0.1,
  "classifier_dropout": null,
  "directionality""bidi",
  "hidden_act""gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type""bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "pooler_fc_size": 768,
  "pooler_num_attention_heads": 12,
  "pooler_num_fc_layers": 3,
  "pooler_size_per_head": 128,
  "pooler_type""first_token_transform",
  "position_embedding_type""absolute",
  "transformers_version""4.32.1",
  "type_vocab_size": 2,
  "use_cache"true,
  "vocab_size": 21128
}

(1)_name_or_path=bert-base-chinese:模型名字。
(2)attention_probs_DropOut_prob=0.1:注意力層DropOut的比例。
(3)hidden_act=gelu:隱藏層的啟用函數。
(4)hidden_DropOut_prob=0.1:隱藏層DropOut的比例。
(5)hidden_size=768:隱藏層神經元的數量。
(6)layer_norm_eps=1e-12:標準化層的eps引數。
(7)max_position_embeddings=512:句子的最大長度。
(8)model_type=bert:模型型別。
(9)num_attention_heads=12:注意力層的頭數量。
(10)num_hidden_layers=12:隱藏層層數。
(11)pad_token_id=0:PAD的編號。
(12)pooler_fc_size=768:池化層的神經元數量。
(13)pooler_num_attention_heads=12:池化層的注意力頭數。
(14)pooler_num_fc_layers=3:池化層的全連線神經網路層數。
(15)vocab_size=21128:字典的大小。

2.初始化模型過程
BertForSequenceClassification類建構函式包括一個BERT模型和全連線神經網路,基本思路為通過BERT提取特徵,通過全連線神經網路進行分類,如下所示:

def __init__(self, config):
    super().__init__(config)
    self.num_labels = config.num_labels
    self.config = config

    self.bert = BertModel(config)
    classifier_dropout = (
        config.classifier_dropout if config.classifier_dropout is not None else config.hidden_dropout_prob
    )
    self.dropout = nn.Dropout(classifier_dropout)
    self.classifier = nn.Linear(config.hidden_size, config.num_labels)

    # Initialize weights and apply final processing
    self.post_init()

通過forward()函數可證明以上推測,根據問題型別為regression(MSELoss()損失函數)、single_label_classification(CrossEntropyLoss()損失函數)和multi_label_classification(BCEWithLogitsLoss()損失函數)選擇損失函數。

參考文獻:
[1]HuggingFace自然語言處理詳解:基於BERT中文模型的任務實戰
[2]https://github.com/ai408/nlp-engineering/blob/main/20230625_HuggingFace自然語言處理詳解/第12章:使用自動模型.py