Python 如何对矩阵上的函数进行离散优化?

Python 如何对矩阵上的函数进行离散优化?,python,numpy,scipy,mathematical-optimization,discrete-mathematics,Python,Numpy,Scipy,Mathematical Optimization,Discrete Mathematics,我想优化所有30乘30的矩阵,其条目为0或1。我的目标函数是行列式。一种方法是某种随机梯度下降或模拟退火 我看过,但据我所知,它似乎不支持这种优化。看起来很诱人,但似乎需要连续变量 Python中有任何工具可用于这种一般的离散优化吗?我不知道在scipy中有任何直接的离散优化方法。另一种选择是使用from pip或github,它允许您引入自己的移动功能,以便您可以将其限制为在域内移动: import random import numpy as np import simanneal cla

我想优化所有30乘30的矩阵,其条目为0或1。我的目标函数是行列式。一种方法是某种随机梯度下降或模拟退火

我看过,但据我所知,它似乎不支持这种优化。看起来很诱人,但似乎需要连续变量


Python中有任何工具可用于这种一般的离散优化吗?

我不知道在
scipy
中有任何直接的离散优化方法。另一种选择是使用from pip或github,它允许您引入自己的移动功能,以便您可以将其限制为在域内移动:

import random
import numpy as np
import simanneal

class BinaryAnnealer(simanneal.Annealer):

    def move(self):
        # choose a random entry in the matrix
        i = random.randrange(self.state.size)
        # flip the entry 0 <=> 1
        self.state.flat[i] = 1 - self.state.flat[i]

    def energy(self):
        # evaluate the function to minimize
        return -np.linalg.det(self.state)

matrix = np.zeros((5, 5))
opt = BinaryAnnealer(matrix)
print(opt.anneal())
随机导入
将numpy作为np导入
进口西曼耐
二元退火机(simanneal.Annealer)类:
def移动(自我):
#在矩阵中选择一个随机项
i=random.randrange(self.state.size)
#将条目0翻转到1
self.state.flat[i]=1-self.state.flat[i]
def能量(自):
#评估函数以最小化
返回-np.linalg.det(自身状态)
矩阵=np.零((5,5))
opt=二元退火炉(矩阵)
打印(opt.anneal())

我对此进行了一些研究

首先有两件事:1)当矩阵的大小为21x21而不是30x30时,最大值为5600万

但这也是-1,1矩阵的上界,而不是1,0

编辑:更仔细地阅读该链接:

{1的最大行列式,−1} 下表给出了大小为n=21的矩阵。22号是最小的开放式箱子。在表中,D(n)表示最大行列式除以2n−1.等价地,D(n)表示大小为n的{0,1}矩阵的最大行列式−一,

所以这个表可以用于上界,但记住它们被2n除−1.还要注意的是,22是最小的开放案例,因此,试图找到30x30矩阵的最大值还没有完成,甚至还没有接近完成

2) David Zwicker的代码给出3000万答案的原因可能是因为他在最小化。不是最大化

return -np.linalg.det(self.state)
看看他是怎么得到负号的

3) 而且,这个问题的解决空间很大。我计算不同矩阵的数量为2^(30*30),即10^270的顺序。因此,查看每个矩阵都是不可能的,即使查看其中的大多数矩阵也是不可能的

我这里有一些代码(改编自David Zwicker的代码)可以运行,但我不知道它离实际的最大值有多近。在我的电脑上进行1000万次迭代大约需要45分钟,而对于1百万次迭代只需要2分钟。我得到的最大值约为34亿。但我也不知道这离理论上的最大值有多近

import numpy as np
import random
import time

MATRIX_SIZE = 30


def Main():

    startTime = time.time()
    mat = np.zeros((MATRIX_SIZE, MATRIX_SIZE), dtype = int)
    for i in range(MATRIX_SIZE):
        for j in range(MATRIX_SIZE):
            mat[i,j] = random.randrange(2)

    print("Starting matrix:\n", mat)       

    maxDeterminant = 0

    for i in range(1000000):
        # choose a random entry in the matrix
        x = random.randrange(MATRIX_SIZE)
        y = random.randrange(MATRIX_SIZE)

    mat[x,y] = 1 - mat[x,y]

        #print(mat)

        detValue = np.linalg.det(mat)
        if detValue > maxDeterminant:
            maxDeterminant = detValue


    timeTakenStr = "\nTotal time to complete: " + str(round(time.time() - startTime, 4)) + " seconds"
    print(timeTakenStr )
    print(maxDeterminant)


Main()
这有帮助吗?

我认为a在这种情况下可能会很好地工作。下面是一个简单的示例,大致基于他们的示例:

在我的笔记本电脑上运行1000代大约需要40秒,这使我的最小行列式值从-5.7845x108到-6.41504x1011。我对元参数(种群大小、变异率、交叉率等)并没有太多的研究,所以我相信这可能会做得更好


这是一个大大改进的版本,它实现了一个更智能的交叉功能,可以在个体之间交换行或列块,并使用一个变量来保证每个变异步骤产生一个新的配置,并跳过对已经尝试过的配置的决定因素的评估:

import numpy as np
import deap
from deap import algorithms, base, tools
import imp
from cachetools import LRUCache

# used to control the size of the cache so that it doesn't exceed system memory
MAX_MEM_BYTES = 11E9


class GeneticDetMinimizer(object):

    def __init__(self, N=30, popsize=500, cachesize=None, seed=0):

        # an 'individual' consists of an (N^2,) flat numpy array of 0s and 1s
        self.N = N
        self.indiv_size = N * N

        if cachesize is None:
            cachesize = int(np.ceil(8 * MAX_MEM_BYTES / self.indiv_size))

        self._gen = np.random.RandomState(seed)

        # we want the creator module to be local to this instance, since
        # creator.create() directly adds new classes to the module's globals()
        # (yuck!)
        cr = imp.load_module('cr', *imp.find_module('creator', deap.__path__))
        self._cr = cr

        self._cr.create("FitnessMin", base.Fitness, weights=(-1.0,))
        self._cr.create("Individual", np.ndarray, fitness=self._cr.FitnessMin)

        self._tb = base.Toolbox()
        self._tb.register("attr_bool", self.random_bool)
        self._tb.register("individual", tools.initRepeat, self._cr.Individual,
                          self._tb.attr_bool, n=self.indiv_size)

        # the 'population' consists of a list of such individuals
        self._tb.register("population", tools.initRepeat, list,
                          self._tb.individual)
        self._tb.register("evaluate", self.fitness)
        self._tb.register("mate", self.crossover)
        self._tb.register("mutate", self.mutate, rate=0.002)
        self._tb.register("select", tools.selTournament, tournsize=3)

        # create an initial population, and initialize a hall-of-fame to store
        # the best individual
        self.pop = self._tb.population(n=popsize)
        self.hof = tools.HallOfFame(1, similar=np.array_equal)

        # print summary statistics for the population on each iteration
        self.stats = tools.Statistics(lambda ind: ind.fitness.values)
        self.stats.register("avg", np.mean)
        self.stats.register("std", np.std)
        self.stats.register("min", np.min)
        self.stats.register("max", np.max)

        # keep track of configurations that have already been visited
        self.tabu = LRUCache(cachesize)

    def random_bool(self, *args):
        return self._gen.rand(*args) < 0.5

    def mutate(self, ind, rate=1E-3):
        """
        mutate an individual by bit-flipping one or more randomly chosen
        elements
        """
        # ensure that each mutation always introduces a novel configuration
        while np.packbits(ind.astype(np.uint8)).tostring() in self.tabu:
            n_flip = self._gen.binomial(self.indiv_size, rate)
            if not n_flip:
                continue
            idx = self._gen.random_integers(0, self.indiv_size - 1, n_flip)
            ind[idx] = ~ind[idx]
        return ind,

    def fitness(self, individual):
        """
        assigns a fitness value to each individual, based on the determinant
        """
        h = np.packbits(individual.astype(np.uint8)).tostring()
        # look up the fitness for this configuration if it has already been
        # encountered
        if h not in self.tabu:
            fitness = np.linalg.det(individual.reshape(self.N, self.N))
            self.tabu.update({h: fitness})
        else:
            fitness = self.tabu[h]
        return fitness,

    def crossover(self, ind1, ind2):
        """
        randomly swaps a block of rows or columns between two individuals
        """

        cx1 = self._gen.random_integers(0, self.N - 2)
        cx2 = self._gen.random_integers(cx1, self.N - 1)
        ind1.shape = ind2.shape = self.N, self.N

        if self._gen.rand() < 0.5:
            # row swap
            ind1[cx1:cx2, :], ind2[cx1:cx2, :] = (
                ind2[cx1:cx2, :].copy(), ind1[cx1:cx2, :].copy())
        else:
            # column swap
            ind1[:, cx1:cx2], ind2[:, cx1:cx2] = (
                ind2[:, cx1:cx2].copy(), ind1[:, cx1:cx2].copy())

        ind1.shape = ind2.shape = self.indiv_size,

        return ind1, ind2

    def run(self, ngen=int(1E6), mutation_rate=0.3, crossover_rate=0.7):

        pop, log = algorithms.eaSimple(self.pop, self._tb,
                                       cxpb=crossover_rate,
                                       mutpb=mutation_rate,
                                       ngen=ngen,
                                       stats=self.stats,
                                       halloffame=self.hof)
        self.log = log

        return self.hof[0].reshape(self.N, self.N), log

if __name__ == "__main__":
    np.random.seed(0)
    gd = GeneticDetMinimizer(0)
    best, log = gd.run()
将numpy导入为np
进口deap
来自deap导入算法、基础、工具
进口小商品
从缓存工具导入LRUCache
#用于控制缓存的大小,使其不超过系统内存
最大内存字节数=11E9
类GeneticDetMinimizer(对象):
def uuu init uuuu(self,N=30,popsize=500,cachesize=None,seed=0):
#“个体”由0和1组成的(N^2,)平面numpy数组组成
self.N=N
self.indiv_size=N*N
如果cachesize为无:
cachesize=int(np.ceil(8*MAX\u MEM\u字节/self.indivu大小))
self.\u gen=np.random.RandomState(种子)
#我们希望creator模块是此实例的本地模块,因为
#create()直接将新类添加到模块的globals()中
#(恶心!)
cr=imp.load_模块('cr',*imp.find_模块('creator',deap.\uuu路径_uu))
自身。_cr=cr
自我控制创建(“FitnessMin”,基本适应度,权重=(-1.0,))
自我。创建(“个人”,np.ndarray,适合度=自我。_cr.FitnessMin)
self.\u tb=base.Toolbox()
self.\u tb.寄存器(“attr\u bool”,self.random\u bool)
自我登记(“个人”,tools.initRepeat,自我登记,
self.\u tb.attr\u bool,n=self.indiv\u大小)
#“人口”包括此类个人的列表
自我登记(“人口”,tools.initRepeat,list,
自我(个人)
自我登记(“评估”,自我健康)
自我._tb.寄存器(“配对”,自我交叉)
自身寄存器(“变异”,自身变异,速率=0.002)
self._tb.register(“选择”,tools.self,tournsize=3)
#创建初始人口,并初始化要存储的名人堂
#最佳个人
self.pop=self.\u tb.人口(n=popsize)
self.hof=tools.HallOfFame(1,相似=np.array_equal)
#打印每次迭代中总体的摘要统计信息
self.stats=tools.Statistics(lambda ind:ind.fitness.values)
自我统计寄存器(“平均值”,np.平均值)
自统计寄存器(“标准”,np.std)
self.stats.register(“min”,np.min)
self.stats.register(“max”,np.max)
#跟踪已访问的配置
self.tabu=L
import numpy as np
import deap
from deap import algorithms, base, tools
import imp
from cachetools import LRUCache

# used to control the size of the cache so that it doesn't exceed system memory
MAX_MEM_BYTES = 11E9


class GeneticDetMinimizer(object):

    def __init__(self, N=30, popsize=500, cachesize=None, seed=0):

        # an 'individual' consists of an (N^2,) flat numpy array of 0s and 1s
        self.N = N
        self.indiv_size = N * N

        if cachesize is None:
            cachesize = int(np.ceil(8 * MAX_MEM_BYTES / self.indiv_size))

        self._gen = np.random.RandomState(seed)

        # we want the creator module to be local to this instance, since
        # creator.create() directly adds new classes to the module's globals()
        # (yuck!)
        cr = imp.load_module('cr', *imp.find_module('creator', deap.__path__))
        self._cr = cr

        self._cr.create("FitnessMin", base.Fitness, weights=(-1.0,))
        self._cr.create("Individual", np.ndarray, fitness=self._cr.FitnessMin)

        self._tb = base.Toolbox()
        self._tb.register("attr_bool", self.random_bool)
        self._tb.register("individual", tools.initRepeat, self._cr.Individual,
                          self._tb.attr_bool, n=self.indiv_size)

        # the 'population' consists of a list of such individuals
        self._tb.register("population", tools.initRepeat, list,
                          self._tb.individual)
        self._tb.register("evaluate", self.fitness)
        self._tb.register("mate", self.crossover)
        self._tb.register("mutate", self.mutate, rate=0.002)
        self._tb.register("select", tools.selTournament, tournsize=3)

        # create an initial population, and initialize a hall-of-fame to store
        # the best individual
        self.pop = self._tb.population(n=popsize)
        self.hof = tools.HallOfFame(1, similar=np.array_equal)

        # print summary statistics for the population on each iteration
        self.stats = tools.Statistics(lambda ind: ind.fitness.values)
        self.stats.register("avg", np.mean)
        self.stats.register("std", np.std)
        self.stats.register("min", np.min)
        self.stats.register("max", np.max)

        # keep track of configurations that have already been visited
        self.tabu = LRUCache(cachesize)

    def random_bool(self, *args):
        return self._gen.rand(*args) < 0.5

    def mutate(self, ind, rate=1E-3):
        """
        mutate an individual by bit-flipping one or more randomly chosen
        elements
        """
        # ensure that each mutation always introduces a novel configuration
        while np.packbits(ind.astype(np.uint8)).tostring() in self.tabu:
            n_flip = self._gen.binomial(self.indiv_size, rate)
            if not n_flip:
                continue
            idx = self._gen.random_integers(0, self.indiv_size - 1, n_flip)
            ind[idx] = ~ind[idx]
        return ind,

    def fitness(self, individual):
        """
        assigns a fitness value to each individual, based on the determinant
        """
        h = np.packbits(individual.astype(np.uint8)).tostring()
        # look up the fitness for this configuration if it has already been
        # encountered
        if h not in self.tabu:
            fitness = np.linalg.det(individual.reshape(self.N, self.N))
            self.tabu.update({h: fitness})
        else:
            fitness = self.tabu[h]
        return fitness,

    def crossover(self, ind1, ind2):
        """
        randomly swaps a block of rows or columns between two individuals
        """

        cx1 = self._gen.random_integers(0, self.N - 2)
        cx2 = self._gen.random_integers(cx1, self.N - 1)
        ind1.shape = ind2.shape = self.N, self.N

        if self._gen.rand() < 0.5:
            # row swap
            ind1[cx1:cx2, :], ind2[cx1:cx2, :] = (
                ind2[cx1:cx2, :].copy(), ind1[cx1:cx2, :].copy())
        else:
            # column swap
            ind1[:, cx1:cx2], ind2[:, cx1:cx2] = (
                ind2[:, cx1:cx2].copy(), ind1[:, cx1:cx2].copy())

        ind1.shape = ind2.shape = self.indiv_size,

        return ind1, ind2

    def run(self, ngen=int(1E6), mutation_rate=0.3, crossover_rate=0.7):

        pop, log = algorithms.eaSimple(self.pop, self._tb,
                                       cxpb=crossover_rate,
                                       mutpb=mutation_rate,
                                       ngen=ngen,
                                       stats=self.stats,
                                       halloffame=self.hof)
        self.log = log

        return self.hof[0].reshape(self.N, self.N), log

if __name__ == "__main__":
    np.random.seed(0)
    gd = GeneticDetMinimizer(0)
    best, log = gd.run()