RBF神經網絡初探
徑向基函數
徑向基函數是一種函數的取值僅僅與輸入的中心點有關的函數,具有這種性質的函數就稱為徑向基函數。
比如,高斯函數是一種徑向基函數,其輸出值的大小與距離中心點的距離有關,距離中心點越遠,函數值越小,距離中心點越近,函數值越大。
RBF神經網絡的結構
RBF神經網絡一般具有兩層結構,是一種前向神經網絡。第一層的作用是將輸入由非線性可分轉變為線性可分,第二層一般是感知機類型的神經元層或ADALINE類型的神經元層。
第一層計算
計算過程
對於只有一個兩維的輸入(p1, p2)而言,假定函數的中心點C1和C2已知(同樣假設只有兩個中心),利用高斯函數作為徑向基函數,如果高斯函數的標准差σ1和σ2已知,那么第一層的輸出即為:
計算過程為:
- 計算由樣本(p1,p2)組成的點到中心C1的距離r1,通過高斯徑向基函數投影為Φ1
- 同理計算到到C2的距離投影到Φ2.
中心C和標准差σ的確定
上述過程假定中心C和標准差σ是已知的,實際上很多任務中是需要學習這兩個量的,對於中心C,我們一般采用kmeans聚類算法來確定,因此聚類的中心點數量k也是一個超參。而σ的確定也非常簡單,即以到聚類中心點的距離(平方根距離)最近的前k個樣本的聚類的均值為σ,之后每個σ就都確定了。
還有一種方法來確定C和σ,就是認為C和σ是可學習的參數,利用梯度下降來更新學習C和σ。后面的代碼例子中將利用梯度來學習C和σ。
第二層計算
感知機和ADALINE
對於第一層輸出p(假設為5維向量),其輸出為線性映射:
其中W為1x5矩陣,b為1x1常量,harddim為sgn函數:
其中感知機和ADALINE其實有一些不同點,雖然前向過程的purelin恆等函數看起來沒啥用(只是形式上要走個激活函數),但兩者在更新參數時使用的標簽是不同的,感知機是用離散的標簽作為gt來更新前面的參數,而ADALINE則是直接根據加權求和的結果,也就是連續值來更新前面的參數,具體可以見下圖:
Coding
本文僅以高斯徑向基函數,第二層為感知機模型,利用梯度下降算法更新參數為例來寫一個RBF神經網絡的Demo。(求梯度的公式感覺自己推就太麻煩了,有自動求導為啥不用.)
Loss:
測試擬合情況:
import torch
import torch.nn as nn
import numpy as np
import torch.optim as optim
from matplotlib import pyplot as plt
class RBF(nn.Module):
def __init__(self,input_dim = 5,k = 3):
super(RBF,self).__init__()
self.k = k
self.C = nn.Parameter(torch.randn(1,k,input_dim)) # (n,k,5)
self.sigma = nn.Parameter(torch.randn(1,k)) # (n,k)
self.w = nn.Parameter(torch.randn(1,1,k)) # (n,1,k)
self.b = nn.Parameter(torch.randn(1,1,1)) # (n,1,1)
self.tanh = nn.Tanh()
def forward(self,x):#(n,5)
r = torch.sqrt(torch.sum((x.view(-1,1,5) - self.C)**2,dim = -1)) # (n,k)
phi = torch.exp(-r**2/(2*self.sigma**2)).unsqueeze(-1) # (n,k,1)
return self.tanh(self.w @ phi + self.b).squeeze(-1)
if __name__ == "__main__":
batch_size = 4
k = 70
input_dim = 5
num_epochs = 300
x_train = torch.rand(batch_size * 160,input_dim) * 2
y_train = torch.FloatTensor(torch.sin(torch.sum(x_train,dim = -1,keepdim=True)))
#y_train[(x_train[:,0] > 0.5) & (x_train[:,2] < 0.5)] = 0
model = RBF(input_dim,k)
optimizer = optim.Adam(model.parameters(),lr = 1e-3)
criterion = nn.MSELoss()
loss_curve = []
for epoch in range(num_epochs):
running_loss = 0.
for i in range(160):
x = x_train[i*batch_size:(i+1)*batch_size]
y = y_train[i*batch_size:(i+1)*batch_size]
pred = model(x)
loss = criterion(pred,y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss
print("loss: {}".format(running_loss / 160))
loss_curve.append(running_loss / 160)
x_test = torch.rand(160,input_dim)*2
y_test = torch.FloatTensor(torch.sin(torch.sum(x_test,dim = -1,keepdim=True)))
model.eval()
with torch.no_grad():
y_pred = model(x_test)
y_test = y_test[indices]
y_pred = y_pred[indices]
plt.plot(y_test.squeeze(1).numpy())
plt.plot(y_pred.squeeze(1).numpy())
#plt.plot(loss_curve, label='train_loss')
plt.show()