Path: blob/main/C2 - Advanced Learning Algorithms/week2/C2W2A1/lab_utils_common.py
3520 views
"""1lab_utils_common2contains common routines and variable definitions3used by all the labs in this week.4by contrast, specific, large plotting routines will be in separate files5and are generally imported into the week where they are used.6those files will import this file7"""8import copy9import math10import numpy as np11import matplotlib.pyplot as plt12from matplotlib.patches import FancyArrowPatch13from ipywidgets import Output14from matplotlib.widgets import Button, CheckButtons1516np.set_printoptions(precision=2)1718dlc = dict(dlblue = '#0096ff', dlorange = '#FF9300', dldarkred='#C00000', dlmagenta='#FF40FF', dlpurple='#7030A0', dldarkblue = '#0D5BDC', dlmedblue='#4285F4')19dlblue = '#0096ff'; dlorange = '#FF9300'; dldarkred='#C00000'; dlmagenta='#FF40FF'; dlpurple='#7030A0'; dldarkblue = '#0D5BDC'; dlmedblue='#4285F4'20dlcolors = [dlblue, dlorange, dldarkred, dlmagenta, dlpurple]21plt.style.use('./deeplearning.mplstyle')2223def sigmoid(z):24"""25Compute the sigmoid of z2627Parameters28----------29z : array_like30A scalar or numpy array of any size.3132Returns33-------34g : array_like35sigmoid(z)36"""37z = np.clip( z, -500, 500 ) # protect against overflow38g = 1.0/(1.0+np.exp(-z))3940return g4142##########################################################43# Regression Routines44##########################################################4546def predict_logistic(X, w, b):47""" performs prediction """48return sigmoid(X @ w + b)4950def predict_linear(X, w, b):51""" performs prediction """52return X @ w + b5354def compute_cost_logistic(X, y, w, b, lambda_=0, safe=False):55"""56Computes cost using logistic loss, non-matrix version5758Args:59X (ndarray): Shape (m,n) matrix of examples with n features60y (ndarray): Shape (m,) target values61w (ndarray): Shape (n,) parameters for prediction62b (scalar): parameter for prediction63lambda_ : (scalar, float) Controls amount of regularization, 0 = no regularization64safe : (boolean) True-selects under/overflow safe algorithm65Returns:66cost (scalar): cost67"""6869m,n = X.shape70cost = 0.071for i in range(m):72z_i = np.dot(X[i],w) + b #(n,)(n,) or (n,) ()73if safe: #avoids overflows74cost += -(y[i] * z_i ) + log_1pexp(z_i)75else:76f_wb_i = sigmoid(z_i) #(n,)77cost += -y[i] * np.log(f_wb_i) - (1 - y[i]) * np.log(1 - f_wb_i) # scalar78cost = cost/m7980reg_cost = 081if lambda_ != 0:82for j in range(n):83reg_cost += (w[j]**2) # scalar84reg_cost = (lambda_/(2*m))*reg_cost8586return cost + reg_cost878889def log_1pexp(x, maximum=20):90''' approximate log(1+exp^x)91https://stats.stackexchange.com/questions/475589/numerical-computation-of-cross-entropy-in-practice92Args:93x : (ndarray Shape (n,1) or (n,) input94out : (ndarray Shape matches x output ~= np.log(1+exp(x))95'''9697out = np.zeros_like(x,dtype=float)98i = x <= maximum99ni = np.logical_not(i)100101out[i] = np.log(1 + np.exp(x[i]))102out[ni] = x[ni]103return out104105106def compute_cost_matrix(X, y, w, b, logistic=False, lambda_=0, safe=True):107"""108Computes the cost using using matrices109Args:110X : (ndarray, Shape (m,n)) matrix of examples111y : (ndarray Shape (m,) or (m,1)) target value of each example112w : (ndarray Shape (n,) or (n,1)) Values of parameter(s) of the model113b : (scalar ) Values of parameter of the model114verbose : (Boolean) If true, print out intermediate value f_wb115Returns:116total_cost: (scalar) cost117"""118m = X.shape[0]119y = y.reshape(-1,1) # ensure 2D120w = w.reshape(-1,1) # ensure 2D121if logistic:122if safe: #safe from overflow123z = X @ w + b #(m,n)(n,1)=(m,1)124cost = -(y * z) + log_1pexp(z)125cost = np.sum(cost)/m # (scalar)126else:127f = sigmoid(X @ w + b) # (m,n)(n,1) = (m,1)128cost = (1/m)*(np.dot(-y.T, np.log(f)) - np.dot((1-y).T, np.log(1-f))) # (1,m)(m,1) = (1,1)129cost = cost[0,0] # scalar130else:131f = X @ w + b # (m,n)(n,1) = (m,1)132cost = (1/(2*m)) * np.sum((f - y)**2) # scalar133134reg_cost = (lambda_/(2*m)) * np.sum(w**2) # scalar135136total_cost = cost + reg_cost # scalar137138return total_cost # scalar139140def compute_gradient_matrix(X, y, w, b, logistic=False, lambda_=0):141"""142Computes the gradient using matrices143144Args:145X : (ndarray, Shape (m,n)) matrix of examples146y : (ndarray Shape (m,) or (m,1)) target value of each example147w : (ndarray Shape (n,) or (n,1)) Values of parameters of the model148b : (scalar ) Values of parameter of the model149logistic: (boolean) linear if false, logistic if true150lambda_: (float) applies regularization if non-zero151Returns152dj_dw: (array_like Shape (n,1)) The gradient of the cost w.r.t. the parameters w153dj_db: (scalar) The gradient of the cost w.r.t. the parameter b154"""155m = X.shape[0]156y = y.reshape(-1,1) # ensure 2D157w = w.reshape(-1,1) # ensure 2D158159f_wb = sigmoid( X @ w + b ) if logistic else X @ w + b # (m,n)(n,1) = (m,1)160err = f_wb - y # (m,1)161dj_dw = (1/m) * (X.T @ err) # (n,m)(m,1) = (n,1)162dj_db = (1/m) * np.sum(err) # scalar163164dj_dw += (lambda_/m) * w # regularize # (n,1)165166return dj_db, dj_dw # scalar, (n,1)167168def gradient_descent(X, y, w_in, b_in, alpha, num_iters, logistic=False, lambda_=0, verbose=True, Trace=True):169"""170Performs batch gradient descent to learn theta. Updates theta by taking171num_iters gradient steps with learning rate alpha172173Args:174X (ndarray): Shape (m,n) matrix of examples175y (ndarray): Shape (m,) or (m,1) target value of each example176w_in (ndarray): Shape (n,) or (n,1) Initial values of parameters of the model177b_in (scalar): Initial value of parameter of the model178logistic: (boolean) linear if false, logistic if true179lambda_: (float) applies regularization if non-zero180alpha (float): Learning rate181num_iters (int): number of iterations to run gradient descent182183Returns:184w (ndarray): Shape (n,) or (n,1) Updated values of parameters; matches incoming shape185b (scalar): Updated value of parameter186"""187# An array to store cost J and w's at each iteration primarily for graphing later188J_history = []189w = copy.deepcopy(w_in) #avoid modifying global w within function190b = b_in191w = w.reshape(-1,1) #prep for matrix operations192y = y.reshape(-1,1)193last_cost = np.Inf194195for i in range(num_iters):196197# Calculate the gradient and update the parameters198dj_db,dj_dw = compute_gradient_matrix(X, y, w, b, logistic, lambda_)199200# Update Parameters using w, b, alpha and gradient201w = w - alpha * dj_dw202b = b - alpha * dj_db203204# Save cost J at each iteration205ccost = compute_cost_matrix(X, y, w, b, logistic, lambda_)206if Trace and i<100000: # prevent resource exhaustion207J_history.append( ccost )208209# Print cost every at intervals 10 times or as many iterations if < 10210if i% math.ceil(num_iters / 10) == 0:211if verbose: print(f"Iteration {i:4d}: Cost {ccost} ")212if verbose ==2: print(f"dj_db, dj_dw = {dj_db: 0.3f}, {dj_dw.reshape(-1)}")213214if ccost == last_cost:215alpha = alpha/10216print(f" alpha now {alpha}")217last_cost = ccost218219return w.reshape(w_in.shape), b, J_history #return final w,b and J history for graphing220221def zscore_normalize_features(X):222"""223computes X, zcore normalized by column224225Args:226X (ndarray): Shape (m,n) input data, m examples, n features227228Returns:229X_norm (ndarray): Shape (m,n) input normalized by column230mu (ndarray): Shape (n,) mean of each feature231sigma (ndarray): Shape (n,) standard deviation of each feature232"""233# find the mean of each column/feature234mu = np.mean(X, axis=0) # mu will have shape (n,)235# find the standard deviation of each column/feature236sigma = np.std(X, axis=0) # sigma will have shape (n,)237# element-wise, subtract mu for that column from each example, divide by std for that column238X_norm = (X - mu) / sigma239240return X_norm, mu, sigma241242#check our work243#from sklearn.preprocessing import scale244#scale(X_orig, axis=0, with_mean=True, with_std=True, copy=True)245246######################################################247# Common Plotting Routines248######################################################249250251def plot_data(X, y, ax, pos_label="y=1", neg_label="y=0", s=80, loc='best' ):252""" plots logistic data with two axis """253# Find Indices of Positive and Negative Examples254pos = y == 1255neg = y == 0256pos = pos.reshape(-1,) #work with 1D or 1D y vectors257neg = neg.reshape(-1,)258259# Plot examples260ax.scatter(X[pos, 0], X[pos, 1], marker='x', s=s, c = 'red', label=pos_label)261ax.scatter(X[neg, 0], X[neg, 1], marker='o', s=s, label=neg_label, facecolors='none', edgecolors=dlblue, lw=3)262ax.legend(loc=loc)263264ax.figure.canvas.toolbar_visible = False265ax.figure.canvas.header_visible = False266ax.figure.canvas.footer_visible = False267268def plt_tumor_data(x, y, ax):269""" plots tumor data on one axis """270pos = y == 1271neg = y == 0272273ax.scatter(x[pos], y[pos], marker='x', s=80, c = 'red', label="malignant")274ax.scatter(x[neg], y[neg], marker='o', s=100, label="benign", facecolors='none', edgecolors=dlblue,lw=3)275ax.set_ylim(-0.175,1.1)276ax.set_ylabel('y')277ax.set_xlabel('Tumor Size')278ax.set_title("Logistic Regression on Categorical Data")279280ax.figure.canvas.toolbar_visible = False281ax.figure.canvas.header_visible = False282ax.figure.canvas.footer_visible = False283284# Draws a threshold at 0.5285def draw_vthresh(ax,x):286""" draws a threshold """287ylim = ax.get_ylim()288xlim = ax.get_xlim()289ax.fill_between([xlim[0], x], [ylim[1], ylim[1]], alpha=0.2, color=dlblue)290ax.fill_between([x, xlim[1]], [ylim[1], ylim[1]], alpha=0.2, color=dldarkred)291ax.annotate("z >= 0", xy= [x,0.5], xycoords='data',292xytext=[30,5],textcoords='offset points')293d = FancyArrowPatch(294posA=(x, 0.5), posB=(x+3, 0.5), color=dldarkred,295arrowstyle='simple, head_width=5, head_length=10, tail_width=0.0',296)297ax.add_artist(d)298ax.annotate("z < 0", xy= [x,0.5], xycoords='data',299xytext=[-50,5],textcoords='offset points', ha='left')300f = FancyArrowPatch(301posA=(x, 0.5), posB=(x-3, 0.5), color=dlblue,302arrowstyle='simple, head_width=5, head_length=10, tail_width=0.0',303)304ax.add_artist(f)305306307#-----------------------------------------------------308# common interactive plotting routines309#-----------------------------------------------------310311class button_manager:312''' Handles some missing features of matplotlib check buttons313on init:314creates button, links to button_click routine,315calls call_on_click with active index and firsttime=True316on click:317maintains single button on state, calls call_on_click318'''319320#@output.capture() # debug321def __init__(self,fig, dim, labels, init, call_on_click):322'''323dim: (list) [leftbottom_x,bottom_y,width,height]324labels: (list) for example ['1','2','3','4','5','6']325init: (list) for example [True, False, False, False, False, False]326'''327self.fig = fig328self.ax = plt.axes(dim) #lx,by,w,h329self.init_state = init330self.call_on_click = call_on_click331self.button = CheckButtons(self.ax,labels,init)332self.button.on_clicked(self.button_click)333self.status = self.button.get_status()334self.call_on_click(self.status.index(True),firsttime=True)335336#@output.capture() # debug337def reinit(self):338self.status = self.init_state339self.button.set_active(self.status.index(True)) #turn off old, will trigger update and set to status340341#@output.capture() # debug342def button_click(self, event):343''' maintains one-on state. If on-button is clicked, will process correctly '''344#new_status = self.button.get_status()345#new = [self.status[i] ^ new_status[i] for i in range(len(self.status))]346#newidx = new.index(True)347self.button.eventson = False348self.button.set_active(self.status.index(True)) #turn off old or reenable if same349self.button.eventson = True350self.status = self.button.get_status()351self.call_on_click(self.status.index(True))352353354