def __check_params(self): pvs = self.pvs x_cv = self.x_cv y_cv = self.y_cv pruning = self.pruning if x_cv is not None and x_cv.shape[1] != len(pvs): raise DataNotMatchError('feature quantity mismatch') if (x_cv is not None and y_cv is None) or (x_cv is None and y_cv is not None): raise ValueError('x_val and y_val must all be none or all exist') if (x_cv is not None and y_cv is not None) and (x_cv.shape[0] != y_cv.shape[0]): raise DataNotMatchError('number of samples does not match') if (x_cv is not None and y_cv is not None) and pruning is None: raise ValueError('pruning must be "post" or "pre"')
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 __check_predict_actual_y(predict_y: ndarray, actual_y: ndarray, *, name: str = 'predict_y') \ -> Tuple[ndarray, ndarray]: predict_y = _t.c2r(predict_y) actual_y = _t.c2r(actual_y) if predict_y.shape[0] != actual_y.shape[0]: raise DataNotMatchError('number of %s and actual_y do not match' % name) return predict_y, actual_y
def match_x_y(x_mat: ndarray, y_vec: ndarray, add_ones: bool = True) -> Tuple[ndarray, ndarray]: """ 检验 x_mat 和 y_vec 是否一致,不一致抛出异常。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param y_vec: 输出向量,可以是列向量也可以是行向量,每一个值代表 x_mat 中对应行的输出 :param add_ones: 是否给 x_mat 增加截距列 :return: 检验完后的 x_mat;检验完后的 y_vec """ if add_ones: x_mat = addones(x_mat) else: x_mat = r2m(x_mat) y_vec = c2r(y_vec) if len(y_vec.shape) == 0: if x_mat.shape[0] != 1: raise DataNotMatchError('number of samples does not match') elif x_mat.shape[0] != y_vec.shape[0]: raise DataNotMatchError('number of samples does not match') return x_mat, y_vec
def predict(self, x_mat: ndarray) -> Union[ndarray, int]: """ 对新的数据进行预测。当 x_mat 中只有一个样本时返回一个数字,否则返回一个行向量。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: 预测值或向量 """ if self._root is None: raise StateError('not trained yet') x_mat = _t.r2m(x_mat) if x_mat.shape[1] != len(self.pvs): raise DataNotMatchError('feature quantity mismatch') return self.__predict(self._root, x_mat)
def __match_theta_x(self, x_mat: ndarray) -> ndarray: """ 检查输入是否和参数匹配。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: """ if self._theta is None: raise StateError('not trained yet') x_mat = _t.r2m(x_mat) if x_mat.shape[1] != self._theta.shape[0]: raise DataNotMatchError('feature quantity mismatch') return x_mat
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 probability(self, x_mat: ndarray) -> Union[ndarray, float]: """ 返回对应于 x_mat 的预测概率。如果是二分类问题,那么返回一个行向量;如果是多分类问题,返回一个 m*num_labels 的矩阵,其中每一行表示样本在每个类上的概率。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: 预测概率。 """ if self._thetas is None: raise StateError('not trained yet') x_mat = _t.addones(x_mat) if x_mat.shape[1] - 1 != self.layer_nodes[0]: raise DataNotMatchError( 'feature number and input layer node number mismatch') return _t.ret(_t.c2r(self.__feedforward(self._thetas, x_mat)[-1]))
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 match_theta_x(theta: ndarray, x_mat: ndarray) -> Tuple[ndarray, ndarray]: """ 检查 x_mat 特征数是否和 theta_vec 一致,不一致给 x_mat 加上一列截距列。还是不一致抛出异常。 注意此方法只能用在线性回归和逻辑回归中。 :param theta: 参数向量,可以是列向量、行向量或者矩阵 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: 检验完后的 x_mat;检验完后的 theta_vec """ x_mat = r2m(x_mat) theta = c2r(theta) m = x_mat.shape[0] if x_mat.shape[1] + 1 == theta.shape[0]: x_mat = np.hstack((np.ones((m, 1)), x_mat)) if x_mat.shape[1] != theta.shape[0]: raise DataNotMatchError('feature quantity mismatch') return theta, x_mat
def predict(self, x_mat: ndarray): """ 返回预测值,是对应于 x_mat 的标记。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: 预测标记 """ if self._theta is None: raise StateError('not trained yet') x_mat = _t.r2m(x_mat) if x_mat.shape[1] != self._theta.shape[0]: raise DataNotMatchError('feature quantity mismatch') if self.kernel == 'linear': pred = x_mat @ self._theta + self._b elif self.kernel == 'gauss': m = x_mat.shape[0] pred = np.empty((m, )) for i in range(m): for j in range(self._x_mat.shape[0]): pred[i] = pred[i] + self._alphas[ j] * self._y_row[j] * gaussian_kernel( x_mat[i], self._x_mat[j], self.gamma) pred[i] = pred[i] + self._b else: m = x_mat.shape[0] pred = np.empty((m, )) for i in range(m): for j in range(self._x_mat.shape[0]): pred[i] = pred[i] + self._alphas[j] * self._y_row[ j] * self.kernel(x_mat[i], self._x_mat[j]) pred[i] = pred[i] + self._b return _t.ret( _t.convert_y(self.labels, (pred >= 0).astype(dtype=np.int16), to=False))
def predict(self, x_mat: ndarray) -> Union[ndarray, int]: """ 返回预测值,是对应于 x_mat 的标记。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :return: 预测标记 """ if self._thetas is None: raise StateError('not trained yet') x_mat = _t.addones(x_mat) if x_mat.shape[1] - 1 != self.layer_nodes[0]: raise DataNotMatchError( 'feature number and input layer node number mismatch') a = self.__feedforward(self._thetas, x_mat)[-1] if len(self.labels) == 2: return _t.ret( _t.convert_y(self.labels, _t.c2r(a >= self.threshold), to=False)) else: return _t.ret(self.labels[np.argmax(a, axis=1)])
def feature_normalize(x_mat: ndarray, mean_row: ndarray = None, std_row: ndarray = None) \ -> Tuple[ndarray, ndarray, ndarray]: """ 将 x_mat 的特征规范化和归一化,也就是缩放以使得不同的参数之间差距不要太大。 如果参数 mean_col 和 std_col 都提供了的话,就使用它们进行规范;否则就计算新的 mean_col 和 std_col。 可以用在线性回归、SVM、PCA 中。 截距列不会被规范化。 :param x_mat: 特征向量组,行数 m 表示样本数,列数 n 表示特征数 :param mean_row: 每列特征值的平均值行向量 :param std_row: 每列特征值的标准行向量 :return: 规范化后的特征向量组,原来的特征向量组不会被影响;每个特征的平均值向量;每个特征的标准差向量 """ x_mat, has_ones = __t.delones(x_mat) if mean_row is not None: mean_row = __t.c2r(mean_row) if std_row is not None: std_row = __t.c2r(std_row) n = x_mat.shape[1] if mean_row is not None and std_row is not None and \ n != mean_row.shape[0] and mean_row.shape[0] != std_row.shape[0]: raise DataNotMatchError( 'x_mat\'s feature num does not match mean_row, std_row') # 转换数据类型防止计算精度丢失 x_norm = __t.i2f_dtype(x_mat.copy()) if mean_row is None and std_row is None: mean_row = np.mean(x_norm, axis=0) # ddof 设置为 1 表示计算样本方差,不设置表示计算总体方差 std_row = np.std(x_norm, ddof=1, axis=0) x_norm = (x_norm - mean_row) / std_row if has_ones: x_norm = np.hstack((np.ones((x_norm.shape[0], 1)), x_norm)) return x_norm, mean_row, std_row
def __check_params(self): if (len(self.labels) == 2 and self.layer_nodes[-1] != 1) or \ (len(self.labels) != self.layer_nodes[-1]): raise DataNotMatchError( 'number of labels does not match number of output layer nodes')