from abc import ABC, abstractmethodimport numpy as npimport timeimport reimport inspectfrom collections import OrderedDictimport syssys.path.append('../')from method.optimizer import OptimizerInitializerfrom method.weight import WeightInitializerfrom method.activation import ActivationInitializerdef sigmoid(x):return 1 / (1 + np.exp(-x))def softmax(x):e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))return e_x / e_x.sum(axis=-1, keepdims=True)class LayerBase(ABC):def __init__(self, optimizer="sgd"):self.X = [] # 网络层输入self.gradients = {} # 网络层待梯度更新变量self.params = {} # 网络层参数变量self.acti_fn = None # 网络层激活函数self.optimizer = OptimizerInitializer(optimizer)() # 网络层优化方法@abstractmethoddef _init_params(self, **kwargs):"""函数作用:初始化参数"""raise NotImplementedError@abstractmethoddef forward(self, X, **kwargs):"""函数作用:前向传播"""raise NotImplementedError@abstractmethoddef backward(self, out, **kwargs):"""函数作用:反向传播"""raise NotImplementedErrordef flush_gradients(self):"""函数作用:重置更新参数列表"""self.X = []for k, v in self.gradients.items():self.gradients[k] = np.zeros_like(v)for k, v in self.derived_variables.items():self.derived_variables[k] = []def update(self):"""函数作用:更新参数"""for k, v in self.gradients.items():if k in self.params:self.params[k] = self.optimizer(self.params[k], v, k)class FullyConnected(LayerBase):"""定义全连接层,实现 a=g(x*W+b),前向传播输入x,返回a;反向传播输入"""def __init__(self, n_out, acti_fn, init_w, optimizer=None):"""参数说明:acti_fn:激活函数, str型init_w:权重初始化方法, str型n_out:隐藏层输出维数optimizer:优化方法"""super().__init__(optimizer)self.n_in = None # 隐藏层输入维数, int型self.n_out = n_out # 隐藏层输出维数, int型self.acti_fn = ActivationInitializer(acti_fn)()self.init_w = init_wself.init_weights = WeightInitializer(mode=init_w)self.is_initialized = False # 是否初始化, bool型变量def _init_params(self):b = np.zeros((1, self.n_out))W = self.init_weights((self.n_in, self.n_out))self.params = {"W": W, "b": b}self.gradients = {"W": np.zeros_like(W), "b": np.zeros_like(b)}self.derived_variables = {"Z": []}self.is_initialized = Truedef forward(self, X, retain_derived=True):"""全连接网络的前向传播,原理见上文 反向传播算法 部分。参数说明:X:输入数组,为(n_samples, n_in),float型retain_derived:是否保留中间变量,以便反向传播时再次使用,bool型"""if not self.is_initialized: # 如果参数未初始化,先初始化参数self.n_in = X.shape[1]self._init_params()W = self.params["W"]b = self.params["b"]z = X @ W + ba = self.acti_fn.forward(z)if retain_derived:self.X.append(X)return adef backward(self, dLda, retain_grads=True):"""全连接网络的反向传播,原理见上文 反向传播算法 部分。参数说明:dLda:关于损失的梯度,为(n_samples, n_out),float型retain_grads:是否计算中间变量的参数梯度,bool型"""if not isinstance(dLda, list):dLda = [dLda]dX = []X = self.Xfor da, x in zip(dLda, X):dx, dw, db = self._bwd(da, x)dX.append(dx)if retain_grads:self.gradients["W"] += dwself.gradients["b"] += dbreturn dX[0] if len(X) == 1 else dXdef _bwd(self, dLda, X):W = self.params["W"]b = self.params["b"]Z = X @ W + bdZ = dLda * self.acti_fn.grad(Z)dX = dZ @ W.TdW = X.T @ dZdb = dZ.sum(axis=0, keepdims=True)return dX, dW, db@propertydef hyperparams(self):return {"layer": "FullyConnected","init_w": self.init_w,"n_in": self.n_in,"n_out": self.n_out,"acti_fn": str(self.acti_fn),"optimizer": {"hyperparams": self.optimizer.hyperparams,},"components": {k: v for k, v in self.params.items()}}class ObjectiveBase(ABC):def __init__(self):super().__init__()@abstractmethoddef loss(self, y_true, y_pred):"""函数作用:计算损失"""raise NotImplementedError@abstractmethoddef grad(self, y_true, y_pred, **kwargs):"""函数作用:计算代价函数的梯度"""raise NotImplementedErrorclass SquaredError(ObjectiveBase):"""二次代价函数。"""def __init__(self):super().__init__()def __call__(self, y_true, y_pred):return self.loss(y_true, y_pred)def __str__(self):return "SquaredError"@staticmethoddef loss(y_true, y_pred):"""参数说明:y_true:训练的 n 个样本的真实值, 形状为(n,m)数组;y_pred:训练的 n 个样本的预测值, 形状为(n,m)数组;"""(n, _) = y_true.shapereturn 0.5 * np.linalg.norm(y_pred - y_true) ** 2 / n@staticmethoddef grad(y_true, y_pred, z, acti_fn):(n, _) = y_true.shapereturn (y_pred - y_true) * acti_fn.grad(z) / nclass CrossEntropy(ObjectiveBase):"""交叉熵代价函数。"""def __init__(self):super().__init__()def __call__(self, y_true, y_pred):return self.loss(y_true, y_pred)def __str__(self):return "CrossEntropy"@staticmethoddef loss(y_true, y_pred):"""参数说明:y_true:训练的 n 个样本的真实值, 要求形状为(n,m)二进制(每个样本均为 one-hot 编码);y_pred:训练的 n 个样本的预测值, 形状为(n,m);"""(n, _) = y_true.shapeeps = np.finfo(float).eps # 防止 np.log(0)cross_entropy = -np.sum(y_true * np.log(y_pred + eps)) / nreturn cross_entropy@staticmethoddef grad(y_true, y_pred):(n, _) = y_true.shapegrad = (y_pred - y_true) / nreturn graddef minibatch(X, batchsize=256, shuffle=True):"""函数作用:将数据集分割成 batch, 基于 mini batch 训练。"""N = X.shape[0]idx = np.arange(N)n_batches = int(np.ceil(N / batchsize))if shuffle:np.random.shuffle(idx)def mb_generator():for i in range(n_batches):yield idx[i * batchsize : (i + 1) * batchsize]return mb_generator(), n_batchesclass DFN(object):def __init__(self,hidden_dims_1=None,hidden_dims_2=None,optimizer="sgd(lr=0.01)",init_w="std_normal",loss=CrossEntropy()):self.optimizer = optimizerself.init_w = init_wself.loss = lossself.hidden_dims_1 = hidden_dims_1self.hidden_dims_2 = hidden_dims_2self.is_initialized = Falsedef _set_params(self):"""函数作用:模型初始化FC1 -> Sigmoid -> FC2 -> Softmax"""self.layers = OrderedDict()self.layers["FC1"] = FullyConnected(n_out=self.hidden_dims_1,acti_fn="sigmoid",init_w=self.init_w,optimizer=self.optimizer)self.layers["FC2"] = FullyConnected(n_out=self.hidden_dims_2,acti_fn="affine(slope=1, intercept=0)",init_w=self.init_w,optimizer=self.optimizer)self.is_initialized = Truedef forward(self, X_train):Xs = {}out = X_trainfor k, v in self.layers.items():Xs[k] = outout = v.forward(out)return out, Xsdef backward(self, grad):dXs = {}out = gradfor k, v in reversed(list(self.layers.items())):dXs[k] = outout = v.backward(out)return out, dXsdef update(self):"""函数作用:梯度更新"""for k, v in reversed(list(self.layers.items())):v.update()self.flush_gradients()def flush_gradients(self, curr_loss=None):"""函数作用:更新后重置梯度"""for k, v in self.layers.items():v.flush_gradients()def fit(self, X_train, y_train, n_epochs=20, batch_size=64, verbose=False, epo_verbose=True):"""参数说明:X_train:训练数据y_train:训练数据标签n_epochs:epoch 次数batch_size:每次 epoch 的 batch sizeverbose:是否每个 batch 输出损失epo_verbose:是否每个 epoch 输出损失"""self.verbose = verboseself.n_epochs = n_epochsself.batch_size = batch_sizeif not self.is_initialized:self.n_features = X_train.shape[1]self._set_params()prev_loss = np.inffor i in range(n_epochs):loss, epoch_start = 0.0, time.time()batch_generator, n_batch = minibatch(X_train, self.batch_size, shuffle=True)for j, batch_idx in enumerate(batch_generator):batch_len, batch_start = len(batch_idx), time.time()X_batch, y_batch = X_train[batch_idx], y_train[batch_idx]out, _ = self.forward(X_batch)y_pred_batch = softmax(out)batch_loss = self.loss(y_batch, y_pred_batch)grad = self.loss.grad(y_batch, y_pred_batch)_, _ = self.backward(grad)self.update()loss += batch_lossif self.verbose:fstr = "\t[Batch {}/{}] Train loss: {:.3f} ({:.1f}s/batch)"print(fstr.format(j + 1, n_batch, batch_loss, time.time() - batch_start))loss /= n_batchif epo_verbose:fstr = "[Epoch {}] Avg. loss: {:.3f} Delta: {:.3f} ({:.2f}m/epoch)"print(fstr.format(i + 1, loss, prev_loss - loss, (time.time() - epoch_start) / 60.0))prev_loss = lossdef evaluate(self, X_test, y_test, batch_size=128):acc = 0.0batch_generator, n_batch = minibatch(X_test, batch_size, shuffle=True)for j, batch_idx in enumerate(batch_generator):batch_len, batch_start = len(batch_idx), time.time()X_batch, y_batch = X_test[batch_idx], y_test[batch_idx]y_pred_batch, _ = self.forward(X_batch)y_pred_batch = np.argmax(y_pred_batch, axis=1)y_batch = np.argmax(y_batch, axis=1)acc += np.sum(y_pred_batch == y_batch)return acc / X_test.shape[0]@propertydef hyperparams(self):return {"init_w": self.init_w,"loss": str(self.loss),"optimizer": self.optimizer,"hidden_dims_1": self.hidden_dims_1,"hidden_dims_2": self.hidden_dims_2,"components": {k: v.params for k, v in self.layers.items()}}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。