一直有人说,SVM这种东西在深度学习时代早就过时了,NLP里谁还用它?我写NLP系列学习笔记到第29篇,反而觉得SVM是绕不开的一块基石。尤其是做传统文本分类、小样本情感分析这类任务,SVM不仅没退场,在很多场景下依然是性价比极高的baseline。这篇笔记就集中梳理我对支持向量机的理解,从间隔最大化原理、核函数选择,到NLP文本特征怎么配合SVM落地,最后附上我常用的实现套路和踩坑记录,争取一篇把SVN在NLP里的用法讲透。
1. 为什么NLP始终绕不开SVM:先理解它解决的真正问题
1.1 从感知机到最大间隔:SVM的立足点
要理解SVM为什么在NLP里还有位置,得先退回到最基础的分类问题。给定一组文本样本,每个样本表示成一个向量,任务是找到一个超平面把它们分开。感知机时代,只要能分开就满意了,超平面稍微偏一点也无所谓。但感知机的致命问题是:存在无数个能正确划分训练集的超平面,算法最后拿到哪一个完全取决于初始化参数和样本顺序,泛化性能没有保障。
SVM换了个思路,不再满足于"找个能分开的平面",而是去找那个离两类样本都尽可能远的平面。这个"最远"就是最大间隔(maximum margin),间隔越大,对新样本的容错能力越强,泛化误差的上界越小。换句话说,SVM在求解一个带约束的优化问题,目标不是简单地分类正确,而是在正确分类的前提下,把分类的置信度拉到最高。
这个思路在NLP里的意义很直接:文本特征维度高、噪音多、类间重叠严重,一个贴着样本走的超平面很容易被几个异常词带偏,但最大间隔超平面天然就对局部扰动不那么敏感。
1.2 线性不可分怎么办:软间隔与核函数的双重解
现实中的文本数据几乎找不到完美线性可分的情况。两句不同情感的话可能只差一个否定词,两个类别的向量在空间里往往犬牙交错。SVM用两个机制解决:一是软间隔(soft margin),允许部分样本被分错,在目标函数里加入惩罚项,用惩罚系数C控制"分错容忍度"和"间隔最大化"之间的权衡;二是核函数(kernel trick),把样本从原始空间映射到更高维的空间,在高维空间里寻找线性超平面,而计算过程并不需要真的做高维映射,只需要计算样本之间的内积。
1.3 为什么NLP学者当年钟爱SVM:高维稀疏场景的天然适配
在深度学习还没普及的年代,文本分类领域公认最好的传统模型之一就是SVM。原因在于文本表示以词袋模型为主,特征维度动辄几万甚至几十万,但每个样本只有极少数的特征非零,呈现出极度稀疏的特性。SVM的优化目标只关心支持向量,也就是那些靠近决策边界的样本,大部分远离边界的样本对模型毫无影响,这让它天然适合稀疏高维数据。相比之下,逻辑回归需要所有样本都在目标函数里起作用,训练迭代代价更大;朴素贝叶斯则受特征独立性假设拖累,对特征之间的关联无能为力。
另一个关键点在于,SVM不需要调太多超参数,核心只有惩罚系数C和核函数参数。对那个年代的NLP研究者来说,这意味着在特征工程上投入的时间可以换来更稳定的模型表现,不需要像调神经网络那样反复折腾层数、学习率、dropout。
2. 核心机制拆解:从函数间隔到对偶问题
2.1 函数间隔与几何间隔:两个定义的不同意义
要搞清楚SVM的目标函数,第一个概念就得掰清楚:函数间隔和几何间隔。给定一个超平面w·x+b=0,样本点(x_i, y_i)的函数间隔定义为γ_i = y_i(w·x_i+b),其中y_i取+1或-1。函数间隔的绝对值表示样本到超平面的远近感觉,但有个问题:如果w和b同时乘以2,超平面本身没动,函数间隔却变成原来的两倍,这个量就不能直接用来衡量间隔大小。
几何间隔才是真正的空间距离,它等于函数间隔除以||w||,也就是样本到超平面的垂直距离。这里有个生活化的类比:函数间隔像是你用像素数来衡量屏幕上两个图标离得多远,但像素密度变了,这个数字就不可信了;几何间隔则像用厘米量物理距离,不管屏幕分辨率怎么变,距离不变。SVM要最大化的就是这个几何间隔。
2.2 从原始优化问题到拉格朗日对偶
把几何间隔最大化表达成优化问题,等价于在约束条件y_i(w·x_i+b) ≥ 1(对每个样本)下,最小化(1/2)||w||²。为什么约束条件右边取1而不是其他值?因为几何间隔由(w,b)的相对尺度决定,我们可以固定函数间隔的最小值为1,把尺度伸缩留给w的范数去消化,这样目标函数唯一确定。
求解这个约束优化问题,标准做法是构造拉格朗日函数,引入拉格朗日乘子α_i ≥ 0,把约束条件和目标函数合并:
L(w, b, α) = (1/2)||w||² - Σ α_i [y_i(w·x_i+b) - 1]
先对w和b求极小,再对α求极大,就能转化成对偶问题。对偶问题里有个对NLP特别友好的结果:w只由α_i > 0的那部分样本决定,这些样本就叫支持向量。绝大多数α_i等于0,意味着绝大多数训练样本对模型毫无贡献,模型只记住边界上的关键样本。
2.3 序列最小优化与文本规模下的实现现实
对偶问题是一个二次规划(QP)问题,理论上求解复杂度与样本量呈近似立方关系,直接用通用QP求解器处理几万条文本数据会很吃力。业界通用做法是使用序列最小优化(SMO)算法,每次只优化两个拉格朗日乘子,把大问题拆成无数个极其简单的小问题迭代求解。这也是LibSVM、sklearn中SVC的实现基础,实际训练速度比理论分析要乐观得多。
我在实测中发现,当文本用TF-IDF表示成稀疏矩阵后,即便样本数到几十万级别,使用带线性核的SVM(LinearSVC)在单机上完成训练也就几分钟到十几分钟的量级。这一点往往被很多人低估,以为SVM训练慢,其实稀疏高维数据和线性核这两者结合,快得远超预期。
3. 核函数在NLP文本分类中的选型逻辑
3.1 线性核:为什么它才是文本场景的默认选项
关于核函数,我见过太多人一上来就选RBF(径向基函数),理由是"RBF能处理非线性,"这句话在图像或数值型特征场景里没问题,但在NLP文本分类里是个常见的坑。
文本经过向量化之后,通常是上万维的稀疏向量。RBF核计算的是两个样本在原始空间的距离度量exp(-γ||x - x'||²),这个距离在稀疏高维空间里几乎没有判别力,因为任意两个文本向量的欧氏距离都被大量零维度的存在稀释,距离值普遍落在很窄的区间,区分度很低。更强的非线性映射能力还会带来严重的过拟合风险。
线性核SVM在高维空间里其实等价于对每个特征学一个权重,然后做加权求和,形式上很像一个不带隐层的线性模型。但因为它用最大间隔准则训练,和逻辑回归的决策边界往往非常接近,却通常更稳定、对异常点更不敏感。对大多数文本分类任务,线性核的表现在排行榜上并不比非线性核差,甚至更好,因为文本数据在高维空间里往往是近似线性可分的。
3.2 多项式核与RBF在文本里的适用范围
那非线性核在NLP里就完全没用吗?也不绝对。文本经过特征工程后如果做了强降维,比如用LSA/SVD压缩到100~200维的稠密向量,或者使用Word2Vec、BERT嵌入取平均得到几百维的稠密表示,这个时空域切换了,RBF核就有了用武之地,因为它捕捉的是样本在低维流形上的局部邻近结构。
多项式核在NLP里比较少见,但偶尔特征交叉本身就有语义含义的情况会用到,比如bigram特征里"not"和"good"同时出现,多项式核能用特征组合隐式建模这种"否定+正面词"的模式。只是在高维稀疏场景下,显式做特征交叉往往比隐式核计算更容易控制风险和解释,所以实践中我很少用多项式核。
3.3 核函数选择的决策路径
我给自己总结了一套选择路径,遇到文本分类问题时基本这么走:
- 原始特征是词袋/TF-IDF的高维稀疏向量,优先线性核,先跑通baseline;
- 如果特征维度已经降到几百维的稠密向量,再测试RBF核,观察是否比线性核有明显提升;
- 对比维度包括准确率、精确率、召回率、F1以及训练耗时,尤其注意少量类别的recall变化;
- 如果样本量只有几百条,线性核还是更稳,因为RBF在这个规模下很容易把支持向量占比推到很高,相当于把整个训练集背下来,泛化无从谈起。
4. 实操手记:我用SVM做文本分类的全过程
4.1 一整套代码实现:清洗、向量化、训练、评估
下面是我个人经常使用的文本分类流水线,以中文评论情感分类为例,包含完整步骤和关键参数。整个流程尽量做到拿来就能用。
import re import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import SVC, LinearSVC from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.metrics import classification_report, confusion_matrix import time # ---------- 1. 原始文本加载 ---------- def load_data(file_path): texts, labels = [], [] with open(file_path, 'r', encoding='utf-8') as f: for line in f: parts = line.strip().split('\t') if len(parts) == 2: texts.append(parts[0]) labels.append(int(parts[1])) return texts, labels # ---------- 2. 文本清洗与分词 ---------- def clean_text(text): # 去掉URL、数字、连续标点,仅保留中英文与基础标点 text = re.sub(r'https?://\S+|www\.\S+', '', text) text = re.sub(r'\d+', '', text) text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', text) return text def tokenize(text): # 使用jieba精确模式分词,用空格拼成分词后的文本 return ' '.join(jieba.lcut(clean_text(text))) # ---------- 3. 构建训练与验证集 ---------- texts, labels = load_data('comment_data.txt') X = [tokenize(t) for t in texts] X_train, X_test, y_train, y_test = train_test_split( X, labels, test_size=0.2, random_state=42, stratify=labels ) # ---------- 4. 使用Pipeline统一管理向量化与模型 ---------- pipeline = Pipeline([ ('tfidf', TfidfVectorizer( ngram_range=(1, 2), # 保留词和bigram,捕获否定结构 sublinear_tf=True, # 用1+log(tf)压缩词频差异 min_df=2, # 只保留至少在2个文档中出现的词 max_df=0.9, # 过滤掉90%以上文档都出现的词 norm='l2' )), ('clf', LinearSVC(max_iter=10000)) ]) # ---------- 5. 网格搜索C参数 ---------- param_grid = {'clf__C': [0.1, 0.5, 1.0, 2.0, 5.0]} grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='f1_macro', n_jobs=-1) start = time.time() grid.fit(X_train, y_train) print('best params:', grid.best_params_) print('train time: %.2fs' % (time.time() - start)) # ---------- 6. 评估 ---------- y_pred = grid.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))4.2 特征细节:ngram_range和sublinear_tf为什么值得调
我在调参的时候发现,ngram_range从(1,1)改成(1,2),在情感分类任务上F1往往能提升2到3个百分点。原因在于中文情感表达经常是"不怎么好""太差了"这种否定词加形容词的组合,单个词"好"是正面特征,"差"是负面特征,但bigram"不好"才是真正的负面表达。当然bigram也不是越多越好,(1,3)开始会显著增大特征维度,在很多真实数据集上收益递减,训练时间却明显变长。
sublinear_tf这个参数也经常被忽略。它把原始词频tf替换成1+log(tf),作用类似于对数值压缩。评论里"非常非常非常好看"这种表达,如果直接用原始词频,"非常"的TF值会异常突出,模型容易把极端词频当作强信号。用log压缩之后,词频差异变得平滑,模型的判别更多依赖文本结构而非某一个词的疯狂重复。我几乎在所有TF-IDF场景里都会开这个参数。
4.3 从记录里看一次真实调参过程
有一次我做某电商平台评论的二分类情感分析,训练集大约3万条,验证集7500条。第一次用默认参数(C=1.0,ngram_range=(1,1),sublinear_tf=False),macro F1是0.8701,看起来还行,但翻开混淆矩阵发现"中评"样本被大量错分到"好评",这其实是类不均衡造成的假象。
我把ngram_range调成(1,2),F1涨到0.8864;打开sublinear_tf,F1到0.8898;再把C从1.0扫到2.0,F1稳定在0.8920左右。三条改动累积提升了约2.2个百分点,没有动任何模型结构。这说明在SVM文本分类里,特征层面的微调往往比换模型收益更明显。
4.4 分类阈值调整与小类别性能改善
线性SVC有个特点:它能输出decision_function距离值,但没有直接的概率输出。距离值为正是预测类别,绝对值大小表示样本到超平面的距离。类别不平衡时,模型天然偏向多数类,可以通过调整阈值来平衡precision和recall。
对于一个二分类问题,如果希望提高少数类recall,可以把decision_function的判别阈值从0降到某个负值,例如-0.2,相当于给少数类更宽松的准入条件。具体做法是计算验证集上所有decision_function值,然后扫描不同阈值,找到F1或业务指标最优的阈值。这套方法比简单调class_weight更精细,尤其适合有明确业务优先级(比如风控场景更怕漏报)的情况。在sklearn里,可以通过clf.decision_function(X_val)获得距离值,然后自己遍历阈值。
真的需要概率时,可以改用SVC(kernel='linear', probability=True),代价是要额外做Platt缩放校准,训练速度明显变慢,而且校准后的概率有时并不可靠。大多数情况下,用好decision_function距离就够了。
5. SVM与主流深度学习模型的对比定位
5.1 在文本分类上,传统模型并没有“死”
深度学习和SVM在文本分类上的对比结论,在我做过的大量实验中都比较一致:在小规模数据集(几千到几万条标注样本)上,精心调过特征的LinearSVM往往比简单训练的LSTM或GRU更好;在大规模数据集上,深度学习确实能拉开差距,但这个优势依赖大规模预训练模型和充足算力。
这里面的原因也好理解。深度学习模型的容量大,要拟合数据背后复杂的语义结构,需要足够多的样本去稳定学习;而SVM的归纳偏置强,最大间隔原则在小数据上本身就是一种很强的正则化。很多NLP从业者觉得深度模型“肯定更强”,其实是在大规模标准数据集上的印象,放到自己手头只有几千条标注数据的具体业务里,结论可能完全不同。
5.2 工程视角下的对比:训练成本、部署与解释性
从工程落地角度,SVM的优势更明显。首先是训练成本:LinearSVC在几万条样本上训练以分钟计,微调BERT模型在同样数据上动不动几小时,还要GPU;其次是推理速度,SVM预测只是计算每个支持向量的内积和,CPU上轻松支撑每秒数千次请求,而深度模型需要完整跑一遍Transformer前向传播,延迟和吞吐量差距不小;再就是解释性,SVM的权重直接对应每个特征词,可以输出"差评主要因为服务、态度、物流"这类直观结论,深度学习模型很难给出同样直接的归因。
5.3 混合定位:深度学习时代里SVM依然是baseline之王
我的经验是做文本分类项目时,第一周先用TF-IDF加LinearSVC搭一个完整基线,跑通数据清洗、评估、上线全链路。之后不管要上BERT还是其他深度模型,这个SVM基线都作为参照系存在:深度模型提升如果不足几个点,那就要认真评估成本是否值得;如果能大幅超越,说明数据规模和复杂度确实需要深模型。这套做法帮我避免了很多次盲目上深度模型却换来微弱提升的尴尬。
在某些受限场景,例如数据量小的冷启动分类、嵌入后做低维特征的分类器、或者实时性要求极高的在线舆情监控,SVM的性价比几乎是无可替代的。它和深度学习不是替代关系,而是各占其位的关系。
6. 文本分类里SVM的易错点与排查经验
6.1 归一化问题:不归一化时为什么效果忽高忽低
SVM的目标函数依赖样本向量的范数,对特征的尺度非常敏感。文本向量化之后如果用L2范数归一化,每个样本向量长度为1;如果忘了归一化,不同长度文本的特征值大小差异会直接影响几何间隔的计算。我在一个项目里发现,不归一化时训练Loss能降到很低,但验证集F1一直上不去,后来检查TF-IDF的norm参数默认值是'l2',但如果数据预处理环节提前做过其他归一化,这个参数覆盖不及时就会出问题。
另外在多特征拼接场景里这点尤其致命。比如你把TF-IDF特征和文本长度特征直接concatenate,长度特征动辄几十到几百,而TF-IDF值普遍在零到一之间,SVM会把长度特征当作主导信号,效果自然崩。解决办法是对拼接后的特征整体再做一次标准化,或者分块归一化再合并。
6.2 样本不均衡:SVM为什么比逻辑回归更容易被带偏
SVM的损失函数只关注支持向量,当正负样本比例悬殊时,决策边界几乎只由多数类的支持向量决定,少数类的支持向量本来就很稀疏,更推不动边界。我处理过一个10:1不平衡的业务数据,直接用LinearSVC训练,少数类recall只有0.2,几乎等于没识别。后来做了三件事:一是设置class_weight='balanced',让少数类样本在损失函数里获得更高权重;二是对多数类做下采样或者对少数类做SMOTE过采样;三是用决策函数阈值调整来挑业务最优的临界点。三者叠加之后少数类recall才回到0.6以上。
这里要特别提醒:class_weight='balanced'虽然方便,但它本质上是把少数类的损失权重放大,可能导致整体准确率下降,所以一定要结合业务场景做权衡,并在验证集上分别看各类别的precision和recall,不能只看一个总体数字。
6.3 SVC与LinearSVC的选择陷阱与求解器差异
我在笔记里专门记录过一个低级的坑:sklearn里有两个SVM分类器,SVC和LinearSVC,它们的默认行为并不一致。LinearSVC默认使用hinge损失,可以理解为SVM目标函数,但它不是libsvm实现,而是用liblinear库做的坐标下降优化,支持多分类时默认用one-vs-rest策略。SVC默认用RBF核,底层是libsvm,支持多分类时用one-vs-one策略。
实际项目里如果直接调用SVC()而不指定kernel='linear',在高维文本特征上会训练极慢且效果不稳定;反过来想在多分类任务里获得概率输出,用LinearSVC就没有现成的probability参数。选哪个取决于对速度和功能的权衡,我一般分类任务用LinearSVC,需要概率或小规模数据上试非线性核时用SVC。
6.4 支持向量数量占比过高时,说明模型正在背样本
一个实用诊断技巧是训练完成后查看支持向量的数量。如果支持向量数量占训练样本的比例超过50%,通常说明模型在背训练集,决策边界被太多样本牵着走,泛化性能不会好。这时优先降低模型复杂度,要么减小C值(对错误容忍度更低),要么换线性核和更强正则化特征。反之,如果支持向量占比极低(比如1%),模型可能过拟合到极少数样本上,也要适当调大C值增加模型的拟合空间。
这个诊断方法对RBF核尤其有效,因为RBF核在样本量小时特别容易把所有训练样本都变成支持向量。我在一个只有800条样本的任务里见过支持向量占比高达95%的情况,换成线性核后降到30%左右,测试集F1反而提升了几个百分点,这类现象足以说明核函数选择在特定数据规模上有多关键。
7. 一个被低估的降维技巧:SVM配合特征筛选提升稳定性
7.1 卡方检验筛选特征:为什么比单纯调C更有效
SVM在高维稀疏特征下表现好,不代表维度越高越好。特征维度中包含大量低频噪音词时,它们虽然没有被直接用到决策里,但会影响特征向量之间的内积计算,让支持向量选择变得不稳定。我在实践中常先做一轮卡方检验特征选择,保留与类别标签相关性最高的K个特征,再交给SVM训练,效果往往好于直接调C。
卡方检验的原理是衡量每个特征词和类别标签之间的独立性,值越大表示词和类别的关联越强。具体实现用sklearn的SelectKBest(chi2, k=20000),在30万维特征上筛到2万维,通常能去掉大量噪音,同时训练时间下降明显。对于文本任务我一般尝试k在5000到50000之间,配合GridSearch一起调。
7.2 组合特征与维数灾难的平衡点
特征筛选和n-gram要配合使用,不是维度越多越好。我在一个测试集上对比过,ngram_range=(1,3)再筛特征到10万维,效果反而不如ngram_range=(1,2)筛到2万维,因为trigram特征极度稀疏,大部分只在训练集出现一两次,卡方检验容易高估这种低频词的判别力,导致特征选择过拟合。总结下来,(1,2)的n-gram配合2万到5万维的特征空间,在中文分类里是一个性价比很高的甜点区。
7.3 从支持向量和权重里提炼类别关键词
SVM训练完成后,线性核权重w的每一维直接对应一个特征词。权重的正负和大小,可以被用来提取各类别的关键词。我在做舆情分类项目时,训练完LinearSVC后按权重绝对值排序,输出了每个类别Top30的关键词,这些词直接给业务方做标签解释,效果比给他们看一堆预测结果要好得多。
更进阶的做法是计算每个词对某个类别的“判别得分”,比如w_i乘以该词的全局词频,得到一个综合得分。这样能同时体现词本身在语料中的出现频率和它对分类的贡献,避免某些极其罕见但权重很大的词占据关键词榜单。这个技巧在很多业务报告里非常有用。
8. 几种容易混淆的“SVM替代品”:到底该在何时用哪个
8.1 LinearSVC vs. LogisticRegression vs. SGDClassifier
文本分类场景里,LinearSVC、带L2正则的逻辑回归、以及SGDClassifier(配置成SVM损失)在实际表现上非常接近,但细节不同。逻辑回归天然输出概率,对分类置信度有自然表达;LinearSVC用hinge损失,决策边界更“硬”;SGDClassifier通过设置loss='hinge'等价于线性SVM,但它支持增量训练和不同优化器,在大规模流式数据场景更灵活。
我在具体选型时按这个规则:需要概率输出又不想承担SVC校准开销,优先逻辑回归;需要最大间隔的鲁棒性且只关心类别判断,优先LinearSVC;数据量在百万级别以上或者需要在线更新,优先SGDClassifier。三者都可以在Pipeline里直接替换,方便对比。
8.2 增量学习场景:文本数据持续更新的不二之选
很多NLP业务不是一次性训练定型的,比如舆情系统每天都有新数据进来,不可能每天全量重训。LinearSVC本身不支持增量训练,但SGDClassifier在线学习模式下可以持续更新模型,效果和LinearSVC非常接近。如果你现在用的还是LinearSVC,模型要面对不断到来的新数据,我建议尽早切换到SGDClassifier来保持实时性。
具体做法是设置SGDClassifier(loss='hinge', alpha=0.0001, max_iter=1000, tol=None),然后对新数据块分批调用partial_fit,类别列表需要预先知道。这套方案我跑了半年多,每天更新、效果稳定,训练开销几乎可以忽略不计。
8.3 多标签分类场景:SVM的One-vs-Rest实战
多标签文本分类,也就是一个样本同时属于多个类别,SVM同样能处理,核心是给每个标签单独训练一个二分类器。在sklearn里直接用OneVsRestClassifier(LinearSVC())包一层即可。这里容易踩的坑是多标签评估不能用accuracy,而应该用Hamming loss、micro/macro F1等专用指标。我在一个有12个标签、平均每样本3.5个标签的项目里,用OneVsRest+SVM收尾,macro F1达到0.75,对一个传统模型方案来说已经相当能打。
9. 走向实际部署:SVM模型的保存、上线与性能观察
9.1 模型体积与推理速度的量化对比
SVM模型的生产部署比深度模型轻量太多。一个2万维特征、约几千个支持向量、二分类的LinearSVC模型,序列化之后通常只有几百KB到几MB,加载耗时在毫秒级。推理时只需要做稀疏矩阵和权重向量的点积,单条样本耗时微秒级,一台普通8核CPU服务器就能扛住每秒几千QPS,换成BERT模型可能需要GPU集群才能达到同样吞吐。
我在一个日均调用量百万次的中文分诊系统里,用LinearSVC部署上线,单机压测稳定在3000 QPS以上,P99延迟不到5毫秒。这个表现对很多中小型业务来说完全够用,也是我在做技术选型时反复向团队强调的一点:不要一上来就奔着最大模型去。
9.2 使用joblib持久化与加载时的坑
sklearn自带的joblib序列化在保存SVM模型时非常方便,joblib.dump(model, 'svm_model.pkl')和joblib.load两行代码搞定。但这里有几个坑要记住:第一,保存的是整个Pipeline,包括TF-IDF向量器,否则上线预测时你没法把文本转成向量;第二,加载模型时sklearn版本最好与保存时一致,跨大版本加载有时会报错或行为变化;第三,TfidfVectorizer的词汇表会占据较大空间,但这是必须保存的一部分,不能删。
9.3 上线之后的模型监控:不仅仅是准确率
模型上线后,我最关注三个指标:各个类别的预测分布、decision_function的绝对值分布、以及新样本和训练集的特征分布偏移。预测分布如果明显偏移,说明线上数据分布和训练数据不一样了;决策距离绝对值整体变小,说明大量样本开始贴近决策边界,模型置信度在下降;特征分布偏移则提示需要增量更新的数据量阈值。
这些指标不需要复杂的监控系统,定时对线上样本抽样跑一遍预测,把decision_function和分类结果统计成图表就行。一旦发现异常,触发增量训练或者提醒人工介入,比等到用户投诉或者准确率报表掉下来再处理要主动得多。
写到这里,SVM从原理到NLP实战的基本盘已经覆盖得差不多了。回头再看,最大间隔思想、支持向量稀疏性、核函数选择这些概念,哪怕在以迁移学习为主流的今天也没有过时,它们其实是一套理解分类模型泛化能力的底层语言。对刚接触NLP的同学,把SVM吃透再去用深度学习,很多调参和过拟合问题会看得更清楚。对已经在生产环境里跑模型的人,这一套传统方法依然能稳定地托底,值得花时间整理进自己的工具箱。