def train(self, x_mat: ndarray, y_row: ndarray) -> List[ndarray]: """ 训练前馈神经网络,既可以训练二分类问题,也可以训练多类分类问题。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 每层的权值(输出层没有)。 """ self.__check_params() x_mat, y_row = _t.match_x_y(x_mat, y_row) if x_mat.shape[1] - 1 != self.layer_nodes[0]: raise DataNotMatchError( 'feature number and input layer node number mismatch') y_row = _t.convert_y(self.labels, y_row) init_theta = np.empty((0, )) for i in range(len(self.layer_nodes) - 1): theta = self.__rand_thetas(self.layer_nodes[i], self.layer_nodes[i + 1]) init_theta = np.hstack((init_theta, theta.ravel())) self._thetas = self.__extract_thetas( op.fmin_cg( f=lambda t, x, y: self.__cost(self.__extract_thetas(t), x, y), x0=init_theta, args=(x_mat, y_row), maxiter=self.max_iter, fprime=lambda t, x, y: self.__gradient( self.__extract_thetas(t), x, y))) return self._thetas
def train(self, x_mat: ndarray, y_row: ndarray) -> ndarray: """ 进行训练。可以使用梯度下降或正规方程算法进行训练,其中正规方程法对于特征不是很多的情况下, 比如 n <= 10000,会取得很好的效果。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return 训练的来的参数 """ x_mat, y_row = _t.match_x_y(x_mat, y_row) n = x_mat.shape[1] if self.method == 'gradient': self._theta_row = opt.fmin_cg( f=lambda t, x, y: self.__cost(t, x, y), x0=np.zeros((n, )), args=(x_mat, y_row), maxiter=self.max_iter, fprime=lambda t, x, y: self.__gradient(t, x, y)) elif self.method == 'normal': self._theta_row = self.__normal_eqn(x_mat, y_row) else: raise ValueError('parameter method must be "gradient" or "normal') self.x_mat = x_mat self.y_row = y_row return self._theta_row
def cost(self, x_mat: ndarray, y_row: ndarray) -> float: """ 计算在 x_mat 和 y_row 上的代价。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值。 """ if self._theta is None: raise StateError('not trained yet') self._theta, x_mat = _t.match_theta_x(self._theta, x_mat) x_mat, y_row = _t.match_x_y(x_mat, y_row) if len(self.labels) == 2: return self.__cost(self._theta, x_mat, y_row) else: m = x_mat.shape[0] cost_sum = 0 for i, label in enumerate(self.labels): y = y_row == label cost_sum = cost_sum + np.sum(y) * self.__cost( self._theta[:, i], x_mat, y) / m return cost_sum
def train(self, x_mat: ndarray, y_row: ndarray) -> ndarray: """ 训练逻辑回归,既可以训练二分类问题,也可以训练多类分类问题。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 参数。二分类问题返回行向量。多类分类问题返回 n*num_labels 的矩阵,每一列表示对应类别的参数。 """ x_mat, y_row = _t.match_x_y(x_mat, y_row) y_row = _t.convert_y(self.labels, y_row) n = x_mat.shape[1] if len(self.labels) == 2: self._theta = opt.fmin_cg( f=lambda t, x, y: self.__cost(t, x, y), x0=np.zeros((n, )), args=(x_mat, y_row), maxiter=self.max_iter, fprime=lambda t, x, y: self.__gradient(t, x, y)) else: self._theta = np.zeros((n, len(self.labels))) for i, label in enumerate(self.labels): self._theta[:, i] = opt.fmin_cg( f=lambda t, x, y: self.__cost(t, x, y == label), x0=np.zeros((n, )), args=(x_mat, y_row), maxiter=self.max_iter, fprime=lambda t, x, y: self.__gradient(t, x, y == label)) return self._theta
def train(self, x_mat: ndarray, y_row: ndarray): """ 使用给定的数据对 LDA 学习算法进行训练。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 学习得来的参数 """ x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) self._center_projections.clear() if len(self.labels) == 2: # 两种类别的样本 x0 = x_mat[y_row == self.labels[0], :] x1 = x_mat[y_row == self.labels[1], :] # 样本中心点 u0 = np.mean(x0, axis=0) u1 = np.mean(x1, axis=0) sw = (x0 - u0).T @ (x0 - u0) + (x1 - u1).T @ (x1 - u1) # 类内散度矩阵 self._theta = np.linalg.pinv(sw) @ (u0 - u1) self._center_projections[self.labels[0]] = self._theta @ u0 self._center_projections[self.labels[1]] = self._theta @ u1 else: xn = [] un = OrderedDict() u_mean = 0 for label in self.labels: xn.append(x_mat[y_row == label, :]) un[label] = np.mean(xn[-1], axis=0) u_mean = u_mean + un[label] u_mean = u_mean / x_mat.shape[0] sw = 0 sb = 0 n = u_mean.shape[0] for x, u in zip(xn, un.values()): sw = sw + (x - u).T @ (x - u) sb = sb + x.shape[0] * (u - u_mean).reshape( (n, 1)) @ (u - u_mean).reshape((1, n)) ev, fv = np.linalg.eig(np.linalg.pinv(sw) @ sb) # 去掉 0 特征 fv = fv[:, np.isclose(ev, 0) == False] ev = ev[np.isclose(ev, 0) == False] # 从大到小排序 # TODO: 考虑是否只在压缩的时候进行排序 indices = np.argsort(ev)[::-1] fv = fv[:, indices] self._theta = np.real(fv) for label in self.labels: self._center_projections[label] = self._theta.T @ un[label] return self._theta
def cost_curve(x_mat: ndarray, y_vec: ndarray, x_cv: ndarray, y_cv: ndarray, learner: Callable[[], ISuperviseLearner], *, ran: Union[int, Tuple[int], List[int], ndarray] = 0, add_ones: bool = True) \ -> Tuple[ndarray, ndarray]: """ 获取学习曲线值,诊断算法是否有高偏差或高方差问题。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_vec: 输出向量,可以是列向量也可以是行向量,每一个值代表 x_mat 中对应行的输出 :param x_cv: 交叉验证集特征向量组 :param y_cv: 交叉验证集输出向量 :param learner: 学习器生成函数,用以生成一个学习器,这个学习器必须有 train 方法用以训练,还要有 cost 方法, 用以计算代价 :param ran: 需要检验的训练集样本范围,默认为 0,表示选取所有的训练集样本。 :param add_ones: 是否增加截距项 :return: 训练集代价行向量;验证集代价行向量 """ x_mat, y_vec = _t.match_x_y(x_mat, y_vec, add_ones=add_ones) x_cv, y_cv = _t.match_x_y(x_cv, y_cv, add_ones=add_ones) m = x_mat.shape[0] if isinstance(ran, int): if ran <= 0 or ran > m: ran = np.arange(1, m + 1) else: ran = np.arange(1, ran + 1) cost_train = np.empty((len(ran), )) cost_cv = np.empty((len(ran), )) for i, interval in enumerate(ran): if interval <= 0 or interval > m: continue xi = x_mat[:interval, :] yi = y_vec[:interval] lm = learner() lm.train(xi, yi) cost_train[i] = lm.cost(xi, yi) cost_cv[i] = lm.cost(x_cv, y_cv) return cost_train, cost_cv
def train(self, x_mat: ndarray, y_row: ndarray): """ 进行训练。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 """ if self.positive_label is not None and self.negative_label is not None and \ self.positive_label == self.negative_label: raise ValueError( 'positive_label and negative_label cannot be the same') x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) self._trained = True self._learners.clear() if self.strategy == 'ovr': for label in self._labels: positive_label = label if self.positive_label is None else self.positive_label negative_label = label + 1 if self.negative_label is None else self.negative_label learner = self.learner(positive_label, negative_label) self._learners[label] = learner y = y_row.copy() # 记录更改之前的坐标,防止 positive_label 和 negative 相等或 negative_label 和 positive 相等这种情况 positive_idx = y == label negative_idx = y != label if self.positive_label is not None: y[positive_idx] = self.positive_label y[negative_idx] = negative_label learner.train(x_mat, y) else: for positive in self.labels: for negative in self.labels: if positive == negative: continue positive_label = positive if self.positive_label is None else self.positive_label negative_label = negative if self.negative_label is None else self.negative_label learner = self.learner(positive_label, negative_label) self._learners[(positive, negative)] = learner y = y_row[(y_row == positive) | (y_row == negative)] x = x_mat[(y_row == positive) | (y_row == negative)] # 记录更改之前的坐标,防止 positive_label 和 negative 相等或 negative_label 和 positive 相等这种情况 positive_idx = y == positive negative_idx = y == negative if self.positive_label is not None: y[positive_idx] = self.positive_label if self.negative_label is not None: y[negative_idx] = self.negative_label learner.train(x, y)
def cost(self, x_mat: ndarray, y_row: ndarray) -> float: """ 计算在 x_mat 和 y_row 上的代价。此方法只计算参与了分类结果的学习器。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值。 """ if not self._trained: raise StateError('not trained yet') x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) pred = self.__predict(x_mat) cost_sum = 0 m = x_mat.shape[0] if self.strategy == 'ovr': for i, label in enumerate(pred): y = y_row[i] if y == label: y = self.positive_label if self.positive_label is not None else label else: y = self.negative_label if self.negative_label is not None else label + 1 cost_sum = cost_sum + self._learners[label].cost( x_mat[i, :], np.array([y])) else: result = np.empty((m, )) for i in range(m): r = pred[:, i] result[i] = sorted([(np.sum(r == label), label) for label in set(r)])[-1][1] for i in range(m): sub_cost_sum = 0 count = 0 for j, ((positive, negative), learner) in enumerate(self._learners.items()): if pred[j][i] == result[i]: y = y_row[i] if y == positive: y = self.positive_label if self.positive_label is not None else positive else: y = self.negative_label if self.negative_label is not None else negative sub_cost_sum = sub_cost_sum + learner.cost( x_mat[i, :], np.array([y])) count = count + 1 if count != 0: cost_sum = cost_sum + sub_cost_sum / count return cost_sum / m
def regularization_curve(x_mat: ndarray, y_vec: ndarray, x_cv: ndarray, y_cv: ndarray, learner: Callable[[Union[int, float]], ISuperviseLearner], *, lambs: Union[tuple, list, ndarray] = (0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10)): """ 不断选取不同的正则化参数,验证在不同正则化参数下的误差。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_vec: 输出向量,可以是列向量也可以是行向量,每一个值代表 x_mat 中对应行的输出 :param x_cv: 交叉验证集特征向量组 :param y_cv: 交叉验证集输出向量 :param learner: 学习器生成函数,参数是正则化参数,用以生成一个学习器,这个学习器必须有 train 方法用以训练, 还要有 cost 方法,用以计算代价 :param lambs: lamb 参数集 :return: 训练集代价行向量;验证集代价行向量;lambs 参数 """ x_mat, y_vec = _t.match_x_y(x_mat, y_vec) x_cv, y_cv = _t.match_x_y(x_cv, y_cv) lambs_len = len(lambs) cost_train = np.empty((lambs_len, )) cost_cv = np.empty((lambs_len, )) for i, lamb in enumerate(lambs): lm = learner(lamb) lm.train(x_mat, y_vec) cost_train[i] = lm.cost(x_mat, y_vec) cost_cv[i] = lm.cost(x_cv, y_cv) return cost_train, cost_cv, lambs
def cost(self, x_mat: ndarray, y_row: ndarray) -> float: """ 计算在 x_mat 和 y_row 上的代价。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值。 """ x_mat = self.__match_theta_x(x_mat) x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) if len(self.labels) == 2: # 两种类别的样本 x0 = x_mat[y_row == self.labels[0], :] x1 = x_mat[y_row == self.labels[1], :] # 样本中心点 u0 = np.mean(x0, axis=0) u1 = np.mean(x1, axis=0) n = u0.shape[0] sw = (x0 - u0).T @ (x0 - u0) + (x1 - u1).T @ (x1 - u1) # 类内散度矩阵 sb = (u0 - u1).reshape((n, 1)) @ (u0 - u1).reshape( (1, n)) # 类间散度矩阵 # 和书上分子分母相反,只是因为约定代价值越小越好,而原来的公式是越大越好,故取反 return (self._theta @ sw @ self._theta) / ( self._theta @ sb @ self._theta) else: xn = [] un = OrderedDict() u_mean = 0 for label in self.labels: xn.append(x_mat[y_row == label, :]) un[label] = np.mean(xn[-1], axis=0) u_mean = u_mean + un[label] u_mean = u_mean / x_mat.shape[0] sw = 0 sb = 0 n = u_mean.shape[0] for x, u in zip(xn, un.values()): sw = sw + (x - u).T @ (x - u) sb = sb + x.shape[0] * (u - u_mean).reshape( (n, 1)) @ (u - u_mean).reshape((1, n)) return np.trace(self._theta.T @ sw @ self._theta) / np.trace( self._theta.T @ sb @ self._theta)
def cost(self, x_mat: ndarray, y_row: ndarray): """ 计算在 x_mat 和 y_row 上的代价。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值 """ if self._theta_row is None: raise StateError('not trained yet') self._theta_row, x_mat = _t.match_theta_x(self._theta_row, x_mat) x_mat, y_row = _t.match_x_y(x_mat, y_row) return self.__cost(self._theta_row, x_mat, y_row)
def train(self, x_mat: ndarray, y_row: ndarray): """ 使用给定的数据对 LDA 学习算法进行训练。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 """ self.__check_params() x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) if x_mat.shape[1] != len(self.pvs): raise DataNotMatchError('feature quantity mismatch') is_pruning = self.x_cv is not None and self.y_cv is not None if is_pruning: self._node0 = None self._pruning_accuracy = 0 if self.pruning == 'pre': # 预剪枝先考虑只有一个叶结点的情况 self._root = _DecisionTreeNode() self._root.label = self.__find_most_label(y_row) self.__pruning_test(self._root) # 递归地进行预剪枝 self._root = self.__generate(x_mat, y_row, self.pvs.copy(), cur_layer=0, max_layer=1) else: # 后剪枝需要先生成整棵树,然后从底层的非叶节点开始进行剪枝,比较是否得到了性能提升 self._stack = [] self._root = self.__generate(x_mat, y_row.ravel(), self.pvs.copy()) self.__pruning_test(self._root) while len(self._stack) > 0: node, label = self._stack.pop(-1) children_num = node.children_num() node.sl_prop() node.label = label # 如果剪枝后性能得到提升,就剪枝,否则就保留 if self.__pruning_test(self._root): self._size = self._size - children_num node.sl_prop(delete=True) else: node.sl_prop(False) else: self._root = self.__generate(x_mat, y_row.ravel(), self.pvs.copy())
def cost(self, x_mat: ndarray, y_row: ndarray) -> float: """ 计算在 x_mat 和 y_row 上的代价。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值。 """ if self._thetas is None: raise StateError('not trained yet') x_mat, y_row = _t.match_x_y(x_mat, y_row) if x_mat.shape[1] - 1 != self.layer_nodes[0]: raise DataNotMatchError( 'feature number and input layer node number mismatch') return self.__cost(self._thetas, x_mat, y_row)
def cost(self, x_mat: ndarray, y_row: ndarray) -> float: """ 计算在 x_mat 和 y_row 上的代价。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_row: 输出行向量,每一个值代表 x_mat 中对应行的输出 :return: 代价值。 """ if self._root is None: raise StateError('not trained yet') x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) if x_mat.shape[1] != len(self.pvs): raise DataNotMatchError('feature quantity mismatch') # TODO: 暂时先用错误率作为代价值,以后想想有什么更好的方法 return 1 - _ms.accuracy(self.__predict(self._root, x_mat), y_row)
def train(self, x_mat: ndarray, y_row: ndarray): """ :param x_mat: :param y_row: :return: """ x_mat, y_row = _t.match_x_y(x_mat, y_row, add_ones=False) y_row = _t.u2i_dtype(y_row) y_row = _t.convert_y(self.labels, y_row, to_labels=np.array([1, -1])) m, n = x_mat.shape if self.kernel == 'linear': k_mat = x_mat @ x_mat.T elif self.kernel == 'gauss': if not self.gamma: self.gamma = 1 / n k_mat = np.empty((m, m), dtype=np.float) for i, xi in enumerate(x_mat): for j in range(i, m): k_mat[i, j] = gaussian_kernel(xi, x_mat[j], self.gamma) k_mat[j, i] = k_mat[i, j] else: k_mat = np.empty((m, m), dtype=np.float) for i, xi in enumerate(x_mat): for j in range(i, m): k_mat[i, j] = self.kernel(xi, x_mat[j]) k_mat[j, i] = k_mat[i, j] self._alphas = np.zeros((m, ), dtype=np.float) self._b = 0 self._errors = self._calc_errors(k_mat, y_row) print('params initialized') iter_num = 0 iter_all = True changed_num = 0 while (iter_num < self.max_iter) and (changed_num > 0 or iter_all): changed_num = 0 # 在整个样本集和非边界样本集之间切换 if iter_all: for i1 in range(m): # 运行 smo 过程 changed_num = changed_num + self._smo(k_mat, y_row, i1) else: invalid_list = np.argwhere((self._alphas <= 0) | (self._alphas >= self.c)).ravel() if self._c else \ np.argwhere(self._alphas <= 0).ravel() for i1 in invalid_list: # 运行 smo 过程 changed_num = changed_num + self._smo(k_mat, y_row, i1) if iter_all: iter_all = False elif changed_num == 0: iter_all = True iter_num = iter_num + 1 print(iter_num, ',', changed_num, ':', self._alphas[self._alphas > 0]) # 只使用支持向量构造划分超平面法向量和其他参数 idx = self._alphas > 0 self._x_mat = x_mat[idx, :] self._y_row = y_row[idx] self._alphas = self._alphas[idx] self._theta = self._alphas * self._y_row @ self._x_mat