日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

pytorch BiLSTM+CRF模型实现NER任务

發(fā)布時(shí)間:2023/12/31 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pytorch BiLSTM+CRF模型实现NER任务 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本次實(shí)現(xiàn)BiLSTM+CRF模型的數(shù)據(jù)來源于DataFountain平臺(tái)上的“產(chǎn)品評(píng)論觀點(diǎn)提取”競(jìng)賽,數(shù)據(jù)僅用來做模型練習(xí)使用,并未參與實(shí)際競(jìng)賽評(píng)分。

?

競(jìng)賽地址:產(chǎn)品評(píng)論觀點(diǎn)提取

1. 數(shù)據(jù)分析

數(shù)據(jù)分為測(cè)試集數(shù)據(jù)7528條,測(cè)試集數(shù)據(jù)(未統(tǒng)計(jì))。

測(cè)試集數(shù)據(jù)共有四個(gè)屬性,分別是:ID號(hào),文本內(nèi)容,BIO實(shí)體標(biāo)簽,class分類

本次比賽的任務(wù)一共分為兩部分,第一部分是NER部分,采用BIO實(shí)體標(biāo)簽作為訓(xùn)練參考,另一部分為文本分類,目前只做了NER部分,因此暫時(shí)只針對(duì)NER部分講解。

測(cè)試集數(shù)據(jù)具體如下:

?首先分析一下我們的text長度,對(duì)訓(xùn)練集中7525個(gè)樣本的text長度進(jìn)行統(tǒng)計(jì),可以得到一下直方圖:

從圖中可以看出,文本長度為20左右的數(shù)據(jù)量最大,總體來說文本長度都在100個(gè)字符以內(nèi),所以我們可以把要放入模型訓(xùn)練的固定文本長度MAX_LEN設(shè)置為100。

要做NER任務(wù),就需要把每個(gè)字符預(yù)測(cè)為某一類標(biāo)簽,加上O標(biāo)簽,我們的標(biāo)簽一共有九類,可以查看一下訓(xùn)練集中除了O以外的八類標(biāo)簽的頻次情況,就可以知道是否有某一類標(biāo)簽的數(shù)據(jù)量過少,過少的話就有可能帶來此類標(biāo)簽預(yù)測(cè)效果不好的結(jié)果。

從上圖的頻次直方圖可以看到,出現(xiàn)頻次最高的是'I-PRODUCT',共有6000多次,頻次最低的是'B-BANK',共有不到2000次?

不過總的來說差距不算太懸殊,tag的分布可以說是較為均勻的,問題不大。

2. BiLSTM+CRF模型

?對(duì)于NER任務(wù),較為常用的模型有HMM、CRF等機(jī)器學(xué)習(xí)方法,(Bi)LSTM+CRF,CNN+CRF,BERT+CRF,后續(xù)我會(huì)記錄一下BERT+CRF等等模型的實(shí)現(xiàn),首先從BiLSTM+CRF開始。

?(1)BiLSTM+CRF模型示意圖:

?模型的輸入為固定長度的文本,文本中的每個(gè)詞向量為Wi。經(jīng)過BiLSTM層訓(xùn)練后進(jìn)入全連接層,就可以得到每個(gè)詞在每個(gè)tag位置的概率了,因?yàn)槲覀兛偣灿芯艂€(gè)tag,所以全連接層的輸出的最低維度就是長度為9的向量。最后經(jīng)過CRF層訓(xùn)練后,就可以輸出loss值;如果是預(yù)測(cè)的話,使用CRF層的decode方法,就可以得到每個(gè)詞具體預(yù)測(cè)的tag了。

(2)模型各層數(shù)據(jù)結(jié)構(gòu)的變化示意圖:

之所以畫有這個(gè)數(shù)據(jù)結(jié)構(gòu)變化流程,是因?yàn)槲覀€(gè)人非常糾結(jié)在模型變化中的數(shù)據(jù)結(jié)構(gòu)變化(可能是我菜..),但是在參考網(wǎng)上別的資料的時(shí)候,基本上沒有見過有提供這類總結(jié)的,所以我就畫個(gè)圖,萬一有人和我一樣需要呢[doge]

上圖中,輸入的結(jié)構(gòu)是[batch_size, seq_len],batch_size就是數(shù)據(jù)集每個(gè)batch的大小了這個(gè)很簡單,seq_len是文本長度,一般文本長度都是固定的(短于固定長度的話就需要padding)。輸入數(shù)據(jù)實(shí)際上就是經(jīng)過詞轉(zhuǎn)index,再經(jīng)過padding過后的訓(xùn)練集/驗(yàn)證集數(shù)據(jù)了。

這里有一點(diǎn)需要注意:輸入數(shù)據(jù)的結(jié)構(gòu)中,我們一般第一個(gè)維度都是batch_size,但是實(shí)際上pytorch的各層模型中,它們默認(rèn)的數(shù)據(jù)輸入?yún)?shù)都是batch_first=False,因此后面就需要將數(shù)據(jù)轉(zhuǎn)換成[seq_len, batch_size]的結(jié)構(gòu)。

另外非常重要的是,由[batch_size,seq_len]轉(zhuǎn)[seq_len, batch_size],不要用tensor類的view方法,也不要用numpy中的reshape方法,這樣轉(zhuǎn)換的維度是不正確的(你可以試試),應(yīng)該要用tensor類的permute方法來轉(zhuǎn)換維度。

圖中中間部分的轉(zhuǎn)換流程就不講了,沒什么太多問題,最后經(jīng)過CRF層的時(shí)候,如果是進(jìn)行訓(xùn)練,那么需要參數(shù)emissions和tags,輸出結(jié)果就是loss值,不太一樣的是這里的loss值應(yīng)該是進(jìn)行了一個(gè)-log的操作,因此直接輸出的loss值就會(huì)變成復(fù)數(shù),為了能夠用常用的優(yōu)化器進(jìn)行參數(shù)優(yōu)化,這里的loss值需要乘以一個(gè)-1;如果是進(jìn)行預(yù)測(cè),就需要調(diào)用decode方法。

3. 具體實(shí)現(xiàn)

模型使用pytorch實(shí)現(xiàn),jupyter notebook版本的完整代碼在github上:NLP-NER-models

整體實(shí)現(xiàn)和核心代碼如下:

(1)詞轉(zhuǎn)index&填充長度不足/截取過長的文本

MAX_LEN = 100 #句子的標(biāo)準(zhǔn)長度 BATCH_SIZE = 8 #minibatch的大小 EMBEDDING_DIM = 120 HIDDEN_DIM = 12# 獲取 tag to index 詞典 def get_tag2index():return {"O": 0,"B-BANK":1,"I-BANK":2, #銀行實(shí)體"B-PRODUCT":3,"I-PRODUCT":4, #產(chǎn)品實(shí)體"B-COMMENTS_N":5,"I-COMMENTS_N":6, #用戶評(píng)論,名詞"B-COMMENTS_ADJ":7,"I-COMMENTS_ADJ":8 #用戶評(píng)論,形容詞} # 獲取 word to index 詞典 def get_w2i(vocab_path = dicPath):w2i = {}with open(vocab_path, encoding = 'utf-8') as f:while True:text = f.readline()if not text:breaktext = text.strip()if text and len(text) > 0:w2i[text] = len(w2i) + 1return w2idef pad2mask(t):if t==pad_index: #轉(zhuǎn)換成mask所用的0return 0else:return 1def text_tag_to_index(dataset):texts = []labels = []masks = []for row in range(len(dataset)):text = dataset.iloc[row]['text']tag = dataset.iloc[row]['BIO_anno']#text#tagif len(text)!=len(tag): #如果從數(shù)據(jù)集獲得的text和label長度不一致next#1. word轉(zhuǎn)index#1.1 text詞匯text_index = []text_index.append(start_index) #先加入開頭indexfor word in text:text_index.append(w2i.get(word, unk_index)) #將當(dāng)前詞轉(zhuǎn)成詞典對(duì)應(yīng)index,或不認(rèn)識(shí)標(biāo)注UNK的indextext_index.append(end_index) #最后加個(gè)結(jié)尾index#index#1.2 tag標(biāo)簽tag = tag.split()tag_index = [tag2i.get(t,0) for t in tag]tag_index = [0] + tag_index + [0]#2. 填充或截至句子至標(biāo)準(zhǔn)長度#2.1 text詞匯&tag標(biāo)簽if len(text_index)<MAX_LEN: #句子短,補(bǔ)充pad_index到滿夠MAX_LENpad_len = MAX_LEN-len(text_index)text_index = text_index + [pad_index]*pad_lentag_index = tag_index + [0]*pad_lenelif len(text_index)>MAX_LEN: #句子過長,截?cái)鄑ext_index = text_index[:MAX_LEN-1]text_index.append(end_index)tag_index = tag_index[:MAX_LEN-1]tag_index.append(0)masks.append([pad2mask(t) for t in text_index])texts.append(text_index)labels.append(tag_index)#把list類型的轉(zhuǎn)成tensor類型,方便后期進(jìn)行訓(xùn)練texts = torch.LongTensor(texts)labels = torch.LongTensor(labels)masks = torch.tensor(masks, dtype=torch.uint8)return texts,labels,masks#unk:未知詞 pad:填充 start:文本開頭 end:文本結(jié)束 unk_flag = '[UNK]' pad_flag = '[PAD]' start_flag = '[STA]' end_flag = '[END]' w2i = get_w2i() #獲得word_to_index詞典 tag2i = get_tag2index() #獲得tag_to_index詞典#獲得各flag的index值 unk_index = w2i.get(unk_flag, 101) pad_index = w2i.get(pad_flag, 1) start_index = w2i.get(start_flag, 102) #開始 end_index = w2i.get(end_flag, 103) #中間截至(主要用在有上下句的情況下)#將訓(xùn)練集的字符全部轉(zhuǎn)成index,并改成MAX_LEN長度 texts,labels,masks = text_tag_to_index(train_dataset)

(2)pytorch BiLSTM+CRF模型設(shè)置

class BiLSTM_CRF(nn.Module):def __init__(self, vocab_size, tag_to_ix, embedding_dim, hidden_dim, pad_index,batch_size):super(BiLSTM_CRF, self).__init__()self.embedding_dim = embedding_dimself.hidden_dim = hidden_dimself.vocab_size = vocab_sizeself.tag_to_ix = tag_to_ixself.tagset_size = len(tag_to_ix)self.pad_idx = pad_indexself.batch_size = batch_size#####中間層設(shè)置#embedding層self.word_embeds = nn.Embedding(vocab_size,embedding_dim,padding_idx=self.pad_idx) #轉(zhuǎn)詞向量#lstm層self.lstm = nn.LSTM(embedding_dim, hidden_dim//2, num_layers = 1, bidirectional = True)#LSTM的輸出對(duì)應(yīng)tag空間(tag space)self.hidden2tag = nn.Linear(hidden_dim, self.tagset_size) #輸入是[batch_size, size]中的size,輸出是[batch_size,output_size]的output_size#CRF層self.crf = CRF(self.tagset_size) #默認(rèn)batch_first=Falsedef forward(self, sentence, tags=None, mask=None): #sentence=(batch,seq_len) tags=(batch,seq_len)self.batch_size = sentence.shape[0] #防止最后一batch中的數(shù)據(jù)量不夠原本BATCH_SIZE#1. 從sentence到Embedding層embeds = self.word_embeds(sentence).permute(1,0,2)#.view(MAX_LEN,len(sentence),-1) #output=[seq_len, batch_size, embedding_size]#2. 從Embedding層到BiLSTM層self.hidden = (torch.randn(2,self.batch_size,self.hidden_dim//2),torch.randn(2,self.batch_size,self.hidden_dim//2)) #修改進(jìn)來 shape=((2,1,2),(2,1,2)) lstm_out, self.hidden = self.lstm(embeds, self.hidden) #3. 從BiLSTM層到全連接層#從lstm的輸出轉(zhuǎn)為tagset_size長度的向量組(即輸出了每個(gè)tag的可能性)lstm_feats = self.hidden2tag(lstm_out) #4. 全連接層到CRF層if tags is not None: #訓(xùn)練用 #mask=attention_masks.byte()if mask is not None:loss = -1.*self.crf(emissions=lstm_feats,tags=tags.permute(1,0),mask=mask.permute(1,0),reduction='mean') #outputs=(batch_size,) 輸出log形式的likelihoodelse:loss = -1.*self.crf(emissions=lstm_feats,tags=tags.permute(1,0),reduction='mean')return losselse: #測(cè)試用if mask is not None:prediction = self.crf.decode(emissions=lstm_feats,mask=mask.permute(1,0)) #mask=attention_masks.byte()else:prediction = self.crf.decode(emissions=lstm_feats)return prediction#創(chuàng)建模型和優(yōu)化器 model = BiLSTM_CRF(len(w2i), tag2i, EMBEDDING_DIM, HIDDEN_DIM,pad_index,BATCH_SIZE) optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-4) #顯示模型基本參數(shù) model

Embedding層將詞indx轉(zhuǎn)詞向量,torch.nn.Embedding方法應(yīng)該是采用隨機(jī)變量確定embedding向量的;

LSTM層,隱藏層節(jié)點(diǎn)數(shù)6,因是BiLSTM所以需要乘以2,即共12個(gè)hidden units;

全連接層,由BiLSTM輸出的向量轉(zhuǎn)入全連接層,輸出維度為tag個(gè)數(shù);

CRF層。

采用SGD梯度優(yōu)化方法進(jìn)行參數(shù)優(yōu)化,learning rate設(shè)為0.01(沒經(jīng)過特別研究),weight_decay設(shè)為1e-4。

(3)訓(xùn)練

samples_cnt = texts.shape[0] batch_cnt = math.ceil(samples_cnt/BATCH_SIZE) #整除 向上取整 loss_list = [] for epoch in range(10):for step, batch_data in enumerate(train_loader):# 1. 清空梯度model.zero_grad()# 2. 運(yùn)行模型loss = model(batch_data['texts'], batch_data['labels'],batch_data['masks']) if step%100 ==0:logger.info('Epoch=%d step=%d/%d loss=%.5f' % (epoch,step,batch_cnt,loss))# 3. 計(jì)算loss值,梯度并更新權(quán)重參數(shù) loss.backward() #retain_graph=True) #反向傳播,計(jì)算當(dāng)前梯度optimizer.step() #根據(jù)梯度更新網(wǎng)絡(luò)參數(shù)loss_list.append(loss)

(4)驗(yàn)證集進(jìn)行驗(yàn)證

因?yàn)檫@次使用的數(shù)據(jù)集沒有驗(yàn)證集,所以在開始時(shí)把訓(xùn)練集按7:3分為訓(xùn)練集和驗(yàn)證集,把分離開的驗(yàn)證集進(jìn)行測(cè)試,看最后的F1-Score值評(píng)分情況。

#batch_masks:tensor數(shù)據(jù),結(jié)構(gòu)為(batch_size,MAX_LEN) #batch_labels: tensor數(shù)據(jù),結(jié)構(gòu)為(batch_size,MAX_LEN) #batch_prediction:list數(shù)據(jù),結(jié)構(gòu)為(batch_size,) #每個(gè)數(shù)據(jù)長度不一(在model參數(shù)mask存在的情況下) def f1_score_evaluation(batch_masks,batch_labels,batch_prediction):all_prediction = []all_labels = []batch_size = batch_masks.shape[0] #防止最后一batch的數(shù)據(jù)不夠batch_sizefor index in range(batch_size):#把沒有mask掉的原始tag都集合到一起length = sum(batch_masks[index].numpy()==1)_label = batch_labels[index].numpy().tolist()[:length]all_labels = all_labels+_label #把沒有mask掉的預(yù)測(cè)tag都集合到一起#_predict = y_pred[index][:length]all_prediction = all_prediction+y_pred[index]assert len(_label)==len(y_pred[index])assert len(all_prediction) == len(all_labels)score = f1_score(all_prediction,all_labels,average='weighted')return score#把每個(gè)batch的數(shù)據(jù)都驗(yàn)證一遍,取均值 model.eval() #不啟用 BatchNormalization 和 Dropout,保證BN和dropout不發(fā)生變化 score_list = [] for step, batch_data in enumerate(test_loader):with torch.no_grad(): #這部分的代碼不用跟蹤反向梯度更新y_pred = model(sentence=batch_data['texts'],mask=batch_data['masks'])score = f1_score_evaluation(batch_masks=batch_data['masks'],batch_labels=batch_data['labels'],batch_prediction=y_pred)score_list.append(score) #score_list logger.info("average-f1-score:"+str(np.mean(score_list)))

總結(jié)

以上是生活随笔為你收集整理的pytorch BiLSTM+CRF模型实现NER任务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。