OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / opencog-kb / language-learning / mvp / experiments / exp9-compare-polysemy.scm

243 lines · scm ref:
;;
;; exp9-compare-polysemy.scm — Compare All Polysemy Detection Approaches
;;
;; Loads and runs all 4 approaches, then produces a comparison table.
;;
;; Usage:
;;   (load "experiments/exp9-compare-polysemy.scm")
;;   (compare-polysemy-approaches)
;;

;; Load all polysemy experiments
(let ((exp-dir (string-append *mvp-dir* "/experiments/")))
  (for-each
    (lambda (file)
      (format #t "Loading ~a...~%" file)
      (load (string-append exp-dir file)))
    (list "exp9b-word-polysemy.scm"
          "exp9c-mi-neighborhood.scm"
          "exp9d-class-centrality.scm"
          "exp9e-connector-entropy.scm")))

(define (compare-polysemy-approaches)
  "Run all 4 polysemy detection approaches and compare results."
  (format #t "~%╔══════════════════════════════════════════════════════════╗~%")
  (format #t "║    POLYSEMY DETECTION: 4-Approach Comparison             ║~%")
  (format #t "╚══════════════════════════════════════════════════════════╝~%~%")

  ;; Ensure exp7 data is available (all approaches depend on it)
  (when (null? *exp7-compartments*)
    (format #t "Running exp7 first (needed by all approaches)...~%~%")
    (run-exp7))

  ;; Run all 4 approaches
  (format #t "~%════ Running Approach 1: Word-Level Connector Cosine ════~%")
  (run-exp9b)

  (format #t "~%════ Running Approach 2: MI Neighborhood Divergence ════~%")
  (run-exp9c)

  (format #t "~%════ Running Approach 3: Class Centrality Difference ════~%")
  (run-exp9d)

  (format #t "~%════ Running Approach 4: Connector Entropy / JSD ════~%")
  (run-exp9e)

  ;; ── Comparison Table ──
  (format #t "~%~%")
  (format #t "╔══════════════════════════════════════════════════════════════════════════╗~%")
  (format #t "║                    COMPARISON OF ALL APPROACHES                        ║~%")
  (format #t "╚══════════════════════════════════════════════════════════════════════════╝~%~%")

  ;; Extract results
  (let* ((approaches
           (list
             (list "9a: Class cosine"
                   (if (null? *exp9-results*)
                       (list 0 1 0 '() '() #f)
                       ;; Normalize: exp9 = (aligned poly aligns-list poly-list passed)
                       ;; Convert to (analyzed poly stable poly-list stable-list passed)
                       (list (list-ref *exp9-results* 0)  ; aligned as "analyzed"
                             (list-ref *exp9-results* 1)  ; polysemy count
                             0                             ; no stable metric
                             (list-ref *exp9-results* 3)  ; polysemy list
                             '()                           ; no stable list
                             (list-ref *exp9-results* 4))) ; passed
                   "Compare connector profiles of CLASSES across compartments"
                   "Polysemy = word in classes with cosine < 0.1")
             (list "9b: Word connector"
                   (if (null? *exp9b-results*)
                       (list 0 0 0 '() '() #f)
                       *exp9b-results*)
                   "Compare a WORD's own connector vector across compartments"
                   "Polysemy = word connector-cosine < 0.3")
             (list "9c: MI neighborhood"
                   (if (null? *exp9c-results*)
                       (list 0 0 0 '() '() #f)
                       *exp9c-results*)
                   "Compare a word's MI partners across compartments"
                   "Polysemy = MI-neighborhood cosine < 0.3")
             (list "9d: Centrality diff"
                   (if (null? *exp9d-results*)
                       (list 0 0 0 '() '() #f)
                       *exp9d-results*)
                   "Compare word's distance to class centroid across compartments"
                   "Polysemy = centrality difference > 0.4")
             (list "9e: Entropy/JSD"
                   (if (null? *exp9e-results*)
                       (list 0 0 0 '() '() #f)
                       *exp9e-results*)
                   "Compare connector entropy + Jensen-Shannon divergence"
                   "Polysemy = JSD > 0.3"))))

    ;; Summary table
    (format #t "┌────────────────────────┬─────────┬──────────┬─────────┬──────────┐~%")
    (format #t "│ Approach               │ Analyzed│ Polysemy │ Stable  │ Verdict  │~%")
    (format #t "├────────────────────────┼─────────┼──────────┼─────────┼──────────┤~%")
    (for-each
      (lambda (a)
        (let* ((name (list-ref a 0))
               (results (list-ref a 1))
               (analyzed (list-ref results 0))
               (polysemy (list-ref results 1))
               (stable (list-ref results 2))
               (passed (list-ref results 5)))
          (format #t "│ ~22a │ ~7a │ ~8a │ ~7a │ ~8a │~%"
                  name analyzed polysemy stable
                  (if passed "PASS" "FAIL"))))
      approaches)
    (format #t "└────────────────────────┴─────────┴──────────┴─────────┴──────────┘~%")

    ;; What each approach measures
    (format #t "~%── What Each Approach Measures ──~%")
    (for-each
      (lambda (a)
        (format #t "~%  ~a:~%" (list-ref a 0))
        (format #t "    Method: ~a~%" (list-ref a 2))
        (format #t "    Signal: ~a~%" (list-ref a 3)))
      approaches)

    ;; Cross-approach agreement: which words appear as polysemy candidates in multiple approaches?
    (format #t "~%── Cross-Approach Agreement ──~%")
    (let ((word-votes (make-hash-table))
          (word-approaches (make-hash-table)))

      ;; Count how many approaches flag each word
      (let ((approach-names (list "9a" "9b" "9c" "9d" "9e"))
            (approach-results (list *exp9-results* *exp9b-results*
                                    *exp9c-results* *exp9d-results*
                                    *exp9e-results*)))
        (do ((i 0 (+ i 1))) ((>= i 5))
          (let ((results (list-ref approach-results i))
                (aname (list-ref approach-names i)))
            (unless (null? results)
              (let ((candidates (list-ref results 3)))
                (for-each
                  (lambda (c)
                    (let ((word (if (and (= i 0) (pair? c))
                                    (list-ref c 0)  ; exp9 format: (word name-a ci-a ...)
                                    (list-ref c 0))))  ; exp9b-e format: (word ...)
                      (hash-set! word-votes word
                                 (+ (hash-ref word-votes word 0) 1))
                      (hash-set! word-approaches word
                                 (cons aname (hash-ref word-approaches word '())))))
                  candidates))))))

      ;; Show words flagged by multiple approaches
      (let ((multi-flagged '()))
        (hash-for-each
          (lambda (word votes)
            (when (>= votes 2)
              (set! multi-flagged
                    (cons (list word votes (hash-ref word-approaches word '()))
                          multi-flagged))))
          word-votes)

        (set! multi-flagged
              (sort multi-flagged (lambda (a b) (> (cadr a) (cadr b)))))

        (if (null? multi-flagged)
            (format #t "  No words flagged by 2+ approaches.~%")
            (begin
              (format #t "  Words flagged by multiple approaches:~%")
              (for-each
                (lambda (mf)
                  (format #t "    ~15a flagged by ~a/5 approaches (~a)~%"
                          (list-ref mf 0)
                          (list-ref mf 1)
                          (string-join (reverse (list-ref mf 2)) ", ")))
                multi-flagged)))

        ;; Also show ALL unique polysemy words across all approaches
        (let ((all-poly-words '()))
          (hash-for-each
            (lambda (word _) (set! all-poly-words (cons word all-poly-words)))
            word-votes)
          (format #t "~%  Total unique polysemy words across all approaches: ~a~%"
                  (length all-poly-words))
          (format #t "  Multi-approach agreement: ~a/~a (~,0f%)~%"
                  (length multi-flagged) (length all-poly-words)
                  (if (> (length all-poly-words) 0)
                      (* 100.0 (/ (length multi-flagged) (length all-poly-words)))
                      0.0)))))

    ;; Recommendation
    (format #t "~%── RECOMMENDATION ──~%")
    (let* ((results-list (list *exp9b-results* *exp9c-results*
                                *exp9d-results* *exp9e-results*))
           (n-passed (length (filter (lambda (r)
                                        (and (not (null? r)) (list-ref r 5)))
                                      results-list)))
           (best-approach
             (let ((best-name "none") (best-count 0))
               (for-each
                 (lambda (name results)
                   (unless (null? results)
                     (when (> (list-ref results 1) best-count)
                       (set! best-count (list-ref results 1))
                       (set! best-name name))))
                 (list "9b" "9c" "9d" "9e")
                 results-list)
               (cons best-name best-count))))

      (format #t "~%  ~a/4 new approaches passed (≥3 polysemy candidates)~%" n-passed)
      (format #t "  Best approach: ~a with ~a candidates~%"
              (car best-approach) (cdr best-approach))

      (cond
        ((>= n-passed 3)
         (format #t "~%  Multiple approaches confirm polysemy → use ensemble (vote ≥ 2)~%"))
        ((>= n-passed 1)
         (format #t "~%  Some approaches find polysemy → use best single approach for compartment.scm~%"))
        (else
         (format #t "~%  No approach reliably finds polysemy at this scale → ~%")
         (format #t "  polysemy detection needs more data (>1000 sentences/compartment)~%")))

      ;; Write comparison results
      (let ((out-file (string-append *mvp-dir* "/experiments/results/exp9-comparison.txt")))
        (call-with-output-file out-file
          (lambda (port)
            (format port "# Polysemy Detection Approach Comparison~%")
            (format port "# Date: ~a~%" (strftime "%Y-%m-%d %H:%M:%S" (localtime (current-time))))
            (format port "#~%")
            (for-each
              (lambda (a)
                (let ((results (list-ref a 1)))
                  (format port "approach: ~a analyzed=~a polysemy=~a stable=~a verdict=~a~%"
                          (list-ref a 0) (list-ref results 0) (list-ref results 1)
                          (list-ref results 2) (if (list-ref results 5) "PASS" "FAIL"))))
              approaches)
            (format port "#~%")
            (format port "best_approach: ~a~%" (car best-approach))
            (format port "best_count: ~a~%" (cdr best-approach))
            (format port "n_passed: ~a~%" n-passed)))
        (format #t "~%Comparison written to ~a~%" out-file)))))

(format #t "~%═══ Polysemy Comparison Loaded ═══~%")
(format #t "  (compare-polysemy-approaches)  Run all 4 + compare~%")
(format #t "  (run-exp9b)                    Word connector cosine~%")
(format #t "  (run-exp9c)                    MI neighborhood divergence~%")
(format #t "  (run-exp9d)                    Class centrality difference~%")
(format #t "  (run-exp9e)                    Connector entropy / JSD~%")
(format #t "═══════════════════════════════════════════════════════~%~%")