Repository navigation
Expand file tree
/
Copy pathgradient_descent.py
More file actions
158 lines (128 loc) · 5.92 KB
/
Copy pathgradient_descent.py
File metadata and controls
158 lines (128 loc) · 5.92 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
from __future__ import annotations
from typing import Callable, NoReturn
import numpy as np
from base_module import BaseModule
from base_learning_rate import BaseLR
from learning_rate import FixedLR
OUTPUT_VECTOR_TYPE = ["last", "best", "average"]
def default_callback(**kwargs) -> NoReturn:
pass
class GradientDescent:
"""
Gradient Descent algorithm
Attributes:
-----------
learning_rate_: BaseLR
Learning rate strategy for retrieving the learning rate at each iteration t of the algorithm
tol_: float
The stopping criterion. Training stops when the Euclidean norm of w^(t)-w^(t-1) is less than
specified tolerance
max_iter_: int
The maximum number of GD iterations to be performed before stopping training
out_type_: str
Type of returned solution:
- `last`: returns the point reached at the last GD iteration
- `best`: returns the point achieving the lowest objective
- `average`: returns the average point over the GD iterations
callback_: Callable[[...], None], default=default_callback
A callable function to be called after each update of the model while fitting to given data.
Callable function receives as input any argument relevant for the current GD iteration. Arguments
are specified in the `GradientDescent.fit` function
"""
def __init__(self,
learning_rate: BaseLR = FixedLR(1e-3),
tol: float = 1e-5,
max_iter: int = 1000,
out_type: str = "last",
callback: Callable[[GradientDescent, ...], None] = default_callback):
"""
Instantiate a new instance of the GradientDescent class
Parameters
----------
learning_rate: BaseLR, default=FixedLR(1e-3)
Learning rate strategy for retrieving the learning rate at each iteration t of the algorithm
tol: float, default=1e-5
The stopping criterion. Training stops when the Euclidean norm of w^(t)-w^(t-1) is less than
specified tolerance
max_iter: int, default=1000
The maximum number of GD iterations to be performed before stopping training
out_type: str, default="last"
Type of returned solution. Supported types are specified in class attributes
callback: Callable[[...], None], default=default_callback
A callable function to be called after each update of the model while fitting to given data.
Callable function receives as input any argument relevant for the current GD iteration. Arguments
are specified in the `GradientDescent.fit` function
"""
self.learning_rate_ = learning_rate
if out_type not in OUTPUT_VECTOR_TYPE:
raise ValueError("output_type not supported")
self.out_type_ = out_type
self.tol_ = tol
self.max_iter_ = max_iter
self.callback_ = callback
def fit(self, f: BaseModule, X: np.ndarray, y: np.ndarray):
"""
Optimize module using Gradient Descent iterations over given input samples and responses
Parameters
----------
f : BaseModule
Module of objective to optimize using GD iterations
X : ndarray of shape (n_samples, n_features)
Input data to optimize module over
y : ndarray of shape (n_samples, )
Responses of input data to optimize module over
Returns
-------
solution: ndarray of shape (n_features)
Obtained solution for module optimization, according to the specified self.out_type_
Notes
-----
- Optimization is performed as long as self.max_iter_ has not been reached and that
Euclidean norm of w^(t)-w^(t-1) is more than the specified self.tol_
- At each iteration the learning rate is specified according to self.learning_rate_.lr_step
- At the end of each iteration the self.callback_ function is called passing self and the
following named arguments:
- solver: GradientDescent
self, the current instance of GradientDescent
- weights: ndarray of shape specified by module's weights
Current weights of objective
- val: ndarray of shape specified by module's compute_output function
Value of objective function at current point, over given data X, y
- grad: ndarray of shape specified by module's compute_jacobian function
Module's jacobian with respect to the weights and at current point, over given data X,y
- t: int
Current GD iteration
- eta: float
Learning rate used at current iteration
- delta: float
Euclidean norm of w^(t)-w^(t-1)
"""
best = f.weights.copy()
best_val = f.compute_output(X=X, y=y)
last = f.weights.copy()
sum = f.weights.copy()
iterations = 0
for i in range(self.max_iter_):
gradient = f.compute_jacobian(X=X, y=y)
eta = self.learning_rate_.lr_step(t=i)
new_weights = f.weights - eta * gradient
f.weights = new_weights
perf = f.compute_output(X=X, y=y)
delta = np.linalg.norm(new_weights - last, ord=2)
self.callback_(solver=self, weights=f.weights, val=perf, grad=gradient, t=i, eta=eta, delta=delta)
if perf < best_val:
best_val = perf
best = f.weights.copy()
last = f.weights.copy()
sum += f.weights
iterations += 1
if delta < self.tol_:
break
if self.out_type_ == "last":
return last
elif self.out_type_ == "best":
return best
elif self.out_type_ == "average":
return sum / iterations
else:
raise ValueError("Invalid output type")