其實各大深度學習框架背後的原理都可以理解爲擬合一個參數數量特別龐大的函數,所以各框架都能用來擬合任意函數,Pytorch也能。在這篇博客中,就以擬合y = ax + b爲例(a和b爲需要擬合的參數),說明在Pytorch中如何擬合一個函數。
一、定義擬合網絡
1、觀察普通的神經網絡的優化流程
# 定義網絡
net = ...
# 定義優化器
optimizer = torch.optim.Adam(net.parameters(), lr=0.001, weight_decay=0.0005)
# 定義損失函數
loss_op = torch.nn.MSELoss(reduction='sum')
# 優化
for step, (inputs, tag) in enumerate(dataset_loader):
# 向前傳播
outputs = net(inputs)
# 計算損失
loss = loss_op(tag, outputs)
# 清空梯度
optimizer.zero_grad()
# 向後傳播
loss.backward()
# 更新梯度
optimizer.step()
上面的代碼就是一般情況下的流程。爲了能使用Pytorch內置的優化器,所以我們需要定義一個一個網絡,實現函數parameters
(返回需要優化的參數)和forward
(向前傳播);爲了能支持GPU優化,還需要實現cuda
和cpu
兩個函數,把參數從內存複製到GPU上和從GPU複製回內存。基於以上要求,網絡的定義就類似於:
class Net:
def __init__(self):
# 在這裏定義要求的參數
pass
def cuda(self):
# 傳輸參數到GPU
pass
def cpu(self):
# 把參數傳輸回內存
pass
def forward(self, inputs):
# 實現向前傳播,就是根據輸入inputs計算一遍輸出
pass
def parameters(self):
# 返回參數
pass
在擬合數據量很大時,還可以使用GPU來加速;如果沒有英偉達顯卡,則可以不實現cuda
和cpu
這兩個函數。
2、初始化網絡
回顧本文目的,擬合: y = ax + b, 所以在__init__
函數中就需要定義a和b兩個參數,另外爲了實現parameters
、cpu
和cuda
,還需要定義屬性__parameters
和__gpu
:
def __init__(self):
# y = a*x + b
self.a = torch.rand(1, requires_grad=True) # 參數a
self.b = torch.rand(1, requires_grad=True) # 參數b
self.__parameters = dict(a=self.a, b=self.b) # 參數字典
self.___gpu = False # 是否使用gpu來擬合
要擬合的參數,不能初始化爲0! ,一般使用隨機值即可。還需要把requires_grad參數設置爲True,這是爲了支持向後傳播。
3、實現向前傳播
def forward(self, inputs):
return self.a * inputs + self.b
非常的簡單,就是根據輸入inputs
計算一遍輸出,在本例中,就是計算一下 y = ax + b。計算完了要記得返回計算的結果。
4、把參數傳送到GPU
爲了支持GPU來加速擬合,需要把參數傳輸到GPU,且需要更新參數字典__parameters
:
def cuda(self):
if not self.___gpu:
self.a = self.a.cuda().detach().requires_grad_(True) # 把a傳輸到gpu
self.b = self.b.cuda().detach().requires_grad_(True) # 把b傳輸到gpu
self.__parameters = dict(a=self.a, b=self.b) # 更新參數
self.___gpu = True # 更新標誌,表示參數已經傳輸到gpu了
# 返回self,以支持鏈式調用
return self
參數a和b,都是先調用detach
再調用requires_grad_
,是爲了避免錯誤raise ValueError("can't optimize a non-leaf Tensor")
(參考:ValueError: can’t optimize a non-leaf Tensor?)。
4、把參數傳輸回內存
類似於cuda
函數,不做過多解釋。
def cpu(self):
if self.___gpu:
self.a = self.a.cpu().detach().requires_grad_(True)
self.b = self.b.cpu().detach().requires_grad_(True)
self.__parameters = dict(a=self.a, b=self.b)
self.___gpu = False
return self
5、返回網絡參數
爲了能使用Pytorch內置的優化器,就要實現parameters
函數,觀察Pytorch裏面的實現:
def parameters(self, recurse=True):
r"""...
"""
for name, param in self.named_parameters(recurse=recurse):
yield param
實際上就是使用yield返回網絡的所有參數,因此本例中的實現如下:
def parameters(self):
for name, param in self.__parameters.items():
yield param
完整的實現將會放在後面。
二、測試
1、生成測試數據
def main():
# 生成虛假數據
x = np.linspace(1, 50, 50)
# 係數a、b
a = 2
b = 1
# 生成y
y = a * x + b
# 轉換爲Tensor
x = torch.from_numpy(x.astype(np.float32))
y = torch.from_numpy(y.astype(np.float32))
2、定義網絡
# 定義網絡
net = Net()
# 定義優化器
optimizer = torch.optim.Adam(net.parameters(), lr=0.001, weight_decay=0.0005)
# 定義損失函數
loss_op = torch.nn.MSELoss(reduction='sum')
3、把數據傳輸到GPU(可選)
# 傳輸到GPU
if torch.cuda.is_available():
x = x.cuda()
y = y.cuda()
net = net.cuda()
4、定義優化器和損失函數
如果要使用GPU加速,優化器必須要在網絡的參數傳輸到GPU之後在定義,否則優化器裏的參數還是內存裏的那些參數,傳到GPU裏面的參數不能被更新。 可以根據代碼來理解這句話。
# 定義優化器
optimizer = torch.optim.Adam(net.parameters(), lr=0.001, weight_decay=0.0005)
# 定義損失函數
loss_op = torch.nn.MSELoss(reduction='sum')
5、擬合(也是優化)
# 最多優化20001次
for i in range(1, 20001, 1):
# 向前傳播
out = net.forward(x)
# 計算損失
loss = loss_op(y, out)
# 清空梯度(非常重要)
optimizer.zero_grad()
# 向後傳播,計算梯度
loss.backward()
# 更新參數
optimizer.step()
# 得到損失的numpy值
loss_numpy = loss.cpu().detach().numpy()
if i % 1000 == 0: # 每1000次打印一下損失
print(i, loss_numpy)
if loss_numpy < 0.00001: # 如果損失小於0.00001
# 打印參數
a = net.a.cpu().detach().numpy()
b = net.b.cpu().detach().numpy()
print(a, b)
# 退出
exit()
6、完整示例代碼
# coding=utf-8
from __future__ import absolute_import, division, print_function
import torch
import numpy as np
class Net:
def __init__(self):
# y = a*x + b
self.a = torch.rand(1, requires_grad=True) # 參數a
self.b = torch.rand(1, requires_grad=True) # 參數b
self.__parameters = dict(a=self.a, b=self.b) # 參數字典
self.___gpu = False # 是否使用gpu來擬合
def cuda(self):
if not self.___gpu:
self.a = self.a.cuda().detach().requires_grad_(True) # 把a傳輸到gpu
self.b = self.b.cuda().detach().requires_grad_(True) # 把b傳輸到gpu
self.__parameters = dict(a=self.a, b=self.b) # 更新參數
self.___gpu = True # 更新標誌,表示參數已經傳輸到gpu了
# 返回self,以支持鏈式調用
return self
def cpu(self):
if self.___gpu:
self.a = self.a.cpu().detach().requires_grad_(True)
self.b = self.b.cpu().detach().requires_grad_(True)
self.__parameters = dict(a=self.a, b=self.b) # 更新參數
self.___gpu = False
return self
def forward(self, inputs):
return self.a * inputs + self.b
def parameters(self):
for name, param in self.__parameters.items():
yield param
def main():
# 生成虛假數據
x = np.linspace(1, 50, 50)
# 係數a、b
a = 2
b = 1
# 生成y
y = a * x + b
# 轉換爲Tensor
x = torch.from_numpy(x.astype(np.float32))
y = torch.from_numpy(y.astype(np.float32))
# 定義網絡
net = Net()
# 傳輸到GPU
if torch.cuda.is_available():
x = x.cuda()
y = y.cuda()
net = net.cuda()
# 定義優化器
optimizer = torch.optim.Adam(net.parameters(), lr=0.001, weight_decay=0.0005)
# 定義損失函數
loss_op = torch.nn.MSELoss(reduction='sum')
# 最多優化20001次
for i in range(1, 20001, 1):
# 向前傳播
out = net.forward(x)
# 計算損失
loss = loss_op(y, out)
# 清空梯度(非常重要)
optimizer.zero_grad()
# 向後傳播,計算梯度
loss.backward()
# 更新參數
optimizer.step()
# 得到損失的numpy值
loss_numpy = loss.cpu().detach().numpy()
if i % 1000 == 0: # 每1000次打印一下損失
print(i, loss_numpy)
if loss_numpy < 0.00001: # 如果損失小於0.00001
# 打印參數
a = net.a.cpu().detach().numpy()
b = net.b.cpu().detach().numpy()
print(a, b)
# 退出
exit()
if __name__ == '__main__':
main()