以一個簡單的RNN爲例梳理神經網絡的訓練過程

本文是學習完集智學園《PyTorch入門課程:火炬上的深度學習——天然語言處理(NLP)》系列課以後的梳理。python

本次任務爲預測字符(數字),讓神經網絡找到下面數字的規律。bash

012
00112
0001112
000011112
00000111112
複製代碼

當咱們給定一組數據(如0000001)的時候,讓神經網絡去預測後面的數字應該是什麼網絡

1. 創建神經網絡架構

咱們構建一個RNN類架構

class simpleRNN(nn.Module):
    def __init():
        ...
    def forword():
        ...
    def initHidden():
        ...
複製代碼

其中函數initHidden的做用是初始化隱含層向量app

def initHidden(self):
    # 對隱含單元的初始化
    # 注意尺寸是: layer_size, batch_size, hidden_size
    return Variable(torch.zeros(self.num_layers, 1, self.hidden_size))
複製代碼

使用init函數

init用於搭建神經網絡的結構,網絡的輸入維度,輸出維度,隱含層維度和數量,過程當中須要用到的模型等等,都在init中定義。dom

其中nn是直接pytorch自帶的模塊,裏面包含了內置的Embedding ,RNN, Linear, logSoftmax等模型,能夠直接使用。函數

# 引入pytorch 中的 nn(模型模塊)
import torch.nn as nn
def __init__(self, input_size, hidden_size, output_size, num_layers = 1):
        # 定義
        super(SimpleRNN, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        # 一個embedding層
        self.embedding = nn.Embedding(input_size, hidden_size)
        # PyTorch的RNN模型,batch_first標誌可讓輸入的張量的第一個維度表示batch指標
        self.rnn = nn.RNN(hidden_size, hidden_size, num_layers, batch_first = True)
        # 輸出的全連接層
        self.linear = nn.Linear(hidden_size, output_size)
        # 最後的logsoftmax層
        self.softmax = nn.LogSoftmax()

複製代碼

使用forward函數做爲神經網絡的運算過程

運算過程也很好理解,就是將輸入一步一步地走過嵌入層,rnn層,linear層,和softmax層學習

  • embedding(嵌入層):用於輸入層到隱含層的嵌入。過程大體是把輸入向量先轉化爲one-hot編碼,再編碼爲一個hidden_size維的向量
  • RNN層:通過一層RNN模型
  • linear層(全連接層):將隱含層向量的全部維度一一映射到輸出上,能夠理解爲共享信息
  • softmax:將數據歸一化處理
# 運算過程
def forward(self, input, hidden):
        # size of input:[batch_size, num_step, data_dim]
        
        # embedding層:
        # 從輸入到隱含層的計算
        output = self.embedding(input, hidden)
        # size of output:[batch_size, num_step, hidden_size]
        
        output, hidden = self.rnn(output, hidden)
        # size of output:[batch_size, num_step, hidden_size]
      
        # 從輸出output中取出最後一個時間步的數值,注意output輸出包含了全部時間步的結果
        output = output[:,-1,:]
        # size of output:[batch_size, hidden_size]
        
        # 全連接層
        output = self.linear(output)
        # output尺寸爲:batch_size, output_size
        
        # softmax層,歸一化處理
        output = self.softmax(output)
         # size of output:batch_size, output_size
        return output, hidden
複製代碼

對RNN的訓練結果中間有一個特別的操做測試

output = output[:, -1 ,:]
複製代碼

output尺寸爲[batch_size, step, hidden_size], 這一步是把第二維時間步的數據只保留最後一個數。由於RNN的特徵就是記憶,最後一步數據包含了以前全部步數的信息。因此這裏只須要取最後一個數便可優化

使用這個init和forword

initforward都是python的class中內置的兩個函數。

  • 若是你定義了__init__,那麼在實例化類的時候就會自動運行init函數體,並且實例化的參數就是init函數的參數
  • 若是你定義了forward, 那麼你在執行這個類的時候,就自動執行 forward函數
# 實例化類simpleRNN,此時執行__init__函數
rnn = simpleRNN(input_size = 4, hidden_size = 1, output_size = 3, num_layers = 1)

# 使用類simpleRNN
output, hidden = rnn(input, hidden)
複製代碼

那麼執行一次forward就至關於一個訓練過程:輸入 -> 輸出

2. 能夠開始訓練了

首先是構造損失函數優化器

強大的pytorch自帶了通用的損失函數以及優化器模型。一句命令就搞定了一切。

criterion = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(rnn.parameters(), lr = 0.001)
複製代碼

損失函數criterion: 用於記錄訓練損失,全部權重都會根據每一步的損失值來調整。這裏使用的是NLLLoss損失函數,是一種比較簡單的損失計算,計算真實值和預測值的絕對差值

# output是預測值,y是真實值
loss = criterion(output, y)
複製代碼

優化器optimizer: 訓練過程的迭代操做。包括梯度反傳和梯度清空。傳入的參數爲神經網絡的參數rnn.parameters()以及學習率lr

# 梯度反傳,調整權重
optimizer.zero_grad()
# 梯度清空
optimizer.step()
複製代碼

訓練過程

訓練的思路是:

  1. 準備訓練數據,校驗數據和測試數據(每一個數據集的一組數據都是一個數字序列)
  2. 循環數數字序列,當前數字做爲輸入,下一個數字做爲標籤(即真實結果)
  3. 每次循環都通過一個rnn網絡
  4. 計算每一組的損失t_loss並記錄
  5. 優化器優化參數
  6. 重複1~5的訓練步驟n次,n自定義

訓練數據的準備不在本次的討論範圍內,因此這裏直接給出處理好的結果以下。

train_set = [[3, 0, 0, 1, 1, 2],
            [3, 0, 1, 2],
            [3, 0, 0, 0, 1, 1, 1, 2],
            [3, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]
            ...]
複製代碼

開始進行訓練

# 重複進行50次試驗
num_epoch = 50
loss_list = []
for epoch in range(num_epoch):
    train_loss = 0
    # 對train_set中的數據進行隨機洗牌,以保證每一個epoch獲得的訓練順序都不同。
    np.random.shuffle(train_set)
    # 對train_set中的數據進行循環
    for i, seq in enumerate(train_set):
        loss = 0
        # 對每個序列的全部字符進行循環
        for t in range(len(seq) - 1):
            #當前字符做爲輸入
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
            # 下一個字符做爲標籤
            y = Variable(torch.LongTensor([seq[t + 1]]))
            # y尺寸:batch_size = 1, data_dimension = 1
            output, hidden = rnn(x, hidden) #RNN輸出
            # output尺寸:batch_size, output_size = 3
            # hidden尺寸:layer_size =1, batch_size=1, hidden_size
            loss += criterion(output, y) #計算損失函數
        loss = 1.0 * loss / len(seq) #計算每字符的損失數值
        optimizer.zero_grad() # 梯度清空
        loss.backward() #反向傳播
        optimizer.step() #一步梯度降低
        train_loss += loss #累積損失函數值
        # 把結果打印出來
        if i > 0 and i % 500 == 0:
            print('第{}輪, 第{}個,訓練Loss:{:.2f}'.format(epoch, i, train_loss.data.numpy()[0] / i))
    loss_list.appand(train_loss)
            
複製代碼

這裏的loss是對每個訓練循環(epoch)的損失,事實上不管訓練的如何,這裏的loss都會降低,由於神經網絡就是會讓最後的結果儘量地靠近真實數據,因此訓練集的loss其實並不能用來評價一個模型的訓練好壞。

在實際的訓練過程當中,咱們會在每一輪訓練後,把獲得的模型放入校驗集去計算loss, 這樣的結果更爲客觀。

校驗集loss的計算和訓練集徹底一致,只不過把train_set替換成了valid_set,並且也不須要去根據結果優化參數,這在訓練步驟中已經作了,校驗集的做用就是看模型的訓練效果:

for epoch in range(num_epoch):
    # 訓練步驟
    ...
    valid_loss = 0
    for i, seq in enumerate(valid_set):
        # 對每個valid_set中的字符串作循環
        loss = 0
        outstring = ''
        targets = ''
        hidden = rnn.initHidden() #初始化隱含層神經元
        for t in range(len(seq) - 1):
            # 對每個字符作循環
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
            y = Variable(torch.LongTensor([seq[t + 1]]))
            # y尺寸:batch_size = 1, data_dimension = 1
            output, hidden = rnn(x, hidden)
            # output尺寸:batch_size, output_size = 3
            # hidden尺寸:layer_size =1, batch_size=1, hidden_size 
            loss += criterion(output, y) #計算損失函數
        loss = 1.0 * loss / len(seq)
        valid_loss += loss #累積損失函數值
# # 打印結果
    print('第%d輪, 訓練Loss:%f, 校驗Loss:%f, 錯誤率:%f'%(epoch, train_loss.data.numpy() / len(train_set),valid_loss.data.numpy() / len(valid_set),1.0 * errors / len(valid_set)))
複製代碼

根據校驗集的loss輸出,咱們能夠繪製出最終的loss變化。

3. 測試模型預測效果

構造數據,測試模型是否能猜出當前數字的下一個數。成功率有多高 首先是構造數據,構造長度分別爲0~20的數字序列

for n in range(20):
    inputs = [0] * n + [1] * n
複製代碼

而後對每個序列進行測試

for n in range(20):
    inputs = [0] * n + [1] * n
    
    outstring = ''
    targets = ''
    diff = 0
    hiddens = []
    hidden = rnn.initHidden()
    for t in range(len(inputs) - 1):
        x = Variable(torch.LongTensor([inputs[t]]).unsqueeze(0))
        # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
        y = Variable(torch.LongTensor([inputs[t + 1]]))
        # y尺寸:batch_size = 1, data_dimension = 1
        output, hidden = rnn(x, hidden)
        # output尺寸:batch_size, output_size = 3
        # hidden尺寸:layer_size =1, batch_size=1, hidden_size
        hiddens.append(hidden.data.numpy()[0][0])
        #mm = torch.multinomial(output.view(-1).exp())
        mm = torch.max(output, 1)[1][0]
        outstring += str(mm.data.numpy()[0])
        targets += str(y.data.numpy()[0])
         # 計算模型輸出字符串與目標字符串之間差別的字符數量
        diff += 1 - mm.eq(y)
    # 打印出每個生成的字符串和目標字符串
    print(outstring)
    print(targets)
    print('Diff:{}'.format(diff.data.numpy()[0]))
複製代碼

最終輸出的結果爲

[0, 1, 2]
[0, 1, 2]
Diff: 0
[0, 0, 1, 1, 2]
[0, 0, 1, 1, 2]
Diff: 0
[0, 0, 0, 1, 1, 1, 2]
[0, 0, 0, 1, 1, 1, 2]
Diff: 0
...
# 結果不一一列出,你們能夠自行嘗試
複製代碼

總結

神經網絡能夠理解爲讓計算機使用各類數學手段從一堆數據中找規律的過程。咱們能夠經過解剖一些簡單任務來理解神經網絡的內部機制。當面對複雜任務的時候,只須要把數據交給模型,它就能盡其所能地給你一個好的結果。

本文是學習完集智學園《PyTorch入門課程:火炬上的深度學習——天然語言處理(NLP)》系列課以後的梳理。課程中還有關於lstm, 翻譯任務實操等基礎並且豐富的知識點,我還會再回來的

相關文章
相關標籤/搜索