Learning with Complete Data
Chapter Sixty-Nine
Syllabus topic Module 2, "Learning with complete data"
Pages 404 to 411 of 591
In one line
When the structure is known and every variable is observed in every row, learning a probability model is nothing but counting, one table row at a time.
Complete data means exactly that: each training row gives a value for every variable, with nothing missing and nothing hidden. That is the easy case, and it is easy for a reason worth understanding.
Why it reduces to counting
This is the result the chapter exists for, and a paper may ask for the argument in words.
The likelihood of the whole data set under a Bayesian network is the product, over rows and over nodes, of each node's probability given its parents. Taking the logarithm turns that into a sum:
ln L = sum over rows, sum over nodes of ln P(node's value given its parents' values)
Now notice what each term contains: one node and its parents, and no other parameter. So the sum separates into independent pieces, one for each node and each combination of that node's parents' values. Maximising the whole is therefore maximising each piece on its own, and each piece is the coin problem of Maximum Likelihood Estimation, whose answer is already known:
P(node = true given this row of parents) = (rows with both) / (rows with those parent values)
That is the whole algorithm. No search, no iteration, no gradient: one pass to count, then a division per row. And it holds only because the data is complete. Hidden Variables shows what breaks the moment one value is missing.
How the data was made, and the name for it
The program needs data from a known network, and the way to draw it is worth a sentence because it is a standard method in its own right. Ancestral sampling, also called prior sampling: take the nodes in an order where every parent comes before its children, and draw each node from its own table row, which the parents already drawn have selected. One pass per sample, no rejection, no iteration.
The measurement
# Learning the parameters of a Bayesian network from COMPLETE data: every value
# of every variable observed. The estimate is a count, one table row at a time,
# and the program checks that claim against the network of chapter 40.
import math
def lcg(seed):
x = seed
while True:
x = (1664525 * x + 1013904223) % (2 ** 32)
yield x / 2 ** 32
gen = lcg(31337)
rnd = lambda: next(gen) # noqa: E731
# The same four-node network as the Bayesian network chapters, with its TRUE
# parameters. The learner is not shown these; it only sees sampled rows.
TRUE = {
"Burglary": ([], {(): 0.001}),
"Earthquake": ([], {(): 0.002}),
"Alarm": (["Burglary", "Earthquake"], {(True, True): 0.95,
(True, False): 0.94,
(False, True): 0.29,
(False, False): 0.001}),
"Call": (["Alarm"], {(True,): 0.90, (False,): 0.05}),
}
ORDER = ["Burglary", "Earthquake", "Alarm", "Call"]
def draw(net):
"""ANCESTRAL SAMPLING: take the nodes in parent-before-child order and draw
each from its own row, which the parents already drawn select."""
row = {}
for node in ORDER:
parents, table = net[node]
p = table[tuple(row[q] for q in parents)]
row[node] = rnd() < p
return row
def log_likelihood(net, data):
total = 0.0
for row in data:
for node in ORDER:
parents, table = net[node]
p = table[tuple(row[q] for q in parents)]
total += math.log(p if row[node] else 1 - p)
return total
print("COMPLETE DATA means every variable is observed in every row. then the")
print("log likelihood SEPARATES: each node's term involves only that node and")
print("its parents, so maximising the whole is maximising each table row on its")
print("own, and each row's answer is a COUNT.")
print()
print(" P(node = true | that row of parents) = (rows with both) / (rows with")
print(" those parent values)")
print()
print("the network needs %d numbers; the full joint over 4 binary variables"
% sum(2 ** len(TRUE[n][0]) for n in ORDER))
print("would need 2**4 - 1 = 15. that is why the structure is worth having.")
print()
ROWS = [("Burglary", ()), ("Earthquake", ()),
("Alarm", (True, True)), ("Alarm", (True, False)),
("Alarm", (False, True)), ("Alarm", (False, False)),
("Call", (True,)), ("Call", (False,))]
def label(node, key):
parents, _ = TRUE[node]
if not parents:
return "P(%s)" % node
bits = ", ".join("%s=%s" % (p, "T" if v else "F") for p, v in zip(parents, key))
return "P(%s | %s)" % (node, bits)
def learn(data):
"""Count. Returns the learned net and the denominator of every row."""
net, seen = {}, {}
for node in ORDER:
parents, table = TRUE[node]
rows = {}
for key in table:
both = sum(1 for r in data
if tuple(r[q] for q in parents) == key and r[node])
denom = sum(1 for r in data if tuple(r[q] for q in parents) == key)
seen[(node, key)] = denom
rows[key] = (both / denom) if denom else 0.5 # 0.5: nothing seen
net[node] = (parents, rows)
return net, seen
learned = {}
sizes = (2000, 40000, 200000)
data_by_size = {}
for n in sizes:
data_by_size[n] = [draw(TRUE) for _ in range(n)]
learned[n] = learn(data_by_size[n])
print("EVERY TABLE ROW, LEARNED BY COUNTING, at three sample sizes.")
print(" row | true | %s" % " | ".join("%8d" % n for n in sizes))
for node, key in ROWS:
line = " %-35s | %6.3f" % (label(node, key), TRUE[node][1][key])
for n in sizes:
net, seen = learned[n]
d = seen[(node, key)]
line += " | %8s" % ("no data" if d == 0 else "%.4f" % net[node][1][key])
print(line)
print()
print(" and the DENOMINATOR of each row, which is what the accuracy depends on:")
print(" row | %s" % " | ".join("%8d" % n for n in sizes))
for node, key in ROWS:
line = " %-35s" % label(node, key)
for n in sizes:
line += " | %8d" % learned[n][1][(node, key)]
print(line)
print()
print("READ THE ALARM ROWS. P(Alarm | B=F, E=F) has almost every row of the data")
print("behind it and is exact to three places. P(Alarm | B=T, E=T) needs a")
print("burglary AND an earthquake in the same night, whose true probability is")
print("0.001 * 0.002 = 0.000002, so %d rows contain NONE and the parameter is"
% sizes[-1])
print("simply not estimable. more data does not help evenly: it helps each row")
print("in proportion to how often that row's parent values occur.")
print()
print("THE MLE BEATS THE TRUTH ON THE SAMPLE, which is worth seeing once.")
n = sizes[1]
data = data_by_size[n]
net, _ = learned[n]
ll_true = log_likelihood(TRUE, data)
ll_learned = log_likelihood({k: (v[0], v[1]) for k, v in net.items()}, data)
print(" log likelihood of the %d sampled rows" % n)
print(" under the TRUE parameters = %.4f" % ll_true)
print(" under the LEARNED parameters = %.4f" % ll_learned)
print(" the learned parameters are higher by %.4f" % (ll_learned - ll_true))
print(" that is not a mistake. maximum likelihood returns the parameters that")
print(" best explain THIS sample, and the truth is not that. the gap is the")
print(" overfitting of the earlier chapter, measured inside a network.")
print()
print("THE ZERO ROW, AND THE REPAIR. at %d rows, P(Alarm | B=T, E=T) saw:" % sizes[0])
d = learned[sizes[0]][1][("Alarm", (True, True))]
print(" %d observations. plain counting gives 0/0, which is undefined; a" % d)
print(" program that returns 0 instead asserts the alarm CANNOT sound during")
print(" a burglary in an earthquake, and any night where it does then has")
print(" likelihood 0 under the whole model.")
print(" LAPLACE: add one imagined observation of each value.")
for both, denom in ((0, 0), (0, 1), (1, 1), (3, 4)):
print(" %d of %d -> plain %-9s laplace (%d+1)/(%d+2) = %.4f"
% (both, denom, "undefined" if denom == 0 else "%.4f" % (both / denom),
both, denom, (both + 1) / (denom + 2)))
print(" a network learned from complete data is therefore counting PLUS a")
print(" smoothing rule, and the rule is not optional on rare parent rows.")Learning with Complete Data
COMPLETE DATA means every variable is observed in every row. then the
log likelihood SEPARATES: each node's term involves only that node and
its parents, so maximising the whole is maximising each table row on its
own, and each row's answer is a COUNT.
P(node = true | that row of parents) = (rows with both) / (rows with
those parent values)
the network needs 8 numbers; the full joint over 4 binary variables
would need 2**4 - 1 = 15. that is why the structure is worth having.
EVERY TABLE ROW, LEARNED BY COUNTING, at three sample sizes.
row | true | 2000 | 40000 | 200000
P(Burglary) | 0.001 | 0.0010 | 0.0012 | 0.0010
P(Earthquake) | 0.002 | 0.0010 | 0.0026 | 0.0019
P(Alarm | Burglary=T, Earthquake=T) | 0.950 | no data | no data | no data
P(Alarm | Burglary=T, Earthquake=F) | 0.940 | 1.0000 | 0.9792 | 0.9219
P(Alarm | Burglary=F, Earthquake=T) | 0.290 | 1.0000 | 0.2788 | 0.2691
P(Alarm | Burglary=F, Earthquake=F) | 0.001 | 0.0005 | 0.0012 | 0.0008
P(Call | Alarm=T) | 0.900 | 0.8000 | 0.8689 | 0.8916
P(Call | Alarm=F) | 0.050 | 0.0516 | 0.0502 | 0.0502
and the DENOMINATOR of each row, which is what the accuracy depends on:
row | 2000 | 40000 | 200000
P(Burglary) | 2000 | 40000 | 200000
P(Earthquake) | 2000 | 40000 | 200000
P(Alarm | Burglary=T, Earthquake=T) | 0 | 0 | 0
P(Alarm | Burglary=T, Earthquake=F) | 2 | 48 | 192
P(Alarm | Burglary=F, Earthquake=T) | 2 | 104 | 379
P(Alarm | Burglary=F, Earthquake=F) | 1996 | 39848 | 199429
P(Call | Alarm=T) | 5 | 122 | 443
P(Call | Alarm=F) | 1995 | 39878 | 199557
READ THE ALARM ROWS. P(Alarm | B=F, E=F) has almost every row of the data
behind it and is exact to three places. P(Alarm | B=T, E=T) needs a
burglary AND an earthquake in the same night, whose true probability is
0.001 * 0.002 = 0.000002, so 200000 rows contain NONE and the parameter is
simply not estimable. more data does not help evenly: it helps each row
in proportion to how often that row's parent values occur.
THE MLE BEATS THE TRUTH ON THE SAMPLE, which is worth seeing once.
log likelihood of the 40000 sampled rows
under the TRUE parameters = -9507.9267
under the LEARNED parameters = -9501.9179
the learned parameters are higher by 6.0088
that is not a mistake. maximum likelihood returns the parameters that
best explain THIS sample, and the truth is not that. the gap is the
overfitting of the earlier chapter, measured inside a network.
THE ZERO ROW, AND THE REPAIR. at 2000 rows, P(Alarm | B=T, E=T) saw:
0 observations. plain counting gives 0/0, which is undefined; a
program that returns 0 instead asserts the alarm CANNOT sound during
a burglary in an earthquake, and any night where it does then has
likelihood 0 under the whole model.
LAPLACE: add one imagined observation of each value.
0 of 0 -> plain undefined laplace (0+1)/(0+2) = 0.5000
0 of 1 -> plain 0.0000 laplace (0+1)/(1+2) = 0.3333
1 of 1 -> plain 1.0000 laplace (1+1)/(1+2) = 0.6667
3 of 4 -> plain 0.7500 laplace (3+1)/(4+2) = 0.6667
a network learned from complete data is therefore counting PLUS a
smoothing rule, and the rule is not optional on rare parent rows.Learning with Complete Data
Reading the two tables
Eight numbers describe this network, against 15 for the full joint distribution over the same four variables. Bayesian Networks argued that; here it is the count of things that have to be learned.
Learning with Complete Data
The estimates converge, but not at the same speed, and the second table says why. Set the two side by side for the four alarm rows:
Learning with Complete Data
| Row | True | At 200,000 rows | Observations behind it |
|---|---|---|---|
| Alarm, no burglary, no earthquake | 0.001 | 0.0008 | 199,429 |
| Alarm, no burglary, earthquake | 0.290 | 0.2691 | 379 |
| Alarm, burglary, no earthquake | 0.940 | 0.9219 | 192 |
| Alarm, burglary and earthquake | 0.950 | no data | 0 |
The last row cannot be estimated from two hundred thousand nights. It needs a burglary and an earthquake on the same night, and the true probability of that is 0.001 * 0.002 = 0.000002, about one night in five hundred thousand. More data does not help a network evenly. It helps each table row in proportion to how often that row's parent values occur, so the rarest rows stay unlearned however large the sample.
Two consequences worth stating in an answer.
A node with many parents is expensive. Its table has 2 to the power of the number of parents rows, and each row needs its own observations, so the data required grows exponentially in the number of parents. That is a practical argument for the sparse graphs Bayesian Networks prized, quite separate from the argument about storage.
Small denominators give wild estimates. At 2,000 rows, two of the alarm rows had two observations each and both returned 1.0000 against true values of 0.940 and 0.290. The estimate 2/2 is the honest maximum likelihood answer and it is useless, which is the next section.
The learned parameters beat the truth
On forty thousand rows:
| Log likelihood of the data | |
|---|---|
| Under the true parameters | -9507.9267 |
| Under the learned parameters | -9501.9179 |
| Difference | +6.0088 |
The learned parameters explain the sample better than the parameters that generated it. That is not an error, and it is not a sign of a good fit either. Maximum likelihood returns whatever best explains this sample, and the sample is not the world: its counts are a little off the true proportions, and the estimate follows them exactly.
Learning with Complete Data
This is Overfitting and Underfitting seen inside a network, and it gives a clean general statement: the training likelihood of a maximum likelihood fit is always at least the training likelihood of the truth, so a high likelihood on the training data is never evidence that the parameters are right.
The zero row, and the repair
Plain counting on a row with no observations is 0/0, undefined. A program that quietly returns 0 instead makes a much worse claim: that the alarm cannot sound during a burglary in an earthquake. Then any night on which it does has probability zero under the whole model, and the network is unusable, exactly as in Maximum Likelihood Estimation.
Laplace smoothing repairs it, by adding one imagined observation of each value to every row:
| Observations | Plain count | With add-one |
|---|---|---|
| 0 of 0 | undefined | 0.5000 |
| 0 of 1 | 0.0000 | 0.3333 |
| 1 of 1 | 1.0000 | 0.6667 |
| 3 of 4 | 0.7500 | 0.6667 |
Read the first line: with nothing observed the smoothed estimate is 0.5000, the honest statement that nothing is known. And read the last: with four observations the smoothing still moves the estimate a long way, which is the right behaviour on four observations and would be the wrong behaviour on four thousand, where it barely moves it at all.
So the practical algorithm is counting plus a smoothing rule, and on a network with rare parent combinations the rule is not optional.
What this chapter does not do
Two things are deliberately outside it, and a paper may ask a student to say so.
The structure was given. Which node is a parent of which was fixed in advance, by someone who understood the domain. Learning the structure from data is a much harder problem: the number of possible graphs grows faster than exponentially in the number of nodes, and adding an edge can never lower the training likelihood, so the search needs a penalty for complexity. It is a real subject and it is not this one.
Every value was observed. That is the assumption that made the sum separate. Hidden Variables removes it.
Distinctions
| Complete data | Incomplete data | |
|---|---|---|
| Every variable observed | yes | no |
| The log likelihood | separates into one term per table row | does not separate |
| The estimate | a count, in one pass | needs iteration |
| Solved by | counting | the EM algorithm |
| Learning parameters | Learning structure | |
|---|---|---|
| Given | the graph | nothing but the variables |
| Search space | none | more than exponential in the nodes |
| Training likelihood | is maximised by counting | rises with every edge added |
| Needs a complexity penalty | no | yes |
| A common parent row | A rare parent row | |
|---|---|---|
| Here | no burglary, no earthquake | burglary and earthquake |
| Observations in 200,000 | 199,429 | 0 |
| Estimate | 0.0008 against a true 0.001 | none |
Learning with Complete Data
What it does not mean
Counting is not a heuristic here. It is the exact maximum likelihood estimate, because the log likelihood separates.
More data does not fix every row. It fixes each row in proportion to how often that row's parent values occur.
A high training likelihood is not evidence of correct parameters. The learned ones beat the truth by 6.0088 on the sample that produced them.
An estimate of 1.0000 from two observations is not a certainty. It is maximum likelihood with a denominator of two.
A count of zero is not a probability of zero. It is the absence of evidence, and treating it as zero destroys the model.
Learning the parameters is not learning the network. The graph was given.
Quick revision
- Complete data: every variable observed in every row. Then
ln Lseparates into one term per node and per parent configuration, so each table row is estimated on its own. - The estimate:
P(node = true given a parent row) = (rows with both) / (rows with those parent values). One counting pass, no search. - Ancestral sampling draws data from a known network: parents before children, one pass per row.
- Measured: 8 parameters against 15 for the full joint. Estimates converge, but each row at its own rate, set by its denominator.
P(Alarm given burglary and earthquake)had 0 observations in 200,000 rows, since its parent values co-occur with probability0.000002. Not estimable.- A node with
kparents has2krows, so the data needed grows exponentially in the number of parents**. Another reason for sparse graphs. - The learned parameters scored -9501.9179 against the truth's -9507.9267 on the same 40,000 rows: the MLE beats the truth on its own sample, so training likelihood is never evidence of correctness.
- Laplace smoothing: add one of each value.
0 of 0becomes 0.5000,1 of 1becomes 0.6667, and the effect fades as real counts grow. - Out of scope here: structure learning, which searches a super-exponential space and needs a complexity penalty, and missing values, which break the separation.
Test yourself
1. Why does learning a Bayesian network from complete data reduce to counting? The likelihood is a product over rows and nodes of each node's probability given its parents, so the log likelihood is a sum in which every term mentions only one node and its parents. The sum therefore separates into independent pieces, one per node and per combination of parent values, and each piece is the coin problem whose maximum likelihood answer is the observed proportion.
2. Write the estimate for one table row. The number of rows in which the node is true and its parents have the given values, divided by the number of rows in which its parents have those values.
Learning with Complete Data
3. What is ancestral sampling? Drawing a row from a known network by taking the nodes in an order that puts every parent before its children and sampling each node from the table row its already-drawn parents select. One pass gives one complete sample.
4. In the measurement, one parameter could not be estimated from 200,000 rows. Which, and why? The probability of the alarm given both a burglary and an earthquake. The two parents are independent with probabilities 0.001 and 0.002, so they co-occur about once in five hundred thousand rows, and 200,000 rows contained none. Data helps each table row only in proportion to how often that row's parent values occur.
5. Why is a node with many parents expensive to learn? Its table has two to the power of the number of parents rows, and each row needs its own observations to be estimated, so the amount of data required grows exponentially in the number of parents. This is an argument for sparse graphs independent of the argument about storage.
6. The learned parameters gave a higher log likelihood on the training data than the true parameters. Explain, and give the consequence. Maximum likelihood returns the parameters that best explain the particular sample, and the sample's counts differ slightly from the true proportions, so the estimate follows the sample rather than the truth. It follows that a high training likelihood can never be evidence that the parameters are correct, since the fitted parameters beat the truth on that data by construction.
7. A table row has no observations. Give the plain estimate, what is wrong with it, and the repair. Plain counting gives zero over zero, which is undefined, and a program that returns 0 instead asserts the event is impossible, so any row exhibiting it has likelihood zero and the whole model becomes unusable. Laplace smoothing adds one imagined observation of each value, which turns nothing observed into 0.5, the honest statement that nothing is known, and its influence fades as real observations accumulate.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.