Skip to content

pruning! leaves pruned nodes without sample-indices, breaking predict-forest/test-forest #14

Description

@masatoi

pruning! turns a split node back into a leaf via delete-children!, but it does not restore the node's sample-indices. Since make-forest defaults to :remove-sample-indices? t, every node that was split already had its sample-indices set to nil, so the newly created leaves have none. Any subsequent predict-forest / test-forest then fails.

Reproduction

Self-contained, no dataset download needed (SBCL 2.6.7, master @ b4e5ec5):

(ql:quickload :cl-random-forest)

(defparameter *seed* 42)
(defun nextrand ()
  (setf *seed* (mod (+ (* 1103515245 *seed*) 12345) 2147483648))
  (/ (float *seed* 1.0) 2147483648.0))
(defun noise () (* 2.0 (- (+ (nextrand) (nextrand) (nextrand)) 1.5)))

(defun gen (n-datum n-dim n-class signal)
  (let ((dm (make-array (list n-datum n-dim) :element-type 'single-float))
        (tg (make-array n-datum :element-type 'fixnum)))
    (dotimes (i n-datum)
      (let ((c (mod i n-class)))
        (setf (aref tg i) c)
        (dotimes (j n-dim)
          (setf (aref dm i j) (+ (noise) (if (= (mod j n-class) c) signal 0.0))))))
    (values dm tg)))

(multiple-value-bind (x y) (gen 2000 20 5 1.0)
  (defparameter *x* x)
  (defparameter *y* y))

;; NOTE: :remove-sample-indices? defaults to T
(defparameter *forest*
  (clrf:make-forest 5 *x* *y* :n-tree 30 :bagging-ratio 0.2 :max-depth 8 :n-trial 15))
(defparameter *rd* (clrf:make-refine-dataset *forest* *x*))
(defparameter *learner* (clrf:make-refine-learner *forest*))
(clrf:train-refine-learner *learner* *rd* *y*)

(clrf:test-forest *forest* *x* *y* :quiet-p t)   ; => 85.1   (OK)

(clrf:pruning! *forest* *learner* 0.2)

(clrf:test-forest *forest* *x* *y* :quiet-p t)   ; => TYPE-ERROR

Result:

The value
  NIL
is not of type
  (SIMPLE-ARRAY FIXNUM)
from the function type declaration.

  0: CL-RANDOM-FOREST/SRC/RANDOM-FOREST::NODE-CLASS-DISTRIBUTION (src/random-forest.lisp:221)
  1: CL-RANDOM-FOREST/SRC/RANDOM-FOREST::CLASS-DISTRIBUTION-FOREST (src/random-forest.lisp:707)
  2: CL-RANDOM-FOREST/SRC/RANDOM-FOREST:PREDICT-FOREST (src/random-forest.lisp:730)
  3: CL-RANDOM-FOREST/SRC/RANDOM-FOREST:TEST-FOREST (src/random-forest.lisp:739)

Cause

set-best-children! nils out the parent's sample-indices when the tree was built with the default :remove-sample-indices? t (src/random-forest.lisp:414-415):

(when (dtree-remove-sample-indices? dtree)
  (setf (node-sample-indices node) nil))

delete-children! (src/random-forest.lisp:1212-1217) makes that node a leaf again but never puts sample-indices back:

(defun delete-children! (node)
  (setf (node-test-attribute node) nil
        (node-test-threshold node) nil
        (node-left-node node) nil
        (node-right-node node) nil)
  node)

node-class-distribution (src/random-forest.lisp:221-224) recomputes the leaf distribution from sample-indices on every prediction, so it receives nil.

Why it is easy to miss

The refine-learner path keeps working after pruning, because make-refine-vector only reads node-leaf-index. So make-refine-dataset -> train-refine-learner -> test-refine-learner all succeed, and the failure only shows up when predict-forest / test-forest is called on the pruned forest.

Regression forests have the same structure through node-regression-mean (src/random-forest.lisp:515).

Workaround

Build the forest with :remove-sample-indices? nil. Verified that the exact same pruning sequence then completes and test-forest works (71.2% before pruning -> 71.5% after, on a 100-tree forest).

Suggested fix

Either

  • make delete-children! restore the node's sample-indices by concatenating the sample indices of the subtree leaves being discarded, or
  • have pruning! assert that the forest was built with :remove-sample-indices? nil, and document the requirement (the same way src/feature-importance.lisp needs it for the OOB set).

The first keeps pruning! usable with the memory-saving default; the second is the minimal change.

Related: #15 (forest-n-leaf is stale after pruning!).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions