Rixf123's picture
download
raw
9.07 kB
import numpy as np
def relu(x):
return np.maximum(0.0, x)
def relu_deriv(x):
return (x > 0.0).astype(float)
def softmax(x):
exps = np.exp(x - np.max(x, axis=1, keepdims=True))
return exps / np.sum(exps, axis=1, keepdims=True)
def cross_entropy_loss(Y_hat, Y):
m = Y.shape[0]
safe_hat = np.clip(Y_hat, 1e-12, 1.0 - 1e-12)
return -np.sum(Y * np.log(safe_hat)) / m
def he_initialization(fan_in, fan_out):
return np.random.randn(fan_in, fan_out) * np.sqrt(2.0 / max(1, fan_in))
def xavier_initialization(fan_in, fan_out):
return np.random.randn(fan_in, fan_out) * np.sqrt(2.0 / (fan_in + fan_out))
class NeuralNetwork:
def __init__(
self,
layer_sizes,
lr=0.01,
optimizer="adam",
reg_lambda=1e-4,
dropout=0.0,
seed=42,
):
if len(layer_sizes) < 3:
raise ValueError("layer_sizes must contain input, hidden, and output sizes")
self.layer_sizes = list(layer_sizes)
self.lr = float(lr)
self.optimizer = optimizer.lower()
self.reg_lambda = float(reg_lambda)
self.dropout = float(dropout)
if self.lr <= 0.0:
raise ValueError("lr must be positive")
if self.reg_lambda < 0.0:
raise ValueError("reg_lambda must be non-negative")
if not 0.0 <= self.dropout < 1.0:
raise ValueError("dropout must be between 0 and 1")
self.rng = np.random.RandomState(seed)
self.params = {}
self.cache = {}
self._initialize_parameters()
self._initialize_optimizer_state()
@property
def num_layers(self):
return len(self.layer_sizes) - 1
def _initialize_parameters(self):
for layer in range(1, self.num_layers + 1):
fan_in = self.layer_sizes[layer - 1]
fan_out = self.layer_sizes[layer]
if layer == self.num_layers:
self.params[f"W{layer}"] = xavier_initialization(fan_in, fan_out)
else:
self.params[f"W{layer}"] = he_initialization(fan_in, fan_out)
self.params[f"b{layer}"] = np.zeros((1, fan_out), dtype=float)
def _initialize_optimizer_state(self):
if self.optimizer == "adam":
self.optimizer_state = {
"t": 0,
"m": {
**{f"W{layer}": np.zeros_like(self.params[f"W{layer}"]) for layer in range(1, self.num_layers + 1)},
**{f"b{layer}": np.zeros_like(self.params[f"b{layer}"]) for layer in range(1, self.num_layers + 1)},
},
"v": {
**{f"W{layer}": np.zeros_like(self.params[f"W{layer}"]) for layer in range(1, self.num_layers + 1)},
**{f"b{layer}": np.zeros_like(self.params[f"b{layer}"]) for layer in range(1, self.num_layers + 1)},
},
"beta1": 0.9,
"beta2": 0.999,
"epsilon": 1e-8,
}
elif self.optimizer == "momentum":
self.optimizer_state = {
"velocity": {
**{f"W{layer}": np.zeros_like(self.params[f"W{layer}"]) for layer in range(1, self.num_layers + 1)},
**{f"b{layer}": np.zeros_like(self.params[f"b{layer}"]) for layer in range(1, self.num_layers + 1)},
},
"beta": 0.9,
}
else:
self.optimizer_state = {}
def forward(self, X, training=False):
self.cache["A0"] = X
self.cache["dropout_masks"] = {}
A = X
for layer in range(1, self.num_layers + 1):
Z = A.dot(self.params[f"W{layer}"]) + self.params[f"b{layer}"]
self.cache[f"Z{layer}"] = Z
if layer < self.num_layers:
A = relu(Z)
if training and self.dropout > 0.0:
mask = (self.rng.rand(*A.shape) >= self.dropout).astype(float) / (1.0 - self.dropout)
A *= mask
self.cache["dropout_masks"][f"D{layer}"] = mask
self.cache[f"A{layer}"] = A
else:
A = softmax(Z)
self.cache[f"A{layer}"] = A
return A
def compute_loss(self, Y_hat, Y):
loss = cross_entropy_loss(Y_hat, Y)
if self.reg_lambda > 0.0:
reg_loss = 0.0
for layer in range(1, self.num_layers + 1):
reg_loss += np.sum(self.params[f"W{layer}"] ** 2)
loss += self.reg_lambda * reg_loss / Y.shape[0]
return loss
def backward(self, Y):
m = Y.shape[0]
grads = {}
dA = self.cache[f"A{self.num_layers}"] - Y
for layer in reversed(range(1, self.num_layers + 1)):
A_prev = self.cache[f"A{layer - 1}"]
grads[f"dW{layer}"] = A_prev.T.dot(dA) / m + (self.reg_lambda / m) * self.params[f"W{layer}"]
grads[f"db{layer}"] = np.sum(dA, axis=0, keepdims=True) / m
if layer > 1:
dA = dA.dot(self.params[f"W{layer}"].T)
dZ = dA * relu_deriv(self.cache[f"Z{layer - 1}"])
if self.dropout > 0.0:
dZ *= self.cache["dropout_masks"][f"D{layer - 1}"]
dA = dZ
self._update_parameters(grads)
def _update_parameters(self, grads):
if self.optimizer == "adam":
state = self.optimizer_state
state["t"] += 1
beta1 = state["beta1"]
beta2 = state["beta2"]
eps = state["epsilon"]
t = state["t"]
for layer in range(1, self.num_layers + 1):
for param_name in [f"W{layer}", f"b{layer}"]:
g = grads[f"d{param_name}"]
state["m"][param_name] = beta1 * state["m"][param_name] + (1 - beta1) * g
state["v"][param_name] = beta2 * state["v"][param_name] + (1 - beta2) * (g * g)
m_hat = state["m"][param_name] / (1 - beta1 ** t)
v_hat = state["v"][param_name] / (1 - beta2 ** t)
self.params[param_name] -= self.lr * m_hat / (np.sqrt(v_hat) + eps)
elif self.optimizer == "momentum":
state = self.optimizer_state
beta = state["beta"]
for layer in range(1, self.num_layers + 1):
for param_name in [f"W{layer}", f"b{layer}"]:
velocity = state["velocity"][param_name]
velocity *= beta
velocity += self.lr * grads[f"d{param_name}"]
self.params[param_name] -= velocity
state["velocity"][param_name] = velocity
else:
for layer in range(1, self.num_layers + 1):
self.params[f"W{layer}"] -= self.lr * grads[f"dW{layer}"]
self.params[f"b{layer}"] -= self.lr * grads[f"db{layer}"]
def fit(
self,
X,
Y,
X_val=None,
Y_val=None,
epochs=100,
batch_size=16,
patience=20,
verbose=True,
):
n = X.shape[0]
best_val_loss = float("inf")
best_params = None
no_improve = 0
for epoch in range(1, epochs + 1):
perm = self.rng.permutation(n)
X_shuffled = X[perm]
Y_shuffled = Y[perm]
for start in range(0, n, batch_size):
xb = X_shuffled[start : start + batch_size]
yb = Y_shuffled[start : start + batch_size]
self.forward(xb, training=True)
self.backward(yb)
Y_hat = self.forward(X)
train_loss = self.compute_loss(Y_hat, Y)
train_acc = np.mean(np.argmax(Y_hat, axis=1) == np.argmax(Y, axis=1))
val_loss = None
val_acc = None
if X_val is not None and Y_val is not None and X_val.shape[0] > 0:
Y_val_hat = self.forward(X_val)
val_loss = self.compute_loss(Y_val_hat, Y_val)
val_acc = np.mean(np.argmax(Y_val_hat, axis=1) == np.argmax(Y_val, axis=1))
if val_loss + 1e-8 < best_val_loss:
best_val_loss = val_loss
best_params = {k: v.copy() for k, v in self.params.items()}
no_improve = 0
else:
no_improve += 1
if verbose and (epoch == 1 or epoch % max(1, epochs // 10) == 0):
msg = f"Epoch {epoch}/{epochs} - train_loss: {train_loss:.4f}, train_acc: {train_acc:.4f}"
if val_loss is not None:
msg += f", val_loss: {val_loss:.4f}, val_acc: {val_acc:.4f}"
print(msg)
if no_improve >= patience:
if best_params is not None:
self.params = best_params
break
return self
def predict_proba(self, X):
return self.forward(X, training=False)
def predict(self, X):
return np.argmax(self.predict_proba(X), axis=1)

Xet Storage Details

Size:
9.07 kB
·
Xet hash:
8fcd0ec0b19f13489bf778c4c0d35b94d8519ad6262c54a072071499cabaf2c9

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.