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