document.write('
Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1.  
  2. --# Notes
  3. --[[
  4. The GA class provides a fairly simple genetic algorithm
  5.  
  6. The key variables are
  7. # Size of population - more is generally better, but slower
  8. # Number of iterations - again, more is often better, but GA will eventually get stuck, so there's no point making this unnecessarily high
  9. # Crossover rate - the % of the population replaced in each iteration. If it is high, eg 75%, it will produce quicker results initially, by focussing on the most successful solutions so far, but these may be a dead end, and the downside is that a high crossover drives out a lot of apparently bad solutions that may come good later on, ie it destroys variety, which is crucial to evolution. Conversely, a low crossover means slow convergence towards the final answer.
  10. # Mutation - when a new solution is created, its random numbers come from its parents (see below), but for each of them, an extra random number is generated, and if it is lower than the mutation %, then that random number is regenerated randomly instead of coming from a parent.
  11.  
  12. Replacement is done by sorting the population by fitness, and replacing the worst X%, where X% is the crossover rate. For each replacement, two 'parents' are chosen using a common technique called 'tournament', choosing three solutions at random, and the best of them is used as one parent, then doing the same to choose the other parent. The reason for this is that if you just used the very best solutions to replace bad ones, you will lose genetic variability, so tournament is a way of accessing a wide range of solutions. while ensuring they are not too bad to start with.
  13.  
  14. The random numbers from the two parents are combined in one of two ways
  15. # 2 slices - the replacement array is made up of the first N random numbers from one parent, and the remaining random numbers from the other parent,where N is chosen randomly. This technique works best when it matters that random numbers are adjacent to each other (as in this demo, where pairs of random numbers are used together)
  16. # Individual selection (called EitherOr in the code), where each random number in the replacement is chosen from one of the parents randomly.
  17. As implemented, GA chooses a replacement method randomly for each replacement, so it mixes these two methods.
  18.  
  19. The critical choices are mainly with
  20. # how to use the random numbers produced by GA, to generate practical solutions, and
  21. # how to calculate fitness.
  22.  
  23.  
  24.  
  25. --]]
  26. --# Main
  27. --Main
  28.  
  29. --NB The Notes tab has explanations and some advice on various aspects!
  30.  
  31. --This class is set up with a very simple exanple, solving the coefficients of a quadratic equation
  32. -- ie solve for a,b,c in ax^2 + bx + c, given the correct answer for x=1,2,3,4,5
  33. --set a,b,c
  34. local a,b,c=-2,33,4
  35.  
  36. local ga=nil --is the instance of the GA class
  37. local Best= 0 --this stores the best score so far
  38. local BestSol = {} -- this array stores the best solution so far
  39. local nIter=0 --number of iterations,set below
  40.  
  41. function setup()
  42. SetupGA()
  43. end
  44.  
  45. function SetupGA()
  46. nIter=500 --number of iterations
  47. --create GA object
  48. local numVars = 6 --numVars = number of random numbers to be used (reason below)
  49. local numPopulation = 5000 --numPopulation = the number of sets of random numbers to create
  50. --the last parameter is a callback for the function that will calculate fitness
  51. --(GA doesn't do this, because it is application specific)
  52. --so we will use a function called Fitness in this tab to do the work
  53. ga=GA(numVars,numPopulation,Fitness)
  54. --NB if you are calling from within a class, add an extra parameter, self, like so
  55. --ga=GA(numVars,numPopulation,Fitness,self)
  56. --set up a coroutine with the function that will do the iterations
  57. --coroutines are used so that you get a chance to draw the screen and other stuff between iterations
  58. --if you are calling from within a class, prefix DoRuns with the classname, eg MyClass.DoRuns
  59. cos=coroutine.create(DoRuns)
  60. coroutine.resume(cos)
  61. end
  62.  
  63. --this function manages the iterations
  64. function DoRuns()
  65. for i=1,nIter do
  66. --call the GA to process a generation of solutions
  67. --the first one just creates random numbers for all of them
  68. --from then on, GA uses crossover and mutation to replace bad solutions with combinations of good ones
  69.  
  70. --Parameters are
  71. --Iteration number (GA only really needs to know if it's the first one or not)
  72. --Crossover % (>0,<=100) the percentage of solutions to be replaced
  73. local Crossover=0.4 --generally 20-80%
  74. --Mutation % when good solutions are combined to replace bad ones, the random numbers
  75. -- are taken from both "parents", but a small percentage is chosen randomly - that's what this is
  76. local Mutation=0.01 --generally 1-2%
  77. --Sample isn't a classic genetic algorithm component, just something I was trying.
  78. -- It lets you choose the candidates from a subset of the population. If you
  79. --set it to 100% it samples the entire population, but if you set it to 60% it samples the best
  80. --60% of solutions
  81. local Sample=1.0
  82. --NB if these variables are not going to change, you could pass them into GA just once, at the top. I
  83. --put them here because the user can play with the parameters as it runs, so they can change
  84.  
  85. --call GA
  86. --it returns two things
  87. --tblRand is an array of the random numbers in the best solution so far
  88. --Best is the fitness value of that solution
  89. BestSol,Best=GA:DoRun(i,Crossover,Mutation,Sample)
  90. currIter=i
  91. --pause so we can draw the screen, use the results whatever
  92. coroutine.yield()
  93. end
  94. end
  95.  
  96. --this function takes a set of random numbers from GA and calculates its fitness
  97. --what this is depends entirely on what you're using this for, and it's worth thinking hard about it
  98. --in this case, we're trying to solve for coefficients that are real numbers of any size, plus and minus
  99. --so we need three candidates that can have a very wide range
  100. --but GA actually only gives us numbers in the range 0-1
  101. --how on earth do we produce such a huge range of numbers, using what GA gives us?
  102. --What I've done below is to combine two random numbers to produce each candidate, like so
  103. -- candidate = (rand1 - 0.5) / (rand2 - 0.5)
  104. --there are probably better ways, but this seems to work quite well
  105. --That's why I asked for 6 random numbers up above, rather than three
  106. function Fitness(t) --t is an array of 6 random numbers
  107. --create the numbers we need, using the random numbers
  108. local aa=(t[1] - 0.5)/(t[2] - 0.5)
  109. local bb=(t[3] - 0.5)/(t[4] - 0.5)
  110. local cc=(t[5] - 0.5)/(t[6] - 0.5)
  111. --now test them
  112. --I've done this very inefficently, calculating the same test results everytime
  113. --but this keeps it all in one place and makes it easy for you to replace it with your stuff
  114. --If I had to solve this function over a wider range of x, the variation in results could bias
  115. --the GA. For example, if I just calculate the squared error as I do below, and one of the
  116. --test answers is 3 and another is 33333, the first error is likely to be dominated by the second one
  117. --so the GA will do all it can to fix the second one, whereas the first one might be just as important
  118. --So in my own implementation of this, I dampened the results by dividing the answers by a carefully
  119. --chosen set of factors that reduced the differences between them, so the GA didn't ignore any of them
  120. local error=0
  121. for x=1,5 do
  122. local e=Fn(a,b,c,x)-Fn(aa,bb,cc,x)
  123. error=error+e*e
  124. end
  125. return error
  126. end
  127.  
  128. function draw()
  129. --we'll print error results after each iteration, when GA yields and the screen refreshes
  130. --to see the coefficients, we'll have to transform the random numbers like we did in Fitness
  131. --these results should all trend towards zero
  132. local aa=(BestSol[1] - 0.5)/(BestSol[2] - 0.5)
  133. local bb=(BestSol[3] - 0.5)/(BestSol[4] - 0.5)
  134. local cc=(BestSol[5] - 0.5)/(BestSol[6] - 0.5)
  135. print(currIter,"Err",string.format("%.1f",Best),"CoeffErr",
  136. string.format("%.1f",aa-a),
  137. string.format("%.1f",bb-b),
  138. string.format("%.1f",cc-c))
  139. coroutine.resume(cos) --important, this has to go somewhere to keep the iterations going
  140. end
  141.  
  142. --this function just calculates the quadratic
  143. function Fn(a,b,c,x)
  144. return a*x*x + b*x + c
  145. end
  146. --# GA
  147. --GA class
  148. --You can treat this like a black box
  149.  
  150. GA = class()
  151.  
  152. local nVar
  153. local nPop
  154. local pctCrossover
  155. local pctMutation
  156. local pctSample
  157. local replaceMethod
  158. local fnFitness
  159. local arrScore={}
  160. local arrVar={}
  161. local arrSort={}
  162. local bestPop
  163. local bestFitness
  164.  
  165. function GA:init(nVar1,nPop1,fnFitness1,fnClass)
  166. nVar=nVar1
  167. nPop=nPop1
  168. replaceMethod="Random"
  169. fnFitness=fnFitness1
  170. fnClass=fnClass1
  171. end
  172.  
  173. function GA:DoRun(r,c,m,s)
  174. pctCrossover=c
  175. pctMutation=m
  176. pctSample=s
  177. nCross = math.floor(pctCrossover * nPop)
  178. --if first iteration, create array ofrandom number
  179. if r==1 then
  180. arrVar={}
  181. for i=1,nPop do
  182. arrVar[i]={}
  183. for j=1,nVar do
  184. arrVar[i][j]=math.random()
  185. end
  186. --get score
  187. if fnClass==nil then
  188. arrScore[i]=fnFitness(arrVar[i])
  189. else
  190. arrScore[i]=fnFitness(fnClass,arrVar[i])
  191. end
  192. end
  193. arrSort=GA:SortLinked(arrScore)
  194. bestPop = arrSort[1]
  195. bestFitness = arrScore[bestPop]
  196. return arrVar[bestPop],bestFitness
  197. else
  198. for j = 1, nCross do
  199. GA:ReplacePop(arrSort[nPop + 1 - j])
  200. end
  201. arrSort=GA:SortLinked(arrScore)
  202. bestPop = arrSort[1]
  203. bestFitness = arrScore[bestPop]
  204. return arrVar[bestPop],bestFitness
  205. end
  206. end
  207.  
  208. function GA:ReplacePop(p)
  209. local p1
  210. local p2
  211. local x1
  212. local x2
  213. local x3
  214. x1 = math.floor(nPop * pctSample * math.random() + 1)
  215. x2 = math.floor(nPop * pctSample * math.random() + 1)
  216. x3 = math.floor(nPop * pctSample * math.random() + 1)
  217. if arrScore[x1] < arrScore[x2] then
  218. if arrScore[x1] < arrScore[x3] then p1 = x1 else p1 = x3 end
  219. else
  220. if arrScore[x2] < arrScore[x3] then p1 = x2 else p1 = x3 end
  221. end
  222. x1 = math.floor(nPop * pctSample * math.random() + 1)
  223. x2 = math.floor(nPop * pctSample * math.random() + 1)
  224. x3 = math.floor(nPop * pctSample * math.random() + 1)
  225. if arrScore[x1] < arrScore[x2] then
  226. if arrScore[x1] < arrScore[x3] then p2 = x1 else p2 = x3 end
  227. else
  228. if arrScore[x2] < arrScore[x3] then p2 = x2 else p2 = x3 end
  229. end
  230.  
  231. local j
  232. local Tmp1
  233. local Tmp2
  234. local rMethod
  235. if replaceMethod == "Random" then
  236. j = math.floor(math.random() * 2 + 1)
  237. if j==1 then
  238. rMethod = "EitherOr"
  239. else
  240. rMethod="2 Slices"
  241. end
  242. else
  243. rMethod = replaceMethod
  244. end
  245. if rMethod == "2 Slices" then
  246. j = math.floor(math.random() * nVar) + 1
  247. for i = 1, nVar do
  248. if math.random() < pctMutation then
  249. arrVar[p][i] = math.random()
  250. end
  251. end
  252. k=math.floor(math.random()*nVar+1)
  253. if k<1 then k=1 elseif k>=nVar then k=nVar end
  254. for i=1,k do
  255. arrVar[p][i] = arrVar[p1][i]
  256. end
  257. for i=k+1,nVar do
  258. arrVar[p][i] = arrVar[p2][i]
  259. end
  260. elseif rMethod=="EitherOr" then
  261. for i = 1, nVar do
  262. if math.random() < pctMutation then
  263. arrVar[p][i] = math.random()
  264. else
  265. if math.random()< 0.5 then
  266. arrVar[p][i] = arrVar[p1][i]
  267. else
  268. arrVar[p][i] = arrVar[p2][i]
  269. end
  270. end
  271. end
  272. end
  273.  
  274. for i = 1,nVar do
  275. if arrVar[p][i] < 0.000001 then
  276. arrVar[p][i] = 0.000001
  277. elseif arrVar[p][i]> 0.999999 then
  278. arrVar[p][i] = 0.999999
  279. end
  280. end
  281.  
  282. if fnClass==nil then
  283. arrScore[p]=fnFitness(arrVar[p])
  284. else
  285. arrScore[p]=fnFitness(fnClass,arrVar[p])
  286. end
  287. arrScore[p]=fnFitness(arrVar[p])
  288.  
  289. end
  290.  
  291. function GA:SortLinked(A)
  292. local switches
  293. local gap
  294. local Low
  295. local Num
  296. local hold
  297. Low = 1
  298. Num = #A
  299. sList={}
  300.  
  301. for ii = 1,Num do
  302. sList[ii] = ii
  303. end
  304.  
  305. gap = Num - Low + 1
  306. repeat
  307. gap = math.floor((gap / 13) * 10)
  308. if gap < 1 then gap = 1 end
  309. if gap == 9 or gap == 10 then gap = 11 end
  310. switches = 0
  311. for ii = Low, Num - gap do
  312. if A[sList[ii]] > A[sList[ii + gap]] then
  313. sList[ii],sList[ii + gap]=sList[ii + gap],sList[ii]
  314. switches = 1
  315. end
  316. end
  317. until switches + gap == 1
  318. return sList
  319. end
');