NLP文字分類學習筆記7.1:基於ERNIE的文字分類
阿新 • • 發佈:2022-04-08
ERNIE
相關連結:ERNIE官方使用介紹,ERNIE專案地址
基於transformer的encoder,主要思想是將文字中已有的知識融入到模型訓練中,因此採用實體mask的方式(實體指人名,地名等詞)
預訓練
模型結構圖如下所示
文字中已有的知識主要有人名,地名等實體,這些詞本來就蘊含一些資訊,而採用bert那種mask方式,如通過哈和濱預測中間的爾,顯然多此一舉,且沒有關注哈爾濱這個詞本來含有的資訊。
- ERNIE使用多個T-encoder,還是像bert一樣輸入token embedding,訓練得到文字序列中的資訊。其中T-encoder為transformer的encoder
- 再使用多個K-encoder,將文字中的實體embedding輸入與T-encoder輸入“拼接”在一起,最後輸出。
- 實體embedding採用知識嵌入模型TransE得到(TransE這組要思想是構造實體向量和關係向量,不斷使兩個實體向量相加接近關係向量),然後實體embedding通過多頭注意力機制提取資訊
- T-encoder的輸出w再經過多頭注意力機制後與實體提取的資訊e“拼接”,經過information fusion層,最後得到輸出
- “拼接”方式採用下圖中公式一,w經過全連線層,e經過全連線層,兩者相加(實體要拼接到最開始的那個token上,如實體哈爾濱要拼到哈上),通過GELU啟用函式,得到h
- 在information fusion層,再用h,分別通過全連線層得到新的w和e
- 如果這段文字沒有實體資訊,就採用下述方法
- 預訓練的任務是用5%時間,隨機替換實體,讓模型預測正確的實體,15%的時間,隨機mask實體知識與token拼接的資訊,用模型去預測這個資訊,剩下的時間不變
微調
以文字分類為例:與bert時相同,先對輸入的句子按字進行切分,最後將[cls]對應的輸出用作分類
pytorch實現基於ERNIE的文字分類
使用Hugging Face的預訓練模型nghuyong/ernie-1.0 ,在10分類任務上準確率為76.98%,更多程式碼詳情見NLP文字分類學習筆記0
結構程式碼 myERNIE.py
import torch import torch.nn as nn from transformers import AutoTokenizer, AutoModel class Config(object): def __init__(self): self.pre_bert_path="nghuyong/ernie-1.0" self.train_path = 'data/dataset_train.csv' # 訓練集 self.dev_path = 'data/dataset_valid.csv' # 驗證集 self.test_path = 'data/test.csv' # 測試集 self.class_path = 'data/class.json' # 類別名單 self.save_path ='mymodel/ernie.pth' # 模型訓練結果 self.num_classes=10 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 裝置 self.epochs = 10 # epoch數 self.batch_size = 128 # mini-batch大小 self.maxlen = 32 # 每句話處理成的長度(短填長切) self.learning_rate = 5e-4 # 學習率 self.hidden_size=768 self.tokenizer = AutoTokenizer.from_pretrained(self.pre_bert_path) class Model(nn.Module): def __init__(self, config): super(Model, self).__init__() self.ernie=AutoModel.from_pretrained(config.pre_bert_path) #設定不更新預訓練模型的引數 for param in self.ernie.parameters(): param.requires_grad = False self.fc = nn.Linear(config.hidden_size, config.num_classes) def forward(self, input): out=self.ernie(input_ids =input['input_ids'],attention_mask=input['attention_mask'],token_type_ids=input['token_type_ids']) #只取最後一層CLS對應的輸出 out = self.fc(out.pooler_output) return out
執行程式碼run.py
import json
from mymodel import myBert,myAlbertl,myERNIE
import mydataset
import torch
import pandas as pd
from torch import nn,optim
from torch.utils.data import DataLoader
config=myERNIE.Config()
label_dict=json.load(open(config.class_path,'r',encoding='utf-8'))
# 載入訓練,驗證,測試資料集
train_df = pd.read_csv(config.train_path)
#這裡將標籤轉化為數字
train_ds=mydataset.GetLoader(train_df['review'],[label_dict[i] for i in train_df['cat']])
train_dl=DataLoader(train_ds,batch_size=config.batch_size,shuffle=True)
valid_df = pd.read_csv(config.dev_path)
valid_ds=mydataset.GetLoader(valid_df['review'],[label_dict[i] for i in valid_df['cat']])
valid_dl=DataLoader(valid_ds,batch_size=config.batch_size,shuffle=True)
test_df = pd.read_csv(config.test_path)
test_ds=mydataset.GetLoader(test_df['review'],[label_dict[i] for i in test_df['cat']])
test_dl=DataLoader(test_ds,batch_size=config.batch_size,shuffle=True)
#計算準確率
def accuracys(pre,label):
pre=torch.max(pre.data,1)[1]
accuracy=pre.eq(label.data.view_as(pre)).sum()
return accuracy,len(label)
#匯入網路結構
model=myERNIE.Model(config).to(config.device)
#訓練
criterion=nn.CrossEntropyLoss()
optimizer=optim.Adam(model.parameters(),lr=config.learning_rate)
best_loss=float('inf')
for epoch in range(config.epochs):
train_acc = []
for batch_idx,(data,target)in enumerate(train_dl):
inputs = config.tokenizer(list(data),truncation=True, return_tensors="pt",padding=True,max_length=config.maxlen)
model.train()
out = model(inputs)
loss=criterion(out,target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_acc.append(accuracys(out,target))
train_r = (sum(tup[0] for tup in train_acc), sum(tup[1] for tup in train_acc))
print('當前epoch:{}\t[{}/{}]{:.0f}%\t損失:{:.6f}\t訓練集準確率:{:.2f}%\t'.format(
epoch, batch_idx, len(train_dl), 100. * batch_idx / len(train_dl), loss.data,
100. * train_r[0].numpy() / train_r[1]
))
#每100批次進行一次驗證
if batch_idx%100==0 and batch_idx!=0:
model.eval()
val_acc=[]
loss_total=0
with torch.no_grad():
for (data,target) in valid_dl:
inputs = config.tokenizer(list(data), truncation=True, return_tensors="pt", padding=True,
max_length=config.maxlen)
out = model(inputs)
loss_total = criterion(out, target).data+loss_total
val_acc.append(accuracys(out,target))
val_r = (sum(tup[0] for tup in val_acc), sum(tup[1] for tup in val_acc))
print('損失:{:.6f}\t驗證集準確率:{:.2f}%\t'.format(loss_total/len(valid_dl),100. * val_r[0].numpy() / val_r[1]))
#如果驗證損失低於最好損失,則儲存模型
if loss_total < best_loss:
best_loss = loss_total
torch.save(model.state_dict(), config.save_path)
#測試
model.load_state_dict(torch.load(config.save_path))
model.eval()
test_acc=[]
with torch.no_grad():
for (data, target) in test_dl:
inputs = config.tokenizer(list(data),truncation=True, return_tensors="pt",padding=True,max_length=config.maxlen)
out = model(inputs)
test_acc.append(accuracys(out, target))
test_r = (sum(tup[0] for tup in test_acc), sum(tup[1] for tup in test_acc))
print('測試集準確率:{:.2f}%\t'.format(100. * test_r[0].numpy() / test_r[1]))