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,181 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validade_matrix(X): #função auxiliar para verificar se os dados da amostra é válido
|
|
5
|
+
X = np.asarray(X, dtype=float)
|
|
6
|
+
if X.ndim != 2 or X.shape[0] == 0 or X.shape[1] == 0: #número de linha e Núemrod de colunas, dimensão dois porque temos as observações e também temos os atributos de cada observação
|
|
7
|
+
raise TypeError("X deve ser 2D e não vazio: (n_amostras, n_atributos)")
|
|
8
|
+
|
|
9
|
+
if not np.all(np.isfinite(X)): #np.all verifica todos os valores são verdadeiros, np.infinite retorna veradeiro ou falso para cada valor, vendp se não é Nan ou infinito
|
|
10
|
+
raise ValueError("X contém NaN ou infinito")
|
|
11
|
+
return X
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_positive_integer(valor, nome): #função auxiliar para verificar se o valor é válido
|
|
15
|
+
if not isinstance(valor, (int, np.integer)) or isinstance(valor, bool): #verifica se o valor é um inteiro
|
|
16
|
+
raise ValueError(f"{nome} deve ser um inteiro positivo.")
|
|
17
|
+
|
|
18
|
+
if valor < 1:
|
|
19
|
+
raise ValueError(f"{nome} deve ser >= 1.") # verifica se o valor é maior ou igual 1
|
|
20
|
+
|
|
21
|
+
return int(valor)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class KMeans:
|
|
25
|
+
"""
|
|
26
|
+
`Kmeans(k, max_iteracao=100)`
|
|
27
|
+
|
|
28
|
+
Agrupa as amostras em `k` clusters (algoritmo de Lloyd): sorteia `k` pontos distintos como centroides, atribui cada amostra ao centroide mais próximo e recalcula cada centroide como a média do seu cluster, até convergir ou atingir `max_iteracao`.
|
|
29
|
+
|
|
30
|
+
| Parâmetro | Descrição |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `k` | Número de clusters (inteiro >= 1) |
|
|
33
|
+
| `max_iteracao` | Limite de iterações (inteiro >= 1) |
|
|
34
|
+
|
|
35
|
+
| Método | O que faz |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `fit(X)` | Ajusta o modelo e guarda `centroides` `(k, n_atributos)`. Retorna `self`. Erro se `k` > nº de amostras ou nº de pontos distintos |
|
|
38
|
+
| `estimar(X)` | Retorna o índice do centroide mais próximo de cada amostra, formato `(n_amostras,)` |
|
|
39
|
+
|
|
40
|
+
Observação: a inicialização é aleatória (sem semente), então a numeração dos clusters pode mudar entre execuções.
|
|
41
|
+
"""
|
|
42
|
+
def __init__(self, k, max_iterations=100):
|
|
43
|
+
self.k = validate_positive_integer(k, "k")
|
|
44
|
+
self.max_iterations = validate_positive_integer(max_iterations, "max_iterations") #verificando se o valor é válido
|
|
45
|
+
self.centroids = None # vetor que vai armazemar os centroids (vetor)
|
|
46
|
+
|
|
47
|
+
def predict(self, X):
|
|
48
|
+
if self.centroids is None:
|
|
49
|
+
raise RuntimeError("chame fit antes de estimar")
|
|
50
|
+
|
|
51
|
+
X = validate_matrix(X)
|
|
52
|
+
|
|
53
|
+
if X.shape[1] != self.centroids.shape[1]: # shape seria: X.shape = (qnt_amostra, qnt_atributos), shape[1] é o número de colunas, ou seja, o número de atributos
|
|
54
|
+
raise ValueError(f"X tem {X.shape[1]} atributos, mas o modelo foi treinado com {self.centroids.shape[1]} atributos")
|
|
55
|
+
|
|
56
|
+
distancias = np.sum((X[:, None, :] - self.centroids[None, :, :]) ** 2, axis=2) # (amostra, centroide, coordenada)
|
|
57
|
+
# Com sum(axis=2), somamos as coordenadas: (amostra, centroide), contendo as distâncias euclidianas ao quadrado
|
|
58
|
+
|
|
59
|
+
return np.argmin(distancias, axis=1) # Para cada amostra, retorna o índice do centroide mais próximo. Formato: (amostra) contendo o índice do centróide mais próximo.
|
|
60
|
+
|
|
61
|
+
def fit(self, X):
|
|
62
|
+
X = validate_matrix(X)
|
|
63
|
+
|
|
64
|
+
if self.k > len(X): #verifica se o número de clusters é maior que o número de amostras
|
|
65
|
+
raise ValueError(f"k={self.k} é maior que o número de amostras ({len(X)})")
|
|
66
|
+
|
|
67
|
+
distintos = np.unique(X, axis=0) #verifica se o número de clusters é maior que o núemor de amostras distintos
|
|
68
|
+
if self.k > len(distintos):
|
|
69
|
+
raise ValueError(f"há apenas {len(distintos)} pontos distintos em X, menos que k={self.k}")
|
|
70
|
+
|
|
71
|
+
rng = np.random.default_rng() # gerador de números aleatórios
|
|
72
|
+
indices = rng.choice(len(distintos), self.k, replace=False) # seleciona k índices aleatórios (índices distintos)
|
|
73
|
+
self.centroids = distintos[indices].copy() #copiando os centroids escolhidos
|
|
74
|
+
|
|
75
|
+
for _ in range(self.max_iterations):
|
|
76
|
+
conjunto = self.predict(X) # atribuindo cada amostra ao centroide mais próximo
|
|
77
|
+
novos = self.centroids.copy() # inicializando os valores dos novos centroids da iteração
|
|
78
|
+
|
|
79
|
+
for i in range(self.k): # para cada centroide calcular seu novo valor como a média dos pontos do seu conjunto
|
|
80
|
+
pontos = X[conjunto == i] # selecionar todos os X[j] cuja conjunto[j] = i
|
|
81
|
+
if len(pontos) > 0:
|
|
82
|
+
novos[i] = pontos.mean(axis=0)
|
|
83
|
+
|
|
84
|
+
if np.array_equal(self.centroids, novos): # se o valor dos novos centroids não alterou, então convergiu
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
self.centroids = novos #atualizando o valor dos centroids
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class PCA:
|
|
92
|
+
"""
|
|
93
|
+
## `Pca()`
|
|
94
|
+
|
|
95
|
+
Encontra as direções de maior variância: centraliza os dados, calcula a covariância amostral e usa `np.linalg.eigh` para obter autovalores e autovetores, ordenados do maior para o menor.
|
|
96
|
+
|
|
97
|
+
| Atributo | Descrição |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `media` | Média de cada atributo `(n_atributos,)` |
|
|
100
|
+
| `componentes` | Autovetores nas colunas `(n_atributos, n_atributos)` |
|
|
101
|
+
| `autovalores` | Variância de cada componente, em ordem decrescente |
|
|
102
|
+
|
|
103
|
+
| Método | O que faz |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `fit(X)` | Calcula `media`, `componentes` e `autovalores`. Exige >= 2 amostras. Retorna `self` |
|
|
106
|
+
| `transformar(X, n_componentes=None)` | Centraliza e projeta nos primeiros `n_componentes` (todos se `None`). Retorna `(n_amostras, n_componentes)` |
|
|
107
|
+
| `fit_transform(X, n_componentes=None)` | `fit` seguido de `transformar` |
|
|
108
|
+
|
|
109
|
+
Observações: o sinal de cada componente é arbitrário, e o PCA não padroniza os dados (use o pré-processamento se os atributos tiverem escalas diferentes).
|
|
110
|
+
"""
|
|
111
|
+
def __init__(self):
|
|
112
|
+
self.mean = None # média de cada atributo original
|
|
113
|
+
self.components = None # autovetores ordenados
|
|
114
|
+
self.eigenvalues = None # variâncias associados aos autovetores (as direções)
|
|
115
|
+
|
|
116
|
+
def fit(self, X):
|
|
117
|
+
X = validate_matrix(X)
|
|
118
|
+
if len(X) < 2: # se a dimensão for <=1 não temos dimensão para reduzir, calcular covariância usa n - 1.
|
|
119
|
+
raise ValueError("PCA precisa de pelo menos 2 amostras")
|
|
120
|
+
|
|
121
|
+
self.mean = X.mean(axis=0) #média dos vetores colunas (amostra)
|
|
122
|
+
X_centralizado = X - self.mean #centralizando os dados
|
|
123
|
+
|
|
124
|
+
cov = X_centralizado.T @ X_centralizado / (len(X) - 1) #calculando a matriz de covariância amostral
|
|
125
|
+
valores, vetores = np.linalg.eigh(cov) #calculando os auto valores e os autovetores (padronizados)
|
|
126
|
+
|
|
127
|
+
indices = np.argsort(valores)[::-1] #pegando os índices dos eigenvalues em ordem decrescente
|
|
128
|
+
self.eigenvalues = np.maximum(valores[indices], 0.0) #substituindo os autovalroes negativos por 0, que podem surgir a instabilidade numérica do ponto flutuante (variância negativo não exite)
|
|
129
|
+
self.components = vetores[:, indices] #reordenando as colunas (veotres colunas)
|
|
130
|
+
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
def transform(self, X, n_components=None):
|
|
134
|
+
if self.mean is None:
|
|
135
|
+
raise RuntimeError("chame fit antes de transformar")
|
|
136
|
+
|
|
137
|
+
X = validate_matrix(X)
|
|
138
|
+
if X.shape[1] != len(self.mean): # compatibilidade entre a dimensão dos dados e a média
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"X tem {X.shape[1]} atributos, esperado {len(self.mean)}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
n = self.components.shape[1] if n_components is None else validate_positive_integer(
|
|
144
|
+
n_components, "n_components" #usar todos os components se None, se não verificar se o valor dado é válido
|
|
145
|
+
)
|
|
146
|
+
if n > self.components.shape[1]: #se o número o n for maior que o número de atributos
|
|
147
|
+
raise ValueError(f"n_components={n} excede o número de atributos : {self.components.shape[1]}")
|
|
148
|
+
|
|
149
|
+
return (X - self.mean) @ self.components[:, :n]
|
|
150
|
+
|
|
151
|
+
def fit_transform(self, X, n_components=None):
|
|
152
|
+
return self.fit(X).transform(X, n_components)
|
|
153
|
+
|
|
154
|
+
# ====================================TESTES=======================================================================================================
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
rng = np.random.default_rng(0)
|
|
157
|
+
|
|
158
|
+
# KMeans
|
|
159
|
+
# dois grupos bem separados: um perto de (0,0) e outro perto de (10,10)
|
|
160
|
+
X = np.vstack([rng.normal(0, 0.5, (50, 2)), rng.normal(10, 0.5, (50, 2))])
|
|
161
|
+
|
|
162
|
+
modelo = KMeans(2).fit(X)
|
|
163
|
+
rotulos = modelo.predict(X)
|
|
164
|
+
|
|
165
|
+
assert len(set(rotulos[:50])) == 1 # primeiro grupo todo no mesmo cluster
|
|
166
|
+
assert len(set(rotulos[50:])) == 1 # segundo grupo todo no mesmo cluster
|
|
167
|
+
assert rotulos[0] != rotulos[50] # clusters diferentes
|
|
168
|
+
print("KMeans OK")
|
|
169
|
+
|
|
170
|
+
# PCA
|
|
171
|
+
# dados quase sobre a reta y = 2x
|
|
172
|
+
t = rng.normal(size=200)
|
|
173
|
+
X = np.column_stack([t, 2 * t]) + 0.01 * rng.normal(size=(200, 2))
|
|
174
|
+
|
|
175
|
+
PCA = PCA()
|
|
176
|
+
Z = PCA.fit_transform(X, n_components=1)
|
|
177
|
+
|
|
178
|
+
esperado = np.array([1, 2]) / np.sqrt(5) # direção da reta
|
|
179
|
+
assert np.isclose(abs(PCA.components[:, 0] @ esperado), 1, atol=1e-3) # abs: sinal é arbitrário
|
|
180
|
+
assert Z.shape == (200, 1)
|
|
181
|
+
print("PCA OK")
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import Functions
|
|
3
|
+
from . import metricas
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NeuralNetwork:
|
|
8
|
+
def __init__(self, layers: list[int], activate_function: Functions.functions_name | list[Functions.functions_name],
|
|
9
|
+
output_activation: Functions.functions_name = 'identity', function_dictionary: dict[str, tuple[Callable, Callable]] = Functions.functions,
|
|
10
|
+
bias_interval: tuple[float, float] = (0, 1), weight_interval: tuple[float, float] = (0, 1),
|
|
11
|
+
seed:int = 782) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Intancia um elemento a classe NeuralNetwork com pesos e vieses aleatórios dentro do intervalo estipulado.
|
|
14
|
+
|
|
15
|
+
Parâmetros
|
|
16
|
+
-----------
|
|
17
|
+
layers list[int] : Quantas layers tem e qual a largura de cada;
|
|
18
|
+
activate_function str | list[str] : Quais funções de ativação serão usadas a cada camada oculta;
|
|
19
|
+
output_activation str | list[str] : Qual a função ativação da camada de saida (identidade por padrão);
|
|
20
|
+
function_dictionary dict[str, tuple[Callable, Callable]] : Mapa de funções de ativação e suas derivadas;
|
|
21
|
+
baias_interval tuple[float, float] : Qual intervalo os vieses serão gerado ((0, 1) por padrão);
|
|
22
|
+
weight_interval tuple[float, float] : Qual intervalo os pesos serão gerado ((0, 1) por padrão);
|
|
23
|
+
seed int : Seed usada para gerar os vieses e pesos
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
self.original_seed = seed
|
|
27
|
+
self._rng = np.random.default_rng(seed)
|
|
28
|
+
self.layers = layers
|
|
29
|
+
|
|
30
|
+
self._state_linear: list[np.ndarray] = [np.array([]) for _ in range(1, len(layers))]
|
|
31
|
+
self._state_layers: list[np.ndarray] = [np.array([]) for _ in range(len(layers))]
|
|
32
|
+
|
|
33
|
+
self.bias: list[np.ndarray] = [self.random_bias(i, *bias_interval) for i in range(1, len(layers))]
|
|
34
|
+
self.weight: list[np.ndarray] = [self.random_weight(i, *weight_interval) for i in range(1, len(layers))]
|
|
35
|
+
|
|
36
|
+
if isinstance(activate_function, list):
|
|
37
|
+
if len(activate_function) != len(layers)-2:
|
|
38
|
+
raise ValueError("ERROR: Quantidade de funções diferente dos layers ocultos")
|
|
39
|
+
self.activate_functions: list[str] = activate_function.copy()
|
|
40
|
+
elif isinstance(activate_function, str):
|
|
41
|
+
self.activate_functions: list[str] = [activate_function] * (len(layers) - 2)
|
|
42
|
+
else :
|
|
43
|
+
raise TypeError("ERROR: Tipo da activete_function está incorreto")
|
|
44
|
+
|
|
45
|
+
self.activate_functions += [output_activation]
|
|
46
|
+
self._function_dictionary = function_dictionary.copy()
|
|
47
|
+
|
|
48
|
+
self._check_activate(self.activate_functions, self._function_dictionary)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _check_activate(activate_functions: list[str], function_dictionary: dict[str, tuple[Callable, Callable]]) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Verifica se todas as funções de ativação usadas estão no mapa
|
|
54
|
+
|
|
55
|
+
Parâmetros
|
|
56
|
+
-----------
|
|
57
|
+
activate_function list[str] : Quais funções de ativação serão usadas a cada camada oculta;
|
|
58
|
+
function_dictionary dict[str, tuple[Callable, Callable]] : Mapa de funções de ativação e suas derivadas.
|
|
59
|
+
"""
|
|
60
|
+
for function_name in activate_functions:
|
|
61
|
+
if function_name not in function_dictionary:
|
|
62
|
+
raise KeyError(f"ERROR: Função de ativação inválida: {function_name}")
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _check_input(x: np.ndarray) -> np.ndarray:
|
|
66
|
+
"""
|
|
67
|
+
Verifica se x é valido e arruma suas dimenções
|
|
68
|
+
|
|
69
|
+
Parâmetros
|
|
70
|
+
-----------
|
|
71
|
+
x np.ndarray : Array a se verificar.
|
|
72
|
+
|
|
73
|
+
Retorna
|
|
74
|
+
-----------
|
|
75
|
+
x com as dimenções atualizadas
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
if isinstance(x, float) or isinstance(x, int):
|
|
79
|
+
x = np.array([x])
|
|
80
|
+
elif isinstance(x, list) or isinstance(x, tuple):
|
|
81
|
+
x = np.array(x)
|
|
82
|
+
|
|
83
|
+
if x.ndim > 2:
|
|
84
|
+
raise ValueError("Entrada invalida, x deve ser um vetor ou matriz")
|
|
85
|
+
elif x.ndim == 1:
|
|
86
|
+
if len(x) == 1:
|
|
87
|
+
x = x.reshape(-1, 1)
|
|
88
|
+
else:
|
|
89
|
+
x = x.reshape(1, -1)
|
|
90
|
+
|
|
91
|
+
return x
|
|
92
|
+
|
|
93
|
+
def random_bias(self, level: int, low: float = 0, high: float = 1) -> np.ndarray:
|
|
94
|
+
"""
|
|
95
|
+
Gera um vetor viés uniformemente aleatório entre [low, high]
|
|
96
|
+
|
|
97
|
+
Parâmetros
|
|
98
|
+
-----------
|
|
99
|
+
level int : Para qual layer será gerado o viés;
|
|
100
|
+
low float : Valor mínimo dos vieses (0 por padrão);
|
|
101
|
+
high float : Valor maximo dos vieses (1 por padrão).
|
|
102
|
+
|
|
103
|
+
Retorna
|
|
104
|
+
-----------
|
|
105
|
+
Vetor dos vieses com o mesma largura do layer[nivel]
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
array = self._rng.uniform(low, high, (self.layers[level], 1))
|
|
109
|
+
|
|
110
|
+
return array
|
|
111
|
+
|
|
112
|
+
def random_weight(self, level: int, low: float = 0, high: float = 1) -> np.ndarray:
|
|
113
|
+
"""
|
|
114
|
+
Gera uma matriz peso uniformemente aleatória entre [low, high]
|
|
115
|
+
|
|
116
|
+
Parâmetros
|
|
117
|
+
-----------
|
|
118
|
+
level int : Para qual layer será gerado o peso;
|
|
119
|
+
low float : Valor mínimo dos pesos (0 por padrão);
|
|
120
|
+
high float : Valor maximo dos pesos (1 por padrão).
|
|
121
|
+
|
|
122
|
+
Retorna
|
|
123
|
+
-----------
|
|
124
|
+
matriz dos pesos com dimenção da largura do layer[nivel] x layer[nivel-1]
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
array = self._rng.uniform(low, high, (self.layers[level], self.layers[level-1]))
|
|
128
|
+
|
|
129
|
+
return array
|
|
130
|
+
|
|
131
|
+
def train(self, X: np.ndarray, y: np.ndarray, loss: Callable = metricas.MSE, loss_diff: Callable = metricas.DMSE, learn_rate: float = 0.1,
|
|
132
|
+
batch: int = 10, epochs: int = 1000, tol = 1e-9) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Treina a rede neural a partir dos dados X, y
|
|
135
|
+
|
|
136
|
+
Parâmetros
|
|
137
|
+
-----------
|
|
138
|
+
X np.ndarray : Matriz P x N dos dados de treino;
|
|
139
|
+
y np.ndarray : Matriz S x N das saidas de treino;
|
|
140
|
+
loss Callable : Função perda para aprendizado;
|
|
141
|
+
loss_diff Callable : Derivada da função perda para backpropagation;
|
|
142
|
+
learn_rate float : Taxa de aprendizado (0.1 por padrão);
|
|
143
|
+
batch int : Quantidade esperada de elementos por balde (10 por padrão);
|
|
144
|
+
epochs int : Quantidade de interações nos dados de treino (1000 por padrão).
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
self.loss = loss
|
|
148
|
+
self.loss_diff = loss_diff
|
|
149
|
+
|
|
150
|
+
loss_function: str = 'MSE' #COLOCAR PARA PARAMETROS
|
|
151
|
+
|
|
152
|
+
X = self._check_input(X)
|
|
153
|
+
y = self._check_input(y)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
index: np.ndarray = np.arange(X.shape[1])
|
|
157
|
+
|
|
158
|
+
loss = self.loss(y, self.prevision(X))
|
|
159
|
+
for epoch in range(epochs):
|
|
160
|
+
self._rng.shuffle(index)
|
|
161
|
+
|
|
162
|
+
for i in range(0, X.shape[1], batch):
|
|
163
|
+
if X.shape[1] - i < 2*batch:
|
|
164
|
+
B = index[i:]
|
|
165
|
+
else:
|
|
166
|
+
B = index[i: i+batch]
|
|
167
|
+
|
|
168
|
+
self._backpropagation(X[:, B], y[:, B], loss_function = loss_function, learn_rate = learn_rate)
|
|
169
|
+
|
|
170
|
+
loss_aux = self.loss(y, self.prevision(X))
|
|
171
|
+
|
|
172
|
+
if abs(loss - loss_aux) < tol:
|
|
173
|
+
break
|
|
174
|
+
else:
|
|
175
|
+
loss = loss_aux
|
|
176
|
+
|
|
177
|
+
def _backpropagation(self, x: np.ndarray, y: np.ndarray, loss_function: str = 'MSE', learn_rate: float = 0.1) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Calcula as decidas de gradiente para cada peso e viés.
|
|
180
|
+
|
|
181
|
+
Parâmetros
|
|
182
|
+
-----------
|
|
183
|
+
x np.ndarray : Matriz P x N dos dados de treino;
|
|
184
|
+
y np.ndarray : Matriz S x N das saidas de treino;
|
|
185
|
+
loss_function str : Função perda utilizada para calcular o gradiente;
|
|
186
|
+
learn_rate float : Taxa de aprendizado (0.1 por padrão);
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
self.prevision(x, save_states=True)
|
|
191
|
+
cost: list[np.ndarray] = [np.array([]) for _ in range(1, len(self.layers))]
|
|
192
|
+
|
|
193
|
+
activate_function = self.activate_functions[-1]
|
|
194
|
+
cost[-1] = self.loss_diff(y, self._state_layers[-1]) * self._function_dictionary[activate_function][1](self._state_linear[-1])
|
|
195
|
+
|
|
196
|
+
for level in range(len(self.layers)-3, -1, -1):
|
|
197
|
+
activate_function = self.activate_functions[level]
|
|
198
|
+
cost[level] = self.weight[level+1].T @ cost[level+1] * self._function_dictionary[activate_function][1](self._state_linear[level])
|
|
199
|
+
|
|
200
|
+
for level in range(len(self.layers)-1):
|
|
201
|
+
self.weight[level] -= learn_rate * (cost[level] @ self._state_layers[level].T)
|
|
202
|
+
self.bias[level] -= learn_rate * np.sum(cost[level], axis=1, keepdims=True)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def prevision(self, x: float | np.ndarray, save_states: bool = False, classify: bool = False) -> np.ndarray:
|
|
206
|
+
"""
|
|
207
|
+
Calcula o valor de saida da rede neural com pesos e vieses atuais
|
|
208
|
+
|
|
209
|
+
Parâmetros
|
|
210
|
+
-----------
|
|
211
|
+
x float | np.ndarray : Vetor ou matriz que queremos prever o resultado;
|
|
212
|
+
save_states bool : Define se queremos salvar os valores de cada layer ou não;
|
|
213
|
+
classify bool : Define se queremos retornar o vetor de probabilidade ou atribuir o elemento a uma classe.
|
|
214
|
+
|
|
215
|
+
Retorna
|
|
216
|
+
-----------
|
|
217
|
+
Valor de saida da rede neural com entrada x
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
x = self._check_input(x)
|
|
221
|
+
|
|
222
|
+
y: np.ndarray = x.copy()
|
|
223
|
+
|
|
224
|
+
if save_states:
|
|
225
|
+
self._state_layers[0] = y.copy()
|
|
226
|
+
|
|
227
|
+
for level in range(len(self.layers)-1):
|
|
228
|
+
bayes: np.ndarray = self.bias[level]
|
|
229
|
+
weight: np.ndarray = self.weight[level]
|
|
230
|
+
activate_func = self._function_dictionary[self.activate_functions[level]][0]
|
|
231
|
+
|
|
232
|
+
linear = bayes + weight @ y
|
|
233
|
+
layer = activate_func(linear)
|
|
234
|
+
if save_states:
|
|
235
|
+
self._state_linear[level] = linear
|
|
236
|
+
self._state_layers[level + 1] = layer
|
|
237
|
+
|
|
238
|
+
y = layer
|
|
239
|
+
|
|
240
|
+
if classify:
|
|
241
|
+
return np.argmax(y)
|
|
242
|
+
|
|
243
|
+
return y
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
|
|
248
|
+
print('-'*100)
|
|
249
|
+
print('Teste 1')
|
|
250
|
+
print('-'*100)
|
|
251
|
+
rn1 = NeuralNetwork([2, 4, 5, 2], activate_function="sigmoid", output_activation="sigmoid", bias_interval = (-1, 1), weight_interval = (-1, 1))
|
|
252
|
+
|
|
253
|
+
rng = np.random.default_rng(1)
|
|
254
|
+
X = rng.uniform(-4, 4, (2, 1000))
|
|
255
|
+
y = np.zeros(X.shape)
|
|
256
|
+
|
|
257
|
+
for i, x in enumerate(X.T):
|
|
258
|
+
if x[0]**2 + x[1]**2 <= 9:
|
|
259
|
+
y[:, i] = [1, 0]
|
|
260
|
+
else:
|
|
261
|
+
y[:, i] = [0, 1]
|
|
262
|
+
|
|
263
|
+
rn1.train(X, y, batch=50)
|
|
264
|
+
|
|
265
|
+
x1 = np.array([[1], [0]])
|
|
266
|
+
x2 = np.array([[3], [3]])
|
|
267
|
+
x3 = np.array([[5], [5]])
|
|
268
|
+
|
|
269
|
+
print(f"\nx_1=({x1[0]}, {x1[1]}) y_1 = {rn1.prevision(x1)}")
|
|
270
|
+
print(f"\ny_2 = {rn1.prevision(x2)}")
|
|
271
|
+
print(f"\ny_3 = {rn1.prevision(x3)}")
|
|
272
|
+
|
|
273
|
+
print(f"\nx_1=({x1[0]}, {x1[1]}) y_1 = {rn1.prevision(x1, classify=True)}")
|
|
274
|
+
print(f"\ny_2 = {rn1.prevision(x2, classify=True)}")
|
|
275
|
+
print(f"\ny_3 = {rn1.prevision(x3, classify=True)}")
|
|
276
|
+
|
|
277
|
+
print('-'*100)
|
|
278
|
+
print('Teste 2')
|
|
279
|
+
print('-'*100)
|
|
280
|
+
|
|
281
|
+
rn2 = NeuralNetwork([1, 4, 5, 1], activate_function="ReLU", output_activation="identity", bias_interval = (-0.1, 0.1), weight_interval = (-0.1, 0.1))
|
|
282
|
+
|
|
283
|
+
f = lambda x: np.exp(x)
|
|
284
|
+
|
|
285
|
+
X = np.linspace(-2, 3, 10000).reshape(1, -1)
|
|
286
|
+
y = f(X)
|
|
287
|
+
|
|
288
|
+
rn2.train(X, y, batch=50, learn_rate=0.001, epochs=10000)
|
|
289
|
+
|
|
290
|
+
for x1 in range(20):
|
|
291
|
+
print(f"\nx_1={x1}, y_1={rn2.prevision(x1)}")
|
|
292
|
+
|