LSTM 与 Bilstm介绍(包含代码实现、Python)
1.1 文章組織
本文簡要介紹了BiLSTM的基本原理,并以句子級情感分類任務(wù)為例介紹為什么需要使用LSTM或BiLSTM進(jìn)行建模。在文章的最后,我們給出在PyTorch下BiLSTM的實(shí)現(xiàn)代碼,供讀者參考。
1.2 情感分類任務(wù)
自然語言處理中情感分類任務(wù)是對給定文本進(jìn)行情感傾向分類的任務(wù),粗略來看可以認(rèn)為其是分類任務(wù)中的一類。對于情感分類任務(wù),目前通常的做法是先對詞或者短語進(jìn)行表示,再通過某種組合方式把句子中詞的表示組合成句子的表示。最后,利用句子的表示對句子進(jìn)行情感分類。
舉一個對句子進(jìn)行褒貶二分類的例子。
句子:我愛賽爾情感標(biāo)簽:褒義
1.3 什么是LSTM和BiLSTM?
LSTM的全稱是Long Short-Term Memory,它是RNN(Recurrent Neural Network)的一種。LSTM由于其設(shè)計(jì)的特點(diǎn),非常適合用于對時序數(shù)據(jù)的建模,如文本數(shù)據(jù)。BiLSTM是Bi-directional Long Short-Term Memory的縮寫,是由前向LSTM與后向LSTM組合而成。兩者在自然語言處理任務(wù)中都常被用來建模上下文信息。
1.4 為什么使用LSTM與BiLSTM?
將詞的表示組合成句子的表示,可以采用相加的方法,即將所有詞的表示進(jìn)行加和,或者取平均等方法,但是這些方法沒有考慮到詞語在句子中前后順序。如句子“我不覺得他好”。“不”字是對后面“好”的否定,即該句子的情感極性是貶義。使用LSTM模型可以更好的捕捉到較長距離的依賴關(guān)系。因?yàn)長STM通過訓(xùn)練過程可以學(xué)到記憶哪些信息和遺忘哪些信息。
但是利用LSTM對句子進(jìn)行建模還存在一個問題:無法編碼從后到前的信息。在更細(xì)粒度的分類時,如對于強(qiáng)程度的褒義、弱程度的褒義、中性、弱程度的貶義、強(qiáng)程度的貶義的五分類任務(wù)需要注意情感詞、程度詞、否定詞之間的交互。舉一個例子,“這個餐廳臟得不行,沒有隔壁好”,這里的“不行”是對“臟”的程度的一種修飾,通過BiLSTM可以更好的捕捉雙向的語義依賴。
二、BiLSTM原理簡介
2.1 LSTM介紹
2.1.1 總體框架
總體框架如圖
2.1.2 詳細(xì)介紹計(jì)算過程
計(jì)算遺忘門,選擇要遺忘的信息。
計(jì)算記憶門,選擇要記憶的信息。
計(jì)算當(dāng)前時刻細(xì)胞狀態(tài)
計(jì)算輸出門和當(dāng)前時刻隱層狀態(tài)
最終,我們可以得到與句子長度相同的隱層狀態(tài)序列
2.2 BiLSTM介紹
前向的LSTM與后向的LSTM結(jié)合成BiLSTM。比如,我們對“我愛中國”這句話進(jìn)行編碼,模型如圖6所示。
對于情感分類任務(wù)來說,我們采用的句子的表示往往是?。因?yàn)槠浒饲跋蚺c后向的所有信息,如圖
三、BiLSTM代碼實(shí)現(xiàn)樣例
3.1 模型搭建
使用PyTorch搭建BiLSTM樣例代碼。代碼地址為
https://github.com/albertwy/BiLSTM/ #!/usr/bin/env python # coding:utf8import torch import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variabletorch.manual_seed(123456)class BLSTM(nn.Module):"""Implementation of BLSTM Concatenation for sentiment classification task"""def __init__(self, embeddings, input_dim, hidden_dim, num_layers, output_dim, max_len=40, dropout=0.5):super(BLSTM, self).__init__()self.emb = nn.Embedding(num_embeddings=embeddings.size(0),embedding_dim=embeddings.size(1),padding_idx=0)self.emb.weight = nn.Parameter(embeddings)self.input_dim = input_dimself.hidden_dim = hidden_dimself.output_dim = output_dim# sen encoderself.sen_len = max_lenself.sen_rnn = nn.LSTM(input_size=input_dim,hidden_size=hidden_dim,num_layers=num_layers,dropout=dropout,batch_first=True,bidirectional=True)self.output = nn.Linear(2 * self.hidden_dim, output_dim)def bi_fetch(self, rnn_outs, seq_lengths, batch_size, max_len):rnn_outs = rnn_outs.view(batch_size, max_len, 2, -1)# (batch_size, max_len, 1, -1)fw_out = torch.index_select(rnn_outs, 2, Variable(torch.LongTensor([0])).cuda())fw_out = fw_out.view(batch_size * max_len, -1)bw_out = torch.index_select(rnn_outs, 2, Variable(torch.LongTensor([1])).cuda())bw_out = bw_out.view(batch_size * max_len, -1)batch_range = Variable(torch.LongTensor(range(batch_size))).cuda() * max_lenbatch_zeros = Variable(torch.zeros(batch_size).long()).cuda()fw_index = batch_range + seq_lengths.view(batch_size) - 1fw_out = torch.index_select(fw_out, 0, fw_index) # (batch_size, hid)bw_index = batch_range + batch_zerosbw_out = torch.index_select(bw_out, 0, bw_index)outs = torch.cat([fw_out, bw_out], dim=1)return outsdef forward(self, sen_batch, sen_lengths, sen_mask_matrix):""":param sen_batch: (batch, sen_length), tensor for sentence sequence:param sen_lengths::param sen_mask_matrix::return:"""''' Embedding Layer | Padding | Sequence_length 40'''sen_batch = self.emb(sen_batch)batch_size = len(sen_batch)''' Bi-LSTM Computation '''sen_outs, _ = self.sen_rnn(sen_batch.view(batch_size, -1, self.input_dim))sen_rnn = sen_outs.contiguous().view(batch_size, -1, 2 * self.hidden_dim) # (batch, sen_len, 2*hid)''' Fetch the truly last hidden layer of both sides'''sentence_batch = self.bi_fetch(sen_rnn, sen_lengths, batch_size, self.sen_len) # (batch_size, 2*hid)representation = sentence_batchout = self.output(representation)out_prob = F.softmax(out.view(batch_size, -1))return out_prob__init__()函數(shù)中對網(wǎng)絡(luò)進(jìn)行初始化,設(shè)定詞向量維度,前向/后向LSTM中隱層向量的維度,還有要分類的類別數(shù)等。
bi_fetch()函數(shù)的作用是將??與??拼接起來并返回拼接后的向量。由于使用了batch,所以需要使用句子長度用來定位開始padding時前一個時刻的輸出的隱層向量。
forward()函數(shù)里進(jìn)行前向計(jì)算,得到各個類別的概率值。
3.2 模型訓(xùn)練
def train(model, training_data, args, optimizer, criterion):model.train()batch_size = args.batch_sizesentences, sentences_seqlen, sentences_mask, labels = training_data# print batch_size, len(sentences), len(labels)assert batch_size == len(sentences) == len(labels)''' Prepare data and prediction'''sentences_, sentences_seqlen_, sentences_mask_ = \var_batch(args, batch_size, sentences, sentences_seqlen, sentences_mask)labels_ = Variable(torch.LongTensor(labels))if args.cuda:labels_ = labels_.cuda()assert len(sentences) == len(labels)model.zero_grad()probs = model(sentences_, sentences_seqlen_, sentences_mask_)loss = criterion(probs.view(len(labels_), -1), labels_)loss.backward()optimizer.step()代碼中training_data是一個batch的數(shù)據(jù),其中包括輸入的句子sentences(句子中每個詞以詞下標(biāo)表示),輸入句子的長度sentences_seqlen,輸入的句子對應(yīng)的情感類別labels。 訓(xùn)練模型前,先清空遺留的梯度值,再根據(jù)該batch數(shù)據(jù)計(jì)算出來的梯度進(jìn)行更新模型。
model.zero_grad()probs = model(sentences_, sentences_seqlen_, sentences_mask_)loss = criterion(probs.view(len(labels_), -1), labels_)loss.backward()optimizer.step()3.3 模型測試
以下是進(jìn)行模型測試的代碼。
def test(model, dataset, args, data_part="test"):""":param model::param args::param dataset::param data_part::return:"""tvt_set = dataset[data_part]tvt_set = yutils.YDataset(tvt_set["xIndexes"],tvt_set["yLabels"],to_pad=True, max_len=args.sen_max_len)test_set = tvt_setsentences, sentences_seqlen, sentences_mask, labels = test_set.next_batch(len(test_set))assert len(test_set) == len(sentences) == len(labels)tic = time.time()model.eval()''' Prepare data and prediction'''batch_size = len(sentences)sentences_, sentences_seqlen_, sentences_mask_ = \var_batch(args, batch_size, sentences, sentences_seqlen, sentences_mask)probs = model(sentences_, sentences_seqlen_, sentences_mask_)_, pred = torch.max(probs, dim=1)if args.cuda:pred = pred.view(-1).cpu().data.numpy()else:pred = pred.view(-1).data.numpy()tit = time.time() - ticprint " Predicting {:d} examples using {:5.4f} seconds".format(len(test_set), tit)labels = numpy.asarray(labels)''' log and return prf scores '''accuracy = test_prf(pred, labels)return accuracydef cal_prf(pred, right, gold, formation=True, metric_type=""):""":param pred: predicted labels:param right: predicting right labels:param gold: gold labels:param formation: whether format the float to 6 digits:param metric_type::return: prf for each label"""''' Pred: [0, 2905, 0] Right: [0, 2083, 0] Gold: [370, 2083, 452] '''num_class = len(pred)precision = [0.0] * num_classrecall = [0.0] * num_classf1_score = [0.0] * num_classfor i in xrange(num_class):''' cal precision for each class: right / predict '''precision[i] = 0 if pred[i] == 0 else 1.0 * right[i] / pred[i]''' cal recall for each class: right / gold '''recall[i] = 0 if gold[i] == 0 else 1.0 * right[i] / gold[i]''' cal recall for each class: 2 pr / (p+r) '''f1_score[i] = 0 if precision[i] == 0 or recall[i] == 0 \else 2.0 * (precision[i] * recall[i]) / (precision[i] + recall[i])if formation:precision[i] = precision[i].__format__(".6f")recall[i] = recall[i].__format__(".6f")f1_score[i] = f1_score[i].__format__(".6f")''' PRF for each label or PRF for all labels '''if metric_type == "macro":precision = sum(precision) / len(precision)recall = sum(recall) / len(recall)f1_score = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0elif metric_type == "micro":precision = 1.0 * sum(right) / sum(pred) if sum(pred) > 0 else 0recall = 1.0 * sum(right) / sum(gold) if sum(recall) > 0 else 0f1_score = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0return precision, recall, f1_score四、總結(jié)
本文中,我們結(jié)合情感分類任務(wù)介紹了LSTM以及BiLSTM的基本原理,并給出一個BiLSTM樣例代碼。除了情感分類任務(wù),LSTM與BiLSTM在自然語言處理領(lǐng)域的其它任務(wù)上也得到了廣泛應(yīng)用,如機(jī)器翻譯任務(wù)中使用其進(jìn)行源語言的編碼和目標(biāo)語言的解碼,機(jī)器閱讀理解任務(wù)中使用其對文章和問題的編碼等。
?
五、參考資料
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
總結(jié)
以上是生活随笔為你收集整理的LSTM 与 Bilstm介绍(包含代码实现、Python)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剑指Offer--二进制中1的个数
- 下一篇: Python操作redis(普通操作,连