现代优化算法探究 遗传算法

遗传算法是模拟生物进化过程的现代最优化算法,这里讲先介绍算法原理,然后先用一个简单的求函数最值的例子,解释我写的c++遗传算法模板的使用,之后用其解决15节点的TSP问题,展示遗传算法解决一般问题的方法和效果。

原理:

更加细节的原理网上很多大佬都有解释,这里就简略说一下。

遗传算法的大体框架就是,将一组解集作为种群,解的效用越高就会有越大的概率产生子代(自然选择),然后不断迭代产生新的种群来找寻最优解。更具体一点,父带可以通过交叉和变异产生子代(模仿染色体的行为),其中交叉是为了可能融合双亲的优势,而变异可以有效避免陷入局部最优解。

伪代码在此:

/*遗传算法伪代码
* Pc:交叉发生的概率
* Pm:变异发生的概率
* M:种群规模
* G:终止进化的代数
* Tf:进化产生的任何一个个体的适应度函数超过Tf,则可以终止进化过程
*/
初始化Pm,Pc,M,G,Tf等参数。随机产生第一代种群Pop

do
{ 
  计算种群Pop中每一个体的适应度F(i)。
  初始化空种群newPop
  do
  {
    根据适应度以比例选择算法从种群Pop中选出2个个体
    if ( random ( 0 , 1 ) < Pc )
    {
      对2个个体按交叉概率Pc执行交叉操作
    }
    if ( random ( 0 , 1 ) < Pm )
    {
      对2个个体按变异概率Pm执行变异操作
    }
将2个新个体加入种群newPop中
} until ( M个子代被创建 )
用newPop取代Pop
}until ( 任何染色体得分超过Tf, 或繁殖代数超过G )

来自 https://blog.csdn.net/u013912596/article/details/43417809

遗传算法基本问题: 求函数 f(x)=x * sin(10 * pi*x) + 2 在[-1,2]上的最大值

分析:

框架上直接使用模板,但是在定义问题时,关键要素有如下几个:

1. 解的定义及适应度的量化(比如求函数最优值得话解就是横轴x的值,y就是适应度)

2. 产生新解的方式(包括编码和解码,交叉和变异的方式)

3. 自然选择的方式(怎样选择种群中的“优良个体”)

解的定义一般是比较自然的

至于编码的话可以选择二进制或者是浮点数编码,这位大佬讲得很详细https://blog.csdn.net/together_cz/article/details/53543695

看了大佬的讲解后,我加入了一层格雷编码来提高局部搜索能力,具体看代码。

然后就是自然选择方式我用的是轮盘赌,这里也有讲解https://blog.csdn.net/mr_orange_klj/article/details/78063563,还是很好理解的。

#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<sstream>
#include<cmath>
#include<iterator>
#include<bitset>
#include<stdio.h>
#include<unordered_set>
#include<ctime>
#include<float.h>
using namespace std;
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
typedef long long LL;
const int INF = 1 << 30;
const int maxn = 100005;
const double eps = 3.0 / (1 << 15);
const unsigned int MAXBIT = 15;
const double pi = acos(-1);

string GreeCode(const string & s) {
	string g;
	g.resize(s.size());
	g[0] = s[0];
	for (int i = 1; i < s.size(); ++i)
		g[i] = s[i] == s[i - 1] ? '0' : '1';
	return g;
}
string GreeDecode(const string & g) {
	string s;
	s.resize(g.size());
	s[0] = g[0];
	for (int i = 1; i < s.size(); ++i)
		s[i] = g[i] == s[i - 1] ? '0' : '1';
	return s;
}

template<typename T = double> class State;
template<typename T = double>
T Decode(State<T> s) {
	string str = s.chro;
	str = GreeDecode(str); //格雷解码
	bitset<MAXBIT> b(str);
	return b.to_ulong()*eps - 1;
}
template<typename T = double>
State<T> Code(T x) {
	int tmp = floor((1 + x) / eps);
	char str[MAXBIT + 2];
	itoa(tmp, str, 2);
	string s(str);
	s = GreeCode(s); //格雷转码
	return State<T>(s);
};
template<typename T = double>
class State {          //解的定义
public:
	string chro;
	T x, f;
	T fitness() {   //适应度的定义
		x = Decode(*this);
		return f = x * sin(10 * pi*x) + 2;
	}
	void Init() {
		chro.clear(); f = -DBL_MAX;
	}
	bool operator<(const State & rhs)const {
		return f < rhs.f;
	}
	State<T>() { Init(); }
	State<T>(string & s) {
		chro = s;
		this->fitness();
	}
};
template<typename T = double>
class GA {     //Genetic Algorithm
private:
	static const int maxGeneration;
	static const int sz;
	static const double pc;
	static const double pm;
	static bool inside(int x) {
		return x >= -1 && x <= 2;
	}
	State<T> randne() {
		double x = ((double)(rand()) / RAND_MAX) * 3 - 1;
		return Code(x);
	}
	static void Choose(vector<State<T> >& a, State<T> & x, State<T> & y) {//轮盘赌
		double mi = DBL_MAX, sum = 0;
		for_each(a.begin(), a.end(), [&](State<T>& tmp) {mi = min(mi, tmp.f); });
		for_each(a.begin(), a.end(), [&](State<T>&tmp) {sum += (tmp.f - mi); });
		if (sum == 0)x = y = a[0];
		else {
			vector<double> p(a.size() + 1);
			for (int i = 1; i <= a.size(); ++i)p[i] = p[i - 1] + (a[i - 1].f - mi) / sum;
			double tmp1 = (double)rand() / RAND_MAX;
			double tmp2 = (double)rand() / RAND_MAX;
			int idx1 = 0; while (p[idx1]<tmp1)idx1++;
			int idx2 = 0; while (p[idx2]<tmp2)idx2++;
			x = a[idx1 == 0 ? 0 : idx1 - 1]; y = a[idx2 == 0 ? 0 : idx2 - 1];
		}
	}
	static void Cross(vector<State<T>>& nepop, State<T> & x, State<T> & y) {//交叉
		string & s1 = x.chro, s2 = y.chro;
		int len = min(s1.size(), s2.size());
		int p = rand() % len;
		string ns1 = s1.substr(0, p + 1) + s2.substr(p + 1, s2.size() - p - 1);
		string ns2 = s2.substr(0, p + 1) + s1.substr(p + 1, s1.size() - p - 1);
		State<T> ne1 = State<T>(ns1), ne2 = State<T>(ns2);
		if (inside(ne1.x)) nepop.push_back(ne1);
		if (inside(ne2.x)) nepop.push_back(ne2);
	}
	static State<T> Mutate(State<T> & x) {   //变异
		string  s = x.chro;
		for (int i = 0; i < 5; ++i) {
			int p = rand() % s.size();
			s[p] = s[p] == '1' ? '0' : '1';
		}
		return State<T>(s);
	}
public:
	State<T> solve() {
		vector<State<T> > pop;
		int step = 0;
		for (int i = 0; i < sz; ++i) { pop.push_back(randne()); }
		do {
			vector<State<T> >nepop;
			State<T> best; best.Init();
			for (auto it : pop)if (best < it)best = it;
			nepop.push_back(best);    //Elitist Strategy
			//cout << best.x << " " << best.f << " ";
			//double sum = 0; for (auto it : pop)sum += it.f;
			//cout << sum / pop.size() << endl;
			do {
				State<T> x, y;
				Choose(pop, x, y);
				if ((double)rand() / RAND_MAX < pc) {
					Cross(nepop, x, y);
					if (nepop.size() >= sz)break;
				}
				if ((double)rand() / RAND_MAX < pm) {
					State<T> tmp = Mutate(x);
					if (inside(tmp.x)) nepop.push_back(tmp);
					tmp = Mutate(y);
					if (inside(tmp.x)) nepop.push_back(tmp);
					if (nepop.size() >= sz)break;
				}
			} while (true);
			pop = nepop;
			step++;
		} while (step< maxGeneration);
		State<T> ans; ans.Init();
		for (auto & it : pop) {
			if (ans < it)ans = it;
		}
		return ans;
	}

};
const int GA<double>::maxGeneration = 100;
const int GA<double>::sz = 10;
const double GA<double>::pc = 0.8;
const double GA<double>::pm = 0.4;

int main()
{
	// freopen("C:\\Users\\admin\\Desktop\\in.txt", "r", stdin);
	//freopen("C:\\Users\\admin\\Desktop\\out.txt", "w", stdout);

	srand((unsigned)time(NULL));

	GA<double> solver;
	State<double> ans = solver.solve();
	cout << Decode(ans) << " " << ans.f << endl;

	return 0;
}

多次运行结果都能稳定在 x=1.85059 y=3.85027处

以下 是f(x)=x * sin(10 * pi*x) + 2函数图像:

红点是得到的解,可以看到算法有效的避开了局部最优解,得到的解十分接近[-1,2]上函数的最大值

种群的迭代过程如下:

可以看到早在第10个种群时解就已经收敛,速度很快。另外可以看到,解可能会有阶梯式上升,这也是因为,某次变异或者交叉突然产生了很好的个体所致。

15节点的TSP问题:

问题描述:

有15个城市, 给出每个城市的坐标,每两个城市之间相互有路径可走,需要消耗一定的费用。问一个商人,想从某个起点出发经过每个城市一次且仅仅一次最后回到起点所需的最小费用是多少。本题直接把两点的距离作为费用。

数据:

53.7121   15.3046    51.1758    0.0322    46.3253   28.2753    30.3313    6.9348
56.5432   21.4188    10.8198   16.2529    22.7891   23.1045    10.1584   12.4819
20.1050   15.4562    1.9451    0.2057    26.4951   22.1221    31.4847    8.9640
26.2418   18.1760    44.0356   13.5401    28.9836   25.9879   

从上到下,从左往右,每两个数代表一个城市的x 和y 坐标。

分析:

仍然使用遗传算法。同样需要考虑三个问题:解的定义及适应度的量化,产生新解的方式和选择方式。由于这里的tsp解的形式就是一个排列,我就没有再用编码了,直接在原排列上交叉和变异,其中交叉用了一种贪心的方式,企图子代在双亲不同的位置上一人选择一个以达到“公平”,变异的话就是段的拼接,和上一篇现代优化算法模拟退火一致, 具体看代码。

#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<sstream>
#include<cmath>
#include<iterator>
#include<bitset>
#include<stdio.h>
#include<unordered_set>
#include<ctime>
#include<float.h>
using namespace std;
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
typedef long long LL;
const int INF = 1 << 30;
const int maxn = 15;
const double eps = 1e-6;
const unsigned int MAXBIT = 15;
const double pi = acos(-1);

struct Node {
	double x, y;
};
Node a[maxn];
double dist[maxn][maxn];
int order[maxn];

string GreeCode(const string & s) {
	string g;
	g.resize(s.size());
	g[0] = s[0];
	for (int i = 1; i < s.size(); ++i)
		g[i] = s[i] == s[i - 1] ? '0' : '1';
	return g;
}
string GreeDecode(const string & g) {
	string s;
	s.resize(g.size());
	s[0] = g[0];
	for (int i = 1; i < s.size(); ++i)
		s[i] = g[i] == s[i - 1] ? '0' : '1';
	return s;
}


template<typename T = double> class State;
template<typename T = double>
T Decode(State<T> s) {
	string str = s.path;
	str = GreeDecode(str); //格雷解码
	bitset<MAXBIT> b(str);
	return b.to_ulong()*eps - 1;
}
template<typename T = double>
State<T> Code(T x) {
	int tmp = floor((1 + x) / eps);
	char str[MAXBIT + 2];
	itoa(tmp, str, 2);
	string s(str);
	s = GreeCode(s); //格雷转码
	return State<T>(s);
};
template<typename T = double>
class State {
public:
	int path[maxn];
	T f;
	T fitness() {
		double sum = 0;
		for (int i = 1; i <= maxn; ++i) {
			sum += dist[path[i%maxn]][path[i - 1]];
		}
		return f = -sum;
	}
	void Init() {
		f = -DBL_MAX;
	}
	bool operator<(const State & rhs)const {
		return f < rhs.f;
	}
	State() { Init(); }
	State(const State<T> & rhs) :f(rhs.f) { memcpy(path, rhs.path, sizeof(path)); }
};
template<typename T = double>
class GA {     //Genetic Algorithm
private:
	static const int maxGeneration;
	static const int sz;
	static const double pc;
	static const double pm;

	State<T> randne() {     //随机产生新解的方式
		State<T> ne;
		memcpy(ne.path, order, sizeof(order));
		random_shuffle(ne.path, ne.path + maxn);
		ne.fitness();
		return ne;
	}
	static void Choose(vector<State<T> >& a, State<T> & x, State<T> & y) {  //轮盘赌
		double mi = DBL_MAX, sum = 0;
		for_each(a.begin(), a.end(), [&](State<T>& tmp) {mi = min(mi, tmp.f); });
		for_each(a.begin(), a.end(), [&](State<T>&tmp) {sum += (tmp.f - mi); });
		if (sum == 0)x = y = a[0];
		else {
			vector<double> p(a.size() + 1);
			p[0] = 0;
			for (int i = 1; i <= a.size(); ++i)p[i] = p[i - 1] + (a[i - 1].f - mi) / sum;
			double tmp1 = (double)rand() / RAND_MAX;
			double tmp2 = (double)rand() / RAND_MAX;
			int idx1 = 0; while (p[idx1]+eps<tmp1)idx1++;
			int idx2 = 0; while (p[idx2]+eps<tmp2)idx2++;
			x = a[idx1 == 0 ? 0 : idx1 - 1]; y = a[idx2 == 0 ? 0 : idx2 - 1];
		}
	}
	static void Cross(vector<State<T>>& nepop, State<T> & x, State<T> & y) {//交叉
		int path1[maxn], path2[maxn];
		bitset<maxn> vis1, vis2; vis1.reset(); vis2.reset();
		int who = 0;
		for (int i = 0; i < maxn; ++i) {
			if (who) {
				if (!vis1[x.path[i]]) {
					vis1.set(x.path[i], true);
					path1[i] = x.path[i];
				}
				else {
					int id;
					for (int j = 0; j < maxn; ++j)if (!vis1[j]) {
						id = j; break;
					}
					vis1[id] = 1;
					path1[i] = id;
				}
				if (!vis2[y.path[i]]) {
					vis2.set(y.path[i], true);
					path2[i] = y.path[i];
				}
				else {
					int id;
					for (int j = 0; j < maxn; ++j)if (!vis2[j]) {
						id = j; break;
					}
					vis2[id] = 1;
					path2[i] = id;
				}
			}
			else {
				if (!vis1[y.path[i]]) {
					vis1.set(y.path[i], true);
					path1[i] = y.path[i];
				}
				else {
					int id;
					for (int j = 0; j < maxn; ++j)if (!vis1[j]) {
						id = j; break;
					}
					vis1[id] = 1;
					path1[i] = id;
				}
				if (!vis2[x.path[i]]) {
					vis2.set(x.path[i], true);
					path2[i] = x.path[i];
				}
				else {
					int id;
					for (int j = 0; j < maxn; ++j)if (!vis2[j]) {
						id = j; break;
					}
					vis2[id] = 1;
					path2[i] = id;
				}
			}
			who ^= 1;

		}
		State<T> tmp1, tmp2;
		memcpy(tmp1.path, path1, sizeof(path1));
		memcpy(tmp2.path, path2, sizeof(path2));
		tmp1.fitness();
		tmp2.fitness();
		nepop.push_back(tmp1);
		nepop.push_back(tmp2);
	}
	static State<T> Mutate(State<T> & s) {  //变异
		State<T> ne = s;
		int head = max(1, rand() % maxn), tail = max(1, rand() % maxn);
		if (head > tail)swap(head, tail);
		reverse(ne.path + head, ne.path + tail);
		ne.fitness();
		return ne;
	}
public:
	State<T> maxans() {
		vector<State<T> > pop;
		int step = 0;
		for (int i = 0; i < sz; ++i) { pop.push_back(randne()); }
		do {
			vector<State<T> >nepop;
			State<T> best; best.Init();
			for (auto it : pop)if (best < it)best = it;
			nepop.push_back(best);    //Elitist Strategy
			//cout << -best.f << " ";
			//double sum = 0;
			//for (auto it : pop) sum += it.f;
			//cout << -sum / pop.size() << endl;
			do {
				State<T> x, y;
				Choose(pop, x, y);
				if ((double)rand() / RAND_MAX < pc) {
					Cross(nepop, x, y);
					if (nepop.size() >= sz)break;
				}
				if ((double)rand() / RAND_MAX < pm) {
					State<T> tmp = Mutate(x);
					nepop.push_back(tmp);
					tmp = Mutate(y);
					nepop.push_back(tmp);
					if (nepop.size() >= sz)break;
				}
			} while (true);
			pop = nepop;
			step++;
		} while (step< maxGeneration);
		State<T> ans; ans.Init();
		for (auto & it : pop) {
			if (ans < it)ans = it;
		}
		return ans;
	}

};
const int GA<double>::maxGeneration = 1000;
const int GA<double>::sz = 50;
const double GA<double>::pc = 0.8;
const double GA<double>::pm = 0.4;

int main()
{
	//freopen("C:\\Users\\admin\\Desktop\\in.txt", "r", stdin);
	//freopen("C:\\Users\\admin\\Desktop\\out.txt", "w", stdout);

	srand((unsigned)time(NULL));

	for (int i = 0; i <maxn; ++i) {
		scanf("%lf%lf", &a[i].x, &a[i].y);
	}
	for (int i = 0; i < maxn; ++i) order[i] = i;
	for (int i = 0; i < maxn; ++i)
		for (int j = 0; j < maxn; ++j)
			dist[i][j] = sqrt((a[i].x - a[j].x)*(a[i].x - a[j].x) + (a[i].y - a[j].y)*(a[i].y - a[j].y));

	GA<double> solver;
	State<double> ans = solver.maxans();
	cout << -ans.f << endl;
	for (int i = 0; i < maxn; ++i)cout << ans.path[i] << " ";

	return 0;
}

多次运行基本最短路程在160到180之间,某次运行结果如下:

161.241
13 1 0 4 2 14 10 6 12 8 5 7 9 3 11

同上一篇模拟退火算法的动态验证部分部分,这个解就是全局最优解。

解的演变过程如下:

可以看到解也是呈阶梯状逐步下降的过程。

相比于模拟退火算法,遗传算法要更加复杂但是有效,自认为遗传算法相较与模拟退火的最大优势是群体的迭代而不像模拟退火一样只有单个解的迭代。这使得遗传算法有着计算时间越长结果就越好的特性,因为它必定是不断进化的且从不会丢失最优解,这样的仿真更加具有智能。

猜你喜欢

转载自blog.csdn.net/tomandjake_/article/details/81697335