pytroch 基本網絡搭建

網絡
快速搭建
第一種方法

   class Net(torch.nn.Module):
        def __init__(self, n_feature, n_hidden, n_output):
            super(Net, self).__init__()
            self.hidden = torch.nn.Linear(n_feature, n_hidden)
            self.predict = torch.nn.Linear(n_hidden, n_output)
    
        def forward(self, x):
            x = F.relu(self.hidden(x))
            x = self.predict(x)
            return x
    
    net1 = Net(1, 10, 1)   # 這是我們用這種方式搭建的 net1

第二種方法

net2 = torch.nn.Sequential(
    torch.nn.Linear(1, 10),
    torch.nn.ReLU(),
    torch.nn.Linear(10, 1)
)

我們再對比一下兩者的結構:

print(net1)
"""
Net (
  (hidden): Linear (1 -> 10)
  (predict): Linear (10 -> 1)
)
"""
print(net2)
"""
Sequential (
  (0): Linear (1 -> 10)
  (1): ReLU ()
  (2): Linear (10 -> 1)
)
"""

我們會發現 net2 多顯示了一些內容, 這是爲什麼呢? 原來他把激勵函數也一同納入進去了, 但是 net1 中, 激勵函數實際上是在 forward() 功能中才被調用的. 這也就說明了, 相比 net2, net1 的好處就是, 你可以根據你的個人需要更加個性化你自己的前向傳播過程, 比如(RNN). 不過如果你不需要七七八八的過程, 相信 net2 這種形式更適合你.

我們快速地建造數據, 搭建網絡:

torch.manual_seed(1)    # reproducible

# 假數據
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)  # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size())  # noisy y data (tensor), shape=(100, 1)

def save():
    # 建網絡
    net1 = torch.nn.Sequential(
        torch.nn.Linear(1, 10),
        torch.nn.ReLU(),
        torch.nn.Linear(10, 1)
    )
    optimizer = torch.optim.SGD(net1.parameters(), lr=0.5)
    loss_func = torch.nn.MSELoss()

    # 訓練
    for t in range(100):
        prediction = net1(x)
        loss = loss_func(prediction, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

接下來我們有兩種途徑來保存

torch.save(net1, 'net.pkl')  # 保存整個網絡
torch.save(net1.state_dict(), 'net_params.pkl')   # 只保存網絡中的參數 (速度快, 佔內存少)

提取網絡

這種方式將會提取整個神經網絡, 網絡大的時候可能會比較慢.

def restore_net():
    # restore entire net1 to net2
    net2 = torch.load('net.pkl')
    prediction = net2(x)

只提取網絡參數

這種方式將會提取所有的參數, 然後再放到你的新建網絡中.

def restore_params():
    # 新建 net3
    net3 = torch.nn.Sequential(
        torch.nn.Linear(1, 10),
        torch.nn.ReLU(),
        torch.nn.Linear(10, 1)
    )

    # 將保存的參數複製到 net3
    net3.load_state_dict(torch.load('net_params.pkl'))
    prediction = net3(x)

顯示結果

調用上面建立的幾個功能, 然後出圖.

# 保存 net1 (1. 整個網絡, 2. 只有參數)
save()

# 提取整個網絡
restore_net()

# 提取網絡參數, 複製到新網絡
restore_params()

要點

Torch 中提供了一種幫你整理你的數據結構的好東西, 叫做 DataLoader, 我們能用它來包裝自己的數據, 進行批訓練. 而且批訓練可以有很多種途徑, 詳情請見 我製作的 訓練優化器 動畫簡介.

DataLoader

DataLoader 是 torch 給你用來包裝你的數據的工具. 所以你要講自己的 (numpy array 或其他) 數據形式裝換成 Tensor, 然後再放進這個包裝器中. 使用 DataLoader 有什麼好處呢? 就是他們幫你有效地迭代數據, 舉例:

import torch
import torch.utils.data as Data
torch.manual_seed(1)    # reproducible

BATCH_SIZE = 5      # 批訓練的數據個數

x = torch.linspace(1, 10, 10)       # x data (torch tensor)
y = torch.linspace(10, 1, 10)       # y data (torch tensor)

# 先轉換成 torch 能識別的 Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)

# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
    dataset=torch_dataset,      # torch TensorDataset format
    batch_size=BATCH_SIZE,      # mini batch size
    shuffle=True,               # 要不要打亂數據 (打亂比較好)
    num_workers=2,              # 多線程來讀數據
)

for epoch in range(3):   # 訓練所有!整套!數據 3 次
    for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 釋放一小批數據用來學習
        # 假設這裏就是你訓練的地方...

        # 打出來一些數據
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())

"""
Epoch:  0 | Step:  0 | batch x:  [ 6.  7.  2.  3.  1.] | batch y:  [  5.   4.   9.   8.  10.]
Epoch:  0 | Step:  1 | batch x:  [  9.  10.   4.   8.   5.] | batch y:  [ 2.  1.  7.  3.  6.]
Epoch:  1 | Step:  0 | batch x:  [  3.   4.   2.   9.  10.] | batch y:  [ 8.  7.  9.  2.  1.]
Epoch:  1 | Step:  1 | batch x:  [ 1.  7.  8.  5.  6.] | batch y:  [ 10.   4.   3.   6.   5.]
Epoch:  2 | Step:  0 | batch x:  [ 3.  9.  2.  6.  7.] | batch y:  [ 8.  2.  9.  5.  4.]
Epoch:  2 | Step:  1 | batch x:  [ 10.   4.   8.   1.   5.] | batch y:  [  1.   7.   3.  10.   6.]
"""

可以看出, 每步都導出了5個數據進行學習. 然後每個 epoch 的導出數據都是先打亂了以後再導出.

真正方便的還不是這點. 如果我們改變一下 BATCH_SIZE = 8, 這樣我們就知道, step=0 會導出8個數據, 但是, step=1 時數據庫中的數據不夠 8個, 這時怎麼辦呢:

BATCH_SIZE = 8      # 批訓練的數據個數

...

for ...:
    for ...:
        ...
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())
"""
Epoch:  0 | Step:  0 | batch x:  [  6.   7.   2.   3.   1.   9.  10.   4.] | batch y:  [  5.   4.   9.   8.  10.   2.   1.   7.]
Epoch:  0 | Step:  1 | batch x:  [ 8.  5.] | batch y:  [ 3.  6.]
Epoch:  1 | Step:  0 | batch x:  [  3.   4.   2.   9.  10.   1.   7.   8.] | batch y:  [  8.   7.   9.   2.   1.  10.   4.   3.]
Epoch:  1 | Step:  1 | batch x:  [ 5.  6.] | batch y:  [ 6.  5.]
Epoch:  2 | Step:  0 | batch x:  [  3.   9.   2.   6.   7.  10.   4.   8.] | batch y:  [ 8.  2.  9.  5.  4.  1.  7.  3.]
Epoch:  2 | Step:  1 | batch x:  [ 1.  5.] | batch y:  [ 10.   6.]
"""

這時, 在 step=1 就只給你返回這個 epoch 中剩下的數據就好了.

爲了對比各種優化器的效果, 我們需要有一些數據, 今天我們還是自己編一些僞數據, 這批數據是這樣的:

Optimizer 優化器

import torch
import torch.utils.data as Data
import torch.nn.functional as F
import matplotlib.pyplot as plt

torch.manual_seed(1)    # reproducible

LR = 0.01
BATCH_SIZE = 32
EPOCH = 12

# fake dataset
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)
y = x.pow(2) + 0.1*torch.normal(torch.zeros(*x.size()))

# plot dataset
plt.scatter(x.numpy(), y.numpy())
plt.show()

# 使用上節內容提到的 data loader
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2,)

每個優化器優化一個神經網絡

爲了對比每一種優化器, 我們給他們各自創建一個神經網絡, 但這個神經網絡都來自同一個 Net 形式.

# 默認的 network 形式
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.hidden = torch.nn.Linear(1, 20)   # hidden layer
        self.predict = torch.nn.Linear(20, 1)   # output layer

    def forward(self, x):
        x = F.relu(self.hidden(x))      # activation function for hidden layer
        x = self.predict(x)             # linear output
        return x

# 爲每個優化器創建一個 net
net_SGD         = Net()
net_Momentum    = Net()
net_RMSprop     = Net()
net_Adam        = Net()
nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]

優化器 Optimizer

接下來在創建不同的優化器, 用來訓練不同的網絡. 並創建一個 loss_func 用來計算誤差. 我們用幾種常見的優化器, SGD, Momentum, RMSprop, Adam.

# different optimizers
opt_SGD         = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum    = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
opt_RMSprop     = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam        = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]

loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []]   # 記錄 training 時不同神經網絡的 loss

訓練/出圖

接下來訓練和 loss 畫圖.

for epoch in range(EPOCH):
    print('Epoch: ', epoch)
    for step, (b_x, b_y) in enumerate(loader):

        # 對每個優化器, 優化屬於他的神經網絡
        for net, opt, l_his in zip(nets, optimizers, losses_his):
            output = net(b_x)              # get output for every net
            loss = loss_func(output, b_y)  # compute loss for every net
            opt.zero_grad()                # clear gradients for next train
            loss.backward()                # backpropagation, compute gradients
            opt.step()                     # apply gradients
            l_his.append(loss.data.numpy())     # loss recoder

Optimizer 優化器

SGD 是最普通的優化器, 也可以說沒有加速效果, 而 Momentum 是 SGD 的改良版, 它加入了動量原則. 後面的 RMSprop 又是 Momentum 的升級版. 而 Adam 又是 RMSprop 的升級版. 不過從這個結果中我們看到, Adam 的效果似乎比 RMSprop 要差一點. 所以說並不是越先進的優化器, 結果越佳. 我們在自己的試驗中可以嘗試不同的優化器, 找到那個最適合你數據/網絡的優化器.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章