透過 PyTorch 這個工具程式庫,相對於過往直接由類神經網路公式來撰寫程式,可以大量減少撰寫的時間以及錯誤;更重要的是,可以透過 PyTorch,很輕易地使用 CUDA 來驅動多核 GPU 的運算能力。目前,多核 GPU 運算幾乎是近十年來標準的人工智慧程式開發及研究的方式。
首先,先就一個單變數的反饋類神經網路 (backpropagation neural network) 來進行簡單的「墨西哥帽」模型學習;這個模型可以繪出如下:
資料的產出可以用簡單的 python 程式來執行。
import numpy as np
import matplotlib.pyplot as plt
x=np.arange(-2*np.pi,2*np.pi,0.1)
y=np.sin(x)*x
plt.xlabel('x')
plt.ylabel('y')
plt.plot(x,y,'b.')
plt.grid()
plt.show()
接下來開始依照 PyTorch 的方式建立類神經網路;首先必須要繼承「torch.nn」的類別原型,然後定義類神經網路的結構以及激發函數,接來來在「forward()」這個函數中,填入類神經網路中層與層之間的關係;這樣就完成了一個常用的「反饋類神經網路」的類別了。
import torch
from torch import nn
class classNeural(nn.Module):
def __init__(self,n_input,n_hidden,n_output):
super().__init__()
self.n_input=n_input
self.n_hidden=n_hidden
self.n_output=n_output
#--------
self.layer1=nn.Linear(n_input,n_hidden)
self.layer2=nn.Linear(n_hidden,n_output)
self.active=nn.Sigmoid()
#--------
def forward(self,x):
x=self.active(self.layer1(x))
return self.layer2(x)
接下來,我們要把訓練資料轉換成「torch.nn」可以接受的「二維 tensor」型態,所以單變數的訓練資料陣列,轉換成 tensor 之後,要再利用「unsqueeze(dim=1)」增加一個空白的維度;同時,因為 torch.nn 所使用的浮點運算為了計算速度的考量,通常會使用「float32」,而非 numpy default 使用的「float64」,所以也一併在這個階段加入。
#-----------------
X_train=torch.tensor(x.astype('float32')).unsqueeze(dim=1)
Y_train=torch.tensor(y.astype('float32')).unsqueeze(dim=1)
#-----------------
接下來,我們就創建一個類神經網路,在這裏取名為「neural」;要注意的是,要起始類神經網路時,PyTorch 會將神經網路各個節點的權重設為亂數;為了後續開發或研究的執行追蹤,我們創建類神經網路之前會習慣固定一個亂數種子。
接下來就是設定在「反饋訓練」(backpropagation training) 時所使用的成本函數以及訓練方法;這也就是我覺得使用 PyTorch 的好處之一,只需要作設定而不用寫複雜的程式。
torch.manual_seed(13)
neural=classNeural(1,10,1)
#-----------------
loss_fn=nn.MSELoss() # MSE
optimizer=torch.optim.AdamW(neural.parameters(),lr=0.01)
#-----------------
然後,開始進行類神經網路的訓練;首先,「train()」這個函數通知 PyTorch 接下來進行訓練模式;然後取出在訓練模式下的類神經網路預測值,將預測值與訓練值比較之後計算差異成本「loss_fn()」,然後起啟反饋訓練參數「optimizer.zero_grad()」,進行成本反饋「loss.backward()」,進行反饋訓練「optmizer.step()」;這樣就完成一個「反饋訓練」的迴圈。
neural.train()
n_epoche=5000
mae_x=[]
mae_y=[]
for epoche in range(n_epoche):
Y_pred=neural(X_train)
loss=loss_fn(Y_pred,Y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoche % 100==0:
neural.eval()
with torch.inference_mode():
Y_pred=neural(X_train)
mae=torch.mean(torch.abs(Y_train-Y_pred)).numpy()
mae_x.append(epoche)
mae_y.append(mae)
在這裏我們用了 3000 迴圈的反饋訓練,最後的結果跟原來訓練資料比較可以得來以下的圖形;在使用訓練好的「類神經網路」模式來作預測值計算時,必須要用「eval()」函數來通知 PyTorch,然後在「torch.reference_mode()」的狀態下進行模式運算;以及影響到訓練參數。
neural.eval()
with torch.inference_mode():
Y_pred=neural(X_train)
y_pred=Y_pred.numpy()
plt.plot(x,y,'b.',label='training data')
plt.plot(x,y_pred,'r-',label='prediction')
plt.grid()
plt.legend(fontsize=12)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
如果把不同次迴圈的訓練誤差畫出來,可以得到以下的趨勢圖。
torch.manual_seed(13)
neural=classNeural(1,10,1)
#-----------------
loss_fn=nn.MSELoss() # MSE
optimizer=torch.optim.AdamW(neural.parameters(),lr=0.01)
#-----------------
neural.train()
n_epoche=5000
mae_x=[]
mae_y=[]
for epoche in range(n_epoche):
Y_pred=neural(X_train)
loss=loss_fn(Y_pred,Y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoche % 100==0:
neural.eval()
with torch.inference_mode():
Y_pred=neural(X_train)
mae=torch.mean(torch.abs(Y_train-Y_pred)).numpy()
mae_x.append(epoche)
mae_y.append(mae)
#------------
plt.plot(mae_x,mae_y,'b--')
plt.ylabel('mean absolute error')
plt.xlabel('training iteration')
plt.grid()
plt.show()
#------------
從誤差趨勢圖上,可以看到有三個不同的訓練階段;如果我們抽樣這三個階段,其實可以觀察到分別在原來訓練資料的不同轉折型態上,有不同的訓練收歛的表現;而且逐步有效的趨近,一直到最後訓練結果接近目標模型後,收歛的進度就會趨緩。
#--------------------
# 訓練 3 個類神經網路
#--------------------
index_Weighting=[500,1000,5000]
saved_Weighting=[]
for i in range(len(index_Weighting)):
n_epoche=index_Weighting[i]
torch.manual_seed(13)
neural=classNeural(1,10,1)
loss_fn=nn.MSELoss() # MSE
optimizer=torch.optim.AdamW(neural.parameters(),lr=0.01)
neural.train()
for epoche in range(n_epoche):
Y_pred=neural(X_train)
loss=loss_fn(Y_pred,Y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
saved_Weighting.append(neural.state_dict())
#-----------------
# 將訓練好的類神經網路進行預測
#-----------------
y_pred=[]
for i in range(len(index_Weighting)):
neural=classNeural(1,10,1)
neural.load_state_dict(saved_Weighting[i])
neural.eval()
with torch.inference_mode():
Y_pred=neural(X_train)
y_pred.append(Y_pred.numpy())
#----------------
# 畫出預測模型結果
#----------------
index_color=['green','brown','red']
for i in range(len(index_Weighting)):
strLabel='train count:'+str(index_Weighting[i])
plt.plot(x,y_pred[i],linestyle='dashed',color=index_color[i],label=strLabel)
plt.grid()
plt.legend(fontsize=8)
plt.xlabel('x')
plt.ylabel('y')
plt.show()