diff --git a/package-lock.json b/package-lock.json index d73778a..3a16b2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,9 +61,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -1368,9 +1368,9 @@ } }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -1499,6 +1499,7 @@ }, "buffer-from": { "version": "1.1.2", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "buffer-xor": { @@ -2279,10 +2280,12 @@ }, "source-map": { "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { "version": "0.5.20", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -2415,6 +2418,7 @@ }, "ws": { "version": "8.2.3", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "requires": {} }, "xtend": { diff --git a/project.clj b/project.clj index a2a8693..42642b1 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject net.clojars.lspector/propeller "0.2.3" +(defproject net.clojars.lspector/propeller "0.3.0" :description "Yet another Push-based genetic programming system in Clojure." :url "https://github.com/lspector/propeller" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/propeller/gp.cljc b/src/propeller/gp.cljc index 28f71dc..9ea78d6 100644 --- a/src/propeller/gp.cljc +++ b/src/propeller/gp.cljc @@ -13,7 +13,8 @@ [propeller.push.instructions.numeric] [propeller.push.instructions.polymorphic] [propeller.push.instructions.string] - [propeller.push.instructions.vector])) + [propeller.push.instructions.vector] + [propeller.selection :as selection])) (defn report "Reports information each generation." @@ -80,7 +81,10 @@ (partial error-function argmap training-data) population)) best-individual (first evaluated-pop) - best-individual-passes-ds (and downsample? (<= (:total-error best-individual) solution-error-threshold))] + best-individual-passes-ds (and downsample? (<= (:total-error best-individual) solution-error-threshold)) + argmap (if (= (:parent-selection argmap) :epsilon-lexicase) + (assoc argmap :epsilons (selection/epsilon-list evaluated-pop)) + argmap)] (if (:custom-report argmap) ((:custom-report argmap) evaluations evaluated-pop generation argmap) (report evaluations evaluated-pop generation argmap training-data)) diff --git a/src/propeller/selection.cljc b/src/propeller/selection.cljc old mode 100755 new mode 100644 index b89ab51..33797f9 --- a/src/propeller/selection.cljc +++ b/src/propeller/selection.cljc @@ -1,4 +1,5 @@ -(ns propeller.selection) +(ns propeller.selection + (:require [propeller.tools.math :as math-tools])) (defn tournament-selection "Selects an individual from the population using a tournament." @@ -38,10 +39,42 @@ (first individuals) (recur (+ tot (:fitness (first (rest individuals)))) (rest individuals)))))) +(defn epsilon-list + [pop] + (let [error-list (map :errors pop) + length (count (:errors (first pop)))] + (loop [epsilons [] i 0] + (if (= i length) + epsilons + (recur (conj epsilons + (math-tools/median-absolute-deviation + (map #(nth % i) error-list))) + (inc i)))))) + +(defn epsilon-lexicase-selection + "Selects an individual from the population using epsilon-lexicase selection." + [pop argmap] + (let [epsilons (:epsilons argmap)] + (loop [survivors pop + cases (shuffle (range (count (:errors (first pop)))))] + (if (or (empty? cases) + (empty? (rest survivors))) + (rand-nth survivors) + (let [min-err-for-case (apply min (map #(nth % (first cases)) + (map :errors survivors))) + epsilon (nth epsilons (first cases))] + (recur (filter #(<= (Math/abs (- (nth (:errors %) + (first cases)) + min-err-for-case)) + epsilon) + survivors) + (rest cases))))))) + (defn select-parent "Selects a parent from the population using the specified method." [pop argmap] (case (:parent-selection argmap) :tournament (tournament-selection pop argmap) :lexicase (lexicase-selection pop argmap) - :roulette (fitness-proportionate-selection pop argmap))) \ No newline at end of file + :roulette (fitness-proportionate-selection pop argmap) + :epsilon-lexicase (epsilon-lexicase-selection pop argmap))) diff --git a/src/propeller/session.cljc b/src/propeller/session.cljc index c9a159d..ead64f2 100755 --- a/src/propeller/session.cljc +++ b/src/propeller/session.cljc @@ -3,8 +3,6 @@ [propeller.gp :as gp] [propeller.selection :as selection] [propeller.variation :as variation] - [propeller.problems.simple-regression :as regression] - [propeller.problems.string-classification :as string-classif] [propeller.push.instructions :as instructions] [propeller.push.interpreter :as interpreter] [propeller.push.state :as state])) @@ -15,143 +13,58 @@ #_(interpreter/interpret-program '(1 2 :integer_add) (assoc state/empty-state :keep-history true) 1000) -;#_(interpreter/interpret-program -; '(3 3 :integer_eq :exec_if (1 "yes") (2 "no")) -; state/empty-state -; 1000) -; -;#_(interpreter/interpret-program -; '(:in1 :string_reverse 1 :string_take "?" :string_eq :exec_if -; (:in1 " I am asking." :string_concat) -; (:in1 " I am saying." :string_concat)) -; (assoc state/empty-state :input {:in1 "Can you hear me?"}) -; 1000) -; -;#_(interpreter/interpret-program -; '(:in1 :string_reverse 1 :string_take "?" :string_eq :exec_if -; (:in1 " I am asking." :string_concat) -; (:in1 " I am saying." :string_concat)) -; (assoc state/empty-state :input {:in1 "I can hear you."}) -; 1000) -; -;#_(genome/plushy->push -; (genome/make-random-plushy (instructions/get-stack-instructions #{:float :integer :exec :boolean}) 20)) -; -;#_(gp/gp {:instructions propeller.problems.software.number-io/instructions -; :error-function propeller.problems.software.number-io/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :variation {:umad 0.5 :crossover 0.5} -; :elitism false}) -; -;#_(gp/gp {:instructions propeller.problems.simple-regression/instructions -; :error-function propeller.problems.simple-regression/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :tournament -; :tournament-size 5 -; :umad-rate 0.01 -; :variation {:umad 1.0 -; :crossover 0.0} -; :elitism false}) -; -;#_(gp/gp {:instructions propeller.problems.simple-regression/instructions -; :error-function propeller.problems.simple-regression/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :tournament -; :tournament-size 5 -; :umad-rate 0.1 -; :variation {:umad 1.0 -; :crossover 0.0} -; :elitism false}) -; -; -;#_(gp/gp {:instructions propeller.problems.simple-regression/instructions -; :error-function propeller.problems.simple-regression/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :variation {:umad 1.0 -; :crossover 0.0} -; :elitism false}) -; -;#_(gp/gp {:instructions propeller.problems.simple-regression/instructions -; :error-function propeller.problems.simple-regression/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :diploid-flip-rate 0.1 -; :variation {:umad 0.8 -; :diploid-flip 0.2} -; :elitism false -; :diploid true}) -; -; -;#_(gp/gp {:instructions propeller.problems.software.smallest/instructions -; :error-function propeller.problems.software.smallest/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :diploid-flip-rate 0.1 -; :variation {;:umad 0.8 -; ;:diploid-flip 0.2 -; :umad 1 -; } -; :elitism false -; :diploid false}) -; -;#_(gp/gp {:instructions propeller.problems.software.smallest/instructions -; :error-function propeller.problems.software.smallest/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 200 ;100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :diploid-flip-rate 0.1 -; :variation {:umad 0.8 -; :diploid-flip 0.2 -; ;:umad 1 -; } -; :elitism false -; :diploid true}) -; -; -;(gp/gp {:instructions propeller.problems.string-classification/instructions -; :error-function propeller.problems.string-classification/error-function -; :max-generations 500 -; :population-size 500 -; :max-initial-plushy-size 100 -; :step-limit 200 -; :parent-selection :lexicase -; :tournament-size 5 -; :umad-rate 0.1 -; :diploid-flip-rate 0.1 -; :variation {:umad 0.8 -; :diploid-flip 0.2 -; } -; :elitism false -; :diploid true}) +#_(interpreter/interpret-program + '(3 3 :integer_eq :exec_if (1 "yes") (2 "no")) + state/empty-state + 1000) + +#_(interpreter/interpret-program + '(:in1 :string_reverse 1 :string_take "?" :string_eq :exec_if + (:in1 " I am asking." :string_concat) + (:in1 " I am saying." :string_concat)) + (assoc state/empty-state :input {:in1 "Can you hear me?"}) + 1000) + +#_(interpreter/interpret-program + '(:in1 :string_reverse 1 :string_take "?" :string_eq :exec_if + (:in1 " I am asking." :string_concat) + (:in1 " I am saying." :string_concat)) + (assoc state/empty-state :input {:in1 "I can hear you."}) + 1000) + +#_(genome/plushy->push + (genome/make-random-plushy (instructions/get-stack-instructions #{:float :integer :exec :boolean}) 20)) + +#_(require '[propeller.problems.simple-regression :as regression]) + +#_(gp/gp {:instructions regression/instructions + :error-function regression/error-function + :training-data (:train regression/train-and-test-data) + :testing-data (:test regression/train-and-test-data) + :max-generations 500 + :population-size 500 + :max-initial-plushy-size 100 + :step-limit 200 + :parent-selection :tournament + :tournament-size 5 + :umad-rate 0.01 + :variation {:umad 1.0 + :crossover 0.0} + :elitism false}) + +#_(require '[propeller.problems.string-classification :as sc]) + +#_(gp/gp {:instructions sc/instructions + :error-function sc/error-function + :training-data (:train sc/train-and-test-data) + :testing-data (:test sc/train-and-test-data) + :max-generations 500 + :population-size 500 + :max-initial-plushy-size 100 + :step-limit 200 + :parent-selection :lexicase + :tournament-size 5 + :umad-rate 0.1 + :variation {:umad 0.5 :crossover 0.5} + :elitism false}) + diff --git a/src/propeller/tools/math.cljc b/src/propeller/tools/math.cljc old mode 100755 new mode 100644 index 0e5f6ac..8aba2d5 --- a/src/propeller/tools/math.cljc +++ b/src/propeller/tools/math.cljc @@ -10,6 +10,28 @@ "returns 1 if number is nonzero, 0 otherwise" [x] (if (zero? x) 0 1)) +(defn mean [coll] + (let [sum (apply + coll) + count (count coll)] + (if (pos? count) + (/ sum (float count)) + 0.0))) + +(defn median [coll] + (let [sorted (sort coll) + cnt (count sorted) + halfway (quot cnt 2.0)] + (if (odd? cnt) + (nth sorted halfway) + (let [bottom (dec halfway) + bottom-val (nth sorted bottom) + top-val (nth sorted halfway)] + (mean [bottom-val top-val]))))) + +(defn median-absolute-deviation + [coll] + (let [median-val (median coll)] + (median (map #(Math/abs (- % median-val)) coll)))) (defn abs "Returns the absolute value of a number." diff --git a/test/propeller/tools/math_test.cljc b/test/propeller/tools/math_test.cljc index c189536..541fb9a 100644 --- a/test/propeller/tools/math_test.cljc +++ b/test/propeller/tools/math_test.cljc @@ -60,4 +60,16 @@ (t/is (m/approx= (m/tan (/ m/PI 4)) 1.0 0.00001)) (t/is (= (m/tan 0) 0.0))) +(t/deftest mean-test + (t/is (= (m/mean []) 0.0)) + (t/is (= (m/mean [1 2 3 4 5]) 3.0)) + (t/is (= (m/mean '(6 7 8 9 10)) 8.0) 8.0)) + +(t/deftest median-test + (t/is (= (m/median [1 2 3 4 5]) 3)) + (t/is (= (m/median '(1 2 3 4 5 6)) 3.5))) + +(t/deftest median-absolute-deviation-test + (t/is (= (m/median-absolute-deviation [1 2 3 4 5]) 1)) + (t/is (= (m/median-absolute-deviation '(1 2 3 4 5 6)) 1.5))) diff --git a/yarn.lock b/yarn.lock index ad8ef05..903eed0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,9 +21,9 @@ assert@^1.1.1: util "0.10.3" async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14"