machinelearning-mnpkllps 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- machinelearning_mnpkllps/Classificacao.py +611 -0
- machinelearning_mnpkllps/Functions.py +86 -0
- machinelearning_mnpkllps/Nao_Supervisionado.py +181 -0
- machinelearning_mnpkllps/NeuralNetwork.py +292 -0
- machinelearning_mnpkllps/Reamostragem.py +330 -0
- machinelearning_mnpkllps/Regressao.py +277 -0
- machinelearning_mnpkllps/__init__.py +0 -0
- machinelearning_mnpkllps/decision_tree.py +326 -0
- machinelearning_mnpkllps/metricas.py +358 -0
- machinelearning_mnpkllps/preprocessing.py +393 -0
- machinelearning_mnpkllps/unsupervised_learning.py +179 -0
- machinelearning_mnpkllps-0.1.3.dist-info/METADATA +24 -0
- machinelearning_mnpkllps-0.1.3.dist-info/RECORD +15 -0
- machinelearning_mnpkllps-0.1.3.dist-info/WHEEL +5 -0
- machinelearning_mnpkllps-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from metricas import dist
|
|
3
|
+
|
|
4
|
+
# Verifica se os dados de input para treino possuem algum erro
|
|
5
|
+
def train_check(X: np.ndarray, Y: np.ndarray) -> None:
|
|
6
|
+
if not isinstance(X, np.ndarray):
|
|
7
|
+
raise TypeError("X must be numpy array")
|
|
8
|
+
if not isinstance(Y, np.ndarray):
|
|
9
|
+
raise TypeError("Y must be numpy array")
|
|
10
|
+
if X.ndim != 2:
|
|
11
|
+
raise ValueError("X must be 2D array")
|
|
12
|
+
if Y.ndim != 1:
|
|
13
|
+
raise ValueError("Y must be 1D array")
|
|
14
|
+
if X.shape[0] != Y.shape[0]:
|
|
15
|
+
raise ValueError("X and Y must have the same lenght")
|
|
16
|
+
if X.shape[0] == 0:
|
|
17
|
+
raise ValueError("Data must be not empty")
|
|
18
|
+
if X.shape[1] == 0:
|
|
19
|
+
raise ValueError("Samples must have at least one feature")
|
|
20
|
+
if not np.issubdtype(X.dtype, np.number):
|
|
21
|
+
raise TypeError("X must contain numeric values")
|
|
22
|
+
|
|
23
|
+
def predict_element_check(P: np.ndarray, n_features: int) -> None:
|
|
24
|
+
if P.ndim != 1:
|
|
25
|
+
raise ValueError("Input must be 1D")
|
|
26
|
+
if P.shape[-1] != n_features:
|
|
27
|
+
raise ValueError("Test and train must contain same number of features")
|
|
28
|
+
if not np.issubdtype(P.dtype, np.number):
|
|
29
|
+
raise TypeError("Input must contain numerical values")
|
|
30
|
+
|
|
31
|
+
# Converte um valor real em um valor no intervalo (0, 1)
|
|
32
|
+
def sigmoid(z: float) -> float:
|
|
33
|
+
return 1/(1+np.exp(-z))
|
|
34
|
+
|
|
35
|
+
# Versão do sigmoide para mais variáveis (scores). A soma total de probabilities deve ser 1.
|
|
36
|
+
def softmax(scores: np.ndarray) -> np.ndarray:
|
|
37
|
+
exp_scores = np.exp(scores)
|
|
38
|
+
return exp_scores / np.sum(exp_scores, axis=-1, keepdims=True)
|
|
39
|
+
|
|
40
|
+
class KNN():
|
|
41
|
+
"""
|
|
42
|
+
O modelo de K nearest neighbors, para um K escolhido
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, K: int = 5, p=2, normalize=False):
|
|
46
|
+
"""
|
|
47
|
+
Inicia o modelo KNN
|
|
48
|
+
--------------
|
|
49
|
+
K: Inteiro que representa o tamanho da vizinhança observada em cada dado. Dessa vizinhança, a maioria elegerá a classe prevista.
|
|
50
|
+
|
|
51
|
+
p: A norma Lp (Σ Pi^p)^(1/p). Por padrão, a distância euclidiana dos pontos (L2).
|
|
52
|
+
normalize: Caso queira manter um desvio padrão consistente para todo preditor, torne isso True. Os dados serão normalizados para média 0 e variância 1.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
if not isinstance(K, (int, np.integer)):
|
|
56
|
+
raise TypeError("K must be integer number")
|
|
57
|
+
if K <= 0:
|
|
58
|
+
raise ValueError("K must be positive number")
|
|
59
|
+
|
|
60
|
+
self.K = K
|
|
61
|
+
self.X = None
|
|
62
|
+
self.Y = None
|
|
63
|
+
self.p = p
|
|
64
|
+
self.normalize = normalize
|
|
65
|
+
self.standard = None
|
|
66
|
+
self.mean = None
|
|
67
|
+
|
|
68
|
+
def distance(self, P1: np.ndarray, P2: np.ndarray) -> float:
|
|
69
|
+
return dist(P1, P2, self.p)
|
|
70
|
+
|
|
71
|
+
def train(self, X: np.ndarray, Y: np.ndarray) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Treina o modelo KNN
|
|
74
|
+
-----------
|
|
75
|
+
X: array com todos os preditores dos dados de treino.
|
|
76
|
+
|
|
77
|
+
Y: array com todas as classes observadas nos dados de treino. Deve ser compatível com a dimensão de X.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
train_check(X, Y)
|
|
81
|
+
if self.normalize:
|
|
82
|
+
self.mean = X.mean(axis=0)
|
|
83
|
+
self.standard = X.std(axis=0)
|
|
84
|
+
self.X = (X - self.mean) / self.standard
|
|
85
|
+
else:
|
|
86
|
+
self.X = X
|
|
87
|
+
self.Y = Y
|
|
88
|
+
|
|
89
|
+
def predict_element(self, P: np.ndarray):
|
|
90
|
+
if self.X is None:
|
|
91
|
+
raise TypeError("The model must be trained")
|
|
92
|
+
|
|
93
|
+
predict_element_check(P, self.X.shape[1])
|
|
94
|
+
|
|
95
|
+
if self.normalize:
|
|
96
|
+
P = (P-self.mean)/self.standard
|
|
97
|
+
|
|
98
|
+
neighborhood = []
|
|
99
|
+
|
|
100
|
+
for i in range(len(self.X)):
|
|
101
|
+
d = self.distance(P, self.X[i])
|
|
102
|
+
if i < self.K:
|
|
103
|
+
neighborhood.append((d, self.Y[i]))
|
|
104
|
+
else:
|
|
105
|
+
index_max_distance = np.argmax([neighbor[0] for neighbor in neighborhood])
|
|
106
|
+
if d < neighborhood[index_max_distance][0]:
|
|
107
|
+
neighborhood[index_max_distance] = (d, self.Y[i])
|
|
108
|
+
|
|
109
|
+
classes = np.array([neighbor[1] for neighbor in neighborhood])
|
|
110
|
+
values, counting = np.unique(classes, return_counts=True)
|
|
111
|
+
return values[np.argmax(counting)]
|
|
112
|
+
|
|
113
|
+
def predict(self, L: np.ndarray) -> np.ndarray:
|
|
114
|
+
"""
|
|
115
|
+
Retorna a previsão do KNN para os dados novos. O modelo deve ter sido treinado antes das previsões.
|
|
116
|
+
-----------
|
|
117
|
+
L: array de novos dados com seus preditores
|
|
118
|
+
"""
|
|
119
|
+
predictions = []
|
|
120
|
+
for P in L:
|
|
121
|
+
predictions.append(self.predict_element(P))
|
|
122
|
+
return np.array(predictions)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Logistic_Regression():
|
|
126
|
+
"""
|
|
127
|
+
O modelo de Regressão Logística, com intercepto opcional
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, learning_rate: float = 0.01, iterations: int = 1000, intercept=True):
|
|
131
|
+
"""
|
|
132
|
+
Inicia o modelo de Regressão Logística
|
|
133
|
+
|
|
134
|
+
learning_rate: Valor do passo na descida de gradiente. Se muito pequeno, pode demorar a convergir ou não chegar ao ótimo. Se muito alto, pode divergir.
|
|
135
|
+
iterations: Número de passos que o algoritmo da descida de gradiente executa.
|
|
136
|
+
intercept: Pode ser interpretado como um viés natural para cada classe ser mais ou menos provável. Torne False se quiser desativá-lo.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
self.beta = None
|
|
140
|
+
self.beta0 = None
|
|
141
|
+
self.values = None
|
|
142
|
+
self.alpha = learning_rate
|
|
143
|
+
self.iter = iterations
|
|
144
|
+
self.intercept = intercept
|
|
145
|
+
|
|
146
|
+
def train(self, X: np.ndarray, Y: np.ndarray) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Treina o modelo de Regressão Logística
|
|
149
|
+
|
|
150
|
+
X: array com todos os preditores dos dados de treino.
|
|
151
|
+
Y: array com todas as classes observadas nos dados de treino. Deve ser compatível com a dimensão de X.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
train_check(X, Y)
|
|
155
|
+
|
|
156
|
+
# Usa a decida de gradiente para estimar beta
|
|
157
|
+
n_samples, n_parameters = X.shape
|
|
158
|
+
self.values = np.unique(Y)
|
|
159
|
+
n_classes = len(self.values)
|
|
160
|
+
self.beta = np.zeros((n_parameters, n_classes))
|
|
161
|
+
self.beta0 = np.zeros(n_classes) if self.intercept else None
|
|
162
|
+
|
|
163
|
+
# Cria uma matriz one hot samples x classes. Marca apenas uma coluna por linha (a classs observada)
|
|
164
|
+
indices = np.searchsorted(self.values, Y)
|
|
165
|
+
T = np.eye(n_classes)[indices]
|
|
166
|
+
|
|
167
|
+
for _ in range(self.iter):
|
|
168
|
+
|
|
169
|
+
Z = X @ self.beta # Soma feita através de broadcast. O resultado é uma matriz de mesmo shape que T
|
|
170
|
+
if self.intercept:
|
|
171
|
+
Z += self.beta0
|
|
172
|
+
P = np.array([softmax(z) for z in Z]) # Apenas traduz score para probabilidade em cada linha
|
|
173
|
+
|
|
174
|
+
# Fórmula dos gradientes. O termo P-T pode ser interpretado como a distância na previsão
|
|
175
|
+
grad_beta = X.T @ (P - T) / n_samples
|
|
176
|
+
grad_beta0 = np.mean(P - T, axis=0)
|
|
177
|
+
|
|
178
|
+
# Atualização dos coeficientes. o processo deve repetir a partir daqui.
|
|
179
|
+
self.beta -= self.alpha * grad_beta
|
|
180
|
+
if self.intercept:
|
|
181
|
+
self.beta0 -= self.alpha * grad_beta0
|
|
182
|
+
|
|
183
|
+
def predict_probabilities(self, X: np.ndarray) -> np.ndarray:
|
|
184
|
+
Z = X @ self.beta
|
|
185
|
+
if self.intercept:
|
|
186
|
+
Z += self.beta0
|
|
187
|
+
return softmax(Z)
|
|
188
|
+
|
|
189
|
+
def predict_element(self, P: np.ndarray):
|
|
190
|
+
predict_element_check(P, self.beta.shape[0])
|
|
191
|
+
ind = np.argmax(self.predict_probabilities(P))
|
|
192
|
+
return self.values[ind]
|
|
193
|
+
|
|
194
|
+
def predict(self, L: np.ndarray) -> np.array:
|
|
195
|
+
"""
|
|
196
|
+
Retorna a previsão da Regressão Logística para os dados novos. O modelo deve ter sido treinado antes das previsões.
|
|
197
|
+
|
|
198
|
+
L: array de novos dados com seus preditores
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
if self.beta is None:
|
|
202
|
+
raise TypeError("The model must be trained")
|
|
203
|
+
predictions = []
|
|
204
|
+
for P in L:
|
|
205
|
+
predictions.append(self.predict_element(P))
|
|
206
|
+
return np.array(predictions)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class LDA():
|
|
210
|
+
"""
|
|
211
|
+
O modelo de Análise de Discriminante Linear
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def __init__(self):
|
|
215
|
+
"""
|
|
216
|
+
Inicia o modelo de QDA. Não há parâmetros de input para essa classe.
|
|
217
|
+
"""
|
|
218
|
+
self.log_pi = None
|
|
219
|
+
self.means = None
|
|
220
|
+
self.sigma_inv = None
|
|
221
|
+
self.values = None
|
|
222
|
+
|
|
223
|
+
def train(self, X: np.ndarray, Y: np.ndarray) -> None:
|
|
224
|
+
"""
|
|
225
|
+
Treina o modelo LDA
|
|
226
|
+
|
|
227
|
+
X: array com todos os preditores dos dados de treino.
|
|
228
|
+
Y: array com todas as classes observadas nos dados de treino. Deve ser compatível com a dimensão de X.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
train_check(X, Y)
|
|
232
|
+
|
|
233
|
+
# Toma a frequência de cada classe como a probabilidade desta ocorrer
|
|
234
|
+
values, counting = np.unique(Y, return_counts=True)
|
|
235
|
+
log_pi = np.log(counting/len(Y))
|
|
236
|
+
|
|
237
|
+
# Toma a média das features em cada classe
|
|
238
|
+
means = []
|
|
239
|
+
for k in values:
|
|
240
|
+
Xk = X[Y==k]
|
|
241
|
+
mean = np.mean(Xk, axis=0)
|
|
242
|
+
means.append(mean)
|
|
243
|
+
means = np.array(means)
|
|
244
|
+
|
|
245
|
+
# Cria a matriz de covariância sigma a partir dos dados
|
|
246
|
+
if len(Y) <= len(values):
|
|
247
|
+
raise ValueError("There's no suficcient information to training LDA (need more samples than classes)")
|
|
248
|
+
sigma_dim = np.shape(X)[1]
|
|
249
|
+
sigma = np.zeros((sigma_dim, sigma_dim))
|
|
250
|
+
|
|
251
|
+
for i, k in enumerate(values):
|
|
252
|
+
Xk = X[Y == k]
|
|
253
|
+
X_centered = Xk - means[i]
|
|
254
|
+
sigma += X_centered.T @ X_centered
|
|
255
|
+
sigma /= len(Y) - len(values)
|
|
256
|
+
|
|
257
|
+
self.log_pi = log_pi
|
|
258
|
+
self.means = means
|
|
259
|
+
self.sigma_inv = np.linalg.inv(sigma)
|
|
260
|
+
self.values = values
|
|
261
|
+
|
|
262
|
+
def aux(self, X, sigma_inv, mean, log_freq):
|
|
263
|
+
# Essa fórmula é a que deve ser maximizada pelo LDA
|
|
264
|
+
return (X.T - 1/2 * mean.T) @ sigma_inv @ mean + log_freq
|
|
265
|
+
|
|
266
|
+
def predict_element(self, P: np.ndarray):
|
|
267
|
+
predict_element_check(P, self.means.shape[1])
|
|
268
|
+
|
|
269
|
+
# Busca a classe que maximiza aux para esses dados novos
|
|
270
|
+
vector_aux = np.zeros(len(self.values))
|
|
271
|
+
|
|
272
|
+
for i in range(len(self.values)):
|
|
273
|
+
vector_aux[i] = self.aux(P, self.sigma_inv, self.means[i], self.log_pi[i])
|
|
274
|
+
|
|
275
|
+
return self.values[np.argmax(vector_aux)]
|
|
276
|
+
|
|
277
|
+
def predict(self, L: np.ndarray) -> np.array:
|
|
278
|
+
"""
|
|
279
|
+
Retorna a previsão do LDA para os dados novos. O modelo deve ter sido treinado antes das previsões.
|
|
280
|
+
|
|
281
|
+
L: array de novos dados com seus preditores
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
if self.means is None:
|
|
285
|
+
raise TypeError("The model must be trained")
|
|
286
|
+
|
|
287
|
+
predictions = []
|
|
288
|
+
for P in L:
|
|
289
|
+
predictions.append(self.predict_element(P))
|
|
290
|
+
return np.array(predictions)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class QDA():
|
|
294
|
+
"""
|
|
295
|
+
O modelo de Análise de Discriminante Quadrática
|
|
296
|
+
"""
|
|
297
|
+
def __init__(self):
|
|
298
|
+
"""
|
|
299
|
+
Inicia o modelo de QDA. Não há parâmetros de input para essa classe.
|
|
300
|
+
"""
|
|
301
|
+
self.log_pi = None
|
|
302
|
+
self.means = None
|
|
303
|
+
self.sigmas = None
|
|
304
|
+
self.sigmas_inv = None
|
|
305
|
+
self.values = None
|
|
306
|
+
|
|
307
|
+
def train(self, X: np.ndarray, Y: np.ndarray) -> None:
|
|
308
|
+
"""
|
|
309
|
+
Treina o modelo QDA
|
|
310
|
+
|
|
311
|
+
X: array com todos os preditores dos dados de treino.
|
|
312
|
+
Y: array com todas as classes observadas nos dados de treino. Deve ser compatível com a dimensão de X.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
train_check(X, Y)
|
|
316
|
+
|
|
317
|
+
# Toma a frequência de cada classe como a probabilidade desta ocorrer
|
|
318
|
+
values, counting = np.unique(Y, return_counts=True)
|
|
319
|
+
if np.any(counting < 2):
|
|
320
|
+
raise ValueError("All the classes must have at least 2 samples")
|
|
321
|
+
log_pi = np.log(counting/len(Y))
|
|
322
|
+
|
|
323
|
+
# Toma a média das features em cada classe
|
|
324
|
+
means = []
|
|
325
|
+
for k in values:
|
|
326
|
+
Xk = X[Y==k]
|
|
327
|
+
mean = np.mean(Xk, axis=0)
|
|
328
|
+
means.append(mean)
|
|
329
|
+
means = np.array(means)
|
|
330
|
+
|
|
331
|
+
# Cria as matrizes de covariância para cada classe
|
|
332
|
+
sigmas = []
|
|
333
|
+
for i, k in enumerate(values):
|
|
334
|
+
Xk = X[Y == k]
|
|
335
|
+
X_centered = Xk - means[i]
|
|
336
|
+
sigma_k = X_centered.T @ X_centered / (len(Xk)-1)
|
|
337
|
+
sigmas.append(sigma_k)
|
|
338
|
+
|
|
339
|
+
sigmas_inv = []
|
|
340
|
+
for sigma in sigmas:
|
|
341
|
+
try:
|
|
342
|
+
sigma_inv = np.linalg.inv(sigma)
|
|
343
|
+
sigmas_inv.append(sigma_inv)
|
|
344
|
+
except:
|
|
345
|
+
raise ValueError("Some class didn't generate an invertible covariance matrix")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
self.log_pi = log_pi
|
|
349
|
+
self.means = means
|
|
350
|
+
self.sigmas = np.array(sigmas)
|
|
351
|
+
self.sigmas_inv = np.array(sigmas_inv)
|
|
352
|
+
self.values = values
|
|
353
|
+
# Necessário verificar possibilidade de erro na inversão
|
|
354
|
+
|
|
355
|
+
def aux(self, X, sigma, sigma_inv, mean, log_freq):
|
|
356
|
+
# Essa fórmula é a que deve ser maximizada pelo QDA
|
|
357
|
+
return -1/2 * (X - mean).T @ sigma_inv @ (X - mean) + log_freq - 1/2 * np.log(np.linalg.det(sigma))
|
|
358
|
+
|
|
359
|
+
def predict_element(self, P: np.ndarray):
|
|
360
|
+
predict_element_check(P, self.means.shape[1])
|
|
361
|
+
|
|
362
|
+
# Busca a classe que maximiza aux para esses dados novos
|
|
363
|
+
vector_aux = np.zeros(len(self.values))
|
|
364
|
+
|
|
365
|
+
for i in range(len(self.values)):
|
|
366
|
+
vector_aux[i] = self.aux(P, self.sigmas[i], self.sigmas_inv[i], self.means[i], self.log_pi[i])
|
|
367
|
+
|
|
368
|
+
return self.values[np.argmax(vector_aux)]
|
|
369
|
+
|
|
370
|
+
def predict(self, L: np.ndarray) -> np.array:
|
|
371
|
+
"""
|
|
372
|
+
Retorna a previsão do QDA para os dados novos. O modelo deve ter sido treinado antes das previsões.
|
|
373
|
+
|
|
374
|
+
L: array de novos dados com seus preditores
|
|
375
|
+
"""
|
|
376
|
+
if self.means is None:
|
|
377
|
+
raise TypeError("The model must be trained")
|
|
378
|
+
|
|
379
|
+
predictions = []
|
|
380
|
+
for P in L:
|
|
381
|
+
predictions.append(self.predict_element(P))
|
|
382
|
+
return np.array(predictions)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class SVM():
|
|
386
|
+
"""
|
|
387
|
+
O modelo de Support Vector Machine linear com margem flexível, treinado por descida do subgradiente no primal.
|
|
388
|
+
Com mais de duas classes, usa a estratégia um-contra-um (one-vs-one).
|
|
389
|
+
"""
|
|
390
|
+
def __init__(self, C: float = 1.0, learning_rate: float = 0.01, max_iterations: int = 1000):
|
|
391
|
+
"""
|
|
392
|
+
Inicia o modelo SVM
|
|
393
|
+
|
|
394
|
+
C: Peso da penalidade por violar a margem.
|
|
395
|
+
learning_rate: Valor do passo na descida do subgradiente.
|
|
396
|
+
max_iterations: Número de passos (cada um usa todos os dados de treino) que o algoritmo executa.
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
if C <= 0:
|
|
400
|
+
raise ValueError("C must be positive number")
|
|
401
|
+
if learning_rate <= 0:
|
|
402
|
+
raise ValueError("learning_rate must be positive number")
|
|
403
|
+
if not isinstance(max_iterations, (int, np.integer)):
|
|
404
|
+
raise TypeError("max_iterations must be integer number")
|
|
405
|
+
if max_iterations <= 0:
|
|
406
|
+
raise ValueError("max_iterations must be positive number")
|
|
407
|
+
|
|
408
|
+
self.C = C
|
|
409
|
+
self.alpha = learning_rate
|
|
410
|
+
self.max_iterations = max_iterations
|
|
411
|
+
self.values = None
|
|
412
|
+
self.n_features = None
|
|
413
|
+
self.machines = None # Lista de (i, j, w, b): SVM binário entre as classes values[i] (-1) e values[j] (+1)
|
|
414
|
+
|
|
415
|
+
def train_binary(self, X: np.ndarray, y: np.ndarray):
|
|
416
|
+
# Minimiza J(w,b) = 1/2 ||w||^2 + C/N Σ max(0, 1 - y_i (w^T x_i + b)). Os rótulos y são -1 ou +1.
|
|
417
|
+
N, n_parameters = X.shape
|
|
418
|
+
w = np.zeros(n_parameters)
|
|
419
|
+
b = 0.0
|
|
420
|
+
|
|
421
|
+
for _ in range(self.max_iterations):
|
|
422
|
+
# Margens e conjunto M dos exemplos que violam a margem: y_i (w^T x_i + b) < 1
|
|
423
|
+
# influencia que hinge loss penaliza pontos que não respeitam a margem funcional de 1.
|
|
424
|
+
margins = y * (X @ w + b)
|
|
425
|
+
M = margins < 1
|
|
426
|
+
|
|
427
|
+
# Subgradientes de J
|
|
428
|
+
grad_w = w - (self.C / N) * (y[M] @ X[M])
|
|
429
|
+
grad_b = -(self.C / N) * np.sum(y[M])
|
|
430
|
+
|
|
431
|
+
w = w - self.alpha * grad_w
|
|
432
|
+
b = b - self.alpha * grad_b
|
|
433
|
+
|
|
434
|
+
return w, b
|
|
435
|
+
|
|
436
|
+
def train(self, X: np.ndarray, Y: np.ndarray) -> None:
|
|
437
|
+
"""
|
|
438
|
+
Treina o modelo SVM
|
|
439
|
+
|
|
440
|
+
X: array com todos os preditores dos dados de treino.
|
|
441
|
+
Y: array com todas as classes observadas nos dados de treino. Deve ser compatível com a dimensão de X. Precisa conter pelo menos 2 classes.
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
train_check(X, Y)
|
|
445
|
+
|
|
446
|
+
values = np.unique(Y)
|
|
447
|
+
|
|
448
|
+
X = X.astype(float)
|
|
449
|
+
|
|
450
|
+
# Um SVM binário para cada par de classes (i < j). A classe values[i] vira -1 e values[j] vira +1.
|
|
451
|
+
machines = []
|
|
452
|
+
for i in range(len(values)):
|
|
453
|
+
for j in range(i + 1, len(values)):
|
|
454
|
+
pair = (Y == values[i]) | (Y == values[j]) #pega apneas as amostras das duas classes atuais
|
|
455
|
+
y = np.where(Y[pair] == values[j], 1.0, -1.0) #colocando rótulos 1 e -1
|
|
456
|
+
w, b = self.train_binary(X[pair], y)
|
|
457
|
+
machines.append((i, j, w, b))
|
|
458
|
+
|
|
459
|
+
self.values = values
|
|
460
|
+
self.n_features = X.shape[1]
|
|
461
|
+
self.machines = machines
|
|
462
|
+
|
|
463
|
+
def predict_element(self, P: np.ndarray):
|
|
464
|
+
if self.machines is None:
|
|
465
|
+
raise TypeError("The model must be trained")
|
|
466
|
+
|
|
467
|
+
predict_element_check(P, self.n_features)
|
|
468
|
+
|
|
469
|
+
return self.predict(P.reshape(1, -1))[0]
|
|
470
|
+
|
|
471
|
+
def predict(self, L: np.ndarray) -> np.ndarray:
|
|
472
|
+
"""
|
|
473
|
+
Retorna a previsão do SVM para os dados novos. O modelo deve ter sido treinado antes das previsões.
|
|
474
|
+
Cada SVM binário vota em uma de suas duas classes, e vence a classe com mais votos (em empate, a menor na ordem crescente das classes).
|
|
475
|
+
|
|
476
|
+
L: array de novos dados com seus preditores
|
|
477
|
+
"""
|
|
478
|
+
|
|
479
|
+
if self.machines is None:
|
|
480
|
+
raise TypeError("The model must be trained")
|
|
481
|
+
|
|
482
|
+
for P in L:
|
|
483
|
+
predict_element_check(P, self.n_features)
|
|
484
|
+
|
|
485
|
+
votes = np.zeros((len(L), len(self.values)), dtype=int)
|
|
486
|
+
for i, j, w, b in self.machines:
|
|
487
|
+
# w^T x + b >= 0 vota em values[j]; caso contrário, em values[i]
|
|
488
|
+
winners = np.where(L @ w + b >= 0, j, i)
|
|
489
|
+
votes[np.arange(len(L)), winners] += 1 #arange cria um vetor com indices de L
|
|
490
|
+
|
|
491
|
+
return self.values[np.argmax(votes, axis=1)]
|
|
492
|
+
# -----TESTES-----
|
|
493
|
+
print("Sigmoide(0) = ", sigmoid(0))
|
|
494
|
+
print("Sigmoide(1) = ", sigmoid(1))
|
|
495
|
+
print("Sigmoide(-1) = ", sigmoid(-1))
|
|
496
|
+
|
|
497
|
+
n = np.array([1, 1, 1])
|
|
498
|
+
print("Softmax(1, 1, 1) = ", softmax(n))
|
|
499
|
+
n = np.array([2, 1, 1])
|
|
500
|
+
print("Softmax(2, 1, 1) = ", softmax(n))
|
|
501
|
+
n = np.array([1, 2, 2])
|
|
502
|
+
print("Softmax(1, 2, 2) = ", softmax(n))
|
|
503
|
+
n = np.array([1, 3, 1])
|
|
504
|
+
print("Softmax(1, 3, 1) = ", softmax(n))
|
|
505
|
+
n = np.array([1.5, 1, 0, -1, -1.5])
|
|
506
|
+
print("Softmax(1.5, 1, 0, -1, -1.5) = ", softmax(n))
|
|
507
|
+
n = np.array([5])
|
|
508
|
+
print("Softmax(5) = ", softmax(n))
|
|
509
|
+
n = np.array([1, 1.01])
|
|
510
|
+
print("Softmax(1, 1.01) = ", softmax(n))
|
|
511
|
+
print()
|
|
512
|
+
|
|
513
|
+
modelRL = Logistic_Regression()
|
|
514
|
+
modelRLNI = Logistic_Regression(intercept = False)
|
|
515
|
+
modelRLHL = Logistic_Regression(learning_rate = 1)
|
|
516
|
+
modelRLFI = Logistic_Regression(iterations = 2)
|
|
517
|
+
modelKNN5 = KNN()
|
|
518
|
+
modelKNN1 = KNN(K=1)
|
|
519
|
+
modelKNN10 = KNN(K=10)
|
|
520
|
+
modelLDA = LDA()
|
|
521
|
+
modelQDA = QDA()
|
|
522
|
+
modelSVM = SVM()
|
|
523
|
+
modelSVMHC = SVM(C=10)
|
|
524
|
+
modelSVMFI = SVM(max_iterations=2)
|
|
525
|
+
|
|
526
|
+
# Para os testes abaixo, o LDA e o QDA foram testados separadamente nos itens em que se aplicam
|
|
527
|
+
def test(X, Y, X_teste):
|
|
528
|
+
modelRL.train(X, Y)
|
|
529
|
+
modelRLNI.train(X, Y)
|
|
530
|
+
modelRLHL.train(X, Y)
|
|
531
|
+
modelRLFI.train(X, Y)
|
|
532
|
+
modelKNN5.train(X, Y)
|
|
533
|
+
modelKNN1.train(X, Y)
|
|
534
|
+
modelKNN10.train(X, Y)
|
|
535
|
+
modelSVM.train(X, Y)
|
|
536
|
+
modelSVMHC.train(X, Y)
|
|
537
|
+
modelSVMFI.train(X, Y)
|
|
538
|
+
|
|
539
|
+
print("Regressão Logística: ", modelRL.predict(X_teste))
|
|
540
|
+
print("Regressão Logística sem intercepto: ", modelRLNI.predict(X_teste))
|
|
541
|
+
print("Regressão Logística com alto learning rate: ", modelRLHL.predict(X_teste))
|
|
542
|
+
print("Regressão Logística com poucas iterações: ", modelRLFI.predict(X_teste))
|
|
543
|
+
print("KNN com K=5: ", modelKNN5.predict(X_teste))
|
|
544
|
+
print("KNN com K=1: ", modelKNN1.predict(X_teste))
|
|
545
|
+
print("KNN com K=10: ", modelKNN10.predict(X_teste))
|
|
546
|
+
print("SVM: ", modelSVM.predict(X_teste))
|
|
547
|
+
print("SVM com C=10: ", modelSVMHC.predict(X_teste))
|
|
548
|
+
print("SVM com poucas iterações: ", modelSVMFI.predict(X_teste))
|
|
549
|
+
|
|
550
|
+
# Vários pontos
|
|
551
|
+
X = np.array([[0, 0], [1, -0.5], [2, 2], [3, 5], [-3, 10], [4, 2.5], [1.5, 3.5], [-1, 1.5], [-3, 2.5], [-2.5, -0.5], [-0.5, -1], [-2, 7], [-2.5, 6]])
|
|
552
|
+
Y = np.array(["Red", "Red", "Blue", "Blue", "Green", "Blue", "Blue", "Red", "Red", "Red", "Red", "Green", "Green"])
|
|
553
|
+
X_teste = np.array([[0, 0], [3, 3], [-2, 0], [-4, 6], [0, -4]])
|
|
554
|
+
test(X, Y, X_teste)
|
|
555
|
+
modelLDA.train(X, Y)
|
|
556
|
+
modelQDA.train(X, Y)
|
|
557
|
+
print("LDA: ", modelLDA.predict(X_teste))
|
|
558
|
+
print("QDA: ", modelQDA.predict(X_teste))
|
|
559
|
+
print()
|
|
560
|
+
|
|
561
|
+
# Pontos em uma linha reta
|
|
562
|
+
X = np.array([[1, 2], [0.6, 2], [1, 2], [0.7, 2], [-10, 2], [0, 2]])
|
|
563
|
+
Y = np.array(["ONE", "TWO", "ONE", "TWO", "THREE", "THREE"])
|
|
564
|
+
X_teste = np.array([[0.5, 1.9], [1.1, 2.7], [0, 2.1], [-1, 2.3], [0.3, 1.6]])
|
|
565
|
+
test(X, Y, X_teste)
|
|
566
|
+
print()
|
|
567
|
+
|
|
568
|
+
# Pontos em uma linha quase reta
|
|
569
|
+
X = np.array([[1, 2], [0.6, 2.1], [1, 2.1], [0.7, 2], [-10, 1.9], [0, 2.2]])
|
|
570
|
+
Y = np.array(["ONE", "TWO", "ONE", "TWO", "THREE", "THREE"])
|
|
571
|
+
X_teste = np.array([[0.5, 1.9], [1.1, 2.7], [0, 2.1], [-1, 2.3], [0.3, 1.6]])
|
|
572
|
+
test(X, Y, X_teste)
|
|
573
|
+
modelLDA.train(X, Y)
|
|
574
|
+
print("LDA: ", modelLDA.predict(X_teste))
|
|
575
|
+
print()
|
|
576
|
+
|
|
577
|
+
# Um único ponto
|
|
578
|
+
X = np.array([[8, -7]])
|
|
579
|
+
Y = np.array(["Only"])
|
|
580
|
+
X_teste = np.array([[0, 0], [1, 1], [2, 2], [3, 3]])
|
|
581
|
+
test(X, Y, X_teste)
|
|
582
|
+
print()
|
|
583
|
+
|
|
584
|
+
# Prevendo mudança em uma reta
|
|
585
|
+
X = np.array([[-1.51, -1.49], [-1, -1], [1, 1]])
|
|
586
|
+
Y = np.array(["A", "A", "B"])
|
|
587
|
+
X_teste = np.array([[0.9, 0.9], [0.5, 0.5], [0.25, 0.25], [0.1, 0.1], [0, 0], [-0.1, -0.1], [-0.25, -0.25], [-0.5, -0.5], [-0.9, -0.9]])
|
|
588
|
+
test(X, Y, X_teste)
|
|
589
|
+
modelLDA.train(X, Y)
|
|
590
|
+
print("LDA: ", modelLDA.predict(X_teste))
|
|
591
|
+
print()
|
|
592
|
+
|
|
593
|
+
# Pontos iguais em classes diferentes
|
|
594
|
+
X = np.array([[0, 0], [0, 0], [1, 2], [1, 2], [-1, -1], [-1, -1]])
|
|
595
|
+
Y = np.array(["Equal", "Identical", "Equal", "Identical", "Equal", "Identical"])
|
|
596
|
+
X_teste = np.array([[5, -3], [0, 0]])
|
|
597
|
+
test(X, Y, X_teste)
|
|
598
|
+
modelLDA.train(X, Y)
|
|
599
|
+
print("LDA: ", modelLDA.predict(X_teste))
|
|
600
|
+
print()
|
|
601
|
+
|
|
602
|
+
# Teste métrica KNN
|
|
603
|
+
modelKNN2 = KNN(2)
|
|
604
|
+
modelKNN2sigma = KNN(2, normalize=True)
|
|
605
|
+
X = np.array([[10, 10], [11, 11], [8, 9], [0, 0], [1, -1], [-1000, 1]])
|
|
606
|
+
Y = np.array(["Direito", "Direito", "Direito", "Esquerdo", "Esquerdo", "Esquerdo"])
|
|
607
|
+
X_teste = np.array([[11, 1]])
|
|
608
|
+
modelKNN2.train(X, Y)
|
|
609
|
+
modelKNN2sigma.train(X, Y)
|
|
610
|
+
print(modelKNN2.predict(X_teste))
|
|
611
|
+
print(modelKNN2sigma.predict(X_teste))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def sigmoid(x: float | np.ndarray) -> np.ndarray:
|
|
6
|
+
x = np.array(x)
|
|
7
|
+
|
|
8
|
+
return 1 / (1 + np.exp(-x))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def sigmoid_diff(x: float | np.ndarray) -> np.ndarray:
|
|
12
|
+
x = np.array(x)
|
|
13
|
+
|
|
14
|
+
s = sigmoid(x)
|
|
15
|
+
return s * (1 - s)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def tanh(x: float | np.ndarray) -> np.ndarray:
|
|
19
|
+
x = np.array(x)
|
|
20
|
+
|
|
21
|
+
return np.tanh(x)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def tanh_diff(x: float | np.ndarray) -> np.ndarray:
|
|
25
|
+
x = np.array(x)
|
|
26
|
+
|
|
27
|
+
return 1 - tanh(x)**2
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ReLU(x: float | np.ndarray) -> np.ndarray:
|
|
31
|
+
x = np.array(x)
|
|
32
|
+
|
|
33
|
+
return np.maximum(0, x)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ReLU_diff(x: float | np.ndarray) -> np.ndarray:
|
|
37
|
+
x = np.array(x)
|
|
38
|
+
|
|
39
|
+
return (x > 0).astype(int)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def identity(x: float | np.ndarray) -> np.ndarray:
|
|
43
|
+
x = np.array(x)
|
|
44
|
+
|
|
45
|
+
return x
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def identity_diff(x: float | np.ndarray) -> np.ndarray:
|
|
49
|
+
x = np.array(x)
|
|
50
|
+
|
|
51
|
+
return np.ones_like(x)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
functions = {
|
|
55
|
+
'sigmoid': (sigmoid, sigmoid_diff),
|
|
56
|
+
'tanh': (tanh, tanh_diff),
|
|
57
|
+
'ReLU': (ReLU, ReLU_diff),
|
|
58
|
+
'identity': (identity, identity_diff)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
functions_name = Literal['identity', 'ReLU', 'sigmoid', 'tanh']
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
x1, x2 = 0, np.pi
|
|
65
|
+
|
|
66
|
+
print(f'Teste das funções para x1 = {x1}')
|
|
67
|
+
print(f'{sigmoid(x1) = }')
|
|
68
|
+
print(f'{sigmoid_diff(x1) = }')
|
|
69
|
+
print(f'{tanh(x1) = }')
|
|
70
|
+
print(f'{tanh_diff(x1) = }')
|
|
71
|
+
print(f'{ReLU(x1) = }')
|
|
72
|
+
print(f'{ReLU_diff(x1) = }')
|
|
73
|
+
print(f'{identity(x1) = }')
|
|
74
|
+
print(f'{identity_diff(x1) = }')
|
|
75
|
+
print()
|
|
76
|
+
|
|
77
|
+
print(f'Teste das funções para x2 = {x2}')
|
|
78
|
+
print(f'{sigmoid(x2) = }')
|
|
79
|
+
print(f'{sigmoid_diff(x2) = }')
|
|
80
|
+
print(f'{tanh(x2) = }')
|
|
81
|
+
print(f'{tanh_diff(x2) = }')
|
|
82
|
+
print(f'{ReLU(x2) = }')
|
|
83
|
+
print(f'{ReLU_diff(x2) = }')
|
|
84
|
+
print(f'{identity(x2) = }')
|
|
85
|
+
print(f'{identity_diff(x2) = }')
|
|
86
|
+
print()
|