[學習筆記] 連續Hopfield網絡解TSP問題


連續Hopfield網絡解TSP問題

上篇講的是離散型Hopfield網絡用於聯想記憶,這篇接上篇講利用連續型Hopfield網絡解TSP問題。

模型

連續型Hopfield網絡與離散型Hopfield網絡結構是一致的,唯一區別就是節點取值連續和在時間上也連續。

連續型的Hopfield網絡一般用一個電路圖來研究:

這里感謝周啟航同學對我在電路方面的指導,才讓我看懂了他認為很簡單的這么個圖。

這是一組放大器電路的結構,神經元的激活函數由運算放大器來模擬,電壓\(u_i\)為激活函數的輸入,所並聯的電阻R決定的是各個神經元之間的連接強度,R和電容C模擬神經元輸出的時間常數,而電流I模擬的是閾值,w模擬神經元間的突觸特性。

由這個電路圖可以得到一組動態方程:

\[C_j\frac{dU_j(t)}{dt} = \sum_i w_{ij}V_i(t) - \frac{U_j(t)}{R_j} + I_j \\V_j(t) = g_j(U_j(t)) \]

上面的方程描述的其實是電流關系(電流的定義\(I = C\frac{dU}{dt}\)),這個很容易看出來,第二個方程描述的是激活函數g。這里用的是sigmoid函數。

其中w對應的是網絡的權重,V是激活后的節點值,U是激活前的節點值。

優化目標

針對TSP問題,其目標函數為最小化能量函數E:

\[E = \frac{A}{2}\sum_i \sum_j \sum_kV_{ij}V_{ik} \\ + \frac{B}{2}\sum_i \sum_j \sum_k V_{ji} V_{ki} \\ + \frac{C}{2} (\sum_i\sum_jV_{ij} - N)^2 \\ +\frac{D}{2} \sum_i\sum_j\sum_k W_{ij} V_{ik}(V_{j,k+1} + V_{j,k-1}) \]

其中V為節點值,\(V_{ij}\)代表第i個城市的的訪問順序,\(V_i\)是個one-hot向量。W是任意兩城市間的距離矩陣,A、B、C、D是超參。

第一行的條件使得對任意第i行的元素其所在行的任意兩元素相乘求和最小,最好的情況是最多只有一個非0元,第二行同理。

第三行使得所有元求和接近N。

第四行是所有路徑求和的表達式,其中需要注意的是k+1如果大於節點數則取1,k-1如果小與1則取N(如果index從1開始的話)。那這個公式咋理解呢?i和j表示第i個和第j個城市,那如果i到j走的話,一定有個k使得\(V_{ik}\)為1,第i行的其他元為0,就意味着j的訪問順序要么是在i之前,要么在其之后,所以考慮k+1和k-1。

在“Theories on the Hopfield neural networks”這篇論文中對上式又進行了改進,提高了收斂速度。

其改進后的公式為目標函數為:

\[E = \frac{A}{2}\sum_i(\sum_j V_{ij} -1)^2 + \frac{A}{2}\sum_j(\sum_i V_{ij} -1)^2 + \frac{D}{2}\sum_i\sum_j\sum_kW_{ij}V_{ik}V_{j,k+1} \]

改進后的公式更易於理解。前兩項是保證每行每列只有一個1,最后一項是路程。

優化過程

由於在初始化階段我們就能確定網絡的權重,即任意兩城市之間的距離,也就是權重不需要學習直接可以初始化。那么優化過程更新的其實是節點,我們先將節點按照如下規則進行初始化:

\[U_{ij} = \frac{1}{2}U_0 ln(N-1)+ \delta_{ij} \]

其中\(\delta_{ij}\)為-1到+1的隨機值。\(U_0\)為超參。

然后就是怎么取更新節點了。

節點的更新需要利用下面的公式:

\[\frac{dU_{ij}}{dt} = - \frac{\partial E}{\partial V_{ij}} \]

其實對於電壓對時間微分為啥等於右邊我也不是很清楚,很多論文直接搬上來的,我無法解釋,所以也直接搬過來。

帶入能量定義式:

\[\frac{dU_{ij}}{dt} = - \frac{\partial E}{\partial V_{ij}} = -A(\sum_kV_{ik}-1) - A(\sum_kV_{kj}-1) - D\sum_kd_{ik}V_{k,j+1} \]

然后更新節點:

\[U_{ij}(t+1) = U_{ij}(t) + \frac{dU_{ij}}{dt} \Delta t \]

然后sigmoid激活:

\[V_{ij} = \frac{1}{2}(1+ tanh(\frac{U_{ij}}{U_0})) \]

Coding

能量變化:

import numpy as np
import matplotlib.pyplot as plt 
class HopfieldTSPSolver():
    def __init__(self,cities):
        self.cities = cities
        self.n = cities.shape[0]
        self.u0 = 0.02
        self.delta_t = 1e-4
        self.A = 200
        self.D = 100
        self.W = np.zeros((self.n,self.n))
        self.U = np.zeros((self.n,self.n))
        self.V = np.zeros((self.n,self.n))
        self.init_weight()
        self.init_node()
    def init_weight(self,):
        for i in range(self.n):
            for j in range(self.n):
                self.W[i,j] = np.sqrt(np.sum((self.cities[i] - self.cities[j])**2))
    def init_node(self):
        for i in range(self.n):
            for j in range(self.n):
                self.U[i,j] = 0.5 * self.u0 * np.log(self.n-1) + np.random.random() * 2-1
    
    def diff(self,i,j):
        t = j+1 if j+1 < self.n else 0
        return -self.A*(np.sum(self.V[i]) -1) - self.A*(np.sum(self.V[:,j]) -1) - self.D * self.W[i,:].dot(self.V[:,t])    
    def get_energy(self):
        energy = 0.
        for i in range(self.n):
            energy += 0.5 * self.A * (np.sum(self.V[i,:]) - 1)**2
            energy += 0.5 * self.A * (np.sum(self.V[:,i]) - 1)**2
            for j in range(self.n):
                for k in range(self.n):
                    t = k+1 if k+1 < self.n else 0
                    energy += 0.5 * self.D * self.W[i,j] * self.V[i,k] * self.V[j,t]
        return energy
    def check(self):
        pos = np.where(self.V<0.2,0,1)
        flag = True
        if np.sum(pos) != self.n:
            flag = False
        for i in range(self.n):
            if np.sum(pos[:,i]) != 1:
                flag = False
            if np.sum(pos[i,:]) != 1:
                flag = False
        return flag
    def __call__(self):
        running_energy = []
        iter = 0
        while not self.check():
            iter +=1
            for i in range(self.n):
                for j in range(self.n):
                    self.U[i,j] += self.delta_t * self.diff(i,j)
            self.V = 0.5 * (1 + np.tanh(self.U / self.u0))
            energy = self.get_energy()
            running_energy.append(energy)
        return running_energy,np.where(self.V<0.2,0,1),iter
if __name__ == "__main__":
    cities = np.array([[2,6],[2,4],[1,3],[4,6],[5,5],[4,4],[6,4],[3,2]])
    solver = HopfieldTSPSolver(cities)
    energy,answer,iter = solver()
    print(answer)
    print(iter)
    plt.plot(energy)
    
    plt.show()
    


輸出:

[[0 0 1 0 0 0 0 0]
 [1 0 0 0 0 0 0 0]
 [0 1 0 0 0 0 0 0]
 [0 0 0 1 0 0 0 0]
 [0 0 0 0 0 1 0 0]
 [0 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 1 0]
 [0 0 0 0 0 0 0 1]]

1918


免責聲明!

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



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