本文是學習完集智學園《PyTorch入門課程:火炬上的深度學習——自然語言處理(NLP)》系列課之后的梳理。
本次任務為預測字符(數字),讓神經網絡找到下面數字的規(guī)律。
012
00112
0001112
000011112
00000111112
當我們給定一組數據(如0000001)的時候,讓神經網絡去預測后面的數字應該是什么
1. 建立神經網絡架構
我們構建一個RNN類
class simpleRNN(nn.Module):
def __init():
...
def forword():
...
def initHidden():
...
其中函數initHidden的作用是初始化隱含層向量
def initHidden(self):
# 對隱含單元的初始化
# 注意尺寸是: layer_size, batch_size, hidden_size
return Variable(torch.zeros(self.num_layers, 1, self.hidden_size))
使用init函數
init用于搭建神經網絡的結構,網絡的輸入維度,輸出維度,隱含層維度和數量,過程中需要用到的模型等等,都在init中定義。
其中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
init和forward都是python的class中內置的兩個函數。
- 如果你定義了
__init__,那么在實例化類的時候就會自動運行init函數體,而且實例化的參數就是init函數的參數 - 如果你定義了
forward, 那么你在執(zhí)行這個類的時候,就自動執(zhí)行forward函數
# 實例化類simpleRNN,此時執(zhí)行__init__函數
rnn = simpleRNN(input_size = 4, hidden_size = 1, output_size = 3, num_layers = 1)
# 使用類simpleRNN
output, hidden = rnn(input, hidden)
那么執(zhí)行一次forward就相當于一個訓練過程:輸入 -> 輸出
2. 可以開始訓練了
首先是構造損失函數和優(yōu)化器
強大的pytorch自帶了通用的損失函數以及優(yōu)化器模型。一句命令就搞定了一切。
criterion = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(rnn.parameters(), lr = 0.001)
損失函數criterion: 用于記錄訓練損失,所有權重都會根據每一步的損失值來調整。這里使用的是NLLLoss損失函數,是一種比較簡單的損失計算,計算真實值和預測值的絕對差值
# output是預測值,y是真實值
loss = criterion(output, y)
優(yōu)化器optimizer: 訓練過程的迭代操作。包括梯度反傳和梯度清空。傳入的參數為神經網絡的參數rnn.parameters()以及學習率lr
# 梯度反傳,調整權重
optimizer.zero_grad()
# 梯度清空
optimizer.step()
訓練過程
訓練的思路是:
- 準備訓練數據,校驗數據和測試數據(每個數據集的一組數據都是一個數字序列)
- 循環(huán)數數字序列,當前數字作為輸入,下一個數字作為標簽(即真實結果)
- 每次循環(huán)都經過一個rnn網絡
- 計算每一組的損失t_loss并記錄
- 優(yōu)化器優(yōu)化參數
- 重復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中的數據進行循環(huán)
for i, seq in enumerate(train_set):
loss = 0
# 對每一個序列的所有字符進行循環(huán)
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是對每一個訓練循環(huán)(epoch)的損失,事實上無論訓練的如何,這里的loss都會下降,因為神經網絡就是會讓最后的結果盡可能地靠近真實數據,所以訓練集的loss其實并不能用來評價一個模型的訓練好壞。
在實際的訓練過程中,我們會在每一輪訓練后,把得到的模型放入校驗集去計算loss, 這樣的結果更為客觀。
校驗集loss的計算和訓練集完全一致,只不過把train_set替換成了valid_set,而且也不需要去根據結果優(yōu)化參數,這在訓練步驟中已經做了,校驗集的作用就是看模型的訓練效果:
for epoch in range(num_epoch):
# 訓練步驟
...
valid_loss = 0
for i, seq in enumerate(valid_set):
# 對每一個valid_set中的字符串做循環(huán)
loss = 0
outstring = ''
targets = ''
hidden = rnn.initHidden() #初始化隱含層神經元
for t in range(len(seq) - 1):
# 對每一個字符做循環(huán)
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
...
# 結果不一一列出,大家可以自行嘗試
總結
神經網絡可以理解為讓計算機使用各種數學手段從一堆數據中找規(guī)律的過程。我們可以通過解剖一些簡單任務來理解神經網絡的內部機制。當面對復雜任務的時候,只需要把數據交給模型,它就能盡其所能地給你一個好的結果。
本文是學習完集智學園《PyTorch入門課程:火炬上的深度學習——自然語言處理(NLP)》系列課之后的梳理。課程中還有關于
lstm,翻譯任務實操等基礎而且豐富的知識點,我還會再回來的