put up all aima code

main
Matthew Butterick 10 years ago
parent e079ae5ad2
commit 54b24fce7a

@ -0,0 +1,8 @@
### demo
>>> min_conflicts(australia)
{'WA': 'B', 'Q': 'B', 'T': 'G', 'V': 'B', 'SA': 'R', 'NT': 'G', 'NSW': 'G'}
>>> min_conflicts(usa)
{'WA': 'B', 'DE': 'R', 'DC': 'Y', 'WI': 'G', 'WV': 'Y', 'HI': 'R', 'FL': 'B', 'WY': 'Y', 'NH': 'R', 'NJ': 'Y', 'NM': 'Y', 'TX': 'G', 'LA': 'R', 'NC': 'B', 'ND': 'Y', 'NE': 'B', 'TN': 'G', 'NY': 'R', 'PA': 'B', 'RI': 'R', 'NV': 'Y', 'VA': 'R', 'CO': 'R', 'CA': 'B', 'AL': 'R', 'AR': 'Y', 'VT': 'Y', 'IL': 'B', 'GA': 'Y', 'IN': 'Y', 'IA': 'Y', 'OK': 'B', 'AZ': 'R', 'ID': 'G', 'CT': 'Y', 'ME': 'B', 'MD': 'G', 'KA': 'Y', 'MA': 'B', 'OH': 'R', 'UT': 'B', 'MO': 'R', 'MN': 'R', 'MI': 'B', 'AK': 'B', 'MT': 'R', 'MS': 'B', 'SC': 'G', 'KY': 'B', 'OR': 'R', 'SD': 'G'}

@ -0,0 +1,43 @@
"""Run all doctests from modules on the command line. For each
module, if there is a "module.txt" file, run that too. However,
if the module.txt file contains the comment "# demo",
then the remainder of the file has its ">>>" lines executed,
but not run through doctest. The idea is that you can use this
to demo statements that return random or otherwise variable results.
Example usage:
python doctests.py *.py
"""
import doctest, re
def run_tests(modules, verbose=None):
"Run tests for a list of modules; then summarize results."
for module in modules:
tests, demos = split_extra_tests(module.__name__ + ".txt")
if tests:
if '__doc__' not in dir(module):
module.__doc__ = ''
module.__doc__ += '\n' + tests + '\n'
doctest.testmod(module, report=0, verbose=verbose)
if demos:
for stmt in re.findall(">>> (.*)", demos):
exec stmt in module.__dict__
doctest.master.summarize()
def split_extra_tests(filename):
"""Take a filename and, if it exists, return a 2-tuple of
the parts before and after '# demo'."""
try:
contents = open(filename).read() + '# demo'
return contents.split("# demo", 1)
except IOError:
return ('', '')
if __name__ == "__main__":
import sys
modules = [__import__(name.replace('.py',''))
for name in sys.argv if name != "-v"]
run_tests(modules, ("-v" in sys.argv))

@ -0,0 +1,21 @@
### This is an example module.txt file.
### It should contain unit tests and their expected results:
>>> 2 + 2
4
>>> '2' + '2'
'22'
### demo
### After the part that says 'demo' we have statements that
### are intended not as unit tests, but as demos of how to
### use the functions and methods in the module. The
### statements are executed, but the results are not
### compared to the expected results. This can be useful
### for nondeterministic functions:
>>> import random; random.choice('abc')
'c'

@ -0,0 +1,286 @@
"""Games, or Adversarial Search. (Chapters 6)
"""
from utils import *
import random
#______________________________________________________________________________
# Minimax Search
def minimax_decision(state, game):
"""Given a state in a game, calculate the best move by searching
forward all the way to the terminal states. [Fig. 6.4]"""
player = game.to_move(state)
def max_value(state):
if game.terminal_test(state):
return game.utility(state, player)
v = -infinity
for (a, s) in game.successors(state):
v = max(v, min_value(s))
return v
def min_value(state):
if game.terminal_test(state):
return game.utility(state, player)
v = infinity
for (a, s) in game.successors(state):
v = min(v, max_value(s))
return v
# Body of minimax_decision starts here:
action, state = argmax(game.successors(state),
lambda ((a, s)): min_value(s))
return action
#______________________________________________________________________________
def alphabeta_full_search(state, game):
"""Search game to determine best action; use alpha-beta pruning.
As in [Fig. 6.7], this version searches all the way to the leaves."""
player = game.to_move(state)
def max_value(state, alpha, beta):
if game.terminal_test(state):
return game.utility(state, player)
v = -infinity
for (a, s) in game.successors(state):
v = max(v, min_value(s, alpha, beta))
if v >= beta:
return v
alpha = max(alpha, v)
return v
def min_value(state, alpha, beta):
if game.terminal_test(state):
return game.utility(state, player)
v = infinity
for (a, s) in game.successors(state):
v = min(v, max_value(s, alpha, beta))
if v <= alpha:
return v
beta = min(beta, v)
return v
# Body of alphabeta_search starts here:
action, state = argmax(game.successors(state),
lambda ((a, s)): min_value(s, -infinity, infinity))
return action
def alphabeta_search(state, game, d=4, cutoff_test=None, eval_fn=None):
"""Search game to determine best action; use alpha-beta pruning.
This version cuts off search and uses an evaluation function."""
player = game.to_move(state)
def max_value(state, alpha, beta, depth):
if cutoff_test(state, depth):
return eval_fn(state)
v = -infinity
for (a, s) in game.successors(state):
v = max(v, min_value(s, alpha, beta, depth+1))
if v >= beta:
return v
alpha = max(alpha, v)
return v
def min_value(state, alpha, beta, depth):
if cutoff_test(state, depth):
return eval_fn(state)
v = infinity
for (a, s) in game.successors(state):
v = min(v, max_value(s, alpha, beta, depth+1))
if v <= alpha:
return v
beta = min(beta, v)
return v
# Body of alphabeta_search starts here:
# The default test cuts off at depth d or at a terminal state
cutoff_test = (cutoff_test or
(lambda state,depth: depth>d or game.terminal_test(state)))
eval_fn = eval_fn or (lambda state: game.utility(state, player))
action, state = argmax(game.successors(state),
lambda ((a, s)): min_value(s, -infinity, infinity, 0))
return action
#______________________________________________________________________________
# Players for Games
def query_player(game, state):
"Make a move by querying standard input."
game.display(state)
return num_or_str(raw_input('Your move? '))
def random_player(game, state):
"A player that chooses a legal move at random."
return random.choice(game.legal_moves())
def alphabeta_player(game, state):
return alphabeta_search(state, game)
def play_game(game, *players):
"Play an n-person, move-alternating game."
state = game.initial
while True:
for player in players:
move = player(game, state)
state = game.make_move(move, state)
if game.terminal_test(state):
return game.utility(state, players[0])
#______________________________________________________________________________
# Some Sample Games
class Game:
"""A game is similar to a problem, but it has a utility for each
state and a terminal test instead of a path cost and a goal
test. To create a game, subclass this class and implement
legal_moves, make_move, utility, and terminal_test. You may
override display and successors or you can inherit their default
methods. You will also need to set the .initial attribute to the
initial state; this can be done in the constructor."""
def legal_moves(self, state):
"Return a list of the allowable moves at this point."
abstract
def make_move(self, move, state):
"Return the state that results from making a move from a state."
abstract
def utility(self, state, player):
"Return the value of this final state to player."
abstract
def terminal_test(self, state):
"Return True if this is a final state for the game."
return not self.legal_moves(state)
def to_move(self, state):
"Return the player whose move it is in this state."
return state.to_move
def display(self, state):
"Print or otherwise display the state."
print state
def successors(self, state):
"Return a list of legal (move, state) pairs."
return [(move, self.make_move(move, state))
for move in self.legal_moves(state)]
def __repr__(self):
return '<%s>' % self.__class__.__name__
class Fig62Game(Game):
"""The game represented in [Fig. 6.2]. Serves as a simple test case.
>>> g = Fig62Game()
>>> minimax_decision('A', g)
'a1'
>>> alphabeta_full_search('A', g)
'a1'
>>> alphabeta_search('A', g)
'a1'
"""
succs = {'A': [('a1', 'B'), ('a2', 'C'), ('a3', 'D')],
'B': [('b1', 'B1'), ('b2', 'B2'), ('b3', 'B3')],
'C': [('c1', 'C1'), ('c2', 'C2'), ('c3', 'C3')],
'D': [('d1', 'D1'), ('d2', 'D2'), ('d3', 'D3')]}
utils = Dict(B1=3, B2=12, B3=8, C1=2, C2=4, C3=6, D1=14, D2=5, D3=2)
initial = 'A'
def successors(self, state):
return self.succs.get(state, [])
def utility(self, state, player):
if player == 'MAX':
return self.utils[state]
else:
return -self.utils[state]
def terminal_test(self, state):
return state not in ('A', 'B', 'C', 'D')
def to_move(self, state):
return if_(state in 'BCD', 'MIN', 'MAX')
class TicTacToe(Game):
"""Play TicTacToe on an h x v board, with Max (first player) playing 'X'.
A state has the player to move, a cached utility, a list of moves in
the form of a list of (x, y) positions, and a board, in the form of
a dict of {(x, y): Player} entries, where Player is 'X' or 'O'."""
def __init__(self, h=3, v=3, k=3):
update(self, h=h, v=v, k=k)
moves = [(x, y) for x in range(1, h+1)
for y in range(1, v+1)]
self.initial = Struct(to_move='X', utility=0, board={}, moves=moves)
def legal_moves(self, state):
"Legal moves are any square not yet taken."
return state.moves
def make_move(self, move, state):
if move not in state.moves:
return state # Illegal move has no effect
board = state.board.copy(); board[move] = state.to_move
moves = list(state.moves); moves.remove(move)
return Struct(to_move=if_(state.to_move == 'X', 'O', 'X'),
utility=self.compute_utility(board, move, state.to_move),
board=board, moves=moves)
def utility(self, state):
"Return the value to X; 1 for win, -1 for loss, 0 otherwise."
return state.utility
def terminal_test(self, state):
"A state is terminal if it is won or there are no empty squares."
return state.utility != 0 or len(state.moves) == 0
def display(self, state):
board = state.board
for x in range(1, self.h+1):
for y in range(1, self.v+1):
print board.get((x, y), '.'),
print
def compute_utility(self, board, move, player):
"If X wins with this move, return 1; if O return -1; else return 0."
if (self.k_in_row(board, move, player, (0, 1)) or
self.k_in_row(board, move, player, (1, 0)) or
self.k_in_row(board, move, player, (1, -1)) or
self.k_in_row(board, move, player, (1, 1))):
return if_(player == 'X', +1, -1)
else:
return 0
def k_in_row(self, board, move, player, (delta_x, delta_y)):
"Return true if there is a line through move on board for player."
x, y = move
n = 0 # n is number of moves in row
while board.get((x, y)) == player:
n += 1
x, y = x + delta_x, y + delta_y
x, y = move
while board.get((x, y)) == player:
n += 1
x, y = x - delta_x, y - delta_y
n -= 1 # Because we counted move itself twice
return n >= self.k
class ConnectFour(TicTacToe):
"""A TicTacToe-like game in which you can only make a move on the bottom
row, or in a square directly above an occupied square. Traditionally
played on a 7x6 board and requiring 4 in a row."""
def __init__(self, h=7, v=6, k=4):
TicTacToe.__init__(self, h, v, k)
def legal_moves(self, state):
"Legal moves are any square not yet taken."
return [(x, y) for (x, y) in state.moves
if y == 0 or (x, y-1) in state.board]

@ -0,0 +1,586 @@
"""Learn to estimate functions from examples. (Chapters 18-20)"""
from utils import *
import agents, random, operator
#______________________________________________________________________________
class DataSet:
"""A data set for a machine learning problem. It has the following fields:
d.examples A list of examples. Each one is a list of attribute values.
d.attrs A list of integers to index into an example, so example[attr]
gives a value. Normally the same as range(len(d.examples)).
d.attrnames Optional list of mnemonic names for corresponding attrs.
d.target The attribute that a learning algorithm will try to predict.
By default the final attribute.
d.inputs The list of attrs without the target.
d.values A list of lists, each sublist is the set of possible
values for the corresponding attribute. If None, it
is computed from the known examples by self.setproblem.
If not None, an erroneous value raises ValueError.
d.name Name of the data set (for output display only).
d.source URL or other source where the data came from.
Normally, you call the constructor and you're done; then you just
access fields like d.examples and d.target and d.inputs."""
def __init__(self, examples=None, attrs=None, target=-1, values=None,
attrnames=None, name='', source='',
inputs=None, exclude=(), doc=''):
"""Accepts any of DataSet's fields. Examples can
also be a string or file from which to parse examples using parse_csv.
>>> DataSet(examples='1, 2, 3')
<DataSet(): 1 examples, 3 attributes>
"""
update(self, name=name, source=source, values=values)
# Initialize .examples from string or list or data directory
if isinstance(examples, str):
self.examples = parse_csv(examples)
elif examples is None:
self.examples = parse_csv(DataFile(name+'.csv').read())
else:
self.examples = examples
map(self.check_example, self.examples)
# Attrs are the indicies of examples, unless otherwise stated.
if not attrs and self.examples:
attrs = range(len(self.examples[0]))
self.attrs = attrs
# Initialize .attrnames from string, list, or by default
if isinstance(attrnames, str):
self.attrnames = attrnames.split()
else:
self.attrnames = attrnames or attrs
self.setproblem(target, inputs=inputs, exclude=exclude)
def setproblem(self, target, inputs=None, exclude=()):
"""Set (or change) the target and/or inputs.
This way, one DataSet can be used multiple ways. inputs, if specified,
is a list of attributes, or specify exclude as a list of attributes
to not put use in inputs. Attributes can be -n .. n, or an attrname.
Also computes the list of possible values, if that wasn't done yet."""
self.target = self.attrnum(target)
exclude = map(self.attrnum, exclude)
if inputs:
self.inputs = removall(self.target, inputs)
else:
self.inputs = [a for a in self.attrs
if a is not self.target and a not in exclude]
if not self.values:
self.values = map(unique, zip(*self.examples))
def add_example(self, example):
"""Add an example to the list of examples, checking it first."""
self.check_example(example)
self.examples.append(example)
def check_example(self, example):
"""Raise ValueError if example has any invalid values."""
if self.values:
for a in self.attrs:
if example[a] not in self.values[a]:
raise ValueError('Bad value %s for attribute %s in %s' %
(example[a], self.attrnames[a], example))
def attrnum(self, attr):
"Returns the number used for attr, which can be a name, or -n .. n."
if attr < 0:
return len(self.attrs) + attr
elif isinstance(attr, str):
return self.attrnames.index(attr)
else:
return attr
def sanitize(self, example):
"Return a copy of example, with non-input attributes replaced by 0."
return [i in self.inputs and example[i] for i in range(len(example))]
def __repr__(self):
return '<DataSet(%s): %d examples, %d attributes>' % (
self.name, len(self.examples), len(self.attrs))
#______________________________________________________________________________
def parse_csv(input, delim=','):
r"""Input is a string consisting of lines, each line has comma-delimited
fields. Convert this into a list of lists. Blank lines are skipped.
Fields that look like numbers are converted to numbers.
The delim defaults to ',' but '\t' and None are also reasonable values.
>>> parse_csv('1, 2, 3 \n 0, 2, na')
[[1, 2, 3], [0, 2, 'na']]
"""
lines = [line for line in input.splitlines() if line.strip() is not '']
return [map(num_or_str, line.split(delim)) for line in lines]
def rms_error(predictions, targets):
return math.sqrt(ms_error(predictions, targets))
def ms_error(predictions, targets):
return mean([(p - t)**2 for p, t in zip(predictions, targets)])
def mean_error(predictions, targets):
return mean([abs(p - t) for p, t in zip(predictions, targets)])
def mean_boolean_error(predictions, targets):
return mean([(p != t) for p, t in zip(predictions, targets)])
#______________________________________________________________________________
class Learner:
"""A Learner, or Learning Algorithm, can be trained with a dataset,
and then asked to predict the target attribute of an example."""
def train(self, dataset):
self.dataset = dataset
def predict(self, example):
abstract
#______________________________________________________________________________
class MajorityLearner(Learner):
"""A very dumb algorithm: always pick the result that was most popular
in the training data. Makes a baseline for comparison."""
def train(self, dataset):
"Find the target value that appears most often."
self.most_popular = mode([e[dataset.target] for e in dataset.examples])
def predict(self, example):
"Always return same result: the most popular from the training set."
return self.most_popular
#______________________________________________________________________________
class NaiveBayesLearner(Learner):
def train(self, dataset):
"""Just count the target/attr/val occurences.
Count how many times each value of each attribute occurs.
Store count in N[targetvalue][attr][val]. Let N[attr][None] be the
sum over all vals."""
N = {}
## Initialize to 0
for gv in self.dataset.values[self.dataset.target]:
N[gv] = {}
for attr in self.dataset.attrs:
N[gv][attr] = {}
for val in self.dataset.values[attr]:
N[gv][attr][val] = 0
N[gv][attr][None] = 0
## Go thru examples
for example in self.dataset.examples:
Ngv = N[example[self.dataset.target]]
for attr in self.dataset.attrs:
Ngv[attr][example[attr]] += 1
Ngv[attr][None] += 1
self._N = N
def N(self, targetval, attr, attrval):
"Return the count in the training data of this combination."
try:
return self._N[targetval][attr][attrval]
except KeyError:
return 0
def P(self, targetval, attr, attrval):
"""Smooth the raw counts to give a probability estimate.
Estimate adds 1 to numerator and len(possible vals) to denominator."""
return ((self.N(targetval, attr, attrval) + 1.0) /
(self.N(targetval, attr, None) + len(self.dataset.values[attr])))
def predict(self, example):
"""Predict the target value for example. Consider each possible value,
choose the most likely, by looking at each attribute independently."""
possible_values = self.dataset.values[self.dataset.target]
def class_probability(targetval):
return product([self.P(targetval, a, example[a])
for a in self.dataset.inputs], 1)
return argmax(possible_values, class_probability)
#______________________________________________________________________________
class NearestNeighborLearner(Learner):
def __init__(self, k=1):
"k-NearestNeighbor: the k nearest neighbors vote."
self.k = k
def predict(self, example):
"""With k=1, find the point closest to example.
With k>1, find k closest, and have them vote for the best."""
if self.k == 1:
neighbor = argmin(self.dataset.examples,
lambda e: self.distance(e, example))
return neighbor[self.dataset.target]
else:
## Maintain a sorted list of (distance, example) pairs.
## For very large k, a PriorityQueue would be better
best = []
for e in examples:
d = self.distance(e, example)
if len(best) < k:
e.append((d, e))
elif d < best[-1][0]:
best[-1] = (d, e)
best.sort()
return mode([e[self.dataset.target] for (d, e) in best])
def distance(self, e1, e2):
return mean_boolean_error(e1, e2)
#______________________________________________________________________________
class DecisionTree:
"""A DecisionTree holds an attribute that is being tested, and a
dict of {attrval: Tree} entries. If Tree here is not a DecisionTree
then it is the final classification of the example."""
def __init__(self, attr, attrname=None, branches=None):
"Initialize by saying what attribute this node tests."
update(self, attr=attr, attrname=attrname or attr,
branches=branches or {})
def predict(self, example):
"Given an example, use the tree to classify the example."
child = self.branches[example[self.attr]]
if isinstance(child, DecisionTree):
return child.predict(example)
else:
return child
def add(self, val, subtree):
"Add a branch. If self.attr = val, go to the given subtree."
self.branches[val] = subtree
return self
def display(self, indent=0):
name = self.attrname
print 'Test', name
for (val, subtree) in self.branches.items():
print ' '*4*indent, name, '=', val, '==>',
if isinstance(subtree, DecisionTree):
subtree.display(indent+1)
else:
print 'RESULT = ', subtree
def __repr__(self):
return 'DecisionTree(%r, %r, %r)' % (
self.attr, self.attrname, self.branches)
Yes, No = True, False
#______________________________________________________________________________
class DecisionTreeLearner(Learner):
def predict(self, example):
if isinstance(self.dt, DecisionTree):
return self.dt.predict(example)
else:
return self.dt
def train(self, dataset):
self.dataset = dataset
self.attrnames = dataset.attrnames
self.dt = self.decision_tree_learning(dataset.examples, dataset.inputs)
def decision_tree_learning(self, examples, attrs, default=None):
if len(examples) == 0:
return default
elif self.all_same_class(examples):
return examples[0][self.dataset.target]
elif len(attrs) == 0:
return self.majority_value(examples)
else:
best = self.choose_attribute(attrs, examples)
tree = DecisionTree(best, self.attrnames[best])
for (v, examples_i) in self.split_by(best, examples):
subtree = self.decision_tree_learning(examples_i,
removeall(best, attrs), self.majority_value(examples))
tree.add(v, subtree)
return tree
def choose_attribute(self, attrs, examples):
"Choose the attribute with the highest information gain."
return argmax(attrs, lambda a: self.information_gain(a, examples))
def all_same_class(self, examples):
"Are all these examples in the same target class?"
target = self.dataset.target
class0 = examples[0][target]
for e in examples:
if e[target] != class0: return False
return True
def majority_value(self, examples):
"""Return the most popular target value for this set of examples.
(If target is binary, this is the majority; otherwise plurality.)"""
g = self.dataset.target
return argmax(self.dataset.values[g],
lambda v: self.count(g, v, examples))
def count(self, attr, val, examples):
return count_if(lambda e: e[attr] == val, examples)
def information_gain(self, attr, examples):
def I(examples):
target = self.dataset.target
return information_content([self.count(target, v, examples)
for v in self.dataset.values[target]])
N = float(len(examples))
remainder = 0
for (v, examples_i) in self.split_by(attr, examples):
remainder += (len(examples_i) / N) * I(examples_i)
return I(examples) - remainder
def split_by(self, attr, examples=None):
"Return a list of (val, examples) pairs for each val of attr."
if examples == None:
examples = self.dataset.examples
return [(v, [e for e in examples if e[attr] == v])
for v in self.dataset.values[attr]]
def information_content(values):
"Number of bits to represent the probability distribution in values."
# If the values do not sum to 1, normalize them to make them a Prob. Dist.
values = removeall(0, values)
s = float(sum(values))
if s != 1.0: values = [v/s for v in values]
return sum([- v * log2(v) for v in values])
#______________________________________________________________________________
### A decision list is implemented as a list of (test, value) pairs.
class DecisionListLearner(Learner):
def train(self, dataset):
self.dataset = dataset
self.attrnames = dataset.attrnames
self.dl = self.decision_list_learning(Set(dataset.examples))
def decision_list_learning(self, examples):
"""[Fig. 18.14]"""
if not examples:
return [(True, No)]
t, o, examples_t = self.find_examples(examples)
if not t:
raise Failure
return [(t, o)] + self.decision_list_learning(examples - examples_t)
def find_examples(self, examples):
"""Find a set of examples that all have the same outcome under some test.
Return a tuple of the test, outcome, and examples."""
NotImplemented
#______________________________________________________________________________
class NeuralNetLearner(Learner):
"""Layered feed-forward network."""
def __init__(self, sizes):
self.activations = map(lambda n: [0.0 for i in range(n)], sizes)
self.weights = []
def train(self, dataset):
NotImplemented
def predict(self, example):
NotImplemented
class NNUnit:
"""Unit of a neural net."""
def __init__(self):
NotImplemented
class PerceptronLearner(NeuralNetLearner):
def predict(self, example):
return sum([])
#______________________________________________________________________________
class Linearlearner(Learner):
"""Fit a linear model to the data."""
NotImplemented
#______________________________________________________________________________
class EnsembleLearner(Learner):
"""Given a list of learning algorithms, have them vote."""
def __init__(self, learners=[]):
self.learners=learners
def train(self, dataset):
for learner in self.learners:
learner.train(dataset)
def predict(self, example):
return mode([learner.predict(example) for learner in self.learners])
#_____________________________________________________________________________
# Functions for testing learners on examples
def test(learner, dataset, examples=None, verbose=0):
"""Return the proportion of the examples that are correctly predicted.
Assumes the learner has already been trained."""
if examples == None: examples = dataset.examples
if len(examples) == 0: return 0.0
right = 0.0
for example in examples:
desired = example[dataset.target]
output = learner.predict(dataset.sanitize(example))
if output == desired:
right += 1
if verbose >= 2:
print ' OK: got %s for %s' % (desired, example)
elif verbose:
print 'WRONG: got %s, expected %s for %s' % (
output, desired, example)
return right / len(examples)
def train_and_test(learner, dataset, start, end):
"""Reserve dataset.examples[start:end] for test; train on the remainder.
Return the proportion of examples correct on the test examples."""
examples = dataset.examples
try:
dataset.examples = examples[:start] + examples[end:]
learner.dataset = dataset
learner.train(dataset)
return test(learner, dataset, examples[start:end])
finally:
dataset.examples = examples
def cross_validation(learner, dataset, k=10, trials=1):
"""Do k-fold cross_validate and return their mean.
That is, keep out 1/k of the examples for testing on each of k runs.
Shuffle the examples first; If trials>1, average over several shuffles."""
if k == None:
k = len(dataset.examples)
if trials > 1:
return mean([cross_validation(learner, dataset, k, trials=1)
for t in range(trials)])
else:
n = len(dataset.examples)
random.shuffle(dataset.examples)
return mean([train_and_test(learner, dataset, i*(n/k), (i+1)*(n/k))
for i in range(k)])
def leave1out(learner, dataset):
"Leave one out cross-validation over the dataset."
return cross_validation(learner, dataset, k=len(dataset.examples))
def learningcurve(learner, dataset, trials=10, sizes=None):
if sizes == None:
sizes = range(2, len(dataset.examples)-10, 2)
def score(learner, size):
random.shuffle(dataset.examples)
return train_and_test(learner, dataset, 0, size)
return [(size, mean([score(learner, size) for t in range(trials)]))
for size in sizes]
#______________________________________________________________________________
# The rest of this file gives Data sets for machine learning problems.
orings = DataSet(name='orings', target='Distressed',
attrnames="Rings Distressed Temp Pressure Flightnum")
zoo = DataSet(name='zoo', target='type', exclude=['name'],
attrnames="name hair feathers eggs milk airborne aquatic " +
"predator toothed backbone breathes venomous fins legs tail " +
"domestic catsize type")
iris = DataSet(name="iris", target="class",
attrnames="sepal-len sepal-width petal-len petal-width class")
#______________________________________________________________________________
# The Restaurant example from Fig. 18.2
def RestaurantDataSet(examples=None):
"Build a DataSet of Restaurant waiting examples."
return DataSet(name='restaurant', target='Wait', examples=examples,
attrnames='Alternate Bar Fri/Sat Hungry Patrons Price '
+ 'Raining Reservation Type WaitEstimate Wait')
restaurant = RestaurantDataSet()
def T(attrname, branches):
return DecisionTree(restaurant.attrnum(attrname), attrname, branches)
Fig[18,2] = T('Patrons',
{'None': 'No', 'Some': 'Yes', 'Full':
T('WaitEstimate',
{'>60': 'No', '0-10': 'Yes',
'30-60':
T('Alternate', {'No':
T('Reservation', {'Yes': 'Yes', 'No':
T('Bar', {'No':'No',
'Yes':'Yes'})}),
'Yes':
T('Fri/Sat', {'No': 'No', 'Yes': 'Yes'})}),
'10-30':
T('Hungry', {'No': 'Yes', 'Yes':
T('Alternate',
{'No': 'Yes', 'Yes':
T('Raining', {'No': 'No', 'Yes': 'Yes'})})})})})
def SyntheticRestaurant(n=20):
"Generate a DataSet with n examples."
def gen():
example = map(random.choice, restaurant.values)
example[restaurant.target] = Fig[18,2].predict(example)
return example
return RestaurantDataSet([gen() for i in range(n)])
#______________________________________________________________________________
# Artificial, generated examples.
def Majority(k, n):
"""Return a DataSet with n k-bit examples of the majority problem:
k random bits followed by a 1 if more than half the bits are 1, else 0."""
examples = []
for i in range(n):
bits = [random.choice([0, 1]) for i in range(k)]
bits.append(sum(bits) > k/2)
examples.append(bits)
return DataSet(name="majority", examples=examples)
def Parity(k, n, name="parity"):
"""Return a DataSet with n k-bit examples of the parity problem:
k random bits followed by a 1 if an odd number of bits are 1, else 0."""
examples = []
for i in range(n):
bits = [random.choice([0, 1]) for i in range(k)]
bits.append(sum(bits) % 2)
examples.append(bits)
return DataSet(name=name, examples=examples)
def Xor(n):
"""Return a DataSet with n examples of 2-input xor."""
return Parity(2, n, name="xor")
def ContinuousXor(n):
"2 inputs are chosen uniformly form (0.0 .. 2.0]; output is xor of ints."
examples = []
for i in range(n):
x, y = [random.uniform(0.0, 2.0) for i in '12']
examples.append([x, y, int(x) != int(y)])
return DataSet(name="continuous xor", examples=examples)
#______________________________________________________________________________
def compare(algorithms=[MajorityLearner, NaiveBayesLearner,
NearestNeighborLearner, DecisionTreeLearner],
datasets=[iris, orings, zoo, restaurant, SyntheticRestaurant(20),
Majority(7, 100), Parity(7, 100), Xor(100)],
k=10, trials=1):
"""Compare various learners on various datasets using cross-validation.
Print results as a table."""
print_table([[a.__name__.replace('Learner','')] +
[cross_validation(a(), d, k, trials) for d in datasets]
for a in algorithms],
header=[''] + [d.name[0:7] for d in datasets], round=2)

@ -0,0 +1,888 @@
"""Representations and Inference for Logic (Chapters 7-10)
Covers both Propositional and First-Order Logic. First we have four
important data types:
KB Abstract class holds a knowledge base of logical expressions
KB_Agent Abstract class subclasses agents.Agent
Expr A logical expression
substitution Implemented as a dictionary of var:value pairs, {x:1, y:x}
Be careful: some functions take an Expr as argument, and some take a KB.
Then we implement various functions for doing logical inference:
pl_true Evaluate a propositional logical sentence in a model
tt_entails Say if a statement is entailed by a KB
pl_resolution Do resolution on propositional sentences
dpll_satisfiable See if a propositional sentence is satisfiable
WalkSAT (not yet implemented)
And a few other functions:
to_cnf Convert to conjunctive normal form
unify Do unification of two FOL sentences
diff, simp Symbolic differentiation and simplification
"""
from __future__ import generators
import re
import agents
from utils import *
#______________________________________________________________________________
class KB:
"""A Knowledge base to which you can tell and ask sentences.
To create a KB, first subclass this class and implement
tell, ask_generator, and retract. Why ask_generator instead of ask?
The book is a bit vague on what ask means --
For a Propositional Logic KB, ask(P & Q) returns True or False, but for an
FOL KB, something like ask(Brother(x, y)) might return many substitutions
such as {x: Cain, y: Able}, {x: Able, y: Cain}, {x: George, y: Jeb}, etc.
So ask_generator generates these one at a time, and ask either returns the
first one or returns False."""
def __init__(self, sentence=None):
abstract
def tell(self, sentence):
"Add the sentence to the KB"
abstract
def ask(self, query):
"""Ask returns a substitution that makes the query true, or
it returns False. It is implemented in terms of ask_generator."""
try:
return self.ask_generator(query).next()
except StopIteration:
return False
def ask_generator(self, query):
"Yield all the substitutions that make query true."
abstract
def retract(self, sentence):
"Remove the sentence from the KB"
abstract
class PropKB(KB):
"A KB for Propositional Logic. Inefficient, with no indexing."
def __init__(self, sentence=None):
self.clauses = []
if sentence:
self.tell(sentence)
def tell(self, sentence):
"Add the sentence's clauses to the KB"
self.clauses.extend(conjuncts(to_cnf(sentence)))
def ask_generator(self, query):
"Yield the empty substitution if KB implies query; else False"
if not tt_entails(Expr('&', *self.clauses), query):
return
yield {}
def retract(self, sentence):
"Remove the sentence's clauses from the KB"
for c in conjuncts(to_cnf(sentence)):
if c in self.clauses:
self.clauses.remove(c)
#______________________________________________________________________________
class KB_Agent(agents.Agent):
"""A generic logical knowledge-based agent. [Fig. 7.1]"""
def __init__(self, KB):
t = 0
def program(percept):
KB.tell(self.make_percept_sentence(percept, t))
action = KB.ask(self.make_action_query(t))
KB.tell(self.make_action_sentence(action, t))
t = t + 1
return action
self.program = program
def make_percept_sentence(self, percept, t):
return(Expr("Percept")(percept, t))
def make_action_query(self, t):
return(expr("ShouldDo(action, %d)" % t))
def make_action_sentence(self, action, t):
return(Expr("Did")(action, t))
#______________________________________________________________________________
class Expr:
"""A symbolic mathematical expression. We use this class for logical
expressions, and for terms within logical expressions. In general, an
Expr has an op (operator) and a list of args. The op can be:
Null-ary (no args) op:
A number, representing the number itself. (e.g. Expr(42) => 42)
A symbol, representing a variable or constant (e.g. Expr('F') => F)
Unary (1 arg) op:
'~', '-', representing NOT, negation (e.g. Expr('~', Expr('P')) => ~P)
Binary (2 arg) op:
'>>', '<<', representing forward and backward implication
'+', '-', '*', '/', '**', representing arithmetic operators
'<', '>', '>=', '<=', representing comparison operators
'<=>', '^', representing logical equality and XOR
N-ary (0 or more args) op:
'&', '|', representing conjunction and disjunction
A symbol, representing a function term or FOL proposition
Exprs can be constructed with operator overloading: if x and y are Exprs,
then so are x + y and x & y, etc. Also, if F and x are Exprs, then so is
F(x); it works by overloading the __call__ method of the Expr F. Note
that in the Expr that is created by F(x), the op is the str 'F', not the
Expr F. See http://www.python.org/doc/current/ref/specialnames.html
to learn more about operator overloading in Python.
WARNING: x == y and x != y are NOT Exprs. The reason is that we want
to write code that tests 'if x == y:' and if x == y were the same
as Expr('==', x, y), then the result would always be true; not what a
programmer would expect. But we still need to form Exprs representing
equalities and disequalities. We concentrate on logical equality (or
equivalence) and logical disequality (or XOR). You have 3 choices:
(1) Expr('<=>', x, y) and Expr('^', x, y)
Note that ^ is bitwose XOR in Python (and Java and C++)
(2) expr('x <=> y') and expr('x =/= y').
See the doc string for the function expr.
(3) (x % y) and (x ^ y).
It is very ugly to have (x % y) mean (x <=> y), but we need
SOME operator to make (2) work, and this seems the best choice.
WARNING: if x is an Expr, then so is x + 1, because the int 1 gets
coerced to an Expr by the constructor. But 1 + x is an error, because
1 doesn't know how to add an Expr. (Adding an __radd__ method to Expr
wouldn't help, because int.__add__ is still called first.) Therefore,
you should use Expr(1) + x instead, or ONE + x, or expr('1 + x').
"""
def __init__(self, op, *args):
"Op is a string or number; args are Exprs (or are coerced to Exprs)."
assert isinstance(op, str) or (isnumber(op) and not args)
self.op = num_or_str(op)
self.args = map(expr, args) ## Coerce args to Exprs
def __call__(self, *args):
"""Self must be a symbol with no args, such as Expr('F'). Create a new
Expr with 'F' as op and the args as arguments."""
assert is_symbol(self.op) and not self.args
return Expr(self.op, *args)
def __repr__(self):
"Show something like 'P' or 'P(x, y)', or '~P' or '(P | Q | R)'"
if len(self.args) == 0: # Constant or proposition with arity 0
return str(self.op)
elif is_symbol(self.op): # Functional or Propositional operator
return '%s(%s)' % (self.op, ', '.join(map(repr, self.args)))
elif len(self.args) == 1: # Prefix operator
return self.op + repr(self.args[0])
else: # Infix operator
return '(%s)' % (' '+self.op+' ').join(map(repr, self.args))
def __eq__(self, other):
"""x and y are equal iff their ops and args are equal."""
return (other is self) or (isinstance(other, Expr)
and self.op == other.op and self.args == other.args)
def __hash__(self):
"Need a hash method so Exprs can live in dicts."
return hash(self.op) ^ hash(tuple(self.args))
# See http://www.python.org/doc/current/lib/module-operator.html
# Not implemented: not, abs, pos, concat, contains, *item, *slice
def __lt__(self, other): return Expr('<', self, other)
def __le__(self, other): return Expr('<=', self, other)
def __ge__(self, other): return Expr('>=', self, other)
def __gt__(self, other): return Expr('>', self, other)
def __add__(self, other): return Expr('+', self, other)
def __sub__(self, other): return Expr('-', self, other)
def __and__(self, other): return Expr('&', self, other)
def __div__(self, other): return Expr('/', self, other)
def __truediv__(self, other):return Expr('/', self, other)
def __invert__(self): return Expr('~', self)
def __lshift__(self, other): return Expr('<<', self, other)
def __rshift__(self, other): return Expr('>>', self, other)
def __mul__(self, other): return Expr('*', self, other)
def __neg__(self): return Expr('-', self)
def __or__(self, other): return Expr('|', self, other)
def __pow__(self, other): return Expr('**', self, other)
def __xor__(self, other): return Expr('^', self, other)
def __mod__(self, other): return Expr('<=>', self, other) ## (x % y)
def expr(s):
"""Create an Expr representing a logic expression by parsing the input
string. Symbols and numbers are automatically converted to Exprs.
In addition you can use alternative spellings of these operators:
'x ==> y' parses as (x >> y) # Implication
'x <== y' parses as (x << y) # Reverse implication
'x <=> y' parses as (x % y) # Logical equivalence
'x =/= y' parses as (x ^ y) # Logical disequality (xor)
But BE CAREFUL; precedence of implication is wrong. expr('P & Q ==> R & S')
is ((P & (Q >> R)) & S); so you must use expr('(P & Q) ==> (R & S)').
>>> expr('P <=> Q(1)')
(P <=> Q(1))
>>> expr('P & Q | ~R(x, F(x))')
((P & Q) | ~R(x, F(x)))
"""
if isinstance(s, Expr): return s
if isnumber(s): return Expr(s)
## Replace the alternative spellings of operators with canonical spellings
s = s.replace('==>', '>>').replace('<==', '<<')
s = s.replace('<=>', '%').replace('=/=', '^')
## Replace a symbol or number, such as 'P' with 'Expr("P")'
s = re.sub(r'([a-zA-Z0-9_.]+)', r'Expr("\1")', s)
## Now eval the string. (A security hole; do not use with an adversary.)
return eval(s, {'Expr':Expr})
def is_symbol(s):
"A string s is a symbol if it starts with an alphabetic char."
return isinstance(s, str) and s[0].isalpha()
def is_var_symbol(s):
"A logic variable symbol is an initial-lowercase string."
return is_symbol(s) and s[0].islower()
def is_prop_symbol(s):
"""A proposition logic symbol is an initial-uppercase string other than
TRUE or FALSE."""
return is_symbol(s) and s[0].isupper() and s != 'TRUE' and s != 'FALSE'
## Useful constant Exprs used in examples and code:
TRUE, FALSE, ZERO, ONE, TWO = map(Expr, ['TRUE', 'FALSE', 0, 1, 2])
A, B, C, F, G, P, Q, x, y, z = map(Expr, 'ABCFGPQxyz')
#______________________________________________________________________________
def tt_entails(kb, alpha):
"""Use truth tables to determine if KB entails sentence alpha. [Fig. 7.10]
>>> tt_entails(expr('P & Q'), expr('Q'))
True
"""
return tt_check_all(kb, alpha, prop_symbols(kb & alpha), {})
def tt_check_all(kb, alpha, symbols, model):
"Auxiliary routine to implement tt_entails."
if not symbols:
if pl_true(kb, model): return pl_true(alpha, model)
else: return True
assert result != None
else:
P, rest = symbols[0], symbols[1:]
return (tt_check_all(kb, alpha, rest, extend(model, P, True)) and
tt_check_all(kb, alpha, rest, extend(model, P, False)))
def prop_symbols(x):
"Return a list of all propositional symbols in x."
if not isinstance(x, Expr):
return []
elif is_prop_symbol(x.op):
return [x]
else:
s = set(())
for arg in x.args:
for symbol in prop_symbols(arg):
s.add(symbol)
return list(s)
def tt_true(alpha):
"""Is the sentence alpha a tautology? (alpha will be coerced to an expr.)
>>> tt_true(expr("(P >> Q) <=> (~P | Q)"))
True
"""
return tt_entails(TRUE, expr(alpha))
def pl_true(exp, model={}):
"""Return True if the propositional logic expression is true in the model,
and False if it is false. If the model does not specify the value for
every proposition, this may return None to indicate 'not obvious';
this may happen even when the expression is tautological."""
op, args = exp.op, exp.args
if exp == TRUE:
return True
elif exp == FALSE:
return False
elif is_prop_symbol(op):
return model.get(exp)
elif op == '~':
p = pl_true(args[0], model)
if p == None: return None
else: return not p
elif op == '|':
result = False
for arg in args:
p = pl_true(arg, model)
if p == True: return True
if p == None: result = None
return result
elif op == '&':
result = True
for arg in args:
p = pl_true(arg, model)
if p == False: return False
if p == None: result = None
return result
p, q = args
if op == '>>':
return pl_true(~p | q, model)
elif op == '<<':
return pl_true(p | ~q, model)
pt = pl_true(p, model)
if pt == None: return None
qt = pl_true(q, model)
if qt == None: return None
if op == '<=>':
return pt == qt
elif op == '^':
return pt != qt
else:
raise ValueError, "illegal operator in logic expression" + str(exp)
#______________________________________________________________________________
## Convert to Conjunctive Normal Form (CNF)
def to_cnf(s):
"""Convert a propositional logical sentence s to conjunctive normal form.
That is, of the form ((A | ~B | ...) & (B | C | ...) & ...) [p. 215]
>>> to_cnf("~(B|C)")
(~B & ~C)
>>> to_cnf("B <=> (P1|P2)")
((~P1 | B) & (~P2 | B) & (P1 | P2 | ~B))
>>> to_cnf("a | (b & c) | d")
((b | a | d) & (c | a | d))
>>> to_cnf("A & (B | (D & E))")
(A & (D | B) & (E | B))
"""
if isinstance(s, str): s = expr(s)
s = eliminate_implications(s) # Steps 1, 2 from p. 215
s = move_not_inwards(s) # Step 3
return distribute_and_over_or(s) # Step 4
def eliminate_implications(s):
"""Change >>, <<, and <=> into &, |, and ~. That is, return an Expr
that is equivalent to s, but has only &, |, and ~ as logical operators.
>>> eliminate_implications(A >> (~B << C))
((~B | ~C) | ~A)
"""
if not s.args or is_symbol(s.op): return s ## (Atoms are unchanged.)
args = map(eliminate_implications, s.args)
a, b = args[0], args[-1]
if s.op == '>>':
return (b | ~a)
elif s.op == '<<':
return (a | ~b)
elif s.op == '<=>':
return (a | ~b) & (b | ~a)
else:
return Expr(s.op, *args)
def move_not_inwards(s):
"""Rewrite sentence s by moving negation sign inward.
>>> move_not_inwards(~(A | B))
(~A & ~B)
>>> move_not_inwards(~(A & B))
(~A | ~B)
>>> move_not_inwards(~(~(A | ~B) | ~~C))
((A | ~B) & ~C)
"""
if s.op == '~':
NOT = lambda b: move_not_inwards(~b)
a = s.args[0]
if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A
if a.op =='&': return NaryExpr('|', *map(NOT, a.args))
if a.op =='|': return NaryExpr('&', *map(NOT, a.args))
return s
elif is_symbol(s.op) or not s.args:
return s
else:
return Expr(s.op, *map(move_not_inwards, s.args))
def distribute_and_over_or(s):
"""Given a sentence s consisting of conjunctions and disjunctions
of literals, return an equivalent sentence in CNF.
>>> distribute_and_over_or((A & B) | C)
((A | C) & (B | C))
"""
if s.op == '|':
s = NaryExpr('|', *s.args)
if len(s.args) == 0:
return FALSE
if len(s.args) == 1:
return distribute_and_over_or(s.args[0])
conj = find_if((lambda d: d.op == '&'), s.args)
if not conj:
return NaryExpr(s.op, *s.args)
others = [a for a in s.args if a is not conj]
if len(others) == 1:
rest = others[0]
else:
rest = NaryExpr('|', *others)
return NaryExpr('&', *map(distribute_and_over_or,
[(c|rest) for c in conj.args]))
elif s.op == '&':
return NaryExpr('&', *map(distribute_and_over_or, s.args))
else:
return s
_NaryExprTable = {'&':TRUE, '|':FALSE, '+':ZERO, '*':ONE}
def NaryExpr(op, *args):
"""Create an Expr, but with an nary, associative op, so we can promote
nested instances of the same op up to the top level.
>>> NaryExpr('&', (A&B),(B|C),(B&C))
(A & B & (B | C) & B & C)
"""
arglist = []
for arg in args:
if arg.op == op: arglist.extend(arg.args)
else: arglist.append(arg)
if len(args) == 1:
return args[0]
elif len(args) == 0:
return _NaryExprTable[op]
else:
return Expr(op, *arglist)
def conjuncts(s):
"""Return a list of the conjuncts in the sentence s.
>>> conjuncts(A & B)
[A, B]
>>> conjuncts(A | B)
[(A | B)]
"""
if isinstance(s, Expr) and s.op == '&':
return s.args
else:
return [s]
def disjuncts(s):
"""Return a list of the disjuncts in the sentence s.
>>> disjuncts(A | B)
[A, B]
>>> disjuncts(A & B)
[(A & B)]
"""
if isinstance(s, Expr) and s.op == '|':
return s.args
else:
return [s]
#______________________________________________________________________________
def pl_resolution(KB, alpha):
"Propositional Logic Resolution: say if alpha follows from KB. [Fig. 7.12]"
clauses = KB.clauses + conjuncts(to_cnf(~alpha))
new = set()
while True:
n = len(clauses)
pairs = [(clauses[i], clauses[j]) for i in range(n) for j in range(i+1, n)]
for (ci, cj) in pairs:
resolvents = pl_resolve(ci, cj)
if FALSE in resolvents: return True
new.union_update(set(resolvents))
if new.issubset(set(clauses)): return False
for c in new:
if c not in clauses: clauses.append(c)
def pl_resolve(ci, cj):
"""Return all clauses that can be obtained by resolving clauses ci and cj.
>>> pl_resolve(to_cnf(A|B|C), to_cnf(~B|~C|F))
[(A | C | ~C | F), (A | B | ~B | F)]
"""
clauses = []
for di in disjuncts(ci):
for dj in disjuncts(cj):
if di == ~dj or ~di == dj:
dnew = unique(removeall(di, disjuncts(ci)) +
removeall(dj, disjuncts(cj)))
clauses.append(NaryExpr('|', *dnew))
return clauses
#______________________________________________________________________________
class PropHornKB(PropKB):
"A KB of Propositional Horn clauses."
def tell(self, sentence):
"Add a Horn Clauses to this KB."
op = sentence.op
assert op == '>>' or is_prop_symbol(op), "Must be Horn Clause"
self.clauses.append(sentence)
def ask_generator(self, query):
"Yield the empty substitution if KB implies query; else False"
if not pl_fc_entails(self.clauses, query):
return
yield {}
def retract(self, sentence):
"Remove the sentence's clauses from the KB"
for c in conjuncts(to_cnf(sentence)):
if c in self.clauses:
self.clauses.remove(c)
def clauses_with_premise(self, p):
"""The list of clauses in KB that have p in the premise.
This could be cached away for O(1) speed, but we'll recompute it."""
return [c for c in self.clauses
if c.op == '>>' and p in conjuncts(c.args[0])]
def pl_fc_entails(KB, q):
"""Use forward chaining to see if a HornKB entails symbol q. [Fig. 7.14]
>>> pl_fc_entails(Fig[7,15], expr('Q'))
True
"""
count = dict([(c, len(conjuncts(c.args[0]))) for c in KB.clauses
if c.op == '>>'])
inferred = DefaultDict(False)
agenda = [s for s in KB.clauses if is_prop_symbol(s.op)]
if q in agenda: return True
while agenda:
p = agenda.pop()
if not inferred[p]:
inferred[p] = True
for c in KB.clauses_with_premise(p):
count[c] -= 1
if count[c] == 0:
if c.args[1] == q: return True
agenda.append(c.args[1])
return False
## Wumpus World example [Fig. 7.13]
Fig[7,13] = expr("(B11 <=> (P12 | P21)) & ~B11")
## Propositional Logic Forward Chanining example [Fig. 7.15]
Fig[7,15] = PropHornKB()
for s in "P>>Q (L&M)>>P (B&L)>>M (A&P)>>L (A&B)>>L A B".split():
Fig[7,15].tell(expr(s))
#______________________________________________________________________________
# DPLL-Satisfiable [Fig. 7.16]
def dpll_satisfiable(s):
"""Check satisfiability of a propositional sentence.
This differs from the book code in two ways: (1) it returns a model
rather than True when it succeeds; this is more useful. (2) The
function find_pure_symbol is passed a list of unknown clauses, rather
than a list of all clauses and the model; this is more efficient.
>>> dpll_satisfiable(A&~B)
{A: True, B: False}
>>> dpll_satisfiable(P&~P)
False
"""
clauses = conjuncts(to_cnf(s))
symbols = prop_symbols(s)
return dpll(clauses, symbols, {})
def dpll(clauses, symbols, model):
"See if the clauses are true in a partial model."
unknown_clauses = [] ## clauses with an unknown truth value
for c in clauses:
val = pl_true(c, model)
if val == False:
return False
if val != True:
unknown_clauses.append(c)
if not unknown_clauses:
return model
P, value = find_pure_symbol(symbols, unknown_clauses)
if P:
return dpll(clauses, removeall(P, symbols), extend(model, P, value))
P, value = find_unit_clause(clauses, model)
if P:
return dpll(clauses, removeall(P, symbols), extend(model, P, value))
P = symbols.pop()
return (dpll(clauses, symbols, extend(model, P, True)) or
dpll(clauses, symbols, extend(model, P, False)))
def find_pure_symbol(symbols, unknown_clauses):
"""Find a symbol and its value if it appears only as a positive literal
(or only as a negative) in clauses.
>>> find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A])
(A, True)
"""
for s in symbols:
found_pos, found_neg = False, False
for c in unknown_clauses:
if not found_pos and s in disjuncts(c): found_pos = True
if not found_neg and ~s in disjuncts(c): found_neg = True
if found_pos != found_neg: return s, found_pos
return None, None
def find_unit_clause(clauses, model):
"""A unit clause has only 1 variable that is not bound in the model.
>>> find_unit_clause([A|B|C, B|~C, A|~B], {A:True})
(B, False)
"""
for clause in clauses:
num_not_in_model = 0
for literal in disjuncts(clause):
sym = literal_symbol(literal)
if sym not in model:
num_not_in_model += 1
P, value = sym, (literal.op != '~')
if num_not_in_model == 1:
return P, value
return None, None
def literal_symbol(literal):
"""The symbol in this literal (without the negation).
>>> literal_symbol(P)
P
>>> literal_symbol(~P)
P
"""
if literal.op == '~':
return literal.args[0]
else:
return literal
#______________________________________________________________________________
# Walk-SAT [Fig. 7.17]
def WalkSAT(clauses, p=0.5, max_flips=10000):
## model is a random assignment of true/false to the symbols in clauses
## See ~/aima1e/print1/manual/knowledge+logic-answers.tex ???
model = dict([(s, random.choice([True, False]))
for s in prop_symbols(clauses)])
for i in range(max_flips):
satisfied, unsatisfied = [], []
for clause in clauses:
if_(pl_true(clause, model), satisfied, unsatisfied).append(clause)
if not unsatisfied: ## if model satisfies all the clauses
return model
clause = random.choice(unsatisfied)
if probability(p):
sym = random.choice(prop_symbols(clause))
else:
## Flip the symbol in clause that miximizes number of sat. clauses
raise NotImplementedError
model[sym] = not model[sym]
# PL-Wumpus-Agent [Fig. 7.19]
class PLWumpusAgent(agents.Agent):
"An agent for the wumpus world that does logical inference. [Fig. 7.19]"""
def __init__(self):
KB = FOLKB()
x, y, orientation = 1, 1, (1, 0)
visited = set() ## squares already visited
action = None
plan = []
def program(percept):
stench, breeze, glitter = percept
x, y, orientation = update_position(x, y, orientation, action)
KB.tell('%sS_%d,%d' % (if_(stench, '', '~'), x, y))
KB.tell('%sB_%d,%d' % (if_(breeze, '', '~'), x, y))
if glitter: action = 'Grab'
elif plan: action = plan.pop()
else:
for [i, j] in fringe(visited):
if KB.ask('~P_%d,%d & ~W_%d,%d' % (i, j, i, j)) != False:
raise NotImplementedError
KB.ask('~P_%d,%d | ~W_%d,%d' % (i, j, i, j)) != False
if action == None:
action = random.choice(['Forward', 'Right', 'Left'])
return action
self.program = program
def update_position(x, y, orientation, action):
if action == 'TurnRight':
orientation = turn_right(orientation)
elif action == 'TurnLeft':
orientation = turn_left(orientation)
elif action == 'Forward':
x, y = x + vector_add((x, y), orientation)
return x, y, orientation
#______________________________________________________________________________
def unify(x, y, s):
"""Unify expressions x,y with substitution s; return a substitution that
would make x,y equal, or None if x,y can not unify. x and y can be
variables (e.g. Expr('x')), constants, lists, or Exprs. [Fig. 9.1]
>>> unify(x + y, y + C, {})
{y: C, x: y}
"""
if s == None:
return None
elif x == y:
return s
elif is_variable(x):
return unify_var(x, y, s)
elif is_variable(y):
return unify_var(y, x, s)
elif isinstance(x, Expr) and isinstance(y, Expr):
return unify(x.args, y.args, unify(x.op, y.op, s))
elif isinstance(x, str) or isinstance(y, str) or not x or not y:
return if_(x == y, s, None)
elif issequence(x) and issequence(y) and len(x) == len(y):
return unify(x[1:], y[1:], unify(x[0], y[0], s))
else:
return None
def is_variable(x):
"A variable is an Expr with no args and a lowercase symbol as the op."
return isinstance(x, Expr) and not x.args and is_var_symbol(x.op)
def unify_var(var, x, s):
if var in s:
return unify(s[var], x, s)
elif occur_check(var, x):
return None
else:
return extend(s, var, x)
def occur_check(var, x):
"Return true if var occurs anywhere in x."
if var == x:
return True
elif isinstance(x, Expr):
return var.op == x.op or occur_check(var, x.args)
elif not isinstance(x, str) and issequence(x):
for xi in x:
if occur_check(var, xi): return True
return False
def extend(s, var, val):
"""Copy the substitution s and extend it by setting var to val; return copy.
>>> extend({x: 1}, y, 2)
{y: 2, x: 1}
"""
s2 = s.copy()
s2[var] = val
return s2
def subst(s, x):
"""Substitute the substitution s into the expression x.
>>> subst({x: 42, y:0}, F(x) + y)
(F(42) + 0)
"""
if isinstance(x, list):
return [subst(s, xi) for xi in x]
elif isinstance(x, tuple):
return tuple([subst(s, xi) for xi in x])
elif not isinstance(x, Expr):
return x
elif is_var_symbol(x.op):
return s.get(x, x)
else:
return Expr(x.op, *[subst(s, arg) for arg in x.args])
def fol_fc_ask(KB, alpha):
"""Inefficient forward chaining for first-order logic. [Fig. 9.3]
KB is an FOLHornKB and alpha must be an atomic sentence."""
while True:
new = {}
for r in KB.clauses:
r1 = standardize_apart(r)
ps, q = conjuncts(r.args[0]), r.args[1]
raise NotImplementedError
def standardize_apart(sentence, dic):
"""Replace all the variables in sentence with new variables."""
if not isinstance(sentence, Expr):
return sentence
elif is_var_symbol(sentence.op):
if sentence in dic:
return dic[sentence]
else:
standardize_apart.counter += 1
dic[sentence] = Expr('V_%d' % standardize-apart.counter)
return dic[sentence]
else:
return Expr(sentence.op, *[standardize-apart(a, dic) for a in sentence.args])
standardize_apart.counter = 0
def fol_bc_ask(KB, goals, theta):
"A simple backward-chaining algorithm for first-order logic. [Fig. 9.6]"
if not goals:
yield theta
q1 = subst(theta, goals[0])
raise NotImplementedError
#______________________________________________________________________________
# Example application (not in the book).
# You can use the Expr class to do symbolic differentiation. This used to be
# a part of AI; now it is considered a separate field, Symbolic Algebra.
def diff(y, x):
"""Return the symbolic derivative, dy/dx, as an Expr.
However, you probably want to simplify the results with simp.
>>> diff(x * x, x)
((x * 1) + (x * 1))
>>> simp(diff(x * x, x))
(2 * x)
"""
if y == x: return ONE
elif not y.args: return ZERO
else:
u, op, v = y.args[0], y.op, y.args[-1]
if op == '+': return diff(u, x) + diff(v, x)
elif op == '-' and len(args) == 1: return -diff(u, x)
elif op == '-': return diff(u, x) - diff(v, x)
elif op == '*': return u * diff(v, x) + v * diff(u, x)
elif op == '/': return (v*diff(u, x) - u*diff(v, x)) / (v * v)
elif op == '**' and isnumber(x.op):
return (v * u ** (v - 1) * diff(u, x))
elif op == '**': return (v * u ** (v - 1) * diff(u, x)
+ u ** v * Expr('log')(u) * diff(v, x))
elif op == 'log': return diff(u, x) / u
else: raise ValueError("Unknown op: %s in diff(%s, %s)" % (op, y, x))
def simp(x):
if not x.args: return x
args = map(simp, x.args)
u, op, v = args[0], x.op, args[-1]
if op == '+':
if v == ZERO: return u
if u == ZERO: return v
if u == v: return TWO * u
if u == -v or v == -u: return ZERO
elif op == '-' and len(args) == 1:
if u.op == '-' and len(u.args) == 1: return u.args[0] ## --y ==> y
elif op == '-':
if v == ZERO: return u
if u == ZERO: return -v
if u == v: return ZERO
if u == -v or v == -u: return ZERO
elif op == '*':
if u == ZERO or v == ZERO: return ZERO
if u == ONE: return v
if v == ONE: return u
if u == v: return u ** 2
elif op == '/':
if u == ZERO: return ZERO
if v == ZERO: return Expr('Undefined')
if u == v: return ONE
if u == -v or v == -u: return ZERO
elif op == '**':
if u == ZERO: return ZERO
if v == ZERO: return ONE
if u == ONE: return ONE
if v == ONE: return u
elif op == 'log':
if u == ONE: return ZERO
else: raise ValueError("Unknown op: " + op)
## If we fall through to here, we can not simplify further
return Expr(op, *args)
def d(y, x):
"Differentiate and then simplify."
return simp(diff(y, x))

@ -0,0 +1,78 @@
### PropKB
>>> kb = PropKB()
>>> kb.tell(A & B)
>>> kb.tell(B >> C)
>>> kb.ask(C) ## The result {} means true, with no substitutions
{}
>>> kb.ask(P)
False
>>> kb.retract(B)
>>> kb.ask(C)
False
>>> pl_true(P, {})
>>> pl_true(P | Q, {P: True})
True
# Notice that the function pl_true cannot reason by cases:
>>> pl_true(P | ~P)
# However, tt_true can:
>>> tt_true(P | ~P)
True
# The following are tautologies from [Fig. 7.11]:
>>> tt_true("(A & B) <=> (B & A)")
True
>>> tt_true("(A | B) <=> (B | A)")
True
>>> tt_true("((A & B) & C) <=> (A & (B & C))")
True
>>> tt_true("((A | B) | C) <=> (A | (B | C))")
True
>>> tt_true("~~A <=> A")
True
>>> tt_true("(A >> B) <=> (~B >> ~A)")
True
>>> tt_true("(A >> B) <=> (~A | B)")
True
>>> tt_true("(A <=> B) <=> ((A >> B) & (B >> A))")
True
>>> tt_true("~(A & B) <=> (~A | ~B)")
True
>>> tt_true("~(A | B) <=> (~A & ~B)")
True
>>> tt_true("(A & (B | C)) <=> ((A & B) | (A & C))")
True
>>> tt_true("(A | (B & C)) <=> ((A | B) & (A | C))")
True
# The following are not tautologies:
>>> tt_true(A & ~A)
False
>>> tt_true(A & B)
False
### [Fig. 7.13]
>>> alpha = expr("~P12")
>>> to_cnf(Fig[7,13] & ~alpha)
((~P12 | B11) & (~P21 | B11) & (P12 | P21 | ~B11) & ~B11 & P12)
>>> tt_entails(Fig[7,13], alpha)
True
>>> pl_resolution(PropKB(Fig[7,13]), alpha)
True
### [Fig. 7.15]
>>> pl_fc_entails(Fig[7,15], expr('SomethingSilly'))
False
### Unification:
>>> unify(x, x, {})
{}
>>> unify(x, 3, {})
{x: 3}
>>> to_cnf((P&Q) | (~P & ~Q))
((~P | P) & (~Q | P) & (~P | Q) & (~Q | Q))

@ -0,0 +1,142 @@
"""Markov Decision Processes (Chapter 17)
First we define an MDP, and the special case of a GridMDP, in which
states are laid out in a 2-dimensional grid. We also represent a policy
as a dictionary of {state:action} pairs, and a Utility function as a
dictionary of {state:number} pairs. We then define the value_iteration
and policy_iteration algorithms."""
from utils import *
class MDP:
"""A Markov Decision Process, defined by an initial state, transition model,
and reward function. We also keep track of a gamma value, for use by
algorithms. The transition model is represented somewhat differently from
the text. Instead of T(s, a, s') being probability number for each
state/action/state triplet, we instead have T(s, a) return a list of (p, s')
pairs. We also keep track of the possible states, terminal states, and
actions for each state. [page 615]"""
def __init__(self, init, actlist, terminals, gamma=.9):
update(self, init=init, actlist=actlist, terminals=terminals,
gamma=gamma, states=set(), reward={})
def R(self, state):
"Return a numeric reward for this state."
return self.reward[state]
def T(state, action):
"""Transition model. From a state and an action, return a list
of (result-state, probability) pairs."""
abstract
def actions(self, state):
"""Set of actions that can be performed in this state. By default, a
fixed list of actions, except for terminal states. Override this
method if you need to specialize by state."""
if state in self.terminals:
return [None]
else:
return self.actlist
class GridMDP(MDP):
"""A two-dimensional grid MDP, as in [Figure 17.1]. All you have to do is
specify the grid as a list of lists of rewards; use None for an obstacle
(unreachable state). Also, you should specify the terminal states.
An action is an (x, y) unit vector; e.g. (1, 0) means move east."""
def __init__(self, grid, terminals, init=(0, 0), gamma=.9):
grid.reverse() ## because we want row 0 on bottom, not on top
MDP.__init__(self, init, actlist=orientations,
terminals=terminals, gamma=gamma)
update(self, grid=grid, rows=len(grid), cols=len(grid[0]))
for x in range(self.cols):
for y in range(self.rows):
self.reward[x, y] = grid[y][x]
if grid[y][x] is not None:
self.states.add((x, y))
def T(self, state, action):
if action == None:
return [(0.0, state)]
else:
return [(0.8, self.go(state, action)),
(0.1, self.go(state, turn_right(action))),
(0.1, self.go(state, turn_left(action)))]
def go(self, state, direction):
"Return the state that results from going in this direction."
state1 = vector_add(state, direction)
return if_(state1 in self.states, state1, state)
def to_grid(self, mapping):
"""Convert a mapping from (x, y) to v into a [[..., v, ...]] grid."""
return list(reversed([[mapping.get((x,y), None)
for x in range(self.cols)]
for y in range(self.rows)]))
def to_arrows(self, policy):
chars = {(1, 0):'>', (0, 1):'^', (-1, 0):'<', (0, -1):'v', None: '.'}
return self.to_grid(dict([(s, chars[a]) for (s, a) in policy.items()]))
#______________________________________________________________________________
Fig[17,1] = GridMDP([[-0.04, -0.04, -0.04, +1],
[-0.04, None, -0.04, -1],
[-0.04, -0.04, -0.04, -0.04]],
terminals=[(3, 2), (3, 1)])
#______________________________________________________________________________
def value_iteration(mdp, epsilon=0.001):
"Solving an MDP by value iteration. [Fig. 17.4]"
U1 = dict([(s, 0) for s in mdp.states])
R, T, gamma = mdp.R, mdp.T, mdp.gamma
while True:
U = U1.copy()
delta = 0
for s in mdp.states:
U1[s] = R(s) + gamma * max([sum([p * U[s1] for (p, s1) in T(s, a)])
for a in mdp.actions(s)])
delta = max(delta, abs(U1[s] - U[s]))
if delta < epsilon * (1 - gamma) / gamma:
return U
def best_policy(mdp, U):
"""Given an MDP and a utility function U, determine the best policy,
as a mapping from state to action. (Equation 17.4)"""
pi = {}
for s in mdp.states:
pi[s] = argmax(mdp.actions(s), lambda a:expected_utility(a, s, U, mdp))
return pi
def expected_utility(a, s, U, mdp):
"The expected utility of doing a in state s, according to the MDP and U."
return sum([p * U[s1] for (p, s1) in mdp.T(s, a)])
#______________________________________________________________________________
def policy_iteration(mdp):
"Solve an MDP by policy iteration [Fig. 17.7]"
U = dict([(s, 0) for s in mdp.states])
pi = dict([(s, random.choice(mdp.actions(s))) for s in mdp.states])
while True:
U = policy_evaluation(pi, U, mdp)
unchanged = True
for s in mdp.states:
a = argmax(mdp.actions(s), lambda a: expected_utility(a,s,U,mdp))
if a != pi[s]:
pi[s] = a
unchanged = False
if unchanged:
return pi
def policy_evaluation(pi, U, mdp, k=20):
"""Return an updated utility mapping U from each state in the MDP to its
utility, using an approximation (modified policy iteration)."""
R, T, gamma = mdp.R, mdp.T, mdp.gamma
for i in range(k):
for s in mdp.states:
U[s] = R(s) + gamma * sum([p * U[s] for (p, s1) in T(s, pi[s])])
return U

@ -0,0 +1,27 @@
### demo
>>> m = Fig[17,1]
>>> pi = best_policy(m, value_iteration(m, .01))
>>> pi
{(3, 2): None, (3, 1): None, (3, 0): (-1, 0), (2, 1): (0, 1), (0, 2): (1, 0), (1, 0): (1, 0), (0, 0): (0, 1), (1, 2): (1, 0), (2, 0): (0, 1), (0, 1): (0, 1), (2, 2): (1, 0)}
>>> m.to_arrows(pi)
[['>', '>', '>', '.'], ['^', None, '^', '.'], ['^', '>', '^', '<']]
>>> print_table(m.to_arrows(pi))
> > > .
^ None ^ .
^ > ^ <
>>> value_iteration(m, .01)
{(3, 2): 1.0, (3, 1): -1.0, (3, 0): 0.12958868267972745, (0, 1): 0.39810203830605462, (0, 2): 0.50928545646220924, (1, 0): 0.25348746162470537, (0, 0): 0.29543540628363629, (1, 2): 0.64958064617168676, (2, 0): 0.34461306281476806, (2, 1): 0.48643676237737926, (2, 2): 0.79536093684710951}
>>> policy_iteration(m)
{(3, 2): None, (3, 1): None, (3, 0): (0, -1), (2, 1): (-1, 0), (0, 2): (1, 0), (1, 0): (1, 0), (0, 0): (1, 0), (1, 2): (1, 0), (2, 0): (1, 0), (0, 1): (1, 0), (2, 2): (1, 0)}
>>> print_table(m.to_arrows(policy_iteration(m)))
> > > .
> None < .
> > > v

@ -0,0 +1,170 @@
"""A chart parser and some grammars. (Chapter 22)"""
from utils import *
#______________________________________________________________________________
# Grammars and Lexicons
def Rules(**rules):
"""Create a dictionary mapping symbols to alternative sequences.
>>> Rules(A = "B C | D E")
{'A': [['B', 'C'], ['D', 'E']]}
"""
for (lhs, rhs) in rules.items():
rules[lhs] = [alt.strip().split() for alt in rhs.split('|')]
return rules
def Lexicon(**rules):
"""Create a dictionary mapping symbols to alternative words.
>>> Lexicon(Art = "the | a | an")
{'Art': ['the', 'a', 'an']}
"""
for (lhs, rhs) in rules.items():
rules[lhs] = [word.strip() for word in rhs.split('|')]
return rules
class Grammar:
def __init__(self, name, rules, lexicon):
"A grammar has a set of rules and a lexicon."
update(self, name=name, rules=rules, lexicon=lexicon)
self.categories = DefaultDict([])
for lhs in lexicon:
for word in lexicon[lhs]:
self.categories[word].append(lhs)
def rewrites_for(self, cat):
"Return a sequence of possible rhs's that cat can be rewritten as."
return self.rules.get(cat, ())
def isa(self, word, cat):
"Return True iff word is of category cat"
return cat in self.categories[word]
def __repr__(self):
return '<Grammar %s>' % self.name
E0 = Grammar('E0',
Rules( # Grammar for E_0 [Fig. 22.4]
S = 'NP VP | S Conjunction S',
NP = 'Pronoun | Noun | Article Noun | Digit Digit | NP PP | NP RelClause',
VP = 'Verb | VP NP | VP Adjective | VP PP | VP Adverb',
PP = 'Preposition NP',
RelClause = 'That VP'),
Lexicon( # Lexicon for E_0 [Fig. 22.3]
Noun = "stench | breeze | glitter | nothing | wumpus | pit | pits | gold | east",
Verb = "is | see | smell | shoot | fell | stinks | go | grab | carry | kill | turn | feel",
Adjective = "right | left | east | south | back | smelly",
Adverb = "here | there | nearby | ahead | right | left | east | south | back",
Pronoun = "me | you | I | it",
Name = "John | Mary | Boston | Aristotle",
Article = "the | a | an",
Preposition = "to | in | on | near",
Conjunction = "and | or | but",
Digit = "0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9",
That = "that"
))
E_ = Grammar('E_', # Trivial Grammar and lexicon for testing
Rules(
S = 'NP VP',
NP = 'Art N | Pronoun',
VP = 'V NP'),
Lexicon(
Art = 'the | a',
N = 'man | woman | table | shoelace | saw',
Pronoun = 'I | you | it',
V = 'saw | liked | feel'
))
def generate_random(grammar=E_, s='S'):
"""Replace each token in s by a random entry in grammar (recursively).
This is useful for testing a grammar, e.g. generate_random(E_)"""
import random
def rewrite(tokens, into):
for token in tokens:
if token in grammar.rules:
rewrite(random.choice(grammar.rules[token]), into)
elif token in grammar.lexicon:
into.append(random.choice(grammar.lexicon[token]))
else:
into.append(token)
return into
return ' '.join(rewrite(s.split(), []))
#______________________________________________________________________________
# Chart Parsing
class Chart:
"""Class for parsing sentences using a chart data structure. [Fig 22.7]
>>> chart = Chart(E0);
>>> len(chart.parses('the stench is in 2 2'))
1
"""
def __init__(self, grammar, trace=False):
"""A datastructure for parsing a string; and methods to do the parse.
self.chart[i] holds the edges that end just before the i'th word.
Edges are 5-element lists of [start, end, lhs, [found], [expects]]."""
update(self, grammar=grammar, trace=trace)
def parses(self, words, S='S'):
"""Return a list of parses; words can be a list or string."""
if isinstance(words, str):
words = words.split()
self.parse(words, S)
# Return all the parses that span the whole input
return [[i, j, S, found, []]
for (i, j, lhs, found, expects) in self.chart[len(words)]
if lhs == S and expects == []]
def parse(self, words, S='S'):
"""Parse a list of words; according to the grammar.
Leave results in the chart."""
self.chart = [[] for i in range(len(words)+1)]
self.add_edge([0, 0, 'S_', [], [S]])
for i in range(len(words)):
self.scanner(i, words[i])
return self.chart
def add_edge(self, edge):
"Add edge to chart, and see if it extends or predicts another edge."
start, end, lhs, found, expects = edge
if edge not in self.chart[end]:
self.chart[end].append(edge)
if self.trace:
print '%10s: added %s' % (caller(2), edge)
if not expects:
self.extender(edge)
else:
self.predictor(edge)
def scanner(self, j, word):
"For each edge expecting a word of this category here, extend the edge."
for (i, j, A, alpha, Bb) in self.chart[j]:
if Bb and self.grammar.isa(word, Bb[0]):
self.add_edge([i, j+1, A, alpha + [(Bb[0], word)], Bb[1:]])
def predictor(self, (i, j, A, alpha, Bb)):
"Add to chart any rules for B that could help extend this edge."
B = Bb[0]
if B in self.grammar.rules:
for rhs in self.grammar.rewrites_for(B):
self.add_edge([j, j, B, [], rhs])
def extender(self, edge):
"See what edges can be extended by this edge."
(j, k, B, _, _) = edge
for (i, j, A, alpha, B1b) in self.chart[j]:
if B1b and B == B1b[0]:
self.add_edge([i, k, A, alpha + [edge], B1b[1:]])
#### TODO:
#### 1. Parsing with augmentations -- requires unification, etc.
#### 2. Sequitor

@ -0,0 +1,23 @@
>>> chart = Chart(E0)
>>> chart.parses('the wumpus that is smelly is near 2 2')
[[0, 9, 'S', [[0, 5, 'NP', [[0, 2, 'NP', [('Article', 'the'), ('Noun', 'wumpus')], []], [2, 5, 'RelClause', [('That', 'that'), [3, 5, 'VP', [[3, 4, 'VP', [('Verb', 'is')], []], ('Adjective', 'smelly')], []]], []]], []], [5, 9, 'VP', [[5, 6, 'VP', [('Verb', 'is')], []], [6, 9, 'PP', [('Preposition', 'near'), [7, 9, 'NP', [('Digit', '2'), ('Digit', '2')], []]], []]], []]], []]]
### There is a built-in trace facility (compare [Fig. 22.9])
>>> Chart(E_, trace=True).parses('I feel it')
parse: added [0, 0, 'S_', [], ['S']]
predictor: added [0, 0, 'S', [], ['NP', 'VP']]
predictor: added [0, 0, 'NP', [], ['Art', 'N']]
predictor: added [0, 0, 'NP', [], ['Pronoun']]
scanner: added [0, 1, 'NP', [('Pronoun', 'I')], []]
extender: added [0, 1, 'S', [[0, 1, 'NP', [('Pronoun', 'I')], []]], ['VP']]
predictor: added [1, 1, 'VP', [], ['V', 'NP']]
scanner: added [1, 2, 'VP', [('V', 'feel')], ['NP']]
predictor: added [2, 2, 'NP', [], ['Art', 'N']]
predictor: added [2, 2, 'NP', [], ['Pronoun']]
scanner: added [2, 3, 'NP', [('Pronoun', 'it')], []]
extender: added [1, 3, 'VP', [('V', 'feel'), [2, 3, 'NP', [('Pronoun', 'it')], []]], []]
extender: added [0, 3, 'S', [[0, 1, 'NP', [('Pronoun', 'I')], []], [1, 3, 'VP', [('V', 'feel'), [2, 3, 'NP', [('Pronoun', 'it')], []]], []]], []]
extender: added [0, 3, 'S_', [[0, 3, 'S', [[0, 1, 'NP', [('Pronoun', 'I')], []], [1, 3, 'VP', [('V', 'feel'), [2, 3, 'NP', [('Pronoun', 'it')], []]], []]], []]], []]
[[0, 3, 'S', [[0, 1, 'NP', [('Pronoun', 'I')], []], [1, 3, 'VP', [('V', 'feel'), [2, 3, 'NP', [('Pronoun', 'it')], []]], []]], []]]

@ -0,0 +1,7 @@
"""Planning (Chapters 11-12)
"""
from __future__ import generators
from utils import *
import agents
import math, random, sys, time, bisect, string

@ -0,0 +1,171 @@
"""Probability models. (Chapter 13-15)
"""
from utils import *
from logic import extend
import agents
import bisect, random
#______________________________________________________________________________
class DTAgent(agents.Agent):
"A decision-theoretic agent. [Fig. 13.1]"
def __init__(self, belief_state):
agents.Agent.__init__(self)
def program(percept):
belief_state.observe(action, percept)
program.action = argmax(belief_state.actions(),
belief_state.expected_outcome_utility)
return program.action
program.action = None
self.program = program
#______________________________________________________________________________
class ProbDist:
"""A discrete probability distribution. You name the random variable
in the constructor, then assign and query probability of values.
>>> P = ProbDist('Flip'); P['H'], P['T'] = 0.5, 0.5; P['H']
0.5
"""
def __init__(self, varname='?'):
update(self, prob={}, varname=varname, values=[])
def __getitem__(self, val):
"Given a value, return P(value)."
return self.prob[val]
def __setitem__(self, val, p):
"Set P(val) = p"
if val not in self.values:
self.values.append(val)
self.prob[val] = p
def normalize(self):
"Make sure the probabilities of all values sum to 1."
total = sum(self.prob.values())
if not (1.0-epsilon < total < 1.0+epsilon):
for val in self.prob:
self.prob[val] /= total
return self
epsilon = 0.001
class JointProbDist(ProbDist):
"""A discrete probability distribute over a set of variables.
>>> P = JointProbDist(['X', 'Y']); P[1, 1] = 0.25
>>> P[1, 1]
0.25
"""
def __init__(self, variables):
update(self, prob={}, variables=variables, vals=DefaultDict([]))
def __getitem__(self, values):
"Given a tuple or dict of values, return P(values)."
if isinstance(values, dict):
values = tuple([values[var] for var in self.variables])
return self.prob[values]
def __setitem__(self, values, p):
"""Set P(values) = p. Values can be a tuple or a dict; it must
have a value for each of the variables in the joint. Also keep track
of the values we have seen so far for each variable."""
if isinstance(values, dict):
values = [values[var] for var in self.variables]
self.prob[values] = p
for var,val in zip(self.variables, values):
if val not in self.vals[var]:
self.vals[var].append(val)
def values(self, var):
"Return the set of possible values for a variable."
return self.vals[var]
def __repr__(self):
return "P(%s)" % self.variables
#______________________________________________________________________________
def enumerate_joint_ask(X, e, P):
"""Return a probability distribution over the values of the variable X,
given the {var:val} observations e, in the JointProbDist P.
Works for Boolean variables only. [Fig. 13.4]"""
Q = ProbDist(X) ## A probability distribution for X, initially empty
Y = [v for v in P.variables if v != X and v not in e]
for xi in P.values(X):
Q[xi] = enumerate_joint(Y, extend(e, X, xi), P)
return Q.normalize()
def enumerate_joint(vars, values, P):
"As in Fig 13.4, except x and e are already incorporated in values."
if not vars:
return P[values]
Y = vars[0]; rest = vars[1:]
return sum([enumerate_joint(rest, extend(values, Y, y), P)
for y in P.values(Y)])
#______________________________________________________________________________
class BayesNet:
def __init__(self, nodes=[]):
update(self, nodes=[], vars=[])
for node in nodes:
self.add(node)
def add(self, node):
self.nodes.append(node)
self.vars.append(node.variable)
def observe(self, var, val):
self.evidence[var] = val
class BayesNode:
def __init__(self, variable, parents, cpt):
if isinstance(parents, str): parents = parents.split()
update(self, variable=variable, parents=parents, cpt=cpt)
node = BayesNode
T, F = True, False
burglary = BayesNet([
node('Burglary', '', .001),
node('Earthquake', '', .002),
node('Alarm', 'Burglary Earthquake', {
(T, T):.95,
(T, F):.94,
(F, T):.29,
(F, F):.001}),
node('JohnCalls', 'Alarm', {T:.90, F:.05}),
node('MaryCalls', 'Alarm', {T:.70, F:.01})
])
#______________________________________________________________________________
def elimination_ask(X, e, bn):
"[Fig. 14.10]"
factors = []
for var in reverse(bn.vars):
factors.append(Factor(var, e))
if is_hidden(var, X, e):
factors = sum_out(var, factors)
return pointwise_product(factors).normalize()
def pointwise_product(factors):
pass
def sum_out(var, factors):
pass
#______________________________________________________________________________
def prior_sample(bn):
x = {}
for xi in bn.vars:
x[xi.var] = xi.sample([x])
#______________________________________________________________________________

@ -0,0 +1,32 @@
## We can build up a probability distribution like this (p. 469):
>>> P = ProbDist()
>>> P['sunny'] = 0.7
>>> P['rain'] = 0.2
>>> P['cloudy'] = 0.08
>>> P['snow'] = 0.02
## and query it like this:
>>> P['rain']
0.20000000000000001
## A Joint Probability Distribution is dealt with like this (p. 475):
>>> P = JointProbDist(['Toothache', 'Cavity', 'Catch'])
>>> T, F = True, False
>>> P[T, T, T] = 0.108; P[T, T, F] = 0.012; P[F, T, T] = 0.072; P[F, T, F] = 0.008
>>> P[T, F, T] = 0.016; P[T, F, F] = 0.064; P[F, F, T] = 0.144; P[F, F, F] = 0.576
>>> P[T, T, T]
0.108
## Ask for P(Cavity|Toothache=T)
>>> PC = enumerate_joint_ask('Cavity', {'Toothache': T}, P)
>>> PC.prob
{False: 0.39999999999999997, True: 0.59999999999999998}
>>> 0.6-epsilon < PC[T] < 0.6+epsilon
True
>>> 0.4-epsilon < PC[F] < 0.4+epsilon
True

@ -0,0 +1,15 @@
"""Reinforcement Learning (Chapter 21)
"""
from utils import *
import agents
class PassiveADPAgent(agents.Agent):
"""Passive (non-learning) agent that uses adaptive dynamic programming
on a given MDP and policy. [Fig. 21.2]"""
NotImplementedError
class PassiveTDAgent(agents.Agent):
"""Passive (non-learning) agent that uses temporal differences to learn
utility estimates. [Fig. 21.4]"""
NotImplementedError

@ -0,0 +1,68 @@
>>> ab = GraphProblem('A', 'B', romania)
>>> breadth_first_tree_search(ab).state
'B'
>>> breadth_first_graph_search(ab).state
'B'
>>> depth_first_graph_search(ab).state
'B'
>>> iterative_deepening_search(ab).state
'B'
>>> depth_limited_search(ab).state
'B'
>>> astar_search(ab).state
'B'
>>> [node.state for node in astar_search(ab).path()]
['B', 'P', 'R', 'S', 'A']
### demo
>>> compare_graph_searchers()
Searcher Romania(A,B) Romania(O, N) Australia
breadth_first_tree_search < 21/ 22/ 59/B> <1158/1159/3288/N> < 7/ 8/ 22/WA>
breadth_first_graph_search < 10/ 19/ 26/B> < 19/ 45/ 45/N> < 5/ 8/ 16/WA>
depth_first_graph_search < 9/ 15/ 23/B> < 16/ 27/ 39/N> < 4/ 7/ 13/WA>
iterative_deepening_search < 11/ 33/ 31/B> < 656/1815/1812/N> < 3/ 11/ 11/WA>
depth_limited_search < 54/ 65/ 185/B> < 387/1012/1125/N> < 50/ 54/ 200/WA>
astar_search < 3/ 4/ 9/B> < 8/ 10/ 22/N> < 2/ 3/ 6/WA>
>>> board = list('SARTELNID')
>>> print_boggle(board)
S A R
T E L
N I D
>>> f = BoggleFinder(board)
>>> len(f)
206
>>> ' '.join(f.words())
'LID LARES DEAL LIE DIETS LIN LINT TIL TIN RATED ERAS LATEN DEAR TIE LINE INTER STEAL LATED LAST TAR SAL DITES RALES SAE RETS TAE RAT RAS SAT IDLE TILDES LEAST IDEAS LITE SATED TINED LEST LIT RASE RENTS TINEA EDIT EDITS NITES ALES LATE LETS RELIT TINES LEI LAT ELINT LATI SENT TARED DINE STAR SEAR NEST LITAS TIED SEAT SERAL RATE DINT DEL DEN SEAL TIER TIES NET SALINE DILATE EAST TIDES LINTER NEAR LITS ELINTS DENI RASED SERA TILE NEAT DERAT IDLEST NIDE LIEN STARED LIER LIES SETA NITS TINE DITAS ALINE SATIN TAS ASTER LEAS TSAR LAR NITE RALE LAS REAL NITER ATE RES RATEL IDEA RET IDEAL REI RATS STALE DENT RED IDES ALIEN SET TEL SER TEN TEA TED SALE TALE STILE ARES SEA TILDE SEN SEL ALINES SEI LASE DINES ILEA LINES ELD TIDE RENT DIEL STELA TAEL STALED EARL LEA TILES TILER LED ETA TALI ALE LASED TELA LET IDLER REIN ALIT ITS NIDES DIN DIE DENTS STIED LINER LASTED RATINE ERA IDLES DIT RENTAL DINER SENTI TINEAL DEIL TEAR LITER LINTS TEAL DIES EAR EAT ARLES SATE STARE DITS DELI DENTAL REST DITE DENTIL DINTS DITA DIET LENT NETS NIL NIT SETAL LATS TARE ARE SATI'
>>> boggle_hill_climbing(list('ABCDEFGHI'))
30 1 ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'S', 'I']
35 2 ['A', 'B', 'S', 'D', 'E', 'F', 'G', 'S', 'I']
36 10 ['A', 'B', 'O', 'D', 'E', 'F', 'G', 'S', 'I']
41 11 ['A', 'B', 'O', 'D', 'O', 'F', 'G', 'S', 'I']
46 13 ['A', 'B', 'O', 'D', 'O', 'C', 'G', 'S', 'I']
48 14 ['A', 'M', 'O', 'D', 'O', 'C', 'G', 'S', 'I']
55 16 ['A', 'M', 'L', 'D', 'O', 'C', 'G', 'S', 'I']
60 17 ['A', 'M', 'L', 'D', 'O', 'C', 'G', 'S', 'E']
67 23 ['A', 'M', 'L', 'D', 'O', 'A', 'G', 'S', 'E']
70 29 ['A', 'B', 'L', 'D', 'O', 'A', 'G', 'S', 'E']
73 33 ['A', 'N', 'L', 'D', 'O', 'A', 'G', 'S', 'E']
80 55 ['A', 'N', 'L', 'D', 'O', 'A', 'G', 'S', 'W']
84 115 ['A', 'N', 'R', 'D', 'O', 'A', 'G', 'S', 'W']
100 116 ['A', 'N', 'R', 'D', 'O', 'A', 'G', 'S', 'T']
111 140 ['E', 'N', 'R', 'D', 'O', 'A', 'G', 'S', 'T']
123 169 ['E', 'P', 'R', 'D', 'O', 'A', 'G', 'S', 'T']
E P R
D O A
G S T
(['E', 'P', 'R', 'D', 'O', 'A', 'G', 'S', 'T'], 123)
>>> random_weighted_selection(range(10), 3, lambda x: x * x)
[8, 9, 6]

@ -0,0 +1,365 @@
"""Statistical Language Processing tools. (Chapter 23)
We define Unigram and Ngram text models, use them to generate random text,
and show the Viterbi algorithm for segmentatioon of letters into words.
Then we show a very simple Information Retrieval system, and an example
working on a tiny sample of Unix manual pages."""
from utils import *
from math import log, exp
import re, probability, string, search
class CountingProbDist(probability.ProbDist):
"""A probability distribution formed by observing and counting examples.
If P is an instance of this class and o
is an observed value, then there are 3 main operations:
p.add(o) increments the count for observation o by 1.
p.sample() returns a random element from the distribution.
p[o] returns the probability for o (as in a regular ProbDist)."""
def __init__(self, observations=[], default=0):
"""Create a distribution, and optionally add in some observations.
By default this is an unsmoothed distribution, but saying default=1,
for example, gives you add-one smoothing."""
update(self, dictionary=DefaultDict(default), needs_recompute=False,
table=[], n_obs=0)
for o in observations:
self.add(o)
def add(self, o):
"""Add an observation o to the distribution."""
self.dictionary[o] += 1
self.n_obs += 1
self.needs_recompute = True
def sample(self):
"""Return a random sample from the distribution."""
if self.needs_recompute: self._recompute()
if self.n_obs == 0:
return None
i = bisect.bisect_left(self.table, (1 + random.randrange(self.n_obs),))
(count, o) = self.table[i]
return o
def __getitem__(self, item):
"""Return an estimate of the probability of item."""
if self.needs_recompute: self._recompute()
return self.dictionary[item] / self.n_obs
def __len__(self):
if self.needs_recompute: self._recompute()
return self.n_obs
def top(self, n):
"Return (count, obs) tuples for the n most frequent observations."
items = [(v, k) for (k, v) in self.dictionary.items()]
items.sort(); items.reverse()
return items[0:n]
def _recompute(self):
"""Recompute the total count n_obs and the table of entries."""
n_obs = 0
table = []
for (o, count) in self.dictionary.items():
n_obs += count
table.append((n_obs, o))
update(self, n_obs=float(n_obs), table=table, needs_recompute=False)
#______________________________________________________________________________
class UnigramTextModel(CountingProbDist):
"""This is a discrete probability distribution over words, so you
can add, sample, or get P[word], just like with CountingProbDist. You can
also generate a random text n words long with P.samples(n)"""
def samples(self, n):
"Return a string of n words, random according to the model."
return ' '.join([self.sample() for i in range(n)])
class NgramTextModel(CountingProbDist):
"""This is a discrete probability distribution over n-tuples of words.
You can add, sample or get P[(word1, ..., wordn)]. The method P.samples(n)
builds up an n-word sequence; P.add_text and P.add_sequence add data."""
def __init__(self, n, observation_sequence=[]):
## In addition to the dictionary of n-tuples, cond_prob is a
## mapping from (w1, ..., wn-1) to P(wn | w1, ... wn-1)
CountingProbDist.__init__(self)
self.n = n
self.cond_prob = DefaultDict(CountingProbDist())
self.add_sequence(observation_sequence)
## sample, __len__, __getitem__ inherited from CountingProbDist
## Note they deal with tuples, not strings, as inputs
def add(self, ngram):
"""Count 1 for P[(w1, ..., wn)] and for P(wn | (w1, ..., wn-1)"""
CountingProbDist.add(self, ngram)
self.cond_prob[ngram[:-1]].add(ngram[-1])
def add_sequence(self, words):
"""Add each of the tuple words[i:i+n], using a sliding window.
Prefix some copies of the empty word, '', to make the start work."""
n = self.n
words = ['',] * (n-1) + words
for i in range(len(words)-n):
self.add(tuple(words[i:i+n]))
def samples(self, nwords):
"""Build up a random sample of text n words long, using the"""
n = self.n
nminus1gram = ('',) * (n-1)
output = []
while len(output) < nwords:
wn = self.cond_prob[nminus1gram].sample()
if wn:
output.append(wn)
nminus1gram = nminus1gram[1:] + (wn,)
else: ## Cannot continue, so restart.
nminus1gram = ('',) * (n-1)
return ' '.join(output)
#______________________________________________________________________________
def viterbi_segment(text, P):
"""Find the best segmentation of the string of characters, given the
UnigramTextModel P."""
# best[i] = best probability for text[0:i]
# words[i] = best word ending at position i
n = len(text)
words = [''] + list(text)
best = [1.0] + [0.0] * n
## Fill in the vectors best, words via dynamic programming
for i in range(n+1):
for j in range(0, i):
w = text[j:i]
if P[w] * best[i - len(w)] >= best[i]:
best[i] = P[w] * best[i - len(w)]
words[i] = w
## Now recover the sequence of best words
sequence = []; i = len(words)-1
while i > 0:
sequence[0:0] = [words[i]]
i = i - len(words[i])
## Return sequence of best words and overall probability
return sequence, best[-1]
#______________________________________________________________________________
class IRSystem:
"""A very simple Information Retrieval System, as discussed in Sect. 23.2.
The constructor s = IRSystem('the a') builds an empty system with two
stopwords. Next, index several documents with s.index_document(text, url).
Then ask queries with s.query('query words', n) to retrieve the top n
matching documents. Queries are literal words from the document,
except that stopwords are ignored, and there is one special syntax:
The query "learn: man cat", for example, runs "man cat" and indexes it."""
def __init__(self, stopwords='the a of'):
"""Create an IR System. Optionally specify stopwords."""
## index is a map of {word: {docid: count}}, where docid is an int,
## indicating the index into the documents list.
update(self, index=DefaultDict(DefaultDict(0)),
stopwords=set(words(stopwords)), documents=[])
def index_collection(self, filenames):
"Index a whole collection of files."
for filename in filenames:
self.index_document(open(filename).read(), filename)
def index_document(self, text, url):
"Index the text of a document."
## For now, use first line for title
title = text[:text.index('\n')].strip()
docwords = words(text)
docid = len(self.documents)
self.documents.append(Document(title, url, len(docwords)))
for word in docwords:
if word not in self.stopwords:
self.index[word][docid] += 1
def query(self, query_text, n=10):
"""Return a list of n (score, docid) pairs for the best matches.
Also handle the special syntax for 'learn: command'."""
if query_text.startswith("learn:"):
doctext = os.popen(query_text[len("learn:"):], 'r').read()
self.index_document(doctext, query_text)
return []
qwords = [w for w in words(query_text) if w not in self.stopwords]
shortest = argmin(qwords, lambda w: len(self.index[w]))
docs = self.index[shortest]
results = [(sum([self.score(w, d) for w in qwords]), d) for d in docs]
results.sort(); results.reverse()
return results[:n]
def score(self, word, docid):
"Compute a score for this word on this docid."
## There are many options; here we take a very simple approach
return (math.log(1 + self.index[word][docid])
/ math.log(1 + self.documents[docid].nwords))
def present(self, results):
"Present the results as a list."
for (score, d) in results:
doc = self.documents[d]
print "%5.2f|%25s | %s" % (100 * score, doc.url, doc.title[:45])
def present_results(self, query_text, n=10):
"Get results for the query and present them."
self.present(self.query(query_text, n))
class UnixConsultant(IRSystem):
"""A trivial IR system over a small collection of Unix man pages."""
def __init__(self):
IRSystem.__init__(self, stopwords="how do i the a of")
import os
mandir = '../data/man/'
man_files = [mandir + f for f in os.listdir(mandir)]
self.index_collection(man_files)
class Document:
"""Metadata for a document: title and url; maybe add others later."""
def __init__(self, title, url, nwords):
update(self, title=title, url=url, nwords=nwords)
def words(text, reg=re.compile('[a-z0-9]+')):
"""Return a list of the words in text, ignoring punctuation and
converting everything to lowercase (to canonicalize).
>>> words("``EGAD!'' Edgar cried.")
['egad', 'edgar', 'cried']
"""
return reg.findall(text.lower())
def canonicalize(text):
"""Return a canonical text: only lowercase letters and blanks.
>>> canonicalize("``EGAD!'' Edgar cried.")
'egad edgar cried'
"""
return ' '.join(words(text))
#______________________________________________________________________________
## Example application (not in book): decode a cipher.
## A cipher is a code that substitutes one character for another.
## A shift cipher is a rotation of the letters in the alphabet,
## such as the famous rot13, which maps A to N, B to M, etc.
#### Encoding
def shift_encode(plaintext, n):
"""Encode text with a shift cipher that moves each letter up by n letters.
>>> shift_encode('abc z', 1)
'bcd a'
"""
return encode(plaintext, alphabet[n:] + alphabet[:n])
def rot13(plaintext):
"""Encode text by rotating letters by 13 spaces in the alphabet.
>>> rot13('hello')
'uryyb'
>>> rot13(rot13('hello'))
'hello'
"""
return shift_encode(plaintext, 13)
def encode(plaintext, code):
"Encodes text, using a code which is a permutation of the alphabet."
from string import maketrans
trans = maketrans(alphabet + alphabet.upper(), code + code.upper())
return plaintext.translate(trans)
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def bigrams(text):
"""Return a list of pairs in text (a sequence of letters or words).
>>> bigrams('this')
['th', 'hi', 'is']
>>> bigrams(['this', 'is', 'a', 'test'])
[['this', 'is'], ['is', 'a'], ['a', 'test']]
"""
return [text[i:i+2] for i in range(len(text) - 1)]
#### Decoding a Shift (or Caesar) Cipher
class ShiftDecoder:
"""There are only 26 possible encodings, so we can try all of them,
and return the one with the highest probability, according to a
bigram probability distribution."""
def __init__(self, training_text):
training_text = canonicalize(training_text)
self.P2 = CountingProbDist(bigrams(training_text), default=1)
def score(self, plaintext):
"Return a score for text based on how common letters pairs are."
s = 1.0
for bi in bigrams(plaintext):
s = s * self.P2[bi]
return s
def decode(self, ciphertext):
"Return the shift decoding of text with the best score."
return argmax(all_shifts(ciphertext), self.score)
def all_shifts(text):
"Return a list of all 26 possible encodings of text by a shift cipher."
return [shift_encode(text, n) for n in range(len(alphabet))]
#### Decoding a General Permutation Cipher
class PermutationDecoder:
"""This is a much harder problem than the shift decoder. There are 26!
permutations, so we can't try them all. Instead we have to search.
We want to search well, but there are many things to consider:
Unigram probabilities (E is the most common letter); Bigram probabilities
(TH is the most common bigram); word probabilities (I and A are the most
common one-letter words, etc.); etc.
We could represent a search state as a permutation of the 26 letters,
and alter the solution through hill climbing. With an initial guess
based on unigram probabilities, this would probably fair well. However,
I chose instead to have an incremental representation. A state is
represented as a letter-to-letter map; for example {'z': 'e'} to
represent that 'z' will be translated to 'e'
"""
def __init__(self, training_text, ciphertext=None):
self.Pwords = UnigramTextModel(words(training_text))
self.P1 = UnigramTextModel(training_text) # By letter
self.P2 = NgramTextModel(2, training_text) # By letter pair
if ciphertext:
return self.decode(ciphertext)
def decode(self, ciphertext):
"Search for a decoding of the ciphertext."
self.ciphertext = ciphertext
problem = PermutationDecoderProblem(decoder=self)
return search.best_first_tree_search(problem, self.score)
def score(self, ciphertext, code):
"""Score is product of word scores, unigram scores, and bigram scores.
This can get very small, so we use logs and exp."""
text = decode(ciphertext, code)
logP = (sum([log(self.Pwords[word]) for word in words(text)]) +
sum([log(self.P1[c]) for c in text]) +
sum([log(self.P2[b]) for b in bigrams(text)]))
return exp(logP)
class PermutationDecoderProblem(search.Problem):
def __init__(self, initial=None, goal=None, decoder=None):
self.initial = initial or {}
self.decoder = decoder
def successors(self, state):
## Find the best
p, plainchar = max([(self.decoder.P1[c], c)
for c in alphabet if c not in state])
succs = [extend(state, plainchar, cipherchar)] #????
def goal_test(self, state):
"We're done when we get all 26 letters assigned."
return len(state) >= 26
#______________________________________________________________________________

@ -0,0 +1,122 @@
## Create a Unigram text model from the words in the book "Flatland".
>>> flatland = DataFile("flat11.txt").read()
>>> wordseq = words(flatland)
>>> P = UnigramTextModel(wordseq)
## Now do segmentation, using the text model as a prior.
>>> s, p = viterbi_segment('itiseasytoreadwordswithoutspaces', P)
>>> s
['it', 'is', 'easy', 'to', 'read', 'words', 'without', 'spaces']
>>> 1e-30 < p < 1e-20
True
>>> s, p = viterbi_segment('wheninthecourseofhumaneventsitbecomesnecessary', P)
>>> s
['when', 'in', 'the', 'course', 'of', 'human', 'events', 'it', 'becomes', 'necessary']
## Test the decoding system
>>> shift_encode("This is a secret message.", 17)
'Kyzj zj r jvtivk dvjjrxv.'
>>> ring = ShiftDecoder(flatland)
>>> ring.decode('Kyzj zj r jvtivk dvjjrxv.')
'This is a secret message.'
>>> ring.decode(rot13('Hello, world!'))
'Hello, world!'
## CountingProbDist
## Add a thousand samples of a roll of a die to D.
>>> D = CountingProbDist()
>>> for i in range(10000):
... D.add(random.choice('123456'))
>>> ps = [D[n] for n in '123456']
>>> 1./7. <= min(ps) <= max(ps) <= 1./5.
True
## demo
## Compare 1-, 2-, and 3-gram word models of the same text.
>>> flatland = DataFile("flat11.txt").read()
>>> wordseq = words(flatland)
>>> P1 = UnigramTextModel(wordseq)
>>> P2 = NgramTextModel(2, wordseq)
>>> P3 = NgramTextModel(3, wordseq)
## Generate random text from the N-gram models
>>> P1.samples(20)
'you thought known but were insides of see in depend by us dodecahedrons just but i words are instead degrees'
>>> P2.samples(20)
'flatland well then can anything else more into the total destruction and circles teach others confine women must be added'
>>> P3.samples(20)
'flatland by edwin a abbott 1884 to the wake of a certificate from nature herself proving the equal sided triangle'
## The most frequent entries in each model
>>> P1.top(10)
[(2081, 'the'), (1479, 'of'), (1021, 'and'), (1008, 'to'), (850, 'a'), (722, 'i'), (640, 'in'), (478, 'that'), (399, 'is'), (348, 'you')]
>>> P2.top(10)
[(368, ('of', 'the')), (152, ('to', 'the')), (152, ('in', 'the')), (86, ('of', 'a')), (80, ('it', 'is')), (71, ('by', 'the')), (68, ('for', 'the')), (68, ('and', 'the')), (62, ('on', 'the')), (60, ('to', 'be'))]
>>> P3.top(10)
[(30, ('a', 'straight', 'line')), (19, ('of', 'three', 'dimensions')), (16, ('the', 'sense', 'of')), (13, ('by', 'the', 'sense')), (13, ('as', 'well', 'as')), (12, ('of', 'the', 'circles')), (12, ('of', 'sight', 'recognition')), (11, ('the', 'number', 'of')), (11, ('that', 'i', 'had')), (11, ('so', 'as', 'to'))]
## Probabilities of some common n-grams
>>> P1['the']
0.061139348356200607
>>> P2[('of', 'the')]
0.010812081325655188
>>> P3[('', '', 'but')]
0.0
>>> P3[('so', 'as', 'to')]
0.00032318721353860618
## Distributions given the previous n-1 words
>>> P2.cond_prob['went',].dictionary
>>> P3.cond_prob['in', 'order'].dictionary
{'to': 6}
## Build and test an IR System
>>> uc = UnixConsultant()
>>> uc.present_results("how do I remove a file")
76.83| ../data/man/rm.txt | RM(1) FSF RM(1)
67.83| ../data/man/tar.txt | TAR(1) TAR(1)
67.79| ../data/man/cp.txt | CP(1) FSF CP(1)
66.58| ../data/man/zip.txt | ZIP(1L) ZIP(1L)
64.58| ../data/man/gzip.txt | GZIP(1) GZIP(1)
63.74| ../data/man/pine.txt | pine(1) pine(1)
62.95| ../data/man/shred.txt | SHRED(1) FSF SHRED(1)
57.46| ../data/man/pico.txt | pico(1) pico(1)
43.38| ../data/man/login.txt | LOGIN(1) Linux Programmer's Manual
41.93| ../data/man/ln.txt | LN(1) FSF LN(1)
>>> uc.present_results("how do I delete a file")
75.47| ../data/man/diff.txt | DIFF(1) GNU Tools DIFF(1)
69.12| ../data/man/pine.txt | pine(1) pine(1)
63.56| ../data/man/tar.txt | TAR(1) TAR(1)
60.63| ../data/man/zip.txt | ZIP(1L) ZIP(1L)
57.46| ../data/man/pico.txt | pico(1) pico(1)
51.28| ../data/man/shred.txt | SHRED(1) FSF SHRED(1)
26.72| ../data/man/tr.txt | TR(1) User Commands TR(1)
>>> uc.present_results("email")
18.39| ../data/man/pine.txt | pine(1) pine(1)
12.01| ../data/man/info.txt | INFO(1) FSF INFO(1)
9.89| ../data/man/pico.txt | pico(1) pico(1)
8.73| ../data/man/grep.txt | GREP(1) GREP(1)
8.07| ../data/man/zip.txt | ZIP(1L) ZIP(1L)
>>> uc.present_results("word counts for files")
112.38| ../data/man/grep.txt | GREP(1) GREP(1)
101.84| ../data/man/wc.txt | WC(1) User Commands WC(1)
82.46| ../data/man/find.txt | FIND(1L) FIND(1L)
74.64| ../data/man/du.txt | DU(1) FSF DU(1)
>>> uc.present_results("learn: date")
>>> uc.present_results("2003")
14.58| ../data/man/pine.txt | pine(1) pine(1)
11.62| ../data/man/jar.txt | FASTJAR(1) GNU FASTJAR(1)

@ -0,0 +1,169 @@
>>> d = DefaultDict(0)
>>> d['x'] += 1
>>> d['x']
1
>>> d = DefaultDict([])
>>> d['x'] += [1]
>>> d['y'] += [2]
>>> d['x']
[1]
>>> s = Struct(a=1, b=2)
>>> s.a
1
>>> s.a = 3
>>> s
Struct(a=3, b=2)
>>> def is_even(x):
... return x % 2 == 0
>>> sorted([1, 2, -3])
[-3, 1, 2]
>>> sorted(range(10), key=is_even)
[1, 3, 5, 7, 9, 0, 2, 4, 6, 8]
>>> sorted(range(10), lambda x,y: y-x)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> removeall(4, [])
[]
>>> removeall('s', 'This is a test. Was a test.')
'Thi i a tet. Wa a tet.'
>>> removeall('s', 'Something')
'Something'
>>> removeall('s', '')
''
>>> list(reversed([]))
[]
>>> count_if(is_even, [1, 2, 3, 4])
2
>>> count_if(is_even, [])
0
>>> argmax([1], lambda x: x*x)
1
>>> argmin([1], lambda x: x*x)
1
# Test of memoize with slots in structures
>>> countries = [Struct(name='united states'), Struct(name='canada')]
# Pretend that 'gnp' was some big hairy operation:
>>> def gnp(country):
... print 'calculating gnp ...'
... return len(country.name) * 1e10
>>> gnp = memoize(gnp, '_gnp')
>>> map(gnp, countries)
calculating gnp ...
calculating gnp ...
[130000000000.0, 60000000000.0]
>>> countries
[Struct(_gnp=130000000000.0, name='united states'), Struct(_gnp=60000000000.0, name='canada')]
# This time we avoid re-doing the calculation
>>> map(gnp, countries)
[130000000000.0, 60000000000.0]
# Test Queues:
>>> nums = [1, 8, 2, 7, 5, 6, -99, 99, 4, 3, 0]
>>> def qtest(q):
... return [q.extend(nums), [q.pop() for i in range(len(q))]][1]
>>> qtest(Stack())
[0, 3, 4, 99, -99, 6, 5, 7, 2, 8, 1]
>>> qtest(FIFOQueue())
[1, 8, 2, 7, 5, 6, -99, 99, 4, 3, 0]
>>> qtest(PriorityQueue(min))
[-99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 99]
>>> qtest(PriorityQueue(max))
[99, 8, 7, 6, 5, 4, 3, 2, 1, 0, -99]
>>> qtest(PriorityQueue(min, abs))
[0, 1, 2, 3, 4, 5, 6, 7, 8, -99, 99]
>>> qtest(PriorityQueue(max, abs))
[99, -99, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> vals = [100, 110, 160, 200, 160, 110, 200, 200, 220]
>>> histogram(vals)
[(100, 1), (110, 2), (160, 2), (200, 3), (220, 1)]
>>> histogram(vals, 1)
[(200, 3), (110, 2), (160, 2), (220, 1), (100, 1)]
>>> histogram(vals, 1, lambda v: round(v, -2))
[(200.0, 6), (100.0, 3)]
>>> log2(1.0)
0.0
>>> def fib(n):
... return (n<=1 and 1) or (fib(n-1) + fib(n-2))
>>> fib(9)
55
# Now we make it faster:
>>> fib = memoize(fib)
>>> fib(9)
55
>>> q = Stack()
>>> q.append(1)
>>> q.append(2)
>>> q.pop(), q.pop()
(2, 1)
>>> q = FIFOQueue()
>>> q.append(1)
>>> q.append(2)
>>> q.pop(), q.pop()
(1, 2)
>>> abc = set('abc')
>>> bcd = set('bcd')
>>> 'a' in abc
True
>>> 'a' in bcd
False
>>> list(abc.intersection(bcd))
['c', 'b']
>>> list(abc.union(bcd))
['a', 'c', 'b', 'd']
## From "What's new in Python 2.4", but I added calls to sl
>>> def sl(x):
... return sorted(list(x))
>>> a = set('abracadabra') # form a set from a string
>>> 'z' in a # fast membership testing
False
>>> sl(a) # unique letters in a
['a', 'b', 'c', 'd', 'r']
>>> b = set('alacazam') # form a second set
>>> sl(a - b) # letters in a but not in b
['b', 'd', 'r']
>>> sl(a | b) # letters in either a or b
['a', 'b', 'c', 'd', 'l', 'm', 'r', 'z']
>>> sl(a & b) # letters in both a and b
['a', 'c']
>>> sl(a ^ b) # letters in a or b but not both
['b', 'd', 'l', 'm', 'r', 'z']
>>> a.add('z') # add a new element
>>> a.update('wxy') # add multiple new elements
>>> sl(a)
['a', 'b', 'c', 'd', 'r', 'w', 'x', 'y', 'z']
>>> a.remove('x') # take one element out
>>> sl(a)
['a', 'b', 'c', 'd', 'r', 'w', 'y', 'z']
Loading…
Cancel
Save