diff --git a/DeepNeuralNet4e.py b/DeepNeuralNet4e.py index b68192ba8..4f9f48e4f 100644 --- a/DeepNeuralNet4e.py +++ b/DeepNeuralNet4e.py @@ -209,7 +209,7 @@ def init_examples(examples, idx_i, idx_t, o_units): # 19.4.1 Stochastic gradient descent -def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01, batch_size=1): +def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01, batch_size=1, verbose=None): """ gradient descent algorithm to update the learnable parameters of a network. :return: the updated network. @@ -236,7 +236,7 @@ def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01, batch_size=1 for j in range(len(weights[i])): net[i].nodes[j].weights = weights[i][j] - if (e+1) % 10 == 0: + if verbose and (e+1) % verbose == 0: print("epoch:{}, total_loss:{}".format(e+1,total_loss)) return net @@ -244,7 +244,7 @@ def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01, batch_size=1 # 19.4.2 Other gradient-based optimization algorithms -def adam_optimizer(dataset, net, loss, epochs=1000, rho=(0.9, 0.999), delta=1/10**8, l_rate=0.001, batch_size=1): +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): """ Adam optimizer in Figure 19.6 to update the learnable parameters of a network. Required parameters are similar to gradient descent. @@ -288,7 +288,7 @@ def adam_optimizer(dataset, net, loss, epochs=1000, rho=(0.9, 0.999), delta=1/1 for j in range(len(weights[i])): net[i].nodes[j].weights = weights[i][j] - if (e+1) % 10 == 0: + if verbose and (e+1) % verbose == 0: print("epoch:{}, total_loss:{}".format(e+1,total_loss)) return net @@ -382,7 +382,7 @@ def get_batch(examples, batch_size=1): # example of NNs -def neural_net_learner(dataset, hidden_layer_sizes=[4], learning_rate=0.01, epochs=100, optimizer=gradient_descent, batch_size=1): +def neural_net_learner(dataset, hidden_layer_sizes=[4], learning_rate=0.01, epochs=100, optimizer=gradient_descent, batch_size=1, verbose=None): """Example of a simple dense multilayer neural network. :param hidden_layer_sizes: size of hidden layers in the form of a list""" @@ -399,7 +399,7 @@ def neural_net_learner(dataset, hidden_layer_sizes=[4], learning_rate=0.01, epoc raw_net.append(DenseLayer(hidden_input_size, output_size)) # update parameters of the network - learned_net = optimizer(dataset, raw_net, mse_loss, epochs, l_rate=learning_rate, batch_size=batch_size) + learned_net = optimizer(dataset, raw_net, mse_loss, epochs, l_rate=learning_rate, batch_size=batch_size, verbose=verbose) def predict(example): n_layers = len(learned_net) @@ -417,7 +417,7 @@ def predict(example): return predict -def perceptron_learner(dataset, learning_rate=0.01, epochs=100): +def perceptron_learner(dataset, learning_rate=0.01, epochs=100, verbose=None): """ Example of a simple perceptron neural network. """ @@ -427,7 +427,7 @@ def perceptron_learner(dataset, learning_rate=0.01, epochs=100): # initialize the network, add dense layer raw_net = [InputLayer(input_size), DenseLayer(input_size, output_size)] # update the network - learned_net = gradient_descent(dataset, raw_net, mse_loss, epochs, l_rate=learning_rate) + learned_net = gradient_descent(dataset, raw_net, mse_loss, epochs, l_rate=learning_rate, verbose=verbose) def predict(example): diff --git a/learning4e.py b/learning4e.py index 68a2d5c48..6b1b7140d 100644 --- a/learning4e.py +++ b/learning4e.py @@ -1,6 +1,6 @@ from utils4e import ( removeall, unique, mode, argmax_random_tie, isclose, dotproduct, weighted_sample_with_replacement, - num_or_str, normalize, clip, print_table, open_data, probability, random_weights + num_or_str, normalize, clip, print_table, open_data, probability, random_weights, euclidean_distance ) import copy @@ -382,8 +382,8 @@ def cross_validation(learner, size, dataset, k=10, trials=1): examples = dataset.examples random.shuffle(dataset.examples) for fold in range(k): - train_data, val_data = train_test_split(dataset, fold * (n / k), - (fold + 1) * (n / k)) + train_data, val_data = train_test_split(dataset, fold * (n // k), + (fold + 1) * (n // k)) dataset.examples = train_data h = learner(dataset, size) fold_errs += err_ratio(h, dataset, train_data) @@ -393,6 +393,37 @@ def cross_validation(learner, size, dataset, k=10, trials=1): return fold_errs/k +def cross_validation_nosize(learner, dataset, k=10, trials=1): + """Do k-fold cross_validate and return their mean. + That is, keep out 1/k of the examples for testing on each of k runs. + Shuffle the examples first; if trials>1, average over several shuffles. + Returns Training error, Validataion error""" + k = k or len(dataset.examples) + if trials > 1: + trial_errs = 0 + for t in range(trials): + errs = cross_validation(learner, dataset, + k=10, trials=1) + trial_errs += errs + return trial_errs/trials + else: + fold_errs = 0 + n = len(dataset.examples) + examples = dataset.examples + random.shuffle(dataset.examples) + for fold in range(k): + train_data, val_data = train_test_split(dataset, fold * (n // k), + (fold + 1) * (n // k)) + dataset.examples = train_data + h = learner(dataset) + fold_errs += err_ratio(h, dataset, train_data) + + # Reverting back to original once test is completed + dataset.examples = examples + return fold_errs/k + + + def err_ratio(predict, dataset, examples=None, verbose=0): """Return the proportion of the examples that are NOT correctly predicted. verbose - 0: No output; 1: Output wrong; 2 (or greater): Output correct""" @@ -521,6 +552,8 @@ def LinearLearner(dataset, learning_rate=0.01, epochs=100): for example in examples: x = [1] + example y = dotproduct(w, x) + # if threshold: + # y = threshold(y) t = example[idx_t] err.append(t - y) @@ -554,17 +587,20 @@ def LogisticLinearLeaner(dataset, learning_rate=0.01, epochs=100): for epoch in range(epochs): err = [] + h= [] # Pass over all examples for example in examples: x = [1] + example y = 1/(1 + math.exp(-dotproduct(w, x))) - h = [y * (1-y)] + h.append(y * (1-y)) t = example[idx_t] err.append(t - y) # update weights for i in range(len(w)): - w[i] = w[i] + learning_rate * (dotproduct(dotproduct(err,h), X_col[i]) / num_examples) + buffer = [x*y for x,y in zip(err, h)] + # w[i] = w[i] + learning_rate * (dotproduct(err, X_col[i]) / num_examples) + w[i] = w[i] + learning_rate * (dotproduct(buffer, X_col[i]) / num_examples) def predict(example): x = [1] + example @@ -580,6 +616,7 @@ def NearestNeighborLearner(dataset, k=1): """k-NearestNeighbor: the k nearest neighbors vote.""" def predict(example): """Find the k closest items, and have them vote for the best.""" + example.pop(dataset.target) best = heapq.nsmallest(k, ((dataset.distance(e, example), e) for e in dataset.examples)) return mode(e[dataset.target] for (d, e) in best) @@ -829,6 +866,6 @@ def compare(algorithms=None, datasets=None, k=10, trials=1): Majority(7, 100), Parity(7, 100), Xor(100)] # of datasets print_table([[a.__name__.replace('Learner', '')] + - [cross_validation(a, d, k, trials) for d in datasets] + [cross_validation_nosize(a, d, k, trials) for d in datasets] for a in algorithms], - header=[''] + [d.name[0:7] for d in datasets], numfmt='%.2f') + header=[''] + [d.name[0:7] for d in datasets], numfmt='{0:.2f}') diff --git a/notebook4e.py b/notebook4e.py new file mode 100644 index 000000000..28f562e41 --- /dev/null +++ b/notebook4e.py @@ -0,0 +1,1151 @@ +from inspect import getsource + +from utils import argmax, argmin +from games import TicTacToe, alphabeta_player, random_player, Fig52Extended, infinity +from logic import parse_definite_clause, standardize_variables, unify, subst +from learning import DataSet +from IPython.display import HTML, display +from collections import Counter, defaultdict + +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap +import numpy as np +from PIL import Image + +import os, struct +import array +import time + +# ______________________________________________________________________________ +# Magic Words + + +def pseudocode(algorithm): + """Print the pseudocode for the given algorithm.""" + from urllib.request import urlopen + from IPython.display import Markdown + + algorithm = algorithm.replace(' ', '-') + url = "https://raw.githubusercontent.com/aimacode/aima-pseudocode/master/md/{}.md".format(algorithm) + f = urlopen(url) + md = f.read().decode('utf-8') + md = md.split('\n', 1)[-1].strip() + md = '#' + md + return Markdown(md) + + +def psource(*functions): + """Print the source code for the given function(s).""" + source_code = '\n\n'.join(getsource(fn) for fn in functions) + try: + from pygments.formatters import HtmlFormatter + from pygments.lexers import PythonLexer + from pygments import highlight + + display(HTML(highlight(source_code, PythonLexer(), HtmlFormatter(full=True)))) + + except ImportError: + print(source_code) + + +def plot_model_boundary(dataset, attr1, attr2, model=None): + # prepare data + examples = np.asarray(dataset.examples) + X = np.asarray([examples[:, attr1], examples[:, attr2]]) + y = examples[:, dataset.target] + h = 0.1 + + # create color maps + cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#00AAFF']) + cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#00AAFF']) + + # calculate min, max and limits + x_min, x_max = X[0].min() - 1, X[0].max() + 1 + y_min, y_max = X[1].min() - 1, X[1].max() + 1 + # mesh the grid + xx, yy = np.meshgrid(np.arange(x_min, x_max, h), + np.arange(y_min, y_max, h)) + Z = [] + for grid in zip(xx.ravel(), yy.ravel()): + # put them back to the example + grid = np.round(grid, decimals=1).tolist() + Z.append(model(grid)) + # Put the result into a color plot + Z = np.asarray(Z) + Z = Z.reshape(xx.shape) + plt.figure() + plt.pcolormesh(xx, yy, Z, cmap=cmap_light) + + # Plot also the training points + plt.scatter(X[0], X[1], c=y, cmap=cmap_bold) + plt.xlim(xx.min(), xx.max()) + plt.ylim(yy.min(), yy.max()) + plt.show() + +# ______________________________________________________________________________ +# Iris Visualization + + +def show_iris(i=0, j=1, k=2): + """Plots the iris dataset in a 3D plot. + The three axes are given by i, j and k, + which correspond to three of the four iris features.""" + from mpl_toolkits.mplot3d import Axes3D + + plt.rcParams.update(plt.rcParamsDefault) + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + + iris = DataSet(name="iris") + buckets = iris.split_values_by_classes() + + features = ["Sepal Length", "Sepal Width", "Petal Length", "Petal Width"] + f1, f2, f3 = features[i], features[j], features[k] + + a_setosa = [v[i] for v in buckets["setosa"]] + b_setosa = [v[j] for v in buckets["setosa"]] + c_setosa = [v[k] for v in buckets["setosa"]] + + a_virginica = [v[i] for v in buckets["virginica"]] + b_virginica = [v[j] for v in buckets["virginica"]] + c_virginica = [v[k] for v in buckets["virginica"]] + + a_versicolor = [v[i] for v in buckets["versicolor"]] + b_versicolor = [v[j] for v in buckets["versicolor"]] + c_versicolor = [v[k] for v in buckets["versicolor"]] + + + for c, m, sl, sw, pl in [('b', 's', a_setosa, b_setosa, c_setosa), + ('g', '^', a_virginica, b_virginica, c_virginica), + ('r', 'o', a_versicolor, b_versicolor, c_versicolor)]: + ax.scatter(sl, sw, pl, c=c, marker=m) + + ax.set_xlabel(f1) + ax.set_ylabel(f2) + ax.set_zlabel(f3) + + plt.show() + + +# ______________________________________________________________________________ +# MNIST + + +def load_MNIST(path="aima-data/MNIST/Digits", fashion=False): + import os, struct + import array + import numpy as np + from collections import Counter + + if fashion: + path = "aima-data/MNIST/Fashion" + + plt.rcParams.update(plt.rcParamsDefault) + plt.rcParams['figure.figsize'] = (10.0, 8.0) + plt.rcParams['image.interpolation'] = 'nearest' + plt.rcParams['image.cmap'] = 'gray' + + train_img_file = open(os.path.join(path, "train-images-idx3-ubyte"), "rb") + train_lbl_file = open(os.path.join(path, "train-labels-idx1-ubyte"), "rb") + test_img_file = open(os.path.join(path, "t10k-images-idx3-ubyte"), "rb") + test_lbl_file = open(os.path.join(path, 't10k-labels-idx1-ubyte'), "rb") + + magic_nr, tr_size, tr_rows, tr_cols = struct.unpack(">IIII", train_img_file.read(16)) + tr_img = array.array("B", train_img_file.read()) + train_img_file.close() + magic_nr, tr_size = struct.unpack(">II", train_lbl_file.read(8)) + tr_lbl = array.array("b", train_lbl_file.read()) + train_lbl_file.close() + + magic_nr, te_size, te_rows, te_cols = struct.unpack(">IIII", test_img_file.read(16)) + te_img = array.array("B", test_img_file.read()) + test_img_file.close() + magic_nr, te_size = struct.unpack(">II", test_lbl_file.read(8)) + te_lbl = array.array("b", test_lbl_file.read()) + test_lbl_file.close() + + #print(len(tr_img), len(tr_lbl), tr_size) + #print(len(te_img), len(te_lbl), te_size) + + train_img = np.zeros((tr_size, tr_rows*tr_cols), dtype=np.int16) + train_lbl = np.zeros((tr_size,), dtype=np.int8) + for i in range(tr_size): + train_img[i] = np.array(tr_img[i*tr_rows*tr_cols : (i+1)*tr_rows*tr_cols]).reshape((tr_rows*te_cols)) + train_lbl[i] = tr_lbl[i] + + test_img = np.zeros((te_size, te_rows*te_cols), dtype=np.int16) + test_lbl = np.zeros((te_size,), dtype=np.int8) + for i in range(te_size): + test_img[i] = np.array(te_img[i*te_rows*te_cols : (i+1)*te_rows*te_cols]).reshape((te_rows*te_cols)) + test_lbl[i] = te_lbl[i] + + return(train_img, train_lbl, test_img, test_lbl) + + +digit_classes = [str(i) for i in range(10)] +fashion_classes = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat", + "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"] + + +def show_MNIST(labels, images, samples=8, fashion=False): + if not fashion: + classes = digit_classes + else: + classes = fashion_classes + + num_classes = len(classes) + + for y, cls in enumerate(classes): + idxs = np.nonzero([i == y for i in labels]) + idxs = np.random.choice(idxs[0], samples, replace=False) + for i , idx in enumerate(idxs): + plt_idx = i * num_classes + y + 1 + plt.subplot(samples, num_classes, plt_idx) + plt.imshow(images[idx].reshape((28, 28))) + plt.axis("off") + if i == 0: + plt.title(cls) + + plt.show() + + +def show_ave_MNIST(labels, images, fashion=False): + if not fashion: + item_type = "Digit" + classes = digit_classes + else: + item_type = "Apparel" + classes = fashion_classes + + num_classes = len(classes) + + for y, cls in enumerate(classes): + idxs = np.nonzero([i == y for i in labels]) + print(item_type, y, ":", len(idxs[0]), "images.") + + ave_img = np.mean(np.vstack([images[i] for i in idxs[0]]), axis = 0) + #print(ave_img.shape) + + plt.subplot(1, num_classes, y+1) + plt.imshow(ave_img.reshape((28, 28))) + plt.axis("off") + plt.title(cls) + + plt.show() + +# ______________________________________________________________________________ +# MDP + + +def make_plot_grid_step_function(columns, rows, U_over_time): + """ipywidgets interactive function supports single parameter as input. + This function creates and return such a function by taking as input + other parameters.""" + + def plot_grid_step(iteration): + data = U_over_time[iteration] + data = defaultdict(lambda: 0, data) + grid = [] + for row in range(rows): + current_row = [] + for column in range(columns): + current_row.append(data[(column, row)]) + grid.append(current_row) + grid.reverse() # output like book + fig = plt.imshow(grid, cmap=plt.cm.bwr, interpolation='nearest') + + plt.axis('off') + fig.axes.get_xaxis().set_visible(False) + fig.axes.get_yaxis().set_visible(False) + + for col in range(len(grid)): + for row in range(len(grid[0])): + magic = grid[col][row] + fig.axes.text(row, col, "{0:.2f}".format(magic), va='center', ha='center') + + plt.show() + + return plot_grid_step + +def make_visualize(slider): + """Takes an input a sliderand returns callback function + for timer and animation.""" + + def visualize_callback(Visualize, time_step): + if Visualize is True: + for i in range(slider.min, slider.max + 1): + slider.value = i + time.sleep(float(time_step)) + + return visualize_callback + +# ______________________________________________________________________________ + + +_canvas = """ + +
+ +
+ + +""" # noqa + + +class Canvas: + """Inherit from this class to manage the HTML canvas element in jupyter notebooks. + To create an object of this class any_name_xyz = Canvas("any_name_xyz") + The first argument given must be the name of the object being created. + IPython must be able to reference the variable name that is being passed.""" + + def __init__(self, varname, width=800, height=600, cid=None): + self.name = varname + self.cid = cid or varname + self.width = width + self.height = height + self.html = _canvas.format(self.cid, self.width, self.height, self.name) + self.exec_list = [] + display_html(self.html) + + def mouse_click(self, x, y): + """Override this method to handle mouse click at position (x, y)""" + raise NotImplementedError + + def mouse_move(self, x, y): + raise NotImplementedError + + def execute(self, exec_str): + """Stores the command to be executed to a list which is used later during update()""" + if not isinstance(exec_str, str): + print("Invalid execution argument:", exec_str) + self.alert("Received invalid execution command format") + prefix = "{0}_canvas_object.".format(self.cid) + self.exec_list.append(prefix + exec_str + ';') + + def fill(self, r, g, b): + """Changes the fill color to a color in rgb format""" + self.execute("fill({0}, {1}, {2})".format(r, g, b)) + + def stroke(self, r, g, b): + """Changes the colors of line/strokes to rgb""" + self.execute("stroke({0}, {1}, {2})".format(r, g, b)) + + def strokeWidth(self, w): + """Changes the width of lines/strokes to 'w' pixels""" + self.execute("strokeWidth({0})".format(w)) + + def rect(self, x, y, w, h): + """Draw a rectangle with 'w' width, 'h' height and (x, y) as the top-left corner""" + self.execute("rect({0}, {1}, {2}, {3})".format(x, y, w, h)) + + def rect_n(self, xn, yn, wn, hn): + """Similar to rect(), but the dimensions are normalized to fall between 0 and 1""" + x = round(xn * self.width) + y = round(yn * self.height) + w = round(wn * self.width) + h = round(hn * self.height) + self.rect(x, y, w, h) + + def line(self, x1, y1, x2, y2): + """Draw a line from (x1, y1) to (x2, y2)""" + self.execute("line({0}, {1}, {2}, {3})".format(x1, y1, x2, y2)) + + def line_n(self, x1n, y1n, x2n, y2n): + """Similar to line(), but the dimensions are normalized to fall between 0 and 1""" + x1 = round(x1n * self.width) + y1 = round(y1n * self.height) + x2 = round(x2n * self.width) + y2 = round(y2n * self.height) + self.line(x1, y1, x2, y2) + + def arc(self, x, y, r, start, stop): + """Draw an arc with (x, y) as centre, 'r' as radius from angles 'start' to 'stop'""" + self.execute("arc({0}, {1}, {2}, {3}, {4})".format(x, y, r, start, stop)) + + def arc_n(self, xn, yn, rn, start, stop): + """Similar to arc(), but the dimensions are normalized to fall between 0 and 1 + The normalizing factor for radius is selected between width and height by + seeing which is smaller.""" + x = round(xn * self.width) + y = round(yn * self.height) + r = round(rn * min(self.width, self.height)) + self.arc(x, y, r, start, stop) + + def clear(self): + """Clear the HTML canvas""" + self.execute("clear()") + + def font(self, font): + """Changes the font of text""" + self.execute('font("{0}")'.format(font)) + + def text(self, txt, x, y, fill=True): + """Display a text at (x, y)""" + if fill: + self.execute('fill_text("{0}", {1}, {2})'.format(txt, x, y)) + else: + self.execute('stroke_text("{0}", {1}, {2})'.format(txt, x, y)) + + def text_n(self, txt, xn, yn, fill=True): + """Similar to text(), but with normalized coordinates""" + x = round(xn * self.width) + y = round(yn * self.height) + self.text(txt, x, y, fill) + + def alert(self, message): + """Immediately display an alert""" + display_html(''.format(message)) + + def update(self): + """Execute the JS code to execute the commands queued by execute()""" + exec_code = "" + self.exec_list = [] + display_html(exec_code) + + +def display_html(html_string): + display(HTML(html_string)) + + +################################################################################ + + +class Canvas_TicTacToe(Canvas): + """Play a 3x3 TicTacToe game on HTML canvas""" + def __init__(self, varname, player_1='human', player_2='random', + width=300, height=350, cid=None): + valid_players = ('human', 'random', 'alphabeta') + 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) + self.ttt = TicTacToe() + self.state = self.ttt.initial + self.turn = 0 + self.strokeWidth(5) + self.players = (player_1, player_2) + self.font("20px Arial") + self.draw_board() + + def mouse_click(self, x, y): + player = self.players[self.turn] + if self.ttt.terminal_test(self.state): + if 0.55 <= x/self.width <= 0.95 and 6/7 <= y/self.height <= 6/7+1/8: + self.state = self.ttt.initial + self.turn = 0 + self.draw_board() + return + + if player == 'human': + x, y = int(3*x/self.width) + 1, int(3*y/(self.height*6/7)) + 1 + if (x, y) not in self.ttt.actions(self.state): + # Invalid move + return + move = (x, y) + elif player == 'alphabeta': + move = alphabeta_player(self.ttt, self.state) + else: + move = random_player(self.ttt, self.state) + self.state = self.ttt.result(self.state, move) + self.turn ^= 1 + self.draw_board() + + def draw_board(self): + self.clear() + self.stroke(0, 0, 0) + offset = 1/20 + self.line_n(0 + offset, (1/3)*6/7, 1 - offset, (1/3)*6/7) + self.line_n(0 + offset, (2/3)*6/7, 1 - offset, (2/3)*6/7) + self.line_n(1/3, (0 + offset)*6/7, 1/3, (1 - offset)*6/7) + self.line_n(2/3, (0 + offset)*6/7, 2/3, (1 - offset)*6/7) + + board = self.state.board + for mark in board: + if board[mark] == 'X': + self.draw_x(mark) + elif board[mark] == 'O': + self.draw_o(mark) + if self.ttt.terminal_test(self.state): + # End game message + utility = self.ttt.utility(self.state, self.ttt.to_move(self.ttt.initial)) + if utility == 0: + self.text_n('Game Draw!', offset, 6/7 + offset) + else: + self.text_n('Player {} wins!'.format("XO"[utility < 0]), offset, 6/7 + offset) + # Find the 3 and draw a line + self.stroke([255, 0][self.turn], [0, 255][self.turn], 0) + for i in range(3): + if all([(i + 1, j + 1) in self.state.board for j in range(3)]) and \ + len({self.state.board[(i + 1, j + 1)] for j in range(3)}) == 1: + self.line_n(i/3 + 1/6, offset*6/7, i/3 + 1/6, (1 - offset)*6/7) + if all([(j + 1, i + 1) in self.state.board for j in range(3)]) and \ + len({self.state.board[(j + 1, i + 1)] for j in range(3)}) == 1: + self.line_n(offset, (i/3 + 1/6)*6/7, 1 - offset, (i/3 + 1/6)*6/7) + if all([(i + 1, i + 1) in self.state.board for i in range(3)]) and \ + len({self.state.board[(i + 1, i + 1)] for i in range(3)}) == 1: + self.line_n(offset, offset*6/7, 1 - offset, (1 - offset)*6/7) + if all([(i + 1, 3 - i) in self.state.board for i in range(3)]) and \ + len({self.state.board[(i + 1, 3 - i)] for i in range(3)}) == 1: + self.line_n(offset, (1 - offset)*6/7, 1 - offset, offset*6/7) + # restart button + self.fill(0, 0, 255) + self.rect_n(0.5 + offset, 6/7, 0.4, 1/8) + self.fill(0, 0, 0) + self.text_n('Restart', 0.5 + 2*offset, 13/14) + else: # Print which player's turn it is + self.text_n("Player {}'s move({})".format("XO"[self.turn], self.players[self.turn]), + offset, 6/7 + offset) + + self.update() + + def draw_x(self, position): + self.stroke(0, 255, 0) + x, y = [i-1 for i in position] + offset = 1/15 + self.line_n(x/3 + offset, (y/3 + offset)*6/7, x/3 + 1/3 - offset, (y/3 + 1/3 - offset)*6/7) + self.line_n(x/3 + 1/3 - offset, (y/3 + offset)*6/7, x/3 + offset, (y/3 + 1/3 - offset)*6/7) + + def draw_o(self, position): + self.stroke(255, 0, 0) + x, y = [i-1 for i in 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""" + def __init__(self, varname, util_list, width=800, height=600, cid=None): + Canvas.__init__(self, 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 + self.nodes = list(range(40)) + self.l = 1/40 + self.node_pos = {} + for i in range(4): + base = len(self.node_pos) + row_size = 3**i + for node in [base + j for j in range(row_size)]: + self.node_pos[node] = ((node - base)/row_size + 1/(2*row_size) - self.l/2, + self.l/2 + (self.l + (1 - 5*self.l)/3)*i) + self.font("12px Arial") + self.node_stack = [] + self.explored = {node for node in self.utils} + self.thick_lines = set() + self.change_list = [] + self.draw_graph() + self.stack_manager = self.stack_manager_gen() + + def minimax(self, node): + game = self.game + player = game.to_move(node) + def max_value(node): + if game.terminal_test(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_node = game.result(node, max_a) + self.utils[node] = self.utils[max_node] + x1, y1 = self.node_pos[node] + x2, y2 = self.node_pos[max_node] + self.change_list.append(('l', (node, max_node - 3*node - 1))) + self.change_list.append(('e', node)) + self.change_list.append(('p',)) + self.change_list.append(('h',)) + return self.utils[node] + + def min_value(node): + if game.terminal_test(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_node = game.result(node, min_a) + self.utils[node] = self.utils[min_node] + x1, y1 = self.node_pos[node] + x2, y2 = self.node_pos[min_node] + self.change_list.append(('l', (node, min_node - 3*node - 1))) + self.change_list.append(('e', node)) + self.change_list.append(('p',)) + self.change_list.append(('h',)) + return self.utils[node] + + return max_value(node) + + def stack_manager_gen(self): + self.minimax(0) + for change in self.change_list: + if change[0] == 'a': + self.node_stack.append(change[1]) + elif change[0] == 'e': + self.explored.add(change[1]) + elif change[0] == 'h': + yield + elif change[0] == 'l': + self.thick_lines.add(change[1]) + elif change[0] == 'p': + self.node_stack.pop() + + def mouse_click(self, x, y): + try: + self.stack_manager.send(None) + except StopIteration: + pass + self.draw_graph() + + def draw_graph(self): + self.clear() + # draw nodes + self.stroke(0, 0, 0) + self.strokeWidth(1) + # highlight for nodes in stack + for node in self.node_stack: + x, y = self.node_pos[node] + self.fill(200, 200, 0) + self.rect_n(x - self.l/5, y - self.l/5, self.l*7/5, self.l*7/5) + for node in self.nodes: + x, y = self.node_pos[node] + if node in self.explored: + self.fill(255, 255, 255) + else: + self.fill(200, 200, 200) + self.rect_n(x, y, self.l, self.l) + self.line_n(x, y, x + self.l, y) + self.line_n(x, y, x, y + self.l) + self.line_n(x + self.l, y + self.l, x + self.l, y) + self.line_n(x + self.l, y + self.l, x, y + self.l) + self.fill(0, 0, 0) + if node in self.explored: + self.text_n(self.utils[node], x + self.l/10, y + self.l*9/10) + # draw edges + for i in range(13): + x1, y1 = self.node_pos[i][0] + self.l/2, self.node_pos[i][1] + self.l + for j in range(3): + x2, y2 = self.node_pos[i*3 + j + 1][0] + self.l/2, self.node_pos[i*3 + j + 1][1] + if i in [1, 2, 3]: + self.stroke(200, 0, 0) + else: + self.stroke(0, 200, 0) + if (i, j) in self.thick_lines: + self.strokeWidth(3) + else: + self.strokeWidth(1) + self.line_n(x1, y1, x2, y2) + self.update() + + +class Canvas_alphabeta(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) + self.utils = {node:util for node, util in zip(range(13, 40), util_list)} + self.game = Fig52Extended() + self.game.utils = self.utils + self.nodes = list(range(40)) + self.l = 1/40 + self.node_pos = {} + for i in range(4): + base = len(self.node_pos) + row_size = 3**i + for node in [base + j for j in range(row_size)]: + self.node_pos[node] = ((node - base)/row_size + 1/(2*row_size) - self.l/2, + 3*self.l/2 + (self.l + (1 - 6*self.l)/3)*i) + self.font("12px Arial") + self.node_stack = [] + self.explored = {node for node in self.utils} + self.pruned = set() + self.ab = {} + self.thick_lines = set() + self.change_list = [] + self.draw_graph() + self.stack_manager = self.stack_manager_gen() + + def alphabeta_search(self, node): + game = self.game + player = game.to_move(node) + + # Functions used by alphabeta + def max_value(node, alpha, beta): + if game.terminal_test(node): + self.change_list.append(('a', node)) + self.change_list.append(('h',)) + self.change_list.append(('p',)) + return game.utility(node, player) + v = -infinity + self.change_list.append(('a', node)) + self.change_list.append(('ab',node, v, beta)) + self.change_list.append(('h',)) + for a in game.actions(node): + min_val = min_value(game.result(node, a), alpha, beta) + if v < min_val: + v = min_val + max_node = game.result(node, a) + self.change_list.append(('ab',node, v, beta)) + if v >= beta: + self.change_list.append(('h',)) + self.pruned.add(node) + break + alpha = max(alpha, v) + self.utils[node] = v + if node not in self.pruned: + self.change_list.append(('l', (node, max_node - 3*node - 1))) + self.change_list.append(('e',node)) + self.change_list.append(('p',)) + self.change_list.append(('h',)) + return v + + def min_value(node, alpha, beta): + if game.terminal_test(node): + self.change_list.append(('a', node)) + self.change_list.append(('h',)) + self.change_list.append(('p',)) + return game.utility(node, player) + v = infinity + self.change_list.append(('a', node)) + self.change_list.append(('ab',node, alpha, v)) + self.change_list.append(('h',)) + for a in game.actions(node): + max_val = max_value(game.result(node, a), alpha, beta) + if v > max_val: + v = max_val + min_node = game.result(node, a) + self.change_list.append(('ab',node, alpha, v)) + if v <= alpha: + self.change_list.append(('h',)) + self.pruned.add(node) + break + beta = min(beta, v) + self.utils[node] = v + if node not in self.pruned: + self.change_list.append(('l', (node, min_node - 3*node - 1))) + self.change_list.append(('e',node)) + self.change_list.append(('p',)) + self.change_list.append(('h',)) + return v + + return max_value(node, -infinity, infinity) + + def stack_manager_gen(self): + self.alphabeta_search(0) + for change in self.change_list: + if change[0] == 'a': + self.node_stack.append(change[1]) + elif change[0] == 'ab': + self.ab[change[1]] = change[2:] + elif change[0] == 'e': + self.explored.add(change[1]) + elif change[0] == 'h': + yield + elif change[0] == 'l': + self.thick_lines.add(change[1]) + elif change[0] == 'p': + self.node_stack.pop() + + def mouse_click(self, x, y): + try: + self.stack_manager.send(None) + except StopIteration: + pass + self.draw_graph() + + def draw_graph(self): + self.clear() + # draw nodes + self.stroke(0, 0, 0) + self.strokeWidth(1) + # highlight for nodes in stack + for node in self.node_stack: + x, y = self.node_pos[node] + # alpha > beta + if node not in self.explored and self.ab[node][0] > self.ab[node][1]: + self.fill(200, 100, 100) + else: + self.fill(200, 200, 0) + self.rect_n(x - self.l/5, y - self.l/5, self.l*7/5, self.l*7/5) + for node in self.nodes: + x, y = self.node_pos[node] + if node in self.explored: + if node in self.pruned: + self.fill(50, 50, 50) + else: + self.fill(255, 255, 255) + else: + self.fill(200, 200, 200) + self.rect_n(x, y, self.l, self.l) + self.line_n(x, y, x + self.l, y) + self.line_n(x, y, x, y + self.l) + self.line_n(x + self.l, y + self.l, x + self.l, y) + self.line_n(x + self.l, y + self.l, x, y + self.l) + self.fill(0, 0, 0) + if node in self.explored and node not in self.pruned: + self.text_n(self.utils[node], x + self.l/10, y + self.l*9/10) + # draw edges + for i in range(13): + x1, y1 = self.node_pos[i][0] + self.l/2, self.node_pos[i][1] + self.l + for j in range(3): + x2, y2 = self.node_pos[i*3 + j + 1][0] + self.l/2, self.node_pos[i*3 + j + 1][1] + if i in [1, 2, 3]: + self.stroke(200, 0, 0) + else: + self.stroke(0, 200, 0) + if (i, j) in self.thick_lines: + self.strokeWidth(3) + else: + self.strokeWidth(1) + self.line_n(x1, y1, x2, y2) + # display alpha and beta + for node in self.node_stack: + if node not in self.explored: + x, y = self.node_pos[node] + alpha, beta = self.ab[node] + self.text_n(alpha, x - self.l/2, y - self.l/10) + self.text_n(beta, x + self.l, y - self.l/10) + self.update() + + +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) + self.kb = kb + self.query = query + self.l = 1/20 + self.b = 3*self.l + bc_out = list(self.fol_bc_ask()) + if len(bc_out) is 0: + self.valid = False + else: + self.valid = True + graph = bc_out[0][0][0] + s = bc_out[0][1] + while True: + new_graph = subst(s, graph) + if graph == new_graph: + break + graph = new_graph + self.make_table(graph) + self.context = None + self.draw_table() + + def fol_bc_ask(self): + KB = self.kb + query = self.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)): + yield ([(goal, theta1[0])], theta1[1]) + + def fol_bc_and(KB, goals, theta): + if theta is None: + pass + elif not goals: + yield ([], theta) + else: + first, rest = goals[0], goals[1:] + for theta1 in fol_bc_or(KB, subst(theta, first), theta): + for theta2 in fol_bc_and(KB, rest, theta1[1]): + yield (theta1[0] + theta2[0], theta2[1]) + + return fol_bc_or(KB, query, {}) + + def make_table(self, graph): + table = [] + pos = {} + links = set() + edges = set() + + def dfs(node, depth): + if len(table) <= depth: + table.append([]) + pos = len(table[depth]) + table[depth].append(node[0]) + for child in node[1]: + child_id = dfs(child, depth + 1) + links.add(((depth, pos), child_id)) + return (depth, pos) + + dfs(graph, 0) + y_off = 0.85/len(table) + for i, row in enumerate(table): + x_off = 0.95/len(row) + for j, node in enumerate(row): + pos[(i, j)] = (0.025 + j*x_off + (x_off - self.b)/2, 0.025 + i*y_off + (y_off - self.l)/2) + for p, c in links: + x1, y1 = pos[p] + x2, y2 = pos[c] + edges.add((x1 + self.b/2, y1 + self.l, x2 + self.b/2, y2)) + + self.table = table + self.pos = pos + self.edges = edges + + def mouse_click(self, x, y): + x, y = x/self.width, y/self.height + for node in self.pos: + xs, ys = self.pos[node] + xe, ye = xs + self.b, ys + self.l + if xs <= x <= xe and ys <= y <= ye: + self.context = node + break + self.draw_table() + + def draw_table(self): + self.clear() + self.strokeWidth(3) + self.stroke(0, 0, 0) + self.font("12px Arial") + if self.valid: + # draw nodes + for i, j in self.pos: + x, y = self.pos[(i, j)] + self.fill(200, 200, 200) + self.rect_n(x, y, self.b, self.l) + self.line_n(x, y, x + self.b, y) + self.line_n(x, y, x, y + self.l) + self.line_n(x + self.b, y, x + self.b, y + self.l) + self.line_n(x, y + self.l, x + self.b, y + self.l) + self.fill(0, 0, 0) + self.text_n(self.table[i][j], x + 0.01, y + self.l - 0.01) + #draw edges + for x1, y1, x2, y2 in self.edges: + self.line_n(x1, y1, x2, y2) + else: + self.fill(255, 0, 0) + self.rect_n(0, 0, 1, 1) + # text area + self.fill(255, 255, 255) + self.rect_n(0, 0.9, 1, 0.1) + self.strokeWidth(5) + self.stroke(0, 0, 0) + self.line_n(0, 0.9, 1, 0.9) + self.font("22px Arial") + self.fill(0, 0, 0) + self.text_n(self.table[self.context[0]][self.context[1]] if self.context else "Click for text", 0.025, 0.975) + self.update() + + +############################################################################################################ + +##################### Functions to assist plotting in search.ipynb #################### + +############################################################################################################ +import networkx as nx +import matplotlib.pyplot as plt +from matplotlib import lines + +from ipywidgets import interact +import ipywidgets as widgets +from IPython.display import display +import time +from search import GraphProblem, romania_map + +def show_map(graph_data, node_colors = None): + G = nx.Graph(graph_data['graph_dict']) + node_colors = node_colors or graph_data['node_colors'] + node_positions = graph_data['node_positions'] + node_label_pos = graph_data['node_label_positions'] + edge_weights= graph_data['edge_weights'] + + # set the size of the plot + plt.figure(figsize=(18,13)) + # draw the graph (both nodes and edges) with locations from romania_locations + nx.draw(G, pos={k: node_positions[k] for k in G.nodes()}, + node_color=[node_colors[node] for node in G.nodes()], linewidths=0.3, edgecolors='k') + + # draw labels for nodes + node_label_handles = nx.draw_networkx_labels(G, pos=node_label_pos, font_size=14) + + # add a white bounding box behind the node labels + [label.set_bbox(dict(facecolor='white', edgecolor='none')) for label in node_label_handles.values()] + + # add edge lables to the graph + nx.draw_networkx_edge_labels(G, pos=node_positions, edge_labels=edge_weights, font_size=14) + + # add a legend + white_circle = lines.Line2D([], [], color="white", marker='o', markersize=15, markerfacecolor="white") + orange_circle = lines.Line2D([], [], color="orange", marker='o', markersize=15, markerfacecolor="orange") + red_circle = lines.Line2D([], [], color="red", marker='o', markersize=15, markerfacecolor="red") + gray_circle = lines.Line2D([], [], color="gray", marker='o', markersize=15, markerfacecolor="gray") + green_circle = lines.Line2D([], [], color="green", marker='o', markersize=15, markerfacecolor="green") + plt.legend((white_circle, orange_circle, red_circle, gray_circle, green_circle), + ('Un-explored', 'Frontier', 'Currently Exploring', 'Explored', 'Final Solution'), + numpoints=1, prop={'size':16}, loc=(.8,.75)) + + # show the plot. No need to use in notebooks. nx.draw will show the graph itself. + plt.show() + +## helper functions for visualisations + +def final_path_colors(initial_node_colors, problem, solution): + "Return a node_colors dict of the final path provided the problem and solution." + + # get initial node colors + final_colors = dict(initial_node_colors) + # color all the nodes in solution and starting node to green + final_colors[problem.initial] = "green" + for node in solution: + final_colors[node] = "green" + return final_colors + +def display_visual(graph_data, user_input, algorithm=None, problem=None): + initial_node_colors = graph_data['node_colors'] + if user_input == False: + def slider_callback(iteration): + # don't show graph for the first time running the cell calling this function + try: + show_map(graph_data, node_colors=all_node_colors[iteration]) + except: + pass + def visualize_callback(Visualize): + if Visualize is True: + button.value = False + + global all_node_colors + + iterations, all_node_colors, node = algorithm(problem) + solution = node.solution() + all_node_colors.append(final_path_colors(all_node_colors[0], problem, solution)) + + slider.max = len(all_node_colors) - 1 + + for i in range(slider.max + 1): + slider.value = i + #time.sleep(.5) + + slider = widgets.IntSlider(min=0, max=1, step=1, value=0) + slider_visual = widgets.interactive(slider_callback, iteration=slider) + display(slider_visual) + + button = widgets.ToggleButton(value=False) + button_visual = widgets.interactive(visualize_callback, Visualize=button) + display(button_visual) + + if user_input == True: + node_colors = dict(initial_node_colors) + if isinstance(algorithm, dict): + assert set(algorithm.keys()).issubset({"Breadth First Tree Search", + "Depth First Tree Search", + "Breadth First Search", + "Depth First Graph Search", + "Best First Graph Search", + "Uniform Cost Search", + "Depth Limited Search", + "Iterative Deepening Search", + "Greedy Best First Search", + "A-star Search", + "Recursive Best First Search"}) + + algo_dropdown = widgets.Dropdown(description="Search algorithm: ", + options=sorted(list(algorithm.keys())), + value="Breadth First Tree Search") + display(algo_dropdown) + elif algorithm is None: + print("No algorithm to run.") + return 0 + + def slider_callback(iteration): + # don't show graph for the first time running the cell calling this function + try: + show_map(graph_data, node_colors=all_node_colors[iteration]) + except: + pass + + def visualize_callback(Visualize): + if Visualize is True: + button.value = False + + problem = GraphProblem(start_dropdown.value, end_dropdown.value, romania_map) + global all_node_colors + + user_algorithm = algorithm[algo_dropdown.value] + + iterations, all_node_colors, node = user_algorithm(problem) + solution = node.solution() + all_node_colors.append(final_path_colors(all_node_colors[0], problem, solution)) + + slider.max = len(all_node_colors) - 1 + + for i in range(slider.max + 1): + slider.value = i + #time.sleep(.5) + + start_dropdown = widgets.Dropdown(description="Start city: ", + options=sorted(list(node_colors.keys())), value="Arad") + display(start_dropdown) + + end_dropdown = widgets.Dropdown(description="Goal city: ", + options=sorted(list(node_colors.keys())), value="Fagaras") + display(end_dropdown) + + button = widgets.ToggleButton(value=False) + button_visual = widgets.interactive(visualize_callback, Visualize=button) + display(button_visual) + + slider = widgets.IntSlider(min=0, max=1, step=1, value=0) + slider_visual = widgets.interactive(slider_callback, iteration=slider) + display(slider_visual) + + +# Function to plot NQueensCSP in csp.py and NQueensProblem in search.py +def plot_NQueens(solution): + n = len(solution) + board = np.array([2 * int((i + j) % 2) for j in range(n) for i in range(n)]).reshape((n, n)) + im = Image.open('images/queen_s.png') + height = im.size[1] + im = np.array(im).astype(np.float) / 255 + fig = plt.figure(figsize=(7, 7)) + ax = fig.add_subplot(111) + ax.set_title('{} Queens'.format(n)) + plt.imshow(board, cmap='binary', interpolation='nearest') + # NQueensCSP gives a solution as a dictionary + if isinstance(solution, dict): + for (k, v) in solution.items(): + newax = fig.add_axes([0.064 + (k * 0.112), 0.062 + ((7 - v) * 0.112), 0.1, 0.1], zorder=1) + newax.imshow(im) + newax.axis('off') + # NQueensProblem gives a solution as a list + elif isinstance(solution, list): + for (k, v) in enumerate(solution): + newax = fig.add_axes([0.064 + (k * 0.112), 0.062 + ((7 - v) * 0.112), 0.1, 0.1], zorder=1) + newax.imshow(im) + newax.axis('off') + fig.tight_layout() + plt.show() + +# Function to plot a heatmap, given a grid +def heatmap(grid, cmap='binary', interpolation='nearest'): + fig = plt.figure(figsize=(7, 7)) + ax = fig.add_subplot(111) + ax.set_title('Heatmap') + plt.imshow(grid, cmap=cmap, interpolation=interpolation) + fig.tight_layout() + plt.show() + +# Generates a gaussian kernel +def gaussian_kernel(l=5, sig=1.0): + ax = np.arange(-l // 2 + 1., l // 2 + 1.) + xx, yy = np.meshgrid(ax, ax) + kernel = np.exp(-(xx**2 + yy**2) / (2. * sig**2)) + return kernel + +# Plots utility function for a POMDP +def plot_pomdp_utility(utility): + save = utility['0'][0] + delete = utility['1'][0] + ask_save = utility['2'][0] + ask_delete = utility['2'][-1] + left = (save[0] - ask_save[0]) / (save[0] - ask_save[0] + ask_save[1] - save[1]) + right = (delete[0] - ask_delete[0]) / (delete[0] - ask_delete[0] + ask_delete[1] - delete[1]) + + colors = ['g', 'b', 'k'] + for action in utility: + for value in utility[action]: + plt.plot(value, color=colors[int(action)]) + plt.vlines([left, right], -20, 10, linestyles='dashed', colors='c') + plt.ylim(-20, 13) + plt.xlim(0, 1) + plt.text(left/2 - 0.05, 10, 'Save') + plt.text((right + left)/2 - 0.02, 10, 'Ask') + plt.text((right + 1)/2 - 0.07, 10, 'Delete') + plt.show() diff --git a/notebooks/chapter19/Learners.ipynb b/notebooks/chapter19/Learners.ipynb new file mode 100644 index 000000000..60c50cd1d --- /dev/null +++ b/notebooks/chapter19/Learners.ipynb @@ -0,0 +1,515 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Learners\n", + "\n", + "In this section, we will introduce several pre-defined learners to learning the datasets by updating their weights to minimize the loss function. when using a learner to deal with machine learning problems, there are several standard steps:\n", + "\n", + "- **Learner initialization**: Before training the network, it usually should be initialized first. There are several choices when initializing the weights: random initialization, initializing weights are zeros or use Gaussian distribution to init the weights.\n", + "\n", + "- **Optimizer specification**: Which means specifying the updating rules of learnable parameters of the network. Usually, we can choose Adam optimizer as default.\n", + "\n", + "- **Applying back-propagation**: In neural networks, we commonly use back-propagation to pass and calculate gradient information of each layer. Back-propagation needs to be integrated with the chosen optimizer in order to update the weights of NN properly in each epoch.\n", + "\n", + "- **Iterations**: Iterating over the forward and back-propagation process of given epochs. Sometimes the iterating process will have to be stopped by triggering early access in case of overfitting.\n", + "\n", + "We will introduce several learners with different structures. We will import all necessary packages before that:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import os, sys\n", + "sys.path = [os.path.abspath(\"../../\")] + sys.path\n", + "from DeepNeuralNet4e import *\n", + "from notebook4e import *\n", + "from learning4e import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Perceptron Learner\n", + "\n", + "### Overview\n", + "\n", + "The Perceptron is a linear classifier. It works the same way as a neural network with no hidden layers (just input and output). First, it trains its weights given a dataset and then it can classify a new item by running it through the network.\n", + "\n", + "Its input layer consists of the item features, while the output layer consists of nodes (also called neurons). Each node in the output layer has *n* synapses (for every item feature), each with its own weight. Then, the nodes find the dot product of the item features and the synapse weights. These values then pass through an activation function (usually a sigmoid). Finally, we pick the largest of the values and we return its index.\n", + "\n", + "Note that in classification problems each node represents a class. The final classification is the class/node with the max output value.\n", + "\n", + "Below you can see a single node/neuron in the outer layer. With *f* we denote the item features, with *w* the synapse weights, then inside the node we have the dot product and the activation function, *g*." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![perceptron](images/perceptron.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Implementation\n", + "\n", + "Perceptron learner is actually a neural network learner with only one hidden layer which is pre-defined in the algorithm of `perceptron_learner`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "raw_net = [InputLayer(input_size), DenseLayer(input_size, output_size)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Where `input_size` and `output_size` are calculated from dataset examples. In the perceptron learner, the gradient descent optimizer is used to update the weights of the network. we return a function `predict` which we will use in the future to classify a new item. The function computes the (algebraic) dot product of the item with the calculated weights for each node in the outer layer. Then it picks the greatest value and classifies the item in the corresponding class." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example\n", + "\n", + "Let's try the perceptron learner with the `iris` dataset examples, first let's regulate the dataset classes:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "iris = DataSet(name=\"iris\")\n", + "classes = [\"setosa\", \"versicolor\", \"virginica\"]\n", + "iris.classes_to_numbers(classes)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch:50, total_loss:14.089098023560856\n", + "epoch:100, total_loss:12.439240091345326\n", + "epoch:150, total_loss:11.848151059704785\n", + "epoch:200, total_loss:11.283665595671044\n", + "epoch:250, total_loss:11.153290841913241\n", + "epoch:300, total_loss:11.00747536734494\n", + "epoch:350, total_loss:10.871093050365419\n", + "epoch:400, total_loss:10.838400319844233\n", + "epoch:450, total_loss:10.687417928867456\n", + "epoch:500, total_loss:10.650371951865573\n" + ] + } + ], + "source": [ + "pl = perceptron_learner(iris, epochs=500, learning_rate=0.01, verbose=50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see from the printed lines that the final total loss is converged to around 10.50. If we check the error ratio of perceptron learner on the dataset after training, we will see it is much higher than randomly guess:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.046666666666666634\n" + ] + } + ], + "source": [ + "print(err_ratio(pl, iris))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we test the trained learner with some test cases:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "tests = [([5.0, 3.1, 0.9, 0.1], 0),\n", + " ([5.1, 3.5, 1.0, 0.0], 0),\n", + " ([4.9, 3.3, 1.1, 0.1], 0),\n", + " ([6.0, 3.0, 4.0, 1.1], 1),\n", + " ([6.1, 2.2, 3.5, 1.0], 1),\n", + " ([5.9, 2.5, 3.3, 1.1], 1),\n", + " ([7.5, 4.1, 6.2, 2.3], 2),\n", + " ([7.3, 4.0, 6.1, 2.4], 2),\n", + " ([7.0, 3.3, 6.1, 2.5], 2)]\n", + "print(grade_learner(pl, tests))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It seems the learner is correct on all the test examples.\n", + "\n", + "Now let's try perceptron learner on a more complicated dataset: the MNIST dataset, to see what the result will be. First, we import the dataset to make the examples a `Dataset` object:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "length of training dataset: 60000\n", + "length of test dataset: 10000\n" + ] + } + ], + "source": [ + "train_img, train_lbl, test_img, test_lbl = load_MNIST(path=\"../../aima-data/MNIST/Digits\")\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "train_examples = [np.append(train_img[i], train_lbl[i]) for i in range(len(train_img))]\n", + "test_examples = [np.append(test_img[i], test_lbl[i]) for i in range(len(test_img))]\n", + "print(\"length of training dataset:\", len(train_examples))\n", + "print(\"length of test dataset:\", len(test_examples))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's train the perceptron learner on the first 1000 examples of the dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch:1, total_loss:423.8627535296463\n", + "epoch:2, total_loss:341.31697581698995\n", + "epoch:3, total_loss:328.98647291325443\n", + "epoch:4, total_loss:327.8999700915627\n", + "epoch:5, total_loss:310.081065570072\n", + "epoch:6, total_loss:268.5474616202945\n", + "epoch:7, total_loss:259.0999998773958\n", + "epoch:8, total_loss:259.09999987481393\n", + "epoch:9, total_loss:259.09999987211944\n", + "epoch:10, total_loss:259.0999998693056\n" + ] + } + ], + "source": [ + "mnist = DataSet(examples=train_examples[:1000])\n", + "pl = perceptron_learner(mnist, epochs=10, verbose=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.893\n" + ] + } + ], + "source": [ + "print(err_ratio(pl, mnist))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It looks like we have a near 90% error ratio on training data after the network is trained on it. Then we can investigate the model's performance on the test dataset which it never has seen before:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.92\n" + ] + } + ], + "source": [ + "test_mnist = DataSet(examples=test_examples[:100])\n", + "print(err_ratio(pl, test_mnist))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It seems a single layer perceptron learner cannot simulate the structure of the MNIST dataset. To improve accuracy, we may not only increase training epochs but also consider changing to a more complicated network structure." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Neural Network Learner\n", + "\n", + "Although there are many different types of neural networks, the dense neural network we implemented can be treated as a stacked perceptron learner. Adding more layers to the perceptron network could add to the non-linearity to the network thus model will be more flexible when fitting complex data-target relations. Whereas it also adds to the risk of overfitting as the side effect of flexibility.\n", + "\n", + "By default we use dense networks with two hidden layers, which has the architecture as the following:\n", + "\n", + "\n", + "\n", + "In our code, we implemented it as:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initialize the network\n", + "raw_net = [InputLayer(input_size)]\n", + "# add hidden layers\n", + "hidden_input_size = input_size\n", + "for h_size in hidden_layer_sizes:\n", + " raw_net.append(DenseLayer(hidden_input_size, h_size))\n", + " hidden_input_size = h_size\n", + "raw_net.append(DenseLayer(hidden_input_size, output_size))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Where hidden_layer_sizes are the sizes of each hidden layer in a list which can be specified by user. Neural network learner uses gradient descent as default optimizer but user can specify any optimizer when calling `neural_net_learner`. The other special attribute that can be changed in `neural_net_learner` is `batch_size` which controls the number of examples used in each round of update. `neural_net_learner` also returns a `predict` function which calculates prediction by multiplying weight to inputs and applying activation functions.\n", + "\n", + "### Example\n", + "\n", + "Let's also try `neural_net_learner` on the `iris` dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch:10, total_loss:15.931817841643683\n", + "epoch:20, total_loss:8.248422285412149\n", + "epoch:30, total_loss:6.102968668275\n", + "epoch:40, total_loss:5.463915043272969\n", + "epoch:50, total_loss:5.298986288420822\n", + "epoch:60, total_loss:4.032928400456889\n", + "epoch:70, total_loss:3.2628899927346855\n", + "epoch:80, total_loss:6.01336701367312\n", + "epoch:90, total_loss:5.412020420311795\n", + "epoch:100, total_loss:3.1044027319850773\n" + ] + } + ], + "source": [ + "nn = neural_net_learner(iris, epochs=100, learning_rate=0.15, optimizer=gradient_descent, verbose=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly we check the model's accuracy on both training and test dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "error ration on training set: 0.033333333333333326\n" + ] + } + ], + "source": [ + "print(\"error ration on training set:\",err_ratio(nn, iris))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accuracy on test set: 1\n" + ] + } + ], + "source": [ + "tests = [([5.0, 3.1, 0.9, 0.1], 0),\n", + " ([5.1, 3.5, 1.0, 0.0], 0),\n", + " ([4.9, 3.3, 1.1, 0.1], 0),\n", + " ([6.0, 3.0, 4.0, 1.1], 1),\n", + " ([6.1, 2.2, 3.5, 1.0], 1),\n", + " ([5.9, 2.5, 3.3, 1.1], 1),\n", + " ([7.5, 4.1, 6.2, 2.3], 2),\n", + " ([7.3, 4.0, 6.1, 2.4], 2),\n", + " ([7.0, 3.3, 6.1, 2.5], 2)]\n", + "print(\"accuracy on test set:\",grade_learner(nn, tests))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the error ratio on the training set is smaller than the perceptron learner. As the error ratio is relatively small, let's try the model on the MNIST dataset to see whether there will be a larger difference. " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch:10, total_loss:89.0002153455983\n", + "epoch:20, total_loss:87.29675663038348\n", + "epoch:30, total_loss:86.29591779319225\n", + "epoch:40, total_loss:83.78091780128402\n", + "epoch:50, total_loss:82.17091581738829\n", + "epoch:60, total_loss:83.8434277386084\n", + "epoch:70, total_loss:83.55209905561495\n", + "epoch:80, total_loss:83.106898191118\n", + "epoch:90, total_loss:83.37041170165992\n", + "epoch:100, total_loss:82.57013813500876\n" + ] + } + ], + "source": [ + "nn = neural_net_learner(mnist, epochs=100, verbose=10)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.784\n" + ] + } + ], + "source": [ + "print(err_ratio(nn, mnist))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After the model converging, the model's error ratio on the training set is still high. We will introduce the convolutional network in the following chapters to see how it helps improve accuracy on learning this dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/chapter19/Loss Functions and Layers.ipynb b/notebooks/chapter19/Loss Functions and Layers.ipynb new file mode 100644 index 000000000..eda7529ab --- /dev/null +++ b/notebooks/chapter19/Loss Functions and Layers.ipynb @@ -0,0 +1,405 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Loss Function\n", + "\n", + "Loss functions evaluate how well specific algorithm models the given data. Commonly loss functions are used to compare the target data and model's prediction. If predictions deviate too much from actual targets, loss function would output a large value. Usually, loss functions can help other optimization functions to improve the accuracy of the model.\n", + "\n", + "However, there’s no one-size-fits-all loss function to algorithms in machine learning. For each algorithm and machine learning projects, specifying certain loss functions could assist the user in getting better model performance. Here we will demonstrate two loss functions: `mse_loss` and `cross_entropy_loss`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Min Square Error\n", + "\n", + "Min square error(MSE) is the most commonly used loss function in machine learning. The intuition of MSE is straight forward: the distance between two points represents the difference between them. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$MSE = -\\sum_i{(y_i-t_i)^2/n}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Where $y_i$ is the prediction of the ith example and $t_i$ is the target of the ith example. And n is the total number of examples.\n", + "\n", + "Below is a plot of an MSE function where the true target value is 100, and the predicted values range between -10,000 to 10,000. The MSE loss (Y-axis) reaches its minimum value at prediction (X-axis) = 100." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cross-Entropy\n", + "\n", + "For most deep learning applications, we can get away with just one loss function: cross-entropy loss function. We can think of most deep learning algorithms as learning probability distributions and what we are learning is a distribution of predictions $P(y|x)$ given a series of inputs. \n", + "\n", + "To associate input examples x with output examples y, the parameters that maximize the likelihood of the training set should be:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$\\theta^* = argmax_\\theta \\prod_{i=0}^n p(y^{(i)}/x^{(i)})$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Maxmizing the above formula equals to minimizing the negative log form of it:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$\\theta^* = argmin_\\theta -\\sum_{i=0}^n logp(y^{(i)}/x^{(i)})$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It can be proven that the above formula equals to minimizing MSE loss.\n", + "\n", + "The majority of deep learning algorithms use cross-entropy in some way. Classifiers that use deep learning calculate the cross-entropy between categorical distributions over the output class. For a given class, its contribution to the loss is dependent on its probability in the following trend:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples\n", + "\n", + "First let's import necessary packages." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import os, sys\n", + "sys.path = [os.path.abspath(\"../../\")] + sys.path\n", + "from DeepNeuralNet4e import *\n", + "from notebook4e import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Neural Network Layers\n", + "\n", + "Neural networks may be conveniently described using data structures of computational graphs. A computational graph is a directed graph describing how many variables should be computed, with each variable by computed by applying a specific operation to a set of other variables. \n", + "\n", + "In our code, we provide class `NNUnit` as the basic structure of a neural network. The structure of `NNUnit` is simple, it only stores the following information:\n", + "\n", + "- **val**: the value of the current node.\n", + "- **parent**: parents of the current node.\n", + "- **weights**: weights between parent nodes and current node. It should be in the same size as parents.\n", + "\n", + "There is another class `Layer` inheriting from `NNUnit`. A `Layer` object holds a list of nodes that represents all the nodes in a layer. It also has a method `forward` to pass a value through the current layer. Here we will demonstrate several pre-defined types of layers in a Neural Network." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Output Layers\n", + "\n", + "Neural networks need specialized output layers for each type of data we might ask them to produce. For many problems, we need to model discrete variables that have k distinct values instead of just binary variables. For example, models of natural language may predict a single word from among of vocabulary of tens of thousands or even more choices. To represent these distributions, we use a softmax layer:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$P(y=i|x)=softmax(h(x)^TW+b)_i$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "where $W$ is matrix of learned weights of output layer $b$ is a vector of learned biases, and the softmax function is:\n", + "\n", + "$$softmax(z_i)=exp(z_i)/\\sum_i exp(z_i)$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is simple to create a output layer and feed an example into it:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.03205860328008499, 0.08714431874203257, 0.23688281808991013, 0.6439142598879722]\n" + ] + } + ], + "source": [ + "layer = OutputLayer(size=4)\n", + "example = [1,2,3,4]\n", + "print(layer.forward(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The output can be treated like normalized probability when the input of output layer is calculated by probability." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Input Layers\n", + "\n", + "Input layers can be treated like a mapping layer that maps each element of the input vector to each input layer node. The input layer acts as a storage of input vector information which can be used when doing forward propagation.\n", + "\n", + "In our realization of input layers, the size of the input vector and input layer should match." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3]\n" + ] + } + ], + "source": [ + "layer = InputLayer(size=3)\n", + "example = [1,2,3]\n", + "print(layer.forward(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Hidden Layers\n", + "\n", + "While processing an input vector x of the neural network, it performs several intermediate computations before producing the output y. We can think of these intermediate computations as the state of memory during the execution of a multi-step program. We call the intermediate computations hidden because the data does not specify the values of these variables.\n", + "\n", + "Most neural network hidden layers are based on a linear transformation followed by the application of an elementwise nonlinear function called the activation function g:\n", + "\n", + "$$h=g(W+b)$$\n", + "\n", + "where W is a learned matrix of weights and b is a learned set of bias parameters.\n", + "\n", + "Here we pre-defined several activation functions in `utils.py`: `sigmoid`, `relu`, `elu`, `tanh` and `leaky_relu`. They are all inherited from the `Activation` class. You can get the value of the function or its derivative at a certain point of x:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sigmoid at 0: 0.5\n", + "Deriavation of sigmoid at 0: 0\n" + ] + } + ], + "source": [ + "s = sigmoid()\n", + "print(\"Sigmoid at 0:\", s.f(0))\n", + "print(\"Deriavation of sigmoid at 0:\", s.derivative(0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create a hidden layer object, there are several attributes need to be specified:\n", + "\n", + "- **in_size**: the input vector size of each hidden layer node.\n", + "- **out_size**: the size of the output vector of the hidden layer. Thus each node will hide the weight of the size of (in_size). The weights will be initialized randomly.\n", + "- **activation**: the activation function used for this layer.\n", + "\n", + "Now let's demonstrate how a dense hidden layer works briefly:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.21990266877137224, 0.2038864498984756, 0.5543443697256466]\n" + ] + } + ], + "source": [ + "layer = DenseLayer(in_size=4, out_size=3, activation=sigmoid())\n", + "example = [1,2,3,4]\n", + "print(layer.forward(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This layer mapped input of size 4 to output of size 3. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Convolutional Layers\n", + "\n", + "The convolutional layer is similar to the hidden layer except they use a different forward strategy. The convolutional layer takes an input of multiple channels and does convolution on each channel with a pre-defined kernel function. Thus the output of the convolutional layer will still be with the same number of channels. If we image each input as an image, then channels represent its color model such as RGB. The output will still have the same color model as the input.\n", + "\n", + "Now let's try the one-dimensional convolution layer:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[array([3.9894228, 3.9894228, 3.9894228]), array([3.9894228, 3.9894228, 3.9894228]), array([3.9894228, 3.9894228, 3.9894228])]\n" + ] + } + ], + "source": [ + "layer = ConvLayer1D(size=3, kernel_size=3)\n", + "example = [[1]*3 for _ in range(3)]\n", + "print(layer.forward(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which can be deemed as a one-dimensional image with three channels." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pooling Layers\n", + "\n", + "Pooling layers can be treated as a special kind of convolutional layer that uses a special kind of kernel to extract a certain value in the kernel region. Here we use max-pooling to report the maximum value in each group." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[3, 4], [4, 4], [4, 4]]\n" + ] + } + ], + "source": [ + "layer = MaxPoolingLayer1D(size=3, kernel_size=3)\n", + "example = [[1,2,3,4], [2,3,4,1],[3,4,1,2]]\n", + "print(layer.forward(example))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that each time kernel picks up the maximum value in its region." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/chapter19/Optimizer and Backpropagation.ipynb b/notebooks/chapter19/Optimizer and Backpropagation.ipynb new file mode 100644 index 000000000..faa459ac5 --- /dev/null +++ b/notebooks/chapter19/Optimizer and Backpropagation.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Optimization Algorithms\n", + "\n", + "Training a neural network consists of modifying the network’s parameters to minimize the cost function on the training set. In principle, any kind of optimization algorithm could be used. In practice, modern neural networks are almost always trained with some variant of stochastic gradient descent(SGD). Here we will provide two optimization algorithms: SGD and Adam optimizer.\n", + "\n", + "## Stochastic Gradient Descent\n", + "\n", + "The goal of an optimization algorithm is to nd the value of the parameter to make loss function very low. For some types of models, an optimization algorithm might find the global minimum value of loss function, but for neural network, the most efficient way to converge loss function to a local minimum is to minimize loss function according to each example.\n", + "\n", + "Gradient descent uses the following update rule to minimize loss function:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$\\theta^{(t+1)} = \\theta^{(t)}-\\alpha\\nabla_\\theta L(\\theta^{(t)})$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "where t is the time step of the algorithm and $\\alpha$ is the learning rate. But this rule could be very costly when $L(\\theta)$ is defined as a sum across the entire training set. Using SGD can accelerate the learning process as we can use only a batch of examples to update the parameters. \n", + "\n", + "We implemented the gradient descent algorithm, which can be viewed with the following code:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import os, sys\n", + "sys.path = [os.path.abspath(\"../../\")] + sys.path\n", + "from DeepNeuralNet4e import *\n", + "from notebook4e import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " Codestin Search App\n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def gradient_descent(dataset, net, loss, epochs=1000, l_rate=0.01,  batch_size=1):\n",
+       "    """\n",
+       "    gradient descent algorithm to update the learnable parameters of a network.\n",
+       "    :return: the updated network.\n",
+       "    """\n",
+       "    # init data\n",
+       "    examples = dataset.examples\n",
+       "\n",
+       "    for e in range(epochs):\n",
+       "        total_loss = 0\n",
+       "        random.shuffle(examples)\n",
+       "        weights = [[node.weights for node in layer.nodes] for layer in net]\n",
+       "\n",
+       "        for batch in get_batch(examples, batch_size):\n",
+       "\n",
+       "            inputs, targets = init_examples(batch, dataset.inputs, dataset.target, len(net[-1].nodes))\n",
+       "            # compute gradients of weights\n",
+       "            gs, batch_loss = BackPropagation(inputs, targets, weights, net, loss)\n",
+       "            # update weights with gradient descent\n",
+       "            weights = vector_add(weights, scalar_vector_product(-l_rate, gs))\n",
+       "            total_loss += batch_loss\n",
+       "            # update the weights of network each batch\n",
+       "            for i in range(len(net)):\n",
+       "                if weights[i]:\n",
+       "                    for j in range(len(weights[i])):\n",
+       "                        net[i].nodes[j].weights = weights[i][j]\n",
+       "\n",
+       "        if (e+1) % 10 == 0:\n",
+       "            print("epoch:{}, total_loss:{}".format(e+1,total_loss))\n",
+       "    return net\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(gradient_descent)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There several key elements need to specify when using a `gradient_descent` optimizer:\n", + "\n", + "- **dataset**: A dataset object we used in the previous chapter, such as `iris` and `orings`.\n", + "- **net**: A neural network object which we will cover in the next chapter.\n", + "- **loss**: The loss function used in representing accuracy.\n", + "- **epochs**: How many rounds the training set is used.\n", + "- **l_rate**: learning rate.\n", + "- **batch_size**: The number of examples is used in each update. When very small batch size is used, gradient descent and be treated as SGD." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adam Optimizer\n", + "\n", + "To mitigate some of the problems caused by the fact that the gradient ignores the second derivatives, some optimization algorithms incorporate the idea of momentum which keeps a running average of the gradients of past mini-batches. Thus Adam optimizer maintains a table saving the previous gradient result.\n", + "\n", + "To view the pseudocode and the implementation, you can use the following codes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pseudocode(adam_optimizer)\n", + "psource(adam_optimizer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are several attributes to specify when using Adam optimizer that is different from gradient descent: rho and delta. These parameters determine the percentage of the last iteration is memorized. For more details of how this algorithm work, please refer to the article [here](https://arxiv.org/abs/1412.6980).\n", + "\n", + "In the Stanford course on deep learning for computer vision, the Adam algorithm is suggested as the default optimization method for deep learning applications: \n", + ">In practice Adam is currently recommended as the default algorithm to use, and often works slightly better than RMSProp. However, it is often also worth trying SGD+Nesterov Momentum as an alternative." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Backpropagation\n", + "\n", + "The above algorithms are optimization algorithms: they update parameters like $\\theta$ to get smaller loss values. And back-propagation is the method to calculate the gradient for each layer. For complicated models like deep neural networks, the gradients can not be calculated directly as there are enormous array-valued variables.\n", + "\n", + "Fortunately, back-propagation can calculate the gradients briefly which we can interpret as calculating gradients from the last layer to the first which is the inverse process to the forwarding procedure. The derivation of the loss function is passed to previous layers to make them changing toward the direction of minimizing the loss function." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Applying optimizers and back-propagation algorithm together, we can update the weights of a neural network to minimize the loss function with alternatively doing forward and back-propagation process. Here is a figure form [here](https://medium.com/datathings/neural-networks-and-backpropagation-explained-in-a-simple-way-f540a3611f5e) describing how a neural network updates its weights:\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In our implementation, all the steps are integrated into the optimizer objects. The forward-backward process of passing information through the whole neural network is put into the method `BackPropagation`. You can view the code with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "psource(BackPropagation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The demonstration of optimizers and back-propagation algorithm will be made together with neural network learners." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/chapter19/RNN.ipynb b/notebooks/chapter19/RNN.ipynb new file mode 100644 index 000000000..2b06b83a2 --- /dev/null +++ b/notebooks/chapter19/RNN.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# RNN\n", + "\n", + "## Overview\n", + "\n", + "When human is thinking, they are thinking based on the understanding of previous time steps but not from scratch. Traditional neural networks can’t do this, and it seems like a major shortcoming. For example, imagine you want to do sentimental analysis of some texts. It will be unclear if the traditional network cannot recognize the short phrase and sentences.\n", + "\n", + "Recurrent neural networks address this issue. They are networks with loops in them, allowing information to persist.\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A recurrent neural network can be thought of as multiple copies of the same network, each passing a message to a successor. Consider what happens if we unroll the above loop:\n", + " \n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As demonstrated in the book, recurrent neural networks may be connected in many different ways: sequences in the input, the output, or in the most general case both.\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementation\n", + "\n", + "In our case, we implemented rnn with modules offered by the package of `keras`. To use `keras` and our module, you must have both `tensorflow` and `keras` installed as a prerequisite. `keras` offered very well defined high-level neural networks API which allows for easy and fast prototyping. `keras` supports many different types of networks such as convolutional and recurrent neural networks as well as user-defined networks. About how to get started with `keras`, please read the [tutorial](https://keras.io/).\n", + "\n", + "To view our implementation of a simple rnn, please use the following code:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "import os, sys\n", + "sys.path = [os.path.abspath(\"../../\")] + sys.path\n", + "from DeepNeuralNet4e import *\n", + "from notebook4e import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " Codestin Search App\n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def simple_rnn_learner(train_data, val_data, epochs=2):\n",
+       "    """\n",
+       "    rnn example for text sentimental analysis\n",
+       "    :param train_data: a tuple of (training data, targets)\n",
+       "            Training data: ndarray taking training examples, while each example is coded by embedding\n",
+       "            Targets: ndarry taking targets of each example. Each target is mapped to an integer.\n",
+       "    :param val_data: a tuple of (validation data, targets)\n",
+       "    :return: a keras model\n",
+       "    """\n",
+       "\n",
+       "    total_inputs = 5000\n",
+       "    input_length = 500\n",
+       "\n",
+       "    # init data\n",
+       "    X_train, y_train = train_data\n",
+       "    X_val, y_val = val_data\n",
+       "\n",
+       "    # init a the sequential network (embedding layer, rnn layer, dense layer)\n",
+       "    model = Sequential()\n",
+       "    model.add(Embedding(total_inputs, 32, input_length=input_length))\n",
+       "    model.add(SimpleRNN(units=128))\n",
+       "    model.add(Dense(1, activation='sigmoid'))\n",
+       "    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])\n",
+       "\n",
+       "    # train the model\n",
+       "    model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=epochs, batch_size=128, verbose=2)\n",
+       "\n",
+       "    return model\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(simple_rnn_learner)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`train_data` and `val_data` are needed when creating a simple rnn learner. Both attributes take lists of examples and the targets in a tuple. Please note that we build the network by adding layers to a `Sequential()` model which means data are passed through the network one by one. `SimpleRNN` layer is the key layer of rnn which acts the recursive role. Both `Embedding` and `Dense` layers before and after the rnn layer are used to map inputs and outputs to data in rnn form. And the optimizer used in this case is the Adam optimizer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example\n", + "\n", + "Here is an example of how we train the rnn network made with `keras`. In this case, we used the IMDB dataset which can be viewed [here](https://keras.io/datasets/#imdb-movie-reviews-sentiment-classification) in detail. In short, the dataset is consist of movie reviews in text and their labels of sentiment (positive/negative). After loading the dataset we use `keras_dataset_loader` to split it into training, validation and test datasets." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from keras.datasets import imdb\n", + "data = imdb.load_data(num_words=5000)\n", + "train, val, test = keras_dataset_loader(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we build and train the rnn model for 10 epochs:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train on 24990 samples, validate on 25000 samples\n", + "Epoch 1/10\n", + " - 45s - loss: 0.6877 - acc: 0.5406 - val_loss: 0.6731 - val_acc: 0.6045\n", + "Epoch 2/10\n", + " - 52s - loss: 0.6441 - acc: 0.6241 - val_loss: 0.6258 - val_acc: 0.6300\n", + "Epoch 3/10\n", + " - 50s - loss: 0.5275 - acc: 0.7393 - val_loss: 0.5547 - val_acc: 0.7229\n", + "Epoch 4/10\n", + " - 50s - loss: 0.4703 - acc: 0.7908 - val_loss: 0.4851 - val_acc: 0.7740\n", + "Epoch 5/10\n", + " - 48s - loss: 0.4021 - acc: 0.8279 - val_loss: 0.4517 - val_acc: 0.8121\n", + "Epoch 6/10\n", + " - 55s - loss: 0.4043 - acc: 0.8269 - val_loss: 0.4532 - val_acc: 0.8042\n", + "Epoch 7/10\n", + " - 51s - loss: 0.4242 - acc: 0.8315 - val_loss: 0.5257 - val_acc: 0.7785\n", + "Epoch 8/10\n", + " - 58s - loss: 0.4534 - acc: 0.7964 - val_loss: 0.5347 - val_acc: 0.7323\n", + "Epoch 9/10\n", + " - 51s - loss: 0.3821 - acc: 0.8354 - val_loss: 0.4671 - val_acc: 0.8054\n", + "Epoch 10/10\n", + " - 56s - loss: 0.3283 - acc: 0.8691 - val_loss: 0.4523 - val_acc: 0.8067\n" + ] + } + ], + "source": [ + "model = simple_rnn_learner(train, val, epochs=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The accuracy of the training dataset and validation dataset are both over 80% which is very promising. Now let's try on some random examples in the test set:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Autoencoder\n", + "\n", + "Autoencoders are an unsupervised learning technique in which we leverage neural networks for the task of representation learning. It works by compressing the input into a latent-space representation, to do transformations on the data. \n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Autoencoders are learned automatically from data examples. It means that it is easy to train specialized instances of the algorithm that will perform well on a specific type of input and that it does not require any new engineering, only the appropriate training data.\n", + "\n", + "Autoencoders have different architectures for different kinds of data. Here we only provide a simple example of a vanilla encoder, which means they're only one hidden layer in the network:\n", + "\n", + "\n", + "\n", + "You can view the source code by:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " Codestin Search App\n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
def auto_encoder_learner(inputs, encoding_size, epochs=200):\n",
+       "    """simple example of linear auto encoder learning producing the input itself.\n",
+       "    :param inputs: a batch of input data in np.ndarray type\n",
+       "    :param encoding_size: int, the size of encoding layer"""\n",
+       "\n",
+       "    # init data\n",
+       "    input_size = len(inputs[0])\n",
+       "\n",
+       "    # init model\n",
+       "    model = Sequential()\n",
+       "    model.add(Dense(encoding_size, input_dim=input_size, activation='relu', kernel_initializer='random_uniform',bias_initializer='ones'))\n",
+       "    model.add(Dense(input_size, activation='relu', kernel_initializer='random_uniform', bias_initializer='ones'))\n",
+       "    # update model with sgd\n",
+       "    sgd = optimizers.SGD(lr=0.01)\n",
+       "    model.compile(loss='mean_squared_error', optimizer=sgd, metrics=['accuracy'])\n",
+       "\n",
+       "    # train the model\n",
+       "    model.fit(inputs, inputs, epochs=epochs, batch_size=10, verbose=2)\n",
+       "\n",
+       "    return model\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "psource(auto_encoder_learner)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It shows we added two dense layers to the network structures." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/chapter19/images/autoencoder.png b/notebooks/chapter19/images/autoencoder.png new file mode 100644 index 000000000..cd216e9f7 Binary files /dev/null and b/notebooks/chapter19/images/autoencoder.png differ diff --git a/notebooks/chapter19/images/backprop.png b/notebooks/chapter19/images/backprop.png new file mode 100644 index 000000000..8d53530e6 Binary files /dev/null and b/notebooks/chapter19/images/backprop.png differ diff --git a/notebooks/chapter19/images/corss_entropy_plot.png b/notebooks/chapter19/images/corss_entropy_plot.png new file mode 100644 index 000000000..8212405e7 Binary files /dev/null and b/notebooks/chapter19/images/corss_entropy_plot.png differ diff --git a/notebooks/chapter19/images/mse_plot.png b/notebooks/chapter19/images/mse_plot.png new file mode 100644 index 000000000..fd58f9db9 Binary files /dev/null and b/notebooks/chapter19/images/mse_plot.png differ diff --git a/notebooks/chapter19/images/nn.png b/notebooks/chapter19/images/nn.png new file mode 100644 index 000000000..673b9338b Binary files /dev/null and b/notebooks/chapter19/images/nn.png differ diff --git a/notebooks/chapter19/images/nn_steps.png b/notebooks/chapter19/images/nn_steps.png new file mode 100644 index 000000000..4a596133b Binary files /dev/null and b/notebooks/chapter19/images/nn_steps.png differ diff --git a/notebooks/chapter19/images/perceptron.png b/notebooks/chapter19/images/perceptron.png new file mode 100644 index 000000000..68d2a258a Binary files /dev/null and b/notebooks/chapter19/images/perceptron.png differ diff --git a/notebooks/chapter19/images/rnn_connections.png b/notebooks/chapter19/images/rnn_connections.png new file mode 100644 index 000000000..c72d459b8 Binary files /dev/null and b/notebooks/chapter19/images/rnn_connections.png differ diff --git a/notebooks/chapter19/images/rnn_unit.png b/notebooks/chapter19/images/rnn_unit.png new file mode 100644 index 000000000..e4ebabf2b Binary files /dev/null and b/notebooks/chapter19/images/rnn_unit.png differ diff --git a/notebooks/chapter19/images/rnn_units.png b/notebooks/chapter19/images/rnn_units.png new file mode 100644 index 000000000..5724f5d46 Binary files /dev/null and b/notebooks/chapter19/images/rnn_units.png differ diff --git a/notebooks/chapter19/images/vanilla.png b/notebooks/chapter19/images/vanilla.png new file mode 100644 index 000000000..db7a45f9a Binary files /dev/null and b/notebooks/chapter19/images/vanilla.png differ diff --git a/utils4e.py b/utils4e.py index c66020b18..dd90e49ca 100644 --- a/utils4e.py +++ b/utils4e.py @@ -360,7 +360,7 @@ def num_or_str(x): # TODO: rename as `atom` def euclidean_distance(X, Y): - return math.sqrt(sum((x - y)**2 for x, y in zip(X, Y))) + return math.sqrt(sum((x - y)**2 for x, y in zip(X, Y) if x and y)) def rms_error(X, Y): @@ -413,12 +413,7 @@ def random_weights(min_value, max_value, num_weights): def conv1D(X, K): """1D convolution. X: input vector; K: kernel vector""" - K = K[::-1] - res = [] - for x in range(len(X)): - res += [sum([X[x+k]*K[k]] for k in K)] - return res - + return np.convolve(X, K, mode='same') def GaussianKernel(size=3): @@ -658,7 +653,6 @@ def print_table(table, header=None, sep=' ', numfmt='{}'): table = [[numfmt.format(x) if isnumber(x) else x for x in row] for row in table] - sizes = list( map(lambda seq: max(map(len, seq)), list(zip(*[map(str, row) for row in table]))))