| … | … | @@ -0,0 +1,193 @@ |
| 1 | ; |
| 2 | ; pair-count.scm -- Example demo of assembling a word-pair counter. |
| 3 | ; |
| 4 | ; Prototype hand-built agent capable of counting things observed |
| 5 | ; in the external environment. |
| 6 | ; |
| 7 | ; Demos counting of word-pairs observed by looking at a text file. |
| 8 | ; The Sensory API allows easy generalization to other text sources, |
| 9 | ; so working with files is OK for now. |
| 10 | ; |
| 11 | ; The dataflow pipeline is hand-crafted. (The sensory API is supposed |
| 12 | ; to eventually auto-build these pipelines, but that code is not working |
| 13 | ; yet.) You'll notice that the pipeline below is ... complicated. |
| 14 | ; |
| 15 | ; The "rest of the demo", where we do something with the word pairs, |
| 16 | ; is in "generate.scm". |
| 17 | |
| 18 | (use-modules (opencog) (opencog exec) (opencog persist)) |
| 19 | (use-modules (opencog nlp) (opencog nlp lg-parse)) |
| 20 | (use-modules (opencog sensory)) |
| 21 | (use-modules (srfi srfi-1)) |
| 22 | |
| 23 | ; -------------------------------------------------------------- |
| 24 | ; Return a text parser that counts edges on a stream. The |
| 25 | ; `txt-stream` must be an Atom that can serve as a source of text. |
| 26 | ; Typically, `txt-stream` will be |
| 27 | ; (ValueOf (Concept "some atom") (Predicate "some key")) |
| 28 | ; and the Value there will be a LinkStream from some file or |
| 29 | ; other text source. |
| 30 | ; |
| 31 | ; These sets up a processing pipeline in Atomese, and returns that |
| 32 | ; pipeline. The actual parsing all happens in C++ code, not in scheme |
| 33 | ; code. The scheme here is just to glue the pipeline together. |
| 34 | (define (make-parser txt-stream) |
| 35 | ; |
| 36 | ; Pipeline steps, from inside to out: |
| 37 | ; * LGParseBonds tokenizes a sentence, and then parses it. |
| 38 | ; * The PureExecLink makes sure that the parsing is done in a |
| 39 | ; sub-AtomSpace so that the main AtomSpace is not garbaged up. |
| 40 | ; |
| 41 | ; The result of parsing is a list of pairs. First item in a pair is |
| 42 | ; the list of words in the sentence; the second is a list of the edges. |
| 43 | ; Thus, each pair has the form |
| 44 | ; (LinkValue |
| 45 | ; (LinkValue (Word "this") (Word "is") (Word "a") (Word "test")) |
| 46 | ; (LinkValue (Edge ...) (Edge ...) ...)) |
| 47 | ; |
| 48 | ; The outer Filter matches this, so that (Glob "$edge-list") is |
| 49 | ; set to the LinkValue of Edges. |
| 50 | ; |
| 51 | ; The inner Filter loops over the list of edges, and invokes a small |
| 52 | ; pipe to increment the count on each edge. |
| 53 | ; |
| 54 | ; The counter is a non-atomic pipe of (SetValue (Plus 1 (GetValue))) |
| 55 | ; |
| 56 | (define NUML (Number 4)) |
| 57 | (define DICT (LgDict "any")) |
| 58 | |
| 59 | ; Increment the count on one edge. |
| 60 | (define (incr-cnt edge) |
| 61 | (SetValue edge (Predicate "count") |
| 62 | (Plus (Number 0 0 1) |
| 63 | (FloatValueOf edge (Predicate "count") (FloatValueOf (Number 0 0 0)))))) |
| 64 | |
| 65 | ; Given a list (an Atomese LinkValue list) of parse results, |
| 66 | ; extract the edges and increment the count on them. |
| 67 | (define (count-edges parsed-stuff) |
| 68 | (Filter |
| 69 | (Rule |
| 70 | ; (Variable "$edge") |
| 71 | (TypedVariable (Variable "$edge") (Type 'Edge)) |
| 72 | (Variable "$edge") |
| 73 | (incr-cnt (Variable "$edge"))) |
| 74 | parsed-stuff)) |
| 75 | |
| 76 | ; Parse text in a private space. |
| 77 | (define (parseli phrali) |
| 78 | (PureExecLink (LgParseBonds phrali DICT NUML))) |
| 79 | |
| 80 | ; Given an Atom `phrali` holding text to be parsed, get that text, |
| 81 | ; parse it, and increment the counts on the edges. |
| 82 | (define (filty phrali) |
| 83 | (Filter |
| 84 | (Rule |
| 85 | ; Type decl |
| 86 | (Glob "$x") |
| 87 | ; Match clause - one per parse. |
| 88 | (LinkSignature |
| 89 | (Type 'LinkValue) ; the wrapper for the pair |
| 90 | (Type 'LinkValue) ; the word-list |
| 91 | (LinkSignature ; the edge-list |
| 92 | (Type 'LinkValue) ; edge-list wrapper |
| 93 | (Glob "$edge-list"))) ; all of the edges |
| 94 | ; Pipeline to apply to the resulting match. |
| 95 | (count-edges (Glob "$edge-list")) |
| 96 | ) |
| 97 | (parseli phrali))) |
| 98 | |
| 99 | ; Return the parser. |
| 100 | (filty txt-stream) |
| 101 | ) |
| 102 | |
| 103 | ; -------------------------------------------------------------- |
| 104 | ; Demo wrapper showing how to use the above: Parse a string. |
| 105 | ; PLAIN-TEXT should be a scheme string. |
| 106 | ; |
| 107 | ; Verify this works as follows: |
| 108 | ; (obs-txt "this is a test") |
| 109 | ; (cog-report-counts) |
| 110 | ; Note the abundance of WordNodes listed in the report. |
| 111 | ; (cog-get-atoms 'WordNode) |
| 112 | ; Cleanup after use: |
| 113 | ; (for-each cog-extract-recursive! (cog-get-atoms 'WordNode)) |
| 114 | ; (extract-type 'WordNode) |
| 115 | (define (obs-txt PLAIN-TEXT) |
| 116 | |
| 117 | ; We don't need to create this over and over; once is enough. |
| 118 | (define txt-stream (ValueOf (Concept "foo") (Predicate "some place"))) |
| 119 | (define parser (make-parser txt-stream)) |
| 120 | |
| 121 | (define phrali (Phrase PLAIN-TEXT)) |
| 122 | (cog-set-value! (Concept "foo") (Predicate "some place") phrali) |
| 123 | |
| 124 | (cog-execute! parser) |
| 125 | |
| 126 | ; Remove the phrase-link, return the list of edges. |
| 127 | (cog-set-value! (Concept "foo") (Predicate "some place") #f) |
| 128 | (cog-extract-recursive! phrali) |
| 129 | ) |
| 130 | |
| 131 | ; -------------------------------------------------------------- |
| 132 | ; Demo wrapper: Parse contents of a file. |
| 133 | (define (obs-file FILE-NAME) |
| 134 | |
| 135 | ; We don't need to create this over and over; once is enough. |
| 136 | (define txt-stream (ValueOf (Concept "foo") (Predicate "some place"))) |
| 137 | (define parser (make-parser txt-stream)) |
| 138 | |
| 139 | (define phrali (Open (Type 'TextFileStream) |
| 140 | (Sensory (string-append "file://" FILE-NAME)))) |
| 141 | |
| 142 | (cog-execute! |
| 143 | (SetValue (Concept "foo") (Predicate "some place") phrali)) |
| 144 | |
| 145 | ; Parse only first line of file: |
| 146 | ; (cog-execute! parser) |
| 147 | |
| 148 | ; Loop over all lines. |
| 149 | (define (looper) (cog-execute! parser) (looper)) |
| 150 | |
| 151 | ; At end of file, exception will be thrown. Catch it. |
| 152 | (catch #t looper |
| 153 | (lambda (key . args) (format #t "The end ~A\n" key))) |
| 154 | |
| 155 | (cog-extract-recursive! phrali) |
| 156 | ) |
| 157 | |
| 158 | ; -------------------------- |
| 159 | ; Issues. |
| 160 | ; Parses arrive as LinkValues. We want to apply a function to each. |
| 161 | ; How? Need: |
| 162 | ; 4) IncrementLink just like cog-inc-value! |
| 163 | ; or is it enough to do atomic lock? |
| 164 | ; Make SetValue exec under lock. ... easier said than done. |
| 165 | ; risks deadlock. |
| 166 | ; |
| 167 | ; cog-update-value calls |
| 168 | ; Ideally, call asp->increment_count(h, key, fvp->value())); |
| 169 | |
| 170 | ; Below works but is a non-atomic increment. |
| 171 | (define ed (Edge (Bond "ANY") (List (Word "words") (Word "know")))) |
| 172 | (define (doinc) |
| 173 | (cog-execute! |
| 174 | (SetValue ed (Predicate "count") |
| 175 | (Plus (Number 0 0 1) |
| 176 | (FloatValueOf ed (Predicate "count") (Number 0 0 0)))))) |
| 177 | |
| 178 | ; Here's a demo of what file-access looks like. |
| 179 | (cog-execute! |
| 180 | (SetValue (Concept "foo") (Predicate "some place") |
| 181 | (FileRead "file:///tmp/demo.txt"))) |
| 182 | |
| 183 | ; Wait ... |
| 184 | (define txt-stream-gen |
| 185 | (ValueOf (Concept "foo") (Predicate "some place"))) |
| 186 | |
| 187 | ; (cog-execute! (LgParseBonds txt-stream-gen DICT NUML)) |
| 188 | |
| 189 | ; (load "count-agent.scm") |
| 190 | ; (obs-txt "Some kind of sentence to process, hell yeah!") |
| 191 | ; (obs-file "/tmp/demo.txt") |
| 192 | ; (cog-report-counts) |
| 193 | ; (cog-get-atoms 'WordNode) |