循環神經網絡---GRU模型


一、GRU介紹

  GRU是LSTM網絡的一種效果很好的變體,它較LSTM網絡的結構更加簡單,而且效果也很好,因此也是當前非常流形的一種網絡。GRU既然是LSTM的變體,因此也是可以解決RNN網絡中的長依賴問題。

  GRU的參數較少,因此訓練速度更快,GRU能夠降低過擬合的風險。

  在LSTM中引入了三個門函數:輸入門、遺忘門和輸出門來控制輸入值、記憶值和輸出值。而在GRU模型中只有兩個門:分別是更新門和重置門。具體結構如下圖所示:

 圖中的zt和rt分別表示更新門和重置門。更新門用於控制前一時刻的狀態信息被帶入到當前狀態中的程度,更新門的值越大說明前一時刻的狀態信息帶入越多。重置門控制前一狀態有多少信息被寫入到當前的候選集 h~th~t 上,重置門越小,前一狀態的信息被寫入的越少。

 

 

 

 

 二、GRU與LSTM的比較

(1). GRU相比於LSTM少了輸出門,其參數比LSTM少。
(2). GRU在復調音樂建模和語音信號建模等特定任務上的性能和LSTM差不多,在某些較小的數據集上,GRU相比於LSTM表現出更好的性能。
(3). LSTM比GRU嚴格來說更強,因為它可以很容易地進行無限計數,而GRU卻不能。這就是GRU不能學習簡單語言的原因,而這些語言是LSTM可以學習的。
(4). GRU網絡在首次大規模的神經網絡機器翻譯的結構變化分析中,性能始終不如LSTM。

三、GRU的API

rnn = nn.GRU(input_size, hidden_size, num_layers, bias, batch_first, dropout, bidirectional)

初始化:

input_size: input的特征維度
hidden_size: 隱藏層的寬度
num_layers: 單元的數量(層數),默認為1,如果為2以為着將兩個GRU堆疊在一起,當成一個GRU單元使用。
bias: True or False,是否使用bias項,默認使用
batch_first: Ture or False, 默認的輸入是三個維度的,即:(seq, batch, feature),第一個維度是時間序列,第二個維度是batch,第三個維度是特征。如果設置為True,則(batch, seq, feature)。即batch,時間序列,每個時間點特征。
dropout:設置隱藏層是否啟用dropout,默認為0
bidirectional:True or False, 默認為False,是否使用雙向的GRU,如果使用雙向的GRU,則自動將序列正序和反序各輸入一次。

調用輸入:

rnn(input, h_0)

輸出:

output, hn = rnn(input, h0)

形狀啥的和LSTM差不多,也有雙向

四、情感分類demo修改成GRU

完整代碼:

  1 import torch
  2 import torch.nn as nn
  3 import torch.nn.functional as F
  4 from torch import optim
  5 import os
  6 import re
  7 import pickle
  8 import numpy as np
  9 from torch.utils.data import Dataset, DataLoader
 10 from tqdm import tqdm
 11 
 12 
 13 dataset_path = r'C:\Users\ci21615\Downloads\aclImdb_v1\aclImdb'
 14 MAX_LEN = 500
 15 
 16 def tokenize(text):
 17     """
 18     分詞,處理原始文本
 19     :param text:
 20     :return:
 21     """
 22     fileters = ['!', '"', '#', '$', '%', '&', '\(', '\)', '\*', '\+', ',', '-', '\.', '/', ':', ';', '<', '=', '>', '\?', '@'
 23         , '\[', '\\', '\]', '^', '_', '`', '\{', '\|', '\}', '~', '\t', '\n', '\x97', '\x96', '', '', ]
 24     text = re.sub("<.*?>", " ", text, flags=re.S)
 25     text = re.sub("|".join(fileters), " ", text, flags=re.S)
 26     return [i.strip() for i in text.split()]
 27 
 28 
 29 class ImdbDataset(Dataset):
 30     """
 31     准備數據集
 32     """
 33     def __init__(self, mode):
 34         super(ImdbDataset, self).__init__()
 35         if mode == 'train':
 36             text_path = [os.path.join(dataset_path, i) for i in ['train/neg', 'train/pos']]
 37         else:
 38             text_path = [os.path.join(dataset_path, i) for i in ['test/neg', 'test/pos']]
 39         self.total_file_path_list = []
 40         for i in text_path:
 41             self.total_file_path_list.extend([os.path.join(i, j) for j in os.listdir(i)])
 42 
 43     def __getitem__(self, item):
 44         cur_path = self.total_file_path_list[item]
 45         cur_filename = os.path.basename(cur_path)
 46         # 獲取標簽
 47         label_temp = int(cur_filename.split('_')[-1].split('.')[0]) - 1
 48         label = 0 if label_temp < 4 else 1
 49         text = tokenize(open(cur_path, encoding='utf-8').read().strip())
 50         return label, text
 51 
 52     def __len__(self):
 53         return len(self.total_file_path_list)
 54 
 55 
 56 class Word2Sequence():
 57     UNK_TAG = 'UNK'
 58     PAD_TAG = 'PAD'
 59     UNK = 0
 60     PAD = 1
 61 
 62     def __init__(self):
 63         self.dict = {
 64             self.UNK_TAG: self.UNK,
 65             self.PAD_TAG: self.PAD
 66         }
 67         self.count = {} # 統計詞頻
 68 
 69     def fit(self, sentence):
 70         """
 71         把單個句子保存到dict中
 72         :return:
 73         """
 74         for word in sentence:
 75             self.count[word] = self.count.get(word, 0) + 1
 76 
 77     def build_vocab(self, min=5, max=None, max_feature=None):
 78         """
 79         生成詞典
 80         :param min: 最小出現的次數
 81         :param max: 最大次數
 82         :param max_feature: 一共保留多少個詞語
 83         :return:
 84         """
 85         # 刪除詞頻小於min的word
 86         if min is not None:
 87             self.count = {word:value for word,value in self.count.items() if value > min}
 88         # 刪除詞頻大於max的word
 89         if max is not None:
 90             self.count = {word:value for word,value in self.count.items() if value < max}
 91         # 限制保留的詞語數
 92         if max_feature is not None:
 93             temp = sorted(self.count.items(), key=lambda x:x[-1],reverse=True)[:max_feature]
 94             self.count = dict(temp)
 95         for word in self.count:
 96             self.dict[word] = len(self.dict)
 97         # 得到一個反轉的字典
 98         self.inverse_dict = dict(zip(self.dict.values(), self.dict.keys()))
 99 
100     def transform(self, sentence, max_len=None):
101         """
102         把句子轉化為序列
103         :param sentence: [word1, word2...]
104         :param max_len: 對句子進行填充或裁剪
105         :return:
106         """
107         if max_len is not None:
108             if max_len > len(sentence):
109                 sentence = sentence + [self.PAD_TAG] * (max_len - len(sentence)) # 填充
110             if max_len < len(sentence):
111                 sentence = sentence[:max_len] # 裁剪
112         return [self.dict.get(word, self.UNK) for word in sentence]
113 
114     def inverse_transform(self, indices):
115         """
116         把序列轉化為句子
117         :param indices: [1,2,3,4...]
118         :return:
119         """
120         return [self.inverse_dict.get(idx) for idx in indices]
121 
122     def __len__(self):
123         return len(self.dict)
124 
125 
126 def fit_save_word_sequence():
127     """
128     從數據集構建字典
129     :return:
130     """
131     ws = Word2Sequence()
132     train_path = [os.path.join(dataset_path, i) for i in ['train/neg', 'train/pos']]
133     total_file_path_list = []
134     for i in train_path:
135         total_file_path_list.extend([os.path.join(i, j) for j in os.listdir(i)])
136     for cur_path in tqdm(total_file_path_list, desc='fitting'):
137         sentence = open(cur_path, encoding='utf-8').read().strip()
138         res = tokenize(sentence)
139         ws.fit(res)
140     # 對wordSequesnce進行保存
141     ws.build_vocab(min=10)
142     # pickle.dump(ws, open('./lstm_model/ws.pkl', 'wb'))
143     return ws
144 
145 
146 def get_dataloader(mode='train', batch_size=20, ws=None):
147     """
148     獲取數據集,轉換成詞向量后的數據集
149     :param mode:
150     :return:
151     """
152     # 導入詞典
153     # ws = pickle.load(open('./model/ws.pkl', 'rb'))
154     # 自定義collate_fn函數
155     def collate_fn(batch):
156         """
157         batch是list,其中是一個一個元組,每個元組是dataset中__getitem__的結果
158         :param batch:
159         :return:
160         """
161         batch = list(zip(*batch))
162         labels = torch.LongTensor(batch[0])
163         texts = batch[1]
164         # 獲取每個文本的長度
165         lengths = [len(i) if len(i) < MAX_LEN else MAX_LEN for i in texts]
166         # 每一段文本句子都轉換成了n個單詞對應的數字組成的向量,即500個單詞數字組成的向量
167         temp = [ws.transform(i, MAX_LEN) for i in texts]
168         texts = torch.LongTensor(temp)
169         del batch
170         return labels, texts, lengths
171     dataset = ImdbDataset(mode)
172     dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
173     return dataloader
174 
175 
176 class ImdbLstmModel(nn.Module):
177 
178     def __init__(self, ws):
179         super(ImdbLstmModel, self).__init__()
180         self.hidden_size = 64   # 隱藏層神經元的數量,即每一層有多少個LSTM單元
181         self.embedding_dim = 200    # 每個詞語使用多長的向量表示
182         self.num_layer = 1  # 即RNN的中LSTM單元的層數
183         self.bidriectional = True  # 是否使用雙向LSTM,默認是False,表示雙向LSTM,也就是序列從左往右算一次,從右往左又算一次,這樣就可以兩倍的輸出
184         self.num_directions = 2 if self.bidriectional else 1 # 是否雙向取值,雙向取值為2,單向取值為1
185         self.dropout = 0.5  # dropout的比例,默認值為0。dropout是一種訓練過程中讓部分參數隨機失活的一種方式,能夠提高訓練速度,同時能夠解決過擬合的問題。這里是在LSTM的最后一層,對每個輸出進行dropout
186         # 每個句子長度為500
187         # ws = pickle.load(open('./model/ws.pkl', 'rb'))
188         print(len(ws))
189         self.embedding = nn.Embedding(len(ws), self.embedding_dim)
190         # self.lstm = nn.LSTM(self.embedding_dim,self.hidden_size,self.num_layer,bidirectional=self.bidriectional,dropout=self.dropout)
191         self.gru = nn.GRU(input_size=self.embedding_dim, hidden_size=self.hidden_size, bidirectional=self.bidriectional)
192 
193         self.fc = nn.Linear(self.hidden_size * self.num_directions, 20)
194         self.fc2 = nn.Linear(20, 2)
195 
196     def init_hidden_state(self, batch_size):
197         """
198         初始化 前一次的h_0(前一次的隱藏狀態)和c_0(前一次memory)
199         :param batch_size:
200         :return:
201         """
202         h_0 = torch.rand(self.num_layer * self.num_directions, batch_size, self.hidden_size)
203         return h_0
204 
205     def forward(self, input):
206         # 句子轉換成詞向量
207         x = self.embedding(input)
208         # 如果batch_first為False的話轉換一下seq_len和batch_size的位置
209         x = x.permute(1,0,2)    # [seq_len, batch_size, embedding_num]
210         # 初始化前一次的h_0(前一次的隱藏狀態)和c_0(前一次memory)
211         h_0 = self.init_hidden_state(x.size(1))    # [num_layers * num_directions, batch, hidden_size]
212         output, h_n = self.gru(x, h_0)
213 
214         # 只要最后一個lstm單元處理的結果,這里多去的hidden state
215         out = torch.cat([h_n[-2, :, :], h_n[-1, :, :]], dim=-1)
216         out = self.fc(out)
217         out = F.relu(out)
218         out = self.fc2(out)
219         return F.log_softmax(out, dim=-1)
220 
221 
222 train_batch_size = 64
223 test_batch_size = 5000
224 
225 def train(epoch, ws):
226     """
227     訓練
228     :param epoch: 輪次
229     :param ws: 字典
230     :return:
231     """
232     mode = 'train'
233     imdb_lstm_model = ImdbLstmModel(ws)
234     optimizer = optim.Adam(imdb_lstm_model.parameters())
235     for i in range(epoch):
236         train_dataloader = get_dataloader(mode=mode, batch_size=train_batch_size, ws=ws)
237         for idx, (target, input, input_length) in enumerate(train_dataloader):
238             optimizer.zero_grad()
239             output = imdb_lstm_model(input)
240             loss = F.nll_loss(output, target)
241             loss.backward()
242             optimizer.step()
243 
244             pred = torch.max(output, dim=-1, keepdim=False)[-1]
245             acc = pred.eq(target.data).numpy().mean() * 100.
246             print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t ACC: {:.6f}'.format(i, idx * len(input), len(train_dataloader.dataset),
247                                                                                      100. * idx / len(train_dataloader), loss.item(), acc))
248     torch.save(imdb_lstm_model.state_dict(), 'model/gru_model.pkl')
249     torch.save(optimizer.state_dict(), 'model/gru_optimizer.pkl')
250 
251 
252 def test(ws):
253     mode = 'test'
254     # 載入模型
255     lstm_model = ImdbLstmModel(ws)
256     lstm_model.load_state_dict(torch.load('model/lstm_model.pkl'))
257     optimizer = optim.Adam(lstm_model.parameters())
258     optimizer.load_state_dict(torch.load('model/lstm_optimizer.pkl'))
259     lstm_model.eval()
260     test_dataloader = get_dataloader(mode=mode, batch_size=test_batch_size, ws=ws)
261     with torch.no_grad():
262         for idx, (target, input, input_length) in enumerate(test_dataloader):
263             output = lstm_model(input)
264             test_loss = F.nll_loss(output, target, reduction='mean')
265             pred = torch.max(output, dim=-1, keepdim=False)[-1]
266             correct = pred.eq(target.data).sum()
267             acc = 100. * pred.eq(target.data).cpu().numpy().mean()
268             print('idx: {} Test set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(idx, test_loss, correct, target.size(0), acc))
269 
270 
271 if __name__ == '__main__':
272     # 構建字典
273     ws = fit_save_word_sequence()
274     # 訓練
275     train(10, ws)
276     # 測試
277     # test(ws)
View Code

結果展示:

 

參考:

【重溫經典】GRU循環神經網絡 —— LSTM的輕量級版本,大白話講解

GRU模型

人人都能看懂的GRU

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM