|
| 1 | +;;;; gng.lisp ;growing neural gas |
| 2 | + |
| 3 | +(in-package #:util) |
| 4 | + |
| 5 | +;; do not use defparameter,defconstant,defvar, and try to copy-table, they will affect each other |
| 6 | +(defun make-gng |
| 7 | + (&key |
| 8 | + (input-dim 2) |
| 9 | + (max-nodes 2147483647) |
| 10 | + ) |
| 11 | + (>> |
| 12 | + 'input-dim input-dim |
| 13 | + 'max-nodes max-nodes |
| 14 | + 'eps-b 0.2 ;;the rate of moving closest node to sample |
| 15 | + 'eps-n 0.006 ;;the rate of moving neighbors of closest node to sample |
| 16 | + 'max-age 50 |
| 17 | + 'lambd 100 |
| 18 | + 'alpha 0.5 |
| 19 | + 'd 0.995 |
| 20 | + 'time-len 0 |
| 21 | + 'nodes (>>) |
| 22 | + 'edges (>>))) |
| 23 | + |
| 24 | +(defun add-gng-node (&key (gng (make-gng)) (pos '(0 0)) (cum-error 0.0)) |
| 25 | + (let ((key (if (= 0 (hash-table-count (>> gng 'nodes))) 0 (+ 1 (reduce #'max (hash-keys (>> gng 'nodes))))))) |
| 26 | + (>> gng `(nodes ,key) (>> 'pos pos 'cum-error cum-error)) |
| 27 | + key)) |
| 28 | + |
| 29 | +(defun add-gng-edge (&key (gng (make-gng)) (nodes '(0 1)) (age 0)) |
| 30 | + (let ((key (if (= 0 (hash-table-count (>> gng 'edges))) 0 (+ 1 (reduce #'max (hash-keys (>> gng 'edges))))))) |
| 31 | + (>> gng `(edges ,key) (>> 'nodes nodes 'age age)) |
| 32 | + key)) |
| 33 | + |
| 34 | +(defun get-nearest-gng-nodes (&key (gng (make-gng)) (vector '(0 0))) |
| 35 | + ;;Return the two nodes in the graph that are nearest to vector and their squared distances. return (list (dist0 node0) (dist1 node1)) |
| 36 | + (let ((distances (mapcar #'(lambda (x) (list (squared-distance :v0 vector :v1 (>> gng `(nodes ,x pos))) x)) (hash-keys (>> gng 'nodes))))) |
| 37 | + (setf distances (sort (copy-seq distances) #'list<)) |
| 38 | + (list (car distances) (second distances)))) |
| 39 | + |
| 40 | +(defun move-gng-node (&key (gng (make-gng)) (node 0) (vector '(0 0)) (eps (>> gng 'eps-b))) |
| 41 | + (loop for i in (range (length vector)) do |
| 42 | + (let ((pos-i (nth i (>> gng `(nodes ,node pos))))) |
| 43 | + (setf (nth i (>> gng `(nodes ,node pos))) (+ pos-i (* eps (- (nth i vector) pos-i))))))) |
| 44 | + |
| 45 | +(defun remove-old-gng-edges (&key (gng (make-gng)) (edges (hash-keys (>> gng 'edges)))) |
| 46 | + ;;Remove all edges older than the maximal age |
| 47 | + (loop for k in edges do |
| 48 | + (when (> (>> gng `(edges ,k age)) (>> gng 'max-age)) |
| 49 | + (let ((nodes-now (>> gng `(edges ,k nodes)))) |
| 50 | + (remhash k (>> gng 'edges)) |
| 51 | + (loop for node in nodes-now do |
| 52 | + (when (= 0 (node-degree gng :node node)) |
| 53 | + (remhash node (>> gng 'nodes)))))))) |
| 54 | + |
| 55 | +(defun insert-new-gng-node (&key (gng (make-gng))) |
| 56 | + ;;Insert a new node in the graph where it is more necessary i.e. where the error is the largest |
| 57 | + (let ((errors (mapcar #'(lambda (x) (list (>> gng `(nodes ,x cum-error)) x)) (hash-keys (>> gng 'nodes)))) |
| 58 | + (q-node nil) |
| 59 | + (neighbor-nodes '()) |
| 60 | + (neighbor-errors '()) |
| 61 | + (f-node nil) |
| 62 | + ) |
| 63 | + (setf errors (sort (copy-seq errors) #'list>)) |
| 64 | + (setf q-node (cadar errors)) |
| 65 | + (setf neighbor-nodes (neighbors gng :node q-node)) |
| 66 | + (setf neighbor-errors (mapcar #'(lambda (x) (list (>> gng `(nodes ,x cum-error)) x)) neighbor-nodes)) |
| 67 | + (setf neighbor-errors (sort (copy-seq neighbor-errors) #'list>)) |
| 68 | + (setf f-node (cadar neighbor-errors)) |
| 69 | + ;;new node, halfway between the worst node and the worst of its neighbors |
| 70 | + (let ((new-node (add-gng-node :gng gng :pos (mapcar #'(lambda (x y) (* 0.5 (+ x y))) (>> gng `(nodes ,q-node pos)) (>> gng `(nodes ,f-node pos)))))) |
| 71 | + (remhash (car (get-edges gng :nodes (list q-node f-node))) (>> gng 'edges)) |
| 72 | + (add-gng-edge :gng gng :nodes (list q-node new-node)) |
| 73 | + (add-gng-edge :gng gng :nodes (list f-node new-node)) |
| 74 | + (let ((q-error (>> gng `(nodes ,q-node cum-error)))) |
| 75 | + (>> gng `(nodes ,q-node cum-error) (* q-error (>> gng 'alpha)))) |
| 76 | + (let ((f-error (>> gng `(nodes ,f-node cum-error)))) |
| 77 | + (>> gng `(nodes ,f-node cum-error) (* f-error (>> gng 'alpha)))) |
| 78 | + (>> gng `(nodes ,new-node cum-error) (* 0.5 (+ (>> gng `(nodes ,q-node cum-error)) (>> gng `(nodes ,f-node cum-error)))))))) |
| 79 | + |
| 80 | +(defun train-gng (&key (gng (make-gng)) (input '((0 0))) (init-nodes 2)) |
| 81 | + (when (= 0 (hash-table-count (>> gng 'nodes))) ;if missing, generate two initial nodes at random |
| 82 | + (loop for i in (range init-nodes) do |
| 83 | + ;;(add-gng-node :gng gng :pos (mapcar #'(lambda (x) (normal-random :mean 0.5 :deviation 0.2)) (range (input-dim gng)))) |
| 84 | + (add-gng-node :gng gng :pos (mapcar (lambda (x) (random-uniform (list (- x 0.01) (+ x 0.01)))) (random-choice input))))) |
| 85 | + (dolist (vector input) |
| 86 | + (let ((time-len (>> gng 'time-len))) (>> gng 'time-len (+ 1 time-len))) ;; incf time-len |
| 87 | + (let* ((nearest-nodes (get-nearest-gng-nodes :gng gng :vector vector)) ;(list (dist0 node0) (dist1 node1)) |
| 88 | + (node0 (cadr (first nearest-nodes))) |
| 89 | + (dist0 (car (first nearest-nodes))) |
| 90 | + (edges0 (get-emanating-edges gng :node node0)) |
| 91 | + (node1 (cadr (second nearest-nodes)))) |
| 92 | + (dolist (e edges0) ;increase age of the emanating edges of the closest node0 |
| 93 | + (let ((age-now (>> gng `(edges ,e age)))) |
| 94 | + (>> gng `(edges ,e age) (+ 1 age-now)))) |
| 95 | + (let ((c-error (>> gng `(nodes ,node0 cum-error)))) ;update error of the closest node0 |
| 96 | + (>> gng `(nodes ,node0 cum-error) (+ c-error (sqrt dist0)))) |
| 97 | + ;;move nearest node and neighbours |
| 98 | + (move-gng-node :gng gng :node node0 :vector vector :eps (>> gng 'eps-b)) |
| 99 | + (let ((neighbor-nodes (neighbors gng :node node0))) |
| 100 | + (dolist (n neighbor-nodes) |
| 101 | + (move-gng-node :gng gng :node n :vector vector :eps (>> gng 'eps-n))) |
| 102 | + ;;update the edge connecting node0 and node1 |
| 103 | + (if (member node1 neighbor-nodes :test #'equal) |
| 104 | + (let ((edge01 (car (get-edges gng :nodes (list node0 node1))))) |
| 105 | + (>> gng `(edges ,edge01 age) 0)) |
| 106 | + (add-gng-edge :gng gng :nodes (list node0 node1)))) |
| 107 | + ;; remove old edges |
| 108 | + (remove-old-gng-edges :gng gng :edges edges0) |
| 109 | + ;; add a new node each lambd steps |
| 110 | + (when (and (= 0 (mod (>> gng 'time-len) (>> gng 'lambd))) (< (hash-table-count (>> gng 'nodes)) (>> gng 'max-nodes))) |
| 111 | + (insert-new-gng-node :gng gng)) |
| 112 | + ;; decrease the errors of all nodes |
| 113 | + (loop for node in (hash-keys (>> gng 'nodes)) do |
| 114 | + (let ((c-error (>> gng `(nodes ,node cum-error)))) |
| 115 | + (>> gng `(nodes ,node cum-error) (* c-error (>> gng 'd)))))))) |
| 116 | + |
| 117 | +(defun circumference-distr (&key (center '(0.0 0.0)) (radius 1.0) (n 2000)) |
| 118 | + (let* ((phi (mapcar #'(lambda (x) (random (* 2 pi))) (range n)))) |
| 119 | + (mapcar #'(lambda (x) (list (+ (* radius (cos x)) (car center)) (+ (* radius (sin x)) (second center)))) phi))) |
| 120 | + |
| 121 | +(defun circle-distr (&key (center '(0.0 0.0)) (radius 1.0) (n 2000)) |
| 122 | + (let* ((phi (mapcar #'(lambda (x) (random (* 2 pi))) (range n))) |
| 123 | + (sqrt-r (mapcar #'(lambda (x) (sqrt (random (* radius radius)))) (range n)))) |
| 124 | + (mapcar #'(lambda (x) (list (+ (* (nth x sqrt-r) (cos (nth x phi))) (car center)) (+ (* (nth x sqrt-r) (sin (nth x phi))) (second center)))) (range n)))) |
| 125 | + |
| 126 | +(defun rectangle-distr (&key (center '(0.0 0.0)) (w 2.0) (h 1.0) (n 2000)) |
| 127 | + (mapcar #'(lambda (x) (list (+ (random-uniform (list (/ w -2) (/ w 2))) (car center)) |
| 128 | + (+ (random-uniform (list (/ h -2) (/ h 2))) (second center)))) (range n))) |
| 129 | + |
| 130 | +(defun test-gng () |
| 131 | + (let ((g (make-gng :max-nodes 10))) |
| 132 | + (show "g: {}" g) |
| 133 | + (let ((node (add-gng-node :gng g))) |
| 134 | + (format t "node-index: ~a~%" node)) |
| 135 | + (add-gng-node :gng g :pos '(2 3)) |
| 136 | + (add-gng-node :gng g :pos '(4 5)) |
| 137 | + (add-gng-node :gng g :pos (mapcar #'(lambda (x y) (* 0.5 (+ x y))) (>> g `(nodes 1 pos)) (>> g `(nodes 2 pos)))) |
| 138 | + (show "(>> g 'nodes) {}" (hash2lst (>> g 'nodes))) |
| 139 | + (add-gng-edge :gng g) |
| 140 | + (add-gng-edge :gng g :nodes '(1 2) :age 100) |
| 141 | + (add-gng-edge :gng g :nodes '(3 0) :age 0) |
| 142 | + (let ((pair-nodes '(2 1))) |
| 143 | + (format t "the edge of pair-nodes ~a is ~a~%" pair-nodes (car (get-edges g :nodes pair-nodes))) |
| 144 | + ) |
| 145 | + (format t "before inserting new node~%") |
| 146 | + (show "(>> g 'edges) {}" (hash2lst (>> g 'edges))) |
| 147 | + (insert-new-gng-node :gng g) |
| 148 | + (format t "after inserting new node~%") |
| 149 | + (show "(>> g 'edges) {}" (hash2lst (>> g 'edges))) |
| 150 | + (let ((node 1)) (format t "neighbors of node ~a: ~a~%" node (neighbors g :node node))) |
| 151 | + (format t "~a~%" (get-nearest-gng-nodes :gng g :vector '(3 5))) |
| 152 | + (move-gng-node :gng g :node 2 :vector '(3 5)) |
| 153 | + (show "(>> g 'nodes) {}" (hash2lst (>> g 'nodes))) |
| 154 | + (node-degree g :node 2) |
| 155 | + (remove-old-gng-edges :gng g) |
| 156 | + (show "(>> g 'edges) {}" (hash2lst (>> g 'edges))) |
| 157 | + (let ((lst (hash2lst (>> g 'edges)))) |
| 158 | + (format t "~a~%" lst) |
| 159 | + (format t "~a~%" (getf (getf lst 0) 'to))) |
| 160 | + (loop for i in (range 10000) do |
| 161 | + (train-gng :gng g :input '((20 20) (25 20) (25 25) (20 25)))) |
| 162 | + (format t "nodes: ~a~%" (hash2lst (>> g 'nodes))) |
| 163 | + (format t "edges: ~a~%" (hash2lst (>> g 'edges))))) |
| 164 | +;;(test-gng) |
| 165 | + |
| 166 | +(defun set-samples () |
| 167 | + (let* ((n 1000) |
| 168 | + (cf1 (circumference-distr :center '(0.8 0.475) :radius 0.1 :n n)) |
| 169 | + (cf2 (circumference-distr :center '(0.65 0.4) :radius 0.015 :n n)) |
| 170 | + (cl1 (circle-distr :center '(0.25 0.65) :radius 0.025 :n (/ n 2))) |
| 171 | + (cl2 (circle-distr :center '(0.675 0.625) :radius 0.035 :n n)) |
| 172 | + (r1 (rectangle-distr :center '(0.425 0.5) :w 0.05 :h 0.2 :n n)) |
| 173 | + (r2 (rectangle-distr :center '(0.575 0.5) :w 0.05 :h 0.2 :n n)) |
| 174 | + (r3 (rectangle-distr :center '(0.5 0.575) :w 0.1 :h 0.05 :n (/ n 2))) |
| 175 | + (r4 (rectangle-distr :center '(0.5 0.425) :w 0.1 :h 0.05 :n (/ n 2))) |
| 176 | + (samples (append cf1 cf2 cl1 cl2 r1 r2 r3 r4))) |
| 177 | + (shuffle samples))) |
| 178 | +;;(show "{}" (set-samples)) |
| 179 | + |
| 180 | +(defun main-loop-gng-test (&key (width 600) (height 300) (timex 2) (samples (set-samples)) (g (make-gng))) |
| 181 | + (sdl:with-init () |
| 182 | + (sdl:window width height :flags sdl:sdl-opengl |
| 183 | + :title-caption "visual" |
| 184 | + :icon-caption "visual") |
| 185 | + ;;(sdl:window w-width w-height :opengl t :opengl-attributes '((:sdl-gl-depth-size 16)(:sdl-gl-doublebuffer 1))) |
| 186 | + (setf (sdl:frame-rate) 0) |
| 187 | + (>> g 'max-nodes 1000) |
| 188 | + ;; load GL extensions |
| 189 | + ;;(setf cl-opengl-bindings:*gl-get-proc-address* #'sdl-cffi::sdl-gl-get-proc-address) |
| 190 | + (sdl:with-events () |
| 191 | + (:quit-event () t) |
| 192 | + (:idle () |
| 193 | + (when (< timex 3000) (incf timex)) |
| 194 | + (train-gng :gng g :input (list (random-choice samples)) :init-nodes 3) |
| 195 | + ;; keep working with main loop (sbcl) :fd-handler swank:*communication-style* |
| 196 | + ;;#+(and sbcl (not sb-thread)) (restartable (sb-sys:serve-all-events 0)) |
| 197 | + (gl:clear-color 1 1 1 1) |
| 198 | + (gl:clear :color-buffer :depth-buffer) |
| 199 | + (let ((origin (list 50 50)) (width (/ width 3)) (height (/ height 1.5))) |
| 200 | + (before-visual-2D :origin origin :width width :height height :color '(0 0 1 1) :border? t) |
| 201 | + (visual-samples samples :origin origin :width width :height height :color '(0 0 1 1) :border? t) |
| 202 | + (visual-gng g :origin origin :width width :height height :color '(1 0 0 1) :border? nil)) |
| 203 | + ;;(draw-triangle2 :origin (list (/ width 2) 0) :width (/ width 2) :height height) |
| 204 | + (let ((origin (list (+ (/ width 2) 10) 10)) (width (- (/ width 2) 20)) (height (- height 20))) |
| 205 | + (before-visual-2D :origin origin :width width :height height :color '(0 0 1 1) :border? t) |
| 206 | + (draw-curve :origin origin :width width :height height :timex timex)) |
| 207 | + (gl:flush) |
| 208 | + (sdl:update-display))))) |
| 209 | +;;(main-loop-gng-test :width 1000 :height 300) |
| 210 | + |
| 211 | + |
| 212 | + |
| 213 | + |
| 214 | + |
| 215 | + |
0 commit comments