1. 程式人生 > >C語言實現粒子群算法(PSO)二

C語言實現粒子群算法(PSO)二

計算 default img 第一個元素 1.4 best 實驗 atl 說過

上一回說了基本粒子群算法的實現,並且給出了C語言代碼。這一篇主要講解影響粒子群算法的一個重要參數---w。我們已經說過粒子群算法的核心的兩個公式為:

Vid(k+1)=w*Vid(k)+c1*r1*(Pid(k)-Xid(k))+c2*r2*(Pgd(k)-Xid(k))
Xid(k+1) = Xid(k) + Vid(k+1)

標紅的w即是本次我們要討論的參數。之前w是不變的(默認取1),而現在w是變化的,w稱之為慣性權重,體現的是粒子繼承先前速度的能力。 經驗表明:一個較大的慣性權重有利於全局搜索,而一個較小的慣性權重則更有利於局部搜索。為了更好地平衡算法的全局搜索能力與局部搜索能力,Shi.Y提出了線性遞減慣性權重(LDIW)
即:w(k) = w_end + (w_start- w_end)*(Tmax-k)/Tmax。其中w_start 為初始慣性權重,w_end 為叠代至最大次數時的慣性權重;k為當前叠代次數, Tmax為最大叠代次數。一般來說,w_start=0.9,w_end=0.4時,算法的性能最好。這樣隨著叠代的進行,慣性權重從0.9遞減到0.4,叠代初期較大的慣性權重使算法保持了較強的全局搜索能力。而叠代後期較小的慣性權重有利於算法進行更精確的局部搜索。線性慣性權重,只是一種經驗做法,常用的慣性權重還包括 以下幾種。
(3) w(k) = w_start - (w_start-w_end)*(k/Tmax)^2
(4) w(k) = w_start + (w_start-w_end)*(2*k/Tmax - (k/Tmax)^2)
(5) w(k) = w_end*(w_start/w_end)^(1/(1+c*k/Tmax)) ,c為常數,比如取10等。
本例的目的就是比較這5種不同的w取值,對於PSO尋優的影響。比較的方法為每種w取值,重復實驗若幹次(比如100次),比較平均最優解的大小,陷入次優解的次數,以及接近最優解的次數。 這樣對於5種方法的優劣可以有一個直觀的比較。

代碼如下:

技術分享
/*
 * 使用C語言實現粒子群算法(PSO) 改進版本
 * 參考自《MATLAB智能算法30個案例分析》
 * update: 16/12/3
 * 主要改進的方面體現在w的選擇上面
* 本例的尋優非線性函數為
 * f(x,y) = sin(sqrt(x^2+y^2))/(sqrt(x^2+y^2)) + exp((cos(2*PI*x)+cos(2*PI*y))/2) - 2.71289
 * 該函數有很多局部極大值點,而極限位置為(0,0),在(0,0)附近取得極大值
 */
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#define c1 1.49445 //加速度因子一般是根據大量實驗所得
#define c2 1.49445
#define maxgen 300  // 叠代次數
#define repeat 100 // 重復實驗次數
#define sizepop 20 // 種群規模
#define popmax 2 // 個體最大取值
#define popmin -2 // 個體最小取值
#define Vmax 0.5 // 速度最大值
#define Vmin -0.5 //速度最小值
#define dim 2 // 粒子的維數
#define w_start 0.9
#define w_end 0.4
#define PI 3.1415926 //圓周率

double pop[sizepop][dim]; // 定義種群數組
double V[sizepop][dim]; // 定義種群速度數組
double fitness[sizepop]; // 定義種群的適應度數組
double result[maxgen];  //定義存放每次叠代種群最優值的數組
double pbest[sizepop][dim];  // 個體極值的位置
double gbest[dim]; //群體極值的位置
double fitnesspbest[sizepop]; //個體極值適應度的值
double fitnessgbest; // 群體極值適應度值
double genbest[maxgen][dim]; //每一代最優值取值粒子

//適應度函數
double func(double * arr)
{
    double x = *arr; //x 的值
    double y = *(arr+1); //y的值
    double fitness = sin(sqrt(x*x+y*y))/(sqrt(x*x+y*y)) + exp((cos(2*PI*x)+cos(2*PI*y))/2) - 2.71289;
    return fitness;

}    
// 種群初始化
void pop_init(void)
{
    for(int i=0;i<sizepop;i++)
    {
        for(int j=0;j<dim;j++)
        {
            pop[i][j] = (((double)rand())/RAND_MAX-0.5)*4; //-2到2之間的隨機數
            V[i][j] = ((double)rand())/RAND_MAX-0.5; //-0.5到0.5之間
        }
        fitness[i] = func(pop[i]); //計算適應度函數值
    }
}
// max()函數定義
double * max(double * fit,int size)
{
    int index = 0; // 初始化序號
    double max = *fit; // 初始化最大值為數組第一個元素
    static double best_fit_index[2];
    for(int i=1;i<size;i++)
    {
        if(*(fit+i) > max)
            max = *(fit+i);
            index = i;
    }
    best_fit_index[0] = index;
    best_fit_index[1] = max;
    return best_fit_index;

}
// 叠代尋優,傳入的參數為一個整數,取值為1到5,分別代表5種不同的計算w的方法
void PSO_func(int n)
{
    pop_init();
    double * best_fit_index; // 用於存放群體極值和其位置(序號)
    best_fit_index = max(fitness,sizepop); //求群體極值
    int index = (int)(*best_fit_index);
    // 群體極值位置
    for(int i=0;i<dim;i++)
    {
        gbest[i] = pop[index][i];
    }
    // 個體極值位置
    for(int i=0;i<sizepop;i++)
    {
        for(int j=0;j<dim;j++)
        {
            pbest[i][j] = pop[i][j];
        }
    }
    // 個體極值適應度值
    for(int i=0;i<sizepop;i++)
    {
        fitnesspbest[i] = fitness[i];
    }
    //群體極值適應度值
    double bestfitness = *(best_fit_index+1);
    fitnessgbest = bestfitness;

    //叠代尋優
    for(int i=0;i<maxgen;i++)
    {
        for(int j=0;j<sizepop;j++)
        {
            //速度更新及粒子更新
            for(int k=0;k<dim;k++)
            {
                // 速度更新
                double rand1 = (double)rand()/RAND_MAX; //0到1之間的隨機數
                double rand2 = (double)rand()/RAND_MAX;
                double w;
                double Tmax = (double)maxgen;
                switch(n)
                {
                    case 1:
                        w = 1;
                    case 2:
                        w = w_end + (w_start - w_end)*(Tmax-i)/Tmax;
                    case 3:
                        w = w_start -(w_start-w_end)*(i/Tmax)*(i/Tmax);
                    case 4:
                        w = w_start + (w_start-w_end)*(2*i/Tmax-(i/Tmax)*(i/Tmax));
                    case 5:
                        w = w_end*(pow((w_start/w_end),(1/(1+10*i/Tmax))));
                    default:
                        w = 1;
                }
                V[j][k] = w*V[j][k] + c1*rand1*(pbest[j][k]-pop[j][k]) + c2*rand2*(gbest[k]-pop[j][k]);
                if(V[j][k] > Vmax)
                    V[j][k] = Vmax;
                if(V[j][k] < Vmin)
                    V[j][k] = Vmin;
                // 粒子更新
                pop[j][k] = pop[j][k] + V[j][k];
                if(pop[j][k] > popmax)
                    pop[j][k] = popmax;
                if(pop[j][k] < popmin)
                    pop[j][k] = popmin;
            }
            fitness[j] = func(pop[j]); //新粒子的適應度值
        }
        for(int j=0;j<sizepop;j++)
        {
            // 個體極值更新
            if(fitness[j] > fitnesspbest[j])
            {
                for(int k=0;k<dim;k++)
                {
                    pbest[j][k] = pop[j][k];
                }
                fitnesspbest[j] = fitness[j];
            }
            // 群體極值更新
            if(fitness[j] > fitnessgbest)
            {
                for(int k=0;k<dim;k++)
                    gbest[k] = pop[j][k];
                fitnessgbest = fitness[j];
            }
        }
        for(int k=0;k<dim;k++)
        {
            genbest[i][k] = gbest[k]; // 每一代最優值取值粒子位置記錄
        }
        result[i] = fitnessgbest; // 每代的最優值記錄到數組
    }
}

// 主函數
int main(void)
{
    clock_t start,finish; //程序開始和結束時間
    start = clock(); //開始計時
    srand((unsigned)time(NULL)); // 初始化隨機數種子
    for(int i=1;i<=5;i++)
    {
        int near_best = 0; // 接近最優解的次數
        double best_sum = 0; // 重復最優值求和
        double best = 0; // 重復實驗得到的最優解
        for(int j=0;j<repeat;j++)
        {
            PSO_func(i); // 第i種w參數取值
            double * best_fit_index = max(result,maxgen);
            double best_result = *(best_fit_index+1); //最優解
            if(best_result > 0.95)
                near_best++;
            if(best_result>best)
                best = best_result;
            best_sum += best_result;
        }
        double average_best = best_sum/repeat; //重復實驗平均最優值
        printf("w參數的第%d種方法:\n",i);
        printf("重復實驗%d次,每次實驗叠代%d次,接近最優解的實驗次數為%d次,求得最優值為:%lf,平均最優值為:%lf\n",repeat,maxgen,near_best,best,average_best);
    }
    finish = clock(); //結束時間
    double duration = (double)(finish - start)/CLOCKS_PER_SEC; // 程序運行時間
    printf("程序運行耗時:%lf\n",duration);
    return 0;
}
技術分享

程序運行結果如下:

技術分享

從實驗的結果來看,第3種w的取法,無論是接近最優解的的次數,最優值大小,還是平均最優值,都是5種裏面最好的。其原因解釋如下:通過w的表達式可以看出,前期w變化較慢,取值較大,維持了算法的全局搜索能力;後期w變化變化較快,極大地提高了算法的局部搜索能力尋優能力,從而取得了很好的求解效果。

從總體上來看,在大部分的情況下,無論w是5種裏面哪種取法,得到的結果都很好地接近實際的最優解,這說明了粒子群算法的搜索尋優能力還是很強的。

C語言實現粒子群算法(PSO)二