#+BEGIN_SRC shell scheme@(guile-user)> (define tree (fit #:train-data (shuffle-dataset banking-dataset #:seed 12345) #:feature-column-indices (list 0 1 2 3) #:label-column-index 4 #:max-depth 5 #:min-data-points 12 #:min-data-points-ratio 0.02 #:min-impurity-split (expt 10 -7) #:stop-at-no-impurity-improvement #t)) recursive split on depth: 1 will check for stop conditions now checking for stop condition: max-depth-reached? checking for stop condition: insufficient-data-points-for-split? checking for stop condition: insufficient-data-points-ratio-for-split? checking for stop condition: all-same-label? INFO: CONTINUING SPLITT: still got 1372 data points ice-9/threads.scm:289:22: In procedure loop: In procedure <: Wrong type argument in position 1: 0.39142673091293256
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. #+END_SRC
This only happens when running the algorithm in parallel and has never happened when running the algorithm without parallelism. It might be a bug in Guile's parallel forms or some code, which cannot safely be run in parallel being part of the algorithm. It is also not always reproducible and the argument to <
changes every time the bug happens. This makes it more likely, that this bug is connected to concurrency issues.
I am not aware of any impure code causing this problem, as I specifically made a lot of effort to implement the algorithm in a purely functional way, except for debug outputs. There is still some chance, that I overlooked something.
Here is a complete transcript of the bug happening:
#+BEGIN_SRC shell [18:24:47]:[~/development/Guile/guile-ml]: guile --debug -L . GNU Guile 2.2.6 Copyright (C) 1995-2019 Free Software Foundation, Inc.
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details.
Enter `,help' for help. scheme@(guile-user)> (use-modules (utils csv) (decision-tree) (dataset) (tree) (utils string) (utils display) (prediction))
(define FILE-PATH "data_banknote_authentication.csv")
(define COLUMN-CONVERTERS (list (list string->number) (list string->number) (list string->number) (list string->number) (list #;(lambda (val) (display (simple-format #f "converting: ~a\n" val)) (display (simple-format #f "converted: ~a\n" (string->number val))) (string->number val)) (lambda (val) (string->number (string-trim-both val))))))
(define banking-dataset (all-rows "data_banknote_authentication.csv" #:converters COLUMN-CONVERTERS))
(define dev-dataset (list #(2.771244718 1.784783929 0) #(1.728571309 1.169761413 0) #(3.678319846 2.81281357 0) #(3.961043357 2.61995032 0) #(2.999208922 2.209014212 0) #(7.497545867 3.162953546 1) #(9.00220326 3.339047188 1) #(7.444542326 0.476683375 1) #(10.12493903 3.234550982 1) #(6.642287351 3.319983761 1)))
(define-public print-tree (lambda (tree label-column-index) (define tree->string (lambda (tree depth) (cond [(leaf-node? tree) (string-append (n-times-string " " depth) "[" (number->string (dataset-majority-prediction (node-data tree) label-column-index)) "]\n")] [else (string-append (string-append (n-times-string " " depth) "[feature:" (number->string (node-split-feature-index tree)) " < " (number->string (node-split-value tree)) "]\n") (tree->string (node-left tree) (+ depth 1)) (tree->string (node-right tree) (+ depth 1)))]))) (displayln (tree->string tree 0))))
;;; note: source file ./decision-tree.scm ;;; newer than compiled home/xiaolong.cache/guile/ccache/2.2-LE-8-3.A/home/xiaolong/development/Guile/guile-ml/decision-tree.scm.go ;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0 ;;; or pass the --no-auto-compile argument to disable. ;;; compiling ./decision-tree.scm ;;; compiled home/xiaolong.cache/guile/ccache/2.2-LE-8-3.A/home/xiaolong/development/Guile/guile-ml/decision-tree.scm.go
scheme@(guile-user)> (define tree (fit #:train-data (shuffle-dataset banking-dataset #:seed 12345) #:feature-column-indices (list 0 1 2 3) #:label-column-index 4 #:max-depth 5 #:min-data-points 12 #:min-data-points-ratio 0.02 #:min-impurity-split (expt 10 -7) #:stop-at-no-impurity-improvement #t)) recursive split on depth: 1 will check for stop conditions now checking for stop condition: max-depth-reached? checking for stop condition: insufficient-data-points-for-split? checking for stop condition: insufficient-data-points-ratio-for-split? checking for stop condition: all-same-label? INFO: CONTINUING SPLITT: still got 1372 data points ice-9/threads.scm:289:22: In procedure loop: In procedure <: Wrong type argument in position 1: 0.39142673091293256
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. scheme@(guile-user) [1]> ,bt In current input: 161:2 3 (_) In decision-tree.scm: 218:18 2 (recursive-split (#(-2.7028 1.6327 0.83598 -0.091393 1) #(-1.3931 1.5664 7.5382 0.78403 0) #(-5.4414 7.2363 0.10938 -7.5642 1) #(-2.7908 -5.7133 5.953 0.45946 1) #(-3.518 2.8763 0.1548 # 1) # …) …) 110:13 1 (get-best-split _ _ _ #:split-quality-proc _) In ice-9/threads.scm: 289:22 0 (loop _) scheme@(guile-user) [1]> #+END_SRC
This way no matter which Scheme is used, the multiprocessing can be ported by just changing the code behind the interface and the algorithm code does not need to care.
car
, cdr
, null?
instead of dataset-empty?
, etc.