tutorial-3-evolving-models.lisp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. (require :asdf)
  2. (require :alexandria)
  3. (require :gems)
  4. ;; --------------------------------------------------------------------------
  5. ;; Global parameters / utilities
  6. ;; -- these parameters control the experimental settings
  7. (defvar *good-model-threshold* 0.1) ; based on overall fitness
  8. ;; for GP system
  9. (defvar *population-size* 500) ; size of population
  10. (defvar *total-generations* 500) ; fixed number of iterations
  11. ;; The following parameters are used within the definition of the fitness
  12. ;; function to fine-tune the individual objective functions.
  13. (defvar *propn-fitness-accuracy* 0.7) ; proportion of f_a
  14. (defvar *propn-fitness-time* 0.2) ; proportion of f_t
  15. (defvar *propn-fitness-size* 0.1) ; proportion of f_s
  16. (defvar *size-ps* 100) ; program size scaling parameter
  17. (defvar *time-rt* 767) ; response time scaling parameter
  18. ;; Return new list of individuals, with duplicates and time-only-code removed.
  19. ;; Note - as dotted list required, we cannot include this in gems:clean-individuals
  20. (defun clean-individuals-time (individuals run-experiment)
  21. (remove-duplicates
  22. (mapcar #'(lambda (individual)
  23. (gems:make-individual
  24. :fitness (gems:individual-fitness individual)
  25. :extras (gems:individual-extras individual)
  26. :tree (gems:replace-timeonly-code
  27. (gems:individual-tree individual)
  28. run-experiment
  29. '((input-target . wait-input)
  30. (input-left . wait-input)
  31. (input-right . wait-input)
  32. (respond-left . wait-output)
  33. (respond-right . wait-output)
  34. (compare-1-2 . wait-cognitive)
  35. (compare-1-3 . wait-cognitive)
  36. (compare-2-3 . wait-cognitive)
  37. (put-stm . wait-cognitive)
  38. (access-stm-1 . wait-stm)
  39. (access-stm-2 . wait-stm)
  40. (access-stm-3 . wait-stm)))))
  41. individuals)
  42. :key #'gems:individual-tree
  43. :test #'equalp))
  44. ;; --------------------------------------------------------------------------
  45. ;; Task Definition: DMTS
  46. ;; Experiment times for
  47. ;; -- when to end presenting the target
  48. (defconstant end-target 1000)
  49. ;; -- when to start showing the two inputs
  50. (defconstant start-input (+ end-target 500))
  51. ;; Data for experiment - in form (target left right)
  52. (defvar *data* '((1 2 1) (1 1 2) (1 3 1) (1 1 3) (1 4 1) (1 1 4) (1 5 1) (1 1 5) (1 6 1) (1 1 6)
  53. (2 1 2) (2 2 1) (2 3 2) (2 2 3) (2 4 2) (2 2 4) (2 5 2) (2 2 5) (2 6 2) (2 2 6)
  54. (3 1 3) (3 3 1) (3 2 3) (3 3 2) (3 4 3) (3 3 4) (3 5 3) (3 3 5) (3 6 3) (3 3 6)
  55. (4 1 4) (4 4 1) (4 2 4) (4 4 2) (4 3 4) (4 4 2) (4 5 4) (4 4 5) (4 6 4) (4 4 6)
  56. (5 1 5) (5 5 1) (5 2 5) (5 5 2) (5 3 5) (5 5 3) (5 4 5) (5 5 4) (5 6 5) (5 5 6)
  57. (6 1 6) (6 6 1) (6 2 6) (6 6 2) (6 3 6) (6 6 3) (6 4 6) (6 6 4) (6 5 6) (6 6 5)))
  58. ;; Holds information about results of experiment
  59. (defstruct result
  60. inputs response accuracy timing)
  61. ;; Given the three inputs, compute the target response
  62. (defun target-response (inputs)
  63. (if (= (first inputs) (second inputs))
  64. "L"
  65. "R"))
  66. ;; Run a single experiment against the given program, returning information on performance.
  67. (defun run-experiment (program)
  68. (let ((results '())
  69. (expt-data (alexandria:shuffle *data*)))
  70. (dolist (input expt-data)
  71. (let ((md (make-model :clock 0 :current 0 :stm '(0 0 0)
  72. :timings (make-timings)
  73. :inputs input :response "-")))
  74. (interpret program md)
  75. (let ((result (make-result :inputs input :response "-" :accuracy 0 :timing 0)))
  76. (when (> (model-clock md) start-input) ; when clock is after allowed time for response
  77. (setf (result-response result) (model-response md)) ; record model's response
  78. (setf (result-accuracy result) ; record whether it is correct or not
  79. (if (string= (result-response result) (target-response input))
  80. 1
  81. 0))
  82. (setf (result-timing result) (- (model-clock md) start-input)) ; record the response time
  83. )
  84. (push result results))))
  85. results))
  86. ;; --------------------------------------------------------------------------
  87. ;; Search Space: Cognitive Model definition
  88. ;; Defines the timings of different operator groups
  89. (defstruct timings
  90. (input 100) ;; perception + attend
  91. (output 140) ;; intend + movement
  92. (cognitive 70) ;; basic cognitive process
  93. (stm 50) ;; basic STM process
  94. (syntax 0) ;; prog2, if etc - different from cognitive operators
  95. )
  96. ;; Defines the state of the model
  97. (defstruct model
  98. clock current stm timings ; base model
  99. inputs response ; I/O requirements for DMTS task
  100. )
  101. ;; These convenience functions allow interpret/display-pseudocode to
  102. ;; work with both s-expressions and syntax-tree:node structures.
  103. ;; Returns label of given operator
  104. (defun operator-label (operator)
  105. (typecase operator
  106. (list
  107. (intern (symbol-name (first operator)) "KEYWORD"))
  108. (syntax-tree:node
  109. (intern (symbol-name (syntax-tree:node-label operator)) "KEYWORD"))
  110. (otherwise
  111. (error "Invalid operator type: ~a~&" operator))))
  112. ;; Returns children of given operator
  113. (defun operator-children (operator)
  114. (typecase operator
  115. (list
  116. (rest operator))
  117. (syntax-tree:node
  118. (syntax-tree:node-children operator))
  119. (otherwise
  120. (error "Invalid operator type"))))
  121. ;; Collect results from running model (defined by operator (program) + md).
  122. (defun interpret (operator md)
  123. (when (syntax-tree:node-p operator)
  124. (incf (syntax-tree:node-entries operator)))
  125. (unless (> (model-clock md) 10000) ; time-out - adjust this if required
  126. (case (operator-label operator)
  127. (:input-left
  128. (incf (model-clock md) (timings-input (model-timings md)))
  129. (when (> (model-clock md) start-input)
  130. (setf (model-current md) (second (model-inputs md)))))
  131. (:input-right
  132. (incf (model-clock md) (timings-input (model-timings md)))
  133. (when (> (model-clock md) start-input)
  134. (setf (model-current md) (third (model-inputs md)))))
  135. (:input-target
  136. (incf (model-clock md) (timings-input (model-timings md)))
  137. (when (<= (model-clock md) end-target)
  138. (setf (model-current md) (first (model-inputs md)))))
  139. (:respond-left
  140. (incf (model-clock md) (timings-output (model-timings md)))
  141. (when (> (model-clock md) start-input)
  142. (setf (model-response md) "L")))
  143. (:respond-right
  144. (incf (model-clock md) (timings-output (model-timings md)))
  145. (when (> (model-clock md) start-input)
  146. (setf (model-response md) "R")))
  147. (:access-stm-1
  148. (incf (model-clock md) (timings-stm (model-timings md)))
  149. (setf (model-current md) (first (model-stm md))))
  150. (:access-stm-2
  151. (incf (model-clock md) (timings-stm (model-timings md)))
  152. (setf (model-current md) (second (model-stm md))))
  153. (:access-stm-3
  154. (incf (model-clock md) (timings-stm (model-timings md)))
  155. (setf (model-current md) (third (model-stm md))))
  156. (:compare-1-2
  157. (incf (model-clock md) (timings-cognitive (model-timings md)))
  158. (setf (model-current md)
  159. (if (= (first (model-stm md)) (second (model-stm md))) 1 0)))
  160. (:compare-2-3
  161. (incf (model-clock md) (timings-cognitive (model-timings md)))
  162. (setf (model-current md)
  163. (if (= (second (model-stm md)) (third (model-stm md))) 1 0)))
  164. (:compare-1-3
  165. (incf (model-clock md) (timings-cognitive (model-timings md)))
  166. (setf (model-current md)
  167. (if (= (first (model-stm md)) (third (model-stm md))) 1 0)))
  168. (:nil
  169. (incf (model-clock md) (timings-cognitive (model-timings md)))
  170. (setf (model-current md) 0))
  171. (:put-stm
  172. (incf (model-clock md) (timings-stm (model-timings md)))
  173. (setf (model-stm md)
  174. (if (= 3 (length (model-stm md)))
  175. (list (model-current md)
  176. (first (model-stm md))
  177. (second (model-stm md)))
  178. (cons (model-current md)
  179. (model-stm md)))))
  180. (:dotimes-2
  181. (incf (model-clock md) (timings-syntax (model-timings md)))
  182. (dotimes (i 2)
  183. (interpret (first (operator-children operator)) md)))
  184. (:dotimes-3
  185. (incf (model-clock md) (timings-syntax (model-timings md)))
  186. (dotimes (i 3)
  187. (interpret (first (operator-children operator)) md)))
  188. (:dotimes-5
  189. (incf (model-clock md) (timings-syntax (model-timings md)))
  190. (dotimes (i 5)
  191. (interpret (first (operator-children operator)) md)))
  192. (:if
  193. (incf (model-clock md) (timings-syntax (model-timings md)))
  194. (interpret (first (operator-children operator)) md)
  195. (if (not (zerop (model-current md))) ; 0 is false, other numbers true
  196. (interpret (second (operator-children operator)) md)
  197. (interpret (third (operator-children operator)) md)))
  198. (:prog2
  199. (incf (model-clock md) (timings-syntax (model-timings md)))
  200. (interpret (first (operator-children operator)) md)
  201. (interpret (second (operator-children operator)) md))
  202. (:prog3
  203. (incf (model-clock md) (timings-syntax (model-timings md)))
  204. (interpret (first (operator-children operator)) md)
  205. (interpret (second (operator-children operator)) md)
  206. (interpret (third (operator-children operator)) md))
  207. (:prog4
  208. (incf (model-clock md) (timings-syntax (model-timings md)))
  209. (interpret (first (operator-children operator)) md)
  210. (interpret (second (operator-children operator)) md)
  211. (interpret (third (operator-children operator)) md)
  212. (interpret (fourth (operator-children operator)) md))
  213. (:wait-25
  214. (incf (model-clock md) 25))
  215. (:wait-50
  216. (incf (model-clock md) 50))
  217. (:wait-100
  218. (incf (model-clock md) 100))
  219. (:wait-200
  220. (incf (model-clock md) 200))
  221. (:wait-1000
  222. (incf (model-clock md) 1000))
  223. (:wait-1500
  224. (incf (model-clock md) 1500))
  225. ;; wait operators, used for simplifying code
  226. (:wait-input
  227. (incf (model-clock md) (timings-input (model-timings md))))
  228. (:wait-output
  229. (incf (model-clock md) (timings-output (model-timings md))))
  230. (:wait-cognitive
  231. (incf (model-clock md) (timings-cognitive (model-timings md))))
  232. (:wait-stm
  233. (incf (model-clock md) (timings-stm (model-timings md))))
  234. (otherwise ; error if comes across an unknown operator
  235. (error "interpret: unknown operator ~a" (operator-label operator))))))
  236. ;; -- accuracy calculations for each fitness component
  237. (defvar *phase* 1) ; number of current phase
  238. ;; Computes the f_a objective function: 95.7% is target mean accuracy in Chao et al.
  239. (defun fitness-accuracy (performance)
  240. (/ (abs (- 0.957 performance))
  241. 0.957))
  242. ;; Computes the f_t objective function: 767ms is target mean response time in Chao et al.
  243. (defun fitness-time (response-time)
  244. (gems:half-sigmoid (/ (abs (- response-time 767))
  245. *time-rt*)))
  246. ;; Computes the f_s objective function.
  247. (defun fitness-size (program-size)
  248. (gems:half-sigmoid (/ program-size *size-ps*)))
  249. ;; Computes the fitness for current phase
  250. (defun fitness-for-phase (f-a f-t f-s)
  251. (case *phase*
  252. (1 ; single objective
  253. f-a)
  254. (2 ; two objectives
  255. (/ (+ (* *propn-fitness-accuracy* f-a)
  256. (* *propn-fitness-time* f-t))
  257. (+ *propn-fitness-accuracy* *propn-fitness-time*)))
  258. (otherwise ; all three objectives
  259. (+ (* *propn-fitness-accuracy* f-a)
  260. (* *propn-fitness-time* f-t)
  261. (* *propn-fitness-size* f-s)))))
  262. ;; Computes fitness, using the phases
  263. (defun overall-phased-fitness (f-a f-t f-s)
  264. (when (and (< *phase* 3)
  265. (< (fitness-for-phase f-a f-t f-s) *good-model-threshold*))
  266. (incf *phase*))
  267. (fitness-for-phase f-a f-t f-s))
  268. ;; runs experiment on a single program
  269. (defun evaluate-program (individual)
  270. (let* ((program (gems:individual-tree individual))
  271. (results (run-experiment program))
  272. (accuracy (alexandria:mean (mapcar #'result-accuracy results)))
  273. (f-a (fitness-accuracy accuracy))
  274. (response-time (alexandria:mean (mapcar #'result-timing results)))
  275. (f-t (fitness-time response-time))
  276. (program-size (gems:program-size program))
  277. (f-s (fitness-size program-size)))
  278. (values ; overall-fitness, optional extra information
  279. (overall-phased-fitness f-a f-t f-s)
  280. (list accuracy f-a response-time f-t program-size f-s *phase*) ; extra information
  281. )))
  282. ;; Returns a dotted list holding the available operators and number of children.
  283. (defun operator-set ()
  284. '((INPUT-LEFT . 0)
  285. (INPUT-RIGHT . 0)
  286. (INPUT-TARGET . 0)
  287. (RESPOND-LEFT . 0)
  288. (RESPOND-RIGHT . 0)
  289. (ACCESS-STM-1 . 0)
  290. (ACCESS-STM-2 . 0)
  291. (ACCESS-STM-3 . 0)
  292. (COMPARE-1-2 . 0)
  293. (COMPARE-2-3 . 0)
  294. (COMPARE-1-3 . 0)
  295. (NIL . 0)
  296. (PUT-STM . 0)
  297. (DOTIMES-2 . 1)
  298. (DOTIMES-3 . 1)
  299. (DOTIMES-5 . 1)
  300. (IF . 3)
  301. (PROG2 . 2)
  302. (PROG3 . 3)
  303. (PROG4 . 4)
  304. (WAIT-25 . 0)
  305. (WAIT-50 . 0)
  306. (WAIT-100 . 0)
  307. (WAIT-200 . 0)
  308. (WAIT-1000 . 0)
  309. (WAIT-1500 . 0)))
  310. ;; Runs the GP system with given parameters, results logged to files.
  311. (defun run-gp (&key (logger nil)) ; logger function
  312. (setf *phase* 1) ; initial phase for phased-evolution
  313. (gems:launch (operator-set) #'evaluate-program
  314. :total-generations *total-generations*
  315. :population-size *population-size*
  316. :initial-depth 1
  317. :maximum-depth 10
  318. :elitism t
  319. :type :steady-state
  320. :logger logger))
  321. ;; --------------------------------------------------------------------------
  322. ;; Simulation experiments
  323. (defun run-expt (name)
  324. (run-gp :logger (gems:combine-loggers
  325. (gems:make-logger (format nil "log-~a.csv" name)
  326. :if-exists :supersede)
  327. (gems:make-logger (format nil "population-~a.yml" name)
  328. :name name
  329. :kind :trace
  330. :filter #'(lambda (gen) (= gen *total-generations*))
  331. :if-exists :supersede
  332. ))))
  333. ;; -- perform analysis
  334. ;; given a file containing a population of models
  335. ;; - perform post-processing
  336. ;; - return a list of models, in the form of gems:individual structures, so preserving fitness etc
  337. (defun good-models (filename &optional (display nil))
  338. (let* ((models ; get models from final population
  339. (rest (first (last (gems:read-trace filename)))))
  340. (good-models ; extract those models within good-model threshold
  341. (remove-if #'(lambda (model) (> (gems:individual-fitness model) *good-model-threshold*))
  342. models))
  343. (ndc-models ; remove dead-code from the model programs
  344. (gems:clean-individuals good-models #'run-experiment))
  345. (nto-models ; remove time-only code from the model programs
  346. (clean-individuals-time ndc-models #'run-experiment)))
  347. (when display
  348. (format t "Found ~a good models out of ~a~&" (length good-models) (length models))
  349. (format t "After removing dead code: ~a models~&" (length ndc-models))
  350. (format t "After removing time-only code: ~a models~&" (length nto-models)))
  351. nto-models))
  352. ;; run the experiment
  353. ;; this outputs two trace files: log-dmts.cvs and population-dmts.yml
  354. ;; the latter contains the models, so load it back in and output the similarity across models
  355. (run-expt "dmts")
  356. (gems:write-similarity-individuals (good-models "population-dmts.yml" t) "dmts-models.dat")