1. 程式人生 > >Capacitated Facility Location Problem

Capacitated Facility Location Problem

文章目錄

Problem Description

Suppose there are n facilities and m customers. We wish to choose:

(1) which of the n facilities to open

(2) the assignment of customers to facilities

(3) The objective is to minimize the sum of the opening cost and the assignment cost.

(4) The total demand assigned to a facility must not exceed its capacity.

遺傳演算法(genetic algorithm)

遺傳演算法是受遺傳學中自然選擇和遺傳機制啟發發展起來的一種搜尋演算法。基本思想是使用模擬生物和人類進化的方法求解複雜的優化問題,因而也稱作為模擬進化優化演算法。模擬進化優化演算法在計算原理上是自適應的,結構上是並行的,而且模仿了人的智慧處理特徵,因而稱為人工智慧的一個重要研究領域。

基本定義

個體(individual): 個體是一個數據結構,用來描述基本的遺傳結構。例如,用0、1組成的串可以表示個體。這樣的串叫染色體,其中每個0或1叫等位基因。這樣的一個串於某個個體相關聯,則稱為該個體的基因型。

適應性(fitness): 每個個體有一對應的適應值。再優化問題中,適應值來自於一個估計函式。

群體(population): 由個體組成的集合。

遺傳操作: 作用於群體而產生新的群體。標準的代遺傳操作一般包括選擇(selection)、複製(reproduction)、交叉(crossover或重組recombination)及變異(mutation)3中基本形式。

基本流程

以此題目為例,流程圖如下:

在這裡插入圖片描述

初始化群體(第一代)

每個個體中每個基因隨機生成。(customer隨機選擇一個facility)

void firstGeneration() {
    int j;
    for(int k=0; k<individuals; k++) {
        vector<customer>customersTemp(customers);
        vector<facility>facilitiesTemp(facilities);
        // 生成一個個體
        for(int i=0; i<m; i++) {
            j = rand() % n;
            while(facilitiesTemp[j].getRemain() - customersTemp[i].getDemand() < 0) {
                j = (j+1) % n;
            }
            facilitiesTemp[j].setRemain(facilitiesTemp[j].getRemain() - customersTemp[i].getDemand());
            customersTemp[i].setFacilityID(j);
        }
        customersGeneration.push_back(customersTemp);
        facilitiesGeneration.push_back(facilitiesTemp);
    }
}

適應值函式(evaluate)

在此問題中,適應值即為 the sum of the opening cost and the assignment cost ,所以在適應值函式中,我們根據facility的開啟狀態(我使用facility剩餘容量與初始容量比較的結果)以及分配(assignment)分配情況來計算代價值(cost)。其中cost越小,適應值越大(對於此題目我們可以取倒數)。

double evaluate(vector<customer>&customers, vector<facility>&facilities) {
    // 計算facility opencost
    double cost = 0;
    for(int j=0; j<n; j++) {
        if(facilities[j].getRemain() < facilities[j].getCapacity()) {
            cost += facilities[j].getOpencost();
        }
    }
    // 計算assignment
    for(int i=0; i<m; i++) {
        cost += assignment[customers[i].getFacilityID()][i];
    }
    return cost;
}

選擇函式(selection)

選擇即從當前群體中選出個體以生成交配池(mating pool)的過程。所選出的這些個體具有良好的特徵,一邊選出優良的後代。

  • 基於適應值比例的選擇

    • 繁殖池選擇
    • 輪盤賭選擇
    • Boltzmann選擇

    其中,輪盤賭選擇使用較多,主要介紹一下輪盤賭選擇:該策略是先將個體的相對適應值 f i f i \frac{f_i}{\sum{f_i}} 記為 p i p_i ,其中 f i f_i 是群體中的第i成員的適應值;然後根據選擇概率{ p i p_i ,i=1,2,3……,N},按下圖所示將圓盤分為N份,其中第i扇形的中心較為 2 π p i 2\pi p_i
    在這裡插入圖片描述

    在進行選擇是,可以假象轉動如上圖所示的圓盤,若某參照點落入到第i個扇形內,則選擇個體i。這種選擇策略可以如下實現:現生成一個[0,1]內的隨機數,若 p 1 + p 2 + + p i 1 &lt; r p 1 + p 2 + + p i p_1+p_2+……+p_{i-1}&lt;r≤p_1+p_2+……+p_i ,則選擇個體i。顯然,小扇區的面積越大,參照點落入其中的概率也越大,即個體的適應值越大,它被選擇到的機會也就越多,其基因被遺傳到下一代的可能性也越大。

  • 基於排名的選擇(此部分內容可以自行百度)

    • 線性排名選擇
    • 非線性排名選擇
  • 基於區域性競爭機制的選擇

    • 錦標賽選擇(tournament selection)。選擇時,先隨機地在群體中選擇k個個體(放回或者不放回)進行比較,適應值最好的個體被選擇作為生成下一代的父體,反覆執行該過程,知道下一代的個體數量達到預定的群體規模。引數k稱為競賽規模,根據大量的實驗結果,一般取k=2
    • ( μ , λ ) (\mu, \lambda) μ + λ \mu+\lambda 選擇。 ( μ , λ ) (\mu, \lambda) 是先從規模為 μ \mu 種群中隨機選取個體通過交叉和變異生成 λ ( μ ) \lambda(≥\mu) 個後代,然後在從這些後代中選取 μ \mu 個最優的後代作為新一帶種群。 μ + λ \mu+\lambda 選擇則是從這些後代與其父體共 μ + λ \mu+\lambda 個後代中選取 μ \mu 個最後的後代。

在此問題GA演算法中,我使用錦標賽選擇機制進行選擇,主要實現如下:

// Tournament Selection 
void selection(vector<vector<customer>>&customersGeneration2, vector<vector<facility>>&facilitiesGeneration2) {
    customersGeneration2.clear();
    facilitiesGeneration2.clear();
    double minCost = INT_MAX * 1.0;
    for(int j=0; j<individuals; j++) {
        int randnum[selections] = {0};
        minCost = INT_MAX * 1.0;
        int minIndividual;
        int individualCost;
        for(int i=0;i<selections;i++) {
            randnum[i] = rand() % individuals;
            individualCost = evaluate(customersGeneration[randnum[i]], facilitiesGeneration[randnum[i]]);
            if(minCost > individualCost) {
                minCost = individualCost;
                minIndividual = randnum[i];
            }
        }
        customersGeneration2.push_back(customersGeneration[minIndividual]);
        facilitiesGeneration2.push_back(facilitiesGeneration[minIndividual]);
    }
}

交叉操作(crossover)

交叉操作是將兩個個體的遺傳物質交換產生新的個體,它可以把兩個個體的優良“格式”傳遞到下一代的某個個體中,使其具有優於前驅地效能。如果交叉後得到地個體效能不加,則可以在後面的replace操作中將其淘汰。交叉是遺傳演算法中獲取新優個體的最重要的手段,交叉的具體步驟如下:

(1)從交配池中隨機取處要交配的一對個體

(2)根據位串長度L,對要交配地一對個體,隨機選取[1,L-1]中一個或多個的整數k作為交叉點。

(3)根據交叉概率 p c ( 0 &lt; p c 1 ) p_c(0&lt;p_c≤1) 實施交叉操作,配對的個體再交差點處,相互交換各自的部分內容,從而形成一對新的個體。

  • 單點交叉(one-point crossover)。對於從交配池中隨機選擇的兩個串 s 1 = a 1 a 2 a L s_1=a_1a_2……a_L s 2 = b 1 b 2 b L s_2=b_1b_2……b_L ,隨機選擇一個交叉位 x [ 1 , 2 L 1 ] x∈[1,2……L-1] ,然後對兩個位串中該位置預測部份的染色體位串進行交換,產生的子位串個體為: s 1 = a 1 a 2 a x b x + 1 b L s_1=a_1a_2…a_xb_{x+1}…b_L s 1 = b 1 b 2 b x a x + 1 a L s_1=b_1b_2…b_xa_{x+1}…a_L 。單點交叉資訊量比較小,交叉點位置選擇會帶來較大偏差,單點交叉不利於長距模式的保留和衝去,而且位於末尾的基因總是被交換,而這些基因可能是很重要的(這就是尾點效應,end-point effect)。
  • 多點交叉(multipoint crossover)。對於選定的兩個個體位串,隨機選擇多個交叉點,構成交叉點集合: x 1 , x 2 , , x k [ 1 , 2 , , L 1 ] x_1,x_2,……,x_k∈[1,2,……,L-1] 。然後對每個交叉點,執行單點交叉運算,最後得到兩個子位串個體。
  • 均勻交叉(uniform crossover)。將位串上的每一位基因按照相同的概率隨機進行均勻交叉。

此題目中,交叉演算法如下,根據交叉概率,選擇兩個個體,將兩者[l,r-1]區間內的基因進行交換:

void crossover(vector<vector<customer>>&customersGeneration2, vector<vector<facility>>&facilitiesGeneration2) {
    int individual1, individual2, l, r, fid1, fid2;
    for(int i=0; i<individuals * (individuals-1); i++) {
        if(rand() % (N + 1) * 1.0 /(N + 1) < crossovers) {
            individual1 = rand() % individuals;
            individual2 = rand() % individuals;
            l = rand() % m;
            r = rand() % m;
            for(int j=l; j<r; j++) {
                fid1 = customersGeneration2[individual1][j].getFacilityID();
                fid2 = customersGeneration2[individual2][j].getFacilityID();
                if(facilitiesGeneration2[individual1][fid2].getRemain() - customersGeneration2[individual1][j].getDemand() > 0 && 
                    facilitiesGeneration2[individual2][fid1].getRemain() - customersGeneration2[individual2][j].getDemand() > 0) {
                    
                    facilitiesGeneration2[individual1][fid1].setRemain(facilitiesGeneration2[individual1][fid1].getRemain() + customersGeneration2[individual1][j].getDemand());
                    facilitiesGeneration2[individual1][fid2].setRemain(facilitiesGeneration2[individual1][fid2].getRemain() - customersGeneration2[individual1][j].getDemand());
                    
                    facilitiesGeneration2[individual2][fid1].setRemain(facilitiesGeneration2[individual2][fid1].getRemain() - customersGeneration2[individual2][j].getDemand());
                    facilitiesGeneration2[individual2][fid2].setRemain(facilitiesGeneration2[individual2][fid2].getRemain() + customersGeneration2[individual2][j].getDemand());
                    customersGeneration2[individual1][j].setFacilityID(fid2);
                    customersGeneration2[individual2][j].setFacilityID(fid1);
                }
            }
        }
    }
}