-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGP_Project
More file actions
246 lines (215 loc) · 8.55 KB
/
GP_Project
File metadata and controls
246 lines (215 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
(ns genetic-programming.core
(:require [clojure.zip :as zip]))
(defn make-program-into-fn
"Takes a GP program represented as a list, with input x,
and transforms it into a function that can be called on an input.
NOTE: If your GP uses variables other than x, will need to change
the argument list below to something other than [x]!"
[program]
(eval (list 'fn '[x] program)))
(def target
(map #(vector % (+ (* % % %) % 3))
(range -10.0 10.0)))
(def instructions
'{+ 2
* 2
- 2
inc 1})
(def line-separator
"-------------------------------------------------------------------------")
(defn program-size
"Finds the size of the program, i.e. number of nodes in its tree."
[prog]
(if (not (seq? prog))
1
(count (flatten prog))))
(defn pd
[num denom]
(if (zero? denom)
0
(/ num denom)))
(defn select-terminal
[]
(let [random (rand-int 2)]
(if (zero? random)
(rand-nth (concat (list(rand 11)) (list(* -1 (rand 11)))))
'x)))
(defn select-fun
[]
(rand-nth '(+ - * pd)))
(defn generate-prog
[depth]
(if (zero? depth)
(select-terminal)
(list (select-fun)
(generate-prog (dec depth))
(generate-prog (dec depth)))))
(defn generate-pop
[pop-size]
(take pop-size
(repeatedly #(generate-prog (rand-int 2)))))
(defn eval-fitness
"Evaluates the error of an individual (the lower, the better)."
[prog]
(let [prog-function (make-program-into-fn prog)]
(reduce + (map (fn [[var1 var2]]
(Math/abs (-' (prog-function var1) var2)))
target))))
(defn select-random-subtree
"Given a program, selects a random subtree and returns it."
([prog]
(select-random-subtree prog (rand-int (program-size prog))))
([prog subtree-index]
(cond
(not (seq? prog)) prog
(and (zero? subtree-index)
(some #{(first prog)} (keys instructions))) prog
(< subtree-index (program-size (first prog))) (recur (first prog)
subtree-index)
:else (recur (rest prog)
(- subtree-index (program-size (first prog)))))))
(defn replace-random-subtree
"Given a program and a replacement-subtree, replace a random node
in the program with the replacement-subtree."
([prog replacement-subtree]
(replace-random-subtree prog replacement-subtree (rand-int (program-size prog))))
([prog replacement-subtree subtree-index]
(cond
(not (seq? prog)) replacement-subtree
(zero? subtree-index) replacement-subtree
:else (map (fn [element start-index]
(if (<= start-index
subtree-index
(+ start-index -1 (program-size element)))
(replace-random-subtree element
replacement-subtree
(- subtree-index start-index))
element))
prog
(cons 0 (reductions + (map program-size prog)))))))
(defn mutate
[prog]
(replace-random-subtree prog (generate-prog 2) (rand-int (program-size prog))))
(defn crossover
[prog1 prog2]
(replace-random-subtree prog1
(select-random-subtree prog2 (rand-int (program-size prog2)))
(rand-int (program-size prog1))))
(defn inject
"Returns a copy of individual i with new inserted randomly somwhere within it (replacing something else)."
[new i]
(if (seq? i)
(if (zero? (rand-int (count (flatten i))))
new
(if (< (rand)
(/ (program-size (nth i 1))
(- (program-size i) 1)))
(list (nth i 0) (inject new (nth i 1)) (nth i 2))
(list (nth i 0) (nth i 1) (inject new (nth i 2)))))
new))
(defn extract
"Returns a random subexpression of individual i."
[i]
(if (seq? i)
(if (zero? (rand-int (count (flatten i))))
i
(if (< (rand) (/ (program-size (nth i 1))
(- (program-size i)) 1))
(extract (nth i 1))
(extract (nth i 2))))
i))
(defn mutate
[i]
(inject (generate-prog 2) i))
(defn crossover
[i j]
(inject (extract j) i))
(defn at-index
"Returns a subtree of tree indexed by point-index in a depth first traversal."
[tree point-index]
(let [index (mod (Math/abs point-index) (program-size tree))
zipper (zip/seq-zip tree)]
(loop [z zipper i index]
(if (zero? i)
(zip/node z)
(if (seq? (zip/node z))
(recur (zip/next (zip/next z)) (dec i))
(recur (zip/next z) (dec i)))))))
(defn insert-at-index
"Returns a copy of tree with the subtree formerly indexed by
point-index (in a depth-first traversal) replaced by new-subtree."
[tree point-index new-subtree]
(let [index (mod (Math/abs point-index) (program-size tree))
zipper (zip/seq-zip tree)]
(loop [z zipper i index]
(if (zero? i)
(zip/root (zip/replace z new-subtree))
(if (seq? (zip/node z))
(recur (zip/next (zip/next z)) (dec i))
(recur (zip/next z) (dec i)))))))
(defn mutate
[i]
(insert-at-index i
(rand-int (program-size i))
(generate-prog 2)))
(defn crossover
[i j]
(insert-at-index i
(rand-int (program-size i))
(at-index j (rand-int (program-size j)))))
(defn pair-individuals-errors
"Returns a vector of [individual error] pairs."
[population]
(vec (map #(vector % (eval-fitness %)) population)))
(defn sort-population
"Sorts the population by ascending order of errors."
[population]
(let [individuals-errors (pair-individuals-errors population)]
(vec (map first ; get the individuals only (discards the error from [individual error])
(sort #(< (second %1) (second %2)) individuals-errors)))))
(defn random-selection
"Selects a random individual from the population of programs, without evaluating
the fitness of the programs."
[population]
(rand-nth population))
(defn tournament-selection
"Selects an individual from the population of programs using tournament-like selection."
[population tournament-size]
(let [index-value (apply min (take tournament-size
(repeatedly #(rand-int (count population)))))]
; index-value is the lowest integer returned by getting index positions repeatedly from
; the population. The individual at this index position is the fittest as compared to
; others in this tournament (assuming that the population is already sorted).
(nth population index-value)))
(defn evolve
[population-size max-generations mutation-ratio crossover-ratio error-threshold]
(if (> (+ mutation-ratio crossover-ratio) 1)
(println "Error: The sum of mutation and crossover ratios should be <= 1.")
(loop [generation-number 0
population (sort-population (generate-pop population-size))]
(let [best-individual (first population)
best-individual-error (eval-fitness best-individual)]
(println line-separator)
(println "Generation number:" generation-number)
(println "Best program so far:" best-individual)
(println "Best error so far:" best-individual-error)
(cond
(< best-individual-error error-threshold) (println line-separator
"\nEvolution successful! \nProgram: "
best-individual
"\nError:" best-individual-error)
(= generation-number max-generations) (println line-separator
"\nMaximum number of generations exceeded! \nBest program so far:"
best-individual
"\nError:" best-individual-error)
:else (recur
(inc generation-number)
(sort-population
(for [i (range population-size)]
(let [genetic-operator (rand)]
(cond
(< genetic-operator mutation-ratio) (mutate (tournament-selection population 5))
(< genetic-operator (+ mutation-ratio crossover-ratio))
(crossover (tournament-selection population 5) (tournament-selection population 5))
:else (random-selection population)))))))))))
(evolve 1000 50 0.5 0.4 0.1)