Radial Basis Function Network

RBF Network

前面的一篇SVM中,最後的分割函數:

10624272-47af0f878141b64a.png
使用高斯核函數方式把數據維度擴展到無限維度進而得到一條粗壯的分界線。仔細看一下這個分割函數,其實就是一些Gaussian函數的線性組合,y就是增長的方向。
Gaussian函數還有另外一個叫法——徑向基函數,這是因爲這個base function的結果只和計算這個x和中心點xn的距離有關,與其他的無關。
從其他方面來看SVM,先構造一個函數:
g(x) = y_nexp(-γ|x - x_n|^2)指數求出來的其實就是x點和中心點的相似度,相似度越高,那麼=晚y這個方向投票的票數就會越多。不同的g(x)有不同的權重,他們的線性組合就成了SVM,g(x)函數稱爲是radial function。所以Gaussian SVM就是把一些radial function聯合起來做linear aggregation。
10624272-6588d51666aadb65.png

RBF Network就是SVM的延伸,目的就是找到所有radial hypotheses的linear aggregation,得到更好的網絡模型。
10624272-ccba9c461dd31303.png

可以看到這兩種網絡其實很類似,Neural Network的隱藏層是權值和數據做內積非線性轉換再uniform的組合得到最後的輸出,而對於RBF Network隱藏層是求高斯距離在做aggregation的方法。比較大的不同點就在於hidden層的不同了。
10624272-287a9913f1ff0e26.png

10624272-dc8b471b19c9f71b.png

β就是每一個radial function的權值,μ就是中心點,m爲中心點的個數,主要的,對比一下之前的SVM,β就是αy,μ就是支持向量。由於是一個分類問題,所以最後的output function就是sign函數了。
10624272-85380a75f0a2e592.png

之前講過,一個核函數不是隨便亂選的,要滿足兩個條件:對稱,半正定。對於SVM裏面的核函數,其實ius把當前的數據提升到某一個很高很高的維度,然後做切片把數據分出來,polynomial function也是一樣的,只不過是有限維度的。而RBF其實就是在當前的空間做相似度處理,而那些kernel其實就是轉換到z空間來計算核函數以表徵兩個向量的相似度。所以RBF和kernel都是衡量相似度的方式。雖然SVM和RBF Network都很相似,甚至可以說最後的決策函數基本一致的,但是他們的學習過程是很不一樣的,一個是直接x空間,一個是轉換到z空間。
10624272-b576a77a0f50e51f.png

衡量相似性並不止一種RBF方法,餘弦相似度這些也可以衡量向量之間的相似度。

回過頭來思考一下SVM,其實支持向量機就是先通過凸優化的一些方法找到有投票權利的點,之後給出相應的權值,最後決策就是這些有投票權利的點進行決策;對於其他線性模型,其實主要的不同就是他們每一個點都有投票的權利,這就導致很遠的點都會干擾到邊界。而RBF Network事實上做的事情和SVM有點像,因爲RBF函數是指數增長,如果這個點很遠的話會非常小,近乎就是0了,所以也起到了弱化遠的點投票權,強化近的點投票權的能力。

RBF Network Learning

RBF Network的決策函數:

10624272-8abbf9c46c2dcc7d.png

μ就是中心點,中心點是自己選擇的。有一種選擇中心點的方法,就是所有的點都作爲中心點,那麼每一個樣本對於預測都會有影響,β就是影響的程度。如果影響的程度都是一樣的,那麼就是1了,β = 1*y,最後相乘做uniform aggregation之後sign得到結果。這種我們稱爲full RBF Network。
10624272-65af45410e3db116.png

這個時候,full RBF Network就可以表示爲:
10624272-3314b0cbfa87a3b6.png

這是一個指數函數,距離越遠,那麼衰減的就越快,x與中心點的距離越近那麼就越大,距離越遠就越小。也就是說,如果我們的樣本點是N個,那麼起了關鍵作用的一般就是最近的那個點而已,當然,不一定是最近的一個點,可以是最近的K個點,用這k個點來代替N個點,當前的點周圍最近的k個點哪個類別最多,那麼這個當前這個點就是屬於哪個類別的。這種算法就叫K近鄰算法。
10624272-abdb46d10cc14581.png

k nearest neighbor通常比nearest neighbor model效果更好,計算量上也比full RBF Network要簡單一些。值得一提的是,k nearest neighbor與full RBF Network都是比較“偷懶”的方法。因爲它們在訓練模型的時候比較簡單,沒有太多的運算,但是在測試的時候卻要花費更多的力氣,甚至可以說是幾乎沒有運算在裏面,只需要做一些簡單的數據處理即可,找出最相近的中心點,計算相對複雜一些。
如果是做迴歸問題,我們就只需要去掉output:
10624272-fe041c0e4a3a4b9e.png

很明顯,這樣就是一個線性迴歸的問題了,每一個RBF 其實可以看做就是一個矩陣比如第一個元素x1,那麼經過RBF的轉換之後:
z_1 = [RBF(x_1,x_1), RBF(x_1, x_2), RBF(x_1,x_3),RBF(x_1,x_3)...RBF(x_1,x_N)]
那麼Z就是z的按列排序了,按照線性迴歸的解公式:
β = (Z^TZ)^{-1}Z^Ty
上述矩陣Z是一個方陣,大小是N,有多少箇中心點那麼就有多少個N。如果每一個x都是不一樣的,那麼這個矩陣就是可以逆的矩陣了,畢竟x是訓練數據,一樣的就沒有意義了。
10624272-5913ecbf7a2a7557.png

化簡一下:
β = Z^{-1}y
我們以x1爲例子,那麼解就是:
10624272-5498027dfc2f4a3a.png

這個結果對於我們來說非常奇怪,如果這樣的話那麼對於所有的x都有:
g_{RBF}(x_n) = y_n所有Ein = 0,這樣對於機器學習來說並不是一個好事情,因爲這樣很大概率會出現過擬合。當然,某些情況下還是很有用的,比如函數擬合或者是做autoencode。
10624272-e9efee11a1452a4f.png

爲了避免過擬合,使用ridge regression的方法:
10624272-30432669c3cd30c8.png

10624272-9578945ba63a7e31.png

L2範式正則化。Z矩陣是由一系列Gaussian函數組成,每個Gaussian函數計算的是兩個樣本之間的distance similarity。這裏的Z與之前我們介紹的Gaussian SVM中的kernel K是一致的。當時使用ridge regression得到的解:
10624272-60898386ba1feba5.png

比較一下kernel ridgeregression與regularized full RBF Network的β解,形式上相似但不完全相同。這是因爲regularization不一樣,在kernel ridgeregression中,是對無限多維的特徵轉換做regularization,而在regularized full RBF Network中,是對有限維(N維度)的特徵轉換做regularization。因此,兩者的公式解有細微差別。
10624272-79b266ba3b47b3f3.png

對於解決過擬合,還有另外的一種方法,可以選擇K個具有代表性的點來代表這N個點,這樣減少了中間點減少了權重的數量,VC維就減少了,可以起到regularization的作用。
10624272-2afdb9fca006deed.png

原本的問題是求解中心點μ,β權重,現在β可以通過迴歸求解,那麼只需要求μ了。

K Mean Algorithm

選擇代表的原因,就是因爲這些點有很高的相似性,所以可以使用一箇中心點來代表,從所有的點中選擇幾個有代表性的點。

10624272-24a890bb6d46805d.png

首先聚類算法是一種非監督的算法,不需要有label。需要確定的一般就是兩個變量,分羣值Sm,沒一個分類可以表示爲S1,S2,S3,S4...Sm,另一個就是中心值μm,μ1,μ2,μ3,μ4...μm,每一個分羣就對應着中心,要求的就是這個中心。對於這類問題的優化,就可以使用square error function了。優化的步驟也是一樣,求導,梯度下降或者梯度爲0求最優值解。
10624272-5696f6057cead3d3.png

剛剛也說過了,既然是衰減的形式,那麼只需要取最大的就好了,最大也就意味着這需要求距離最近的即可。所以,表達式裏面只有屬於這個類別的數據求error。
最後就是求導做優化了:
10624272-c119d97943482ab3.png

兩個變量組合的優化問題,通常的方法就是對這兩個變量分別求。仔細觀察一下這兩個變量,可以發現,只要確定了μ,就可以確定S;或者只要確定了S,求個平均也可以得到μ。所以假設μ已經是固定的了,那麼可以依次迭代x,和哪個μ的距離最小就屬於哪個類別。
10624272-aa609845a7ead7c4.png

如果類別S是確定的,那麼目標就是找到對應的μ中心點,顯然這個,求個導的事,梯度下降就可以解決了。
10624272-618b92228598d4f6.png

所以這就成了一個雞生蛋蛋生雞的問題,所以一般一開始我們會選擇隨機的幾個點作爲中心μ,然後按照上訴步驟優化。

優化有結果嗎?

這個優化的過程好像有些不一樣,求導等於0應該是直接求出了最優的解,linear regression就是這個道理,但是爲什麼要一直這樣迭代呢?這是因爲求出來的這個μ並不是一個全局的μ,只是在當前對於每一個羣S的一個最優,但是這個S並不是最優的,之前也說了:這個S和μ是互相牽制的,雞生蛋蛋生雞的問題,S可以得到μ,μ也可以得到S。所以整個過程通俗點就是通過μ求局部最優的S,通過S有球局部的最優μ,不斷的迭代,慢慢的跑到全局。但是也沒有可以跑到局部呢?這個是有可能的,這於初值有關,所以Kmean均值算法也是一個初值敏感的算法。對於局部這個問題,有一種解法就是可以合併最近的幾個質心。事實上如果中心比較小,比如3個這樣,一般都不會有局部出現,因爲\sum_{m=1}^M(x_n - μ_m)^2不會這麼的彎曲。
停止是一定的,因爲無論是通過S優化μ還是μ優化S都朝Ein = 0爲目的,如果Ein增加了是不會繼續的,所以最後一定會到達一個平衡點。

The process of the RBF Network

既然中心有其他算法可以幫忙解決了,那麼整個算法也就清晰了:


10624272-88c33dd8b29df039.png

求解優化的過程中,可以使用validation來求解最優的λ和M。


10624272-d463a4157f1644a0.png

RBF Network and KMeans in action

10624272-142851b7a6d23522.png

k值的大小和初始位置的不同都會影響聚類的結果。
把這些機構k均值使用到RBF裏面:


10624272-7a32d978f9c5a778.png

10624272-6bb82eb88a192090.png

對於正則化也有不同的影響。

coding

KMeans

接下來就是代碼實現KMeans算法了。KMeans算法其實很簡單,首先隨機得到幾個中心點,根據中心點預測做聚類算法即可。

def loadDataSet():
    '''loading data......'''
    data = dataSet.load_iris()
    dataset = data.data
    target = data.target
    PCA = pca.PCA(n_components=2)
    dataset = PCA.fit_transform(dataset)
    return np.mat(dataset), np.mat(target)
    pass

加載數據,iris數據集進行降維操作便於可視化。

def distEclud(vecA, vecB):
    '''calculate the distance from vecA to vecB'''
    return np.sqrt(np.sum(np.power(vecA - vecB, 2)))
    pass

def randCent(dataSet, k):
    '''create the center'''
    n = np.shape(dataSet)[1]
    centroids = np.mat(np.zeros((k, n)))
    for j in range(n):
        minJ = np.min(dataSet[:, j])
        rangeJ = float(max(dataSet[:, j]) - minJ)
        centroids[:, j] = minJ + rangeJ * np.random.rand(k, 1)
    return centroids

計算距離,隨機選擇中心點。隨機選擇中心點這裏是先計算了每一個維度的範圍,之後在這個範圍裏面隨機選擇。

def KMeans(dataSet, k, distMeas = tool.distEclud, createCent = tool.randCent):
    '''KMeans Algorithm is running......'''
    m = np.shape(dataSet)[0]
    clusterAssment = np.mat(np.zeros((m, 2)))
    centroids = createCent(dataSet, k)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):
            minDist = np.inf
            minIndex = -1
            for j in range(k):
                distJI = distMeas(centroids[j,:], dataSet[i,:])
                if distJI < minDist:
                    minDist = distJI
                    minIndex = j
            if clusterAssment[i, 0] != minIndex:
                clusterChanged = True
            clusterAssment[i, :] = minIndex, minDist**2
        for cent in range(k):
            ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
            centroids[cent, :] = np.mean(ptsInClust, axis=0)
    dataFrame = pd.DataFrame(data=np.hstack((dataSet, clusterAssment[:,0])))
    return dataFrame, centroids

選擇和calculate distance的算法都是動態的方式,便於以後的改進。整個過程也比較簡單,離哪個近就屬於哪個類別。

RBF Network

①獲取中心點

_,center = kMeans.KMeans(np.mat(x_train), k)

剛剛寫好的KMeans就是這個作用。

②計算β值

beta = rbf(x_train, center, y_train, gama=gamma, lamda=lamda)

rbf函數的實現:

def rbf(x_train, center, y_train, gama = 0.001, lamda = 0.01):
    M = center.shape[0]
    N = len(x_train)
    Z = np.zeros((M, N))
    for i in range(M):
        for j in range(N):
            Z[i][j] = Gaussian(x_train[j], center[i], gama)
    I1 = np.eye(N, k = 0)
    beta = np.linalg.inv(np.dot(Z.T, Z) + lamda * I1)
    beta = np.dot(beta, Z.T)
    y_train = np.mat(y_train)
    beta = np.dot(y_train, beta)
    return beta
    pass
def Gaussian(vecA, vecB, gama):
    x_x = np.abs(np.sum(vecA - vecB))
    x_x_2 = np.power(x_x, 2)
    return np.exp(-1.0 * gama * x_x_2)
    pass

首先是計算Z矩陣,就是φ(x)的矩陣。其實就是和上面步驟一模一樣的,使用的是線性迴歸,β = (Z^TZ + λI)^{-1}Z^Ty使用直接就可以算,要是邏輯迴歸也是一樣。

③預測

def predict(beta, x_train, center, gama):
    result = []
    for x in x_train:
        x = np.mat(x)
        sum = 0
        for i, vecB in enumerate(center):
            sum += beta[0,i]*Gaussian(x, vecB, gama)
        result.append(sum)
    return result
    pass

爲了方便調用,整合成一個函數:

def RBF(y_test, x_test, y_train, x_train, gamma = 0.001, lamda = 0.01, k = 4):
    Again = True
    while Again == True:
        _,center = kMeans.KMeans(np.mat(x_train), k)
        beta = rbf(x_train, center, y_train, gama=gamma, lamda=lamda)
        Again = False
        for i in range(beta.shape[1]):
            if np.isnan(beta[0, i]):
                Again = True
    result = predict(beta, x_train, center, gamma)
    for i in range(len(result)):
        if result[i] > 0:
            result[i] = 1
        else:
            result[i] = -1
    posibility = 0
    for i in range(len(result)):
        if result[i] == y_train[i]:
            posibility += 1
    train_accuracy = posibility/len(result)
    result = predict(beta, x_test, center, gamma)

    for i in range(len(result)):
        if result[i] > 0:
            result[i] = 1
        else:
            result[i] = -1
    posibility = 0
    for i in range(len(result)):
        if result[i] == y_test[i]:
            posibility += 1
    test_accuracy = posibility/len(result)
    return train_accuracy, test_accuracy

可以計算不同gamma,lamda,k的影響。

④獲取數據

def load_data():
    data = dataset.load_breast_cancer().data
    target = dataset.load_breast_cancer().target
    for i in range(len(target)):
        if target[i] == 0:
            target[i] = -1
    x_train, x_test, y_train, y_test = train_test_split(data, target, random_state=42, shuffle=True, test_size=0.4)
    return x_train, x_test, y_train, y_test
    pass

⑤啓動函數

if __name__ == '__main__':
    x_train, x_test, y_train, y_test = load_data()
    gamma = [1,0.1,0.01,0.001,0.0001]
    lamda = gamma
    train_accuracy = []
    test_accutacy = []
    c = ['red', 'blue', 'orange', 'green', 'yellow', 'black']
    for n, i in enumerate(gamma):
        for j in lamda:
            train, text = RBF(x_test=x_test, y_test=y_test, x_train=x_train, y_train=y_train, gamma=i, lamda=j)
            print('gama : ',i, ' lamda : ', j, ' train_accuracy : ', train, ' text_accuray : ', text)
            train_accuracy.append(train)
            test_accutacy.append(text)
        plt.plot(lamda, train_accuracy, c = c[n], label = 'gamma:'+str(i) + ' (train)')
        plt.plot(lamda, test_accutacy, c = c[n], linestyle='--', label = 'gamma:'+str(i) + ' (test)')
        plt.xlabel('lambda')
        plt.ylabel('accuracy')
        plt.legend(loc = 'upper right')
        train_accuracy = []
        test_accutacy = []
    plt.show()

    for n, i in enumerate(lamda):
        for j in gamma:
            train, text = RBF(x_test=x_test, y_test=y_test, x_train=x_train, y_train=y_train, gamma=j, lamda=i)
            print('lamda : ',i, ' gama : ', j, ' train_accuracy : ', train, ' text_accuray : ', text)
            train_accuracy.append(train)
            test_accutacy.append(text)
        plt.plot(gamma, train_accuracy, c = c[n], label = 'lamda:'+str(i) + ' (train)')
        plt.plot(gamma, test_accutacy, c = c[n], linestyle='--', label = 'lamda:'+str(i) + ' (test)')
        plt.xlabel('gamma')
        plt.ylabel('accuracy')
        plt.legend(loc = 'upper right')
        train_accuracy = []
        test_accutacy = []
    plt.show()

    ks = [2,3,4,5,6,7]
    train_accuracy = []
    test_accutacy = []
    for i in range(6):
        for n, i in enumerate(ks):
            train, text = RBF(x_test=x_test, y_test=y_test, x_train=x_train, y_train=y_train, gamma=0.0001, lamda=0.01, k=i)
            print('k == ' + str(i))
            train_accuracy.append(train)
            test_accutacy.append(text)
        plt.plot(ks, train_accuracy, c = c[n], label = 'train')
        plt.plot(ks, test_accutacy, c = c[n], linestyle='--', label = 'test')
        plt.xlabel('the number of k')
        plt.ylabel('accuracy')
        plt.legend(loc = 'upper left')
        plt.show()
        train_accuracy = []
        test_accutacy = []
    pass

效果的討論

在運行函數得到結果後進行分析。

①當γ固定了,看看lamda變化對於結果的影響。

10624272-c7ccbb3e9a62733d.png

10624272-bea21533047ced19.png

其實還是很正常的,隨着gamma的減少,準確率慢慢提上去了,虛線的測試數據,直線是準確率。Gaussian函數:g(x) = exp(-γ|x_n - μ|^2)γ越小,就相當於σ變大,高斯函數的標準差變大那麼這個函數就會變的更加平滑,泛化能力會很強。其實就是regularization的過程。
觀察上圖是可以得到λ對於整體的影響不會太大。基本是平緩的。

②當λ固定了,看看γ變化對於結果的影響。

10624272-1068f82b15b63ef9.png

10624272-fdbe6122ee34f505.png

γ越小效果越好,但是如果γ非常小,那麼λ對於模型的影響幾乎是沒有影響。

③對於k的數量和準確率的討論

效果不太穩定,我多做了幾次。

10624272-6473cc9ae0e47246.png

10624272-f9ae2f31198e2d64.png

10624272-4ebf0f271d759e0a.png

10624272-ed0b4d219bd902e8.png

10624272-fedafb9be9fc6108.png

10624272-28cceea27f9c07ef.png

效果非常非常不穩定,我之前懷疑是線性迴歸的解不穩定的緣故,之前學習到病態矩陣,也就是近似奇異矩陣的矩陣,而regularization L2正則化就是爲了使得病態矩陣轉換成正常矩陣,所以增大了λ,顯然並沒有什麼卵用。雖然整體效果不錯。上面的結果就已經是λ增大的結果了。

所以最後還有兩個問題沒有解決,一個就是λ爲什麼對於模型的影響很很小,另一個上面沒有提到,爲什麼測試數據的準確率會大於訓練數據的準確率?這還打的明顯,不知道是不是沒有做交叉驗證的結果,因爲懶。。。。之前看到的解釋是:

如果程序很好的實現了模型,那麼就是模型不適合你的數據,因爲這表明存在如下問題:每次訓練,都使得訓練之後的模型對測試的 1折效果很好,而對用於訓練的9折效果慘淡,也就是模型落入了局部極值點而非全局極值點。這很有可能是模型在具體數據下的失效問題。

但是這個問題已經重複過了很多次,一直在使用test_train_split區分,同時也有shuffle。事實上在γ比較小的時候,測試數據就超過了訓練數據的準確率,上面γ的變化可以看到。

最後附上GitHub代碼:https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/RBFNetwork

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