SVM文本分类概率结果极差的技术求助
解决SVC文本年代分类概率区分度低的问题
问题描述
我是机器学习新手,正在使用sklearn的SVC进行文本年代分类项目,但得到的概率分数极低。我有13篇创作于598-1358年不同年代的文本(将年代作为类别),存储在训练和测试目录中,使用CountVectorizer和TfidfTransformer处理数据并通过pickle保存:
src = "../data/datasets/pickledWordLists/" corpus = [] for filename in os.listdir(src): with (open(os.path.join(src, filename), "rb")) as openfile: while True: try: text = pickle.load(openfile) text = ' '.join(word for word in text) corpus.append(text) except EOFError: break src = "../data/datasets/TestsPickled/" for filename in os.listdir(src): with (open(os.path.join(src, filename), "rb")) as openfile: while True: try: text = pickle.load(openfile) text = ' '.join(word for word in text) corpus.append(text) except EOFError: break vectorizer = CountVectorizer() vectorizer.fit(corpus) vector = vectorizer.transform(corpus) tfidf_transformer = TfidfTransformer() vector = tfidf_transformer.fit_transform(vector) with open('../data/datasets/labeledData/training/train_matrices/corpus.pickle', 'wb') as handle: pickle.dump(vector, handle)之后加载数据:
train_sparse = [] with open('../data/datasets/labeledData/training/train_matrices/corpus.pickle', 'rb') as handle: train_sparse = pickle.load(handle) train_set = np.array(train_sparse.toarray()[0:9]) test_set = np.array(train_sparse.toarray()[10:14]) labels = [1028, 1107, 1358, 598, 707, 875, 884, 890, 988]接着用训练数据和类别标签拟合SVC:
clf = SVC(kernel='linear', C=100, cache_size=300, class_weight='balanced', coef0=10.0, decision_function_shape='ovo', degree=10, gamma='auto', max_iter=-1, probability=True, random_state=None, shrinking=False, tol=1, verbose=False) clf.fit(train_set, labels)我用训练过的样本测试(实际类别884),预期该类别概率接近1.0,但得到错误分类且所有类别概率在10%-12%左右:
Actual Class: 884 Predicted Class: [988] Class: 1028 probability: 0.13680521863292022 % Class: 1107 probability: 0.1372151835630488 % Class: 1358 probability: 0.09314753496099398 % Class: 598 probability: 0.11216304253012621 % Class: 707 probability: 0.07705449472997644 % Class: 875 probability: 0.07702437742491991 % Class: 884 probability: 0.11694844959109225 % Class: 890 probability: 0.12739816653603753 % Class: 988 probability: 0.1222435320308847 %其他测试结果类似:
Actual Class: 890 Predicted Class: [890] Class: 1028 probability: 0.13682366473176108 % Class: 1107 probability: 0.1372180833047104 % Class: 1358 probability: 0.09312179238345174 % Class: 598 probability: 0.11286567433780788 % Class: 707 probability: 0.07636519076484871 % Class: 875 probability: 0.07712682059152805 % Class: 884 probability: 0.1169118710112273 % Class: 890 probability: 0.12735823672708854 % Class: 988 probability: 0.12220866614757639 %我需要提升概率分数的区分度,另一组英文语料测试也有相同问题,项目需要通过类别相似度分数粗略判定文本年代,请问该如何解决?
解决方案与思路
首先得明确:你遇到的核心问题是数据集规模极小(9个训练样本对应9个类别),再加上SVC的概率估计本身是通过近似方法(如Platt缩放)得到的,并非模型原生输出,这直接导致了概率分布趋于平均,区分度极低。下面是具体的调整方向:
1. 解决数据量不足的根本问题
这是最关键的一步,小样本下任何模型都很难学到有效的特征:
- 数据增强:针对古文本可以尝试:
- 同义词替换(利用古汉语/英语的专属同义词库,比如古汉语《同义词词林》或英文古旧词汇资源)
- 合理的文本重排(注意古文本语法结构固定,不要破坏语义)
- 补充公开的同年代古文本数据集,扩充样本量
- 合并相近年代类别:把跨度较小的年代合并为大类别(比如500-700、700-900、900-1100等),减少类别数量,让每个类别有足够的样本支撑模型学习。
2. 优化文本特征工程
文本特征的质量直接决定模型性能:
- 简化特征流程:用
TfidfVectorizer直接替代CountVectorizer+TfidfTransformer,效果一致且代码更简洁 - 添加n-gram特征:设置
ngram_range=(1,2),捕捉词语搭配关系,古文本的固定词组往往带有明显的年代特征 - 过滤噪音特征:在向量器中设置
min_df=2,去掉只在单个文本中出现的低频词,减少无关噪音 - 引入语言学特征:
- 统计虚词(古汉语“之乎者也”、英语古旧助词)的使用频率,不同年代虚词差异极大
- 字符级特征:比如平均词长、特定古文字符的出现频率,这些细节也能携带年代信息
3. 调整SVC模型与概率校准
SVC的概率估计需要针对性优化:
- 简化模型参数:你当前的参数过于冗余(比如线性核下
degree和coef0是无效的,tol=1容忍度过高),先简化为基础版本,再用网格搜索调优:clf = SVC(kernel='linear', C=1.0, class_weight='balanced', probability=True, random_state=42, tol=1e-3) - 概率校准:用
CalibratedClassifierCV校准SVC的概率输出,让其更接近真实置信度:from sklearn.calibration import CalibratedClassifierCV base_clf = SVC(kernel='linear', C=1.0, class_weight='balanced', random_state=42) calibrated_clf = CalibratedClassifierCV(base_clf, method='sigmoid', cv=3) calibrated_clf.fit(train_set, labels)
4. 换用更适合小样本的模型
如果SVC表现不佳,可以尝试其他更适配小样本文本任务的模型:
- 朴素贝叶斯(MultinomialNB):对文本数据友好,小样本下稳定,概率估计基于统计原理,天然更可靠
- K近邻(KNN):基于文本相似度直接判断类别,适合小样本场景,推荐用余弦相似度作为距离度量:
from sklearn.neighbors import KNeighborsClassifier knn_clf = KNeighborsClassifier(n_neighbors=3, metric='cosine') knn_clf.fit(train_set, labels) - 回归思路:把年代看作连续数值,转换为回归任务预测具体年份,用预测值与真实年份的偏差来衡量相似度,输出的连续值更容易区分差异
5. 调整评估方式
小样本下常规评估方式误差大,建议:
- 采用留一交叉验证(LeaveOneOut),更准确反映模型真实性能
- 除了准确率,计算平均绝对误差(MAE),衡量预测年代与真实年代的偏差,更符合你“粗略判定年代”的需求
内容的提问来源于stack exchange,提问作者Charlie Tizzard • Kevlahan
相关产品推荐
相关产品推荐

