pytorch分別用MLP和RNN擬合sinx

0

理論上帶有一個非線性函數的網絡能夠擬合任意函數。那顯然MLP和RNN是科研擬合sinx的。
開頭先把結果給展示出來,然後是代碼,最後是我的過程。懶得看的直接看前半部分行了,過程給有興趣的人看看。

先上結果圖

注:每次訓練torch初始化有不同,所以結果有出入。
在這裏插入圖片描述

代碼

乍一看挺多的,實際上簡單得一批。只不過是定義了兩個網絡,訓練了兩次,展示圖片的重複代碼而已。具體代碼已經註釋。

import torch
import math
import matplotlib.pyplot as plt




class MLP(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1=torch.nn.Linear(1,16)
        self.layer2=torch.nn.Linear(16,16)
        self.layer3=torch.nn.Linear(16,1)

    def forward(self,x):
        x=self.layer1(x)
        x=torch.nn.functional.relu(x)

        x=self.layer2(x)
        x=torch.nn.functional.relu(x)

        x=self.layer3(x)

        return x

# rnn takes 3d input while mlp only takes 2d input
class RecNN(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.rnn=torch.nn.LSTM(input_size=1,hidden_size=2,num_layers=1,batch_first=True)
        #至於這個線性層爲什麼是2維度接收,要看最後網絡輸出的維度是否匹配label的維度
        self.linear=torch.nn.Linear(2,1)
        
    def forward(self,x):
        # print("x shape: {}".format(x.shape))
        # x [batch_size, seq_len, input_size]
        output,hn=self.rnn(x)
        # print("output shape: {}".format(output.shape))
        # out [seq_len, batch_size, hidden_size]
        x=output.reshape(-1,2)
	
        # print("after change shape: {}".format(x.shape))
        x=self.linear(x)

        # print("after linear shape: {}".format(x.shape))

        return x

def PlotCurve(mlp, rnn, input_x, x):
    # input_x 是輸入網絡的x。
    # sin_x 是列表,x的取值,一維數據、
    # 雖然他們的內容(不是維度)是一樣的。可以print shape看一下。
    mlp_eval = mlp.eval()
    rnn_eval = rnn.eval()
    mlp_y = mlp_eval(input_x)
    rnn_y = rnn_eval(input_x.unsqueeze(0))

    plt.figure(figsize=(6, 8))

    plt.subplot(211)
    plt.plot([i + 1 for i in range(EPOCH)], mlp_loss, label='MLP')
    plt.plot([i + 1 for i in range(EPOCH)], rnn_loss, label='RNN')
    plt.title('loss')
    plt.legend()

    plt.subplot(212)
    plt.plot(x, torch.sin(x), label="original", linewidth=3)
    plt.plot(x, [y[0] for y in mlp_y], label='MLP')
    plt.plot(x, [y[0] for y in rnn_y], label='RNN')
    plt.title('evaluation')
    plt.legend()

    plt.tight_layout()
    plt.show()

#常量都取出來,以便改動
EPOCH=1000
RNN_LR=0.01
MLP_LR=0.001
left,right=-10,10
PI=math.pi

if __name__ == '__main__':
    mlp=MLP()
    rnn=RecNN()

    # x,y 是普通sinx 的torch tensor
    x = torch.tensor([num * PI / 4 for num in range(left, right)])
    y = torch.sin(x)
    # input_x和labels是訓練網絡時候用的輸入和標籤。
    input_x=x.reshape(-1, 1)
    labels=y.reshape(-1,1)


    #訓練mlp
    mlp_optimizer=torch.optim.Adam(mlp.parameters(), lr=MLP_LR)
    mlp_loss=[]
    for epoch in range(EPOCH):
        preds=mlp(input_x)
        loss=torch.nn.functional.mse_loss(preds,labels)

        mlp_optimizer.zero_grad()
        loss.backward()
        mlp_optimizer.step()
        mlp_loss.append(loss.item())

    #訓練rnn
    rnn_optimizer=torch.optim.Adam(rnn.parameters(),lr=RNN_LR)
    rnn_loss=[]
    for epoch in range(EPOCH):
        preds=rnn(input_x.unsqueeze(0))
        # print(x.unsqueeze(0).shape)
        # print(preds.shape)
        # print(labels.shape)
        loss=torch.nn.functional.mse_loss(preds,labels)

        rnn_optimizer.zero_grad()
        loss.backward()
        rnn_optimizer.step()
        rnn_loss.append(loss.item())

    PlotCurve(mlp, rnn, input_x, x)

一些注意的點(過程)

  1. 有些人的代碼是多加了dalaloader來做了數據集的loader,我個人認爲沒啥必要,這麼簡單的東西。當然加了loader或許更加符合習慣。
  2. 爲什麼數據只取了20個(從left到right只有sinx的20個數據)?我一開始是從-128附近取到了128附近,但是發現訓練效果奇差無比,懷疑人生了都。這僅僅取了20個數據,都需要1000次訓練,更大的數據集的時間代價可見一斑。
  3. RNN的lr是0.01,MLP的是0.001?這個也是根據loss的圖來調節的,0.001在我這個rnn裏並不適合,訓練太慢了。而且爲了和mlp的EPOCH保持一致,就換了0.01的學習率。但是爲什麼RNN比MLP下降的慢?這個有待進一步討論(當然是因爲我太菜了)。
  4. 關於loss function,爲什麼用mse loss?隨便選的。我又換了l1_loss和其他的loss試了,效果差不多,畢竟這麼簡單的函數擬合,什麼損失函數無所謂了。
  5. 論文指出,RNN系列網絡比MLP擬合時間序列數據能力更強,爲什麼這次訓練反而比MLP下降更慢?不僅如此,其實如果多次比較MLP和RNN的擬合效果,發現MLP效果更穩定更好一些,這又是爲什麼呢?有待進一步探討。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章