<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 6.4 循環神經網絡的從零開始實現 在本節中,我們將從零開始實現一個基于字符級循環神經網絡的語言模型,并在周杰倫專輯歌詞數據集上訓練一個模型來進行歌詞創作。首先,我們讀取周杰倫專輯歌詞數據集: ``` python import time import math import numpy as np import torch from torch import nn, optim import torch.nn.functional as F import sys sys.path.append("..") import d2lzh_pytorch as d2l device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') (corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics() ``` ## 6.4.1 one-hot向量 為了將詞表示成向量輸入到神經網絡,一個簡單的辦法是使用one-hot向量。假設詞典中不同字符的數量為`$ N $`(即詞典大小`vocab_size`),每個字符已經同一個從0到`$ N-1 $`的連續整數值索引一一對應。如果一個字符的索引是整數`$ i $`, 那么我們創建一個全0的長為`$ N $`的向量,并將其位置為`$ i $`的元素設成1。該向量就是對原字符的one-hot向量。下面分別展示了索引為0和2的one-hot向量,向量長度等于詞典大小。 > pytorch沒有自帶one-hot函數(新版好像有了),下面自己實現一個 ``` python def one_hot(x, n_class, dtype=torch.float32): # X shape: (batch), output shape: (batch, n_class) x = x.long() res = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device) res.scatter_(1, x.view(-1, 1), 1) return res x = torch.tensor([0, 2]) one_hot(x, vocab_size) ``` 我們每次采樣的小批量的形狀是(批量大小, 時間步數)。下面的函數將這樣的小批量變換成數個可以輸入進網絡的形狀為(批量大小, 詞典大小)的矩陣,矩陣個數等于時間步數。也就是說,時間步`$ t $`的輸入為`$ \boldsymbol{X}_t \in \mathbb{R}^{n \times d} $`,其中`$ n $`為批量大小,`$ d $`為輸入個數,即one-hot向量長度(詞典大小)。 ``` python # 本函數已保存在d2lzh_pytorch包中方便以后使用 def to_onehot(X, n_class): # X shape: (batch, seq_len), output: seq_len elements of (batch, n_class) return [one_hot(X[:, i], n_class) for i in range(X.shape[1])] X = torch.arange(10).view(2, 5) inputs = to_onehot(X, vocab_size) print(len(inputs), inputs[0].shape) ``` 輸出: ``` 5 torch.Size([2, 1027]) ``` ## 6.4.2 初始化模型參數 接下來,我們初始化模型參數。隱藏單元個數 `num_hiddens`是一個超參數。 ``` python num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size print('will use', device) def get_params(): def _one(shape): ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32) return torch.nn.Parameter(ts, requires_grad=True) # 隱藏層參數 W_xh = _one((num_inputs, num_hiddens)) W_hh = _one((num_hiddens, num_hiddens)) b_h = torch.nn.Parameter(torch.zeros(num_hiddens, device=device, requires_grad=True)) # 輸出層參數 W_hq = _one((num_hiddens, num_outputs)) b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device, requires_grad=True)) return nn.ParameterList([W_xh, W_hh, b_h, W_hq, b_q]) ``` ## 6.4.3 定義模型 我們根據循環神經網絡的計算表達式實現該模型。首先定義`init_rnn_state`函數來返回初始化的隱藏狀態。它返回由一個形狀為(批量大小, 隱藏單元個數)的值為0的`NDArray`組成的元組。使用元組是為了更便于處理隱藏狀態含有多個`NDArray`的情況。 ``` python def init_rnn_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), ) ``` 下面的`rnn`函數定義了在一個時間步里如何計算隱藏狀態和輸出。這里的激活函數使用了tanh函數。3.8節(多層感知機)中介紹過,當元素在實數域上均勻分布時,tanh函數值的均值為0。 ``` python def rnn(inputs, state, params): # inputs和outputs皆為num_steps個形狀為(batch_size, vocab_size)的矩陣 W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] for X in inputs: H = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(H, W_hh) + b_h) Y = torch.matmul(H, W_hq) + b_q outputs.append(Y) return outputs, (H,) ``` 做個簡單的測試來觀察輸出結果的個數(時間步數),以及第一個時間步的輸出層輸出的形狀和隱藏狀態的形狀。 ```python state = init_rnn_state(X.shape[0], num_hiddens, device) inputs = to_onehot(X.to(device), vocab_size) params = get_params() outputs, state_new = rnn(inputs, state, params) print(len(outputs), outputs[0].shape, state_new[0].shape) ``` 輸出: ``` 5 torch.Size([2, 1027]) torch.Size([2, 256]) ``` ## 6.4.4 定義預測函數 以下函數基于前綴`prefix`(含有數個字符的字符串)來預測接下來的`num_chars`個字符。這個函數稍顯復雜,其中我們將循環神經單元`rnn`設置成了函數參數,這樣在后面小節介紹其他循環神經網絡時能重復使用這個函數。 ``` python # 本函數已保存在d2lzh_pytorch包中方便以后使用 def predict_rnn(prefix, num_chars, rnn, params, init_rnn_state, num_hiddens, vocab_size, device, idx_to_char, char_to_idx): state = init_rnn_state(1, num_hiddens, device) output = [char_to_idx[prefix[0]]] for t in range(num_chars + len(prefix) - 1): # 將上一時間步的輸出作為當前時間步的輸入 X = to_onehot(torch.tensor([[output[-1]]], device=device), vocab_size) # 計算輸出和更新隱藏狀態 (Y, state) = rnn(X, state, params) # 下一個時間步的輸入是prefix里的字符或者當前的最佳預測字符 if t < len(prefix) - 1: output.append(char_to_idx[prefix[t + 1]]) else: output.append(int(Y[0].argmax(dim=1).item())) return ''.join([idx_to_char[i] for i in output]) ``` 我們先測試一下`predict_rnn`函數。我們將根據前綴“分開”創作長度為10個字符(不考慮前綴長度)的一段歌詞。因為模型參數為隨機值,所以預測結果也是隨機的。 ``` python predict_rnn('分開', 10, rnn, params, init_rnn_state, num_hiddens, vocab_size, device, idx_to_char, char_to_idx) ``` 輸出: ``` '分開西圈緒升王凝瓜必客映' ``` ## 6.4.5 裁剪梯度 循環神經網絡中較容易出現梯度衰減或梯度爆炸。我們會在6.6節(通過時間反向傳播)中解釋原因。為了應對梯度爆炸,我們可以裁剪梯度(clip gradient)。假設我們把所有模型參數梯度的元素拼接成一個向量 `$ \boldsymbol{g} $`,并設裁剪的閾值是`$ \theta $`。裁剪后的梯度 ```[tex] \min\left(\frac{\theta}{\|\boldsymbol{g}\|}, 1\right)\boldsymbol{g} ``` 的`$ L_2 $`范數不超過`$ \theta $`。 ``` python # 本函數已保存在d2lzh_pytorch包中方便以后使用 def grad_clipping(params, theta, device): norm = torch.tensor([0.0], device=device) for param in params: norm += (param.grad.data ** 2).sum() norm = norm.sqrt().item() if norm > theta: for param in params: param.grad.data *= (theta / norm) ``` ## 6.4.6 困惑度 我們通常使用困惑度(perplexity)來評價語言模型的好壞。回憶一下3.4節(softmax回歸)中交叉熵損失函數的定義。困惑度是對交叉熵損失函數做指數運算后得到的值。特別地, * 最佳情況下,模型總是把標簽類別的概率預測為1,此時困惑度為1; * 最壞情況下,模型總是把標簽類別的概率預測為0,此時困惑度為正無窮; * 基線情況下,模型總是預測所有類別的概率都相同,此時困惑度為類別個數。 顯然,任何一個有效模型的困惑度必須小于類別個數。在本例中,困惑度必須小于詞典大小`vocab_size`。 ## 6.4.7 定義模型訓練函數 跟之前章節的模型訓練函數相比,這里的模型訓練函數有以下幾點不同: 1. 使用困惑度評價模型。 2. 在迭代模型參數前裁剪梯度。 3. 對時序數據采用不同采樣方法將導致隱藏狀態初始化的不同。相關討論可參考6.3節(語言模型數據集(周杰倫專輯歌詞))。 另外,考慮到后面將介紹的其他循環神經網絡,為了更通用,這里的函數實現更長一些。 ``` python # 本函數已保存在d2lzh_pytorch包中方便以后使用 def train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens, vocab_size, device, corpus_indices, idx_to_char, char_to_idx, is_random_iter, num_epochs, num_steps, lr, clipping_theta, batch_size, pred_period, pred_len, prefixes): if is_random_iter: data_iter_fn = d2l.data_iter_random else: data_iter_fn = d2l.data_iter_consecutive params = get_params() loss = nn.CrossEntropyLoss() for epoch in range(num_epochs): if not is_random_iter: # 如使用相鄰采樣,在epoch開始時初始化隱藏狀態 state = init_rnn_state(batch_size, num_hiddens, device) l_sum, n, start = 0.0, 0, time.time() data_iter = data_iter_fn(corpus_indices, batch_size, num_steps, device) for X, Y in data_iter: if is_random_iter: # 如使用隨機采樣,在每個小批量更新前初始化隱藏狀態 state = init_rnn_state(batch_size, num_hiddens, device) else: # 否則需要使用detach函數從計算圖分離隱藏狀態, 這是為了 # 使模型參數的梯度計算只依賴一次迭代讀取的小批量序列(防止梯度計算開銷太大) for s in state: s.detach_() inputs = to_onehot(X, vocab_size) # outputs有num_steps個形狀為(batch_size, vocab_size)的矩陣 (outputs, state) = rnn(inputs, state, params) # 拼接之后形狀為(num_steps * batch_size, vocab_size) outputs = torch.cat(outputs, dim=0) # Y的形狀是(batch_size, num_steps),轉置后再變成長度為 # batch * num_steps 的向量,這樣跟輸出的行一一對應 y = torch.transpose(Y, 0, 1).contiguous().view(-1) # 使用交叉熵損失計算平均分類誤差 l = loss(outputs, y.long()) # 梯度清0 if params[0].grad is not None: for param in params: param.grad.data.zero_() l.backward() grad_clipping(params, clipping_theta, device) # 裁剪梯度 d2l.sgd(params, lr, 1) # 因為誤差已經取過均值,梯度不用再做平均 l_sum += l.item() * y.shape[0] n += y.shape[0] if (epoch + 1) % pred_period == 0: print('epoch %d, perplexity %f, time %.2f sec' % ( epoch + 1, math.exp(l_sum / n), time.time() - start)) for prefix in prefixes: print(' -', predict_rnn(prefix, pred_len, rnn, params, init_rnn_state, num_hiddens, vocab_size, device, idx_to_char, char_to_idx)) ``` ## 6.4.8 訓練模型并創作歌詞 現在我們可以訓練模型了。首先,設置模型超參數。我們將根據前綴“分開”和“不分開”分別創作長度為50個字符(不考慮前綴長度)的一段歌詞。我們每過50個迭代周期便根據當前訓練的模型創作一段歌詞。 ``` python num_epochs, num_steps, batch_size, lr, clipping_theta = 250, 35, 32, 1e2, 1e-2 pred_period, pred_len, prefixes = 50, 50, ['分開', '不分開'] ``` 下面采用隨機采樣訓練模型并創作歌詞。 ``` python train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens, vocab_size, device, corpus_indices, idx_to_char, char_to_idx, True, num_epochs, num_steps, lr, clipping_theta, batch_size, pred_period, pred_len, prefixes) ``` 輸出: ``` epoch 50, perplexity 70.039647, time 0.11 sec - 分開 我不要再想 我不能 想你的讓我 我的可 你怎么 一顆四 一顆四 我不要 一顆兩 一顆四 一顆四 我 - 不分開 我不要再 你你的外 在人 別你的讓我 狂的可 語人兩 我不要 一顆兩 一顆四 一顆四 我不要 一 epoch 100, perplexity 9.726828, time 0.12 sec - 分開 一直的美棧人 一起看 我不要好生活 你知不覺 我已好好生活 我知道好生活 后知不覺 我跟了這生活 - 不分開堡 我不要再想 我不 我不 我不要再想你 不知不覺 你已經離開我 不知不覺 我跟了好生活 我知道好生 epoch 150, perplexity 2.864874, time 0.11 sec - 分開 一只會停留 有不它元羞 這蝪什么奇怪的事都有 包括像貓的狗 印地安老斑鳩 平常話不多 除非是烏鴉搶 - 不分開掃 我不你再想 我不能再想 我不 我不 我不要再想你 不知不覺 你已經離開我 不知不覺 我跟了這節奏 epoch 200, perplexity 1.597790, time 0.11 sec - 分開 有杰倫 干 載顆拳滿的讓空美空主 相愛還有個人 再狠狠忘記 你愛過我的證 有晶瑩的手滴 讓說些人 - 不分開掃 我叫你爸 你打我媽 這樣對嗎干嘛這樣 何必讓它牽鼻子走 瞎 說底牽打我媽要 難道球耳 快使用雙截 epoch 250, perplexity 1.303903, time 0.12 sec - 分開 有杰人開留 仙唱它怕羞 蜥蝪橫著走 這里什么奇怪的事都有 包括像貓的狗 印地安老斑鳩 平常話不多 - 不分開簡 我不能再想 我不 我不 我不能 愛情走的太快就像龍卷風 不能承受我已無處可躲 我不要再想 我不能 ``` 接下來采用相鄰采樣訓練模型并創作歌詞。 ``` python train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens, vocab_size, device, corpus_indices, idx_to_char, char_to_idx, False, num_epochs, num_steps, lr, clipping_theta, batch_size, pred_period, pred_len, prefixes) ``` 輸出: ``` epoch 50, perplexity 59.514416, time 0.11 sec - 分開 我想要這 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 - 不分開 我不要這 全使了雙 我想了這 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 我想了空 epoch 100, perplexity 6.801417, time 0.11 sec - 分開 我說的這樣笑 想你都 不著我 我想就這樣牽 你你的回不笑多難的 它在云實 有一條事 全你了空 - 不分開覺 你已經離開我 不知不覺 我跟好這節活 我該好好生活 不知不覺 你跟了離開我 不知不覺 我跟好這節 epoch 150, perplexity 2.063730, time 0.16 sec - 分開 我有到這樣牽著你的手不放開 愛可不可以簡簡單單沒有傷 古有你煩 我有多煩惱向 你知帶悄 回我的外 - 不分開覺 你已經很個我 不知不覺 我跟了這節奏 后知后覺 又過了一個秋 后哼哈兮 快使用雙截棍 哼哼哈兮 epoch 200, perplexity 1.300031, time 0.11 sec - 分開 我想要這樣牽著你的手不放開 愛能不能夠永遠單甜沒有傷害 你 靠著我的肩膀 你 在我胸口睡著 像這樣 - 不分開覺 你已經離開我 不知不覺 我跟了這節奏 后知后覺 又過了一個秋 后知后覺 我該好好生活 我該好好生 epoch 250, perplexity 1.164455, time 0.11 sec - 分開 我有一這樣布 對你依依不舍 連隔壁鄰居都猜到我現在的感受 河邊的風 在吹著頭發飄動 牽著你的手 一 - 不分開覺 你已經離開我 不知不覺 我跟了這節奏 后知后覺 又過了一個秋 后知后覺 我該好好生活 我該好好生 ``` ## 小結 * 可以用基于字符級循環神經網絡的語言模型來生成文本序列,例如創作歌詞。 * 當訓練循環神經網絡時,為了應對梯度爆炸,可以裁剪梯度。 * 困惑度是對交叉熵損失函數做指數運算后得到的值。 ----------- > 注:除代碼外本節與原書此節基本相同,[原書傳送門](https://zh.d2l.ai/chapter_recurrent-neural-networks/rnn-scratch.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看