-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinear_regression.py
More file actions
376 lines (216 loc) · 11.1 KB
/
Copy pathlinear_regression.py
File metadata and controls
376 lines (216 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
import pandas as pd
import numpy as np
from tqdm import tqdm, tqdm_notebook
class Regression_Model :
'''
A class used to represent a polynomial regression model in two variables.
~~~~~~~~~~~~~~~
Attributes
------------
featureMatrix : the matrix containing features as columns according to the degree of polynomial (in two variables) that must be fitted to the data
targetAttribute : the target values for altitude that we want to predict
initialWeights : may be passed by the user as pre-trained weights. Optional parameter, defaults to None.
lambdaReg : regularization parameter. Defaults to 0.
regularizationType : specifies the type of regularization. Type may be 'L1' , 'L2' , or 'None'. Defaults to 'None'.
learningRate : initial learning rate for use in batch gradient descent. Defaults to 0.01.
epochs : number of times over the whole training set to run stochastic gradient descent. Defaults to 2.
isBatch : takes a value 1, if the object uses batch gradient descent for weight estimation, and 0 if it uses stochastic gradient descent.
Irrelevent in the case of weight estimation using normal equation. Defaults to 1.
maxTolerance : the maximum tolerance value for difference in the cost of the current iteration and previous iteration in batch gradient descent.
Used for conditional termination of the batch gradient descent algorithm. Defaults to 1e-10.
maxIter : the maximum number of iterations for which the batch gradient descent algorithm should run. Used to force termination. Defaults to 10000.
Methods
---------
hypothesis_function (X, theta) : returns prediction, for specified weight matrix and feature matrix
cost_function () : returns halved mean squared error
update_weights (X_i, Y_i) : updates the attribute theta of the object according to the appropriate weight update rule
batch_gradient_descent () : performs batch gradient descent on the object's attributes
stochastic_gradient_descent () : performs stochastic gradient descent on the object's attributes
normal_equation () : estimates object attribute theta using an analytical method
'''
def __init__ (self, featureMatrix, targetAttribute, initialWeights = None, lambdaReg = 0, regularizationType = 'None', learningRate = 0.01, epochs = 2, isBatch = 1, maxTolerance = 1e-10, maxIters = 10000) :
'''
Initializes the object attributes with user specified values (or) default values where applicable.
Refer class description for an elaboration of the object attributes.
Necessary Parameters
----------------------
featureMatrix : ndarray
targetAttribute : ndarray
Optional Parameters
---------------------
initialWeights : ndarray, defaults to None
lambdaReg : float, defaults to 0
regularizationType : str, defaults to 'None'
learningRate : float, defaults to 0.01
epochs : int, defaults to 2
isBatch : int, defaults to 1
maxTolerance : float, defaults to 1e-10
maxIters : int, defaults to 10000
'''
self.X = featureMatrix
self.yTrain = targetAttribute
self.theta = initialWeights
self.lambdaReg = lambdaReg
self.regularizationType = regularizationType
self.alpha = learningRate
self.epochs = epochs
self.isBatch = isBatch
self.maxTolerance = maxTolerance
self.maxIters = maxIters
def hypothesis_function (self, X, theta) :
'''
Returns the predicted value of target variable altitude, using the weights of the model.
It uses the formula Y_predicted = featureMatrix * theta
Arguments
----------
X : feature matrix
theta : weights of model
Returns
---------
The predicted values for the target variable altitude
'''
return X.dot(theta)
def cost_function (self) :
'''
Returns the mean square error (halved), using the feature matrix, weights and training labels.
This function handles the case for no regularization, L1 regularization and L2 regularization.
Defaults to no regularization.
Returns
---------
cost : the halved mean square error for predicting the target attribute by using the weights associated with this object.
'''
diff = self.hypothesis_function(self.X, self.theta) - self.yTrain
m = self.X.shape[0]
cost = np.sum(diff**2)/(2*m)
if self.regularizationType == 'L2' :
cost += (lambda_reg/2) * np.sum(theta**2)
if self.regularizationType == 'L1' :
cost += (lambda_reg) * np.sum(np.abs(theta))
return cost
def update_weights (self,X_i = None, Y_i = None) :
'''
Applies the appropriate weight update rule to the theta attribute associated with the instance.
This function handles the case for no regularization, L1 regularization and L2 regularization,
for both batch gradient descent and stochastic gradient descent.
Defaults to no regularization.
Arguments
-----------
X_i : The features for one training data point, as needed for stochastic gradient descent. Defaults to None.
Y_i : The target value for one training data point, as needed for stochastic gradient descent. Defaults to None.
'''
if self.isBatch :
diff = self.hypothesis_function (self.X, self.theta) - self.yTrain
m = self.X.shape[0]
if self.regularizationType == 'L2' :
self.theta *= (1 - self.alpha * self.lambdaReg)
self.theta -= ((self.alpha/m)*(np.dot((self.X).T,diff)))
elif self.regularizationType == 'L1' :
temp_theta = np.copy(self.theta)
temp_theta[temp_theta > 0] = 1
temp_theta[temp_theta < 0] = -1
self.theta -= (self.alpha * self.lambdaReg * temp_theta)
self.theta -= ((self.alpha/m)*(np.dot((self.X).T,diff)))
else :
self.theta -= ((self.alpha/m)*(np.dot((self.X).T,diff)))
else:
diff = np.sum(np.multiply(self.theta, X_i)) - Y_i
if self.regularizationType == 'L2' :
self.theta *= (1 - self.alpha * self.lambdaReg)
self.theta -= (self.alpha * diff * X_i)
elif self.regularizationType == 'L1' :
temp_theta = np.copy(self.theta)
temp_theta[temp_theta > 0] = 1
temp_theta[temp_theta < 0] = -1
self.theta -= (self.alpha * self.lambdaReg * temp_theta)
self.theta -= (self.alpha * diff * X_i)
else :
self.theta -= (self.alpha * diff * X_i)
def batch_gradient_descent (self) :
'''
Performs batch gradient descent for fitting the polynomial of a desired degree (as appropriated by the feature matrix),
to the data.
If no initial weights are specified, it initializes the weights to random values.
The maximum number of iterations (maxIter) and maximum acceptable tolerance level (maxTolerance) for decrease in
mean square error (halved) in subsequent weight updates, jointly act as the stopping critera for batch gradient descent.
This means that the algorithm will stop either if the number of iterations has reached the maximum limit, or sooner, if
the change in cost for subsequent iterations is less than the maximum accepted tolerance.
We also employ the 'Bold Driver Technique' for dynamic learning rate adaptation.
This works as follows:
If the cost in the current iteration has decreased as compared to the cost in the previous iteration, we increase alpha
by 5 % of its current value.
However, if the cost in the current iteration is found to have increased, it means that the algorithm has overshot the minima.
In this event, we reset the weights to their previous values before the update, and penalize alpha by decreasing it to 50 % of
its current value.
Thus, as alpha is adjusted accordingly, it eliminates the need to optimize this hyperparameter by trying different values randomly.
It also makes the descent faster, as alpha is dynamically adjusted in order for it to speed up.
Batch gradient descent may be performed with L1, L2 or no regularization. By default, we use no regularization.
The lambda parameter for L1 and L2 regularization, however, may be determined by running the model for different values
of lambdaReg.
No weights are returned, as the weights are changed in the theta attribute of the object, and may be consequently accessed.
Returns
---------
cost : A list with the halved mean squared errors as calculated for each iteration throughout the course of gradient descent.
alphas : A list of learning rates following the bold driver technique used during gradient descent, for the purpose of analysis.
'''
if not self.theta:
self.theta = np.random.rand (self.X.shape[1],1)
m = self.X.shape[0]
prev_theta = np.copy (self.theta)
cost = []
alphas = []
for i in tqdm(range(1, self.maxIters+1)):
prev_theta[:] = self.theta
self.update_weights()
cost.append(self.cost_function())
# print(cost[-1])
# print('\nalpha',self.alpha)
alphas.append(self.alpha)
# To change alpha or reset weights according to bold driver method for dynamic learning rate adaptation
if len(cost)>1 and cost[-1]-cost[-2] < 0:
self.alpha*=1.05
elif len(cost)>1 and cost[-1]-cost[-2]>0:
# print('\n\nbefore', self.theta)
self.theta[:] = prev_theta
# print('\n\nafter', self.theta)
self.alpha*=0.5
del alphas[-1]
del cost[-1]
continue
# Termination condition for gradient descent
if len(cost)>1 and (cost[-2]-cost[-1]) <= self.maxTolerance:
break
return cost, alphas
def stochastic_gradient_descent (self) :
'''
Performs stochastic gradient descent on the object's attributes and updates its theta attribute accordingly.
If no initial weights are specified, it initializes the weights to random values.
Stochastic gradient descent runs over the entire set of training data points as many times as specified by the
object attribute epochs.
Does not return weights, as they are updated in, and may be consequently accessed by, the object parameter theta.
If the cost for each iteration is not required to be calculated, it may be avoided for faster execution of the descent.
Returns
---------
cost : A list with the halved mean squared errors as calculated for each iteration throughout the course of stochastic gradient descent.
'''
self.isBatch = 0
if not self.theta :
self.theta = np.random.rand(self.X.shape[1],1)
m = self.X.shape[0]
cost = []
for epoch in range(self.epochs):
for i in tqdm(range(m)):
X_i = np.reshape(self.X[i],(self.X[i].shape[0],1))
Y_i = self.yTrain[i]
self.update_weights (X_i, Y_i)
### Comment the line below for calculating cost, if not required, for fastest completion of stochastic gradient descent.
cost.append(self.cost_function())
return cost
def normal_equation (self):
'''
Estimates the weights for fitting the polynomial of the desired degree, using an analytical method.
The formula used is :
theta = inverse (X_transpose * X) * (X_transpose * Y)
where X is the feature matrix, and Y represents the target attribute
This method involves no training of the parameters theta, and is a direct formula-based method.
'''
self.theta = np.matmul(np.linalg.pinv(np.matmul(self.X.T,self.X)),np.matmul(self.X.T,self.yTrain))