1.RNN簡介
rnn,相比很多人都已經聽膩,但是真正用代碼操練起來,其中還是有很多細節值得琢磨。
雖然大家都在說,我還是要強調一次,rnn實際上是處理的是序列問題,與之形成對比的是cnn,cnn不能夠處理序列問題,因為它沒有記憶能力,那為什么rnn能夠處理序列問題以及有記憶能力呢?
首先簡單介紹一下rnn以及lstm的背景,這里給出兩個鏈接,鏈接1,鏈接2
以最簡單的rnn為例,如下圖
上面是rnn展開成3個單元的結構示意圖,h_t是上一個時刻的輸出,這個輸出送到下一個時刻的輸入;x_t是當前時刻的輸入,h_t是當前時刻的輸出,這個輸出有兩個用途,第一個當然還是作為當前時刻的輸出,另外一個作用是作為下一個時刻的輸出,所以你不難理解,為什么rnn有記憶能力,因為,下一個單元的輸入是綜合當前時刻的輸入x_t與上一個時刻的輸出h_t-1啊,所以rnn的記憶功能體現在這個地方
h_t-1與x_t經過concatenate之后經過一個權重相乘,然后加上一個偏執bias,經過一個tanh函數,就成了h_t,非常簡單
公式如下
非常簡單,那么問題來了
大家經常所說的是rnn展開之后像上面這一個圖一樣,那么不展開呢?
大概是像這樣
實際上這是非常合理的,為什么呢?因為其實對於一層的rnn而言,看上面的公式,確實是只有一個w_ih,b_ih,w_hh,b_hh.記住這個是非常重要的,因為一開始的時候大家都可能會有一個誤解說,為什么應該是同一個權重,為什么不是不同的權重?
我覺得,在nlp領域,假如你輸入的是一個序列,就是一個句子,每個句子中的每個單詞經過word2vec變換成一個向量,這個句子有長有短,而對不不同長度的句子,難不成rnn上面展開的單元的個數還是變換的?這顯然是不可能的!如果這還說服不了你,那稍后看pytorch代碼
剛剛還說到,關於rnn的層數的問題,這個層數很容易誤解為第一個圖那樣展開,實際上和展開沒有半毛錢關系,展開的長度隨着輸入的序列的長短決定,層數的定義實際上是如下這樣
即是往上延伸的,輸出的h_t還是會網上繼續輸入,t時刻的輸出是h_t^',而不是h_t。
2.pytorch實現rnn
考慮到rnn的記憶特性,即rnn能夠記住前面的東西,這樣是否可行:即我每一時刻輸入的是一個vector,這個vector對應的是圖像的某一列,有多少列就對應多少時刻,那最后一個時刻輸入的是最后一列,rnn最后輸出的h_t實際上就是對應的哪一個類別,實際上這樣是行得通的
說干就干

1 # -*-coding: utf-8 -*- 2 import torch 3 import torch.nn as nn 4 import torchvision.datasets as dsets 5 import torchvision.transforms as transforms 6 from torch.autograd import Variable 7 8 9 # Hyper Parameters 10 sequence_length = 28 # 序列長度,將圖像的每一列作為一個序列 11 input_size = 28 # 輸入數據的維度 12 hidden_size = 128 # 隱藏層的size 13 num_layers = 2 # 有多少層 14 15 num_classes = 10 16 batch_size = 100 17 num_epochs = 20 18 learning_rate = 0.01 19 20 # MNIST Dataset 21 train_dataset = dsets.MNIST(root='./data/', 22 train=True, 23 transform=transforms.ToTensor(), 24 download=True) 25 26 test_dataset = dsets.MNIST(root='./data/', 27 train=False, 28 transform=transforms.ToTensor()) 29 30 # Data Loader (Input Pipeline) 31 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, 32 batch_size=batch_size, 33 shuffle=True) 34 35 test_loader = torch.utils.data.DataLoader(dataset=test_dataset, 36 batch_size=batch_size, 37 shuffle=False) 38 39 # RNN Model (Many-to-One) 40 class RNN(nn.Module): 41 def __init__(self, input_size, hidden_size, num_layers, num_classes): 42 super(RNN, self).__init__() 43 self.hidden_size = hidden_size 44 self.num_layers = num_layers 45 self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) # batch_first=True僅僅針對輸入而言 46 self.fc = nn.Linear(hidden_size, num_classes) 47 48 def forward(self, x): 49 # 設置初始狀態h_0與c_0的狀態是初始的狀態,一般設置為0,尺寸是,x.size(0) 50 h0 = Variable(torch.zeros(self.num_layers, x.size(0), self.hidden_size).cuda()) 51 c0 = Variable(torch.zeros(self.num_layers, x.size(0), self.hidden_size).cuda()) 52 53 # Forward propagate RNN 54 out, (h_n, c_n) = self.lstm(x, (h0, c0)) # 送入一個初始的x值,作為輸入以及(h0, c0) 55 56 # Decode hidden state of last time step 57 out = self.fc(out[:, -1, :]) # output也是batch_first, 實際上h_n與c_n並不是batch_first 58 return out 59 60 rnn = RNN(input_size, hidden_size, num_layers, num_classes) 61 rnn.cuda() 62 63 # Loss and Optimizer 64 criterion = nn.CrossEntropyLoss() 65 optimizer = torch.optim.Adam(rnn.parameters(), lr=learning_rate) 66 67 # Train the Model 68 for epoch in range(num_epochs): 69 for i, (images, labels) in enumerate(train_loader): 70 # a = images.numpy() 71 images = Variable(images.view(-1, sequence_length, input_size)).cuda() # 100*1*28*28 -> 100*28*28 72 # b = images.data.cpu().numpy() 73 labels = Variable(labels).cuda() 74 75 # Forward + Backward + Optimize 76 optimizer.zero_grad() 77 outputs = rnn(images) 78 loss = criterion(outputs, labels) 79 loss.backward() 80 optimizer.step() 81 82 if (i+1) % 100 == 0: 83 print ('Epoch [%d/%d], Step [%d/%d], Loss: %.4f' 84 %(epoch+1, num_epochs, i+1, len(train_dataset)//batch_size, loss.data[0])) 85 86 # Test the Model 87 correct = 0 88 total = 0 89 for images, labels in test_loader: 90 images = Variable(images.view(-1, sequence_length, input_size)).cuda() 91 outputs = rnn(images) 92 _, predicted = torch.max(outputs.data, 1) 93 total += labels.size(0) 94 correct += (predicted.cpu() == labels).sum() 95 96 print('Test Accuracy of the model on the 10000 test images: %d %%' % (100 * correct / total)) 97 98 # Save the Model 99 torch.save(rnn.state_dict(), 'rnn.pkl')
在實際上用的時候,我並沒有調用rnn,而是用lstm類

# RNN Model (Many-to-One) class RNN(nn.Module): def __init__(self, input_size, hidden_size, num_layers, num_classes): super(RNN, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) # batch_first=True僅僅針對輸入而言 self.fc = nn.Linear(hidden_size, num_classes) def forward(self, x): # 設置初始狀態h_0與c_0的狀態是初始的狀態,一般設置為0,尺寸是,x.size(0) h0 = Variable(torch.zeros(self.num_layers, x.size(0), self.hidden_size).cuda()) c0 = Variable(torch.zeros(self.num_layers, x.size(0), self.hidden_size).cuda()) # Forward propagate RNN out, (h_n, c_n) = self.lstm(x, (h0, c0)) # 送入一個初始的x值,作為輸入以及(h0, c0) # Decode hidden state of last time step out = self.fc(out[:, -1, :]) # output也是batch_first, 實際上h_n與c_n並不是batch_first return out
實際上是定義了一個lstm,lstm輸出的out最后一個,實際上也就是h_n^',這個參數送到一個全連接層,這個全連接層最后輸出的是10類別
loss由softmax定義

mages = Variable(images.view(-1, sequence_length, input_size)).cuda() # 100*1*28*28 -> 100*28*28
作者經過這樣的操作能夠得到batchsize×sequence×length的向量,輸入到rnn中,由於作者設置了batch_first,所以第一個維度是batch,第二個維度是sequence,那么就可以把沒衣服圖像的每一列當做一個時刻輸入到網絡中的一個vector
然后反傳,優化
實際上我把訓練的loss畫出來,發現loss振盪非常劇烈
盡管最后的精度能夠達到93%左右,確實能夠發現網絡不穩定