引言

编程中很多算法都是基于一些严谨的理论来作为基础,从而进行编程实现,解决问题。但我认为遗传算法是比较特殊的一种。首先,它是基于生物进化理论来的,理论虽然已被证明,但总归觉得有一些概率,可以说是运气在里面。其实,往往使用遗传算法去解决问题时,和常规的直面问题,制定严谨的执行步骤去解决问题不同,遗传算法总是将问题往这个模型上靠,制定简单的进化规则,然后运行起来后,它就按照这些既定的简单理论开始自己进化,从而得到一个往往不是最优解的答案,但往往用它来解决一些问题,你总归是会得到一个可用的答案。

什么是遗传算法

如果要讲遗传算法(Genetic Algorithm),那要从生物进化理论来讲。一个种群,起初是由一定规模的生物个体组成,在自然界竞争中优胜劣汰,从而一代一代进化下来,能够成功进化到下一代的种群,肯定有一个帮它克服大自然,良好地适应于恶劣环境的特点。比如,人类进化过程中,刚开始都不会直立行走,但某一些第一个尝试直立行走的祖先当中,他们解放了双手,进行了更有效的生产劳动,从而得到了更多的生产资源,存活了下来,而那些未能尝试学习直立行走的部分,要么被淘汰,要么没有进化,种群没能良好的适应环境。而这群尝试直立行走的种群,得到了更多的生存机会,进一步进化过程中,那些强壮的个体,某一方面能力出众的个体,适应环境更好,得到了更多繁衍生息后代的机会,就这样一代一代进化成我们现在的样子。如果假设我们现在的状态为最佳状态(当然不是),第一批猴子为人类初始状态,那么人类进化的过程可以看作是人类进化过程中的一个解决方案,按照这条路走,总归会达到现在的样子,当然可能有更多更好的方法,但至少这种方法可以达到现在状态。
   我们知道,人类的特性控制是由基因控制的,每一个性状都是一对基因的体现,基因也在不段的繁衍中,进行着各种杂交和突变,这才给了一些良好基因生存的机会,那些被证明杂交过程中留下来的基因,大多是更能适应环境的基因,而突变也给了基因一些闪亮的机会,并且最终突显出来。
   根据上面的描述,不难看出,遗传过程中的几个重要步骤:

  • 一个种群
  • 种群中进行繁衍的亲本
  • 基因杂交
  • 基因突变

那这些与我们的编程算法有什么关系呢?
很多种实际问题,其实它们的解可以类比为基因的排序,比如一个机器人去走迷宫,从入口找到出口,那么它的街轨迹,就是找到出口的解,即类似于,上下左右,四个方向,左->上->左->右->下,这个问题的解就是类似于这样的一个序列,我们可以把它看作是一个染色体,每一步可以看作是一对基因,如果我们随机给出一个种群,种群中每个个体都是一套染色体,每个染色体就包含这样一个基因序列,即给出一组随机的基因序列,那我们怎么去解决这个问题呢:

  • 先生产出一个群体,里面个体的基因序列随机;
  • 让每个基因组(即机器人走迷宫的步骤),去尝试走迷宫,看看它能走多远;
  • 根据结果对这组基因进行打分;
  • 从结果中,按照一定规则选出两个亲本,可以按照打分情况来随机选,让分数高的更容易被选上;
  • 用这两个亲本,进行杂交(比如随机选择一些片段整合一下),产生两个子代;
  • 对这两个子代亲本,按照一定变异率进行基因变异(比如一定概率基因值取反);
  • 把这两个子段放进下一代的种群中,依然再选择亲本,按照上面的方法产生子代,形成一个和上一代同等规模的种群;
  • 让这个种群去走下迷宫,得到他们的分数,依次类推,看是否有哪一条基因找到了出口;
  • 依次往复,直到得到答案。

关键点

从一个种群到下一个种群,适应下来的解,局部最优的解更容易得到繁衍的机会,而杂交算法的加入,会使得,这些优质的解可以取长补短,让子代有机会达到更佳,但局部最优可能不是全局最优,很有可能劣质基因的积累,让整个种群慢慢特征收敛,万一进入死胡同,将没有机会得到最优解,而变异就给了种群有了多样性的可能。这些算子的整合,就产生了一个完整的遗传算法。

代码演示

'''
 * Created on: Oct 11, 2017
 * Author:     Heng Xiangzhong
 * 
 * May the code be with you!
 '''

import random
import copy

map = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
	   [1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
	   [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
	   [1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1],
	   [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
	   [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
	   [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1],
	   [1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5],
	   [1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
	   [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

map_shadow = [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
	   [1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
	   [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1],
	   [1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1],
	   [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
	   [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
	   [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1],
	   [1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5],
	   [1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
	   [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]


def _print_map(map):
	for x,row in enumerate(map):
		for y, i in enumerate(row):
			if i == 999:
				print("\033[0;32m* \033[0m", end="")
			elif i > 1:
				print("\033[0;31m%d \033[0m" % (i), end="")
			elif x == 2 and y == 0:
				print("\033[0;32m%d \033[0m" % (i), end="")
			elif i == 0:
				print("  ", end="")
			elif i == 1:
				print("+ ", end="")

		print("")

class Genome:
	def __init__(self):
		self.bits = []
		self.fitness = 0

		for i in range(0, 100):
			self.bits.append(random.randint(0, 1))

	def clear(self):
		self.bits.clear()

class GARobot:
	def __init__(self):
		self.genomes = []
		self.pop_size = 200
		self.mutation_rate = 0.001
		self.chromo_length = 0
		self.gene_length = 0
		self.fittest_genome = 0
		self.best_fitness_score = 0
		self.total_fitness_score = 0
		self.crossover_rate = 0.7
		self.generation = 0
		self.map = map
		self.busy = False

		for i in range(0, self.pop_size):
			self.genomes.append(Genome())

	def epoch(self):
		self.__test_route()

		new_genomes = []
		for i in range(0, self.pop_size >> 1):
			mum = self.__roulette_wheel_selection()
			dad = self.__roulette_wheel_selection()

			#print("mum (%f), dad (%f), best (%f)" % (mum.fitness, dad.fitness, self.best_fitness_score))

			#Crossover
			baby = self.__crossover(mum, dad)
			baby_temp = baby[0]
			self.__mutate(baby[0])
			self.__mutate(baby[1])
			self.__mutate(baby_temp)
			new_genomes.append(baby[0])
			new_genomes.append(baby[1])

		self.genomes = new_genomes

	def __decode_direction(self, a, b):
		if a == 1 and b == 1:
			return (-1, 0)
		elif a == 1 and b == 0:
			return (1, 0)
		elif a == 0 and b == 0:
			return (0, -1)
		else:
			return (0, 1)

	def __show_path(self, genome):
		start = ()
		for x,row in enumerate(map):
			for y,val in enumerate(row):
				if val == 5:
					start = (x, y)

		temp_map = copy.deepcopy(map)
		pos = start
		for couple in range(0, len(genome.bits) >> 1):
			# Check whether is wall
			direction = self.__decode_direction(genome.bits[couple << 1], genome.bits[(couple << 1) + 1])
			next_x = pos[0] + direction[0]
			next_y = pos[1] + direction[1]
			if next_x >= 0 and next_x < len(map) and next_y >=0 and next_y < len(map[next_x]) and map[next_x][next_y] != 1:
				pos = (next_x, next_y)

				temp_map[pos[0]][pos[1]] = 999

		_print_map(temp_map)

	def __test_route(self):
		start = ()
		for x,row in enumerate(map):
			for y,val in enumerate(row):
				if val == 5:
					start = (x, y)

		#print("%d, %d, (%d)" % (start[0], start[1], map[start[0]][start[1]]))
		self.best_fitness_score = 0;
		self.total_fitness_score = 0
		for genome in self.genomes:
			pos = start
			for couple in range(0, len(genome.bits) >> 1):
				# Check whether is wall
				direction = self.__decode_direction(genome.bits[couple << 1], genome.bits[(couple << 1) + 1])
				next_x = pos[0] + direction[0]
				next_y = pos[1] + direction[1]
				#print("direction(%d, %d), next_x(%d), next_y(%d)" % (direction[0], direction[1], next_x, next_y))
				if next_x >= 0 and next_x < len(map) and next_y >=0 and next_y < len(map[next_x]) and map[next_x][next_y] != 1:
					pos = (next_x, next_y)

			genome.fitness = 1 / (abs(pos[0] - 2) + abs(pos[1] - 0) + 1)

			if (genome.fitness == 1.0):
				print("Fina a path!")
				self.__show_path(genome)
				exit(0)

			self.total_fitness_score += genome.fitness
			if genome.fitness > self.best_fitness_score:
				self.best_fitness_score = genome.fitness
			#print(genome.fitness)

			if (map_shadow[pos[0]][pos[1]] == 0):
				map_shadow[pos[0]][pos[1]] = 2
			else:
				map_shadow[pos[0]][pos[1]] += 1

	def __roulette_wheel_selection(self):
		slice = random.random() * self.total_fitness_score
		#print(slice, self.total_fitness_score)
		total = 0
		for genome in self.genomes:
			total += genome.fitness
			if total > slice:
				return genome

		return self.genomes[0]

	def __crossover(self, mum, dad):
		baby1 = Genome()
		baby2 = Genome()
		baby1.clear()
		baby2.clear()
		if random.random() > self.crossover_rate or mum == dad:
			baby1 = dad
			baby2 = mum
			return (baby1, baby2)

		crossover_point = random.randint(0, len(mum.bits) - 1)
		#print(crossover_point)

		for i in range(0, crossover_point):
			baby1.bits.append(mum.bits[i])
			baby2.bits.append(dad.bits[i])

		for i in range(crossover_point, len(mum.bits)):
			baby1.bits.append(dad.bits[i])
			baby2.bits.append(mum.bits[i])

		return (baby1, baby2)

	def __mutate(self, genome):
		for i,val in enumerate(genome.bits):
			if random.random() < self.mutation_rate:
				genome.bits[i] = not genome.bits[i]

_print_map(map_shadow)

bob = GARobot()

for i in range(0, 1000):
	map_shadow = copy.deepcopy(map)
	bob.epoch()
	_print_map(map_shadow)
	#if map_shadow[2][0] > 0:
	#	break;

上面是 python 代码,用以演示前面提到的理论,代码仅作展示用,没有优化,本人对 python 也不熟,你当然可以选任何你喜欢的语言去实现,此代码只用于展示思路和可行性,不具有大的参考价值。

运行效果

java遗传算法编程 遗传算法_人工智能

运行程序,可以看到整个算法运行过程中,答案的收敛过程,以及发现可行路径的解,当然可以添加一些打印,观察不同算子对算法的影响,以及得到解的过程中,都是怎么进化的。