支持向量机是一种做分类的模型。它要找的是一条把两类样本尽量分开的分界线,并且让这条线两侧留出的空白带尽量宽。落在空白带边缘上的那些点叫支持向量,分界线只由它们决定,离得更远的点不影响这条线。
分界线写成
w⋅x+b=0
w 是法向量,b 是截距。预测时看这个值的正负:大于 0 为一类,小于 0 为另一类。空白带的宽度是 2/∥w∥。所以训练时是在「分对所有点」的前提下,把 ∥w∥ 尽量变小。
用四个点就能把这件事看完。负类是 (1,1)、(1,2),正类是 (3,1)、(3,2)。左右两列之间最宽的空白在 x=2。对应的参数是 w=(1,0)、b=−2,判别式是
f(x,y)=x−2
代入检查:
- (1,1):1−2=−1,判为负类,并且刚好贴在空白带边上,是支持向量。
- (3,2):3−2=1,判为正类,也是支持向量。
- 再加一个更远的负类点 (0,1.5):0−2=−2。它在带的外面,删掉它,分界线仍然是 x=2。
两边空白的总宽度是 2/∥w∥=2,和 x=1到 x=3 的距离一致。
实际数据常常混在一起,硬要完全分开会把空白挤得很窄。软间隔允许少量点越过边界或进到空白里,用惩罚系数 C控制:C大更强调分对,C 小更强调空白要宽。
还有一类数据用直线根本分不开,比如内圈是一类、外圈是另一类,或者异或:(0,0)、(1,1)一类,(0,1)、(1,0) 另一类。核函数先把点变到更高维,在那边用平面分开,算的时候却只需要两点的核值,不用真的写出高维坐标。常用的有线性核、多项式核和高斯核(RBF)。高斯核能围出弯曲的边界。
def predict(x, y):
score = x - 2 # w=(1, 0), b=-2
return 1 if score > 0 else -1
print(predict(1, 1)) # -1
print(predict(3, 2)) # 1
print(predict(0, 1.5)) # -1,离边界更远,不是支持向量
它适合样本不算特别大、特征已经提取好、又希望边界清楚的分类问题。常见用途有:
- 文本分类:垃圾邮件、情感、主题。词袋特征又高又稀疏,线性支持向量机很合适。
- 图像里的传统检测:行人检测里,用 HOG 特征加上支持向量机,是深度学习普及前的常用做法。
- 生物信息:基因表达、蛋白质是否属于某一类,样本往往只有几十到几百个。
- 中小规模表格数据:欺诈、故障、合格/不合格。特征维数中等、类别大致可分时,效果稳定。
- 异常检测:单类支持向量机只用正常样本学一个边界,落在外面的视为异常。
例子:文本分类用支持向量机,关键是先把一篇文字变成高维稀疏向量,再用一条线性分界来判类别。我用几条短文本把这个向量和判别过程走一遍。
文本分类用支持向量机,是先把一篇文章变成一组数字,再找一条最宽的分界线来区分类别。垃圾邮件、评论正负面、工单类型,都是这条流程。
一篇文字怎么变成向量
模型不能直接读句子,只能读数字。常用做法是词袋:先定一张词表,每个词占一维,文章里出现了该词就记次数,没出现就记 0。
用四篇已经标好的短文本做例子。词表按这个顺序:优惠、中奖、点击、会议、项目。
| 文本 | 标签 | 向量 |
|---|---|---|
| 优惠 中奖 | 垃圾 +1 | (1, 1, 0, 0, 0) |
| 优惠 点击 | 垃圾 +1 | (1, 0, 1, 0, 0) |
| 会议 项目 | 正常 -1 | (0, 0, 0, 1, 1) |
| 明天 会议 | 正常 -1 | (0, 0, 0, 1, 0) |
「明天」不在词表里,这一维直接丢掉。一篇文章通常只有几个词是 1,其余全是 0,所以向量又长又稀疏。线性支持向量机正好适合这种数据:分界是加权求和,计算只碰到那些非 0 的词。
对应的判别式可以写成
f(x)=1⋅x优惠+1⋅x中奖+1⋅x点击−1⋅x会议−1⋅x项目
f>0 判垃圾,f<0 判正常。
- 「优惠 中奖」:1+1=2,垃圾。这两个词把分数顶到空白带边缘,是支持向量。
- 「会议 项目」:−1−1=−2,正常。
- 新来的「点击 优惠」:1+1=2,垃圾。训练时没见过这句组合,但两个词都在垃圾文本里出现过。
- 「优惠 会议」:1−1=0,落在分界上。业务上可以交给人工,或看哪边的代价更大再定。
真正训练时,这些权重不是手写的,而是由「分对样本,并且空白带尽量宽」算出来的。软间隔里的 C 用来容忍个别标错或很难分的句子。
实际还要做的几步
分词。 中文先切成词,再用词做特征。英文一般转小写、去掉标点。HTML 标签、链接、重复空格先清掉。
用 TF-IDF 代替裸次数。 「的」「了」几乎每篇都有,区分力弱,权重压低;「中奖」「发票」只在少数文章里出现,权重提高。公式是词频乘以 log(文章总数/包含该词的文章数)。连在一起的两个词也常作为特征,例如「免费 领取」,单看「免费」或「领取」都不如这一个组合像广告。
训练和预测用同一套词表。 训练时记下词表和每个词的 IDF。预测时按这张表把新文章变成向量,没见过的词忽略,再计算 f(x)。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
docs = "优惠 中奖", "优惠 点击", "会议 项目", "明天 会议"
y = 1, 1, -1, -1
clf = Pipeline([
("tfidf", TfidfVectorizer(token_pattern=r"(?u)\b\w+\b", ngram_range=(1, 2))),
("svm", LinearSVC(C=1.0)),
])
clf.fit(docs, y)
print(clf.predict("点击 优惠")) # 1
print(clf.decision_function("点击 优惠")) # 离分界越远,越有把握
LinearSVC 是线性核。文本维数已经很高,一般用线性核;高斯核又慢,也容易过拟合。数据量大时,可以用 SGDClassifier(loss="hinge"),它是同一个间隔目标的在线训练版本。
类别多于两个时,比如新闻要分成体育、财经、科技,就训练多条「这一类 vs 其余类」的分界,预测时取得分最高的那一类。
上线时怎么判断好不好
看混淆矩阵,不要只看准确率。垃圾邮件里正常邮件被拦(误报)通常比漏掉一封广告更严重,所以要单独看正常类的精确率和召回率。类别差很多时,给少的那一类加大 class_weight="balanced"。
词表用训练集拟合。测试文案里的新词不要提前写进词表,否则评估会偏高。CC 用验证集选:太大时会把个别句子记死,太小时两类又分不开。