summaryrefslogtreecommitdiff
path: root/AnalogyUtils.md
blob: 1dedb58c0d0556d6cefaa92d4f0433d4f9edb76a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# Analogy-Utils
Python driver for interacting with the Mapper rules in [mapper.py](mapper.py).

### Running Example
We will use the following running example from
[examples/letter_analogies/letter_analogy.py](examples/letter_analogies/letter_analogy.py):
```
If abc becomes bcd and efg becomes efh, then what does ijk become?
```
For notational simplicity, we will label the nodes corresponding to letters
like so:
```
If abc becomes (b')(c')(d') and efg becomes (f')(g')(h'), then what does ijk become?
```

We will also assume facts related to the position of letters in the strings as
well as alphabetical ordering:
```
(n1, a, Left), (n1, b, Right)
(n2, b, Left), (n2, c, Right)
...
(s1, a, Pred), (s1, b, Succ)
(s2, b, Pred), (s2, c, Succ)
(s3, a, Pred), (s3, b', Succ)
(s4, b, Pred), (s4, c', Succ)
...
```
and so forth.

### Broad Idea: Joint Traversal
Our goal is to form a map identifying corresponding nodes.

If we think of the triplet structure as a graph, the basic idea behind our
approach is to do a joint, breadth-first traversal of the graph. We start by
assigning two nodes to correspond to each other, then we extend the
correspondance by following edges from each node iteratively and
simultaneously.

### Starting the Analogy
We seed the analogy by telling it that two given nodes should correspond to
each other. In fact, we tell it that two _facts_ should correspond to each
other. In this case, a pretty safe fact to start with is that `abc` and `efg`
are both letter strings that are "pre-transformation." In other words, we
want to abstract the facts:
```
(t1, abc, TransformFrom) and (t2, efg, TransformFrom)
```
to form abstract nodes `^t` and `^???` with fact:
```
(^t, ^???, TransformFrom).
```

##### In Code
In order to do this, we use the `Analogy.Begin` method, roughly like:
```
analogy = Analogy.Begin(rt,  {
    no_slip[":MA"]: t1,
    no_slip[":A"]: abc,
    no_slip[":MB"]: t2,
    no_slip[":B"]: efg,
    no_slip[":C"]: TransformFrom,
}).
```

### Extending the Start
If `extend=True` is passed to `Analogy.Begin` (the default) then it will
automatically start to build out from this fact. Essentially, it will look at
all other facts regarding `t1` and `t2` and try to lift (antiunify) them into
abstract facts. In this case, we find that there are corresponding facts:
```
(t1, b'c'd', TransformTo) and (t2, f'g'h', TransformTo)
```
Hence in the abstract we can add the node `^?'?'?'` as well as the fact:
```
(^t, ^?'?'?', TransformTo).
```

##### In Code
Again, this happens automatically in `Analogy.Begin(..., extend=True)`. At this
point, the abstraction consists of two abstract groups, `^???` and `^?'?'?'`,
where the latter is the post-transformation of the former. Hence the only
correspondance we know between the two examples so far is that they both
involve pairs of letter strings before and after the transformation. This is
all that is claimed by our initial mapping of `t1` and `t2`, hence
`Analogy.Begin` finishes.

### Pivots: Extending With New Fact Nodes
To extend the analogy further, we need to involve additional fact nodes. We do
this by pivoting off of nodes already in the analogy and identifying fact nodes
that claim similar things about nodes already mapped to each other. For
example, we might have fact nodes `h1` and `h2` expressing that `a` is the
start of string `abc` and `e` is the start of string `efg`:
```
(h1, abc, Group), (h1, a, Head)
and
(h2, efg, Group), (h2, e, Head).
```
Note that `abc` and `efg` are already mapped to each other in the analogy, and
`h1` and `h2` both claim the same thing about `abc`/`efg` (namely, that they're
groups). Hence, we can infer that `h1` and `h2` might correspond to each other,
forming a new abstract node `^h` with fact:
```
(^h, ^???, Group).
```

##### In Code
We perform this pivoting to a new fact node with the method
`Analogy.ExtendMap`:
```
analogy.ExtendMap([Group]).
```

### Building off a Fact Node
We've now recognized that both `abc` and `efg` are groups of letters, but `h1`
and `h2` also claim something else: that each group has a head letter that
starts it. Because we've mapped `h1` and `h2` to each other, we can follow this
fact as well to infer that the heads of each group should probably correspond
as well. In other words, we can lift `a` and `e` to abstract node `^1` and add
fact:
```
(^h, ^1, Head).
```

##### In Code
The call to `Analogy.ExtendMap` where we added `^h` in the first place will
automatically follow all facts of this form when possible. It does this by
calling the method `Analogy.ExtendFacts(^h)` which in turn repeatedly calls
the `NewConcrete` rule to add nodes like `^1` and `Analogy.LiftFacts(^h)` to
lift any other triplets like `(^h, ^1, Head)`.

### Summary of Analogy-Making by Traversal
In general, the operations described above are enough to create an analogy. We
pivot repeatedly between:
(i) Abstracting fact nodes that make claims about nodes already in the
analogy. E.g., `h1` and `h2` claim `abc` and `efg` (which we know correspond)
are groups, hence, `h1<->h2` is probably consistent with our analogy so we
can abstract them to `^h`.
(ii) Abstract nodes for which claims are made in those corresponding fact
nodes.  E.g., we think `h1` and `h2` correspond, and `h1` claims `a` is a head
while `h2` claims `e` is a head of corresponding groups `abc` and `efg`. Hence,
we might infer that in fact `a` and `e` correspond, forming some abstract node
`^1` which is also a head of the abstract group `^???`.

### Avoiding Bad Maps
Unfortunately, this approach can run into problems. For example, after we say
that `a` and `e` correspond, we might notice that there are fact nodes `s1` and
`m1` with facts:
```
(s1, a, Pred), (s1, b, Succ)
and
(m1, e, Pred), (m1, f, Succ).
```
We could then map `s1<->m1` and follow this to map `b<->f`, which would
work perfectly. However, there might _also_ be a fact node `m3` with facts:
```
(m3, e, Pred), (m3, f', Succ),
```
which actually maps _across groups_ `efg` and `f'g'h'`. The problem is that we
pivot to groups based only on a single triplet, and, hence, looking at only a
single triplet it's not clear if we should map
```
(s1, a, Pred)<->(m1, e, Pred)
or
(s1, a, Pred)<->(m3, e, Pred).
```
Both options look equally good when deciding whether `s1` should correspond to
`m1` or `m3`. If we pick `s1<->m1`, we saw that everything works and we map
`b<->f` as desired. However, if we map `s1<->m3` then we will infer that
actually `b<->f'`, which is probably wrong. We need some way to decide
between the two equally plausible mappings.

##### Heuristic 1: Follow Unique Claims First
The first heuristic is to avoid such scenarios when possible by following
claims which are _unique_. In this case, the problem only came about because
the first `a` was actually an alphabetical predecessor of two different nodes,
the `b` in `abc` and the `b'` in `b'c'd'`. So when we follow predecessor ->
successor, we have to make a choice of which successor we want to choose.

If we instead had followed the claim that that `a` is _to the left_ of some
other letter, there would only be one choice: the `b` in `abc`. Similarly, the
only thing to the right of the `e` is the `f` in `efg`. Hence, if we had
followed the `Left`/`Right` relation instead of `Pred`/`Succ`, we would have
arrived at the most reasonable option `b<->f`.

This generally means following _structural_ relations first, and _semantic_
relations only after that.

In code, this usually looks like calling `analogy.ExtendMap` multiple times
with different parameters, of decreasing level of uniqueness.

##### Heuristic 2: All or Nothing
Following `Left`/`Right` relations first gives us the desired correspondance of
`b<->f`. However, this doesn't immediately solve the original problem of
determining if `s1<->m1` or `s1<->m3`. Once we have decided `b<->f`, however,
we can try both `s1<->m1` and `s1<->m3` and apply our second heuristic: take
_only the fact nodes where all facts lift_. In this case, we could try to
correspond `s1<->m3` but then we would find that we could _not_ make the facts
```
(s1, b, Succ) and (m3, f', Succ)
```
correspond to each other, because we do not have `b<->f'`. Thus, `s1<->m3`
would leave facts which don't lift to the abstraction while `s1<->m1` would be
able to lift all the relavant facts. Hence, we would prefer `s1<->m1`.

In the code, this is implemented in `analogy.ExtendFacts` by calling
`analogy.LiftFacts` to try and lift all relevant facts to the abstract and
then `analogy.FactsMissing` to check if all facts were lifted. If some can't
be lifted, then `ExtendFacts` will return `False` and `ExtendMap` will give
up on that mapping.

##### Heuristic 3: Voting
In the near future we would like to take an alternate approach, which is
somewhat closer to the original Copycat: voting. Essentially, in this case we
have that following `s1<->m1` leads to a better analogy because then we can
lift all facts and it also agrees with the unique mapping when we follow
`Left`/`Right` to get `b<->f`.

### Completing an Analogy
Suppose we have already mapped `abc->bcd` and `efg->fgh` and want to start
solving `ijk->?`. We:
* First call `Analogy.Begin(..., exists=True)` to map `ijk` into the _existing_
  analogy noting correspondances between `abc->bcd` and `efg->fgh`.
* Then, we call `Analogy.ExtendMap` as before to complete the analogy between
  `ijk->?` and `abc->bcd`/`efg->fgh`.
* Then, we set `analogy.state="concretize"`.
* Then, we again call `Analogy.ExtendMap`. It will continue to traverse the
  existing analogy between `abc->bcd` and `efg->fgh`, but, because we set
  `state="concretize"`, instead of looking for nodes already in the structure
  that might correspond to abstract nodes, it just adds new nodes to the
  structure and lowers the corresponding facts from the abstract to these
  nodes.
* Finally, we run inference rules which solve those lowered facts. E.g., we
  might lower a fact that says that `_1` is the successor of `a`, then infer
  that `_1` is the letter `b`.
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback