Repository navigation
Expand file tree
/
Copy pathclassifiers.py
More file actions
367 lines (294 loc) · 12.2 KB
/
Copy pathclassifiers.py
File metadata and controls
367 lines (294 loc) · 12.2 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
from __future__ import annotations
from typing import Callable
from typing import NoReturn
from base_estimator import BaseEstimator
import numpy as np
from numpy.linalg import det, inv
def default_callback(fit: Perceptron, x: np.ndarray, y: int):
pass
class Perceptron(BaseEstimator):
"""
Perceptron half-space classifier
Finds a separating hyperplane for given linearly separable data.
Attributes
----------
include_intercept: bool, default = True
Should fitted model include an intercept or not
max_iter_: int, default = 1000
Maximum number of passes over training data
coefs_: ndarray of shape (n_features,) or (n_features+1,)
Coefficients vector fitted by Perceptron algorithm. To be set in
`Perceptron.fit` function.
callback_: Callable[[Perceptron, np.ndarray, int], None]
A callable to be called after each update of the model while fitting to given data
Callable function should receive as input a Perceptron instance, current sample and current response
"""
def __init__(self,
include_intercept: bool = True,
max_iter: int = 1000,
callback: Callable[[Perceptron, np.ndarray, int], None] = default_callback):
"""
Instantiate a Perceptron classifier
Parameters
----------
include_intercept: bool, default=True
Should fitted model include an intercept or not
max_iter: int, default = 1000
Maximum number of passes over training data
callback: Callable[[Perceptron, np.ndarray, int], None]
A callable to be called after each update of the model while fitting to given data
Callable function should receive as input a Perceptron instance, current sample and current response
"""
super().__init__()
self.include_intercept_ = include_intercept
self.max_iter_ = max_iter
self.callback_ = callback
self.coefs_ = None
def _fit(self, X: np.ndarray, y: np.ndarray) -> NoReturn:
"""
Fit a halfspace to to given samples. Iterate over given data as long as there exists a sample misclassified
or that did not reach `self.max_iter_`
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to fit an estimator for
y : ndarray of shape (n_samples, )
Responses of input data to fit to
Notes
-----
Fits model with or without an intercept depending on value of `self.fit_intercept_`
"""
self.fitted_ = True
if self.include_intercept_:
X = np.c_[np.ones(len(X)), X]
self.coefs_ = np.zeros(X.shape[1])
for h in range(self.max_iter_):
no_errors = True
for i in range(len(X)):
if y[i] * (X[i] @ self.coefs_) <= 0:
self.coefs_ += y[i] * X[i]
no_errors = False
self.callback_(self, X[i], y[i])
if no_errors:
break
def _predict(self, X: np.ndarray) -> np.ndarray:
"""
Predict responses for given samples using fitted estimator
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to predict responses for
Returns
-------
responses : ndarray of shape (n_samples, )
Predicted responses of given samples
"""
if self.include_intercept_ and X.shape[1] != self.coefs_.shape[0]:
X = np.c_[np.ones(len(X)), X]
return np.sign(X @ self.coefs_)
def _loss(self, X: np.ndarray, y: np.ndarray) -> float:
"""
Evaluate performance under misclassification loss function
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Test samples
y : ndarray of shape (n_samples, )
True labels of test samples
Returns
-------
loss : float
Performance under missclassification loss function
"""
from loss_functions import misclassification_error
return misclassification_error(y, self._predict(X))
class LDA(BaseEstimator):
"""
Linear Discriminant Analysis (LDA) classifier
Attributes
----------
self.classes_ : np.ndarray of shape (n_classes,)
The different labels classes. To be set in `LDA.fit`
self.mu_ : np.ndarray of shape (n_classes,n_features)
The estimated features means for each class. To be set in `LDA.fit`
self.cov_ : np.ndarray of shape (n_features,n_features)
The estimated features covariance. To be set in `LDA.fit`
self._cov_inv : np.ndarray of shape (n_features,n_features)
The inverse of the estimated features covariance. To be set in `LDA.fit`
self.pi_: np.ndarray of shape (n_classes)
The estimated class probabilities. To be set in `GaussianNaiveBayes.fit`
"""
def __init__(self):
"""
Instantiate an LDA classifier
"""
super().__init__()
self.classes_, self.mu_, self.cov_, self._cov_inv, self.pi_ = None, None, None, None, None
self.fitted_ = False
def _fit(self, X: np.ndarray, y: np.ndarray) -> NoReturn:
"""
fits an LDA model.
Estimates gaussian for each label class - Different mean vector, same covariance
matrix with dependent features.
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to fit an estimator for
y : ndarray of shape (n_samples, )
Responses of input data to fit to
"""
self.classes_, self.pi_ = np.unique(y, return_counts=True)
self.pi_ = self.pi_ / len(y)
self.mu_ = np.array([np.mean(X[y == cls], axis=0) for cls in self.classes_])
self.cov_ = np.sum([(X[y == cls] - self.mu_[i]).T @ (X[y == cls] - self.mu_[i])
for cls, i in enumerate(self.classes_)], axis=0) / (len(X) - len(self.classes_))
self._cov_inv = inv(self.cov_)
self.fitted_ = True
def _predict(self, X: np.ndarray) -> np.ndarray:
"""
Predict responses for given samples using fitted estimator
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to predict responses for
Returns
-------
responses : ndarray of shape (n_samples, )
Predicted responses of given samples
"""
return self.classes_[np.argmax(self.likelihood(X), axis=1)]
def likelihood(self, X: np.ndarray) -> np.ndarray:
"""
Calculate the likelihood of a given data over the estimated model
Parameters
----------
X : np.ndarray of shape (n_samples, n_features)
Input data to calculate its likelihood over the different classes.
Returns
-------
likelihoods : np.ndarray of shape (n_samples, n_classes)
The likelihood for each sample under each of the classes
"""
if not self.fitted_:
raise ValueError("Estimator must first be fitted before calling `likelihood` function")
n_samples, n_features = X.shape
n_classes = len(self.classes_)
likelihoods = np.zeros((n_samples, n_classes))
for i in range(n_samples):
likelihoods[i] = [
np.power(2 * np.pi, -n_features * 0.5) *
np.power(np.linalg.det(self.cov_), -0.5) *
np.exp(-0.5 * np.dot(np.dot((X[i] - self.mu_[k]), self._cov_inv), (X[i] - self.mu_[k]).T)) *
self.pi_[k]
for k in range(n_classes)
]
return likelihoods
def _loss(self, X: np.ndarray, y: np.ndarray) -> float:
"""
Evaluate performance under misclassification loss function
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Test samples
y : ndarray of shape (n_samples, )
True labels of test samples
Returns
-------
loss : float
Performance under missclassification loss function
"""
from loss_functions import misclassification_error
return misclassification_error(y, self.predict(X))
class GaussianNaiveBayes(BaseEstimator):
"""
Gaussian Naive-Bayes classifier
"""
def __init__(self):
"""
Instantiate a Gaussian Naive Bayes classifier
Attributes
----------
self.classes_ : np.ndarray of shape (n_classes,)
The different labels classes. To be set in `GaussianNaiveBayes.fit`
self.mu_ : np.ndarray of shape (n_classes,n_features)
The estimated features means for each class. To be set in `GaussianNaiveBayes.fit`
self.vars_ : np.ndarray of shape (n_classes, n_features)
The estimated features variances for each class. To be set in `GaussianNaiveBayes.fit`
self.pi_: np.ndarray of shape (n_classes)
The estimated class probabilities. To be set in `GaussianNaiveBayes.fit`
"""
super().__init__()
self.classes_, self.mu_, self.vars_, self.pi_ = None, None, None, None
def _fit(self, X: np.ndarray, y: np.ndarray) -> NoReturn:
"""
fits a gaussian naive bayes model
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to fit an estimator for
y : ndarray of shape (n_samples, )
Responses of input data to fit to
"""
self.classes_, self.pi_ = np.unique(y, return_counts=True)
self.pi_ = self.pi_ / len(y)
self.vars_ = np.array([np.var(X[y == cls], axis=0, ddof=1) for cls in self.classes_])
self.mu_ = np.array([np.mean(X[y == cls], axis=0) for cls in self.classes_])
self.fitted_ = True
def _predict(self, X: np.ndarray) -> np.ndarray:
"""
Predict responses for given samples using fitted estimator
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Input data to predict responses for
Returns
-------
responses : ndarray of shape (n_samples, )
Predicted responses of given samples
"""
return self.classes_[np.argmax(self.likelihood(X), axis=1)]
def likelihood(self, X: np.ndarray) -> np.ndarray:
"""
Calculate the likelihood of a given data over the estimated model
Parameters
----------
X : np.ndarray of shape (n_samples, n_features)
Input data to calculate its likelihood over the different classes.
Returns
-------
likelihoods : np.ndarray of shape (n_samples, n_classes)
The likelihood for each sample under each of the classes
"""
if not self.fitted_:
raise ValueError("Estimator must first be fitted before calling `likelihood` function")
n_samples, n_features = X.shape
n_classes = len(self.classes_)
likelihoods = np.zeros((n_samples, n_classes))
for i in range(n_samples):
for k in range(n_classes):
cov = np.diag(self.vars_[k])
diff = X[i] - self.mu_[k]
exponent = -0.5 * np.dot(np.dot(diff, np.linalg.inv(cov)), diff.T)
likelihoods[i, k] = (
np.power(2 * np.pi, -n_features * 0.5) *
np.power(np.linalg.det(cov), -0.5) *
np.exp(exponent) *
self.pi_[k]
)
return likelihoods
def _loss(self, X: np.ndarray, y: np.ndarray) -> float:
"""
Evaluate performance under misclassification loss function
Parameters
----------
X : ndarray of shape (n_samples, n_features)
Test samples
y : ndarray of shape (n_samples, )
True labels of test samples
Returns
-------
loss : float
Performance under missclassification loss function
"""
from loss_functions import misclassification_error
return misclassification_error(y, self.predict(X))