训练中文文本分类 baseline:TF-IDF + 逻辑回归
前面我们已经讲了中文分词、停用词、词袋模型和 TF-IDF。现在终于可以把它们接起来,训练一个真正能跑的文本分类 baseline。
这里的 baseline 指的是"第一版可运行基线"。它不一定是最终最强模型,但必须简单、清楚、可复现,后面的优化都可以拿它做对照。
为什么选 TF-IDF + 逻辑回归
这个组合非常适合入门文本分类。TF-IDF 负责把文本变成数字特征,逻辑回归负责学习这些特征和标签之间的关系。
它有几个优点:
- 训练速度快;
- 参数不复杂;
- 效果通常不差;
- 比较容易解释;
- 适合当作后续模型的对照组。
如果一个复杂模型比这个 baseline 还差,就说明要先检查数据、标签、特征和评估方式,而不是继续堆模型。
很多人拿到文本分类任务会想直接上 BERT,但先跑通这条基线更稳妥。两者对比:
| 特性 | TF-IDF + 逻辑回归 | BERT 微调 |
|---|---|---|
| 训练时间 | 几秒 | 几十分钟到几小时 |
| 内存占用 | 几十 MB | 几 GB |
| 可解释性 | 看权重就知道哪个词重要 | 黑盒,需要 LIME/SHAP |
| 中文分词依赖 | jieba 即可 | 自带 tokenizer |
| 小数据效果 | 收敛快 | 需要足够数据 |
如果 TF-IDF + 逻辑回归已经达到 92% 准确率,BERT 可能提升到 95%,但是否值得花几十倍算力,要看具体场景。先把基线跑通,再决定要不要上更重的模型。
准备数据
假设我们有一个 DataFrame:
importpandasaspd df=pd.read_csv("text_classification.csv")print(df.head())至少需要两列:
text 原始中文文本 label 文本类别训练之前先做几个基本检查:
print(df.shape)print(df["label"].value_counts())print(df["text"].isna().sum())如果有空文本、重复文本、标签极度不平衡,要先处理,否则模型结果很容易失真。
分词函数
中文文本要先分词。
importjieba stopwords={"的","了","是","在","和","也"}defcut_text(text):words=jieba.lcut(str(text))words=[w.strip()forwinwordsifw.strip()]words=[wforwinwordsifwnotinstopwords]return" ".join(words)df["text_cut"]=df["text"].apply(cut_text)这里返回的是空格分隔的字符串,因为TfidfVectorizer默认按空格和规则切分 token。
划分训练集和测试集
接下来划分数据。
fromsklearn.model_selectionimporttrain_test_split X_train,X_test,y_train,y_test=train_test_split(df["text_cut"],df["label"],test_size=0.2,random_state=42,stratify=df["label"],)stratify=df["label"]的作用是尽量保持训练集和测试集里的类别比例一致。
分类任务里,这一点很重要。如果某个类别样本本来就少,随机划分可能让测试集里这个类别过少,评估就不稳定。
建立 Pipeline
推荐把 TF-IDF 和模型放进 Pipeline。
fromsklearn.pipelineimportPipelinefromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.linear_modelimportLogisticRegression pipe=Pipeline([("tfidf",TfidfVectorizer(max_features=5000,min_df=2,ngram_range=(1,2))),("model",LogisticRegression(max_iter=1000)),])这样做有两个好处。第一,流程更清楚,训练和预测都用同一条链路。第二,后面做交叉验证和调参时,不容易发生数据泄漏。
调参方向
基线跑通后,调参主要看两个地方。
向量化部分:
TfidfVectorizer(max_features=3000,# 保留的词汇量ngram_range=(1,2),# (1,2) 表示单字词加双字词组min_df=2,# 只保留出现在至少 2 篇文档中的词max_df=0.85,# 过滤出现在 85% 以上文档中的词sublinear_tf=True,# 用 1+log(TF) 抑制超高词频)逻辑回归部分:
LogisticRegression(C=1.0,# 正则化强度,越小正则越强max_iter=1000,# 迭代次数,特征多时要加大class_weight="balanced",# 类别不均衡时加这个)C是正则化强度的倒数,调小能抑制过拟合,调大则让模型更贴合训练数据。类别不平衡时,class_weight="balanced"会让少数类获得更高权重。
训练和预测
训练很简单:
pipe.fit(X_train,y_train)预测:
y_pred=pipe.predict(X_test)如果你想看每个类别的概率:
y_proba=pipe.predict_proba(X_test)不是所有模型都有predict_proba,但逻辑回归通常可以用。
看第一版分数
先用classification_report看整体表现。
fromsklearn.metricsimportclassification_reportprint(classification_report(y_test,y_pred))它会输出每个类别的 precision、recall、f1-score 和 support。
不要只盯着 accuracy。文本分类里,少数类的 recall 和 F1 经常更关键。
查看哪些词"说了算"
逻辑回归的好处是可解释。它的系数直接告诉你每个特征(词)对分类的影响,不需要额外的解释工具。
vec=pipe.named_steps["tfidf"]clf=pipe.named_steps["model"]words=vec.get_feature_names_out()# 找出对第一个类别预测贡献最大的 10 个词top_indices=clf.coef_[0].argsort()[-10:][::-1]foriintop_indices:print(f"{words[i]}:{clf.coef_[0][i]:.3f}")跑完会看到一组词和对应权重。权重越高,说明这个词越倾向把样本分到这个类别。如果权重高的词看起来和类别无关,往往说明数据或分词环节有问题。
保存 baseline
训练完成后,可以保存模型。
importjoblib joblib.dump(pipe,"text_classifier_baseline.joblib")以后加载:
model=joblib.load("text_classifier_baseline.joblib")model.predict(["功能 不好用 经常 闪退"])因为我们保存的是整个 Pipeline,所以 TF-IDF 词表和逻辑回归模型都会一起保存。
baseline 的价值
baseline 的价值不只是给出一个分数,而是建立一条标准流程。
后面你可以逐步对比:
- 换停用词表有没有提升;
- 加 bigram 有没有提升;
- 调
C参数有没有提升; - 换朴素贝叶斯、SVM、随机森林有没有提升;
- 增加数据后有没有提升。
每次只改一个主要变量,才能知道到底是什么带来了变化。
这一课先记住
TF-IDF + 逻辑回归是一条很适合文本分类入门的 baseline。
核心流程是:
清洗文本 -> 中文分词 -> 划分数据 -> TF-IDF -> 逻辑回归 -> 评估下一课我们不急着换模型,而是先学会读懂评估结果:混淆矩阵、分类报告和错分样本。
在线阅读
点击这里阅读博客原文
原文地址:https://bestsdz.xyz/posts/tfidf-logistic-regression-baseline/
