目录

  • 基于CHNN求解TSP问题
  • CHNN
  • CHNN的网络结构
  • CHNN状态方程
  • CHNN的能量函数
  • TSP问题
  • 基于CHNN求解TSP问题的思路
  • 1. 分析问题,将TSP的状态用数学符号描述出来
  • 1.1 TSP问题描述
  • 1.2 TSP问题的约束条件
  • 1.3 TSP问题的目标函数
  • 2. 构造网络能量函数
  • 3. 优化网络能量函数
  • 4. CHNN的动态方程
  • 5. 状态更新方程
  • python代码实现


基于CHNN求解TSP问题

CHNN

CHNN是连续型Hopfield神经网络。CHNN是以模拟量作为网络的输入和输出,而且各个神经元之间的工作方式是并行的。因此相对于DHNN,CHNN更接近于生物神经网络。CHNN更适合于求解组合优化问题。

CHNN的网络结构

CHNN的神经网络结构可以等效为放大电子电路。每一个神经元可以等效为一个电路放大器元件。CHNN的等效电路拓扑图如下:

titanic 神经网络 神经网络解决tsp问题_python


其中:

titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_02 是放大电子元件的输入电压,对应于CHNN神经网络中神经元的输入,包括恒定的外部电流和其他电子元件的反馈连接。

titanic 神经网络 神经网络解决tsp问题_神经网络_03是放大电子元件的输出电压,对应于CHNN神经网络中神经元的输出,其输出有正负值,正值代表兴奋,负值代表抑制。

运算放大器titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_04表示第titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_04个神经元。

CHNN状态方程

设电容C的两端电压为titanic 神经网络 神经网络解决tsp问题_算法_06,存储的电荷量为Q,则有titanic 神经网络 神经网络解决tsp问题_神经网络_07
则经过电容C的电流为:titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_08
根据基尔霍夫电流定律,CHNN等效电路的电流关系为:
titanic 神经网络 神经网络解决tsp问题_神经网络_09
titanic 神经网络 神经网络解决tsp问题_python_10表示神经网络中神经元之间的连接权重:titanic 神经网络 神经网络解决tsp问题_神经网络_11
则电流关系式可以化简为
titanic 神经网络 神经网络解决tsp问题_算法_12
上式就是CHNN神经网络的状态方程,其中titanic 神经网络 神经网络解决tsp问题_python_13,即输入电压是输出电压的非线性映射。titanic 神经网络 神经网络解决tsp问题_python_14是S型激励函数

CHNN的能量函数

由于CHNN的神经网络模型中的权重是不变的,并且不需要学习,因此我们采用能量函数的方式来衡量网路的稳定性。
CHNN网络能量函数公式如下:
titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_15

TSP问题

旅行商(TSP)问题是人工智能领域一个组合优化问题。

问题描述:有一个推销员,要到n个城市推销商品,他要找出一个包含所有n个城市的具有最短路程的环路。

基于CHNN求解TSP问题的思路

1. 分析问题,将TSP的状态用数学符号描述出来

1.1 TSP问题描述

我们假设一共有5个城市,A、B、C、D、E。我们使用一个titanic 神经网络 神经网络解决tsp问题_状态方程_16矩阵表示每一种走法(状态)。

城市

第一步

第二步

第三步

第四步

第五步

A

0

0

1

0

0

B

1

0

0

0

0

C

0

1

0

0

0

D

0

0

0

1

0

E

0

0

0

0

1

如上表所示,titanic 神经网络 神经网络解决tsp问题_算法_17 表示第i步在第x城市, titanic 神经网络 神经网络解决tsp问题_神经网络_18 表示第i步不在第x城市 。
故,每个元素的取值为0或1
注:x,y表示城市,i,j表示的第几步

1.2 TSP问题的约束条件

TSP问题有三个约束条件

  1. 一个城市只去一次 =====> 每一行只有一个‘1’
    titanic 神经网络 神经网络解决tsp问题_神经网络_19
  2. titanic 神经网络 神经网络解决tsp问题_算法_20

  3. titanic 神经网络 神经网络解决tsp问题_算法_21
  4. 一次只去一个城市 =====> 每一列只有一个‘1’
    titanic 神经网络 神经网络解决tsp问题_算法_22
    titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_23
  5. 一共有n个城市 =====> 矩阵之和为n titanic 神经网络 神经网络解决tsp问题_神经网络_24
1.3 TSP问题的目标函数

我们想要得到的是访问n个城市的总距离最小。我们使用titanic 神经网络 神经网络解决tsp问题_titanic 神经网络_25表示titanic 神经网络 神经网络解决tsp问题_神经网络_26titanic 神经网络 神经网络解决tsp问题_算法_27两个城市的距离,那么顺序访问城市titanic 神经网络 神经网络解决tsp问题_神经网络_26和城市titanic 神经网络 神经网络解决tsp问题_算法_27,有titanic 神经网络 神经网络解决tsp问题_python_30至少有一个为0。

那么顺序访问titanic 神经网络 神经网络解决tsp问题_神经网络_26titanic 神经网络 神经网络解决tsp问题_算法_27的所有可能的路径有
titanic 神经网络 神经网络解决tsp问题_python_33

那么顺序访问所有城市的可能的路径有
titanic 神经网络 神经网络解决tsp问题_状态方程_34
我们想要得到总距离最小,即上式取最小值。

2. 构造网络能量函数

现在我们就把求解TSP问题转化为求解有三个约束条件下求解最小值的最优化问题。
我们根据三个约束条件和一个优化目标函数来构造网络能量函数
titanic 神经网络 神经网络解决tsp问题_状态方程_35
其中A、B、C、D是常数。
当E达到最小值的时候,就选择了最优路径。

3. 优化网络能量函数

在’Theories on the Hopfield neural networks’这篇论文中对上式进行了改进,提高了收敛速度。改进后的公式如下:
titanic 神经网络 神经网络解决tsp问题_python_36

4. CHNN的动态方程

根据优化后的能量函数可以得到动态方程如下:
titanic 神经网络 神经网络解决tsp问题_算法_37

5. 状态更新方程

输入状态的更新方程如下:
titanic 神经网络 神经网络解决tsp问题_算法_38
输出状态的更新方程为(激活函数使用sigmoid函数):
titanic 神经网络 神经网络解决tsp问题_python_39

python代码实现

下面我们就可以根据以上过程来实现代码

import numpy as np
import random
from math import sqrt, log, tanh, exp
from matplotlib import pyplot as plt
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s[line:%(lineno)d]- %(levelname)s:%(message)s')

# 动态方程中的两个系数
A = 300
D = 200
U0 = 0.1
max_iter = 120  # 最大迭代次数
step = 0.0001  # 步长


def d_u(state_v, distance, n):
    """ 动态方程
    :param state_v: 输出矩阵
    :param distance: 城市之间的距离矩阵
    :param n: 城市的个数
    :return: 返回 动态方程的矩阵
    """
    a = np.sum(state_v, axis=0) - 1  # 得到行向量,把每一列的元素都加到第一行  对前一个下标求和
    b = np.sum(state_v, axis=1) - 1  # 得到列向量(列向量的值,行向量的形式),把每一行的元素都加到第一列  对后一个下标求和
    # 把上述两个向量扩展为两个矩阵
    x = np.array([[0.0 for i in range(n)] for i in range(n)])   # 用来扩展a向量
    y = np.array([[0.0 for i in range(n)] for i in range(n)])   # 用来扩展b向量
    # 扩展a向量为一个矩阵
    for i in range(n):
        for j in range(n):
            x[i, j] = a[j]  # x的每一行都和a向量相同,相当于x矩阵是n行a
    # 扩展b向量为一个矩阵
    for i in range(n):
        for j in range(n):
            y[j, i] = b[j]  # y的每一列都和b相同,相当于y矩阵是n列b,但是b在python里的形式是行向量

    # 创建C矩阵: 将V矩阵的第一列移到最后一列,并与距离矩阵相乘??? 为什么要移动第一列
    c = np.array([[0.0 for i in range(n)] for i in range(n)])
    c[:, 0:n-1] = state_v[:, 1:n]
    c[:, n-1] = state_v[:, 0]  # 将V的第一列放到最后一列
    c = np.dot(distance, c)  # 距离矩阵乘以V
    return -A * (x + y) - D * c


def energy(state_v, distance, n):
    """ 能量函数
    :param state_v: 输出矩阵
    :param distance: 距离矩阵
    :param n: 城市的个数
    :return: 能量函数的值,是一个数
    """
    a = sum([x*x for x in (np.sum(state_v, axis=0)-1)])
    b = sum([x*x for x in (np.sum(state_v, axis=1)-1)])
    c = np.array([[0.0 for i in range(n)] for i in range(n)])
    c[:, 0:n-1] = state_v[:, 1:n]
    c[:, n-1] = state_v[:, 0]  # 将V的第一列放到最后一列
    c = np.sum(np.sum(np.multiply(state_v, np.dot(distance, c)), axis=0))
    return 0.5 * (A * (a + b) + D * c)

def get_v(state_v, n):
    ''' 得到稳定状态下的矩阵信息
    :param state_v: 稳定输出矩阵
    :param n: 城市的个数
    :return:
    '''
    (row, col) = state_v.shape
    V_H = np.array([0.0 for i in range(row)])  # 用来存放每一列的最大值,共row行
    V_W = np.array([0 for i in range(row)])  # 用来存放每一列最大值的行号
    for i in range(n):
        for j in range(n):
            if V[j, i] > V_H[i]:
                V_H[i] = V[j, i]
                V_W[i] = j
    # 创建一个数组将V中每列最大值的位置设为1,其他设为0
    V_1 = np.array([[0 for i in range(row)] for i in range(col)])
    for i in range(col):
        V_1[V_W[i], i] = 1
    return V_H, V_W, V_1

# 归一化处理用到的函数
def normalization(energy_all):
    # return [float(x-energy_all.mean())/energy_all.std() for x in energy_all]  #z-score
    return [log(x)/log(np.max(energy_all)) for x in energy_all]   #log函数转化

def draw_enegry(energy_all):
    ''' 画出能量函数的曲线图
    :param energy_all: 能量函数数组
    :return: 无返回值
    '''
    y = normalization(energy_all)  # 对能量函数值进行归一化处理
    plt.plot(y)
    plt.xlabel('迭代次数', loc='right')
    plt.ylabel('能量函数值', loc='top')
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体设置-黑体
    plt.show()

def draw_rode(state_v, V_1, V_W, n):
    ''' 画出路线图
    :param state_v: 稳定输出矩阵
    :param V_1: 0-1状态矩阵
    :param V_W: 每列最大值所在的行向量
    :param n: 城市个数
    :return:
    '''
    (row, col) = state_v.shape
    V_1H = np.array([0.0 for i in range(n)])
    V_1W = np.array([0 for i in range(n)])
    for i in range(col):
        V_1[V_W[i], i] = 1
    for i in range(n):
        for j in range(n):
            if V_1[j, i] > V_1H[i]:
                V_1H[i] = V_1[j, i]
                V_1W[i] = j
    # 随机一种路径和优化后的路径进行比较
    city_i = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    V_0W = random.sample(list(city_i), 10)
    # 创建两个城市矩阵,将他们按照我们所给的初始顺序和最终顺序进行排序
    city_b = np.array(
        [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0],
         [0.0, 0.0]])
    city_f = np.array(
        [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0],
         [0.0, 0.0]])
    k = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    k = [city[i] for i in V_0W]
    for i in range(n):
        city_b[i] = k[i]
    j = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    j = [city[i] for i in V_1W]
    for i in range(n):
        city_f[i] = j[i]
    # 下面将坐标点的X,Y轴数值分别提取进行画图
    X_b = []
    Y_b = []
    X_f = []
    Y_f = []
    for i in range(n):
        X_b.append(city_b[i][0])
        Y_b.append(city_b[i][1])
        X_f.append(city_f[i][0])
        Y_f.append(city_f[i][1])
    # 将第一个点加入,因为要回到初始点
    X_b.append(city_b[0][0])
    Y_b.append(city_b[0][1])
    X_f.append(city_f[0][0])
    Y_f.append(city_f[0][1])
    plt.plot(X_b, Y_b, 'r', X_f, Y_f, '-.b')   # 红线是随机,蓝线是优化后的结果
    plt.scatter(X_f, Y_f)
    plt.legend(labels=['随机路线', '优化后路线'], loc='lower right', fontsize=6)    # 图例
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体设置-黑体
    plt.show()


if __name__ == '__main__':
    city = np.array([
        [0.7000, 0.2000], [0.4000, 0.3000], [0.5000, 0.8000],
        [0.3000, 0.4000], [0.1000, 0.9000], [0.9000, 0.4000],
        [0.8000, 0.6000], [0.6000, 0.9000], [0.3000, 0.6000],
        [0.2000, 0.8000]])  # 10个城市的坐标
    n = len(city)  # 城市的个数

    # 计算距离矩阵
    distance = np.array([[0.0 for i in range(n)] for i in range(n)])
    for i in range(n):
        for j in range(n):
            a = sqrt((city[i, 0]-city[j, 0]) ** 2 + (city[i, 1]-city[j, 1]) ** 2)
            distance[i, j] = a
            distance[j, i] = a

    # 随机给定网络初始状态矩阵
    delta = 2 * (np.random.random((n, n))) - 1   # 随机产生一个n*n的矩阵,用来随机初始化状态矩阵
    U = U0 * log(n-1) + delta  # 随机定义一个初始输入矩阵
    V = np.array([[0.0 for i in range(n)] for i in range(n)])
    for i in range(n):
        for j in range(n):
            # V[i, j] = (1+2/(1+exp(-2*(U[i, j]/U0)))-1)/2
            V[i, j] = 0.5 * (1 + tanh(U[i, j] / U0))   # 输出矩阵、状态矩阵

    # 创建一个向量,用来存储每一步的能量值
    energy_all = np.array([0.0 for i in range(max_iter)])

    # 迭代
    for k in range(max_iter):
        du = d_u(V, distance, n)  # 动态方程、梯度
        U = U + du * step  # 更新输入矩阵
        for i in range(n):
            for j in range(n):
                V[i, j] = 0.5 * (1 + tanh(U[i, j] / U0))  # 输出矩阵、状态矩阵
                # V[i, j] = (1 + 2 / (1 + exp(-2 * (U[i, j] / U0))) - 1) / 2
        energy_all[k] = energy(V, distance, n)  # 求能量函数的值,放入向量中
    Vs = get_v(V, n)
    draw_enegry(energy_all)  # 画出能量函数图
    draw_rode(V, V_W=Vs[1], V_1=Vs[2], n=n)  # 画出路线图