-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
323 lines (268 loc) · 10.9 KB
/
Copy pathmodels.py
File metadata and controls
323 lines (268 loc) · 10.9 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
from abc import ABC, abstractmethod
import numpy as np
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
ROWS = 0
COLS = 1
FIRST = 0
POSITIVE = 1
NEGATIVE = -1
NO_LABEL = 0
MU_POS = 0
MU_NEG = 1
SIGMA = 2
PR = 3
SAMPLES_NUM = 1
class Model(ABC):
"""
An abstract class for a Classification model
"""
@abstractmethod
def fit(self, X, y):
"""
Fits the model with a given training set.
Stores the model in self.model
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
pass
@abstractmethod
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
pass
def score(self, X, y):
"""
Calculates parameters of quality of the model for set of samples X
:param X: Unlabeled test set of m' samples - numpy array of shape (d x m')
:param y: true labels of the set - numpy array of shape (m',)
:return: a dictionary of (num_samples, "error", accuracy, FPR, TPR, precision, recall)
"""
y_hat = self.predict(X)
tp, tn, fp, fn = self.__get_pred_stats(y, y_hat)
pos_amount = y[y > 0].size
neg_amount = y.size - pos_amount
err_rate = 0 if y.size == 0 else (fp + fn) / y.size
acc = 0 if y.size == 0 else (tp + tn) / y.size
fpr = 0 if fp == 0 else fp / neg_amount
tpr = 0 if tp == 0 else tp / pos_amount
precision = 0 if tp == 0 else tp / (tp + fp)
recall = 0 if tp == 0 else tp / pos_amount
return {"num_samples": y.size, "error": err_rate, "accuracy": acc, "FPR": fpr, "TPR": tpr,
"precision": precision, "recall": recall}
@staticmethod
def __get_pred_stats(y, y_hat):
"""
Calculates stats for measuring the model quality: True positives, True negatives,
False positives, False negatives
:param y: true labels, array of shape (m,)
:param y_hat: estimated labels, array of shape (m,)
:return: 4 numpy arrays in the shape of y: TP, TN, FP, FN
"""
if y.size == 0:
return 0, 0, 0, 0
correct_pos = y_hat[y > 0]
correct_neg = y_hat[y <= 0]
est_pos = y[y_hat > 0]
est_neg = y[y_hat <= 0]
tp = np.count_nonzero(correct_pos > 0)
tn = np.count_nonzero(correct_neg <= 0)
fp = np.count_nonzero(est_pos <= 0)
fn = np.count_nonzero(est_neg > 0)
return tp, tn, fp, fn
class Perceptron(Model):
"""
A classifier implementing the Perceptron learning algorithm
"""
def __init__(self):
"""
Initializing the model with an empty vector for the weights
"""
self.model = np.array([])
def fit(self, X, y):
"""
Fits the model with a given training set
Stores the weights vector in self.model
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
train_set = np.concatenate((np.ones_like(y).reshape(1, -1), X))
self.model = np.zeros((train_set.shape[ROWS], 1))
y_hat = np.zeros_like(y)
constrains = (y * y_hat) <= 0
while constrains.any():
fix_idx = np.where(constrains)[FIRST][FIRST] # np.where returns tuple size 1
self.model += (y[fix_idx] * train_set.T[fix_idx]).reshape(self.model.shape)
y_hat = self.model.T @ train_set
constrains = np.squeeze((y * y_hat) <= 0)
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
test_set = np.concatenate((np.ones(X.shape[COLS]).reshape(1, -1), X))
y_hat = np.sign(self.model.T @ test_set)
y_hat = np.where(y_hat == NO_LABEL, NEGATIVE, y_hat)
return y_hat.squeeze()
class LDA(Model):
"""
A class for LDA classifier
"""
def __init__(self):
"""
Initializes the model with an empty array
"""
self.model = []
def fit(self, X, y):
"""
Fits the model with a given training set.
Stores the array [positive mean, negative mean, Sigma^-1, Pr(y=1)] in self.model
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
pos_pr = self.__get_positive_p(y)
mu_pos, mu_neg, inv_sigma = self.__get_statistics(X, y)
self.model = [mu_pos, mu_neg, inv_sigma, pos_pr]
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
pos_delta, neg_delta = self.__get_delta(X)
compare = pos_delta > neg_delta
y_hat = np.where(compare, POSITIVE, NEGATIVE)
return y_hat.squeeze()
def __get_delta(self, X):
"""
Calculates the discriminant functions for given samples
:param X: numpy array of shape (d x m)
:return: positive delta, negative delta - numpy arrays of shape (m x 1)
"""
mu_pos, mu_neg, inv_sigma, pos_pr = self.model
pos_delta = X.T @ inv_sigma @ mu_pos - 0.5 * mu_pos.T @ inv_sigma @ mu_pos + np.log(pos_pr)
neg_delta = X.T @ inv_sigma @ mu_neg - 0.5 * mu_neg.T @ inv_sigma @ mu_neg + np.log(1 - pos_pr)
return pos_delta, neg_delta
@staticmethod
def __get_statistics(X, y):
"""
Calculates the mean vectors for samples with positive labels and with negative labels
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: mu_pos, mu_neg, inv_sigma - means of the positive and negative labeled samples respectively,
inverse covariance matrix
"""
pos_samples = (X.T[y == POSITIVE]).T
neg_samples = (X.T[y == NEGATIVE]).T
mu_pos = pos_samples.mean(axis=COLS).reshape(-1, 1)
mu_neg = neg_samples.mean(axis=COLS).reshape(-1, 1)
inv_sigma = LDA.__get_inv_sigma(pos_samples, neg_samples, mu_pos, mu_neg, X.shape[SAMPLES_NUM])
return mu_pos, mu_neg, inv_sigma
@staticmethod
def __get_positive_p(y):
"""
Calculates the probability for a positive label
:param y: array of +-1 labels in shape (num_samples,)
:return: Pr(y=1)
"""
pos_num = y[y == POSITIVE].size
return pos_num / y.size
@staticmethod
def __get_inv_sigma(pos_x, neg_x, mu_pos, mu_neg, m):
"""
Calculates the inverse of estimated covariance matrix using conditional distribution
:param pos_x: Samples with a positive label - numpy array of shape (d, pos_samples)
:param neg_x: Samples with a negative label - numpy array of shape (d, neg_samples)
:param mu_pos: mean of the positive labeled samples - numpy array of shape (d,)
:param mu_neg: mean of the negative labeled samples - numpy array of shape (d,)
:param m: total number of samples (pos_samples + neg_samples)
:return: inverse of the estimated covariance matrix - numpy array of shape (d, d)
"""
pos_cov = (pos_x - mu_pos.reshape(-1, 1)) @ (pos_x - mu_pos.reshape(-1, 1)).T
neg_cov = (neg_x - mu_neg.reshape(-1, 1)) @ (neg_x - mu_neg.reshape(-1, 1)).T
est_cov = (pos_cov + neg_cov) / (m - 2)
return np.linalg.inv(est_cov)
class SVM(Model):
"""
A class for a Hard-SVM Classifier
"""
def __init__(self):
"""
Initializes the object with an object of sklearn.svm.svc and an empty weights vector
"""
self.__svm = SVC(C=1e10, kernel="linear")
self.model = np.array([])
def fit(self, X, y):
"""
Fits the model with a given training set.
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
self.__svm.fit(X.T, y)
self.model = np.concatenate((self.__svm.intercept_, self.__svm.coef_.squeeze()))
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
return self.__svm.predict(X.T)
class Logistic(Model):
"""
A class for a Logistic Regression Classifier
"""
def __init__(self):
"""
Initializes the object with an object of sklearn.linear_model.LogisticRegression
"""
self.__logistic = LogisticRegression(solver="liblinear")
self.model = np.array([])
def fit(self, X, y):
"""
Fits the model with a given training set.
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
self.__logistic.fit(X.T, y)
self.model = np.concatenate((self.__logistic.intercept_, self.__logistic.coef_.squeeze()))
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
return self.__logistic.predict(X.T)
class DecisionTree(Model):
"""
A class for a Decision Tree Classifier
"""
def __init__(self):
"""
Initializes the object with an object of sklearn.linear_model.LogisticRegression
"""
self.__tree = DecisionTreeClassifier(max_depth=5)
def fit(self, X, y):
"""
Fits the model with a given training set.
:param X: The training samples - numpy array of shape (d x m)
:param y: The labels of X - numpy array of shape (m,)
:return: None
"""
self.__tree.fit(X.T, y)
def predict(self, X):
"""
Given an unlabeled test set X, predicts the label of each sample
:param X: numpy array of shape (d x m)
:return: y_hat - numpy array of shape (m,)
"""
return self.__tree.predict(X.T) #