transformers是huggingface提供的預(yù)訓(xùn)練模型庫挪鹏,可以輕松調(diào)用API來得到你的詞向量圾旨。transformers的前身有pytorch-pretrained-bert妙黍,pytorch-transformers稚铣,原理基本都一致衰伯。本文以bert為例,主要介紹如何調(diào)用transformers庫以及下游任務(wù)的使用方法积蔚。
1. transformers相關(guān)配置
-
在正式使用之前意鲸,首先要安裝transformers包,此以python3.7為例:
python == 3.7.3 tensorflow == 2.0.0 pytorch == 1.5.1 transformers == 3.0.2
若準(zhǔn)備采用GPU加速尽爆,需自于Pytorch怎顾、Tensorflow官網(wǎng)上進行CUDA、CuDNN版本遴選配置
2. 整理架構(gòu)
-
transformers新版本中使用每個模型只需要三個標(biāo)準(zhǔn)類
-
configuration:configuration是模型具體的結(jié)構(gòu)配置漱贱,例如可以配置多頭的數(shù)量等槐雾,這里配置需要注意的地方就是,如果自定義配置不改變核心網(wǎng)絡(luò)結(jié)構(gòu)的則仍舊可以使用預(yù)訓(xùn)練模型權(quán)重幅狮,如果配置涉及到核心結(jié)構(gòu)的修改募强,例如前饋網(wǎng)絡(luò)的隱層神經(jīng)元的個數(shù),則無法使用預(yù)訓(xùn)練模型權(quán)重崇摄,這個時候transformers會默認你要重新自己預(yù)訓(xùn)練一個模型從而隨機初始化整個模型的權(quán)重擎值,這是是一種半靈活性的設(shè)計
# configuration.json { "architectures": [ "BertForMaskedLM" ], "attention_probs_dropout_prob": 0.1, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "max_position_embeddings": 512, "num_attention_heads": 12, "num_hidden_layers": 12, "type_vocab_size": 2, "vocab_size": 30522 }
將配置好的configuration.json配置加載如下:
config = BertConfig.from_json_file(os.path.join(PATH, "config.json")) model = BertModel.from_pretrained(PATH, config=config)
#Tensorflow2版本 from transformers import TFBertModel import tensorflow as tf config = BertConfig.from_json_file(os.path.join(PATH, "config.json")) model = TFBertModel.from_pretrained(PATH, config=config)
-
models:models用于指定使用哪一種模型,例如model為bert逐抑,則相應(yīng)的網(wǎng)絡(luò)結(jié)構(gòu)為bert的網(wǎng)絡(luò)結(jié)構(gòu)
# Pytorch版本 import torch from transformers import BertModel, BertConfig, BertTokenizer model = BertModel.from_pretrained("bert-base-uncased")
# Tensorflow2版本 import tensorflow as tf from transformers import TFBertModel, BertConfig, BertTokenizer model = TFBertModel.from_pretrained("bert-base-uncased")
-
tokenizer
# Pytorch版本 import torch from transformers import BertModel, BertConfig, BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Tensorflow2版本 import tensorflow as tf from transformers import TFBertModel, BertConfig, BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
所有這些類都可以使用通用的from_pretrained()實例化方法鸠儿,以簡單統(tǒng)一的方式從受過訓(xùn)練的實例中初始化
-
3. 參數(shù)詳解
3.1 配置 Bert 模型 transformers.PretrainedConfig
-
transformers.BertConfig
可以自定義 Bert 模型的結(jié)構(gòu),以下參數(shù)都是可選的:- 通用參數(shù)
- vocab_size:詞匯數(shù)厕氨,默認30522
- hidden_size:編碼器內(nèi)隱藏層神經(jīng)元數(shù)量进每,默認768
- num_hidden_layers:編碼器內(nèi)隱藏層層數(shù),默認12
- num_attention_heads:編碼器內(nèi)注意力頭數(shù)命斧,默認12
- 其他參數(shù)
- intermediate_size:編碼器內(nèi)全連接層的輸入維度田晚,默認3072
- hidden_act:編碼器內(nèi)激活函數(shù),默認"gelu"国葬,還可為"relu"肉瓦、"swish"或 "gelu_new"
- hidden_dropout_prob:詞嵌入層或編碼器的 dropout,默認為0.1
- attention_probs_dropout_prob:注意力機制的 dropout胃惜,默認為0.1
- max_position_embeddings:模型使用的最大序列長度泞莉,默認為512
- type_vocab_size:詞匯表類別,默認為2
- initializer_range:神經(jīng)元權(quán)重的標(biāo)準(zhǔn)差船殉,默認為0.02
- layer_norm_eps:layer normalization 的 epsilon 值鲫趁,默認為 1e-12
- 通用參數(shù)
-
transformers.BertConfig.from_json_file
可以從json文件中進行配置參數(shù)的修改:transformers.BertConfig.from_json_file(json_file)
transformers.BertConfig.from_dict
可以從字典中進行配置參數(shù)的修改-
transformers.BertConfig.from_pretrained
可以從預(yù)訓(xùn)練模型中進行配置參數(shù)的配置transformers.BertConfig.from_pretrained(pretrained_model_name_or_path)
-
Examples:
config = BertConfig.from_pretrained('bert-base-uncased') config = BertConfig.from_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)
3.2 加載 Bert 模型 transformers.PreTrainedModel
transformers.BertModel.from_pretrained
從預(yù)訓(xùn)練的模型配置實例化預(yù)訓(xùn)練的 pytorch 模型-
BertModel 主要為transformer encoder結(jié)構(gòu),包含三個部分:
embeddings利虫,即BertEmbeddings類的實體挨厚,根據(jù)單詞符號獲取對應(yīng)的向量表示堡僻;
encoder,即BertEncoder類的實體疫剃;
-
pooler钉疫,即BertPooler類的實體,這一部分是可選的巢价。
transformers.BertModel.from_pretrained(pretrained_model_name_or_path)
#Tensorflow2版本 transformers.TFBertModel.from_pretrained(pretrained_model_name_or_path)
-
BertModel前向傳播過程中各個參數(shù)的含義以及返回值:
- input_ids:經(jīng)過 tokenizer 分詞后的 subword 對應(yīng)的下標(biāo)列表牲阁;
-
attention_mask:在 self-attention 過程中,這一塊 mask 用于標(biāo)記 subword 所處句子和
padding 的區(qū)別壤躲,將 padding 部分填充為 0城菊; - token_type_ids:標(biāo)記 subword 當(dāng)前所處句子(第一句/第二句/ padding);
- position_ids:標(biāo)記當(dāng)前詞所在句子的位置下標(biāo)碉克;
- head_mask:用于將某些層的某些注意力計算無效化凌唬;
- inputs_embeds:如果提供了,那就不需要input_ids漏麦,跨過 embedding lookup 過程直接作為 Embedding 進入 Encoder 計算客税;
- encoder_hidden_states:這一部分在 BertModel 配置為 decoder 時起作用,將執(zhí)行 cross-attention 而不是 self-attention撕贞;
- encoder_attention_mask:同上霎挟,在 cross-attention 中用于標(biāo)記 encoder 端輸入的 padding;
- past_key_values:這個參數(shù)貌似是把預(yù)先計算好的 K-V 乘積傳入麻掸,以降低 cross-attention 的開銷(因為原本這部分是重復(fù)計算)酥夭;
- use_cache:將保存上一個參數(shù)并傳回,加速 decoding脊奋;
- output_attentions:是否返回中間每層的 attention 輸出熬北;
- output_hidden_states:是否返回中間每層的輸出;
-
return_dict:是否按鍵值對的形式(ModelOutput 類诚隙,也可以當(dāng)作 tuple 用)返回輸出讶隐,默認為真。
注意久又,這里的 head_mask 對注意力計算的無效化巫延,和下文提到的注意力頭剪枝不同,而僅僅把某些注意力的計算結(jié)果給乘以這一系數(shù)地消。
Examples:
from transformers import BertConfig, BertModel
# 從huggingface.co 下載模型和配置并緩存炉峰。
model = BertModel.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('./test/saved_model/')
# 在加載期間更新配置
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=True)
assert model.config.output_attentions == True
config = BertConfig.from_json_file('./tf_model/my_tf_model_config.json')
model = BertModel.from_pretrained('./tf_model/my_tf_checkpoint.ckpt.index', from_tf=True, config=config)
# Loading from a Flax checkpoint file instead of a PyTorch model (slower)
model = BertModel.from_pretrained('bert-base-uncased', from_flax=True)
3.3 優(yōu)化器 Optimization
3.3.1transformers.AdamW
- AdamW:transformers 庫實現(xiàn)了基于權(quán)重衰減的優(yōu)化器,這個優(yōu)化器初始化時有6個參數(shù)脉执,第一個是params疼阔,可以是torch的Parameter,也可以是一個grouped參數(shù)。betas是Adam的beta參數(shù)婆廊,b1和b2迅细。eps也是Adam為了數(shù)值穩(wěn)定的參數(shù)。correct_bias淘邻,如果應(yīng)用到tf的模型上時需要設(shè)置為False茵典。具體參數(shù)配置如下:
-
params (
Iterable[nn.parameter.Parameter]
) :可迭代的參數(shù)以優(yōu)化或定義參數(shù)組的字典 -
lr (
float
, optional , defaults to 1e-3) :要使用的學(xué)習(xí)率 -
betas (
Tuple[float,float]
, optional , defaults to (0.9, 0.999)) :Adam 的 betas 參數(shù) (b1, b2) -
eps (
float
, optional , defaults to 1e-6) :Adam's epsilon 數(shù)值穩(wěn)定性 -
weight_decay (
float
, optional , defaults to 0) :要應(yīng)用的解耦權(quán)重衰減 -
Correct_bias (
bool
, optional , defaults to True ) :是否糾正 Adam 中的偏差
-
params (
3.3.2 transformers.Adafactor
- Adafactor:具有次線性記憶代價的自適應(yīng)學(xué)習(xí)率 。請注意宾舅,此優(yōu)化器根據(jù)scale參數(shù)统阿、相對步長和warmup_init選項在內(nèi)部調(diào)整學(xué)習(xí)速率。要使用手動(外部)學(xué)習(xí)速率計劃贴浙,您應(yīng)將scale參數(shù)設(shè)置為False,相對步驟設(shè)置為False署恍。具體參數(shù)配置如下:
-
params (
Iterable[nn.parameter.Parameter]
):用于優(yōu)化的參數(shù)的Iterable或定義參數(shù)組的字典崎溃。 -
lr (
float
, optional) :要使用的學(xué)習(xí)率 -
eps (
Tuple[float, float]
, optional, defaults to (1e-30, 1e-3)) :平方梯度和參數(shù)比例的正則化常數(shù) -
clip_threshold (
float
, optional, defaults 1.0):最終梯度更新的均方根閾值 -
decay_rate (
float
, optional, defaults to -0.8):用于計算平方的運行平均值的系數(shù) -
beta1 (
float
, optional) :用于計算梯度運行平均值的系數(shù) -
weight_decay (
float
, optional, defaults to 0):權(quán)重衰減(L2懲罰) -
scale_parameter (
bool
, optional, defaults toTrue
):如果為True,則學(xué)習(xí)率按均方根進行縮放 -
relative_step (
bool
, optional, defaults toTrue
):如果為True盯质,則計算與時間相關(guān)的學(xué)習(xí)率袁串,而不是外部學(xué)習(xí)率 -
warmup_init (
bool
, optional, defaults toFalse
) :時間相關(guān)的學(xué)習(xí)速率計算取決于是否使用預(yù)熱初始化
-
params (
3.3.3transformers.AdamWeightDecay(Tensorflow2)
- AdamWeightDecay:transformers 庫實現(xiàn)的對于TF2模型的基于權(quán)重衰減的優(yōu)化器,這個優(yōu)化器初始化時有10個參數(shù)呼巷,第一個是learning_rate囱修,用于設(shè)置基礎(chǔ)學(xué)習(xí)率。接下來是Adam的衰減穩(wěn)定設(shè)置 beta_1,beta_2與epsilon王悍。amsgrad決定是否應(yīng)用Amsgrad破镰,weight_decay_rate決定了衰減率, include_in_weight_decay決定了應(yīng)用權(quán)重衰減的參數(shù)名字压储,exclude_from_weight_decay決定了不參與權(quán)重衰減的參數(shù)名字鲜漩,name和kwargs是tensorflow優(yōu)化器的常規(guī)參數(shù)。具體參數(shù)配置如下:
-
learning_rate (
float
, optional, default to 1e-3 ) :基礎(chǔ)學(xué)習(xí)率 -
beta_1, beta_2, epsilon (
float
, optional , defaults to 0.9, 0.999, 1e-7 ) :Adam設(shè)置參數(shù) -
amsgrad (
bool
, optional, default to False ) :決定是否應(yīng)用Amsgrad -
weight_decay_rate (
float
, optional , defaults to 0 ) :權(quán)重衰減率 -
include_in_decay (
List[str]
, optional) :應(yīng)用權(quán)重衰減的參數(shù)名字 -
exclude_from_weight_deacy (
List[str]
,optional ) :不參與權(quán)重衰減的參數(shù)名字 -
name (
str
, optional, defaults to ‘AdamWeightDecay’ ) :名稱
-
learning_rate (
3.3.4 Examples:
from transformers.optimization import Adafactor, AdafactorSchedule
optimizer = Adafactor(model.parameters(), scale_parameter=True, relative_step=True, warmup_init=True, lr=None)
lr_scheduler = AdafactorSchedule(optimizer)
***training step***
optimizer.step()
scheduler.step()
3.4 加載分詞模型 transformers.PreTrainedTokenizer
-
所有分詞器的基類:處理tokenization和special tokens的所有共享方法集惋,以及下載/緩存/加載預(yù)訓(xùn)練標(biāo)記器的方法以及向詞匯表中添加標(biāo)記的方法孕似。
- BasicTokenizer負責(zé)處理的第一步——按標(biāo)點、空格等分割句子刮刑,并處理是否統(tǒng)一小寫喉祭,以及清理非法字符
- 對于中文字符,通過預(yù)處理(加空格)來按字分割雷绢;同時可以通過never_split指定對某些詞不進行分割泛烙;這一步是可選的(默認執(zhí)行)
- Word_Piece_Tokenizer在詞的基礎(chǔ)上,進一步將詞分解為子詞(subword)翘紊。subword 介于 char 和 word 之間胶惰,既在一定程度保留了詞的含義,又能夠照顧到英文中單復(fù)數(shù)霞溪、時態(tài)導(dǎo)致的詞表爆炸和未登錄詞的 OOV(Out-Of-Vocabulary)問題孵滞,將詞根與時態(tài)詞綴等分割出來中捆,從而減小詞表,也降低了訓(xùn)練難度
- 例如坊饶,tokenizer 這個詞就可以拆解為"token"和"##izer"兩部分泄伪,注意后面一個詞的"##"表示接在前一個詞后面。
-
BertTokenizer有以下常用方法:
-
vocab_file (
str
):包含詞匯表的文件 -
do_lower_case (
bool
, optional, defaults toTrue
):標(biāo)記化時是否將輸入小寫 -
do_basic_tokenize (
bool
, optional, defaults toTrue
):是否在WordPiece之前進行基本分詞化 -
never_split (
Iterable
, optional):在標(biāo)記化過程中永遠不會分割的標(biāo)記集合匿级。僅當(dāng)do_basic_tokenize=True
時才有效 -
unk_token (
str
, optional, defaults to"[UNK]"
):未知標(biāo)記蟋滴。不在詞匯表中的令牌無法轉(zhuǎn)換為ID,而是設(shè)置為此標(biāo)記 -
sep_token (
str
, optional, defaults to"[SEP]"
):分隔符標(biāo)記痘绎,用于從多個序列構(gòu)建序列津函,例如用于序列分類的兩個序列或用于文本和用于問答的問題。它還用作使用特殊令牌構(gòu)建的序列的最后一個標(biāo)記 -
pad_token (
str
, optional, defaults to"[PAD]"
):用于填充的標(biāo)記孤页,例如在批處理不同長度的序列時補充 -
cls_token (
str
, optional, defaults to"[CLS]"
):進行序列分類時使用的分類器標(biāo)記(對整個序列進行分類尔苦,而不是按令牌分類)。當(dāng)使用特殊標(biāo)記構(gòu)建時行施,它是序列的第一個標(biāo)記允坚。 -
mask_token (
str
, optional, defaults to"[MASK]"
):用于MASK值的標(biāo)記。這是使用掩碼語言建模訓(xùn)練此模型時使用的標(biāo)記蛾号,作為模型將嘗試預(yù)測的標(biāo)記
-
vocab_file (
-
Example:
# 獲取最后一層隱層的embedding from transformers import BertTokenizer, BertModel import torch tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertModel.from_pretrained('bert-base-uncased') inputs = tokenizer("Hello, my dog is cute", return_tensors="pt") inputs = torch.tensor([inputs]) outputs = model(**inputs) last_hidden_states = outputs.last_hidden_state
3.5 BERT的應(yīng)用微調(diào)
-
將數(shù)據(jù)集轉(zhuǎn)換為可以訓(xùn)練BERT的格式
-
BERT令牌生成器
-
要將文本提供給BERT稠项,必須將其拆分為令牌,然后將這些令牌映射到令牌生成器詞匯表中的索引
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True) sentence = "Hello, my son is cuting." input_ids_method1 = torch.tensor( tokenizer.encode(sentence, add_special_tokens=True)) # Batch size 1 # tensor([ 101, 7592, 1010, 2026, 2365, 2003, 3013, 2075, 1012, 102]) input_token2 = tokenizer.tokenize(sentence) # ['hello', ',', 'my', 'son', 'is', 'cut', '##ing', '.'] input_ids_method2 = tokenizer.convert_tokens_to_ids(input_token2) # tensor([7592, 1010, 2026, 2365, 2003, 3013, 2075, 1012]) # 并沒有開頭和結(jié)尾的標(biāo)記:[cls]鲜结、[sep]
-
-
特殊令牌添加
- 在每個句子的末尾展运,我們需要附加特殊
[SEP]
標(biāo)記,該令牌是兩句任務(wù)的產(chǎn)物精刷,其中給BERT兩個單獨的句子并要求確定某些內(nèi)容 - 對于分類任務(wù)乐疆,必須在
[CLS]
每個句子的開頭添加特殊標(biāo)記。此令牌具有特殊意義贬养。BERT由12個Transformer層組成挤土。每個轉(zhuǎn)換器接收一個令牌嵌入列表,并在輸出上產(chǎn)生相同數(shù)量的嵌入 - 句子長度與掩碼,BERT有兩個制約因素
- 所有句子都必須填充或截斷為單個固定長度
- 句子的最大長度為512個令牌
- 使用特殊
[PAD]
令牌完成填充,該令牌在BERT詞匯表中的索引為0處
- 在每個句子的末尾展运,我們需要附加特殊
-
examples:
# Tokenize all of the sentences and map the tokens to thier word IDs. input_ids = [] attention_masks = [] # For every sentence... for sent in sentences: # `encode_plus` will: # (1) Tokenize the sentence. # (2) Prepend the `[CLS]` token to the start. # (3) Append the `[SEP]` token to the end. # (4) Map tokens to their IDs. # (5) Pad or truncate the sentence to `max_length` # (6) Create attention masks for [PAD] tokens. encoded_dict = tokenizer.encode_plus( sent, # Sentence to encode. add_special_tokens = True, # Add '[CLS]' and '[SEP]' max_length = 64, # Pad & truncate all sentences. pad_to_max_length = True, return_attention_mask = True, # Construct attn. masks. return_tensors = 'pt', # Return pytorch tensors. ) # Add the encoded sentence to the list. input_ids.append(encoded_dict['input_ids']) # And its attention mask (simply differentiates padding from non-padding). attention_masks.append(encoded_dict['attention_mask']) # Convert the lists into tensors. input_ids = torch.cat(input_ids, dim=0) attention_masks = torch.cat(attention_masks, dim=0) labels = torch.tensor(labels) # Print sentence 0, now as a list of IDs. print('Original: ', sentences[0]) print('Token IDs:', input_ids[0]) ''' Original : Our friends won't buy this analysis, let alone the next one we propose. Token IDs: tensor([ 101, 2256, 2814, 2180, 1005, 1056, 4965, 2023, 4106, 1010, 2292, 2894, 1996, 2279, 2028, 2057, 16599, 1012, 102, 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]) '''
后續(xù)直接將BERT進行模型訓(xùn)練被丧、模型存儲即可
-
4.1 模型應(yīng)用案例
BertForSequenceClassification(文本分類)
本文將在這一節(jié)重點介紹Bert的全系使用方法,包括工具引入咖杂、數(shù)據(jù)處理、計算資源選擇蚊夫、模型訓(xùn)練與測試使用诉字。由于Bert不同任務(wù)存在重疊性,后續(xù)講不再贅述重合部分,而是專注于網(wǎng)絡(luò)結(jié)構(gòu)壤圃。
-
所需引入工具包
import random import torch from torch.utils.data import TensorDataset, DataLoader, random_split from transformers import BertTokenizer from transformers import BertForSequenceClassification, AdamW from transformers import get_linear_schedule_with_warmup
-
使用計算資源為GPU還是CPU(Bert建議使用GPU啟動加載陵霉,否則訓(xùn)練過程過慢),以及所用seed確定
# device可選擇cuda | cpu device = torch.device('cuda') # 隨機種子數(shù)值確定 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True
-
加載數(shù)據(jù)
# pandas讀取數(shù)據(jù)[sentence, type] data = pd.read_pickle("XXX.csv") # sentences text_values = list(df(['sentence'])) # label label_sample = list(df['type'])
-
加載bert模型
# 加載預(yù)訓(xùn)練分詞模型 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese', do_lower_case=False) # 加載預(yù)訓(xùn)練Bert模型 model = BertForSequenceClassification.from_pretrained( 'bert-base-uncased', num_labels=num_labels, output_attentions=False, output_hidden_states=False ) # 決定是否將模型推送到GPU model.cuda()
-
訓(xùn)練數(shù)據(jù)準(zhǔn)備
# 函數(shù)獲取文本列表的令牌ID def encode_fn(text_list): all_input_ids = [] for text in text_list: input_ids = tokenizer.encode( text, add_special_tokens = True, # 添加special tokens伍绳, 也就是CLS和SEP max_length = 160, # 設(shè)定最大文本長度 pad_to_max_length = True, # pad到最大的長度 return_tensors = 'pt' # 返回的類型為pytorch tensor ) all_input_ids.append(input_ids) all_input_ids = torch.cat(all_input_ids, dim=0) return all_input_ids all_input_ids = encode_fn(text_values)
-
講數(shù)據(jù)分為訓(xùn)練集與驗證集踊挠,并構(gòu)建dataloader
epochs = 4 batch_size = 32 # 將數(shù)據(jù)拆分為訓(xùn)練集和驗證集 dataset = TensorDataset(all_input_ids, labels) # 此處采用9:1的數(shù)據(jù)集構(gòu)建 train_size = int(0.90 * len(dataset)) val_size = len(dataset) - train_size train_dataset, val_dataset = random_split(dataset, [train_size, val_size]) # 創(chuàng)建訓(xùn)練和驗證數(shù)據(jù)集的DataLoader train_dataloader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True) val_dataloader = DataLoader(val_dataset, batch_size = batch_size, shuffle = False)
-
定義Bert訓(xùn)練所需optimizer與learning rate scheduler
# 創(chuàng)建優(yōu)化器和學(xué)習(xí)率計劃 optimizer = AdamW(model.parameters(), lr=2e-5) total_steps = len(train_dataloader) * epochs scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps)
-
定義一個可視化指標(biāo)計算的方法,此處以sklearn的accuracy為例
from sklearn.metrics import f1_score, accuracy_score def flat_accuracy(preds, labels): pred_flat = np.argmax(preds, axis=1).flatten() labels_flat = labels.flatten() return accuracy_score(labels_flat, pred_flat)
-
Bert的訓(xùn)練與驗證
for epoch in range(epochs): # 訓(xùn)練集過程 model.train() total_loss, total_val_loss = 0, 0 total_eval_accuracy = 0 for step, batch in enumerate(train_dataloader): model.zero_grad() loss, logits = model(batch[0].to(device), token_type_ids=None, attention_mask=(batch[0]>0).to(device), labels=batch[1].to(device)) total_loss += loss.item() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() # 測試集過程 model.eval() for i, batch in enumerate(val_dataloader): with torch.no_grad(): loss, logits = model(batch[0].to(device), token_type_ids=None, attention_mask=(batch[0]>0).to(device), labels=batch[1].to(device)) total_val_loss += loss.item() logits = logits.detach().cpu().numpy() label_ids = batch[1].to('cpu').numpy() total_eval_accuracy += flat_accuracy(logits, label_ids) avg_train_loss = total_loss / len(train_dataloader) avg_val_loss = total_val_loss / len(val_dataloader) avg_val_accuracy = total_eval_accuracy / len(val_dataloader) print(f'Train loss : {avg_train_loss}') print(f'Validation loss: {avg_val_loss}') print(f'Accuracy: {avg_val_accuracy:.2f}') print('\n') ''' 輸出格式 Train loss : 0.3275374324204257 Validation loss: 0.3286557973672946 Accuracy: 0.88 '''
-
模型預(yù)測過程冲杀,數(shù)據(jù)集構(gòu)建參考訓(xùn)練集dataloader
model.eval() preds = [] for i, (batch,) in enumerate(pred_dataloader): with torch.no_grad(): outputs = model(batch.to(device), token_type_ids=None, attention_mask=(batch>0).to(device)) logits = outputs[0] logits = logits.detach().cpu().numpy() preds.append(logits) final_preds = np.concatenate(preds, axis=0) final_preds = np.argmax(final_preds, axis=1) ''' 輸出格式如下效床,后續(xù)按照標(biāo)簽編號回推即可 1 0 0 1 0 '''
BertForSequenceClassification(文本分類 Tensorflow2)
Tensorflow2上應(yīng)用預(yù)訓(xùn)練模型進行文本分類的一個簡單例子。
-
準(zhǔn)備過程权谁,加載模型和數(shù)據(jù)
import tensorflow as tf import tensorflow_datasets from transformers import * tokenizer = BertTokenizer.from_pretrained('bert-base-cased') model = TFBertForSequenceClassification.from_pretrained('bert-base-cased') data = tensorflow_datasets.load('glue/mrpc')
-
訓(xùn)練數(shù)據(jù)準(zhǔn)備
train_dataset = glue_convert_examples_to_features(data['train'], tokenizer, 128, 'mrpc') valid_dataset = glue_convert_examples_to_features(data['validation'], tokenizer, 128, 'mrpc') train_dataset = train_dataset.shuffle(100).batch(32).repeat(2) valid_dataset = valid_dataset.batch(64)
-
優(yōu)化器設(shè)置以及訓(xùn)練
optimizer = tf.keras.optimizer.Adam(learning_rate=3e-5, epsilon=1e-8, clipnorm=1.0) metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy') loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) model.compile(optimizer=optimizer, loss=loss, metrics=[metric]) model.fit(train_dataset, epochs=2, steps_per_epoch=115, validation_data=valid_dataset, validation_step=7)
-
模型預(yù)測
sentence_0 = "This research was consistent with his findings" sentence_1 = "his findings were compatible with this research" sentence_2 = "his findings were not compatible with this research" inputs1 = tokenizer.encoder_plus(sentence_0, sentence_1, add_special_tokens=True, return_tensors='pt') inputs2 = tokenizer.encoder_plus(sentence_0, sentence_2, add_special_tokens=True, return_tensors='pt') pred1 = tf.argmax(model.predict(**inputs1)[0]) pred2 = tf.argmax(model.predict(**inputs2)[0])
BertForTokenClassification(token分類)
以NER任務(wù)為例剩檀,命名實體識別任務(wù)是NLP中的一個基礎(chǔ)任務(wù)。主要是從一句話中識別出命名實體旺芽,下為Bert+CRF的模型結(jié)構(gòu)沪猴,訓(xùn)練過程與數(shù)據(jù)預(yù)處理部分參考上章內(nèi)容,后續(xù)不再贅述甥绿,此處展示兩種BERT使用方法
-
常規(guī)方案:直接使用Bert作為底層embedding字币,后續(xù)拼接其他網(wǎng)絡(luò)则披,其網(wǎng)絡(luò)結(jié)構(gòu)如下:
import torch from transformers import BertModel, BertPreTrainedModel import torch.nn as nn from torchcrf import CRF class BertNER(BertPreTrainedModel): def __init__(self, config): super(BertNER, self).__init__(config) self.num_labels = config.num_labels self.bert = BertModel(config) self.dropout = nn.Dropout(config.hidden_dropout_prob) self.classifier = nn.Linear(config.hidden_size, config.num_labels) self.crf = CRF(config.num_labels, batch_first=True) self.init_weights() def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None, position_ids=None, inputs_embeds=None, head_mask=None): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds) sequence_output = outputs[0] sequence_output = self.dropout(sequence_output) # 得到判別值 logits = self.classifier(sequence_output) outputs = (logits,) if labels is not None: if attention_mask is not None: active_loss = attention_mask.view(-1) == 1 active_logits = logits.view(-1, self.num_labels) active_labels = torch.where( active_loss, labels.view(-1), torch.tensor(-100).type_as(labels) ) # [-100, -100, 1, 0...] else: active_logits = logits.view(-1, self.num_labels) active_labels = labels.view(-1) select_index = [] final_labels = [] for index, label in enumerate(active_labels): if label != -100: final_labels.append(label) select_index.append(index) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') active_logits.to(device) select_index = torch.tensor(select_index, device=active_logits.device) final_labels = torch.tensor(final_labels, device=active_logits.device).unsqueeze(0) final_logits = active_logits.index_select(0, select_index).unsqueeze(0) loss = self.crf(final_logits, final_labels) * (-1) outputs = (loss,) + outputs # contain: (loss), scores return outputs
-
直接使用BertForTokenClassification類進行序列標(biāo)注共缕,可對其模型進行修改拼接,其模型結(jié)構(gòu)如下
import torch from transformers import BertForTokenClassification class TokenClassifier(BertForTokenClassification): def forward(self, input_ids, attention_mask=None, token_type_ids=None, labels=None, valid_ids=None, attention_mask_label=None): sequence_output = self.bert(input_ids, attention_mask, token_type_ids, head_mask=None)[0] batch_size, max_len, feat_dim = sequence_output.shape valid_output = torch.zeros( batch_size, max_len, feat_dim, dtype=torch.float32, device=next(self.parameters()).device ) for i in range(batch_size): jj = -1 for j in range(max_len): if valid_ids[i][j].item() == 1: jj += 1 valid_output[i][jj] = sequence_output[i][j] sequence_output = self.dropout(valid_output) logits = self.classifier(sequence_output) if labels is not None: loss_fct = torch.nn.CrossEntropyLoss(ignore_index=0) attention_mask_label = None if attention_mask_label is not None: active_loss = attention_mask_label.view(-1) == 1 active_logits = logits.view(-1, self.num_labels)[active_loss] active_labels = labels.view(-1)[active_loss] loss = loss_fct(active_logits, active_labels) else: loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) return (loss, logits) else: return (logits,)
BertForTokenClassification(QA得分)
此類適用于問答系統(tǒng)士复,輸入中將上面幾個模型中的label改成了start_position和end_position图谷,即答案在原文中起始和結(jié)束位置,輸出是將預(yù)測分?jǐn)?shù)改成了對答案起始位置和結(jié)束位置的預(yù)測分?jǐn)?shù)阱洪。下面為直接使用預(yù)訓(xùn)練模型的代碼參考
-
準(zhǔn)備過程便贵,加載模型
from transformers import BertTokenizer, BertForQuestionAnswering model = BertForQuestionAnswering.from_pretrained(MODEL_PATH) tokenizer = BertTokenizer.from_pretrained(VOCAB_PATH)
-
保留概率最大的答案
with torch.no_grad(): model.eval() pred_results = {} for batch in tqdm(test_dataloader): q_ids, raw_sentence, input_ids, segment_ids = batch input_ids, segment_ids = \ input_ids.to(device), segment_ids.to(device) input_mask = (input_ids > 0).to(device) start_prob, end_prob = model(input_ids.to(device), token_type_ids=segment_ids.to(device), attention_mask=input_mask.to(device) ) # start_prob = start_prob.squeeze(0) # end_prob = end_prob.squeeze(0) for i in range(len(batch[0])): try: (best_start, best_end), max_prob = find_best_answer_for_passage(start_prob[i], end_prob[i]) if type(max_prob) == int: max_prob = 0 else: max_prob = max_prob.cpu().numpy()[0] except: pass if q_ids[i] in pred_results: pred_results[q_ids[i]].append( (raw_sentence[i][best_start.cpu().numpy()[0]:best_end.cpu().numpy()[0]], max_prob)) else: pred_results[q_ids[i]] = [(raw_sentence[i][best_start.cpu().numpy()[0]:best_end.cpu().numpy()[0]], max_prob)] # 保留最大概率的答案 for id in pred_results: pred_results[id] = sorted(pred_results[id], key=lambda x: x[1], reverse=True)[0] submit = {} for item in test_data: q_id = item[0] question = item[2] if q_id not in pred_results:continue submit[q_id] = pred_results[q_id][0].strip() print(question, pred_results[q_id][0].strip())
5.1 Bert原理簡介
BERT的全稱為Bidirectional Encoder Representation from Transformers,是一個預(yù)訓(xùn)練的語言表征模型冗荸。它強調(diào)了不再像以往一樣采用傳統(tǒng)的單向語言模型或者把兩個單向語言模型進行淺層拼接的方法進行預(yù)訓(xùn)練承璃,而是采用新的masked language model(MLM),以致能生成深度的雙向語言表征蚌本。BERT論文發(fā)表時提及在11個NLP(Natural Language Processing盔粹,自然語言處理)任務(wù)中獲得了新的state-of-the-art的結(jié)果,令人目瞪口呆程癌。
該模型有以下主要優(yōu)點:
- 采用MLM對雙向的Transformers進行預(yù)訓(xùn)練舷嗡,以生成深層的雙向語言表征
- 預(yù)訓(xùn)練后,只需要添加一個額外的輸出層進行fine-tune嵌莉,就可以在各種各樣的下游任務(wù)中取得state-of-the-art的表現(xiàn)进萄。在這過程中并不需要對BERT進行任務(wù)特定的結(jié)構(gòu)修改。
5.1.1 Bert結(jié)構(gòu)
以往的預(yù)訓(xùn)練模型的結(jié)構(gòu)會受到單向語言模型(從左到右或者從右到左)的限制,因而也限制了模型的表征能力中鼠,使其只能獲取單方向的上下文信息可婶。而BERT利用MLM進行預(yù)訓(xùn)練并且采用深層的雙向Transformer組件(單向的Transformer一般被稱為Transformer decoder,其每一個token(符號)只會attend到目前往左的token兜蠕。而雙向的Transformer則被稱為Transformer encoder扰肌,其每一個token會attend到所有的token)來構(gòu)建整個模型,因此最終生成能融合左右上下文信息的深層雙向語言表征熊杨。關(guān)于Transformer的詳細解釋可以參見Attention Is All You Need 或者 The Illustrated Transformer曙旭,經(jīng)過多層Transformer結(jié)構(gòu)的堆疊后,形成BERT的主體結(jié)構(gòu)
5.1.2 模型輸入
BERT的輸入為每一個token對應(yīng)的表征(圖中的粉紅色塊就是token晶府,黃色塊就是token對應(yīng)的表征)桂躏,并且單詞字典是采用WordPiece算法來進行構(gòu)建的。為了完成具體的分類任務(wù)川陆,除了單詞的token之外剂习,作者還在輸入的每一個序列開頭都插入特定的分類token([CLS]),該分類token對應(yīng)的最后一個Transformer層輸出被用來起到聚集整個序列表征信息的作用较沪。
由于BERT是一個預(yù)訓(xùn)練模型鳞绕,其必須要適應(yīng)各種各樣的自然語言任務(wù),因此模型所輸入的序列必須有能力包含一句話(文本情感分類尸曼,序列標(biāo)注任務(wù))或者兩句話以上(文本摘要们何,自然語言推斷,問答任務(wù))控轿。那么如何令模型有能力去分辨哪個范圍是屬于句子A冤竹,哪個范圍是屬于句子B呢?BERT采用了兩種方法去解決:
- 在序列tokens中把分割token([SEP])插入到每個句子后茬射,以分開不同的句子tokens
- 為每一個token表征都添加一個可學(xué)習(xí)的分割embedding來指示其屬于句子A還是句子B
上面提到了BERT的輸入為每一個token對應(yīng)的表征鹦蠕,實際上該表征是由三部分組成的,分別是對應(yīng)的token在抛,分割和位置 embeddings(位置embeddings的詳細解釋可參見Attention Is All You Need 或 The Illustrated Transformer)钟病,如下圖:
5.1.3 模型輸出
C為分類token([CLS])對應(yīng)最后一個Transformer的輸出,則代表其他token對應(yīng)最后一個Transformer的輸出刚梭。對于一些token級別的任務(wù)(如肠阱,序列標(biāo)注和問答任務(wù)),就把輸入到額外的輸出層中進行預(yù)測望浩。對于一些句子級別的任務(wù)(如辖所,自然語言推斷和情感分類任務(wù)),就把C輸入到額外的輸出層中磨德,這里也就解釋了為什么要在每一個token序列前都要插入特定的分類token缘回。
5.1.4 Bert的預(yù)訓(xùn)練任務(wù)
-
Masked Language Model(MLM):是BERT能夠不受單向語言模型所限制的原因吆视。簡單來說就是以15%的概率用[MASK]隨機地對每一個訓(xùn)練序列中的token進行替換,然后預(yù)測出[MASK]位置原有的單詞酥宴。然而啦吧,由于[MASK]并不會出現(xiàn)在下游任務(wù)的微調(diào)(fine-tuning)階段,因此預(yù)訓(xùn)練階段和微調(diào)階段之間產(chǎn)生了不匹配拙寡。因此BERT采用了以下策略來解決這個問題:
首先在每一個訓(xùn)練序列中以15%的概率隨機地選中某個token位置用于預(yù)測授滓,假如是第i個token被選中,則會被替換成以下三個token之一
- 80%的時候是[MASK]肆糕,如my dog is hairy——>my dog is [MASK]
- 10%的時候是隨機的其他token般堆,如my dog is hairy——>my dog is apple
- 10%的時候是原來的token,如my dog is hairy——>my dog is hairy
Next Sentence Prediction(NSP):MLM任務(wù)傾向于抽取token層次的表征诚啃,因此不能直接獲取句子層次的表征淮摔。為了使模型能夠有能力理解句子間的關(guān)系,BERT使用了NSP任務(wù)來預(yù)訓(xùn)練始赎,簡單來說就是預(yù)測兩個句子是否連在一起和橙。具體的做法是:對于每一個訓(xùn)練樣例,我們在語料庫中挑選出句子A和句子B來組成造垛,50%的時候句子B就是句子A的下一句魔招,剩下50%的時候句子B是語料庫中的隨機句子。接下來把訓(xùn)練樣例輸入到BERT模型中五辽,用[CLS]對應(yīng)的C信息去進行二分類的預(yù)測办斑。
5.2 Bert的優(yōu)缺點
5.2.1 BERT存在哪些優(yōu)缺點?
-
優(yōu)點:
- 能夠獲取上下文相關(guān)的雙向特征表示
缺點:
- 生成任務(wù)表現(xiàn)不佳:預(yù)訓(xùn)練過程和生成過程的不一致奔脐,導(dǎo)致在生成任務(wù)上效果不佳
- 采取獨立性假設(shè):沒有考慮預(yù)測[MASK]之間的相關(guān)性俄周,是對語言模型聯(lián)合概率的有偏估計(不是密度估計)
- 輸入噪聲[MASK]吁讨,造成預(yù)訓(xùn)練-精調(diào)兩階段之間的差異
- 無法文檔級別的NLP任務(wù)髓迎,只適合于句子和段落級別的任務(wù)
ALBERT:提出了兩種參數(shù)縮減技術(shù),以降低內(nèi)存消耗并提高BERT的訓(xùn)練速度
XLNet:XLnet是Transformer XL模型的一個擴展建丧,該模型使用自回歸方法預(yù)先訓(xùn)練排龄,通過最大化輸入序列分解順序的所有排列的期望似然來學(xué)習(xí)雙向上下文
Roberta:建立在BERT的基礎(chǔ)上,修改關(guān)鍵的超參數(shù)翎朱,刪除next-sentence pretraining的預(yù)訓(xùn)練目標(biāo)橄维,并以更大的mini-batches和learning rates進行訓(xùn)練
GPT2:超大規(guī)模語料上通過transformer結(jié)構(gòu)進行的無監(jiān)督訓(xùn)練的語言模型,適用于各項生成任務(wù)