本文主要是針對入門級別的Bert使用,先讓模型能夠實現文本分類,後續會講解huggingface的Bert流程化的使用,包括英文文本分類和中文文本分類。
英文部分使用
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding中的Cola數據集,任務如下圖
這個數據集包括四列:[‘sentence_source’, ‘label’, ‘label_notes’, ‘sentence’]
第一列是句子來源,第三列大部分缺失,我們的目的是做文本分類,這倆列可以不用。
第二列lable:[0,1]
第四列sentence:英文
下面就開始:
這裏我們使用自己的方法快速處理數據集進行模型訓練,後續文章將會進一步使用huggingface的方法來處理數據集
https://github.com/huggingface/transformers
首先定義路徑:
data_path='cola_public/raw/'#數據集路徑
bert_pre_model='bert-base-uncased/pytorch_model.bin'#預訓練模型文件
bert_config='bert-base-uncased/bert_config.json'#配置文件
bert_pre_tokenizer='bert-base-uncased/bert-base-uncased-vocab.txt'#詞表
首先將數據使用pandas讀取,並處理成bert的輸入格式
- 處理後的句子
- [0,0,0…,1,1,1…]編號如果是一句話,不輸入也可以,默認會自動生成[0,0,0…0]
- attietion mask 數據集中有效的長度,模型能attention到的所有位置
#讀取訓練數據 os.path.join(data_dir, "train.txt")
df = pd.read_csv(os.path.join(data_path,"in_domain_train.tsv"), delimiter='\t', header=None,\
names=['sentence_source', 'label', 'label_notes', 'sentence'])
#提取語句並處理
sentencses=['[CLS] ' + sent + ' [SEP]' for sent in df.sentence.values]
labels=df.label.values
print("第一句話:",sentencses[0])
tokenizer=BertTokenizer.from_pretrained(bert_pre_tokenizer,do_lower_case=True)
tokenized_sents=[tokenizer.tokenize(sent) for sent in sentencses]
print("tokenized的第一句話:",tokenized_sents[0])
#定義句子最大長度(512)
MAX_LEN=128
#將分割後的句子轉化成數字 word-->idx
input_ids=[tokenizer.convert_tokens_to_ids(sent) for sent in tokenized_sents]
print("轉化後的第一個句子:",input_ids[0])
#做PADDING,這裏使用keras的包做pad,也可以自己手動做pad,truncating表示大於最大長度截斷
#大於128做截斷,小於128做PADDING
input_ids=pad_sequences(input_ids, maxlen=MAX_LEN, dtype="long", truncating="post", padding="post")
print("Padding 第一個句子:",input_ids[0])
#建立mask
attention_masks = []
for seq in input_ids:
seq_mask = [float(i>0) for i in seq]
attention_masks.append(seq_mask)
print("第一個attention mask:",attention_masks[0])
劃分訓練集,這裏使用sklearn的方法,當然自己可以手動改切分。
#劃分訓練集、驗證集
train_inputs, validation_inputs, train_labels, validation_labels = train_test_split(input_ids, labels,
random_state=2018, test_size=0.1)
train_masks, validation_masks, _, _ = train_test_split(attention_masks, input_ids,
random_state=2018, test_size=0.1)
print("訓練集的一個inputs",train_inputs[0])
print("訓練集的一個mask",train_masks[0])
到這裏我們就可以生成dataloader放入到模型中進行訓練了
#將訓練集、驗證集轉化成tensor
train_inputs = torch.tensor(train_inputs)
validation_inputs = torch.tensor(validation_inputs)
train_labels = torch.tensor(train_labels)
validation_labels = torch.tensor(validation_labels)
train_masks = torch.tensor(train_masks)
validation_masks = torch.tensor(validation_masks)
#生成dataloader
batch_size = 32
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
validation_data = TensorDataset(validation_inputs, validation_masks, validation_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=batch_size)
加載預訓練模型,首先加載bert的配置文件,然後使用BertForSequenceClassification這個類來進行文本分類
modelConfig = BertConfig.from_pretrained('bert-base-uncased/bert_config.json')
model = BertForSequenceClassification.from_pretrained('bert-base-uncased/pytorch_model.bin', config=modelConfig)
print(model.cuda())
定義優化器,注意BertAdam、AdamW是不同版本的adam優化方法,版本更新太快,知道使用就行,定義需要weight decay的參數
‘gamma’, ‘beta’ 是值LayerNormal層的,不要decay,直接訓練即可。其他參數除去bias,均使用weight decay的方法進行訓練
weight decay可以簡單理解在Adam上的一個優化的基礎上成使用L2正則(AdamW)。
param_optimizer = list(model.named_parameters())
no_decay = ['bias', 'gamma', 'beta']
optimizer_grouped_parameters = [
{'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],
'weight_decay_rate': 0.01},
{'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)],
'weight_decay_rate': 0.0}
]
optimizer = BertAdam(optimizer_grouped_parameters,
lr=2e-5,
warmup=.1)
接下來就是訓練部分了,注意在訓練是傳入label,模型可以直接得到loss,如果不傳入label,便只有一個logits
- (loss,logits) train
- (logits) dev 或者 test
#定義一個計算準確率的函數
def flat_accuracy(preds, labels):
pred_flat = np.argmax(preds, axis=1).flatten()
labels_flat = labels.flatten()
return np.sum(pred_flat == labels_flat) / len(labels_flat)
#訓練開始
train_loss_set = []#可以將loss加入到列表中,後期畫圖使用
epochs = 10
for _ in trange(epochs, desc="Epoch"):
#訓練開始
model.train()
tr_loss = 0
nb_tr_examples, nb_tr_steps = 0, 0
for step, batch in enumerate(train_dataloader):
batch = tuple(t.to(device) for t in batch)
b_input_ids, b_input_mask, b_labels = batch
optimizer.zero_grad()
#取第一個位置,BertForSequenceClassification第一個位置是Loss,第二個位置是[CLS]的logits
loss = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)[0]
train_loss_set.append(loss.item())
loss.backward()
optimizer.step()
tr_loss += loss.item()
nb_tr_examples += b_input_ids.size(0)
nb_tr_steps += 1
print("Train loss: {}".format(tr_loss / nb_tr_steps))
#模型評估
model.eval()
eval_loss, eval_accuracy = 0, 0
nb_eval_steps, nb_eval_examples = 0, 0
for batch in validation_dataloader:
batch = tuple(t.to(device) for t in batch)
b_input_ids, b_input_mask, b_labels = batch
with torch.no_grad():
logits = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)[0]
logits = logits.detach().cpu().numpy()
label_ids = b_labels.to('cpu').numpy()
tmp_eval_accuracy = flat_accuracy(logits, label_ids)
eval_accuracy += tmp_eval_accuracy
nb_eval_steps += 1
print("Validation Accuracy: {}".format(eval_accuracy / nb_eval_steps))
結果如下:
[1] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
[2] https://github.com/huggingface/transformers