RNNLM
RNNLM
基于RNN的語言模型稱為RNNLM(Language Model)。
Embedding 層:將單詞ID轉化為單詞的分布式表示(單詞向量)。
RNN層:向下一層(上方)輸出隱藏狀態,同時也向下一時刻的RNN層(右邊)輸出隱藏狀態。
對于“you say goodbye and i say hello.”如果模型學習順利。
輸入的數據是單詞ID列表,輸入單詞ID為0的you,Softmax層輸出的概率分布P0中,say的概率最高。這說明預測出了you后面出現的單詞為say。
輸入單詞ID為1的say,Softmax層輸出的概率分布P1中,goodbye和hello的概率最高。RNN層記憶了you say這一上下文。RNN將you say這一過去的信息保存成隱藏狀態向量。
RNNLM可以記憶目前為止輸入的單詞,并以此為基礎預測接下來會出現的單詞。
如果整體處理時序數據,神經網絡就如下圖所示。整體處理含有T個時序數據的層稱為Time xx層。
Time Affine 層:使用T個Affine層分別處理各個時刻的數據。代碼使用矩陣運算實現整體處理。
class TimeAffine:def __init__(self, W, b):#接收權重參數和偏置參數self.params = [W, b]#參數設置為列表類型的成員變量paramsself.grads = [np.zeros_like(W), np.zeros_like(b)]self.x = Nonedef forward(self, x):#x包含T個時序數據N, T, D = x.shape#批大小是 N,輸入向量的維數是 D,x形狀為(N,T,D)W, b = self.paramsrx = x.reshape(N*T, -1)out = np.dot(rx, W) + b#使用矩陣運算實現整體處理self.x = xreturn out.reshape(N, T, -1)def backward(self, dout):x = self.xN, T, D = x.shapeW, b = self.paramsdout = dout.reshape(N*T, -1)rx = x.reshape(N*T, -1)db = np.sum(dout, axis=0)dW = np.dot(rx.T, dout)dx = np.dot(dout, W.T)dx = dx.reshape(*x.shape)self.grads[0][...] = dWself.grads[1][...] = dbreturn dxTime Softmax with Loss 層:
x表示從下方的層傳來的得分(正規化為概率之前的值);t表示正確解標簽;T個Softmax with Loss層各自算出損失,相加并求平均,得到的值作為最終的損失。
class TimeSoftmaxWithLoss:def __init__(self):self.params, self.grads = [], []self.cache = Noneself.ignore_label = -1def forward(self, xs, ts):N, T, V = xs.shapeif ts.ndim == 3: # 在監督標簽為one-hot向量的情況下ts = ts.argmax(axis=2)mask = (ts != self.ignore_label)# 按批次大小和時序大小進行整理(reshape)xs = xs.reshape(N * T, V)ts = ts.reshape(N * T)mask = mask.reshape(N * T)ys = softmax(xs)ls = np.log(ys[np.arange(N * T), ts])ls *= mask # 與ignore_label相應的數據將損失設為0loss = -np.sum(ls)loss /= mask.sum()self.cache = (ts, ys, mask, (N, T, V))return lossdef backward(self, dout=1):ts, ys, mask, (N, T, V) = self.cachedx = ysdx[np.arange(N * T), ts] -= 1dx *= doutdx /= mask.sum()dx *= mask[:, np.newaxis] # 與ignore_label相應的數據將梯度設為0dx = dx.reshape((N, T, V))return dxRNNLM學習與評價
將RNNLM使用的網絡實現為SimpleRnnlm類,結構如下。
class SimpleRnnlm:def __init__(self, vocab_size, wordvec_size, hidden_size):V, D, H = vocab_size, wordvec_size, hidden_sizern = np.random.randn# 初始化權重,對各個層使用的參數(權重和偏置)進行初始化embed_W = (rn(V, D) / 100).astype('f')rnn_Wx = (rn(D, H) / np.sqrt(D)).astype('f')rnn_Wh = (rn(H, H) / np.sqrt(H)).astype('f')rnn_b = np.zeros(H).astype('f')affine_W = (rn(H, V) / np.sqrt(H)).astype('f')affine_b = np.zeros(V).astype('f')'''使用 Truncated BPTT 進行學習,將 Time RNN 層的 stateful設置為 TrueTime RNN 層就可以繼承上一時刻的隱藏狀態RNN 層和 Affine 層使用了Xavier 初始值'''# 生成層Time RNN 層就可以繼承上一時刻的隱藏狀態self.layers = [TimeEmbedding(embed_W),TimeRNN(rnn_Wx, rnn_Wh, rnn_b, stateful=True),TimeAffine(affine_W, affine_b)]self.loss_layer = TimeSoftmaxWithLoss()self.rnn_layer = self.layers[1]# 將所有的權重和梯度整理到列表中self.params, self.grads = [], []for layer in self.layers:self.params += layer.paramsself.grads += layer.gradsdef forward(self, xs, ts):for layer in self.layers:xs = layer.forward(xs)loss = self.loss_layer.forward(xs, ts)return lossdef backward(self, dout=1):dout = self.loss_layer.backward(dout)for layer in reversed(self.layers):dout = layer.backward(dout)return doutdef reset_state(self):self.rnn_layer.reset_state()常使用困惑度(perplexity)評價語言模型。
困惑度是概率的倒數(數據量為1時),因為預測出的正確單詞的概率越大越好,所以困惑度越小越好。
困惑度可以解釋為分叉度,表示下一個可以選擇的選項的數量(下一個可能出現單詞的候選個數)。
輸入數據為多個的情況,困惑度計算:
L是神經網絡的損失,數據量為N個,tn是one-hot向量形式正確解標簽,tnk表示第n個數據的第k個值,ynk是概率分布(神經網絡Softmax的輸出)。
RNNLM的學習和評價的代碼如下。
# 設定超參數 batch_size = 10 wordvec_size = 100 hidden_size = 100 time_size = 5 # Truncated BPTT的時間跨度大小 lr = 0.1 max_epoch = 100# 讀入訓練數據(縮小了數據集) corpus, word_to_id, id_to_word = ptb.load_data('train') corpus_size = 1000 corpus = corpus[:corpus_size] vocab_size = int(max(corpus) + 1)xs = corpus[:-1] # 輸入 ts = corpus[1:] # 輸出(監督標簽) data_size = len(xs) print('corpus size: %d, vocabulary size: %d' % (corpus_size, vocab_size))# 學習用的參數 max_iters = data_size // (batch_size * time_size) time_idx = 0 total_loss = 0 loss_count = 0 ppl_list = []# 生成模型 model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size) optimizer = SGD(lr)''' 使用 Truncated BPTT 進行學習,因此數據需要按順序輸入. mini-batch 的各批次要平移讀入數據的開始位置。'''# 計算讀入mini-batch的各筆樣本數據的開始位置 jump = (corpus_size - 1) // batch_size offsets = [i * jump for i in range(batch_size)]#offsets 的各個元素中存放了讀入數據的開始位置for epoch in range(max_epoch):for iter in range(max_iters):# 獲取mini-batch,按順序讀入數據batch_x = np.empty((batch_size, time_size), dtype='i')batch_t = np.empty((batch_size, time_size), dtype='i')for t in range(time_size):#for i, offset in enumerate(offsets):#各批次增加偏移量batch_x[i, t] = xs[(offset + time_idx) % data_size]#將time_idx 處的數據從語料庫中取出,將當前位置除以語料庫大小后的余數作為索引使用batch_t[i, t] = ts[(offset + time_idx) % data_size]#取余數為的是:讀入語料庫的位置超過語料庫大小時,回到語料庫的開頭time_idx += 1# 計算梯度,更新參數loss = model.forward(batch_x, batch_t)model.backward()optimizer.update(model.params, model.grads)total_loss += lossloss_count += 1# 各個epoch的困惑度評價ppl = np.exp(total_loss / loss_count)#計算每個 epoch 的平均損失,然后計算困惑度print('| epoch %d | perplexity %.2f'% (epoch+1, ppl))ppl_list.append(float(ppl))total_loss, loss_count = 0, 0# 繪制圖形 x = np.arange(len(ppl_list)) plt.plot(x, ppl_list, label='train') plt.xlabel('epochs') plt.ylabel('perplexity') plt.show()結果如下。困惑度逐漸減小。
| epoch 78 | perplexity 16.30 | epoch 79 | perplexity 15.07 | epoch 80 | perplexity 14.23 | epoch 81 | perplexity 13.74 | epoch 82 | perplexity 13.12 | epoch 83 | perplexity 12.36 | epoch 84 | perplexity 11.58 | epoch 85 | perplexity 11.16 | epoch 86 | perplexity 10.23 | epoch 87 | perplexity 10.12 | epoch 88 | perplexity 9.08 | epoch 89 | perplexity 8.71 | epoch 90 | perplexity 8.29 | epoch 91 | perplexity 8.24 | epoch 92 | perplexity 7.79 | epoch 93 | perplexity 7.41 | epoch 94 | perplexity 6.99 | epoch 95 | perplexity 7.17 | epoch 96 | perplexity 6.36 | epoch 97 | perplexity 5.98 | epoch 98 | perplexity 5.78 | epoch 99 | perplexity 5.55 | epoch 100 | perplexity 5.48Process finished with exit code 0總結
- 上一篇: 放大电路频率响应基础概念
- 下一篇: java封装概念_Java面向对象---