[學習筆記] Hebb 學習規則和Hopfield網絡


Hebb 學習規則和Hopfield網絡

Hebb學習規則

Hebb學習規則是Donald Hebb在1949年提出的一種學習規則,用來描述神經元的行為是如何影響神經元之間的連接的,通俗的說,就是如果相鏈接的兩個神經元同時被激活,顯然我們可以認為這兩個神經元之間的關系應該比較近,因此將這兩個神經元之間連接的權值增加,而一個被激活一個被抑制,顯然兩者間的權值應該減小。

此外,Hebb還有一句非常注明的話,我在很多資料中都見到這句話的引用:“neurons that fire together, wire together”,這句話就是對權值如何更新的一個解釋,同被激活者連接的權重理應增加。

公式表示為:

\[W_{ij}(t+1) := W_{ij}(t) + \alpha x_i x_j \]

即表明神經元xi和xj的連接權重由兩者輸出決定。

盡管已經給出了生物學上的解釋(或者說是啟發),但其實僅看這么一個公式是不可能完全理解的,需要一個例子來說明,到底什么網絡才需要這樣的權值更新。

下面將以離散Hopfield網絡為例說明這種權值更新的具體實現。

Hopfield 網絡

定義和作用

Hopfield網絡是一個有向完全圖(僅僅以我看到的資料去定義,並非嚴謹或者官方定義),是一種遞歸神經網絡。有向完全圖可以理解,每兩個節點之間都有連接,遞歸即一個輸入經過多次訓練最終收斂。本文僅討論離散Hopfield網絡,即節點取值為離散的。(下一篇嘗試用連續Hopfield網絡解決一下旅行商問題)

離散Hopfield網絡的作用是:存儲一個或更多的patterns,並且能夠根據任意的輸入從存儲的這些patterns中將對應的原始數據還原。例如,我們的任務是一個手寫數字分類,我們將數字1對應的圖片作為一種pattern讓網絡存儲起來,如果將數字1遮擋一半,我們希望網絡利用存儲的記憶將這個數字1恢復,並得到最接近的pattern,也就完成了一個分類任務。

訓練

定義輸入\(x_i\)為第i個神經元節點的值,\(W_{ij}\)為第i個和第j和節點之間的權值,則每個樣本作為節點初始化的權值\(W_{ij}\)定義為:

\[W_{ij} = x_i x_j \]

則N個樣本的權值經過N次更新為:

\[W_{ij}(N) = \sum_{n=1}^N x_i(n) x_j(n) \]

因此訓練階段很簡單,僅僅是將所有樣本的信息以權值求和的形式存儲起來,因此,最終的權值存儲的是每個樣本的記憶,而測試階段是需要利用這些權值恢復記憶。
那么這里的權值更新就是利用了Hebb學習規則。

測試

測試階段先用測試樣本初始化節點,利用訓練階段存儲的權值,循環隨機選擇一個節點\(x_i\),將節點值根據下式更新:

\[x_i = sgn(\sum_{j=1}^N W_{ji} x_j) \]

經過若干iter,則所有節點會收斂到一個合適的值。

穩定性分析

當跑完實例后(可以先看下面代碼例子),第一個問題就是:為什么Hopfield網絡能夠收斂,而且這么穩定?而這一切的解釋其實是用一個穩定性指標來決定的。

在Hopfield網絡中,通過引入李亞普洛夫函數作為能量函數,而能量函數就是穩定性指標,當能量達到最低點不變時,系統達到穩定,也就是說,我們需要證明該能量函數是遞減的。

Hopfield網絡中的能量函數定義為:

\[E = (- \frac{1}{2}) \sum_i \sum_j W_{ij}x_ix_j + \sum_j \theta_j x_j \]

其中,\(W_{ij}\)為第i個節點和第j個節點的鏈接權重,\(x_i\)為第i個節點的節點值,\(\theta_i\)為第i個節點的閾值(激活函數sgn可以的輸入可以通過加減閾值調整激活的位置,即\(y = sgn(x - \theta)\)來調整)。

上式能量E可以化為:

\[E = \sum_j\{ [(-\frac{1}{2})\sum_i W_{ij} x_i x_j]+ \theta_jx_j \} \]

現在要定義能量E的變化量,我們假定t時刻到t+1時刻只有第i個神經元發生了變化,則能量變化量可以表示為:

\[\Delta E = \sum_{i,j}(-\frac{1}{2})W_{ij}(\hat{x_i}\hat{x_j} - x_ix_j) + \sum_j\theta_j (\hat{x_j} - x_j) \\=\sum_{k,j}(-\frac{1}{2})W_{kj}(\hat{x_k}\hat{x_j} - x_kx_j) + \sum_j\theta_j (\hat{x_j} - x_j) \\=\sum_k (-\frac{1}{2})W_{ki} (\hat{x_k}\hat{x_i} - x_kx_i) + \sum_j (-\frac{1}{2})W_{ji} (\hat{x_j}\hat{x_i} - x_jx_i) + \sum_j\theta_j (\hat{x_j} - x_j) \\=-\sum_{k(k \neq i)}W_{ki}(\hat{x_k}\hat{x_i} - x_kx_i) + \theta_i(\hat{x_i} - x_i) \\=-(\sum_kW_{ki}x_k - \theta_i)(\hat{x_i} -x_i) \]

這里先解釋一下第二行到第三行的變換,因為只有第i個神經元發生了變化,所以對k和j分四種情況討論

  1. \(k\neq i,j \neq i\),此時沒有任何變化
  2. \(k \neq i,j=i\),此時固定j為i,將i帶入得到左式
  3. \(k=i,j \neq i\),同理將k=i帶入得到中間的式子
  4. \(i=j=k\),此時無變化

然后解釋一下第三行到第四行的變換,因為2. 3. 可通過變量代換結果一致,所以將j代換為k,而最后一項也很簡單,對j求和,而當j不等於i的時候最后一項為0,所以直接把求和去掉。

下面討論下最終結果:

  1. \(\hat{x_i} > x_i\)時,說明由負變正,而\((\sum_kW_{ki}x_k - \theta_i)\)表示的正式第i個節點的輸出(sgn之前,大於0為1,小於0為-1的那個函數),即為正,所以能量變化為負。
  2. \(\hat{x_i} < x_i\),說明由正變負,同理負負得正,能量變化為負。
  3. 由於認為第i個神經元的值變化,所以不討論相等。

綜上,能量變化一直為負,故會朝着能量減小的方向迭代。

所以說明了Hopfield能夠穩定。(注意咯,下面的實驗中隨機挑選神經元用當前狀態其他所有神經元作為輸入,計算當前神經元的結果可能和上一時刻該神經元的狀態相同,如果所有神經元都是如此,那么相當於每次迭代並沒有改變任意神經元的值,此時收斂,能量不變,可以由實驗的圖看出。)

Coding

以一張二值圖為輸入,將每個像素值定義為-1或+1,來初始化節點,探究遮擋一般圖片時利用Hopfied網絡恢復圖像。本文以矩陣運算代替所有循環。

原圖:

masked:

恢復的(iter = 10):

能量變化:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
class Hopfield():
    def __init__(self,size = 64,iter = 10):
        self.iter = iter
        self.size = size
        self.W = np.zeros((size**2,size**2))
        
    def train(self,X): 
        n = self.size**2
        for x in X: # (-1,64*64)
            x = np.reshape(x,(n,1))
            xT = np.reshape(x,(1,n))
            self.W += x*xT/n
        self.W[np.diag_indices_from(self.W)] = 0
    def test_one_frame(self,x):
        n = self.size **2
        x = np.reshape(x,(n,))
        energy = []
        for iter in range(self.iter):
            h = np.zeros((n,))
            for i in range(n):
                i = np.random.randint(n)
                h[i] = self.W[i,:].dot(x)
            x[h>0] = 1
            x[h<0] = -1
            energy.append(self.cal_energy(x))
            
        return np.resize(x,(self.size,self.size)),energy
    def cal_energy(self,x):
        n = self.size **2
        energy = np.sum(self.W.dot(x) * x)
        
        return -0.5 * energy
def show(x):
    img = np.where(x >0,255,0).astype(np.uint8)
    cv.imshow("img",img)
    cv.waitKey(0)

if __name__ =="__main__":
    img = cv.imread("/home/xueaoru/圖片/攝像頭/handsome_boy.jpg",0)
    
    img = cv.resize(img,(64,64))
    x = np.where(img>255/2.5,1,-1)
    x_masked = x.copy()
    x_masked[64//2:,:] = -1
    #show(x_masked)
    model = Hopfield()
    model.train([x])
    y,energy = model.test_one_frame(x_masked)
    show(y)
    plt.plot(energy, label='energy')
    plt.show()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM