蚁群算法解决任务调度问题-Python

  蚁群算法是一种启发式优化算法,也是一种智能算法、进化计算。和遗传算法、粒子群算法相比,蚁群算法所优化的内容是拓扑序(或者路径)的信息素浓度,而遗传算法、粒子群算法优化的是某一个个体(解向量)。

  例如TSP问题,30个城市之间有900个对应关系,30*15/2=435条路径,在蚂蚁经过之后,都留下了信息素,而某一个拓扑序指的是一个解向量,旅行商的回路应是首尾相连的30条路径,蚂蚁在走路的时候会考虑到能走的路径上的信息素浓度,然后选择一个拓扑序作为新解。例如任务车间调度问题,假如有8个机器和10个任务,则一共有80个对应关系,每个对应关系看作一个路径,会有信息素的标记,而解向量是一个长度为10的向量,蚂蚁在走路的时候,会选择每一个任务到所有机器的对应关系,考虑信息素的浓度选择其中一个,由此选择10个任务各自放在哪一个机器上,作为新解。

  蚁群算法天生适合解决路径问题、指派问题,效果通常比粒子群等要好。相比于模拟退火算法,计算更稳定。相比于粒子群算法收敛性更好。

 

对于任务调度问题,需要定义模型对象:

  节点和云任务。节点VM具有节点编号id、两种资源总量和两种资源的已占有量。因此其剩余空间是总量capacity减去已占有量supply。

class Cloudlet:
    def __init__(self, cpu_demand: float, mem_demand: float):
        self.cpu_demand = cpu_demand
        self.mem_demand = mem_demand


class VM:
    def __init__(self, vm_id: int, cpu_supply: float, cpu_velocity: float, mem_supply: float, mem_capacity: float):
        self.id = vm_id
        self.cpu_supply = cpu_supply
        self.cpu_velocity = cpu_velocity
        self.mem_supply = mem_supply
        self.mem_capacity = mem_capacity

 

然后定义蚁群算法解决任务调度问题的类。

  初始化属性有云任务列表,节点列表,蚂蚁种群数量,迭代次数,以及人物的路径数和最优策略记录。

  函数有生成新解的函数gen_topo_jobs(),评估解的负载均衡值和计算适应度evaluate_particle、calculate_fitness,更新信息素update_topo以及蚁群算法主流程schedulet_main。后面逐一讲解

import numpy as np
from typing import List
from matplotlib import pyplot as plt


class ACScheduler:
    def __init__(self, cloudlets, vms, population_number=100, times=500):
        self.cloudlets = cloudlets
        self.vms = vms
        self.cloudlet_num = len(cloudlets)  # 任务数量也就是粒子长度
        self.machine_number = len(vms)  # 机器数量

        self.population_number = population_number  # 种群数量
        self.times = times  # 迭代代数

        # 表示任务选择的机器的信息素
        self.topo_phs = [[100 for _ in range(self.machine_number)] for _ in range(self.cloudlet_num)]
        # 最优策略
        self.best_topo = None

    # 生成新的解向量--根据信息素浓度生成
    def gen_topo_jobs(self):
        pass

    # 与算法无关的函数--评估当前解向量
    def evaluate_particle(self, topo_jobs: List[int]) -> int:
        pass

    # 与算法无关的函数--计算适应度
    def calculate_fitness(self, topo_jobs: List[int]) -> float:
        pass

    # 更新信息素
    def update_topo(self):
        pass

    def scheduler_main(self):
        pass


if __name__ == '__main__':
    nodes = [VM(0, 0.862, 950, 950, 1719), VM(1, 0.962, 2, 950, 1719), VM(2, 1.062, 2, 1500, 1719)]
    lets = [Cloudlet(0.15, 50), Cloudlet(0.05, 100), Cloudlet(0.2, 60),
            Cloudlet(0.01, 70), Cloudlet(0.04, 80), Cloudlet(0.07, 20),
            Cloudlet(0.14, 150), Cloudlet(0.15, 200), Cloudlet(0.03, 40), Cloudlet(0.06, 90)]
    ac = ACScheduler(lets, nodes, times=150)
    res = ac.scheduler_main()
    i = 0
    for _ in ac.best_topo:
        print("任务:", i, " 放置到机器", ac.best_topo[i], "上执行")
        i += 1

 1. 生成新的解向量gen_topo_jobs(self)

  初始化解向量为-1,然后逐个根据信息素的浓度选择任务的对应的机器,以此生成新的解向量返回

    # 生成新的解向量--根据信息素浓度生成
    def gen_topo_jobs(self):
        ans = [-1 for _ in range(self.cloudlet_num)]
        node_free = [node_id for node_id in range(self.machine_number)]
        for let in range(self.cloudlet_num):
            ph_sum = np.sum(list(map(lambda j: self.topo_phs[let][j], node_free)))
            test_val = 0
            rand_ph = np.random.uniform(0, ph_sum)
            for node_id in node_free:
                test_val += self.topo_phs[let][node_id]
                if rand_ph <= test_val:
                    ans[let] = node_id
                    break
        return ans

2. 评估解向量然后计算适应度

  把解向量的策略利用到集群中,然后计算每个节点每个资源的利用率,然后对所有节点取标准差,各个资源的利用率标准差加和作为负载均衡度,越小越均衡。

  由于适应度定义为越大越好,因此评估结果取倒数作为适应度。

    # 与算法无关的函数--评估当前解向量
    def evaluate_particle(self, topo_jobs: List[int]) -> int:
        cpu_util = np.zeros(self.machine_number)
        mem_util = np.zeros(self.machine_number)
        for i in range(len(self.vms)):
            cpu_util[i] = self.vms[i].cpu_supply
            mem_util[i] = self.vms[i].mem_supply

        for i in range(self.cloudlet_num):
            cpu_util[topo_jobs[i]] += self.cloudlets[i].cpu_demand
            mem_util[topo_jobs[i]] += self.cloudlets[i].mem_demand

        for i in range(self.machine_number):
            if cpu_util[i] > self.vms[i].cpu_velocity:
                return 100
            if mem_util[i] > self.vms[i].mem_capacity:
                return 100

        for i in range(self.machine_number):
            cpu_util[i] /= self.vms[i].cpu_velocity
            mem_util[i] /= self.vms[i].mem_capacity

        return np.std(cpu_util, ddof=1) + np.std(mem_util, ddof=1)

    # 与算法无关的函数--计算适应度
    def calculate_fitness(self, topo_jobs: List[int]) -> float:
        return 1 / self.evaluate_particle(topo_jobs)

3.  更新信息素:有蚂蚁经过的路径信息素加倍,否则挥发减半

     # 更新信息素
     def update_topo(self):
        for i in range(self.cloudlet_num):
            for j in range(self.machine_number):
                if j == self.best_topo[i]:
                    self.topo_phs[i][j] *= 2
                else:
                    self.topo_phs[i][j] *= 0.5

 

4. 主流程:迭代求解

    def scheduler_main(self):
        results = [0 for _ in range(self.times)]
        fitness = 0

        for it in range(self.times):
            best_time = 0

            for ant_id in range(self.population_number):
                topo_jobs = self.gen_topo_jobs()
                fitness = self.calculate_fitness(topo_jobs)
                if fitness > best_time:
                    self.best_topo = topo_jobs
                    best_time = fitness
            assert self.best_topo is not None
            self.update_topo()
            results[it] = best_time
            if it % 10 == 0:
                print("ACO iter: ", it, " / ", self.times, ", 适应度: ", fitness)
        plt.plot(range(self.times), results)
        plt.xlabel("迭代次数")
        plt.ylabel("适应度")
        plt.rcParams['font.sans-serif'] = ['KaiTi']  # 指定默认字体
        plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
        plt.title("蚁群算法求解云任务调度负载均衡问题")
        # plt.savefig('img/ACScheduler-1.1_0.9-popu100-iter200.png', dpi=300,
        #             format='png')  # bbox_inches="tight"解决X轴时间两个字不被保存的问题
        plt.show()
        return results

 

测试数据:

if __name__ == '__main__':
    # 第四组数据data9

    nodes = [
        VM(0, 0.662, 2, 620, 2223),
        VM(1, 0.662, 2, 1100, 2223),
        VM(2, 1.662, 2, 720, 2223),
        VM(3, 0.662, 2, 1100, 2223),
        VM(4, 0.662, 2, 620, 2223),
        VM(5, 0.562, 2, 650, 2223),
        VM(6, 0.562, 2, 620, 2223),
        VM(7, 0.462, 2, 440, 2223),  # 8
    ]
    lets = [
        Cloudlet(0.133364, 272.435810),
        Cloudlet(0.226357, 141.126392),
        Cloudlet(0.084122, 7.183883),
        Cloudlet(0.029290, 96.658838),
        Cloudlet(0.027560, 247.821058),
        Cloudlet(0.191912, 80.636804),
        Cloudlet(0.134658, 220.702279),
        Cloudlet(0.133052, 163.046071),
        Cloudlet(0.272010, 253.477271),
        Cloudlet(0.175000, 19.409176),
        Cloudlet(0.166933, 140.880123),
        Cloudlet(0.286495, 71.288800),
        Cloudlet(0.080714, 354.839232),
        Cloudlet(0.209842, 211.351191),
        Cloudlet(0.221753, 249.500490),
        Cloudlet(0.128952, 81.599575),
        Cloudlet(0.168469, 122.216016),
        Cloudlet(0.049628, 135.728968),
        Cloudlet(0.051167, 230.172949),
        Cloudlet(0.158938, 135.356776),
        Cloudlet(0.212047, 202.830773),
        Cloudlet(0.372328, 13.145747),
        Cloudlet(0.092549, 130.122476),
        Cloudlet(0.166031, 97.761267),
        Cloudlet(0.142820, 45.852985),
        Cloudlet(0.016367, 189.495519),
        Cloudlet(0.112156, 173.926518),
        Cloudlet(0.004466, 156.806505),
        Cloudlet(0.222208, 62.619918),
        Cloudlet(0.073526, 232.175486),
        Cloudlet(0.158527, 178.624649),
        Cloudlet(0.103075, 133.896667),
        Cloudlet(0.176026, 156.076929),
        Cloudlet(0.098275, 136.450544),
        Cloudlet(0.192065, 33.923922),
        Cloudlet(0.213519, 134.820860),

    ]

    ac = ACScheduler(lets, nodes, times=150)
    res = ac.scheduler_main()
    i = 0
    for _ in ac.best_topo:
        print("任务:", i, " 放置到机器", ac.best_topo[i], "上执行")
        i += 1

求解结果:

ACO iter: 0 / 150 , 适应度: 0.01
ACO iter: 10 / 150 , 适应度: 4.0408235554621665
ACO iter: 20 / 150 , 适应度: 4.754201711827778
ACO iter: 30 / 150 , 适应度: 4.754201711827778
ACO iter: 40 / 150 , 适应度: 4.754201711827778
ACO iter: 50 / 150 , 适应度: 4.754201711827778
ACO iter: 60 / 150 , 适应度: 4.754201711827778
ACO iter: 70 / 150 , 适应度: 4.754201711827778
ACO iter: 80 / 150 , 适应度: 4.754201711827778
ACO iter: 90 / 150 , 适应度: 4.754201711827778
ACO iter: 100 / 150 , 适应度: 4.754201711827778
ACO iter: 110 / 150 , 适应度: 4.754201711827778
ACO iter: 120 / 150 , 适应度: 4.754201711827778
ACO iter: 130 / 150 , 适应度: 4.754201711827778
ACO iter: 140 / 150 , 适应度: 4.754201711827778

posted @ 2021-03-27 14:22  倦鸟已归时  阅读(3345)  评论(2编辑  收藏  举报