首先思考一個問題,爲什麼需要RNN?
神經網路只能處理孤立的的輸入,每一個輸入之間是沒有關係的。但是,某些任務需要能夠更好的處理序列的資訊,即前面的輸入和後面的輸入是有關係的,例如音訊、視訊、句子等。
RNN較神經網路的不同是它將上一時刻的輸出作爲輸入一起傳入網路中,從而將輸入聯繫起來。
序列依次進入網路中,之前進入序列的數據會儲存資訊而對後面的數據產生影響。圖中X0X1…Xn不是同一時刻輸入網路的,圖中代表的是時間維度,實際上整個網路只有一個A單元,例如t=0時輸入X0,t=1時輸入X1。
但是這種簡易的RNN結構有兩個致命缺點。
LSTM(Long Short-Term Memory)長短期網路模型,它解決了RNN會遺忘序列前段資訊的問題。
網路外部結構與RNN相同,同樣是只有一個A單元,但A的內部有所不同。
CLASS torch.nn.LSTM(*args, **kwargs)
參數列表
input_size
– 輸入數據X的向量維度。hidden_size
– 隱含數據h的向量維度。num_layers
– LSTM 堆疊的層數,預設值是1層,每一層都只有一個A單元。如果設定爲2,第二個LSTM接收第一個LSTM的計算結果。也就是第一層輸入 [ X0 X1 X2 … Xt],計算出 [ h0 h1 h2 … ht ],第二層將 [ h0 h1 h2 … ht ] 作爲 [ X0 X1 X2 … Xt] 輸入再次計算,輸出最後的 [ h0 h1 h2 … ht ]。bias
– 若設定爲False,網路就不帶偏置,只有權重。預設爲True。batch_first
– 設爲True, 則輸入和輸出格式爲(batch, seq, feature),因爲輸入輸出格式預設爲(seq, batch, feature)。dropout
– 若不爲0,在除了最後一層外的單元新增dropout層,drop概率爲傳入的不爲0的數。bidirectional
– 如果爲真,則爲雙向LSTM。輸入格式
Inputs: input, (h_0, c_0)
input
輸入數據 形狀(序列長度,batchsize,輸入向量維度) (seq_len, batch, input_size)h_0
形狀 (num_layers * num_directions, batch, hidden_size)c_0
形狀(num_layers * num_directions, batch, hidden_size)輸出格式
output, (h_n, c_n)
output
of shape (seq_len, batch, num_directions * hidden_size) 最後一層的hn(多層情況,一層時即爲所有hn)h_n
of shape (num_layers * num_directions, batch, hidden_size) 包含所有層的hnc_n
of shape (num_layers * num_directions, batch, hidden_size) 包含所有層的cn參照庫檔案
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms as T
import matplotlib.pyplot as plt
超參設定
batch_size = 100 # 批次大小
lr = 1e-3 # 學習率
num_epoches = 30 # 訓練迭代週期
use_gpu = torch.cuda.is_available() # 是否使用cpu
MINST數據集匯入
train_dataset = datasets.MNIST(root='G:/dl_dataset/',train=True,transform=T.ToTensor())
test_dataset = datasets.MNIST(root='G:/dl_dataset/',train=False,transform=T.ToTensor())
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=batch_size,shuffle=False)
定義LSTM結構
要使用的LSTM參數分析。
將影象的每一行作爲一個X輸入LSTM,所以每張圖片都包含一個X0-X27,每個X是28維向量。
定義 input_size
=28,hidden_size
=128,num_layers
= 2,batch_first
=True。
class RNN(nn.Module):
def __init__(self, in_dim, hidden_dim, n_layer, n_class):
super(RNN, self).__init__()
self.lstm = nn.LSTM(in_dim, hidden_dim, n_layer, batch_first=True)
self.classifer = nn.Linear(hidden_dim, n_class)
def forward(self,x):
out ,_ = self.lstm(x) # 輸出爲output(batch,seq_len, hidden_size * num_directions)
out = out[:,-1,:] # seq=-1,即取每張圖片數據的最後一個h,即hn
out = self.classifer(out) # hn後接全連線層,使用softmax損失函數
return out # 返回最後的輸出
model = RNN(28,128,2,10)
# in_dim=28(每個X爲圖片的一行,維度爲28), hidden_dim=128(hn的維度), n_layer=2(LSTM層數爲兩層), n_class(最後的分類數)
if use_gpu:
model = model.cuda() # 把模型放到gpu上
定義損失函數和優化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(),lr=lr)
訓練和測試過程
for epoch in range(num_epoches):
print('*'*20)
print("epoch{}".format(epoch+1))
train_loss = 0.0 # 訓練損失函數計算
train_acc = 0.0 # 訓練準確度計算
model.train() # 置模型於訓練模式
for i, data in enumerate(train_loader): # 一次回圈讀入一個batch
img, label = data # 讀入影象張量和標籤張量
b, c, h, w = img.size() # MINST爲黑白單通道圖片
assert c == 1
img = img.squeeze(1) # size b h w 去除通道維度
if use_gpu:
img, label = img.cuda(), label.cuda() # 放入gpu
out = model(img) # input(batch,seq_len,in_dim) # 輸入影象進行前向傳播,輸出各分類的得分
loss = criterion(out,label) # 計算損失函數
train_loss += loss.item() # 記錄總的損失函數
_,pred = torch.max(out,1) # 返回得分最大值的索引,dim=0爲batch,dim=1爲每個圖片的分類得分
num_correct = (pred == label).sum() # 計算得分最大值索引與label相同的個數
train_acc += num_correct.item() # 計算正確總個數
optimizer.zero_grad() # 梯度清零
loss.backward() # 梯度反向傳播
optimizer.step() # 優化器前進step
# 完成一個epoch的訓練,列印訓練的loss和acc
print('Finish {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
epoch + 1, train_loss / (len(train_dataset)), train_acc / (len(
train_dataset))))
model.eval() # 置模型於測試模式,使dropout和bn失去作用
eval_loss = 0.0 # 測試模式損失記錄
eval_acc = 0.0 # 測試模式準確率記錄
for data in test_loader: 一次回圈讀入一個batch
img, label = data
b, c, h, w = img.size()
assert c == 1, 'channel must be 1'
img = img.squeeze(1)
if use_gpu:
img, label = img.cuda(), label.cuda()
with torch.no_grad(): # 測試模式無需梯度
out = model(img)
loss = criterion(out, label)
eval_loss += loss.item()
_, pred = torch.max(out, 1)
num_correct = (pred == label).sum()
eval_acc += num_correct.item()
print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
test_dataset)), eval_acc / (len(test_dataset))))
訓練30個週期在測試集達到99.08%準確率。
********************
epoch30
Finish 30 epoch, Loss: 0.000106, Acc: 0.996667
Test Loss: 0.000364, Acc: 0.990800
儲存模型
torch.save(model.state_dict(),'./RNN.pth')
最終效能測試
model.load_state_dict(torch.load('./RNN.pth'))
print('Final Test')
model.eval()
eval_loss = 0.0
eval_acc = 0.0
for data in test_loader:
img, label = data
b, c, h, w = img.size()
assert c == 1, 'channel must be 1'
img = img.squeeze(1)
if use_gpu:
img, label = img.cuda(), label.cuda()
with torch.no_grad():
out = model(img)
loss = criterion(out, label)
eval_loss += loss.item()
_,pred = torch.max(out,1)
eval_acc += ( pred==label ).float().mean()
print(f'Test Loss: {eval_loss/len(test_loader):.6f}, Acc: {eval_acc/len(test_loader):.6f}')
提取一個範例進行直觀認識
imgs,labels = next(iter(test_loader)) # 提取一個batch
img = imgs[0] # 取一張圖片
show = img
show = show.view(28,28)
# img = img.view(img.size(0),-1)
plt.imshow(show.numpy(), cmap='Greys_r') # 顯示該圖片
img = img.cuda()
label = labels[0].cuda() # 圖片和標籤放入gpu
output = model(img) # 前向傳播
print(label,output,sep='\n') # 列印各分類得分和標籤
pred = torch.squeeze(torch.max(output,dim=1)[1],dim=0) #squeeze降維,max找到得分最大的索引
print(label,pred,sep='\n') # 對比預測值和真實標籤
tensor(7, device='cuda:0') # label
tensor([[-2.8946, -2.8025, 0.2505, -0.9841, 1.3492, -3.3886, -6.0807, 13.3174,
-3.3386, 1.1477]], device='cuda:0', grad_fn=<AddmmBackward>) # output
tensor(7, device='cuda:0') # label
tensor(7, device='cuda:0') # 預測標籤