diff --git a/src/propeller/gp.cljc b/src/propeller/gp.cljc index 9668a6f..8f8b24b 100644 --- a/src/propeller/gp.cljc +++ b/src/propeller/gp.cljc @@ -5,6 +5,7 @@ [propeller.simplification :as simplification] [propeller.variation :as variation] [propeller.downsample :as downsample] + [propeller.hyperselection :as hyperselection] [propeller.push.instructions.bool] [propeller.push.instructions.character] [propeller.push.instructions.code] @@ -50,18 +51,18 @@ indexed-training-data (downsample/assign-indices-to-data (downsample/initialize-case-distances argmap))] (prn {:ind-training-data (first indexed-training-data)}) (let [training-data (if (= (:parent-selection argmap) :ds-lexicase) - (case (:ds-function argmap) - :case-avg (downsample/select-downsample-avg indexed-training-data argmap) - :case-maxmin (downsample/select-downsample-maxmin indexed-training-data argmap) - (downsample/select-downsample-random indexed-training-data argmap)) + (case (:ds-function argmap) + :case-avg (downsample/select-downsample-avg indexed-training-data argmap) + :case-maxmin (downsample/select-downsample-maxmin indexed-training-data argmap) + (downsample/select-downsample-random indexed-training-data argmap)) indexed-training-data) ;defaults to random parent-reps (if (zero? (mod generation ds-parent-gens)) ;every ds-parent-gens generations (take (* ds-parent-rate (count population)) (shuffle population)) '()) ;else just empty list rep-evaluated-pop (sort-by :total-error - (mapper - (partial error-function argmap indexed-training-data) - parent-reps)) + (mapper + (partial error-function argmap indexed-training-data) + parent-reps)) ds-evaluated-pop (sort-by :total-error (mapper (partial error-function argmap training-data) @@ -93,12 +94,13 @@ nil ;; :else (recur (inc generation) - (if (:elitism argmap) - (conj (repeatedly (dec population-size) - #(variation/new-individual ds-evaluated-pop argmap)) - (first ds-evaluated-pop)) - (repeatedly population-size - #(variation/new-individual ds-evaluated-pop argmap))) + (let [reindexed-pop (hyperselection/reindex-pop ds-evaluated-pop)] + (if (:elitism argmap) + (hyperselection/log-hyperselection-and-ret (conj (repeatedly (dec population-size) + #(variation/new-individual reindexed-pop argmap)) + (first reindexed-pop))) + (hyperselection/log-hyperselection-and-ret (repeatedly population-size ;need to count occurance of each parent, and reset IDs + #(variation/new-individual reindexed-pop argmap))))) (if (= (:parent-selection argmap) :ds-lexicase) (if (zero? (mod generation ds-parent-gens)) (downsample/update-case-distances rep-evaluated-pop indexed-training-data indexed-training-data) ; update distances every ds-parent-gens generations diff --git a/src/propeller/hyperselection.cljc b/src/propeller/hyperselection.cljc new file mode 100644 index 0000000..7d406d8 --- /dev/null +++ b/src/propeller/hyperselection.cljc @@ -0,0 +1,35 @@ +(ns propeller.hyperselection) + +(defn sum-list-map-indices + "sums a list of maps that have the :index property's index multiplicity" + [list-of-maps] + (->> list-of-maps + (map #(:index %)) + frequencies)) + +(defn ordered-freqs + "takes a map from indices to frequencies, and returns a sorted list of the frequences is descencing order" + [freqs] + (->> freqs + vals + (sort >))) + +(defn normalize-list-by-popsize [popsize lst] + (map #(double (/ % popsize)) lst)) + +(defn hyperselection-track + "outputs a normalized list of the hyperselection proportion for each parent" + [new-pop] + (->> new-pop + sum-list-map-indices + ordered-freqs + (normalize-list-by-popsize (count new-pop)))) + +(defn log-hyperselection-and-ret [new-pop] + (prn {:hyperselection (hyperselection-track new-pop)}) + new-pop) + +(defn reindex-pop + "assigns each member of the population a unique index before selection to track hyperselection" + [pop] + (map (fn [indiv index] (assoc indiv :index index)) pop (range (count pop)))) \ No newline at end of file diff --git a/src/propeller/variation.cljc b/src/propeller/variation.cljc index 51b0398..dcd0be5 100755 --- a/src/propeller/variation.cljc +++ b/src/propeller/variation.cljc @@ -130,6 +130,8 @@ "Returns a new individual produced by selection and variation of individuals in the population." [pop argmap] + (let [umad-parent (selection/select-parent pop argmap) + parent-ind (:index umad-parent)] ;this is a hack to log hyperselection, only works for umad {:plushy (let [r (rand) op (loop [accum 0.0 @@ -140,7 +142,7 @@ (if (>= (+ accum prob1) r) op1 (recur (+ accum prob1) - (rest ops-probs))))))] + (rest ops-probs))))))] (case op :crossover (crossover @@ -153,7 +155,7 @@ (:plushy (selection/select-parent pop argmap))) ; :umad - (-> (:plushy (selection/select-parent pop argmap)) + (-> (:plushy umad-parent) (uniform-addition (:instructions argmap) (:umad-rate argmap)) (uniform-deletion (:umad-rate argmap))) ; @@ -216,4 +218,5 @@ :else (throw #?(:clj (Exception. (str "No match in new-individual for " op)) :cljs (js/Error - (str "No match in new-individual for " op))))))}) + (str "No match in new-individual for " op)))))) + :index parent-ind})) diff --git a/test/propeller/utils_test.cljc b/test/propeller/utils_test.cljc index 0555c2f..55a3a66 100644 --- a/test/propeller/utils_test.cljc +++ b/test/propeller/utils_test.cljc @@ -2,7 +2,8 @@ (:require [clojure.test :as t] [propeller.utils :as u] [propeller.simplification :as s] - [propeller.downsample :as ds])) + [propeller.downsample :as ds] + [propeller.hyperselection :as hs])) (t/deftest first-non-nil-test (t/is (= 1 (u/first-non-nil '(1 2 3)))) @@ -159,10 +160,34 @@ (t/deftest case-maxmin-test (t/testing "case-maxmin selects correct downsample" - (t/is (let [selected (ds/select-downsample-maxmin '({:input1 [0] :output1 [10] :index 0 :distances [0 5 0 0 0]} - {:input1 [1] :output1 [11] :index 1 :distances [0 5 0 0 0]} - {:input1 [2] :output1 [12] :index 2 :distances [5 5 5 5 5]} - {:input1 [3] :output1 [13] :index 3 :distances [0 5 0 0 0]} - {:input1 [4] :output1 [14] :index 4 :distances [0 5 0 0 0]}) - {:downsample-rate 0.4 :case-t-size 5})] - (or (= (:index (first selected)) 1) (= (:index (second selected)) 1)))))) \ No newline at end of file + (t/is (let [selected (ds/select-downsample-maxmin + '({:input1 [0] :output1 [10] :index 0 :distances [0 5 0 0 0]} + {:input1 [1] :output1 [11] :index 1 :distances [0 5 0 0 0]} + {:input1 [2] :output1 [12] :index 2 :distances [5 5 5 5 5]} + {:input1 [3] :output1 [13] :index 3 :distances [0 5 0 0 0]} + {:input1 [4] :output1 [14] :index 4 :distances [0 5 0 0 0]}) + {:downsample-rate 0.4 :case-t-size 5})] + (or (= (:index (first selected)) 1) (= (:index (second selected)) 1)))))) + + +(t/deftest hyperselection-test + (let [parents1 '({:blah 3 :index 1} {:blah 3 :index 1} + {:blah 3 :index 1} {:blah 3 :index 2}) + parents2 '({:plushy 2 :index 0} {:blah 3 :index 2} + {:blah 3 :index 3} {:index 4}) + emptyparents '({:blah 1} {:blah 1} {:blah 1})] + (t/testing "sum-list-map-indices function works correctly" + (t/is (= {1 3, 2 1} (hs/sum-list-map-indices parents1))) + (t/is (= {0 1, 2 1, 3 1, 4 1} (hs/sum-list-map-indices parents2)))) + (t/testing "ordered-freqs function works correctly" + (t/is (= '(3 1) (hs/ordered-freqs (hs/sum-list-map-indices parents1)))) + (t/is (= '(1 1 1 1) (hs/ordered-freqs (hs/sum-list-map-indices parents2))))) + (t/testing "hyperselection-track works correctly" + (t/is (= '(0.75 0.25) (hs/hyperselection-track parents1))) + (t/is (= '(0.25 0.25 0.25 0.25) (hs/hyperselection-track parents2)))) + (t/testing "reindex-pop works correctly" + (t/is (= '({:blah 3 :index 0} {:blah 3 :index 1} + {:blah 3 :index 2} {:blah 3 :index 3}) (hs/reindex-pop parents1))) + (t/is (= '({:plushy 2 :index 0} {:blah 3 :index 1} + {:blah 3 :index 2} {:index 3}) (hs/reindex-pop parents2))) + (t/is (= '({:blah 1 :index 0} {:blah 1 :index 1} {:blah 1 :index 2}) (hs/reindex-pop emptyparents))))))