diff --git a/.travis.yml b/.travis.yml index 294287f9b..dc4ed0d05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,31 @@ -language: - - python +language: python python: - - "3.4" + - 3.4 + - 3.5 + - 3.6 + - 3.7 before_install: - git submodule update --remote install: - - pip install six - pip install flake8 - pip install ipython - - pip install matplotlib - - pip install networkx - - pip install ipywidgets - - pip install Pillow - - pip install pytest-cov - pip install ipythonblocks + - pip install ipywidgets - pip install keras + - pip install matplotlib + - pip install networkx - pip install numpy - - pip install tensorflow - pip install opencv-python + - pip install Pillow + - pip install pytest-cov + - pip install qpsolvers + - pip install quadprog + - pip install six - pip install sortedcontainers - + - pip install tensorflow script: - py.test --cov=./ diff --git a/agents.py b/agents.py index 6c01aa5b4..bfe8f074c 100644 --- a/agents.py +++ b/agents.py @@ -1,4 +1,5 @@ -"""Implement Agents and Environments (Chapters 1-2). +""" +Implement Agents and Environments. (Chapters 1-2) The class hierarchies are as follows: @@ -23,16 +24,14 @@ EnvToolbar ## contains buttons for controlling EnvGUI EnvCanvas ## Canvas to display the environment of an EnvGUI - """ -# TO DO: +# TODO # Implement grabbing correctly. # When an object is grabbed, does it still have a location? # What if it is released? # What if the grabbed or the grabber is deleted? # What if the grabber moves? -# # Speed control in GUI does not have any effect -- fix it. from utils import distance_squared, turn_heading @@ -90,8 +89,7 @@ def __init__(self, program=None): self.holding = [] self.performance = 0 if program is None or not isinstance(program, collections.Callable): - print("Can't find a valid program for {}, falling back to default.".format( - self.__class__.__name__)) + print("Can't find a valid program for {}, falling back to default.".format(self.__class__.__name__)) def program(percept): return eval(input('Percept={}; action? '.format(percept))) @@ -122,10 +120,13 @@ def new_program(percept): def TableDrivenAgentProgram(table): - """This agent selects an action based on the percept sequence. + """ + [Figure 2.7] + This agent selects an action based on the percept sequence. It is practical only for tiny domains. To customize it, provide as table a dictionary of all - {percept_sequence:action} pairs. [Figure 2.7]""" + {percept_sequence:action} pairs. + """ percepts = [] def program(percept): @@ -154,7 +155,10 @@ def RandomAgentProgram(actions): def SimpleReflexAgentProgram(rules, interpret_input): - """This agent takes action based solely on the percept. [Figure 2.10]""" + """ + [Figure 2.10] + This agent takes action based solely on the percept. + """ def program(percept): state = interpret_input(percept) @@ -166,7 +170,10 @@ def program(percept): def ModelBasedReflexAgentProgram(rules, update_state, model): - """This agent takes action based on the percept and state. [Figure 2.12]""" + """ + [Figure 2.12] + This agent takes action based on the percept and state. + """ def program(percept): program.state = update_state(program.state, program.action, percept, model) @@ -219,7 +226,9 @@ def TableDrivenVacuumAgent(): def ReflexVacuumAgent(): - """A reflex agent for the two-state vacuum environment. [Figure 2.8] + """ + [Figure 2.8] + A reflex agent for the two-state vacuum environment. >>> agent = ReflexVacuumAgent() >>> environment = TrivialVacuumEnvironment() >>> environment.add_thing(agent) @@ -436,13 +445,13 @@ def move_forward(self, from_location): """ x, y = from_location if self.direction == self.R: - return (x + 1, y) + return x + 1, y elif self.direction == self.L: - return (x - 1, y) + return x - 1, y elif self.direction == self.U: - return (x, y - 1) + return x, y - 1 elif self.direction == self.D: - return (x, y + 1) + return x, y + 1 class XYEnvironment(Environment): @@ -497,7 +506,7 @@ def execute_action(self, agent, action): agent.holding.pop() def default_location(self, thing): - return (random.choice(self.width), random.choice(self.height)) + return random.choice(self.width), random.choice(self.height) def move_to(self, thing, destination): """Move a thing to a new location. Returns True on success or False if there is an Obstacle. @@ -525,7 +534,7 @@ def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False) def is_inbounds(self, location): """Checks to make sure that the location is inbounds (within walls if we have walls)""" x, y = location - return not (x < self.x_start or x >= self.x_end or y < self.y_start or y >= self.y_end) + return not (x < self.x_start or x > self.x_end or y < self.y_start or y > self.y_end) def random_location_inbounds(self, exclude=None): """Returns a random location that is inbounds (within walls if we have walls)""" @@ -723,7 +732,7 @@ def percept(self, agent): status = ('Dirty' if self.some_things_at( agent.location, Dirt) else 'Clean') bump = ('Bump' if agent.bump else 'None') - return (status, bump) + return status, bump def execute_action(self, agent, action): agent.bump = False @@ -752,12 +761,11 @@ def __init__(self): loc_B: random.choice(['Clean', 'Dirty'])} def thing_classes(self): - return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent, - TableDrivenVacuumAgent, ModelBasedVacuumAgent] + return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent, TableDrivenVacuumAgent, ModelBasedVacuumAgent] def percept(self, agent): """Returns the agent's location, and the location status (Dirty/Clean).""" - return (agent.location, self.status[agent.location]) + return agent.location, self.status[agent.location] def execute_action(self, agent, action): """Change agent's location and/or location's status; track performance. @@ -992,8 +1000,8 @@ def is_done(self): else: print("Death by {} [-1000].".format(explorer[0].killed_by)) else: - print("Explorer climbed out {}.".format("with Gold [+1000]!" - if Gold() not in self.things else "without Gold [+0]")) + print("Explorer climbed out {}." + .format("with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) return True # TODO: Arrow needs to be implemented diff --git a/agents4e.py b/agents4e.py index fab36a46c..f1deace6a 100644 --- a/agents4e.py +++ b/agents4e.py @@ -1,4 +1,5 @@ -"""Implement Agents and Environments (Chapters 1-2). +""" +Implement Agents and Environments. (Chapters 1-2) The class hierarchies are as follows: @@ -23,16 +24,14 @@ EnvToolbar ## contains buttons for controlling EnvGUI EnvCanvas ## Canvas to display the environment of an EnvGUI - """ -# TO DO: +# TODO # Implement grabbing correctly. # When an object is grabbed, does it still have a location? # What if it is released? # What if the grabbed or the grabber is deleted? # What if the grabber moves? -# # Speed control in GUI does not have any effect -- fix it. from utils4e import distance_squared, turn_heading @@ -90,8 +89,7 @@ def __init__(self, program=None): self.holding = [] self.performance = 0 if program is None or not isinstance(program, collections.Callable): - print("Can't find a valid program for {}, falling back to default.".format( - self.__class__.__name__)) + print("Can't find a valid program for {}, falling back to default.".format(self.__class__.__name__)) def program(percept): return eval(input('Percept={}; action? '.format(percept))) @@ -122,10 +120,13 @@ def new_program(percept): def TableDrivenAgentProgram(table): - """This agent selects an action based on the percept sequence. + """ + [Figure 2.7] + This agent selects an action based on the percept sequence. It is practical only for tiny domains. To customize it, provide as table a dictionary of all - {percept_sequence:action} pairs. [Figure 2.7]""" + {percept_sequence:action} pairs. + """ percepts = [] def program(percept): @@ -154,7 +155,10 @@ def RandomAgentProgram(actions): def SimpleReflexAgentProgram(rules, interpret_input): - """This agent takes action based solely on the percept. [Figure 2.10]""" + """ + [Figure 2.10] + This agent takes action based solely on the percept. + """ def program(percept): state = interpret_input(percept) @@ -166,7 +170,10 @@ def program(percept): def ModelBasedReflexAgentProgram(rules, update_state, trainsition_model, sensor_model): - """This agent takes action based on the percept and state. [Figure 2.12]""" + """ + [Figure 2.12] + This agent takes action based on the percept and state. + """ def program(percept): program.state = update_state(program.state, program.action, percept, trainsition_model, sensor_model) @@ -219,7 +226,9 @@ def TableDrivenVacuumAgent(): def ReflexVacuumAgent(): - """A reflex agent for the two-state vacuum environment. [Figure 2.8] + """ + [Figure 2.8] + A reflex agent for the two-state vacuum environment. >>> agent = ReflexVacuumAgent() >>> environment = TrivialVacuumEnvironment() >>> environment.add_thing(agent) @@ -333,8 +342,7 @@ def run(self, steps=1000): def list_things_at(self, location, tclass=Thing): """Return all things exactly at a given location.""" - return [thing for thing in self.things - if thing.location == location and isinstance(thing, tclass)] + return [thing for thing in self.things if thing.location == location and isinstance(thing, tclass)] def some_things_at(self, location, tclass=Thing): """Return true if at least one of the things at location @@ -437,13 +445,13 @@ def move_forward(self, from_location): """ x, y = from_location if self.direction == self.R: - return (x + 1, y) + return x + 1, y elif self.direction == self.L: - return (x - 1, y) + return x - 1, y elif self.direction == self.U: - return (x, y - 1) + return x, y - 1 elif self.direction == self.D: - return (x, y + 1) + return x, y + 1 class XYEnvironment(Environment): @@ -498,7 +506,7 @@ def execute_action(self, agent, action): agent.holding.pop() def default_location(self, thing): - return (random.choice(self.width), random.choice(self.height)) + return random.choice(self.width), random.choice(self.height) def move_to(self, thing, destination): """Move a thing to a new location. Returns True on success or False if there is an Obstacle. @@ -724,7 +732,7 @@ def percept(self, agent): status = ('Dirty' if self.some_things_at( agent.location, Dirt) else 'Clean') bump = ('Bump' if agent.bump else 'None') - return (status, bump) + return status, bump def execute_action(self, agent, action): agent.bump = False @@ -753,12 +761,11 @@ def __init__(self): loc_B: random.choice(['Clean', 'Dirty'])} def thing_classes(self): - return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent, - TableDrivenVacuumAgent, ModelBasedVacuumAgent] + return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent, TableDrivenVacuumAgent, ModelBasedVacuumAgent] def percept(self, agent): """Returns the agent's location, and the location status (Dirty/Clean).""" - return (agent.location, self.status[agent.location]) + return agent.location, self.status[agent.location] def execute_action(self, agent, action): """Change agent's location and/or location's status; track performance. @@ -994,8 +1001,7 @@ def is_done(self): print("Death by {} [-1000].".format(explorer[0].killed_by)) else: print("Explorer climbed out {}." - .format( - "with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) + .format("with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) return True # TODO: Arrow needs to be implemented diff --git a/csp.py b/csp.py index ce3754914..9cfdafdef 100644 --- a/csp.py +++ b/csp.py @@ -402,10 +402,8 @@ def mac(csp, var, value, assignment, removals, constraint_propagation=AC3b): # The search, proper -def backtracking_search(csp, - select_unassigned_variable=first_unassigned_variable, - order_domain_values=unordered_domain_values, - inference=no_inference): +def backtracking_search(csp, select_unassigned_variable=first_unassigned_variable, + order_domain_values=unordered_domain_values, inference=no_inference): """[Figure 6.5]""" def backtrack(assignment): @@ -634,12 +632,13 @@ def queen_constraint(A, a, B, b): class NQueensCSP(CSP): - """Make a CSP for the nQueens problem for search with min_conflicts. + """ + Make a CSP for the nQueens problem for search with min_conflicts. Suitable for large n, it uses only data structures of size O(n). Think of placing queens one per column, from left to right. That means position (x, y) represents (var, val) in the CSP. The main structures are three arrays to count queens that could conflict: - rows[i] Number of queens in the ith row (i.e val == i) + rows[i] Number of queens in the ith row (i.e. val == i) downs[i] Number of queens in the \ diagonal such that their (x, y) coordinates sum to i ups[i] Number of queens in the / diagonal @@ -741,7 +740,8 @@ def flatten(seqs): class Sudoku(CSP): - """A Sudoku problem. + """ + A Sudoku problem. The box grid is a 3x3 array of boxes, each a 3x3 array of cells. Each cell holds a digit in 1..9. In each box, all digits are different; the same for each row and column as a 9x9 grid. @@ -895,15 +895,16 @@ def solve_zebra(algorithm=min_conflicts, **args): # n-ary Constraint Satisfaction Problem class NaryCSP: - """A nary-CSP consists of - * domains, a dictionary that maps each variable to its domain - * constraints, a list of constraints - * variables, a set of variables - * var_to_const, a variable to set of constraints dictionary + """ + A nary-CSP consists of: + domains : a dictionary that maps each variable to its domain + constraints : a list of constraints + variables : a set of variables + var_to_const: a variable to set of constraints dictionary """ def __init__(self, domains, constraints): - """domains is a variable:domain dictionary + """Domains is a variable:domain dictionary constraints is a list of constraints """ self.variables = set(domains) @@ -915,11 +916,11 @@ def __init__(self, domains, constraints): self.var_to_const[var].add(con) def __str__(self): - """string representation of CSP""" + """String representation of CSP""" return str(self.domains) def display(self, assignment=None): - """more detailed string representation of CSP""" + """More detailed string representation of CSP""" if assignment is None: assignment = {} print(assignment) @@ -935,10 +936,11 @@ def consistent(self, assignment): class Constraint: - """A Constraint consists of - * scope: a tuple of variables - * condition: a function that can applied to a tuple of values - for the variables + """ + A Constraint consists of: + scope : a tuple of variables + condition: a function that can applied to a tuple of values + for the variables. """ def __init__(self, scope, condition): @@ -956,12 +958,12 @@ def holds(self, assignment): return self.condition(*tuple(assignment[v] for v in self.scope)) -def all_diff(*values): +def all_diff_constraint(*values): """Returns True if all values are different, False otherwise""" return len(values) is len(set(values)) -def is_word(words): +def is_word_constraint(words): """Returns True if the letters concatenated form a word in words, False otherwise""" def isw(*letters): @@ -970,7 +972,7 @@ def isw(*letters): return isw -def meet_at(p1, p2): +def meet_at_constraint(p1, p2): """Returns a function that is True when the words meet at the positions (p1, p2), False otherwise""" def meets(w1, w2): @@ -980,12 +982,12 @@ def meets(w1, w2): return meets -def adjacent(x, y): +def adjacent_constraint(x, y): """Returns True if x and y are adjacent numbers, False otherwise""" return abs(x - y) == 1 -def sum_(n): +def sum_constraint(n): """Returns a function that is True when the the sum of all values is n, False otherwise""" def sumv(*values): @@ -995,7 +997,7 @@ def sumv(*values): return sumv -def is_(val): +def is_constraint(val): """Returns a function that is True when x is equal to val, False otherwise""" def isv(x): @@ -1005,7 +1007,7 @@ def isv(x): return isv -def ne_(val): +def ne_constraint(val): """Returns a function that is True when x is not equal to val, False otherwise""" def nev(x): @@ -1033,9 +1035,10 @@ def __init__(self, csp): self.csp = csp def GAC(self, orig_domains=None, to_do=None, arc_heuristic=sat_up): - """Makes this CSP arc-consistent using Generalized Arc Consistency - orig_domains is the original domains - to_do is a set of (variable,constraint) pairs + """ + Makes this CSP arc-consistent using Generalized Arc Consistency + orig_domains: is the original domains + to_do : is a set of (variable,constraint) pairs returns the reduced domains (an arc-consistent variable:domain dictionary) """ if orig_domains is None: @@ -1137,7 +1140,7 @@ def domain_splitting(self, domains=None, to_do=None, arc_heuristic=sat_up): def partition_domain(dom): - """partitions domain dom into two""" + """Partitions domain dom into two""" split = len(dom) // 2 dom1 = set(list(dom)[:split]) dom2 = dom - dom1 @@ -1157,7 +1160,7 @@ def __init__(self, csp, arc_heuristic=sat_up): super().__init__(self.domains) def goal_test(self, node): - """node is a goal if all domains have 1 element""" + """Node is a goal if all domains have 1 element""" return all(len(node[var]) == 1 for var in node) def actions(self, state): @@ -1178,12 +1181,12 @@ def result(self, state, action): def ac_solver(csp, arc_heuristic=sat_up): - """arc consistency (domain splitting)""" + """Arc consistency (domain splitting interface)""" return ACSolver(csp).domain_splitting(arc_heuristic=arc_heuristic) def ac_search_solver(csp, arc_heuristic=sat_up): - """arc consistency (search interface)""" + """Arc consistency (search interface)""" from search import depth_first_tree_search solution = None try: @@ -1203,11 +1206,11 @@ def ac_search_solver(csp, arc_heuristic=sat_up): 'two_down': {'ginger', 'search', 'symbol', 'syntax'}, 'three_across': {'book', 'buys', 'hold', 'land', 'year'}, 'four_across': {'ant', 'big', 'bus', 'car', 'has'}}, - [Constraint(('one_across', 'one_down'), meet_at(0, 0)), - Constraint(('one_across', 'two_down'), meet_at(2, 0)), - Constraint(('three_across', 'two_down'), meet_at(2, 2)), - Constraint(('three_across', 'one_down'), meet_at(0, 2)), - Constraint(('four_across', 'two_down'), meet_at(0, 4))]) + [Constraint(('one_across', 'one_down'), meet_at_constraint(0, 0)), + Constraint(('one_across', 'two_down'), meet_at_constraint(2, 0)), + Constraint(('three_across', 'two_down'), meet_at_constraint(2, 2)), + Constraint(('three_across', 'one_down'), meet_at_constraint(0, 2)), + Constraint(('four_across', 'two_down'), meet_at_constraint(0, 4))]) crossword1 = [['_', '_', '_', '*', '*'], ['_', '*', '_', '*', '*'], @@ -1234,10 +1237,10 @@ def __init__(self, puzzle, words): scope.append(var) else: if len(scope) > 1: - constraints.append(Constraint(tuple(scope), is_word(words))) + constraints.append(Constraint(tuple(scope), is_word_constraint(words))) scope.clear() if len(scope) > 1: - constraints.append(Constraint(tuple(scope), is_word(words))) + constraints.append(Constraint(tuple(scope), is_word_constraint(words))) puzzle_t = list(map(list, zip(*puzzle))) for i, line in enumerate(puzzle_t): scope = [] @@ -1246,10 +1249,10 @@ def __init__(self, puzzle, words): scope.append("p" + str(i) + str(j)) else: if len(scope) > 1: - constraints.append(Constraint(tuple(scope), is_word(words))) + constraints.append(Constraint(tuple(scope), is_word_constraint(words))) scope.clear() if len(scope) > 1: - constraints.append(Constraint(tuple(scope), is_word(words))) + constraints.append(Constraint(tuple(scope), is_word_constraint(words))) super().__init__(domains, constraints) self.puzzle = puzzle @@ -1355,8 +1358,8 @@ def __init__(self, puzzle): if len(var2) == 1: var2 = "0" + var2 x.append("X" + var1 + var2) - constraints.append(Constraint(x, sum_(element[0]))) - constraints.append(Constraint(x, all_diff)) + constraints.append(Constraint(x, sum_constraint(element[0]))) + constraints.append(Constraint(x, all_diff_constraint)) # right - line if element[1] != '': x = [] @@ -1370,8 +1373,8 @@ def __init__(self, puzzle): if len(var2) == 1: var2 = "0" + var2 x.append("X" + var1 + var2) - constraints.append(Constraint(x, sum_(element[1]))) - constraints.append(Constraint(x, all_diff)) + constraints.append(Constraint(x, sum_constraint(element[1]))) + constraints.append(Constraint(x, all_diff_constraint)) super().__init__(domains, constraints) self.puzzle = puzzle @@ -1411,7 +1414,7 @@ def display(self, assignment=None): two_two_four = NaryCSP({'T': set(range(1, 10)), 'F': set(range(1, 10)), 'W': set(range(0, 10)), 'O': set(range(0, 10)), 'U': set(range(0, 10)), 'R': set(range(0, 10)), 'C1': set(range(0, 2)), 'C2': set(range(0, 2)), 'C3': set(range(0, 2))}, - [Constraint(('T', 'F', 'W', 'O', 'U', 'R'), all_diff), + [Constraint(('T', 'F', 'W', 'O', 'U', 'R'), all_diff_constraint), Constraint(('O', 'R', 'C1'), lambda o, r, c1: o + o == r + 10 * c1), Constraint(('W', 'U', 'C1', 'C2'), lambda w, u, c1, c2: c1 + w + w == u + 10 * c2), Constraint(('T', 'O', 'C2', 'C3'), lambda t, o, c2, c3: c2 + t + t == o + 10 * c3), @@ -1423,7 +1426,7 @@ def display(self, assignment=None): 'O': set(range(0, 10)), 'R': set(range(0, 10)), 'Y': set(range(0, 10)), 'C1': set(range(0, 2)), 'C2': set(range(0, 2)), 'C3': set(range(0, 2)), 'C4': set(range(0, 2))}, - [Constraint(('S', 'E', 'N', 'D', 'M', 'O', 'R', 'Y'), all_diff), + [Constraint(('S', 'E', 'N', 'D', 'M', 'O', 'R', 'Y'), all_diff_constraint), Constraint(('D', 'E', 'Y', 'C1'), lambda d, e, y, c1: d + e == y + 10 * c1), Constraint(('N', 'R', 'E', 'C1', 'C2'), lambda n, r, e, c1, c2: c1 + n + r == e + 10 * c2), Constraint(('E', 'O', 'N', 'C2', 'C3'), lambda e, o, n, c2, c3: c2 + e + o == n + 10 * c3), diff --git a/deep_learning4e.py b/deep_learning4e.py index d92a5f3ee..4f8f52ad9 100644 --- a/deep_learning4e.py +++ b/deep_learning4e.py @@ -4,13 +4,11 @@ import random import statistics -from keras import optimizers -from keras.layers import Dense, SimpleRNN -from keras.layers.embeddings import Embedding -from keras.models import Sequential +from keras import Sequential, optimizers +from keras.layers import Embedding, SimpleRNN, Dense from keras.preprocessing import sequence -from utils4e import (sigmoid, dot_product, softmax1D, conv1D, GaussianKernel, element_wise_product, vector_add, +from utils4e import (sigmoid, dot_product, softmax1D, conv1D, gaussian_kernel, element_wise_product, vector_add, random_weights, scalar_vector_product, matrix_multiplication, map_vector, mse_loss) @@ -123,7 +121,7 @@ def __init__(self, size=3, kernel_size=3): super(ConvLayer1D, self).__init__(size) # init convolution kernel as gaussian kernel for node in self.nodes: - node.weights = GaussianKernel(kernel_size) + node.weights = gaussian_kernel(kernel_size) def forward(self, features): # each node in layer takes a channel in the features. @@ -213,8 +211,8 @@ def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01, batch_size=1, return net -def adam_optimizer(dataset, net, loss, epochs=1000, rho=(0.9, 0.999), delta=1 / 10 ** 8, - l_rate=0.001, batch_size=1, verbose=None): +def adam(dataset, net, loss, epochs=1000, rho=(0.9, 0.999), delta=1 / 10 ** 8, + l_rate=0.001, batch_size=1, verbose=None): """ [Figure 19.6] Adam optimizer to update the learnable parameters of a network. diff --git a/games.py b/games.py index cdc24af09..efc65cc67 100644 --- a/games.py +++ b/games.py @@ -1,20 +1,21 @@ -"""Games, or Adversarial Search (Chapter 5)""" +"""Games or Adversarial Search. (Chapter 5)""" -from collections import namedtuple -import random -import itertools import copy -from utils import argmax, vector_add, inf +import itertools +import random +from collections import namedtuple + +from utils import vector_add, inf GameState = namedtuple('GameState', 'to_move, utility, board, moves') StochasticGameState = namedtuple('StochasticGameState', 'to_move, utility, board, moves, chance') # ______________________________________________________________________________ -# Minimax Search +# MinMax Search -def minimax_decision(state, game): +def minmax_decision(state, game): """Given a state in a game, calculate the best move by searching forward all the way to the terminal states. [Figure 5.3]""" @@ -36,17 +37,19 @@ def min_value(state): v = min(v, max_value(game.result(state, a))) return v - # Body of minimax_decision: - return argmax(game.actions(state), - key=lambda a: min_value(game.result(state, a))) + # Body of minmax_decision: + return max(game.actions(state), key=lambda a: min_value(game.result(state, a))) # ______________________________________________________________________________ -def expectiminimax(state, game): - """Return the best move for a player after dice are thrown. The game tree - includes chance nodes along with min and max nodes. [Figure 5.11]""" +def expect_minmax(state, game): + """ + [Figure 5.11] + Return the best move for a player after dice are thrown. The game tree + includes chance nodes along with min and max nodes. + """ player = game.to_move(state) def max_value(state): @@ -77,18 +80,17 @@ def chance_node(state, action): sum_chances += util * game.probability(chance) return sum_chances / num_chances - # Body of expectiminimax: - return argmax(game.actions(state), - key=lambda a: chance_node(state, a), default=None) + # Body of expect_minmax: + return max(game.actions(state), key=lambda a: chance_node(state, a), default=None) -def alphabeta_search(state, game): +def alpha_beta_search(state, game): """Search game to determine best action; use alpha-beta pruning. As in [Figure 5.7], this version searches all the way to the leaves.""" player = game.to_move(state) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(state, alpha, beta): if game.terminal_test(state): return game.utility(state, player) @@ -111,7 +113,7 @@ def min_value(state, alpha, beta): beta = min(beta, v) return v - # Body of alphabeta_search: + # Body of alpha_beta_search: best_score = -inf beta = inf best_action = None @@ -123,20 +125,19 @@ def min_value(state, alpha, beta): return best_action -def alphabeta_cutoff_search(state, game, d=4, cutoff_test=None, eval_fn=None): +def alpha_beta_cutoff_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) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(state, alpha, beta, depth): if cutoff_test(state, depth): return eval_fn(state) v = -inf for a in game.actions(state): - v = max(v, min_value(game.result(state, a), - alpha, beta, depth + 1)) + v = max(v, min_value(game.result(state, a), alpha, beta, depth + 1)) if v >= beta: return v alpha = max(alpha, v) @@ -147,18 +148,15 @@ def min_value(state, alpha, beta, depth): return eval_fn(state) v = inf for a in game.actions(state): - v = min(v, max_value(game.result(state, a), - alpha, beta, depth + 1)) + v = min(v, max_value(game.result(state, a), alpha, beta, depth + 1)) if v <= alpha: return v beta = min(beta, v) return v - # Body of alphabeta_cutoff_search starts here: + # Body of alpha_beta_cutoff_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))) + 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)) best_score = -inf beta = inf @@ -198,12 +196,12 @@ def random_player(game, state): return random.choice(game.actions(state)) if game.actions(state) else None -def alphabeta_player(game, state): - return alphabeta_search(state, game) +def alpha_beta_player(game, state): + return alpha_beta_search(state, game) -def expectiminimax_player(game, state): - return expectiminimax(state, game) +def expect_minmax_player(game, state): + return expect_minmax(state, game) # ______________________________________________________________________________ @@ -273,7 +271,7 @@ def outcome(self, state, chance): raise NotImplementedError def probability(self, chance): - """Return the probability of occurence of a chance.""" + """Return the probability of occurrence of a chance.""" raise NotImplementedError def play_game(self, *players): @@ -576,5 +574,5 @@ def outcome(self, state, chance): moves=state.moves, chance=dice) def probability(self, chance): - """Return the probability of occurence of a dice roll.""" + """Return the probability of occurrence of a dice roll.""" return 1 / 36 if chance[0] == chance[1] else 1 / 18 diff --git a/games4e.py b/games4e.py index 6bc97c2bb..3fb000862 100644 --- a/games4e.py +++ b/games4e.py @@ -1,20 +1,21 @@ -"""Games, or Adversarial Search (Chapter 5)""" +"""Games or Adversarial Search. (Chapter 5)""" -from collections import namedtuple -import random -import itertools import copy -from utils4e import argmax, vector_add, MCT_Node, ucb, inf +import itertools +import random +from collections import namedtuple + +from utils4e import vector_add, MCT_Node, ucb, inf GameState = namedtuple('GameState', 'to_move, utility, board, moves') StochasticGameState = namedtuple('StochasticGameState', 'to_move, utility, board, moves, chance') # ______________________________________________________________________________ -# Minimax Search +# MinMax Search -def minimax_decision(state, game): +def minmax_decision(state, game): """Given a state in a game, calculate the best move by searching forward all the way to the terminal states. [Figure 5.3]""" @@ -36,17 +37,19 @@ def min_value(state): v = min(v, max_value(game.result(state, a))) return v - # Body of minimax_decision: - return argmax(game.actions(state), - key=lambda a: min_value(game.result(state, a))) + # Body of minmax_decision: + return max(game.actions(state), key=lambda a: min_value(game.result(state, a))) # ______________________________________________________________________________ -def expectiminimax(state, game): - """Return the best move for a player after dice are thrown. The game tree - includes chance nodes along with min and max nodes. [Figure 5.11]""" +def expect_minmax(state, game): + """ + [Figure 5.11] + Return the best move for a player after dice are thrown. The game tree + includes chance nodes along with min and max nodes. + """ player = game.to_move(state) def max_value(state): @@ -77,18 +80,17 @@ def chance_node(state, action): sum_chances += util * game.probability(chance) return sum_chances / num_chances - # Body of expectiminimax: - return argmax(game.actions(state), - key=lambda a: chance_node(state, a), default=None) + # Body of expect_min_max: + return max(game.actions(state), key=lambda a: chance_node(state, a), default=None) -def alphabeta_search(state, game): +def alpha_beta_search(state, game): """Search game to determine best action; use alpha-beta pruning. As in [Figure 5.7], this version searches all the way to the leaves.""" player = game.to_move(state) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(state, alpha, beta): if game.terminal_test(state): return game.utility(state, player) @@ -111,7 +113,7 @@ def min_value(state, alpha, beta): beta = min(beta, v) return v - # Body of alphabeta_search: + # Body of alpha_beta_search: best_score = -inf beta = inf best_action = None @@ -123,20 +125,19 @@ def min_value(state, alpha, beta): return best_action -def alphabeta_cutoff_search(state, game, d=4, cutoff_test=None, eval_fn=None): +def alpha_beta_cutoff_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) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(state, alpha, beta, depth): if cutoff_test(state, depth): return eval_fn(state) v = -inf for a in game.actions(state): - v = max(v, min_value(game.result(state, a), - alpha, beta, depth + 1)) + v = max(v, min_value(game.result(state, a), alpha, beta, depth + 1)) if v >= beta: return v alpha = max(alpha, v) @@ -147,18 +148,15 @@ def min_value(state, alpha, beta, depth): return eval_fn(state) v = inf for a in game.actions(state): - v = min(v, max_value(game.result(state, a), - alpha, beta, depth + 1)) + v = min(v, max_value(game.result(state, a), alpha, beta, depth + 1)) if v <= alpha: return v beta = min(beta, v) return v - # Body of alphabeta_cutoff_search starts here: + # Body of alpha_beta_cutoff_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))) + 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)) best_score = -inf beta = inf @@ -249,12 +247,12 @@ def random_player(game, state): return random.choice(game.actions(state)) if game.actions(state) else None -def alphabeta_player(game, state): - return alphabeta_search(state, game) +def alpha_beta_player(game, state): + return alpha_beta_search(state, game) -def expectiminimax_player(game, state): - return expectiminimax(state, game) +def expect_min_max_player(game, state): + return expect_minmax(state, game) def mcts_player(game, state): @@ -328,7 +326,7 @@ def outcome(self, state, chance): raise NotImplementedError def probability(self, chance): - """Return the probability of occurence of a chance.""" + """Return the probability of occurrence of a chance.""" raise NotImplementedError def play_game(self, *players): @@ -631,5 +629,5 @@ def outcome(self, state, chance): moves=state.moves, chance=dice) def probability(self, chance): - """Return the probability of occurence of a dice roll.""" + """Return the probability of occurrence of a dice roll.""" return 1 / 36 if chance[0] == chance[1] else 1 / 18 diff --git a/gui/tic-tac-toe.py b/gui/tic-tac-toe.py index 5c3bdb497..4f51425c1 100644 --- a/gui/tic-tac-toe.py +++ b/gui/tic-tac-toe.py @@ -2,7 +2,7 @@ import sys import os.path sys.path.append(os.path.join(os.path.dirname(__file__), '..')) -from games import minimax_decision, alphabeta_player, random_player, TicTacToe +from games import minmax_decision, alpha_beta_player, random_player, TicTacToe # "gen_state" can be used to generate a game state to apply the algorithm from tests.test_games import gen_state @@ -95,9 +95,9 @@ def on_click(button): if "Random" in choice: a, b = random_player(ttt, state) elif "Pro" in choice: - a, b = minimax_decision(state, ttt) + a, b = minmax_decision(state, ttt) else: - a, b = alphabeta_player(ttt, state) + a, b = alpha_beta_player(ttt, state) except (ValueError, IndexError, TypeError) as e: disable_game() result.set("It's a draw :|") diff --git a/knowledge.py b/knowledge.py index 2c00f22aa..945f27d3d 100644 --- a/knowledge.py +++ b/knowledge.py @@ -2,7 +2,7 @@ from random import shuffle from math import log -from utils import powerset +from utils import power_set from collections import defaultdict from itertools import combinations, product from logic import (FolKB, constant_symbols, predicate_symbols, standardize_variables, @@ -67,7 +67,7 @@ def generalizations(examples_so_far, h): hypotheses = [] # Delete disjunctions - disj_powerset = powerset(range(len(h))) + disj_powerset = power_set(range(len(h))) for disjs in disj_powerset: h2 = h.copy() for d in reversed(list(disjs)): @@ -78,7 +78,7 @@ def generalizations(examples_so_far, h): # Delete AND operations in disjunctions for i, disj in enumerate(h): - a_powerset = powerset(disj.keys()) + a_powerset = power_set(disj.keys()) for attrs in a_powerset: h2 = h[i].copy() for a in attrs: @@ -106,7 +106,7 @@ def add_or(examples_so_far, h): e = examples_so_far[-1] attrs = {k: v for k, v in e.items() if k != 'GOAL'} - a_powerset = powerset(attrs.keys()) + a_powerset = power_set(attrs.keys()) for c in a_powerset: h2 = {} @@ -144,7 +144,7 @@ def version_space_update(V, e): def all_hypotheses(examples): """Build a list of all the possible hypotheses""" values = values_table(examples) - h_powerset = powerset(values.keys()) + h_powerset = power_set(values.keys()) hypotheses = [] for s in h_powerset: hypotheses.extend(build_attr_combinations(s, values)) @@ -203,7 +203,7 @@ def build_h_combinations(hypotheses): """Given a set of hypotheses, builds and returns all the combinations of the hypotheses.""" h = [] - h_powerset = powerset(range(len(hypotheses))) + h_powerset = power_set(range(len(hypotheses))) for s in h_powerset: t = [] @@ -249,7 +249,7 @@ class FOILContainer(FolKB): def __init__(self, clauses=None): self.const_syms = set() self.pred_syms = set() - FolKB.__init__(self, clauses) + super().__init__(clauses) def tell(self, sentence): if is_definite_clause(sentence): diff --git a/learning.py b/learning.py index 2d4bd4d4b..401729cb9 100644 --- a/learning.py +++ b/learning.py @@ -7,11 +7,14 @@ from collections import defaultdict from statistics import mean, stdev +import numpy as np +from qpsolvers import solve_qp + from probabilistic_learning import NaiveBayesLearner -from utils import (remove_all, unique, mode, argmax, argmax_random_tie, isclose, dot_product, vector_add, - scalar_vector_product, weighted_sample_with_replacement, num_or_str, normalize, clip, sigmoid, - print_table, open_data, sigmoid_derivative, probability, relu, relu_derivative, tanh, - tanh_derivative, leaky_relu_derivative, elu, elu_derivative, mean_boolean_error, random_weights) +from utils import (remove_all, unique, mode, argmax_random_tie, isclose, dot_product, vector_add, clip, sigmoid, + scalar_vector_product, weighted_sample_with_replacement, num_or_str, normalize, print_table, + open_data, sigmoid_derivative, probability, relu, relu_derivative, tanh, tanh_derivative, leaky_relu, + leaky_relu_derivative, elu, elu_derivative, mean_boolean_error, random_weights, linear_kernel, inf) class DataSet: @@ -195,7 +198,7 @@ def __repr__(self): 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. 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') @@ -271,7 +274,7 @@ def cross_validation_wrapper(learner, dataset, k=10, trials=1): # check for convergence provided err_val is not empty if errT and not isclose(errT[-1], errT, rel_tol=1e-6): best_size = 0 - min_val = math.inf + min_val = inf i = 0 while i < size: if errs[i] < min_val: @@ -287,7 +290,7 @@ def cross_validation(learner, dataset, size=None, 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. + Shuffle the examples first; if trials > 1, average over several shuffles. Returns Training error, Validation error """ k = k or len(dataset.examples) @@ -321,14 +324,13 @@ def leave_one_out(learner, dataset, size=None): return cross_validation(learner, dataset, size, len(dataset.examples)) -# TODO learning_curve needs to be fixed def learning_curve(learner, dataset, trials=10, sizes=None): if sizes is None: - sizes = list(range(2, len(dataset.examples) - 10, 2)) + sizes = list(range(2, len(dataset.examples) - trials, 2)) def score(learner, size): random.shuffle(dataset.examples) - return train_test_split(learner, dataset, 0, size) + return cross_validation(learner, dataset, size, trials) return [(size, mean([score(learner, size) for _ in range(trials)])) for size in sizes] @@ -370,7 +372,7 @@ def __call__(self, example): return self.default_child(example) def add(self, val, subtree): - """Add a branch. If self.attr = val, go to the given subtree.""" + """Add a branch. If self.attr = val, go to the given subtree.""" self.branches[val] = subtree def display(self, indent=0): @@ -446,8 +448,8 @@ def information_gain(attr, examples): def I(examples): return information_content([count(target, v, examples) for v in values[target]]) - N = len(examples) - remainder = sum((len(examples_i) / N) * I(examples_i) for (v, examples_i) in split_by(attr, examples)) + n = len(examples) + remainder = sum((len(examples_i) / n) * I(examples_i) for (v, examples_i) in split_by(attr, examples)) return I(examples) - remainder def split_by(attr, examples): @@ -692,8 +694,10 @@ def BackPropagationLearner(dataset, net, learning_rate, epochs, activation=sigmo delta[-1] = [tanh_derivative(o_nodes[i].value) * err[i] for i in range(o_units)] elif node.activation == elu: delta[-1] = [elu_derivative(o_nodes[i].value) * err[i] for i in range(o_units)] - else: + elif node.activation == leaky_relu: delta[-1] = [leaky_relu_derivative(o_nodes[i].value) * err[i] for i in range(o_units)] + else: + return ValueError("Activation function unknown.") # backward pass h_layers = n_layers - 2 @@ -717,9 +721,11 @@ def BackPropagationLearner(dataset, net, learning_rate, epochs, activation=sigmo elif activation == elu: delta[i] = [elu_derivative(layer[j].value) * dot_product(w[j], delta[i + 1]) for j in range(h_units)] - else: + elif activation == leaky_relu: delta[i] = [leaky_relu_derivative(layer[j].value) * dot_product(w[j], delta[i + 1]) for j in range(h_units)] + else: + return ValueError("Activation function unknown.") # update weights for i in range(1, n_layers): @@ -777,8 +783,7 @@ def network(input_units, hidden_layer_sizes, output_units, activation=sigmoid): """ layers_sizes = [input_units] + hidden_layer_sizes + [output_units] - net = [[NNUnit(activation) for _ in range(size)] - for size in layers_sizes] + net = [[NNUnit(activation) for _ in range(size)] for size in layers_sizes] n_layers = len(net) # make connection @@ -810,7 +815,137 @@ def init_examples(examples, idx_i, idx_t, o_units): def find_max_node(nodes): - return nodes.index(argmax(nodes, key=lambda node: node.value)) + return nodes.index(max(nodes, key=lambda node: node.value)) + + +class BinarySVM: + def __init__(self, kernel=linear_kernel, C=1.0): + self.kernel = kernel + self.C = C # hyper-parameter + self.eps = 1e-6 + self.n_sv = -1 + self.sv_x, self.sv_y, = np.zeros(0), np.zeros(0) + self.alphas = np.zeros(0) + self.w = None + self.b = 0.0 # intercept + + def fit(self, X, y): + """ + Trains the model by solving a quadratic programming problem. + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + """ + # In QP formulation (dual): m variables, 2m+1 constraints (1 equation, 2m inequations) + self.QP(X, y) + sv_indices = list(filter(lambda i: self.alphas[i] > self.eps, range(len(y)))) + self.sv_x, self.sv_y, self.alphas = X[sv_indices], y[sv_indices], self.alphas[sv_indices] + self.n_sv = len(sv_indices) + if self.kernel == linear_kernel: + self.w = np.dot(self.alphas * self.sv_y, self.sv_x) + # calculate b: average over all support vectors + sv_boundary = self.alphas < self.C - self.eps + self.b = np.mean(self.sv_y[sv_boundary] - np.dot(self.alphas * self.sv_y, + self.kernel(self.sv_x, self.sv_x[sv_boundary]))) + + def QP(self, X, y): + """ + Solves a quadratic programming problem. In QP formulation (dual): + m variables, 2m+1 constraints (1 equation, 2m inequations). + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + """ + # + m = len(y) # m = n_samples + K = self.kernel(X) # gram matrix + P = K * np.outer(y, y) + q = -np.ones(m) + G = np.vstack((-np.identity(m), np.identity(m))) + h = np.hstack((np.zeros(m), np.ones(m) * self.C)) + A = y.reshape((1, -1)) + b = np.zeros(1) + # make sure P is positive definite + P += np.eye(P.shape[0]).__mul__(1e-3) + self.alphas = solve_qp(P, q, G, h, A, b, sym_proj=True) + + def predict_score(self, x): + """ + Predicts the score for a given example. + """ + if self.w is None: + return np.dot(self.alphas * self.sv_y, self.kernel(self.sv_x, x)) + self.b + return np.dot(x, self.w) + self.b + + def predict(self, x): + """ + Predicts the class of a given example. + """ + return np.sign(self.predict_score(x)) + + +class MultiSVM: + def __init__(self, kernel=linear_kernel, decision_function='ovr', C=1.0): + self.kernel = kernel + self.decision_function = decision_function + self.C = C # hyper-parameter + self.n_class, self.classifiers = 0, [] + + def fit(self, X, y): + """ + Trains n_class or n_class * (n_class - 1) / 2 classifiers + according to the training method, ovr or ovo respectively. + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + :return: array of classifiers + """ + labels = np.unique(y) + self.n_class = len(labels) + if self.decision_function == 'ovr': # one-vs-rest method + for label in labels: + y1 = np.array(y) + y1[y1 != label] = -1.0 + y1[y1 == label] = 1.0 + clf = BinarySVM(self.kernel, self.C) + clf.fit(X, y1) + self.classifiers.append(copy.deepcopy(clf)) + elif self.decision_function == 'ovo': # use one-vs-one method + n_labels = len(labels) + for i in range(n_labels): + for j in range(i + 1, n_labels): + neg_id, pos_id = y == labels[i], y == labels[j] + x1, y1 = np.r_[X[neg_id], X[pos_id]], np.r_[y[neg_id], y[pos_id]] + y1[y1 == labels[i]] = -1.0 + y1[y1 == labels[j]] = 1.0 + clf = BinarySVM(self.kernel, self.C) + clf.fit(x1, y1) + self.classifiers.append(copy.deepcopy(clf)) + else: + return ValueError("Decision function must be either 'ovr' or 'ovo'.") + + def predict(self, x): + """ + Predicts the class of a given example according to the training method. + """ + n_samples = len(x) + if self.decision_function == 'ovr': # one-vs-rest method + assert len(self.classifiers) == self.n_class + score = np.zeros((n_samples, self.n_class)) + for i in range(self.n_class): + clf = self.classifiers[i] + score[:, i] = clf.predict_score(x) + return np.argmax(score, axis=1) + elif self.decision_function == 'ovo': # use one-vs-one method + assert len(self.classifiers) == self.n_class * (self.n_class - 1) / 2 + vote = np.zeros((n_samples, self.n_class)) + clf_id = 0 + for i in range(self.n_class): + for j in range(i + 1, self.n_class): + res = self.classifiers[clf_id].predict(x) + vote[res < 0, i] += 1.0 # negative sample: class i + vote[res > 0, j] += 1.0 # positive sample: class j + clf_id += 1 + return np.argmax(vote, axis=1) + else: + return ValueError("Decision function must be either 'ovr' or 'ovo'.") def EnsembleLearner(learners): @@ -831,16 +966,16 @@ def ada_boost(dataset, L, K): """[Figure 18.34]""" examples, target = dataset.examples, dataset.target - N = len(examples) - epsilon = 1 / (2 * N) - w = [1 / N] * N + n = len(examples) + eps = 1 / (2 * n) + w = [1 / n] * n h, z = [], [] for k in range(K): h_k = L(dataset, w) h.append(h_k) error = sum(weight for example, weight in zip(examples, w) if example[target] != h_k(example)) # avoid divide-by-0 from either 0% or 100% error rates - error = clip(error, epsilon, 1 - epsilon) + error = clip(error, eps, 1 - eps) for j, example in enumerate(examples): if example[target] == h_k(example): w[j] *= error / (1 - error) diff --git a/learning4e.py b/learning4e.py index e4a566667..bd3bcf50a 100644 --- a/learning4e.py +++ b/learning4e.py @@ -1,4 +1,4 @@ -"""Learning from examples (Chapters 18)""" +"""Learning from examples. (Chapters 18)""" import copy import heapq @@ -7,11 +7,14 @@ from collections import defaultdict from statistics import mean, stdev +import numpy as np +from qpsolvers import solve_qp + from probabilistic_learning import NaiveBayesLearner from utils import sigmoid, sigmoid_derivative -from utils4e import (remove_all, unique, mode, argmax_random_tie, isclose, dot_product, - weighted_sample_with_replacement, num_or_str, normalize, clip, print_table, open_data, probability, - random_weights, mean_boolean_error) +from utils4e import (remove_all, unique, mode, argmax_random_tie, isclose, dot_product, num_or_str, normalize, clip, + weighted_sample_with_replacement, print_table, open_data, probability, random_weights, + mean_boolean_error, linear_kernel, inf) class DataSet: @@ -195,7 +198,7 @@ def __repr__(self): 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. 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') @@ -270,7 +273,7 @@ def model_selection(learner, dataset, k=10, trials=1): # check for convergence provided err_val is not empty if err and not isclose(err[-1], err, rel_tol=1e-6): best_size = 0 - min_val = math.inf + min_val = inf i = 0 while i < size: if errs[i] < min_val: @@ -286,7 +289,7 @@ def cross_validation(learner, dataset, size=None, 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. + Shuffle the examples first; if trials > 1, average over several shuffles. Returns Training error """ k = k or len(dataset.examples) @@ -316,14 +319,13 @@ def leave_one_out(learner, dataset, size=None): return cross_validation(learner, dataset, size, len(dataset.examples)) -# TODO learning_curve needs to be fixed def learning_curve(learner, dataset, trials=10, sizes=None): if sizes is None: - sizes = list(range(2, len(dataset.examples) - 10, 2)) + sizes = list(range(2, len(dataset.examples) - trials, 2)) def score(learner, size): random.shuffle(dataset.examples) - return train_test_split(learner, dataset, 0, size) + return cross_validation(learner, dataset, size, trials) return [(size, mean([score(learner, size) for _ in range(trials)])) for size in sizes] @@ -365,7 +367,7 @@ def __call__(self, example): return self.default_child(example) def add(self, val, subtree): - """Add a branch. If self.attr = val, go to the given subtree.""" + """Add a branch. If self.attr = val, go to the given subtree.""" self.branches[val] = subtree def display(self, indent=0): @@ -441,8 +443,8 @@ def information_gain(attr, examples): def I(examples): return information_content([count(target, v, examples) for v in values[target]]) - N = len(examples) - remainder = sum((len(examples_i) / N) * I(examples_i) for (v, examples_i) in split_by(attr, examples)) + n = len(examples) + remainder = sum((len(examples_i) / n) * I(examples_i) for (v, examples_i) in split_by(attr, examples)) return I(examples) - remainder def split_by(attr, examples): @@ -590,6 +592,136 @@ def predict(example): return predict +class BinarySVM: + def __init__(self, kernel=linear_kernel, C=1.0): + self.kernel = kernel + self.C = C # hyper-parameter + self.eps = 1e-6 + self.n_sv = -1 + self.sv_x, self.sv_y, = np.zeros(0), np.zeros(0) + self.alphas = np.zeros(0) + self.w = None + self.b = 0.0 # intercept + + def fit(self, X, y): + """ + Trains the model by solving a quadratic programming problem. + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + """ + # In QP formulation (dual): m variables, 2m+1 constraints (1 equation, 2m inequations) + self.QP(X, y) + sv_indices = list(filter(lambda i: self.alphas[i] > self.eps, range(len(y)))) + self.sv_x, self.sv_y, self.alphas = X[sv_indices], y[sv_indices], self.alphas[sv_indices] + self.n_sv = len(sv_indices) + if self.kernel == linear_kernel: + self.w = np.dot(self.alphas * self.sv_y, self.sv_x) + # calculate b: average over all support vectors + sv_boundary = self.alphas < self.C - self.eps + self.b = np.mean(self.sv_y[sv_boundary] - np.dot(self.alphas * self.sv_y, + self.kernel(self.sv_x, self.sv_x[sv_boundary]))) + + def QP(self, X, y): + """ + Solves a quadratic programming problem. In QP formulation (dual): + m variables, 2m+1 constraints (1 equation, 2m inequations). + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + """ + # + m = len(y) # m = n_samples + K = self.kernel(X) # gram matrix + P = K * np.outer(y, y) + q = -np.ones(m) + G = np.vstack((-np.identity(m), np.identity(m))) + h = np.hstack((np.zeros(m), np.ones(m) * self.C)) + A = y.reshape((1, -1)) + b = np.zeros(1) + # make sure P is positive definite + P += np.eye(P.shape[0]).__mul__(1e-3) + self.alphas = solve_qp(P, q, G, h, A, b, sym_proj=True) + + def predict_score(self, x): + """ + Predicts the score for a given example. + """ + if self.w is None: + return np.dot(self.alphas * self.sv_y, self.kernel(self.sv_x, x)) + self.b + return np.dot(x, self.w) + self.b + + def predict(self, x): + """ + Predicts the class of a given example. + """ + return np.sign(self.predict_score(x)) + + +class MultiSVM: + def __init__(self, kernel=linear_kernel, decision_function='ovr', C=1.0): + self.kernel = kernel + self.decision_function = decision_function + self.C = C # hyper-parameter + self.n_class, self.classifiers = 0, [] + + def fit(self, X, y): + """ + Trains n_class or n_class * (n_class - 1) / 2 classifiers + according to the training method, ovr or ovo respectively. + :param X: array of size [n_samples, n_features] holding the training samples + :param y: array of size [n_samples] holding the class labels + :return: array of classifiers + """ + labels = np.unique(y) + self.n_class = len(labels) + if self.decision_function == 'ovr': # one-vs-rest method + for label in labels: + y1 = np.array(y) + y1[y1 != label] = -1.0 + y1[y1 == label] = 1.0 + clf = BinarySVM(self.kernel, self.C) + clf.fit(X, y1) + self.classifiers.append(copy.deepcopy(clf)) + elif self.decision_function == 'ovo': # use one-vs-one method + n_labels = len(labels) + for i in range(n_labels): + for j in range(i + 1, n_labels): + neg_id, pos_id = y == labels[i], y == labels[j] + x1, y1 = np.r_[X[neg_id], X[pos_id]], np.r_[y[neg_id], y[pos_id]] + y1[y1 == labels[i]] = -1.0 + y1[y1 == labels[j]] = 1.0 + clf = BinarySVM(self.kernel, self.C) + clf.fit(x1, y1) + self.classifiers.append(copy.deepcopy(clf)) + else: + return ValueError("Decision function must be either 'ovr' or 'ovo'.") + + def predict(self, x): + """ + Predicts the class of a given example according to the training method. + """ + n_samples = len(x) + if self.decision_function == 'ovr': # one-vs-rest method + assert len(self.classifiers) == self.n_class + score = np.zeros((n_samples, self.n_class)) + for i in range(self.n_class): + clf = self.classifiers[i] + score[:, i] = clf.predict_score(x) + return np.argmax(score, axis=1) + elif self.decision_function == 'ovo': # use one-vs-one method + assert len(self.classifiers) == self.n_class * (self.n_class - 1) / 2 + vote = np.zeros((n_samples, self.n_class)) + clf_id = 0 + for i in range(self.n_class): + for j in range(i + 1, self.n_class): + res = self.classifiers[clf_id].predict(x) + vote[res < 0, i] += 1.0 # negative sample: class i + vote[res > 0, j] += 1.0 # positive sample: class j + clf_id += 1 + return np.argmax(vote, axis=1) + else: + return ValueError("Decision function must be either 'ovr' or 'ovo'.") + + def EnsembleLearner(learners): """Given a list of learning algorithms, have them vote.""" @@ -608,16 +740,16 @@ def ada_boost(dataset, L, K): """[Figure 18.34]""" examples, target = dataset.examples, dataset.target - N = len(examples) - epsilon = 1 / (2 * N) - w = [1 / N] * N + n = len(examples) + eps = 1 / (2 * n) + w = [1 / n] * n h, z = [], [] for k in range(K): h_k = L(dataset, w) h.append(h_k) error = sum(weight for example, weight in zip(examples, w) if example[target] != h_k(example)) # avoid divide-by-0 from either 0% or 100% error rates - error = clip(error, epsilon, 1 - epsilon) + error = clip(error, eps, 1 - eps) for j, example in enumerate(examples): if example[target] == h_k(example): w[j] *= error / (1 - error) diff --git a/logic.py b/logic.py index bd0493043..1624d55a5 100644 --- a/logic.py +++ b/logic.py @@ -1,5 +1,5 @@ """ -Representations and Inference for Logic (Chapters 7-9, 12) +Representations and Inference for Logic. (Chapters 7-9, 12) Covers both Propositional and First-Order Logic. First we have four important data types: @@ -42,8 +42,7 @@ from agents import Agent, Glitter, Bump, Stench, Breeze, Scream from csp import parse_neighbors, UniversalDict from search import astar_search, PlanRoute -from utils import (remove_all, unique, first, argmax, probability, isnumber, - issequence, Expr, expr, subexpressions, extend) +from utils import remove_all, unique, first, probability, isnumber, issequence, Expr, expr, subexpressions, extend class KB: @@ -58,7 +57,8 @@ class KB: first one or returns False.""" def __init__(self, sentence=None): - raise NotImplementedError + if sentence: + self.tell(sentence) def tell(self, sentence): """Add the sentence to the KB.""" @@ -81,9 +81,8 @@ class PropKB(KB): """A KB for propositional logic. Inefficient, with no indexing.""" def __init__(self, sentence=None): + super().__init__(sentence) self.clauses = [] - if sentence: - self.tell(sentence) def tell(self, sentence): """Add the sentence's clauses to the KB.""" @@ -1108,7 +1107,7 @@ def sat_count(sym): model[sym] = not model[sym] return count - sym = argmax(prop_symbols(clause), key=sat_count) + sym = max(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None @@ -1930,10 +1929,11 @@ class FolKB(KB): False """ - def __init__(self, initial_clauses=None): + def __init__(self, clauses=None): + super().__init__() self.clauses = [] # inefficient: no indexing - if initial_clauses: - for clause in initial_clauses: + if clauses: + for clause in clauses: self.tell(clause) def tell(self, sentence): @@ -1957,7 +1957,7 @@ def fol_fc_ask(kb, alpha): [Figure 9.3] A simple forward-chaining algorithm. """ - # TODO: Improve efficiency + # TODO: improve efficiency kb_consts = list({c for clause in kb.clauses for c in constant_symbols(clause)}) def enum_subst(p): @@ -1968,7 +1968,7 @@ def enum_subst(p): # check if we can answer without new inferences for q in kb.clauses: - phi = unify(q, alpha) + phi = unify_mm(q, alpha) if phi is not None: yield phi @@ -1979,9 +1979,9 @@ def enum_subst(p): for theta in enum_subst(p): if set(subst(theta, p)).issubset(set(kb.clauses)): q_ = subst(theta, q) - if all([unify(x, q_) is None for x in kb.clauses + new]): + if all([unify_mm(x, q_) is None for x in kb.clauses + new]): new.append(q_) - phi = unify(q_, alpha) + phi = unify_mm(q_, alpha) if phi is not None: yield phi if not new: @@ -2003,7 +2003,7 @@ def fol_bc_ask(kb, query): def fol_bc_or(kb, goal, theta): for rule in kb.fetch_rules_for_goal(goal): lhs, rhs = parse_definite_clause(standardize_variables(rule)) - for theta1 in fol_bc_and(kb, lhs, unify(rhs, goal, theta)): + for theta1 in fol_bc_and(kb, lhs, unify_mm(rhs, goal, theta)): yield theta1 @@ -2019,7 +2019,7 @@ def fol_bc_and(kb, goals, theta): yield theta2 -# A simple KB that defines the relevant conditions of the Wumpus World as in Fig 7.4. +# A simple KB that defines the relevant conditions of the Wumpus World as in Figure 7.4. # See Sec. 7.4.3 wumpus_kb = PropKB() diff --git a/making_simple_decision4e.py b/making_simple_decision4e.py index 25ba3e3b6..a3b50e57c 100644 --- a/making_simple_decision4e.py +++ b/making_simple_decision4e.py @@ -1,11 +1,10 @@ +"""Making Simple Decisions. (Chapter 15)""" + import random from agents import Agent from probability import BayesNet -from utils4e import argmax, vector_add, weighted_sample_with_replacement - - -# Making Simple Decisions (Chapter 15) +from utils4e import vector_add, weighted_sample_with_replacement class DecisionNetwork(BayesNet): @@ -16,7 +15,7 @@ class DecisionNetwork(BayesNet): def __init__(self, action, infer): """action: a single action node infer: the preferred method to carry out inference on the given BayesNet""" - super(DecisionNetwork, self).__init__() + super().__init__() self.action = action self.infer = infer @@ -47,6 +46,7 @@ def __init__(self, decnet, infer, initial_evidence=None): """decnet: a decision network infer: the preferred method to carry out inference on the given decision network initial_evidence: initial evidence""" + super().__init__() self.decnet = decnet self.infer = infer self.observation = initial_evidence or [] @@ -60,7 +60,7 @@ def execute(self, percept): """Execute the information gathering algorithm""" self.observation = self.integrate_percept(percept) vpis = self.vpi_cost_ratio(self.variables) - j = argmax(vpis) + j = max(vpis) variable = self.variables[j] if self.vpi(variable) > self.cost(variable): diff --git a/mdp.py b/mdp.py index 54d3102ca..f558c8d40 100644 --- a/mdp.py +++ b/mdp.py @@ -1,17 +1,20 @@ -"""Markov Decision Processes (Chapter 17) +""" +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 argmax, vector_add, orientations, turn_right, turn_left +and policy_iteration algorithms. +""" import random -import numpy as np from collections import defaultdict +import numpy as np + +from utils import vector_add, orientations, turn_right, turn_left + class MDP: """A Markov Decision Process, defined by an initial state, transition model, @@ -20,7 +23,7 @@ class MDP: the text. Instead of P(s' | s, a) being a probability number for each state/state/action 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 646]""" + terminal states, and actions for each state. [Page 646]""" def __init__(self, init, actlist, terminals, transitions=None, reward=None, states=None, gamma=0.9): if not (0 < gamma <= 1): @@ -215,11 +218,11 @@ def value_iteration(mdp, epsilon=0.001): 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)""" + as a mapping from state to action. [Equation 17.4]""" pi = {} for s in mdp.states: - pi[s] = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) + pi[s] = max(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) return pi @@ -241,7 +244,7 @@ def policy_iteration(mdp): U = policy_evaluation(pi, U, mdp) unchanged = True for s in mdp.states: - a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) + a = max(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) if a != pi[s]: pi[s] = a unchanged = False @@ -266,7 +269,7 @@ class POMDP(MDP): and a sensor model P(e|s). We also keep track of a gamma value, for use by algorithms. The transition and the sensor models are defined as matrices. We also keep track of the possible states - and actions for each state. [page 659].""" + and actions for each state. [Page 659].""" def __init__(self, actions, transitions=None, evidences=None, rewards=None, states=None, gamma=0.95): """Initialize variables of the pomdp""" @@ -474,16 +477,16 @@ def pomdp_value_iteration(pomdp, epsilon=0.1): """ s = { 'a' : { 'plan1' : [(0.2, 'a'), (0.3, 'b'), (0.3, 'c'), (0.2, 'd')], - 'plan2' : [(0.4, 'a'), (0.15, 'b'), (0.45, 'c')], - 'plan3' : [(0.2, 'a'), (0.5, 'b'), (0.3, 'c')], - }, - 'b' : { 'plan1' : [(0.2, 'a'), (0.6, 'b'), (0.2, 'c'), (0.1, 'd')], - 'plan2' : [(0.6, 'a'), (0.2, 'b'), (0.1, 'c'), (0.1, 'd')], - 'plan3' : [(0.3, 'a'), (0.3, 'b'), (0.4, 'c')], - }, - 'c' : { 'plan1' : [(0.3, 'a'), (0.5, 'b'), (0.1, 'c'), (0.1, 'd')], - 'plan2' : [(0.5, 'a'), (0.3, 'b'), (0.1, 'c'), (0.1, 'd')], - 'plan3' : [(0.1, 'a'), (0.3, 'b'), (0.1, 'c'), (0.5, 'd')], - }, - } + 'plan2' : [(0.4, 'a'), (0.15, 'b'), (0.45, 'c')], + 'plan3' : [(0.2, 'a'), (0.5, 'b'), (0.3, 'c')], + }, + 'b' : { 'plan1' : [(0.2, 'a'), (0.6, 'b'), (0.2, 'c'), (0.1, 'd')], + 'plan2' : [(0.6, 'a'), (0.2, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.3, 'a'), (0.3, 'b'), (0.4, 'c')], + }, + 'c' : { 'plan1' : [(0.3, 'a'), (0.5, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan2' : [(0.5, 'a'), (0.3, 'b'), (0.1, 'c'), (0.1, 'd')], + 'plan3' : [(0.1, 'a'), (0.3, 'b'), (0.1, 'c'), (0.5, 'd')], + }, + } """ diff --git a/mdp4e.py b/mdp4e.py index bef1a7940..afa87ea0a 100644 --- a/mdp4e.py +++ b/mdp4e.py @@ -1,5 +1,5 @@ """ -Markov Decision Processes (Chapter 16) +Markov Decision Processes. (Chapter 16) 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 @@ -8,15 +8,12 @@ and policy_iteration algorithms. """ -from utils4e import argmax, vector_add, orientations, turn_right, turn_left -from planning import * import random -import numpy as np from collections import defaultdict +import numpy as np -# _____________________________________________________________ -# 16.1 Sequential Detection Problems +from utils4e import vector_add, orientations, turn_right, turn_left class MDP: @@ -26,7 +23,7 @@ class MDP: the text. Instead of P(s' | s, a) being a probability number for each state/state/action 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 646]""" + terminal states, and actions for each state. [Page 646]""" def __init__(self, init, actlist, terminals, transitions=None, reward=None, states=None, gamma=0.9): if not (0 < gamma <= 1): @@ -229,8 +226,8 @@ def value_iteration(mdp, epsilon=0.001): 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)) + # U1[s] = R(s) + gamma * max(sum(p * U[s1] for (p, s1) in T(s, a)) + # for a in mdp.actions(s)) U1[s] = max(q_value(mdp, s, a, U) for a in mdp.actions(s)) delta = max(delta, abs(U1[s] - U[s])) if delta <= epsilon * (1 - gamma) / gamma: @@ -247,7 +244,7 @@ def best_policy(mdp, U): pi = {} for s in mdp.states: - pi[s] = argmax(mdp.actions(s), key=lambda a: q_value(mdp, s, a, U)) + pi[s] = max(mdp.actions(s), key=lambda a: q_value(mdp, s, a, U)) return pi @@ -266,8 +263,8 @@ def policy_iteration(mdp): U = policy_evaluation(pi, U, mdp) unchanged = True for s in mdp.states: - a_star = argmax(mdp.actions(s), key=lambda a: q_value(mdp, s, a, U)) - # a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) + a_star = max(mdp.actions(s), key=lambda a: q_value(mdp, s, a, U)) + # a = max(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp)) if q_value(mdp, s, a_star, U) > q_value(mdp, s, pi[s], U): pi[s] = a_star unchanged = False @@ -296,7 +293,7 @@ class POMDP(MDP): and a sensor model P(e|s). We also keep track of a gamma value, for use by algorithms. The transition and the sensor models are defined as matrices. We also keep track of the possible states - and actions for each state. [page 659].""" + and actions for each state. [Page 659].""" def __init__(self, actions, transitions=None, evidences=None, rewards=None, states=None, gamma=0.95): """Initialize variables of the pomdp""" @@ -517,38 +514,3 @@ def pomdp_value_iteration(pomdp, epsilon=0.1): }, } """ - - -# __________________________________________________________________________ -# Chapter 17 Multiagent Planning - - -def double_tennis_problem(): - """ - [Figure 17.1] DOUBLE-TENNIS-PROBLEM - A multiagent planning problem involving two partner tennis players - trying to return an approaching ball and repositioning around in the court. - - Example: - >>> from planning import * - >>> dtp = double_tennis_problem() - >>> goal_test(dtp.goals, dtp.initial) - False - >>> dtp.act(expr('Go(A, RightBaseLine, LeftBaseLine)')) - >>> dtp.act(expr('Hit(A, Ball, RightBaseLine)')) - >>> goal_test(dtp.goals, dtp.initial) - False - >>> dtp.act(expr('Go(A, LeftNet, RightBaseLine)')) - >>> goal_test(dtp.goals, dtp.initial) - True - """ - - return PlanningProblem( - initial='At(A, LeftBaseLine) & At(B, RightNet) & Approaching(Ball, RightBaseLine) & Partner(A, B) & Partner(B, A)', - goals='Returned(Ball) & At(a, LeftNet) & At(a, RightNet)', - actions=[Action('Hit(actor, Ball, loc)', - precond='Approaching(Ball, loc) & At(actor, loc)', - effect='Returned(Ball)'), - Action('Go(actor, to, loc)', - precond='At(actor, loc)', - effect='At(actor, to) & ~At(actor, loc)')]) diff --git a/nlp.py b/nlp.py index 03aabf54b..d883f3566 100644 --- a/nlp.py +++ b/nlp.py @@ -1,4 +1,4 @@ -"""Natural Language Processing; Chart Parsing and PageRanking (Chapter 22-23)""" +"""Natural Language Processing; Chart Parsing and PageRanking. (Chapter 22-23)""" from collections import defaultdict from utils import weighted_choice diff --git a/notebook.py b/notebook.py index c08685418..b28e97230 100644 --- a/notebook.py +++ b/notebook.py @@ -11,11 +11,10 @@ from PIL import Image from matplotlib import lines -from games import TicTacToe, alphabeta_player, random_player, Fig52Extended, inf +from games import TicTacToe, alpha_beta_player, random_player, Fig52Extended, inf from learning import DataSet -from logic import parse_definite_clause, standardize_variables, unify, subst +from logic import parse_definite_clause, standardize_variables, unify_mm, subst from search import GraphProblem, romania_map -from utils import argmax, argmin # ______________________________________________________________________________ @@ -384,10 +383,10 @@ class Canvas_TicTacToe(Canvas): def __init__(self, varname, player_1='human', player_2='random', width=300, height=350, cid=None): - valid_players = ('human', 'random', 'alphabeta') + valid_players = ('human', 'random', 'alpha_beta') if player_1 not in valid_players or player_2 not in valid_players: raise TypeError("Players must be one of {}".format(valid_players)) - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.ttt = TicTacToe() self.state = self.ttt.initial self.turn = 0 @@ -411,8 +410,8 @@ def mouse_click(self, x, y): # Invalid move return move = (x, y) - elif player == 'alphabeta': - move = alphabeta_player(self.ttt, self.state) + elif player == 'alpha_beta': + move = alpha_beta_player(self.ttt, self.state) else: move = random_player(self.ttt, self.state) self.state = self.ttt.result(self.state, move) @@ -480,11 +479,11 @@ def draw_o(self, position): self.arc_n(x / 3 + 1 / 6, (y / 3 + 1 / 6) * 6 / 7, 1 / 9, 0, 360) -class Canvas_minimax(Canvas): - """Minimax for Fig52Extended on HTML canvas""" +class Canvas_min_max(Canvas): + """MinMax for Fig52Extended on HTML canvas""" def __init__(self, varname, util_list, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super.__init__(varname, width, height, cid) self.utils = {node: util for node, util in zip(range(13, 40), util_list)} self.game = Fig52Extended() self.game.utils = self.utils @@ -505,7 +504,7 @@ def __init__(self, varname, util_list, width=800, height=600, cid=None): self.draw_graph() self.stack_manager = self.stack_manager_gen() - def minimax(self, node): + def min_max(self, node): game = self.game player = game.to_move(node) @@ -514,7 +513,7 @@ def max_value(node): return game.utility(node, player) self.change_list.append(('a', node)) self.change_list.append(('h',)) - max_a = argmax(game.actions(node), key=lambda x: min_value(game.result(node, x))) + max_a = max(game.actions(node), key=lambda x: min_value(game.result(node, x))) max_node = game.result(node, max_a) self.utils[node] = self.utils[max_node] x1, y1 = self.node_pos[node] @@ -530,7 +529,7 @@ def min_value(node): return game.utility(node, player) self.change_list.append(('a', node)) self.change_list.append(('h',)) - min_a = argmin(game.actions(node), key=lambda x: max_value(game.result(node, x))) + min_a = min(game.actions(node), key=lambda x: max_value(game.result(node, x))) min_node = game.result(node, min_a) self.utils[node] = self.utils[min_node] x1, y1 = self.node_pos[node] @@ -544,7 +543,7 @@ def min_value(node): return max_value(node) def stack_manager_gen(self): - self.minimax(0) + self.min_max(0) for change in self.change_list: if change[0] == 'a': self.node_stack.append(change[1]) @@ -605,11 +604,11 @@ def draw_graph(self): self.update() -class Canvas_alphabeta(Canvas): +class Canvas_alpha_beta(Canvas): """Alpha-beta pruning for Fig52Extended on HTML canvas""" def __init__(self, varname, util_list, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.utils = {node: util for node, util in zip(range(13, 40), util_list)} self.game = Fig52Extended() self.game.utils = self.utils @@ -632,11 +631,11 @@ def __init__(self, varname, util_list, width=800, height=600, cid=None): self.draw_graph() self.stack_manager = self.stack_manager_gen() - def alphabeta_search(self, node): + def alpha_beta_search(self, node): game = self.game player = game.to_move(node) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(node, alpha, beta): if game.terminal_test(node): self.change_list.append(('a', node)) @@ -698,7 +697,7 @@ def min_value(node, alpha, beta): return max_value(node, -inf, inf) def stack_manager_gen(self): - self.alphabeta_search(0) + self.alpha_beta_search(0) for change in self.change_list: if change[0] == 'a': self.node_stack.append(change[1]) @@ -779,7 +778,7 @@ class Canvas_fol_bc_ask(Canvas): """fol_bc_ask() on HTML canvas""" def __init__(self, varname, kb, query, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.kb = kb self.query = query self.l = 1 / 20 @@ -807,7 +806,7 @@ def fol_bc_ask(self): def fol_bc_or(KB, goal, theta): for rule in KB.fetch_rules_for_goal(goal): lhs, rhs = parse_definite_clause(standardize_variables(rule)) - for theta1 in fol_bc_and(KB, lhs, unify(rhs, goal, theta)): + for theta1 in fol_bc_and(KB, lhs, unify_mm(rhs, goal, theta)): yield ([(goal, theta1[0])], theta1[1]) def fol_bc_and(KB, goals, theta): diff --git a/notebook4e.py b/notebook4e.py index 060a1deb4..8a5d92cd6 100644 --- a/notebook4e.py +++ b/notebook4e.py @@ -12,11 +12,10 @@ from matplotlib import lines from matplotlib.colors import ListedColormap -from games import TicTacToe, alphabeta_player, random_player, Fig52Extended, inf +from games import TicTacToe, alpha_beta_player, random_player, Fig52Extended, inf from learning import DataSet -from logic import parse_definite_clause, standardize_variables, unify, subst +from logic import parse_definite_clause, standardize_variables, unify_mm, subst from search import GraphProblem, romania_map -from utils import argmax, argmin # ______________________________________________________________________________ @@ -420,10 +419,10 @@ class Canvas_TicTacToe(Canvas): def __init__(self, varname, player_1='human', player_2='random', width=300, height=350, cid=None): - valid_players = ('human', 'random', 'alphabeta') + valid_players = ('human', 'random', 'alpha_beta') if player_1 not in valid_players or player_2 not in valid_players: raise TypeError("Players must be one of {}".format(valid_players)) - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.ttt = TicTacToe() self.state = self.ttt.initial self.turn = 0 @@ -447,8 +446,8 @@ def mouse_click(self, x, y): # Invalid move return move = (x, y) - elif player == 'alphabeta': - move = alphabeta_player(self.ttt, self.state) + elif player == 'alpha_beta': + move = alpha_beta_player(self.ttt, self.state) else: move = random_player(self.ttt, self.state) self.state = self.ttt.result(self.state, move) @@ -516,11 +515,11 @@ def draw_o(self, position): self.arc_n(x / 3 + 1 / 6, (y / 3 + 1 / 6) * 6 / 7, 1 / 9, 0, 360) -class Canvas_minimax(Canvas): - """Minimax for Fig52Extended on HTML canvas""" +class Canvas_min_max(Canvas): + """MinMax for Fig52Extended on HTML canvas""" def __init__(self, varname, util_list, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.utils = {node: util for node, util in zip(range(13, 40), util_list)} self.game = Fig52Extended() self.game.utils = self.utils @@ -541,7 +540,7 @@ def __init__(self, varname, util_list, width=800, height=600, cid=None): self.draw_graph() self.stack_manager = self.stack_manager_gen() - def minimax(self, node): + def min_max(self, node): game = self.game player = game.to_move(node) @@ -550,7 +549,7 @@ def max_value(node): return game.utility(node, player) self.change_list.append(('a', node)) self.change_list.append(('h',)) - max_a = argmax(game.actions(node), key=lambda x: min_value(game.result(node, x))) + max_a = max(game.actions(node), key=lambda x: min_value(game.result(node, x))) max_node = game.result(node, max_a) self.utils[node] = self.utils[max_node] x1, y1 = self.node_pos[node] @@ -566,7 +565,7 @@ def min_value(node): return game.utility(node, player) self.change_list.append(('a', node)) self.change_list.append(('h',)) - min_a = argmin(game.actions(node), key=lambda x: max_value(game.result(node, x))) + min_a = min(game.actions(node), key=lambda x: max_value(game.result(node, x))) min_node = game.result(node, min_a) self.utils[node] = self.utils[min_node] x1, y1 = self.node_pos[node] @@ -580,7 +579,7 @@ def min_value(node): return max_value(node) def stack_manager_gen(self): - self.minimax(0) + self.min_max(0) for change in self.change_list: if change[0] == 'a': self.node_stack.append(change[1]) @@ -641,11 +640,11 @@ def draw_graph(self): self.update() -class Canvas_alphabeta(Canvas): +class Canvas_alpha_beta(Canvas): """Alpha-beta pruning for Fig52Extended on HTML canvas""" def __init__(self, varname, util_list, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.utils = {node: util for node, util in zip(range(13, 40), util_list)} self.game = Fig52Extended() self.game.utils = self.utils @@ -668,11 +667,11 @@ def __init__(self, varname, util_list, width=800, height=600, cid=None): self.draw_graph() self.stack_manager = self.stack_manager_gen() - def alphabeta_search(self, node): + def alpha_beta_search(self, node): game = self.game player = game.to_move(node) - # Functions used by alphabeta + # Functions used by alpha_beta def max_value(node, alpha, beta): if game.terminal_test(node): self.change_list.append(('a', node)) @@ -734,7 +733,7 @@ def min_value(node, alpha, beta): return max_value(node, -inf, inf) def stack_manager_gen(self): - self.alphabeta_search(0) + self.alpha_beta_search(0) for change in self.change_list: if change[0] == 'a': self.node_stack.append(change[1]) @@ -815,7 +814,7 @@ class Canvas_fol_bc_ask(Canvas): """fol_bc_ask() on HTML canvas""" def __init__(self, varname, kb, query, width=800, height=600, cid=None): - Canvas.__init__(self, varname, width, height, cid) + super().__init__(varname, width, height, cid) self.kb = kb self.query = query self.l = 1 / 20 @@ -843,7 +842,7 @@ def fol_bc_ask(self): def fol_bc_or(KB, goal, theta): for rule in KB.fetch_rules_for_goal(goal): lhs, rhs = parse_definite_clause(standardize_variables(rule)) - for theta1 in fol_bc_and(KB, lhs, unify(rhs, goal, theta)): + for theta1 in fol_bc_and(KB, lhs, unify_mm(rhs, goal, theta)): yield ([(goal, theta1[0])], theta1[1]) def fol_bc_and(KB, goals, theta): diff --git a/perception4e.py b/perception4e.py index 887d014b2..a36461cf6 100644 --- a/perception4e.py +++ b/perception4e.py @@ -1,15 +1,15 @@ -"""Perception (Chapter 24)""" +"""Perception. (Chapter 24)""" +import cv2 +import keras +import matplotlib.pyplot as plt import numpy as np import scipy.signal -import matplotlib.pyplot as plt -from utils4e import gaussian_kernel_2d, inf -import keras from keras.datasets import mnist +from keras.layers import Dense, Activation, Flatten, InputLayer, Conv2D, MaxPooling2D from keras.models import Sequential -from keras.layers import Dense, Activation, Flatten, InputLayer -from keras.layers import Conv2D, MaxPooling2D -import cv2 + +from utils4e import gaussian_kernel_2D, inf # ____________________________________________________ @@ -18,7 +18,7 @@ def array_normalization(array, range_min, range_max): - """normalize an array in the range of (range_min, range_max)""" + """Normalize an array in the range of (range_min, range_max)""" if not isinstance(array, np.ndarray): array = np.asarray(array) array = array - np.min(array) @@ -47,7 +47,7 @@ def gaussian_derivative_edge_detector(image): """Image edge detector using derivative of gaussian kernels""" if not isinstance(image, np.ndarray): image = np.asarray(image) - gaussian_filter = gaussian_kernel_2d() + gaussian_filter = gaussian_kernel_2D() # init derivative of gaussian filters x_filter = scipy.signal.convolve2d(gaussian_filter, np.asarray([[1, -1]]), 'same') y_filter = scipy.signal.convolve2d(gaussian_filter, np.asarray([[1], [-1]]), 'same') @@ -82,7 +82,7 @@ def show_edges(edges): def sum_squared_difference(pic1, pic2): - """ssd of two frames""" + """SSD of two frames""" pic1 = np.asarray(pic1) pic2 = np.asarray(pic2) assert pic1.shape == pic2.shape @@ -131,7 +131,7 @@ def gen_gray_scale_picture(size, level=3): def probability_contour_detection(image, discs, threshold=0): """ - detect edges/contours by applying a set of discs to an image + Detect edges/contours by applying a set of discs to an image :param image: an image in type of numpy ndarray :param discs: a set of discs/filters to apply to pixels of image :param threshold: threshold to tell whether the pixel at (x, y) is on an edge @@ -157,7 +157,7 @@ def probability_contour_detection(image, discs, threshold=0): def group_contour_detection(image, cluster_num=2): """ - detecting contours in an image with k-means clustering + Detecting contours in an image with k-means clustering :param image: an image in numpy ndarray type :param cluster_num: number of clusters in k-means """ @@ -169,7 +169,7 @@ def group_contour_detection(image, cluster_num=2): ret, label, center = cv2.kmeans(Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) center = np.uint8(center) res = center[label.flatten()] - res2 = res.reshape((img.shape)) + res2 = res.reshape(img.shape) # show the image # cv2.imshow('res2', res2) # cv2.waitKey(0) @@ -179,7 +179,7 @@ def group_contour_detection(image, cluster_num=2): def image_to_graph(image): """ - convert an image to an graph in adjacent matrix form + Convert an image to an graph in adjacent matrix form """ graph_dict = {} for x in range(image.shape[0]): @@ -191,7 +191,7 @@ def image_to_graph(image): def generate_edge_weight(image, v1, v2): """ - find edge weight between two vertices in an image + Find edge weight between two vertices in an image :param image: image in numpy ndarray type :param v1, v2: verticles in the image in form of (x index, y index) """ @@ -200,7 +200,7 @@ def generate_edge_weight(image, v1, v2): class Graph: - """graph in adjacent matrix to represent an image""" + """Graph in adjacent matrix to represent an image""" def __init__(self, image): """image: ndarray""" @@ -219,7 +219,7 @@ def __init__(self, image): self.flow[s][t] = generate_edge_weight(image, s, t) def bfs(self, s, t, parent): - """breadth first search to tell whether there is an edge between source and sink + """Breadth first search to tell whether there is an edge between source and sink parent: a list to save the path between s and t""" # queue to save the current searching frontier queue = [s] @@ -236,7 +236,7 @@ def bfs(self, s, t, parent): return True if t in visited else False def min_cut(self, source, sink): - """find the minimum cut of the graph between source and sink""" + """Find the minimum cut of the graph between source and sink""" parent = [] max_flow = 0 @@ -298,7 +298,7 @@ def gen_discs(init_scale, scales=1): def load_MINST(train_size, val_size, test_size): - """load MINST dataset from keras""" + """Load MINST dataset from keras""" (x_train, y_train), (x_test, y_test) = mnist.load_data() total_size = len(x_train) if train_size + val_size > total_size: @@ -318,25 +318,17 @@ def load_MINST(train_size, val_size, test_size): def simple_convnet(size=3, num_classes=10): """ - simple convolutional network for digit recognition + Simple convolutional network for digit recognition :param size: number of convolution layers :param num_classes: number of output classes :return a convolution network in keras model type """ model = Sequential() # add input layer for images of size (28, 28) - model.add( - InputLayer(input_shape=(1, 28, 28)) - ) + model.add(InputLayer(input_shape=(1, 28, 28))) # add convolution layers and max pooling layers for _ in range(size): - model.add( - Conv2D( - 32, (2, 2), - padding='same', - kernel_initializer='random_uniform' - ) - ) + model.add(Conv2D(32, (2, 2), padding='same', kernel_initializer='random_uniform')) model.add(MaxPooling2D(padding='same')) # add flatten layer and output layers @@ -354,7 +346,7 @@ def simple_convnet(size=3, num_classes=10): def train_model(model): - """train the simple convolution network""" + """Train the simple convolution network""" # load dataset (train_x, train_y), (val_x, val_y), (test_x, test_y) = load_MINST(1000, 100, 100) model.fit(train_x, train_y, validation_data=(val_x, val_y), epochs=5, verbose=2, batch_size=32) @@ -369,7 +361,7 @@ def train_model(model): def selective_search(image): """ - selective search for object detection + Selective search for object detection :param image: str, the path of image or image in ndarray type with 3 channels :return list of bounding boxes, each element is in form of [x_min, y_min, x_max, y_max] """ @@ -378,7 +370,7 @@ def selective_search(image): elif isinstance(image, str): im = cv2.imread(image) else: - im = np.stack((image) * 3, axis=-1) + im = np.stack(image * 3, axis=-1) # use opencv python to extract bounding box with selective search ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() diff --git a/planning.py b/planning.py index f62c23e02..5d57c3f55 100644 --- a/planning.py +++ b/planning.py @@ -8,8 +8,8 @@ from functools import reduce as _reduce import search -from csp import sat_up, NaryCSP, Constraint, ac_search_solver, is_ -from logic import FolKB, conjuncts, unify, associate, SAT_plan, cdcl_satisfiable +from csp import sat_up, NaryCSP, Constraint, ac_search_solver, is_constraint +from logic import FolKB, conjuncts, unify_mm, associate, SAT_plan, cdcl_satisfiable from search import Node from utils import Expr, expr, first, inf @@ -104,7 +104,7 @@ def expand_actions(self, name=None): for action in action_list: for permutation in itertools.permutations(objects, len(action.args)): - bindings = unify(Expr(action.name, *action.args), Expr(action.name, *permutation)) + bindings = unify_mm(Expr(action.name, *action.args), Expr(action.name, *permutation)) if bindings is not None: new_args = [] for arg in action.args: @@ -684,15 +684,15 @@ def eq_if_not_in(x1, a, x2): domains = {av: list(map(lambda action: expr(str(action)), expanded_actions)) for av in act_vars} domains.update({st(var, stage): {True, False} for var in fluent_values for stage in range(horizon + 2)}) # initial state constraints - constraints = [Constraint((st(var, 0),), is_(val)) + constraints = [Constraint((st(var, 0),), is_constraint(val)) for (var, val) in {expr(str(fluent).replace('Not', '')): True if fluent.op[:3] != 'Not' else False for fluent in planning_problem.initial}.items()] - constraints += [Constraint((st(var, 0),), is_(False)) + constraints += [Constraint((st(var, 0),), is_constraint(False)) for var in {expr(str(fluent).replace('Not', '')) for fluent in fluent_values if fluent not in planning_problem.initial}] # goal state constraints - constraints += [Constraint((st(var, horizon + 1),), is_(val)) + constraints += [Constraint((st(var, horizon + 1),), is_constraint(val)) for (var, val) in {expr(str(fluent).replace('Not', '')): True if fluent.op[:3] != 'Not' else False for fluent in planning_problem.goals}.items()] @@ -1160,7 +1160,7 @@ def find_action_for_precondition(self, oprec): for action in self.planning_problem.actions: for effect in action.effect: if effect.op == oprec.op: - bindings = unify(effect, oprec) + bindings = unify_mm(effect, oprec) if bindings is None: break return action, bindings diff --git a/probabilistic_learning.py b/probabilistic_learning.py index 4b78ef2d9..1138e702d 100644 --- a/probabilistic_learning.py +++ b/probabilistic_learning.py @@ -2,7 +2,7 @@ import heapq -from utils import weighted_sampler, argmax, product, gaussian +from utils import weighted_sampler, product, gaussian class CountingProbDist: @@ -93,7 +93,7 @@ def class_probability(target_val): attr_dist = attr_dists[target_val] return target_dist[target_val] * product(attr_dist[a] for a in example) - return argmax(target_dist.keys(), key=class_probability) + return max(target_dist.keys(), key=class_probability) return predict @@ -124,7 +124,7 @@ def class_probability(target_val): return (target_dist[target_val] * product(attr_dists[target_val, attr][example[attr]] for attr in dataset.inputs)) - return argmax(target_vals, key=class_probability) + return max(target_vals, key=class_probability) return predict @@ -149,6 +149,6 @@ def class_probability(target_val): prob *= gaussian(means[target_val][attr], deviations[target_val][attr], example[attr]) return prob - return argmax(target_vals, key=class_probability) + return max(target_vals, key=class_probability) return predict diff --git a/probability.py b/probability.py index 06a502547..9925079a2 100644 --- a/probability.py +++ b/probability.py @@ -1,6 +1,4 @@ -""" -Probability models. (Chapter 13-15) -""" +"""Probability models. (Chapter 13-15)""" import random from collections import defaultdict @@ -9,19 +7,19 @@ import numpy as np from agents import Agent -from utils import (product, argmax, element_wise_product, matrix_multiplication, vector_to_diagonal, vector_add, - scalar_vector_product, inverse_matrix, weighted_sample_with_replacement, isclose, probability, - normalize, extend) +from utils import (product, element_wise_product, matrix_multiplication, vector_add, scalar_vector_product, + weighted_sample_with_replacement, isclose, probability, normalize, extend) def DTAgentProgram(belief_state): """ [Figure 13.1] - A decision-theoretic agent.""" + A decision-theoretic agent. + """ def program(percept): belief_state.observe(program.action, percept) - program.action = argmax(belief_state.actions(), key=belief_state.expected_outcome_utility) + program.action = max(belief_state.actions(), key=belief_state.expected_outcome_utility) return program.action program.action = None @@ -41,14 +39,14 @@ class ProbDist: (0.125, 0.375, 0.5) """ - def __init__(self, var_name='?', freqs=None): - """If freqs is given, it is a dictionary of values - frequency pairs, + def __init__(self, var_name='?', freq=None): + """If freq is given, it is a dictionary of values - frequency pairs, then ProbDist is normalized.""" self.prob = {} self.var_name = var_name self.values = [] - if freqs: - for (v, p) in freqs.items(): + if freq: + for (v, p) in freq.items(): self[v] = p self.normalize() @@ -161,8 +159,7 @@ def enumerate_joint(variables, e, P): if not variables: return P[e] Y, rest = variables[0], variables[1:] - return sum([enumerate_joint(rest, extend(e, Y, y), P) - for y in P.values(Y)]) + return sum([enumerate_joint(rest, extend(e, Y, y), P) for y in P.values(Y)]) # ______________________________________________________________________________ @@ -261,7 +258,7 @@ def execute(self, percept): """Execute the information gathering algorithm""" self.observation = self.integrate_percept(percept) vpis = self.vpi_cost_ratio(self.variables) - j = argmax(vpis) + j = max(vpis) variable = self.variables[j] if self.vpi(variable) > self.cost(variable): @@ -376,13 +373,12 @@ def __repr__(self): T, F = True, False -burglary = BayesNet([ - ('Burglary', '', 0.001), - ('Earthquake', '', 0.002), - ('Alarm', 'Burglary Earthquake', - {(T, T): 0.95, (T, F): 0.94, (F, T): 0.29, (F, F): 0.001}), - ('JohnCalls', 'Alarm', {T: 0.90, F: 0.05}), - ('MaryCalls', 'Alarm', {T: 0.70, F: 0.01})]) +burglary = BayesNet([('Burglary', '', 0.001), + ('Earthquake', '', 0.002), + ('Alarm', 'Burglary Earthquake', + {(T, T): 0.95, (T, F): 0.94, (F, T): 0.29, (F, F): 0.001}), + ('JohnCalls', 'Alarm', {T: 0.90, F: 0.05}), + ('MaryCalls', 'Alarm', {T: 0.70, F: 0.01})]) # ______________________________________________________________________________ @@ -513,12 +509,11 @@ def all_events(variables, bn, e): # [Figure 14.12a]: sprinkler network -sprinkler = BayesNet([ - ('Cloudy', '', 0.5), - ('Sprinkler', 'Cloudy', {T: 0.10, F: 0.50}), - ('Rain', 'Cloudy', {T: 0.80, F: 0.20}), - ('WetGrass', 'Sprinkler Rain', - {(T, T): 0.99, (T, F): 0.90, (F, T): 0.90, (F, F): 0.00})]) +sprinkler = BayesNet([('Cloudy', '', 0.5), + ('Sprinkler', 'Cloudy', {T: 0.10, F: 0.50}), + ('Rain', 'Cloudy', {T: 0.80, F: 0.20}), + ('WetGrass', 'Sprinkler Rain', + {(T, T): 0.99, (T, F): 0.90, (F, T): 0.90, (F, F): 0.00})]) # ______________________________________________________________________________ @@ -527,8 +522,9 @@ def all_events(variables, bn, e): def prior_sample(bn): """ [Figure 14.13] - Randomly sample from bn's full joint distribution. The result - is a {variable: value} dict.""" + Randomly sample from bn's full joint distribution. + The result is a {variable: value} dict. + """ event = {} for node in bn.nodes: event[node.variable] = node.sample(event) @@ -584,9 +580,11 @@ def likelihood_weighting(X, e, bn, N=10000): def weighted_sample(bn, e): - """Sample an event from bn that's consistent with the evidence e; + """ + Sample an event from bn that's consistent with the evidence e; return the event and its weight, the likelihood that the event - accords to the evidence.""" + accords to the evidence. + """ w = 1 event = dict(e) # boldface x in [Figure 14.15] for node in bn.nodes: @@ -669,13 +667,13 @@ def forward_backward(HMM, ev): """ [Figure 15.4] Forward-Backward algorithm for smoothing. Computes posterior probabilities - of a sequence of states given a sequence of observations.""" + of a sequence of states given a sequence of observations. + """ t = len(ev) ev.insert(0, None) # to make the code look similar to pseudo code fv = [[0.0, 0.0] for _ in range(len(ev))] b = [1.0, 1.0] - bv = [b] # we don't need bv; but we will have a list of all backward messages here sv = [[0, 0] for _ in range(len(ev))] fv[0] = HMM.prior @@ -685,7 +683,6 @@ def forward_backward(HMM, ev): for i in range(t, -1, -1): sv[i - 1] = normalize(element_wise_product(fv[i], b)) b = backward(HMM, b, ev[i]) - bv.append(b) sv = sv[::-1] @@ -696,7 +693,8 @@ def viterbi(HMM, ev): """ [Equation 15.11] Viterbi algorithm to find the most likely sequence. Computes the best path and the - corresponding probabilities, given an HMM model and a sequence of observations.""" + corresponding probabilities, given an HMM model and a sequence of observations. + """ t = len(ev) ev = ev.copy() ev.insert(0, None) @@ -741,20 +739,19 @@ def fixed_lag_smoothing(e_t, HMM, d, ev, t): [Figure 15.6] Smoothing algorithm with a fixed time lag of 'd' steps. Online algorithm that outputs the new smoothed estimate if observation - for new time step is given.""" + for new time step is given. + """ ev.insert(0, None) T_model = HMM.transition_model f = HMM.prior B = [[1, 0], [0, 1]] - evidence = [] - evidence.append(e_t) - O_t = vector_to_diagonal(HMM.sensor_dist(e_t)) + O_t = np.diag(HMM.sensor_dist(e_t)) if t > d: f = forward(HMM, f, e_t) - O_tmd = vector_to_diagonal(HMM.sensor_dist(ev[t - d])) - B = matrix_multiplication(inverse_matrix(O_tmd), inverse_matrix(T_model), B, T_model, O_t) + O_tmd = np.diag(HMM.sensor_dist(ev[t - d])) + B = matrix_multiplication(np.linalg.inv(O_tmd), np.linalg.inv(T_model), B, T_model, O_t) else: B = matrix_multiplication(B, T_model, O_t) t += 1 @@ -801,7 +798,6 @@ def particle_filtering(e, N, HMM): w[i] = float("{0:.4f}".format(w[i])) # STEP 2 - s = weighted_sample_with_replacement(N, s, w) return s @@ -831,7 +827,7 @@ def sample(self): return kin_state def ray_cast(self, sensor_num, kin_state): - """Returns distace to nearest obstacle or map boundary in the direction of sensor""" + """Returns distance to nearest obstacle or map boundary in the direction of sensor""" pos = kin_state[:2] orient = kin_state[2] # sensor layout when orientation is 0 (towards North) @@ -843,7 +839,7 @@ def ray_cast(self, sensor_num, kin_state): for _ in range(orient): delta = (delta[1], -delta[0]) range_count = 0 - while (0 <= pos[0] < self.nrows) and (0 <= pos[1] < self.nrows) and (not self.m[pos[0]][pos[1]]): + while 0 <= pos[0] < self.nrows and 0 <= pos[1] < self.nrows and not self.m[pos[0]][pos[1]]: pos = vector_add(pos, delta) range_count += 1 return range_count @@ -852,13 +848,13 @@ def ray_cast(self, sensor_num, kin_state): def monte_carlo_localization(a, z, N, P_motion_sample, P_sensor, m, S=None): """ [Figure 25.9] - Monte Carlo localization algorithm""" + Monte Carlo localization algorithm + """ def ray_cast(sensor_num, kin_state, m): return m.ray_cast(sensor_num, kin_state) M = len(z) - W = [0] * N S_ = [0] * N W_ = [0] * N v = a['v'] diff --git a/probability4e.py b/probability4e.py index 66d18dcf6..cd1ff2022 100644 --- a/probability4e.py +++ b/probability4e.py @@ -6,7 +6,7 @@ from functools import reduce from math import sqrt, pi, exp -from utils4e import product, argmax, isclose, probability, extend +from utils4e import product, isclose, probability, extend # ______________________________________________________________________________ @@ -19,7 +19,7 @@ def DTAgentProgram(belief_state): def program(percept): belief_state.observe(program.action, percept) - program.action = argmax(belief_state.actions(), key=belief_state.expected_outcome_utility) + program.action = max(belief_state.actions(), key=belief_state.expected_outcome_utility) return program.action program.action = None diff --git a/reinforcement_learning.py b/reinforcement_learning.py index 05c7a890f..a640ac39a 100644 --- a/reinforcement_learning.py +++ b/reinforcement_learning.py @@ -1,14 +1,14 @@ -"""Reinforcement Learning (Chapter 21)""" +"""Reinforcement Learning. (Chapter 21)""" +import random from collections import defaultdict -from utils import argmax -from mdp import MDP, policy_evaluation -import random +from mdp import MDP, policy_evaluation class PassiveDUEAgent: - """Passive (non-learning) agent that uses direct utility estimation + """ + Passive (non-learning) agent that uses direct utility estimation on a given MDP and policy. import sys @@ -25,7 +25,6 @@ class PassiveDUEAgent: agent.estimate_U() agent.U[(0, 0)] > 0.2 True - """ def __init__(self, pi, mdp): @@ -73,14 +72,16 @@ def estimate_U(self): return self.U def update_state(self, percept): - '''To be overridden in most cases. The default case - assumes the percept to be of type (state, reward)''' + """To be overridden in most cases. The default case + assumes the percept to be of type (state, reward)""" return percept class PassiveADPAgent: - """Passive (non-learning) agent that uses adaptive dynamic programming - on a given MDP and policy. [Figure 21.2] + """ + [Figure 21.2] + Passive (non-learning) agent that uses adaptive dynamic programming + on a given MDP and policy. import sys from mdp import sequential_decision_environment @@ -101,8 +102,8 @@ class PassiveADPAgent: """ class ModelMDP(MDP): - """ Class for implementing modified Version of input MDP with - an editable transition model P and a custom function T. """ + """Class for implementing modified Version of input MDP with + an editable transition model P and a custom function T.""" def __init__(self, init, actlist, terminals, gamma, states): super().__init__(init, actlist, terminals, states=states, gamma=gamma) @@ -160,10 +161,12 @@ def update_state(self, percept): class PassiveTDAgent: - """The abstract class for a Passive (non-learning) agent that uses + """ + [Figure 21.4] + The abstract class for a Passive (non-learning) agent that uses temporal differences to learn utility estimates. Override update_state method to convert percept to state and reward. The mdp being provided - should be an instance of a subclass of the MDP Class. [Figure 21.4] + should be an instance of a subclass of the MDP Class. import sys from mdp import sequential_decision_environment @@ -221,9 +224,11 @@ def update_state(self, percept): class QLearningAgent: - """ An exploratory Q-learning agent. It avoids having to learn the transition - model because the Q-value of a state can be related directly to those of - its neighbors. [Figure 21.8] + """ + [Figure 21.8] + An exploratory Q-learning agent. It avoids having to learn the transition + model because the Q-value of a state can be related directly to those of + its neighbors. import sys from mdp import sequential_decision_environment @@ -262,7 +267,7 @@ def __init__(self, mdp, Ne, Rplus, alpha=None): self.alpha = lambda n: 1. / (1 + n) # udacity video def f(self, u, n): - """ Exploration function. Returns fixed Rplus until + """Exploration function. Returns fixed Rplus until agent has visited state, action a Ne number of times. Same as ADP agent in book.""" if n < self.Ne: @@ -271,8 +276,8 @@ def f(self, u, n): return u def actions_in_state(self, state): - """ Return actions possible in given state. - Useful for max and argmax. """ + """Return actions possible in given state. + Useful for max and argmax.""" if state in self.terminals: return [None] else: @@ -294,7 +299,7 @@ def __call__(self, percept): self.s = self.a = self.r = None else: self.s, self.r = s1, r1 - self.a = argmax(actions_in_state(s1), key=lambda a1: self.f(Q[s1, a1], Nsa[s1, a1])) + self.a = max(actions_in_state(s1), key=lambda a1: self.f(Q[s1, a1], Nsa[s1, a1])) return self.a def update_state(self, percept): diff --git a/reinforcement_learning4e.py b/reinforcement_learning4e.py index 44fda5c87..fecfdaa32 100644 --- a/reinforcement_learning4e.py +++ b/reinforcement_learning4e.py @@ -1,10 +1,9 @@ -"""Reinforcement Learning (Chapter 21)""" +"""Reinforcement Learning. (Chapter 21)""" +import random from collections import defaultdict -from utils4e import argmax -from mdp import MDP, policy_evaluation -import random +from mdp4e import MDP, policy_evaluation # _________________________________________ @@ -13,7 +12,8 @@ class PassiveDUEAgent: - """Passive (non-learning) agent that uses direct utility estimation + """ + Passive (non-learning) agent that uses direct utility estimation on a given MDP and policy. import sys @@ -30,7 +30,6 @@ class PassiveDUEAgent: agent.estimate_U() agent.U[(0, 0)] > 0.2 True - """ def __init__(self, pi, mdp): @@ -87,8 +86,10 @@ def update_state(self, percept): class PassiveADPAgent: - """Passive (non-learning) agent that uses adaptive dynamic programming - on a given MDP and policy. [Figure 21.2] + """ + [Figure 21.2] + Passive (non-learning) agent that uses adaptive dynamic programming + on a given MDP and policy. import sys from mdp import sequential_decision_environment @@ -109,8 +110,8 @@ class PassiveADPAgent: """ class ModelMDP(MDP): - """ Class for implementing modified Version of input MDP with - an editable transition model P and a custom function T. """ + """Class for implementing modified Version of input MDP with + an editable transition model P and a custom function T.""" def __init__(self, init, actlist, terminals, gamma, states): super().__init__(init, actlist, terminals, states=states, gamma=gamma) @@ -171,10 +172,12 @@ def update_state(self, percept): class PassiveTDAgent: - """The abstract class for a Passive (non-learning) agent that uses + """ + [Figure 21.4] + The abstract class for a Passive (non-learning) agent that uses temporal differences to learn utility estimates. Override update_state method to convert percept to state and reward. The mdp being provided - should be an instance of a subclass of the MDP Class. [Figure 21.4] + should be an instance of a subclass of the MDP Class. import sys from mdp import sequential_decision_environment @@ -237,9 +240,11 @@ def update_state(self, percept): class QLearningAgent: - """ An exploratory Q-learning agent. It avoids having to learn the transition - model because the Q-value of a state can be related directly to those of - its neighbors. [Figure 21.8] + """ + [Figure 21.8] + An exploratory Q-learning agent. It avoids having to learn the transition + model because the Q-value of a state can be related directly to those of + its neighbors. import sys from mdp import sequential_decision_environment @@ -278,7 +283,7 @@ def __init__(self, mdp, Ne, Rplus, alpha=None): self.alpha = lambda n: 1. / (1 + n) # udacity video def f(self, u, n): - """ Exploration function. Returns fixed Rplus until + """Exploration function. Returns fixed Rplus until agent has visited state, action a Ne number of times. Same as ADP agent in book.""" if n < self.Ne: @@ -287,8 +292,8 @@ def f(self, u, n): return u def actions_in_state(self, state): - """ Return actions possible in given state. - Useful for max and argmax. """ + """Return actions possible in given state. + Useful for max and argmax.""" if state in self.terminals: return [None] else: @@ -310,7 +315,7 @@ def __call__(self, percept): self.s = self.a = self.r = None else: self.s, self.r = s1, r1 - self.a = argmax(actions_in_state(s1), key=lambda a1: self.f(Q[s1, a1], Nsa[s1, a1])) + self.a = max(actions_in_state(s1), key=lambda a1: self.f(Q[s1, a1], Nsa[s1, a1])) return self.a def update_state(self, percept): diff --git a/requirements.txt b/requirements.txt index bf019e803..5d0d607dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,18 @@ -ipywidgets -scipy -pytest -sortedcontainers -networkx -jupyter -pandas -matplotlib -pillow Image ipython ipythonblocks +ipywidgets +jupyter keras +matplotlib +networkx numpy -tensorflow opencv-python +pandas +pillow +pytest +qpsolvers +quadprog +scipy +sortedcontainers +tensorflow \ No newline at end of file diff --git a/search.py b/search.py index 262f5a793..999dc8f57 100644 --- a/search.py +++ b/search.py @@ -12,8 +12,8 @@ import sys from collections import deque -from utils import (is_in, argmin, argmax, argmax_random_tie, probability, weighted_sampler, memoize, - print_table, open_data, PriorityQueue, name, distance, vector_add, inf) +from utils import (is_in, argmax_random_tie, probability, weighted_sampler, memoize, print_table, open_data, + PriorityQueue, name, distance, vector_add, inf) class Problem: @@ -879,8 +879,8 @@ def __call__(self, s1): # as of now s1 is a state rather than a percept self.H) for b in self.problem.actions(self.s)) # an action b in problem.actions(s1) that minimizes costs - self.a = argmin(self.problem.actions(s1), - key=lambda b: self.LRTA_cost(s1, b, self.problem.output(s1, b), self.H)) + self.a = min(self.problem.actions(s1), + key=lambda b: self.LRTA_cost(s1, b, self.problem.output(s1, b), self.H)) self.s = s1 return self.a @@ -928,14 +928,14 @@ def genetic_algorithm(population, fitness_fn, gene_pool=[0, 1], f_thres=None, ng if fittest_individual: return fittest_individual - return argmax(population, key=fitness_fn) + return max(population, key=fitness_fn) def fitness_threshold(fitness_fn, f_thres, population): if not f_thres: return None - fittest_individual = argmax(population, key=fitness_fn) + fittest_individual = max(population, key=fitness_fn) if fitness_fn(fittest_individual) >= f_thres: return fittest_individual @@ -1083,7 +1083,7 @@ def distance_to_node(n): return inf return distance(g.locations[n], here) - neighbor = argmin(nodes, key=distance_to_node) + neighbor = min(nodes, key=distance_to_node) d = distance(g.locations[neighbor], here) * curvature() g.connect(node, neighbor, int(d)) return g diff --git a/tests/test_agents.py b/tests/test_agents.py index 3b3182389..39d9b9262 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -2,12 +2,10 @@ import pytest -from agents import Agent -from agents import Direction from agents import (ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, Wall, Gold, Explorer, Thing, Bump, Glitter, - WumpusEnvironment, Pit, VacuumEnvironment, Dirt) + WumpusEnvironment, Pit, VacuumEnvironment, Dirt, Direction, Agent) random.seed("aima-python") diff --git a/tests/test_agents4e.py b/tests/test_agents4e.py index a84e67e7f..2c6759c22 100644 --- a/tests/test_agents4e.py +++ b/tests/test_agents4e.py @@ -2,11 +2,10 @@ import pytest -from agents4e import Agent, WumpusEnvironment, Explorer, Thing, Gold, Pit, Bump, Glitter -from agents4e import Direction from agents4e import (ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, - SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, Wall, VacuumEnvironment, Dirt) + SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, Wall, Gold, Explorer, Thing, Bump, + Glitter, WumpusEnvironment, Pit, VacuumEnvironment, Dirt, Direction, Agent) random.seed("aima-python") diff --git a/tests/test_deep_learning4e.py b/tests/test_deep_learning4e.py index 2a611076c..92d73e96e 100644 --- a/tests/test_deep_learning4e.py +++ b/tests/test_deep_learning4e.py @@ -1,9 +1,9 @@ +import numpy as np import pytest +from keras.datasets import imdb from deep_learning4e import * from learning4e import DataSet, grade_learner, err_ratio -from keras.datasets import imdb -import numpy as np random.seed("aima-python") @@ -12,7 +12,7 @@ def test_neural_net(): iris = DataSet(name='iris') classes = ['setosa', 'versicolor', 'virginica'] iris.classes_to_numbers(classes) - nnl_adam = NeuralNetLearner(iris, [4], learning_rate=0.001, epochs=200, optimizer=adam_optimizer) + nnl_adam = NeuralNetLearner(iris, [4], learning_rate=0.001, epochs=200, optimizer=adam) nnl_gd = NeuralNetLearner(iris, [4], learning_rate=0.15, epochs=100, optimizer=gradient_descent) tests = [([5.0, 3.1, 0.9, 0.1], 0), ([5.1, 3.5, 1.0, 0.0], 0), @@ -54,7 +54,7 @@ def test_rnn(): assert score[1] >= 0.3 -def test_auto_encoder(): +def test_autoencoder(): iris = DataSet(name='iris') classes = ['setosa', 'versicolor', 'virginica'] iris.classes_to_numbers(classes) diff --git a/tests/test_games.py b/tests/test_games.py index bea2668a4..b7541ee93 100644 --- a/tests/test_games.py +++ b/tests/test_games.py @@ -9,14 +9,13 @@ random.seed("aima-python") -def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3, k=3): +def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3): """Given whose turn it is to move, the positions of X's on the board, the positions of O's on the board, and, (optionally) number of rows, columns and how many consecutive X's or O's required to win, return the corresponding game state""" - moves = set([(x, y) for x in range(1, h + 1) for y in range(1, v + 1)]) \ - - set(x_positions) - set(o_positions) + moves = set([(x, y) for x in range(1, h + 1) for y in range(1, v + 1)]) - set(x_positions) - set(o_positions) moves = list(moves) board = {} for pos in x_positions: @@ -26,44 +25,44 @@ def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3, k=3): return GameState(to_move=to_move, utility=0, board=board, moves=moves) -def test_minimax_decision(): - assert minimax_decision('A', f52) == 'a1' - assert minimax_decision('B', f52) == 'b1' - assert minimax_decision('C', f52) == 'c1' - assert minimax_decision('D', f52) == 'd3' +def test_minmax_decision(): + assert minmax_decision('A', f52) == 'a1' + assert minmax_decision('B', f52) == 'b1' + assert minmax_decision('C', f52) == 'c1' + assert minmax_decision('D', f52) == 'd3' -def test_alphabeta_search(): - assert alphabeta_search('A', f52) == 'a1' - assert alphabeta_search('B', f52) == 'b1' - assert alphabeta_search('C', f52) == 'c1' - assert alphabeta_search('D', f52) == 'd3' +def test_alpha_beta_search(): + assert alpha_beta_search('A', f52) == 'a1' + assert alpha_beta_search('B', f52) == 'b1' + assert alpha_beta_search('C', f52) == 'c1' + assert alpha_beta_search('D', f52) == 'd3' state = gen_state(to_move='X', x_positions=[(1, 1), (3, 3)], o_positions=[(1, 2), (3, 2)]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='O', x_positions=[(1, 1), (3, 1), (3, 3)], o_positions=[(1, 2), (3, 2)]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='O', x_positions=[(1, 1)], o_positions=[]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='X', x_positions=[(1, 1), (3, 1)], o_positions=[(2, 2), (3, 1)]) - assert alphabeta_search(state, ttt) == (1, 3) + assert alpha_beta_search(state, ttt) == (1, 3) def test_random_tests(): - assert Fig52Game().play_game(alphabeta_player, alphabeta_player) == 3 + assert Fig52Game().play_game(alpha_beta_player, alpha_beta_player) == 3 # The player 'X' (one who plays first) in TicTacToe never loses: - assert ttt.play_game(alphabeta_player, alphabeta_player) >= 0 + assert ttt.play_game(alpha_beta_player, alpha_beta_player) >= 0 # The player 'X' (one who plays first) in TicTacToe never loses: - assert ttt.play_game(alphabeta_player, random_player) >= 0 + assert ttt.play_game(alpha_beta_player, random_player) >= 0 if __name__ == "__main__": diff --git a/tests/test_games4e.py b/tests/test_games4e.py index 7957aaf15..7dfa47f11 100644 --- a/tests/test_games4e.py +++ b/tests/test_games4e.py @@ -10,14 +10,13 @@ random.seed("aima-python") -def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3, k=3): +def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3): """Given whose turn it is to move, the positions of X's on the board, the positions of O's on the board, and, (optionally) number of rows, columns and how many consecutive X's or O's required to win, return the corresponding game state""" - moves = set([(x, y) for x in range(1, h + 1) for y in range(1, v + 1)]) \ - - set(x_positions) - set(o_positions) + moves = set([(x, y) for x in range(1, h + 1) for y in range(1, v + 1)]) - set(x_positions) - set(o_positions) moves = list(moves) board = {} for pos in x_positions: @@ -27,34 +26,34 @@ def gen_state(to_move='X', x_positions=[], o_positions=[], h=3, v=3, k=3): return GameState(to_move=to_move, utility=0, board=board, moves=moves) -def test_minimax_decision(): - assert minimax_decision('A', f52) == 'a1' - assert minimax_decision('B', f52) == 'b1' - assert minimax_decision('C', f52) == 'c1' - assert minimax_decision('D', f52) == 'd3' +def test_minmax_decision(): + assert minmax_decision('A', f52) == 'a1' + assert minmax_decision('B', f52) == 'b1' + assert minmax_decision('C', f52) == 'c1' + assert minmax_decision('D', f52) == 'd3' -def test_alphabeta_search(): - assert alphabeta_search('A', f52) == 'a1' - assert alphabeta_search('B', f52) == 'b1' - assert alphabeta_search('C', f52) == 'c1' - assert alphabeta_search('D', f52) == 'd3' +def test_alpha_beta_search(): + assert alpha_beta_search('A', f52) == 'a1' + assert alpha_beta_search('B', f52) == 'b1' + assert alpha_beta_search('C', f52) == 'c1' + assert alpha_beta_search('D', f52) == 'd3' state = gen_state(to_move='X', x_positions=[(1, 1), (3, 3)], o_positions=[(1, 2), (3, 2)]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='O', x_positions=[(1, 1), (3, 1), (3, 3)], o_positions=[(1, 2), (3, 2)]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='O', x_positions=[(1, 1)], o_positions=[]) - assert alphabeta_search(state, ttt) == (2, 2) + assert alpha_beta_search(state, ttt) == (2, 2) state = gen_state(to_move='X', x_positions=[(1, 1), (3, 1)], o_positions=[(2, 2), (3, 1)]) - assert alphabeta_search(state, ttt) == (1, 3) + assert alpha_beta_search(state, ttt) == (1, 3) def test_monte_carlo_tree_search(): @@ -75,22 +74,22 @@ def test_monte_carlo_tree_search(): o_positions=[(2, 2), (3, 1)]) assert monte_carlo_tree_search(state, ttt) == (1, 3) - # should never lose to a random or alphabeta player in a ttt game + # should never lose to a random or alpha_beta player in a ttt game assert ttt.play_game(mcts_player, random_player) >= 0 - assert ttt.play_game(mcts_player, alphabeta_player) >= 0 + assert ttt.play_game(mcts_player, alpha_beta_player) >= 0 # should never lose to a random player in a connect four game assert con4.play_game(mcts_player, random_player) >= 0 def test_random_tests(): - assert Fig52Game().play_game(alphabeta_player, alphabeta_player) == 3 + assert Fig52Game().play_game(alpha_beta_player, alpha_beta_player) == 3 # The player 'X' (one who plays first) in TicTacToe never loses: - assert ttt.play_game(alphabeta_player, alphabeta_player) >= 0 + assert ttt.play_game(alpha_beta_player, alpha_beta_player) >= 0 # The player 'X' (one who plays first) in TicTacToe never loses: - assert ttt.play_game(alphabeta_player, random_player) >= 0 + assert ttt.play_game(alpha_beta_player, random_player) >= 0 if __name__ == "__main__": diff --git a/tests/test_learning.py b/tests/test_learning.py index 1590a4d33..fd84d74ed 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -44,7 +44,6 @@ def test_k_nearest_neighbors(): iris = DataSet(name='iris') knn = NearestNeighborLearner(iris, k=3) assert knn([5, 3, 1, 0.1]) == 'setosa' - assert knn([5, 3, 1, 0.1]) == 'setosa' assert knn([6, 5, 3, 1.5]) == 'versicolor' assert knn([7.5, 4, 6, 2]) == 'virginica' @@ -57,6 +56,25 @@ def test_decision_tree_learner(): assert dtl([7.5, 4, 6, 2]) == 'virginica' +def test_svm(): + iris = DataSet(name='iris') + classes = ['setosa', 'versicolor', 'virginica'] + iris.classes_to_numbers(classes) + svm = MultiSVM() + n_samples, n_features = len(iris.examples), iris.target + X, y = np.array([x[:n_features] for x in iris.examples]), np.array([x[n_features] for x in iris.examples]) + svm.fit(X, y) + assert svm.predict([[5.0, 3.1, 0.9, 0.1]]) == 0 + assert svm.predict([[5.1, 3.5, 1.0, 0.0]]) == 0 + assert svm.predict([[4.9, 3.3, 1.1, 0.1]]) == 0 + assert svm.predict([[6.0, 3.0, 4.0, 1.1]]) == 1 + assert svm.predict([[6.1, 2.2, 3.5, 1.0]]) == 1 + assert svm.predict([[5.9, 2.5, 3.3, 1.1]]) == 1 + assert svm.predict([[7.5, 4.1, 6.2, 2.3]]) == 2 + assert svm.predict([[7.3, 4.0, 6.1, 2.4]]) == 2 + assert svm.predict([[7.0, 3.3, 6.1, 2.5]]) == 2 + + def test_information_content(): assert information_content([]) == 0 assert information_content([4]) == 0 diff --git a/tests/test_learning4e.py b/tests/test_learning4e.py index 987a9bffc..3913443b1 100644 --- a/tests/test_learning4e.py +++ b/tests/test_learning4e.py @@ -45,7 +45,6 @@ def test_k_nearest_neighbors(): iris = DataSet(name='iris') knn = NearestNeighborLearner(iris, k=3) assert knn([5, 3, 1, 0.1]) == 'setosa' - assert knn([5, 3, 1, 0.1]) == 'setosa' assert knn([6, 5, 3, 1.5]) == 'versicolor' assert knn([7.5, 4, 6, 2]) == 'virginica' @@ -58,6 +57,25 @@ def test_decision_tree_learner(): assert dtl([7.5, 4, 6, 2]) == 'virginica' +def test_svm(): + iris = DataSet(name='iris') + classes = ['setosa', 'versicolor', 'virginica'] + iris.classes_to_numbers(classes) + svm = MultiSVM() + n_samples, n_features = len(iris.examples), iris.target + X, y = np.array([x[:n_features] for x in iris.examples]), np.array([x[n_features] for x in iris.examples]) + svm.fit(X, y) + assert svm.predict([[5.0, 3.1, 0.9, 0.1]]) == 0 + assert svm.predict([[5.1, 3.5, 1.0, 0.0]]) == 0 + assert svm.predict([[4.9, 3.3, 1.1, 0.1]]) == 0 + assert svm.predict([[6.0, 3.0, 4.0, 1.1]]) == 1 + assert svm.predict([[6.1, 2.2, 3.5, 1.0]]) == 1 + assert svm.predict([[5.9, 2.5, 3.3, 1.1]]) == 1 + assert svm.predict([[7.5, 4.1, 6.2, 2.3]]) == 2 + assert svm.predict([[7.3, 4.0, 6.1, 2.4]]) == 2 + assert svm.predict([[7.0, 3.3, 6.1, 2.5]]) == 2 + + def test_information_content(): assert information_content([]) == 0 assert information_content([4]) == 0 diff --git a/tests/test_logic.py b/tests/test_logic.py index 8d018bc40..2ead21746 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -6,12 +6,12 @@ random.seed("aima-python") definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', - '(A & E & F)==>G', - '(B & C)==>F', - '(A & B)==>D', - '(E & F)==>H', - '(H & I)==>J', +for clause in ['(B & F) ==> E', + '(A & E & F) ==> G', + '(B & C) ==> F', + '(A & B) ==> D', + '(E & F) ==> H', + '(H & I) ==> J', 'A', 'B', 'C']: definite_clauses_KB.tell(expr(clause)) @@ -47,8 +47,7 @@ def test_variables(): def test_expr(): assert repr(expr('P <=> Q(1)')) == '(P <=> Q(1))' assert repr(expr('P & Q | ~R(x, F(x))')) == '((P & Q) | ~R(x, F(x)))' - assert (expr_handle_infix_ops('P & Q ==> R & ~S') - == "P & Q |'==>'| R & ~S") + assert expr_handle_infix_ops('P & Q ==> R & ~S') == "P & Q |'==>'| R & ~S" def test_extend(): @@ -261,10 +260,8 @@ def test_dissociate(): def test_associate(): - assert (repr(associate('&', [(A & B), (B | C), (B & C)])) - == '(A & B & (B | C) & B & C)') - assert (repr(associate('|', [A | (B | (C | (A & B)))])) - == '(A | B | C | (A & B))') + assert repr(associate('&', [(A & B), (B | C), (B & C)])) == '(A & B & (B | C) & B & C)' + assert repr(associate('|', [A | (B | (C | (A & B)))])) == '(A | B | C | (A & B))' def test_move_not_inwards(): @@ -288,8 +285,8 @@ def test_entailment(s, has_and=False): def test_to_cnf(): - assert (repr(to_cnf(wumpus_world_inference & ~expr('~P12'))) == - '((~P12 | B11) & (~P21 | B11) & (P12 | P21 | ~B11) & ~B11 & P12)') + assert repr(to_cnf(wumpus_world_inference & ~expr('~P12'))) == \ + '((~P12 | B11) & (~P21 | B11) & (P12 | P21 | ~B11) & ~B11 & P12)' assert repr(to_cnf((P & Q) | (~P & ~Q))) == '((~P | P) & (~Q | P) & (~P | Q) & (~Q | Q))' assert repr(to_cnf('A <=> B')) == '((A | ~B) & (B | ~A))' assert repr(to_cnf('B <=> (P1 | P2)')) == '((~P1 | B) & (~P2 | B) & (P1 | P2 | ~B))' @@ -297,8 +294,8 @@ def test_to_cnf(): assert repr(to_cnf('a | (b & c) | d')) == '((b | a | d) & (c | a | d))' assert repr(to_cnf('A & (B | (D & E))')) == '(A & (D | B) & (E | B))' assert repr(to_cnf('A | (B | (C | (D & E)))')) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf( - '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == \ + '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -314,18 +311,15 @@ def test_pl_resolution(): def test_standardize_variables(): e = expr('F(a, b, c) & G(c, A, 23)') assert len(variables(standardize_variables(e))) == 3 - # assert variables(e).intersection(variables(standardize_variables(e))) == {} assert is_variable(standardize_variables(expr('x'))) def test_fol_bc_ask(): def test_ask(query, kb=None): q = expr(query) - test_variables = variables(q) answers = fol_bc_ask(kb or test_kb, q) - return sorted( - [dict((x, v) for x, v in list(a.items()) if x in test_variables) - for a in answers], key=repr) + return sorted([dict((x, v) for x, v in list(a.items()) if x in variables(q)) + for a in answers], key=repr) assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' @@ -336,11 +330,9 @@ def test_ask(query, kb=None): def test_fol_fc_ask(): def test_ask(query, kb=None): q = expr(query) - test_variables = variables(q) answers = fol_fc_ask(kb or test_kb, q) - return sorted( - [dict((x, v) for x, v in list(a.items()) if x in test_variables) - for a in answers], key=repr) + return sorted([dict((x, v) for x, v in list(a.items()) if x in variables(q)) + for a in answers], key=repr) assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' @@ -359,12 +351,12 @@ def check_SAT(clauses, single_solution=None): # Sometimes WalkSat may run out of flips before finding a solution if single_solution is None: single_solution = {} - soln = WalkSAT(clauses) - if soln: - assert all(pl_true(x, soln) for x in clauses) + sol = WalkSAT(clauses) + if sol: + assert all(pl_true(x, sol) for x in clauses) if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) - assert soln == single_solution + assert sol == single_solution # Test WalkSat for problems with solution check_SAT([A & B, A & C]) diff --git a/tests/test_perception4e.py b/tests/test_perception4e.py index ee5f12fd9..46d534523 100644 --- a/tests/test_perception4e.py +++ b/tests/test_perception4e.py @@ -40,7 +40,7 @@ def test_generate_edge_weight(): def test_graph_bfs(): graph = Graph(gray_scale_image) - assert graph.bfs((1, 1), (0, 0), []) == False + assert not graph.bfs((1, 1), (0, 0), []) parents = [] assert graph.bfs((0, 0), (2, 2), parents) assert len(parents) == 8 diff --git a/tests/test_reinforcement_learning4e.py b/tests/test_reinforcement_learning4e.py index 6cfb44e16..287ec397b 100644 --- a/tests/test_reinforcement_learning4e.py +++ b/tests/test_reinforcement_learning4e.py @@ -1,6 +1,6 @@ import pytest -from mdp import sequential_decision_environment +from mdp4e import sequential_decision_environment from reinforcement_learning4e import * random.seed("aima-python") diff --git a/tests/test_utils.py b/tests/test_utils.py index 6e2bdbcdd..e7a22b562 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -59,7 +59,7 @@ def test_first(): assert first('') is None assert first('', 'empty') == 'empty' assert first([1, 2, 3, 4, 5]) == 1 - assert first([]) == None + assert first([]) is None assert first(range(10)) == 0 assert first(x for x in range(10) if x > 3) == 4 assert first(x for x in range(10) if x > 100) is None @@ -81,27 +81,15 @@ def test_mode(): assert mode("artificialintelligence") == 'i' -def test_powerset(): - assert powerset([1, 2, 3]) == [(1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] - - -def test_argminmax(): - assert argmin([-2, 1], key=abs) == 1 - assert argmin(['one', 'to', 'three'], key=len) == 'to' - assert argmax([-2, 1], key=abs) == -2 - assert argmax(['one', 'to', 'three'], key=len) == 'three' +def test_power_set(): + assert power_set([1, 2, 3]) == [(1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] def test_histogram(): - assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1]) == [(1, 2), (2, 3), - (4, 2), (5, 1), - (7, 1), (9, 1)] - assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1], 0, lambda x: x * x) == [(1, 2), (4, 3), - (16, 2), (25, 1), - (49, 1), (81, 1)] - assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1], 1) == [(2, 3), (4, 2), - (1, 2), (9, 1), - (7, 1), (5, 1)] + assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1]) == [(1, 2), (2, 3), (4, 2), (5, 1), (7, 1), (9, 1)] + assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1], 0, lambda x: x * x) == \ + [(1, 2), (4, 3), (16, 2), (25, 1), (49, 1), (81, 1)] + assert histogram([1, 2, 4, 2, 4, 5, 7, 9, 2, 1], 1) == [(2, 3), (4, 2), (1, 2), (9, 1), (7, 1), (5, 1)] def test_euclidean(): @@ -163,62 +151,17 @@ def test_dot_product(): assert dot_product([1, 2, 3], [0, 0, 0]) == 0 -def test_element_wise_product(): - assert element_wise_product([1, 2, 5], [7, 10, 0]) == [7, 20, 0] - assert element_wise_product([1, 6, 3, 0], [9, 12, 0, 0]) == [9, 72, 0, 0] - - -def test_matrix_multiplication(): - assert matrix_multiplication([[1, 2, 3], - [2, 3, 4]], - [[3, 4], - [1, 2], - [1, 0]]) == [[8, 8], [13, 14]] - - assert matrix_multiplication([[1, 2, 3], - [2, 3, 4]], - [[3, 4, 8, 1], - [1, 2, 5, 0], - [1, 0, 0, 3]], - [[1, 2], - [3, 4], - [5, 6], - [1, 2]]) == [[132, 176], [224, 296]] - - -def test_vector_to_diagonal(): - assert vector_to_diagonal([1, 2, 3]) == [[1, 0, 0], [0, 2, 0], [0, 0, 3]] - assert vector_to_diagonal([0, 3, 6]) == [[0, 0, 0], [0, 3, 0], [0, 0, 6]] - - def test_vector_add(): assert vector_add((0, 1), (8, 9)) == (8, 10) assert vector_add((1, 1, 1), (2, 2, 2)) == (3, 3, 3) -def test_scalar_vector_product(): - assert scalar_vector_product(2, [1, 2, 3]) == [2, 4, 6] - assert scalar_vector_product(0, [9, 9, 9]) == [0, 0, 0] - - -def test_scalar_matrix_product(): - assert rounder(scalar_matrix_product(-5, [[1, 2], [3, 4], [0, 6]])) == [[-5, -10], [-15, -20], [0, -30]] - assert rounder(scalar_matrix_product(0.2, [[1, 2], [2, 3]])) == [[0.2, 0.4], [0.4, 0.6]] - - -def test_inverse_matrix(): - assert rounder(inverse_matrix([[1, 0], [0, 1]])) == [[1, 0], [0, 1]] - assert rounder(inverse_matrix([[2, 1], [4, 3]])) == [[1.5, -0.5], [-2.0, 1.0]] - assert rounder(inverse_matrix([[4, 7], [2, 6]])) == [[0.6, -0.7], [-0.2, 0.4]] - - def test_rounder(): assert rounder(5.3330000300330) == 5.3330 assert rounder(10.234566) == 10.2346 assert rounder([1.234566, 0.555555, 6.010101]) == [1.2346, 0.5556, 6.0101] assert rounder([[1.234566, 0.555555, 6.010101], - [10.505050, 12.121212, 6.030303]]) == [[1.2346, 0.5556, 6.0101], - [10.5051, 12.1212, 6.0303]] + [10.505050, 12.121212, 6.030303]]) == [[1.2346, 0.5556, 6.0101], [10.5051, 12.1212, 6.0303]] def test_num_or_str(): @@ -230,64 +173,16 @@ def test_normalize(): assert normalize([1, 2, 1]) == [0.25, 0.5, 0.25] -def test_norm(): - assert isclose(norm([1, 2, 1], 1), 4) - assert isclose(norm([3, 4], 2), 5) - assert isclose(norm([-1, 1, 2], 4), 18 ** 0.25) - - def test_clip(): assert [clip(x, 0, 1) for x in [-1, 0.5, 10]] == [0, 0.5, 1] -def test_sigmoid(): - assert isclose(0.5, sigmoid(0)) - assert isclose(0.7310585786300049, sigmoid(1)) - assert isclose(0.2689414213699951, sigmoid(-1)) - - def test_gaussian(): assert gaussian(1, 0.5, 0.7) == 0.6664492057835993 assert gaussian(5, 2, 4.5) == 0.19333405840142462 assert gaussian(3, 1, 3) == 0.3989422804014327 -def test_sigmoid_derivative(): - value = 1 - assert sigmoid_derivative(value) == 0 - - value = 3 - assert sigmoid_derivative(value) == -6 - - -def test_truncated_svd(): - test_mat = [[17, 0], - [0, 11]] - _, _, eival = truncated_svd(test_mat) - assert isclose(eival[0], 17) - assert isclose(eival[1], 11) - - test_mat = [[17, 0], - [0, -34]] - _, _, eival = truncated_svd(test_mat) - assert isclose(eival[0], 34) - assert isclose(eival[1], 17) - - test_mat = [[1, 0, 0, 0, 2], - [0, 0, 3, 0, 0], - [0, 0, 0, 0, 0], - [0, 2, 0, 0, 0]] - _, _, eival = truncated_svd(test_mat) - assert isclose(eival[0], 3) - assert isclose(eival[1], 5 ** 0.5) - - test_mat = [[3, 2, 2], - [2, 3, -2]] - _, _, eival = truncated_svd(test_mat) - assert isclose(eival[0], 5) - assert isclose(eival[1], 3) - - def test_weighted_choice(): choices = [('a', 0.5), ('b', 0.3), ('c', 0.2)] choice = weighted_choice(choices) diff --git a/text.py b/text.py index bf1809f96..58918bb4d 100644 --- a/text.py +++ b/text.py @@ -1,10 +1,13 @@ -"""Statistical Language Processing tools. (Chapter 22) +""" +Statistical Language Processing tools. (Chapter 22) + We define Unigram and Ngram text models, use them to generate random text, -and show the Viterbi algorithm for segmentatioon of letters into words. +and show the Viterbi algorithm for segmentation 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.""" +working on a tiny sample of Unix manual pages. +""" -from utils import argmin, argmax, hashabledict +from utils import hashabledict from probabilistic_learning import CountingProbDist import search @@ -152,8 +155,7 @@ def index_collection(self, filenames): """Index a whole collection of files.""" prefix = os.path.dirname(__file__) for filename in filenames: - self.index_document(open(filename).read(), - os.path.relpath(filename, prefix)) + self.index_document(open(filename).read(), os.path.relpath(filename, prefix)) def index_document(self, text, url): """Index the text of a document.""" @@ -175,15 +177,14 @@ def query(self, query_text, n=10): return [] qwords = [w for w in words(query_text) if w not in self.stopwords] - shortest = argmin(qwords, key=lambda w: len(self.index[w])) + shortest = min(qwords, key=lambda w: len(self.index[w])) docids = self.index[shortest] return heapq.nlargest(n, ((self.total_score(qwords, docid), docid) for docid in docids)) def score(self, word, docid): """Compute a score for this word on the document with this docid.""" # There are many options; here we take a very simple approach - return (log(1 + self.index[word][docid]) / - log(1 + self.documents[docid].nwords)) + return log(1 + self.index[word][docid]) / log(1 + self.documents[docid].nwords) def total_score(self, words, docid): """Compute the sum of the scores of these words on the document with this docid.""" @@ -193,9 +194,7 @@ def present(self, results): """Present the results as a list.""" for (score, docid) in results: doc = self.documents[docid] - print( - ("{:5.2}|{:25} | {}".format(100 * score, doc.url, - doc.title[:45].expandtabs()))) + print("{:5.2}|{:25} | {}".format(100 * score, doc.url, doc.title[:45].expandtabs())) def present_results(self, query_text, n=10): """Get results for the query and present them.""" @@ -211,8 +210,7 @@ def __init__(self): import os aima_root = os.path.dirname(__file__) mandir = os.path.join(aima_root, 'aima-data/MAN/') - man_files = [mandir + f for f in os.listdir(mandir) - if f.endswith('.txt')] + man_files = [mandir + f for f in os.listdir(mandir) if f.endswith('.txt')] self.index_collection(man_files) @@ -332,7 +330,7 @@ def score(self, plaintext): def decode(self, ciphertext): """Return the shift decoding of text with the best score.""" - return argmax(all_shifts(ciphertext), key=lambda shift: self.score(shift)) + return max(all_shifts(ciphertext), key=lambda shift: self.score(shift)) def all_shifts(text): @@ -396,16 +394,16 @@ def score(self, code): class PermutationDecoderProblem(search.Problem): def __init__(self, initial=None, goal=None, decoder=None): - self.initial = initial or hashabledict() + super().__init__(initial or hashabledict(), goal) self.decoder = decoder def actions(self, state): search_list = [c for c in self.decoder.chardomain if c not in state] target_list = [c for c in alphabet if c not in state.values()] - # Find the best charater to replace - plainchar = argmax(search_list, key=lambda c: self.decoder.P1[c]) - for cipherchar in target_list: - yield (plainchar, cipherchar) + # Find the best character to replace + plain_char = max(search_list, key=lambda c: self.decoder.P1[c]) + for cipher_char in target_list: + yield (plain_char, cipher_char) def result(self, state, action): new_state = hashabledict(state) # copy to prevent hash issues diff --git a/utils.py b/utils.py index 9576108cf..04fbd303c 100644 --- a/utils.py +++ b/utils.py @@ -3,18 +3,21 @@ import bisect import collections import collections.abc +import functools import heapq +import math import operator import os.path import random -import math -import functools +from itertools import chain, combinations from statistics import mean import numpy as np -from itertools import chain, combinations -inf = float('inf') +try: # math.inf was added in Python 3.5 + from math import inf +except ImportError: # Python 3.4 + inf = float('inf') # ______________________________________________________________________________ @@ -87,17 +90,20 @@ def mode(data): return item -def powerset(iterable): - """powerset([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)""" +def power_set(iterable): + """power_set([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)""" s = list(iterable) return list(chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)))[1:] def extend(s, var, val): """Copy dict s and extend it by setting var to val; return copy.""" - s2 = s.copy() - s2[var] = val - return s2 + try: # Python 3.5 and later + return eval('{**s, var: val}') + except SyntaxError: # Python 3.4 + s2 = s.copy() + s2[var] = val + return s2 # ______________________________________________________________________________ @@ -105,18 +111,15 @@ def extend(s, var, val): identity = lambda x: x -argmin = min -argmax = max - def argmin_random_tie(seq, key=identity): """Return a minimum element of seq; break ties at random.""" - return argmin(shuffled(seq), key=key) + return min(shuffled(seq), key=key) def argmax_random_tie(seq, key=identity): """Return an element with highest fn(seq[i]) score; break ties at random.""" - return argmax(shuffled(seq), key=key) + return max(shuffled(seq), key=key) def shuffled(iterable): @@ -147,74 +150,35 @@ def histogram(values, mode=0, bin_function=None): return sorted(bins.items()) -def dot_product(X, Y): - """Return the sum of the element-wise product of vectors X and Y.""" - return sum(x * y for x, y in zip(X, Y)) +def dot_product(x, y): + """Return the sum of the element-wise product of vectors x and y.""" + return sum(_x * _y for _x, _y in zip(x, y)) -def element_wise_product(X, Y): - """Return vector as an element-wise product of vectors X and Y""" - assert len(X) == len(Y) - return [x * y for x, y in zip(X, Y)] +def element_wise_product(x, y): + """Return vector as an element-wise product of vectors x and y.""" + assert len(x) == len(y) + return np.multiply(x, y) -def matrix_multiplication(X_M, *Y_M): - """Return a matrix as a matrix-multiplication of X_M and arbitrary number of matrices *Y_M""" +def matrix_multiplication(x, *y): + """Return a matrix as a matrix-multiplication of x and arbitrary number of matrices *y.""" - def _mat_mult(X_M, Y_M): - """Return a matrix as a matrix-multiplication of two matrices X_M and Y_M - >>> matrix_multiplication([[1, 2, 3], [2, 3, 4]], [[3, 4], [1, 2], [1, 0]]) - [[8, 8],[13, 14]] - """ - assert len(X_M[0]) == len(Y_M) - - result = [[0 for i in range(len(Y_M[0]))] for _ in range(len(X_M))] - for i in range(len(X_M)): - for j in range(len(Y_M[0])): - for k in range(len(Y_M)): - result[i][j] += X_M[i][k] * Y_M[k][j] - return result - - result = X_M - for Y in Y_M: - result = _mat_mult(result, Y) + result = x + for _y in y: + result = np.matmul(result, _y) return result -def vector_to_diagonal(v): - """Converts a vector to a diagonal matrix with vector elements - as the diagonal elements of the matrix""" - diag_matrix = [[0 for i in range(len(v))] for _ in range(len(v))] - for i in range(len(v)): - diag_matrix[i][i] = v[i] - - return diag_matrix - - def vector_add(a, b): """Component-wise addition of two vectors.""" return tuple(map(operator.add, a, b)) -def scalar_vector_product(X, Y): +def scalar_vector_product(x, y): """Return vector as a product of a scalar and a vector""" - return [X * y for y in Y] - - -def scalar_matrix_product(X, Y): - """Return matrix as a product of a scalar and a matrix""" - return [scalar_vector_product(X, y) for y in Y] - - -def inverse_matrix(X): - """Inverse a given square matrix of size 2x2""" - assert len(X) == 2 - assert len(X[0]) == 2 - det = X[0][0] * X[1][1] - X[0][1] * X[1][0] - assert det != 0 - inv_mat = scalar_matrix_product(1.0 / det, [[X[1][1], -X[0][1]], [-X[1][0], X[0][0]]]) - return inv_mat + return np.multiply(x, y) def probability(p): @@ -271,37 +235,36 @@ def num_or_str(x): # TODO: rename as `atom` return str(x).strip() -def euclidean_distance(X, Y): - return math.sqrt(sum((x - y) ** 2 for x, y in zip(X, Y))) +def euclidean_distance(x, y): + return math.sqrt(sum((_x - _y) ** 2 for _x, _y in zip(x, y))) -def cross_entropy_loss(X, Y): - n = len(X) - return (-1.0 / n) * sum(x * math.log(y) + (1 - x) * math.log(1 - y) for x, y in zip(X, Y)) +def cross_entropy_loss(x, y): + return (-1.0 / len(x)) * sum(x * math.log(y) + (1 - x) * math.log(1 - y) for x, y in zip(x, y)) -def rms_error(X, Y): - return math.sqrt(ms_error(X, Y)) +def rms_error(x, y): + return math.sqrt(ms_error(x, y)) -def ms_error(X, Y): - return mean((x - y) ** 2 for x, y in zip(X, Y)) +def ms_error(x, y): + return mean((x - y) ** 2 for x, y in zip(x, y)) -def mean_error(X, Y): - return mean(abs(x - y) for x, y in zip(X, Y)) +def mean_error(x, y): + return mean(abs(x - y) for x, y in zip(x, y)) -def manhattan_distance(X, Y): - return sum(abs(x - y) for x, y in zip(X, Y)) +def manhattan_distance(x, y): + return sum(abs(_x - _y) for _x, _y in zip(x, y)) -def mean_boolean_error(X, Y): - return mean(x != y for x, y in zip(X, Y)) +def mean_boolean_error(x, y): + return mean(_x != _y for _x, _y in zip(x, y)) -def hamming_distance(X, Y): - return sum(x != y for x, y in zip(X, Y)) +def hamming_distance(x, y): + return sum(_x != _y for _x, _y in zip(x, y)) def normalize(dist): @@ -310,15 +273,15 @@ def normalize(dist): total = sum(dist.values()) for key in dist: dist[key] = dist[key] / total - assert 0 <= dist[key] <= 1 # Probabilities must be between 0 and 1 + assert 0 <= dist[key] <= 1 # probabilities must be between 0 and 1 return dist total = sum(dist) return [(n / total) for n in dist] -def norm(X, n=2): - """Return the n-norm of vector X""" - return sum([x ** n for x in X]) ** (1 / n) +def norm(x, ord=2): + """Return the n-norm of vector x.""" + return np.linalg.norm(x, ord) def random_weights(min_value, max_value, num_weights): @@ -335,17 +298,10 @@ def sigmoid_derivative(value): def sigmoid(x): - """Return activation value of x with sigmoid function""" + """Return activation value of x with sigmoid function.""" return 1 / (1 + math.exp(-x)) -def relu_derivative(value): - if value > 0: - return 1 - else: - return 0 - - def elu(x, alpha=0.01): return x if x > 0 else alpha * (math.exp(x) - 1) @@ -388,78 +344,35 @@ def gaussian(mean, st_dev, x): return 1 / (math.sqrt(2 * math.pi) * st_dev) * math.e ** (-0.5 * (float(x - mean) / st_dev) ** 2) -try: # math.isclose was added in Python 3.5; but we might be in 3.4 +def linear_kernel(x, y=None): + if y is None: + y = x + return np.dot(x, y.T) + + +def polynomial_kernel(x, y=None, degree=2.0): + if y is None: + y = x + return (1.0 + np.dot(x, y.T)) ** degree + + +def rbf_kernel(x, y=None, gamma=None): + """Radial-basis function kernel (aka squared-exponential kernel).""" + if y is None: + y = x + if gamma is None: + gamma = 1.0 / x.shape[1] # 1.0 / n_features + return np.exp(-gamma * (-2.0 * np.dot(x, y.T) + + np.sum(x * x, axis=1).reshape((-1, 1)) + np.sum(y * y, axis=1).reshape((1, -1)))) + + +try: # math.isclose was added in Python 3.5 from math import isclose -except ImportError: +except ImportError: # Python 3.4 def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): """Return true if numbers a and b are close to each other.""" return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) - -def truncated_svd(X, num_val=2, max_iter=1000): - """Compute the first component of SVD.""" - - def normalize_vec(X, n=2): - """Normalize two parts (:m and m:) of the vector.""" - X_m = X[:m] - X_n = X[m:] - norm_X_m = norm(X_m, n) - Y_m = [x / norm_X_m for x in X_m] - norm_X_n = norm(X_n, n) - Y_n = [x / norm_X_n for x in X_n] - return Y_m + Y_n - - def remove_component(X): - """Remove components of already obtained eigen vectors from X.""" - X_m = X[:m] - X_n = X[m:] - for eivec in eivec_m: - coeff = dot_product(X_m, eivec) - X_m = [x1 - coeff * x2 for x1, x2 in zip(X_m, eivec)] - for eivec in eivec_n: - coeff = dot_product(X_n, eivec) - X_n = [x1 - coeff * x2 for x1, x2 in zip(X_n, eivec)] - return X_m + X_n - - m, n = len(X), len(X[0]) - A = [[0] * (n + m) for _ in range(n + m)] - for i in range(m): - for j in range(n): - A[i][m + j] = A[m + j][i] = X[i][j] - - eivec_m = [] - eivec_n = [] - eivals = [] - - for _ in range(num_val): - X = [random.random() for _ in range(m + n)] - X = remove_component(X) - X = normalize_vec(X) - - for i in range(max_iter): - old_X = X - X = matrix_multiplication(A, [[x] for x in X]) - X = [x[0] for x in X] - X = remove_component(X) - X = normalize_vec(X) - # check for convergence - if norm([x1 - x2 for x1, x2 in zip(old_X, X)]) <= 1e-10: - break - - projected_X = matrix_multiplication(A, [[x] for x in X]) - projected_X = [x[0] for x in projected_X] - new_eigenvalue = norm(projected_X, 1) / norm(X, 1) - ev_m = X[:m] - ev_n = X[m:] - if new_eigenvalue < 0: - new_eigenvalue = -new_eigenvalue - ev_m = [-ev_m_i for ev_m_i in ev_m] - eivals.append(new_eigenvalue) - eivec_m.append(ev_m) - eivec_n.append(ev_n) - return eivec_m, eivec_n, eivals - - # ______________________________________________________________________________ # Grid Functions @@ -708,7 +621,7 @@ def __rmatmul__(self, lhs): def __call__(self, *args): """Call: if 'f' is a Symbol, then f(0) == Expr('f', 0).""" if self.args: - raise ValueError('can only do a call for a Symbol, not an Expr') + raise ValueError('Can only do a call for a Symbol, not an Expr') else: return Expr(self.op, *args) @@ -821,9 +734,8 @@ def __missing__(self, key): class hashabledict(dict): - """Allows hashing by representing a dictionary as tuple of key:value pairs - May cause problems as the hash value may change during runtime - """ + """Allows hashing by representing a dictionary as tuple of key:value pairs. + May cause problems as the hash value may change during runtime.""" def __hash__(self): return 1 @@ -849,7 +761,7 @@ def __init__(self, order='min', f=lambda x: x): elif order == 'max': # now item with max f(x) self.f = lambda x: -f(x) # will be popped first else: - raise ValueError("order must be either 'min' or 'max'.") + raise ValueError("Order must be either 'min' or 'max'.") def append(self, item): """Insert item at its correct position.""" @@ -898,7 +810,7 @@ def __delitem__(self, key): class Bool(int): - """Just like `bool`, except values display as 'T' and 'F' instead of 'True' and 'False'""" + """Just like `bool`, except values display as 'T' and 'F' instead of 'True' and 'False'.""" __str__ = __repr__ = lambda self: 'T' if self else 'F' diff --git a/utils4e.py b/utils4e.py index d23d168e5..3aec273f8 100644 --- a/utils4e.py +++ b/utils4e.py @@ -13,7 +13,10 @@ import numpy as np -inf = float('inf') +try: # math.inf was added in Python 3.5 + from math import inf +except ImportError: # Python 3.4 + inf = float('inf') # part1. General data structures and their functions @@ -37,7 +40,7 @@ def __init__(self, order='min', f=lambda x: x): elif order == 'max': # now item with max f(x) self.f = lambda x: -f(x) # will be popped first else: - raise ValueError("order must be either 'min' or 'max'.") + raise ValueError("Order must be either 'min' or 'max'.") def append(self, item): """Insert item at its correct position.""" @@ -148,17 +151,20 @@ def mode(data): return item -def powerset(iterable): - """powerset([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)""" +def power_set(iterable): + """power_set([1,2,3]) --> (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)""" s = list(iterable) return list(chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)))[1:] def extend(s, var, val): """Copy dict s and extend it by setting var to val; return copy.""" - s2 = s.copy() - s2[var] = val - return s2 + try: # Python 3.5 and later + return eval('{**s, var: val}') + except SyntaxError: # Python 3.4 + s2 = s.copy() + s2[var] = val + return s2 # ______________________________________________________________________________ @@ -166,18 +172,15 @@ def extend(s, var, val): identity = lambda x: x -argmin = min -argmax = max - def argmin_random_tie(seq, key=identity): """Return a minimum element of seq; break ties at random.""" - return argmin(shuffled(seq), key=key) + return min(shuffled(seq), key=key) def argmax_random_tie(seq, key=identity): """Return an element with highest fn(seq[i]) score; break ties at random.""" - return argmax(shuffled(seq), key=key) + return max(shuffled(seq), key=key) def shuffled(iterable): @@ -208,64 +211,31 @@ def histogram(values, mode=0, bin_function=None): return sorted(bins.items()) -def dot_product(X, Y): - """Return the sum of the element-wise product of vectors X and Y.""" - return sum(x * y for x, y in zip(X, Y)) - - -def element_wise_product_2D(X, Y): - """Return vector as an element-wise product of vectors X and Y""" - assert len(X) == len(Y) - return [x * y for x, y in zip(X, Y)] +def dot_product(x, y): + """Return the sum of the element-wise product of vectors x and y.""" + return sum(_x * _y for _x, _y in zip(x, y)) -def element_wise_product(X, Y): - if hasattr(X, '__iter__') and hasattr(Y, '__iter__'): - assert len(X) == len(Y) - return [element_wise_product(x, y) for x, y in zip(X, Y)] - elif hasattr(X, '__iter__') == hasattr(Y, '__iter__'): - return X * Y +def element_wise_product(x, y): + if hasattr(x, '__iter__') and hasattr(y, '__iter__'): + assert len(x) == len(y) + return [element_wise_product(_x, _y) for _x, _y in zip(x, y)] + elif hasattr(x, '__iter__') == hasattr(y, '__iter__'): + return x * y else: - raise Exception("Inputs must be in the same size!") - - -def transpose2D(M): - return list(map(list, zip(*M))) - + raise Exception('Inputs must be in the same size!') -def matrix_multiplication(X_M, *Y_M): - """Return a matrix as a matrix-multiplication of X_M and arbitrary number of matrices *Y_M""" - def _mat_mult(X_M, Y_M): - """Return a matrix as a matrix-multiplication of two matrices X_M and Y_M - >>> matrix_multiplication([[1, 2, 3], [2, 3, 4]], [[3, 4], [1, 2], [1, 0]]) - [[8, 8],[13, 14]] - """ - assert len(X_M[0]) == len(Y_M) - result = [[0 for i in range(len(Y_M[0]))] for j in range(len(X_M))] - for i in range(len(X_M)): - for j in range(len(Y_M[0])): - for k in range(len(Y_M)): - result[i][j] += X_M[i][k] * Y_M[k][j] - return result +def matrix_multiplication(x, *y): + """Return a matrix as a matrix-multiplication of x and arbitrary number of matrices *y.""" - result = X_M - for Y in Y_M: - result = _mat_mult(result, Y) + result = x + for _y in y: + result = np.matmul(result, _y) return result -def vector_to_diagonal(v): - """Converts a vector to a diagonal matrix with vector elements - as the diagonal elements of the matrix""" - diag_matrix = [[0 for i in range(len(v))] for j in range(len(v))] - for i in range(len(v)): - diag_matrix[i][i] = v[i] - - return diag_matrix - - def vector_add(a, b): """Component-wise addition of two vectors.""" if not (a and b): @@ -277,33 +247,17 @@ def vector_add(a, b): try: return a + b except TypeError: - raise Exception("Inputs must be in the same size!") - - -def scalar_vector_product(X, Y): - """Return vector as a product of a scalar and a vector recursively""" - return [scalar_vector_product(X, y) for y in Y] if hasattr(Y, '__iter__') else X * Y + raise Exception('Inputs must be in the same size!') -def map_vector(f, X): - """apply function f to iterable X""" - return [map_vector(f, x) for x in X] if hasattr(X, '__iter__') else list(map(f, [X]))[0] +def scalar_vector_product(x, y): + """Return vector as a product of a scalar and a vector recursively.""" + return [scalar_vector_product(x, _y) for _y in y] if hasattr(y, '__iter__') else x * y -def scalar_matrix_product(X, Y): - """Return matrix as a product of a scalar and a matrix""" - return [scalar_vector_product(X, y) for y in Y] - - -def inverse_matrix(X): - """Inverse a given square matrix of size 2x2""" - assert len(X) == 2 - assert len(X[0]) == 2 - det = X[0][0] * X[1][1] - X[0][1] * X[1][0] - assert det != 0 - inv_mat = scalar_matrix_product(1.0 / det, [[X[1][1], -X[0][1]], [-X[1][0], X[0][0]]]) - - return inv_mat +def map_vector(f, x): + """Apply function f to iterable x.""" + return [map_vector(f, _x) for _x in x] if hasattr(x, '__iter__') else list(map(f, [x]))[0] def probability(p): @@ -363,47 +317,45 @@ def num_or_str(x): # TODO: rename as `atom` return str(x).strip() -def euclidean_distance(X, Y): - return math.sqrt(sum((x - y) ** 2 for x, y in zip(X, Y) if x and y)) +def euclidean_distance(x, y): + return math.sqrt(sum((_x - _y) ** 2 for _x, _y in zip(x, y))) -def rms_error(X, Y): - return math.sqrt(ms_error(X, Y)) +def rms_error(x, y): + return math.sqrt(ms_error(x, y)) -def ms_error(X, Y): - return mean((x - y) ** 2 for x, y in zip(X, Y)) +def ms_error(x, y): + return mean((x - y) ** 2 for x, y in zip(x, y)) -def mean_error(X, Y): - return mean(abs(x - y) for x, y in zip(X, Y)) +def mean_error(x, y): + return mean(abs(x - y) for x, y in zip(x, y)) -def manhattan_distance(X, Y): - return sum(abs(x - y) for x, y in zip(X, Y)) +def manhattan_distance(x, y): + return sum(abs(_x - _y) for _x, _y in zip(x, y)) -def mean_boolean_error(X, Y): - return mean(int(x != y) for x, y in zip(X, Y)) +def mean_boolean_error(x, y): + return mean(_x != _y for _x, _y in zip(x, y)) -def hamming_distance(X, Y): - return sum(x != y for x, y in zip(X, Y)) +def hamming_distance(x, y): + return sum(_x != _y for _x, _y in zip(x, y)) # 19.2 Common Loss Functions -def cross_entropy_loss(X, Y): - """Example of cross entropy loss. X and Y are 1D iterable objects""" - n = len(X) - return (-1.0 / n) * sum(x * math.log(y) + (1 - x) * math.log(1 - y) for x, y in zip(X, Y)) +def cross_entropy_loss(x, y): + """Example of cross entropy loss. x and y are 1D iterable objects.""" + return (-1.0 / len(x)) * sum(x * math.log(y) + (1 - x) * math.log(1 - y) for x, y in zip(x, y)) -def mse_loss(X, Y): - """Example of min square loss. X and Y are 1D iterable objects""" - n = len(X) - return (1.0 / n) * sum((x - y) ** 2 for x, y in zip(X, Y)) +def mse_loss(x, y): + """Example of min square loss. x and y are 1D iterable objects.""" + return (1.0 / len(x)) * sum((_x - _y) ** 2 for _x, _y in zip(x, y)) # part3. Neural network util functions @@ -416,38 +368,35 @@ def normalize(dist): total = sum(dist.values()) for key in dist: dist[key] = dist[key] / total - assert 0 <= dist[key] <= 1, "Probabilities must be between 0 and 1." + assert 0 <= dist[key] <= 1 # probabilities must be between 0 and 1 return dist total = sum(dist) return [(n / total) for n in dist] -def norm(X, n=2): - """Return the n-norm of vector X""" - return sum([x ** n for x in X]) ** (1 / n) +def norm(x, ord=2): + """Return the n-norm of vector x.""" + return np.linalg.norm(x, ord) def random_weights(min_value, max_value, num_weights): return [random.uniform(min_value, max_value) for _ in range(num_weights)] -def conv1D(X, K): - """1D convolution. X: input vector; K: kernel vector""" - return np.convolve(X, K, mode='same') +def conv1D(x, k): + """1D convolution. x: input vector; K: kernel vector.""" + return np.convolve(x, k, mode='same') -def GaussianKernel(size=3): - mean = (size - 1) / 2 - stdev = 0.1 - return [gaussian(mean, stdev, x) for x in range(size)] +def gaussian_kernel(size=3): + return [gaussian((size - 1) / 2, 0.1, x) for x in range(size)] -def gaussian_kernel_1d(size=3, sigma=0.5): - mean = (size - 1) / 2 - return [gaussian(mean, sigma, x) for x in range(size)] +def gaussian_kernel_1D(size=3, sigma=0.5): + return [gaussian((size - 1) / 2, sigma, x) for x in range(size)] -def gaussian_kernel_2d(size=3, sigma=0.5): +def gaussian_kernel_2D(size=3, sigma=0.5): x, y = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1] g = np.exp(-((x ** 2 + y ** 2) / (2.0 * sigma ** 2))) return g / g.sum() @@ -468,9 +417,9 @@ def clip(x, lowest, highest): return max(lowest, min(x, highest)) -def softmax1D(Z): - """Return the softmax vector of input vector Z""" - exps = [math.exp(z) for z in Z] +def softmax1D(x): + """Return the softmax vector of input vector x.""" + exps = [math.exp(_x) for _x in x] sum_exps = sum(exps) return [exp / sum_exps for exp in exps] @@ -525,7 +474,7 @@ def derivative(self, value, alpha=0.01): def step(x): - """Return activation value of x with sign function""" + """Return activation value of x with sign function.""" return 1 if x >= 0 else 0 @@ -536,16 +485,38 @@ def gaussian(mean, st_dev, x): def gaussian_2D(means, sigma, point): det = sigma[0][0] * sigma[1][1] - sigma[0][1] * sigma[1][0] - inverse = inverse_matrix(sigma) + inverse = np.linalg.inv(sigma) assert det != 0 x_u = vector_add(point, scalar_vector_product(-1, means)) - buff = matrix_multiplication(matrix_multiplication([x_u], inverse), transpose2D([x_u])) + buff = matrix_multiplication(matrix_multiplication([x_u], inverse), np.array(x_u).T) return 1 / (math.sqrt(det) * 2 * math.pi) * math.exp(-0.5 * buff[0][0]) -try: # math.isclose was added in Python 3.5; but we might be in 3.4 +def linear_kernel(x, y=None): + if y is None: + y = x + return np.dot(x, y.T) + + +def polynomial_kernel(x, y=None, degree=2.0): + if y is None: + y = x + return (1.0 + np.dot(x, y.T)) ** degree + + +def rbf_kernel(x, y=None, gamma=None): + """Radial-basis function kernel (aka squared-exponential kernel).""" + if y is None: + y = x + if gamma is None: + gamma = 1.0 / x.shape[1] # 1.0 / n_features + return np.exp(-gamma * (-2.0 * np.dot(x, y.T) + + np.sum(x * x, axis=1).reshape((-1, 1)) + np.sum(y * y, axis=1).reshape((1, -1)))) + + +try: # math.isclose was added in Python 3.5 from math import isclose -except ImportError: +except ImportError: # Python 3.4 def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): """Return true if numbers a and b are close to each other.""" return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) @@ -801,7 +772,7 @@ def __rmatmul__(self, lhs): def __call__(self, *args): """Call: if 'f' is a Symbol, then f(0) == Expr('f', 0).""" if self.args: - raise ValueError('can only do a call for a Symbol, not an Expr') + raise ValueError('Can only do a call for a Symbol, not an Expr') else: return Expr(self.op, *args) @@ -917,9 +888,8 @@ def __missing__(self, key): class hashabledict(dict): - """Allows hashing by representing a dictionary as tuple of key:value pairs - May cause problems as the hash value may change during runtime - """ + """Allows hashing by representing a dictionary as tuple of key:value pairs. + May cause problems as the hash value may change during runtime.""" def __hash__(self): return 1 @@ -928,7 +898,7 @@ def __hash__(self): # ______________________________________________________________________________ # Monte Carlo tree node and ucb function class MCT_Node: - """Node in the Monte Carlo search tree, keeps track of the children states""" + """Node in the Monte Carlo search tree, keeps track of the children states.""" def __init__(self, parent=None, state=None, U=0, N=0): self.__dict__.update(parent=parent, state=state, U=U, N=N) @@ -945,7 +915,7 @@ def ucb(n, C=1.4): class Bool(int): - """Just like `bool`, except values display as 'T' and 'F' instead of 'True' and 'False'""" + """Just like `bool`, except values display as 'T' and 'F' instead of 'True' and 'False'.""" __str__ = __repr__ = lambda self: 'T' if self else 'F'