OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / opencog-kb / language-learning / experiments / learn-wiki-full.scm

263 lines · scm ref:
;
; learn-wiki-full.scm -- Language learning on full Wikipedia article
;
(use-modules (opencog) (opencog exec) (opencog sensory))
(use-modules (opencog lg))
(use-modules (srfi srfi-1))
(use-modules (ice-9 rdelim))

(display "=== Language Learning on Wikipedia: Alan Turing (FULL) ===\n\n")

; Read text
(define raw-text (call-with-input-file "/tmp/wiki-turing.txt"
  (lambda (p) (read-string p))))

; Simple sentence splitter
(define (split-sentences text)
  (let ((sentences '())
        (current "")
        (chars (string->list text)))
    (let loop ((chars chars) (current ""))
      (if (null? chars)
        (if (> (string-length current) 10)
          (reverse (cons current sentences))
          (reverse sentences))
        (let ((c (car chars)))
          (cond
            ((and (char=? c #\.)
                  (not (null? (cdr chars)))
                  (char=? (cadr chars) #\space)
                  (not (null? (cddr chars)))
                  (char-upper-case? (caddr chars)))
             (loop (cddr chars)
                   (if (> (string-length current) 10)
                     (begin
                       (set! sentences (cons (string-append current ".") sentences))
                       "")
                     current)))
            (else
             (loop (cdr chars) (string-append current (string c))))))))))

(define sentences (split-sentences raw-text))
(format #t "Total sentences: ~a\n" (length sentences))
(display "Processing ALL sentences...\n\n")

; Write all sentences
(call-with-output-file "/tmp/wiki-all-sentences.txt"
  (lambda (port)
    (for-each (lambda (s) (display s port) (newline port)) sentences)))

; Setup parser
(define file-node (TextFile "file:///tmp/wiki-all-sentences.txt"))
(cog-execute! (SetValue file-node (Predicate "*-open-*") (Type 'StringValue)))
(cog-execute!
  (SetValue (Anchor "wiki pipe") (Predicate "text source")
    (DontExec (ValueOf file-node (Predicate "*-stream-*")))))
(define txt-stream (ValueOf (Anchor "wiki pipe") (Predicate "text source")))

(define parser
  (Filter
    (Rule
      (TypedVariable (Variable "$x") (Type 'StringStream))
      (Variable "$x")
      (LgParseBonds (Variable "$x") (LgDict "en") (Number 1)))
    txt-stream))

; Counting structures
(define pair-counts (make-hash-table))
(define word-counts (make-hash-table))
(define bond-type-counts (make-hash-table))

(define (count-pair w1 w2 bond-type)
  (let* ((key (string-append w1 "|" w2))
         (current (hash-ref pair-counts key 0)))
    (hash-set! pair-counts key (+ current 1))
    (hash-set! word-counts w1 (+ (hash-ref word-counts w1 0) 1))
    (hash-set! word-counts w2 (+ (hash-ref word-counts w2 0) 1))
    (hash-set! bond-type-counts bond-type (+ (hash-ref bond-type-counts bond-type 0) 1))))

(define (extract-pairs result)
  (when (and result (not (null? result)))
    (let ((linkages (cog-value->list result)))
      (for-each
        (lambda (linkage)
          (when (and linkage (not (null? linkage)))
            (let ((parts (cog-value->list linkage)))
              (when (>= (length parts) 2)
                (let ((bonds-val (list-ref parts 1)))
                  (when bonds-val
                    (let ((bonds (cog-value->list bonds-val)))
                      (for-each
                        (lambda (bond)
                          (when (cog-atom? bond)
                            (let ((out (cog-outgoing-set bond)))
                              (when (>= (length out) 2)
                                (let ((bond-atom (list-ref out 0))
                                      (word-list (list-ref out 1)))
                                  (when (and (cog-atom? bond-atom) (cog-atom? word-list))
                                    (let ((bond-type (cog-name bond-atom))
                                          (words (cog-outgoing-set word-list)))
                                      (when (= (length words) 2)
                                        (let ((w1 (cog-name (car words)))
                                              (w2 (cog-name (cadr words))))
                                          (count-pair w1 w2 bond-type))))))))))
                        bonds))))))))
        linkages))))

; Process ALL sentences
(display "Parsing with Link Grammar:\n")
(define start-time (current-time))
(do ((i 0 (+ i 1)))
    ((>= i (length sentences)))
  (when (= (modulo i 50) 0)
    (format #t "  ~a / ~a sentences...\n" i (length sentences)))
  (let ((result (cog-execute! parser)))
    (extract-pairs result)))
(define end-time (current-time))
(format #t "Done! (~a seconds)\n\n" (- end-time start-time))

; Results
(define pair-list (hash-map->list cons pair-counts))
(define sorted-pairs
  (sort pair-list (lambda (a b) (> (cdr a) (cdr b)))))

; Filter content pairs
(define content-pairs
  (filter (lambda (p)
            (let ((key (car p)))
              (and (not (string-contains key "###"))
                   (not (string-contains key "LEFT-WALL"))
                   (not (string-contains key "RIGHT-WALL"))
                   (> (string-length (car (string-split key #\|))) 1)
                   (> (string-length (cadr (string-split key #\|))) 1))))
          sorted-pairs))

(format #t "══════════════════════════════════════════════════════════\n")
(format #t "                    FULL CORPUS RESULTS                    \n")
(format #t "══════════════════════════════════════════════════════════\n\n")

(format #t "Sentences processed:     ~a\n" (length sentences))
(format #t "Total word pairs:        ~a\n" (length sorted-pairs))
(format #t "Content word pairs:      ~a\n" (length content-pairs))
(format #t "Unique words:            ~a\n\n" (hash-count (const #t) word-counts))

; Top pairs
(display "TOP 40 WORD PAIRS (by frequency):\n")
(display "──────────────────────────────────────────────────────────\n")
(for-each
  (lambda (pair)
    (let* ((words (string-split (car pair) #\|))
           (w1 (car words))
           (w2 (cadr words))
           (count (cdr pair)))
      (format #t "  ~20a ─── ~20a │ ~3a\n" w1 w2 count)))
  (take content-pairs (min 40 (length content-pairs))))

; Bond types
(display "\n\nGRAMMATICAL BOND TYPES:\n")
(display "──────────────────────────────────────────────────────────\n")
(define bond-list (hash-map->list cons bond-type-counts))
(define sorted-bonds (sort bond-list (lambda (a b) (> (cdr a) (cdr b)))))
(for-each
  (lambda (b)
    (format #t "  ~10a │ ~4a   " (car b) (cdr b))
    (cond
      ((string=? (car b) "MVp") (display "(verb → preposition)"))
      ((string=? (car b) "Js") (display "(preposition → object)"))
      ((string=? (car b) "G") (display "(possessive/genitive)"))
      ((string=? (car b) "A") (display "(pre-noun adjective)"))
      ((string=? (car b) "AN") (display "(noun-noun compound)"))
      ((string-prefix? "Ss" (car b)) (display "(subject → verb)"))
      ((string-prefix? "Ds" (car b)) (display "(determiner → noun)"))
      ((string=? (car b) "Mp") (display "(noun → prep phrase)"))
      ((string=? (car b) "Xp") (display "(punctuation)"))
      ((string=? (car b) "Xd") (display "(punctuation)"))
      ((string=? (car b) "WV") (display "(wall → verb)"))
      ((string=? (car b) "Wd") (display "(wall → subject)"))
      ((string=? (car b) "RW") (display "(right wall)"))
      ((string=? (car b) "MX") (display "(comma phrase)"))
      ((string=? (car b) "TH") (display "(that-clause)"))
      ((string=? (car b) "Ce") (display "(conj → clause)"))
      ((string=? (car b) "Pv") (display "(passive participle)"))
      ((string=? (car b) "Pg") (display "(present participle)"))
      ((string=? (car b) "Pp") (display "(prep → verb)"))
      ((string=? (car b) "I") (display "(infinitive)"))
      ((string=? (car b) "TO") (display "(to + infinitive)")))
    (newline))
  (take sorted-bonds (min 20 (length sorted-bonds))))

; PMI calculation
(display "\n\nPOINTWISE MUTUAL INFORMATION (PMI):\n")
(display "──────────────────────────────────────────────────────────\n")
(define total-pairs (apply + (map cdr pair-list)))
(define total-words (apply + (hash-map->list (lambda (k v) v) word-counts)))

(define (compute-pmi w1 w2 pair-count)
  (let* ((w1-count (hash-ref word-counts w1 1))
         (w2-count (hash-ref word-counts w2 1))
         (p-pair (/ pair-count total-pairs))
         (p-w1 (/ w1-count total-words))
         (p-w2 (/ w2-count total-words)))
    (if (and (> p-w1 0) (> p-w2 0))
      (/ (log (/ p-pair (* p-w1 p-w2))) (log 2))
      0)))

; High-frequency pairs with PMI
(define pmi-pairs
  (filter-map
    (lambda (pair)
      (if (>= (cdr pair) 4)  ; Only pairs with count >= 4
        (let* ((words (string-split (car pair) #\|))
               (w1 (car words))
               (w2 (cadr words))
               (pmi (compute-pmi w1 w2 (cdr pair))))
          (list w1 w2 (cdr pair) pmi))
        #f))
    content-pairs))

(define sorted-pmi
  (sort pmi-pairs (lambda (a b) (> (list-ref a 3) (list-ref b 3)))))

(display "\nHighest PMI (statistically significant associations, n≥4):\n\n")
(for-each
  (lambda (entry)
    (format #t "  ~20a ─── ~20a │ n=~2a  PMI=~,2f\n"
      (list-ref entry 0) (list-ref entry 1)
      (list-ref entry 2) (list-ref entry 3)))
  (take sorted-pmi (min 25 (length sorted-pmi))))

; Word frequency
(display "\n\nMOST FREQUENT WORDS:\n")
(display "──────────────────────────────────────────────────────────\n")
(define word-list (hash-map->list cons word-counts))
(define sorted-words (sort word-list (lambda (a b) (> (cdr a) (cdr b)))))
(define content-words
  (filter (lambda (w)
            (and (> (string-length (car w)) 2)
                 (not (string-contains (car w) "###"))
                 (char-alphabetic? (string-ref (car w) 0))))
          sorted-words))
(for-each
  (lambda (w) (format #t "  ~20a │ ~a\n" (car w) (cdr w)))
  (take content-words (min 25 (length content-words))))

(display "\n══════════════════════════════════════════════════════════\n")
(display "                     WHAT WE LEARNED                       \n")
(display "══════════════════════════════════════════════════════════\n")
(display "\n")
(display "1. DOMAIN VOCABULARY:\n")
(display "   • Turing, machine, computer, science, mathematics\n")
(display "   • cryptanalysis, ciphers, Enigma, Bletchley\n")
(display "   • algorithm, computation, decidability\n\n")
(display "2. NAMED ENTITIES (high PMI = always co-occur):\n")
(display "   • Alan Turing, King's College, Princeton University\n")
(display "   • World War, Bletchley Park, Turing machine\n\n")
(display "3. GRAMMATICAL PATTERNS:\n")
(display "   • Subject-Verb: 'Turing was', 'he worked', 'it is'\n")
(display "   • Det-Noun: 'the machine', 'a computer', 'the war'\n")
(display "   • Prep-Noun: 'of science', 'in mathematics', 'at Cambridge'\n\n")
(display "4. COLLOCATIONS (multi-word expressions):\n")
(display "   • 'computer science', 'Turing machine', 'World War'\n")
(display "   • These have high PMI because they form units\n\n")
(display "This is unsupervised grammar learning from raw text!\n")