Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jxareas
GitHub Repository: jxareas/Machine-Learning-Notebooks
Path: blob/master/1_Supervised_Machine_Learning/Week 3. Classification/plt_quad_logistic.py
2826 views
1
"""
2
plt_quad_logistic.py
3
interactive plot and supporting routines showing logistic regression
4
"""
5
6
import time
7
from matplotlib import cm
8
import matplotlib.colors as colors
9
from matplotlib.gridspec import GridSpec
10
from matplotlib.widgets import Button
11
from matplotlib.patches import FancyArrowPatch
12
from ipywidgets import Output
13
from lab_utils_common import np, plt, dlc, dlcolors, sigmoid, compute_cost_matrix, gradient_descent
14
15
# for debug
16
#output = Output() # sends hidden error messages to display when using widgets
17
#display(output)
18
19
class plt_quad_logistic:
20
''' plots a quad plot showing logistic regression '''
21
# pylint: disable=too-many-instance-attributes
22
# pylint: disable=too-many-locals
23
# pylint: disable=missing-function-docstring
24
# pylint: disable=attribute-defined-outside-init
25
def __init__(self, x_train,y_train, w_range, b_range):
26
# setup figure
27
fig = plt.figure( figsize=(10,6))
28
fig.canvas.toolbar_visible = False
29
fig.canvas.header_visible = False
30
fig.canvas.footer_visible = False
31
fig.set_facecolor('#ffffff') #white
32
gs = GridSpec(2, 2, figure=fig)
33
ax0 = fig.add_subplot(gs[0, 0])
34
ax1 = fig.add_subplot(gs[0, 1])
35
ax2 = fig.add_subplot(gs[1, 0], projection='3d')
36
ax3 = fig.add_subplot(gs[1,1])
37
pos = ax3.get_position().get_points() ##[[lb_x,lb_y], [rt_x, rt_y]]
38
h = 0.05
39
width = 0.2
40
axcalc = plt.axes([pos[1,0]-width, pos[1,1]-h, width, h]) #lx,by,w,h
41
ax = np.array([ax0, ax1, ax2, ax3, axcalc])
42
self.fig = fig
43
self.ax = ax
44
self.x_train = x_train
45
self.y_train = y_train
46
47
self.w = 0. #initial point, non-array
48
self.b = 0.
49
50
# initialize subplots
51
self.dplot = data_plot(ax[0], x_train, y_train, self.w, self.b)
52
self.con_plot = contour_and_surface_plot(ax[1], ax[2], x_train, y_train, w_range, b_range, self.w, self.b)
53
self.cplot = cost_plot(ax[3])
54
55
# setup events
56
self.cid = fig.canvas.mpl_connect('button_press_event', self.click_contour)
57
self.bcalc = Button(axcalc, 'Run Gradient Descent \nfrom current w,b (click)', color=dlc["dlorange"])
58
self.bcalc.on_clicked(self.calc_logistic)
59
60
# @output.capture() # debug
61
def click_contour(self, event):
62
''' called when click in contour '''
63
if event.inaxes == self.ax[1]: #contour plot
64
self.w = event.xdata
65
self.b = event.ydata
66
67
self.cplot.re_init()
68
self.dplot.update(self.w, self.b)
69
self.con_plot.update_contour_wb_lines(self.w, self.b)
70
self.con_plot.path.re_init(self.w, self.b)
71
72
self.fig.canvas.draw()
73
74
# @output.capture() # debug
75
def calc_logistic(self, event):
76
''' called on run gradient event '''
77
for it in [1, 8,16,32,64,128,256,512,1024,2048,4096]:
78
w, self.b, J_hist = gradient_descent(self.x_train.reshape(-1,1), self.y_train.reshape(-1,1),
79
np.array(self.w).reshape(-1,1), self.b, 0.1, it,
80
logistic=True, lambda_=0, verbose=False)
81
self.w = w[0,0]
82
self.dplot.update(self.w, self.b)
83
self.con_plot.update_contour_wb_lines(self.w, self.b)
84
self.con_plot.path.add_path_item(self.w,self.b)
85
self.cplot.add_cost(J_hist)
86
87
time.sleep(0.3)
88
self.fig.canvas.draw()
89
90
91
class data_plot:
92
''' handles data plot '''
93
# pylint: disable=missing-function-docstring
94
# pylint: disable=attribute-defined-outside-init
95
def __init__(self, ax, x_train, y_train, w, b):
96
self.ax = ax
97
self.x_train = x_train
98
self.y_train = y_train
99
self.m = x_train.shape[0]
100
self.w = w
101
self.b = b
102
103
self.plt_tumor_data()
104
self.draw_logistic_lines(firsttime=True)
105
self.mk_cost_lines(firsttime=True)
106
107
self.ax.autoscale(enable=False) # leave plot scales the same after initial setup
108
109
def plt_tumor_data(self):
110
x = self.x_train
111
y = self.y_train
112
pos = y == 1
113
neg = y == 0
114
self.ax.scatter(x[pos], y[pos], marker='x', s=80, c = 'red', label="malignant")
115
self.ax.scatter(x[neg], y[neg], marker='o', s=100, label="benign", facecolors='none',
116
edgecolors=dlc["dlblue"],lw=3)
117
self.ax.set_ylim(-0.175,1.1)
118
self.ax.set_ylabel('y')
119
self.ax.set_xlabel('Tumor Size')
120
self.ax.set_title("Logistic Regression on Categorical Data")
121
122
def update(self, w, b):
123
self.w = w
124
self.b = b
125
self.draw_logistic_lines()
126
self.mk_cost_lines()
127
128
def draw_logistic_lines(self, firsttime=False):
129
if not firsttime:
130
self.aline[0].remove()
131
self.bline[0].remove()
132
self.alegend.remove()
133
134
xlim = self.ax.get_xlim()
135
x_hat = np.linspace(*xlim, 30)
136
y_hat = sigmoid(np.dot(x_hat.reshape(-1,1), self.w) + self.b)
137
self.aline = self.ax.plot(x_hat, y_hat, color=dlc["dlblue"],
138
label="y = sigmoid(z)")
139
f_wb = np.dot(x_hat.reshape(-1,1), self.w) + self.b
140
self.bline = self.ax.plot(x_hat, f_wb, color=dlc["dlorange"], lw=1,
141
label=f"z = {np.squeeze(self.w):0.2f}x+({self.b:0.2f})")
142
self.alegend = self.ax.legend(loc='upper left')
143
144
def mk_cost_lines(self, firsttime=False):
145
''' makes vertical cost lines'''
146
if not firsttime:
147
for artist in self.cost_items:
148
artist.remove()
149
self.cost_items = []
150
cstr = f"cost = (1/{self.m})*("
151
ctot = 0
152
label = 'cost for point'
153
addedbreak = False
154
for p in zip(self.x_train,self.y_train):
155
f_wb_p = sigmoid(self.w*p[0]+self.b)
156
c_p = compute_cost_matrix(p[0].reshape(-1,1), p[1],np.array(self.w), self.b, logistic=True, lambda_=0, safe=True)
157
c_p_txt = c_p
158
a = self.ax.vlines(p[0], p[1],f_wb_p, lw=3, color=dlc["dlpurple"], ls='dotted', label=label)
159
label='' #just one
160
cxy = [p[0], p[1] + (f_wb_p-p[1])/2]
161
b = self.ax.annotate(f'{c_p_txt:0.1f}', xy=cxy, xycoords='data',color=dlc["dlpurple"],
162
xytext=(5, 0), textcoords='offset points')
163
cstr += f"{c_p_txt:0.1f} +"
164
if len(cstr) > 38 and addedbreak is False:
165
cstr += "\n"
166
addedbreak = True
167
ctot += c_p
168
self.cost_items.extend((a,b))
169
ctot = ctot/(len(self.x_train))
170
cstr = cstr[:-1] + f") = {ctot:0.2f}"
171
## todo.. figure out how to get this textbox to extend to the width of the subplot
172
c = self.ax.text(0.05,0.02,cstr, transform=self.ax.transAxes, color=dlc["dlpurple"])
173
self.cost_items.append(c)
174
175
176
class contour_and_surface_plot:
177
''' plots combined in class as they have similar operations '''
178
# pylint: disable=missing-function-docstring
179
# pylint: disable=attribute-defined-outside-init
180
def __init__(self, axc, axs, x_train, y_train, w_range, b_range, w, b):
181
182
self.x_train = x_train
183
self.y_train = y_train
184
self.axc = axc
185
self.axs = axs
186
187
#setup useful ranges and common linspaces
188
b_space = np.linspace(*b_range, 100)
189
w_space = np.linspace(*w_range, 100)
190
191
# get cost for w,b ranges for contour and 3D
192
tmp_b,tmp_w = np.meshgrid(b_space,w_space)
193
z = np.zeros_like(tmp_b)
194
for i in range(tmp_w.shape[0]):
195
for j in range(tmp_w.shape[1]):
196
z[i,j] = compute_cost_matrix(x_train.reshape(-1,1), y_train, tmp_w[i,j], tmp_b[i,j],
197
logistic=True, lambda_=0, safe=True)
198
if z[i,j] == 0:
199
z[i,j] = 1e-9
200
201
### plot contour ###
202
CS = axc.contour(tmp_w, tmp_b, np.log(z),levels=12, linewidths=2, alpha=0.7,colors=dlcolors)
203
axc.set_title('log(Cost(w,b))')
204
axc.set_xlabel('w', fontsize=10)
205
axc.set_ylabel('b', fontsize=10)
206
axc.set_xlim(w_range)
207
axc.set_ylim(b_range)
208
self.update_contour_wb_lines(w, b, firsttime=True)
209
axc.text(0.7,0.05,"Click to choose w,b", bbox=dict(facecolor='white', ec = 'black'), fontsize = 10,
210
transform=axc.transAxes, verticalalignment = 'center', horizontalalignment= 'center')
211
212
#Surface plot of the cost function J(w,b)
213
axs.plot_surface(tmp_w, tmp_b, z, cmap = cm.jet, alpha=0.3, antialiased=True)
214
axs.plot_wireframe(tmp_w, tmp_b, z, color='k', alpha=0.1)
215
axs.set_xlabel("$w$")
216
axs.set_ylabel("$b$")
217
axs.zaxis.set_rotate_label(False)
218
axs.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
219
axs.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
220
axs.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
221
axs.set_zlabel("J(w, b)", rotation=90)
222
axs.view_init(30, -120)
223
224
axs.autoscale(enable=False)
225
axc.autoscale(enable=False)
226
227
self.path = path(self.w,self.b, self.axc) # initialize an empty path, avoids existance check
228
229
def update_contour_wb_lines(self, w, b, firsttime=False):
230
self.w = w
231
self.b = b
232
cst = compute_cost_matrix(self.x_train.reshape(-1,1), self.y_train, np.array(self.w), self.b,
233
logistic=True, lambda_=0, safe=True)
234
235
# remove lines and re-add on contour plot and 3d plot
236
if not firsttime:
237
for artist in self.dyn_items:
238
artist.remove()
239
a = self.axc.scatter(self.w, self.b, s=100, color=dlc["dlblue"], zorder= 10, label="cost with \ncurrent w,b")
240
b = self.axc.hlines(self.b, self.axc.get_xlim()[0], self.w, lw=4, color=dlc["dlpurple"], ls='dotted')
241
c = self.axc.vlines(self.w, self.axc.get_ylim()[0] ,self.b, lw=4, color=dlc["dlpurple"], ls='dotted')
242
d = self.axc.annotate(f"Cost: {cst:0.2f}", xy= (self.w, self.b), xytext = (4,4), textcoords = 'offset points',
243
bbox=dict(facecolor='white'), size = 10)
244
#Add point in 3D surface plot
245
e = self.axs.scatter3D(self.w, self.b, cst , marker='X', s=100)
246
247
self.dyn_items = [a,b,c,d,e]
248
249
250
class cost_plot:
251
""" manages cost plot for plt_quad_logistic """
252
# pylint: disable=missing-function-docstring
253
# pylint: disable=attribute-defined-outside-init
254
def __init__(self,ax):
255
self.ax = ax
256
self.ax.set_ylabel("log(cost)")
257
self.ax.set_xlabel("iteration")
258
self.costs = []
259
self.cline = self.ax.plot(0,0, color=dlc["dlblue"])
260
261
def re_init(self):
262
self.ax.clear()
263
self.__init__(self.ax)
264
265
def add_cost(self,J_hist):
266
self.costs.extend(J_hist)
267
self.cline[0].remove()
268
self.cline = self.ax.plot(self.costs)
269
270
class path:
271
''' tracks paths during gradient descent on contour plot '''
272
# pylint: disable=missing-function-docstring
273
# pylint: disable=attribute-defined-outside-init
274
def __init__(self, w, b, ax):
275
''' w, b at start of path '''
276
self.path_items = []
277
self.w = w
278
self.b = b
279
self.ax = ax
280
281
def re_init(self, w, b):
282
for artist in self.path_items:
283
artist.remove()
284
self.path_items = []
285
self.w = w
286
self.b = b
287
288
def add_path_item(self, w, b):
289
a = FancyArrowPatch(
290
posA=(self.w, self.b), posB=(w, b), color=dlc["dlblue"],
291
arrowstyle='simple, head_width=5, head_length=10, tail_width=0.0',
292
)
293
self.ax.add_artist(a)
294
self.path_items.append(a)
295
self.w = w
296
self.b = b
297
298
#-----------
299
# related to the logistic gradient descent lab
300
#----------
301
302
def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
303
""" truncates color map """
304
new_cmap = colors.LinearSegmentedColormap.from_list(
305
'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),
306
cmap(np.linspace(minval, maxval, n)))
307
return new_cmap
308
309
def plt_prob(ax, w_out,b_out):
310
""" plots a decision boundary but include shading to indicate the probability """
311
#setup useful ranges and common linspaces
312
x0_space = np.linspace(0, 4 , 100)
313
x1_space = np.linspace(0, 4 , 100)
314
315
# get probability for x0,x1 ranges
316
tmp_x0,tmp_x1 = np.meshgrid(x0_space,x1_space)
317
z = np.zeros_like(tmp_x0)
318
for i in range(tmp_x0.shape[0]):
319
for j in range(tmp_x1.shape[1]):
320
z[i,j] = sigmoid(np.dot(w_out, np.array([tmp_x0[i,j],tmp_x1[i,j]])) + b_out)
321
322
323
cmap = plt.get_cmap('Blues')
324
new_cmap = truncate_colormap(cmap, 0.0, 0.5)
325
pcm = ax.pcolormesh(tmp_x0, tmp_x1, z,
326
norm=cm.colors.Normalize(vmin=0, vmax=1),
327
cmap=new_cmap, shading='nearest', alpha = 0.9)
328
ax.figure.colorbar(pcm, ax=ax)
329
330