使用双向LSTM进行情感分析详解
一、原理簡介
RNN能夠記憶上下文信息,因此常常用來處理時間序列數據。雖然理論上RNN能夠記憶無限長度的歷史信息,但是由于梯度的累計導致計算量過大無法實際操作,因此在應用中實際上RNN只能記錄前面若干個單詞的信息。由此就延申出了GRU和LSTM的網絡結構,主要是增加門控機制來控制信息的流動,從而提高對歷史信息的記憶能力,擯棄冗余的無關信息。本文將使用較為簡單的雙向LSTM來完成情感分析。
二、數據處理
本文使用aclImdb數據集,包含了50000條評論數據及其標簽,標簽包含積極和消極兩個類別。按1:1的比例將其分為訓練集和測試集。下面先導入要用到的包:
import collections import os import random import tarfile import torch from torch import nn import torchtext.vocab as Vocab import torch.utils.data as Dataimport sys sys.path.append("..") os.environ["CUDA_VISIBLE_DEVICES"] = "0" device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') DATA_ROOT='C:/Users/wuyang/Datasets'下面是數據讀取,將標簽轉化為0,1標量。
fname = os.path.join(DATA_ROOT, "aclImdb_v1.tar.gz") if not os.path.exists(os.path.join(DATA_ROOT, "aclImdb")):print("從壓縮包解壓...")with tarfile.open(fname, 'r') as f:f.extractall(DATA_ROOT) from tqdm import tqdm def read_imdb(folder='train', data_root='C:/Users/wuyang/Datasets/aclImdb'): data = []for label in ['pos', 'neg']:folder_name = os.path.join(data_root, folder, label)for file in tqdm(os.listdir(folder_name)):with open(os.path.join(folder_name, file), 'rb') as f:review = f.read().decode('utf-8').replace('\n', '').lower()data.append([review, 1 if label == 'pos' else 0])random.shuffle(data)return datatrain_data, test_data = read_imdb('train'), read_imdb('test')此后,對評論進行分詞。英語評論的分詞有多種,我們直接對評論按空格分開即可取得較好的效果。
def get_tokenized_imdb(data):#分詞"""data: list of [string, label]"""def tokenizer(text):return [tok.lower() for tok in text.split(' ')]return [tokenizer(review) for review, _ in data]下面利用Counter函數統計訓練集內單詞的數量和種類,并過濾掉詞頻小于5的單詞。
def get_vocab_imdb(data):tokenized_data = get_tokenized_imdb(data)counter = collections.Counter([tk for st in tokenized_data for tk in st])return Vocab.Vocab(counter, min_freq=5) #統計詞頻并過濾掉詞頻小于5的詞 vocab = get_vocab_imdb(train_data)由于RNN的輸入是定長的序列,而每條評論的長度都是不盡相同的,因此,我們需要對序列進行定長化,方法是設置一個標準序列長度,然后將更長的評論裁剪為定長,將較短的評論補0至定長。此處我們取定長為500.
def preprocess_imdb(data,vocab):#較短文本補長,較長文本裁剪,得到統一長序列max_l=500def pad(x):return x[:max_l] if len(x)>max_l else x+[0]*(max_l-len(x))tokenized_data=get_tokenized_imdb(data)features=torch.tensor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])labels = torch.tensor([score for _,score in data])return features,labels下面我們建立訓練和測試迭代器,并設置批量大小為64.
batch_size = 64#批量大小 train_set = Data.TensorDataset(*preprocess_imdb(train_data, vocab)) test_set = Data.TensorDataset(*preprocess_imdb(test_data, vocab)) train_iter = Data.DataLoader(train_set, batch_size, shuffle=True) test_iter = Data.DataLoader(test_set, batch_size)三、模型構建
下面我們使用pytorch搭建一個BiRNN網絡。注意,網絡的輸入為(batch_size,seq_length),也就是批量大小和序列長度。輸入序列先進入Embedding層得到每個單詞對應的詞向量,也就是將每個單詞轉化為固定維度的向量。此處的embed_size設置為300。通過Embedding層后,得到形狀為(batch_size,seq_length,embed_size)的中間結果。
此后,我們將該中間結果轉秩一下,得到(seq_length,batch_size,embed_size)的tensor.然后將該序列輸入到BiLSTM模塊。BiLSTM將會輸出最后一層結構的每個時間步的輸出,形狀為(seq_length, batch_size, num_directions * hidden_size)。此處num_directions為2。此后,我們連接output的初始時間步和最終時間步的隱藏狀態, 得到(batch_size,num_directionshidden_size2)的tensor。最后,我們將這個tensor輸入全連接層,轉化成(batch_size,2)的向量,完成對序列的分類工作。
以上模型用到了Embedding層,需要對詞向量進行訓練。這一步或許會耗費較多的時間并且效果不盡人意。一個更好的選擇是加載已經訓練好的詞向量直接使用。下面是加載預訓練詞向量的方法。
glove_vocab=Vocab.GloVe(name='6B',dim=100,cache=os.path.join(DATA_ROOT, "glove"))#加載預訓練詞向量 def load_pretrained_embedding(words,pretrained_vocab):#加載預訓練詞向量embed=torch.zeros(len(words),pretrained_vocab.vectors[0].shape[0])oov_count=0for i,word in enumerate(words):try:idx=pretrained_vocab.stoi[word]embed[i,:]=pretrained_vocab.vectors[idx]except KeyError:oov_count+=1if oov_count>0:print('words number:',oov_count)return embed net.embedding.weight.data.copy_( load_pretrained_embedding(vocab.itos,glove_vocab) ) net.embedding.weight.requires_grad = False四、模型訓練
模型訓練就跟其他網絡沒有什么區別了,流程如下,不做過多贅述。
lr, num_epochs = 0.01, 5 # 要過濾掉不計算梯度的embedding參數,因為加載使用的是預訓練詞向量,所以embedding層不更新參數,設置梯度不存在 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr) loss = nn.CrossEntropyLoss() def train(train_iter, test_iter, net, loss, optimizer, device, num_epochs):net = net.to(device)print("training on ", device)batch_count = 0for epoch in range(num_epochs):train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()for X, y in train_iter:X = X.to(device)y = y.to(device)y_hat = net(X)l = loss(y_hat, y) optimizer.zero_grad()l.backward()optimizer.step()train_l_sum += l.cpu().item()train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()n += y.shape[0]batch_count += 1test_acc = evaluate_accuracy(test_iter, net)print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start)) train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)下面是訓練結果:
training on cpu epoch 1, loss 0.6216, train acc 0.629, test acc 0.808, time 2335.8 sec epoch 2, loss 0.2077, train acc 0.812, test acc 0.823, time 2106.4 sec epoch 3, loss 0.1243, train acc 0.836, test acc 0.836, time 2053.3 sec epoch 4, loss 0.0849, train acc 0.854, test acc 0.828, time 1915.6 sec總結
以上是生活随笔為你收集整理的使用双向LSTM进行情感分析详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用海洋光学光谱仪测日光灯的光谱
- 下一篇: STM32-Keil软件仿真和硬件仿真/