如何将手写二分类SVM改造为支持20+类别的多分类SVM?
改造二分类SVM为多分类SVM的方案与思路
现有一段从零实现的二分类SVM代码,仅能输出0或1的预测结果,无法处理MNIST这类多分类任务,代码如下:
import numpy as np class SVM: def __init__(self, learning_rate=1e-3, lambda_param=1e-2, n_iters=1000): self.lr = learning_rate self.lambda_param = lambda_param self.n_iters = n_iters self.w = None self.b = None def _init_weights_bias(self, X): n_features = X.shape[1] self.w = np.zeros(n_features) self.b = 0 def _get_cls_map(self, y): return np.where(y <= 0, -1, 1) def _satisfy_constraint(self, x, idx): linear_model = np.dot(x, self.w) + self.b return self.cls_map[idx] * linear_model >= 1 def _get_gradients(self, constrain, x, idx): if constrain: dw = self.lambda_param * self.w db = 0 return dw, db dw = self.lambda_param * self.w - np.dot(self.cls_map[idx], x) db = - self.cls_map[idx] return dw, db def _update_weights_bias(self, dw, db): self.w -= self.lr * dw self.b -= self.lr * db def fit(self, X, y): self._init_weights_bias(X) self.cls_map = self._get_cls_map(y) for _ in range(self.n_iters): for idx, x in enumerate(X): constrain = self._satisfy_constraint(x, idx) dw, db = self._get_gradients(constrain, x, idx) self._update_weights_bias(dw, db) def predict(self, X): estimate = np.dot(X, self.w) + self.b prediction = np.sign(estimate) return np.where(prediction == -1, 0, 1)
请问如何将其改造为支持20个以上类别的多分类SVM?能否提供相关思路提示?
核心思路:采用一对多(One-vs-Rest, OvR)策略
对于20+类的多分类场景,**一对多(OvR)*是效率更高的选择——它只需要训练N个二分类SVM(N为类别数),远少于一对一(OvO)策略需要的N(N-1)/2个模型,更适合类别数量较多的场景。
OvR的核心逻辑
- 训练阶段:为每个类别单独训练一个二分类SVM,将当前类别标记为正类(+1),所有其他类别标记为负类(-1)
- 预测阶段:对输入样本,用所有N个SVM计算线性得分(
np.dot(X, w) + b),选择得分最高的类别作为最终预测结果
具体改造步骤
- 参数存储改造:原代码只存一组w和b,现在需要为每个类别保存一组参数,用列表存储所有模型的(w, b)对
- 标签适配:不再是固定的0/1转-1/1,而是针对每个目标类别,动态生成二分类标签
- 训练逻辑扩展:遍历每个类别,生成对应二分类标签后调用原有的训练逻辑,保存每个类的模型参数
- 预测逻辑重构:对每个样本计算所有类别的得分,取最大值对应的类别作为输出
改造后的完整代码
import numpy as np class MultiClassSVM: def __init__(self, learning_rate=1e-3, lambda_param=1e-2, n_iters=1000): self.lr = learning_rate self.lambda_param = lambda_param self.n_iters = n_iters # 存储每个类对应的(w, b),列表元素为元组 self.models = [] # 记录所有类别 self.classes = None # 复用原二分类SVM的权重初始化逻辑 def _init_weights_bias(self, n_features): return np.zeros(n_features), 0 # 针对单个类别生成二分类标签映射 def _get_cls_map_for_class(self, y, target_cls): return np.where(y == target_cls, 1, -1) # 复用原二分类SVM的约束检查逻辑 def _satisfy_constraint(self, x, w, b, cls_label): linear_model = np.dot(x, w) + b return cls_label * linear_model >= 1 # 复用原二分类SVM的梯度计算逻辑 def _get_gradients(self, constrain, x, w, cls_label): if constrain: dw = self.lambda_param * w db = 0 return dw, db dw = self.lambda_param * w - np.dot(cls_label, x) db = - cls_label return dw, db # 复用原二分类SVM的权重更新逻辑 def _update_weights_bias(self, w, b, dw, db): w -= self.lr * dw b -= self.lr * db return w, b # 训练单个二分类SVM def _train_binary_svm(self, X, y): n_features = X.shape[1] w, b = self._init_weights_bias(n_features) cls_map = self._get_cls_map_for_class(y, self.current_cls) for _ in range(self.n_iters): for idx, x in enumerate(X): constrain = self._satisfy_constraint(x, w, b, cls_map[idx]) dw, db = self._get_gradients(constrain, x, w, cls_map[idx]) w, b = self._update_weights_bias(w, b, dw, db) return w, b def fit(self, X, y): # 获取所有唯一类别 self.classes = np.unique(y) # 为每个类别训练一个二分类SVM for cls in self.classes: self.current_cls = cls w, b = self._train_binary_svm(X, y) self.models.append((w, b)) def predict(self, X): # 存储每个样本在所有类别上的得分 all_scores = [] for w, b in self.models: scores = np.dot(X, w) + b all_scores.append(scores) # 转置后,每行对应一个样本的所有类别得分,取最大值索引对应类别 all_scores = np.array(all_scores).T pred_indices = np.argmax(all_scores, axis=1) return self.classes[pred_indices]
额外说明
- 如果追求更高精度,也可以尝试一对一(OvO)策略,但20类的话需要训练190个模型,训练和预测效率会低很多
- 原代码是基于随机梯度下降(SGD)的硬间隔SVM,实际应用中可以考虑加入软间隔的容错逻辑,或者改用批量梯度下降提升稳定性
内容的提问来源于stack exchange,提问作者Not Bedo
相关产品推荐
相关产品推荐

