☰
文本数据分析实战:从分词、TF-IDF到情感分类建模
2026/10/17 5:11:37 网站建设 项目流程

文本数据分析(二)来了。上一篇我们聊了怎么把一份乱糟糟的文本清洗成能用的语料,这一篇直接把战线拉到建模侧:分词、向量化、训练、调参、踩坑,一条龙跑完。很多朋友在做文本数据分析时会卡在一个奇怪的位置——数据明明已经洗干净了,下一步却不知道从哪开始,或者说试了一圈模型后发现效果始终上不去。这篇文章就是来解决这个问题的,适合已经开始接触文本数据分析、准备做文本分类或情感判断的开发者,尤其适合那些手头有一批短文本(评论、标题、摘要)但还没形成成熟建模流程的人。

我会从技术选型的底层逻辑讲起,再逐步拆解特征向量化、模型训练和常见问题排查,所有代码都基于 Python 生态,尽量给出能直接改改就用的模板。

1. 文本数据分析的整体链路与第二篇的定位

1.1 为什么第一篇文章不够用

很多刚入门的朋友以为文本数据分析就是把一堆词频统计出来,画个词云就结束了。但从业务角度看,词频只能回答“出现了什么”,回答不了“这是什么情绪”“这条内容属于哪个类别”“两条内容是不是同一话题”。文本数据分析的真正价值,是从无序的字符序列里提取出可计算的信号,然后让模型替我们做判断。

所以第二篇的核心任务就清晰了:把文本从“能看”变成“能算”。具体来说分成三件事,一是把字符串切成有意义的词汇单位,二是把词汇单位映射成数值向量,三是用这些向量去训练一个能泛化的模型。这三件事每一件都有不少隐藏的坑,哪怕是经常做数据的人,一不小心也会在参数选择上翻车。

1.2 文本特征化的三条主流路线

文本数据必须数值化后才能喂给机器学习模型,目前主流路线有三条。

第一种是词袋模型和 TF-IDF。这种方案历史悠久,实现成本低,可解释性强,适合中小规模语料。它把文本变成稀疏向量,每个维度对应一个词,数值表示这个词在文档中的重要程度。第二种是词向量平均方案,比如用 Word2Vec 或 GloVe 预训练向量,把句子中所有词的向量取平均作为句子表示。这个方案比词袋更能捕获语义,但训练数据量不够大时收益有限,而且调起来比较麻烦。第三种是预训练语言模型,比如基于 Transformer 的各类模型,直接对整句编码,效果通常最好,但需要较大的算力和显存,部署成本也高。

本篇我选择 TF-IDF 作为主力讲解方案,不是因为词向量和预训练模型不好,而是因为大多数中小业务场景下,TF-IDF 加一个合适的分类器,已经能在半小时内跑出一个战斗力不错的基线模型,后续再根据效果决定要不要升级到更复杂的方案。这种“先基线,再升级”的思路,在真实项目里能省下大量时间。

1.3 本次实战的整体技术栈

接下来的案例会围绕一批短评论数据展开,任务是做一个二分类情感判断。我手头的模拟数据大概是几千条带标签的文本,用到的工具链如下:

  • Python 3.8+
  • pandas:处理表格型数据
  • jieba:中文分词
  • scikit-learn:特征向量化和模型训练
  • imbalanced-learn:处理标签不平衡问题

代码示例里我不会用太花哨的东西,所有功能都基于稳定版本,方便你在自己机器上直接复现。需要说明的是,这里的数据是我自己构造的示例数据,字段只有两列:text和label,label 取值为 0 或 1,表示负面和正面。你可以把同样结构的数据替换成自己的真实数据。

2. 从文本到特征向量:分词、停用词与向量化的坑

2.1 分词工具选型:不要执着于“最智能”

中文分词是文本数据分析绕不开的第一步,句子的基本语义单元是词而不是字。目前常用的工具有 jieba、HanLP、LTP 等,各有侧重。我的个人经验是:如果是快速做实验,直接用 jieba 就够了;如果精度要求高且愿意花时间做领域词表,HanLP 和 LTP 会更稳。

但比选哪个工具更重要的是:必须保证训练和预测时使用完全相同的分词逻辑。很多朋友在训练时用 jieba 默认词典,上线时又往词典里加了一堆领域词,导致线上结果和离线评测对不上,找半天发现是分词漂移了。这个问题在工程里非常典型,解决办法是把分词函数封装成统一的预处理模块,训练、验证、预测都调用同一个入口。

另外,短文本场景下不要过度依赖词典,因为用户评论里满是网络用语、错别字和表情符号。我的做法是先在预处理阶段做一层简单的字符串替换,比如把“不要”拆成“不 要”,把“哈哈哈哈”归一到“哈哈”,这比单纯调分词器参数更直接有效。

2.2 停用词表的正确打开方式

网上随便下载的通用停用词表可以用,但不能盲用。通用表中常见的“的、了、是”这些词确实该删,但很多看似“虚词”的词在情感分析里反而有价值。比如“不”“太”“挺”“比较”这类程度词和否定词,如果直接塞进停用词表,情感特征会被明显削弱。

我见过一个真实案例,项目助教做情感分类时把“不”加进了停用词表,模型准确率下降了好几个点,因为“不好”“不错”这两个关键表达被切成了同一个“好”。所以正确做法是:先建一个基础停用词表,然后在自己的验证集上跑一遍模型,再看最重要的特征词,把那些既没有区分度又影响理解的词依次剔除。这个过程听起来慢,其实只花十几分钟。

一个比较稳妥的停用词表组成方式是:通用高频虚词 + 标点符号 + 无意义语气词 + 与业务无关的弱信号词。注意不要包含否定词、程度副词和业务关键词。

2.3 TF-IDF 向量化:四个参数决定成败

向量化阶段最常用的是 scikit-learn 里的TfidfVectorizer,但默认参数并不是为中文短文本准备的,理解下面四个参数能帮你少走很多弯路。

第一是token_pattern,这个分词模式在默认情况下只匹配字母和数字,直接用于中文等于把整句话截成互不连贯的片段,所以中文场景下必须先自己分词,再把词之间用空格隔开,或者设置自定义分析器。

第二是ngram_range,默认是(1, 1),也就是只看单个词。对评论场景来说,关键词组往往比单词更有意义,比如“太差”“非常优秀”,把这些相邻词组合起来能显著提高模型区分度。我常用(1, 2),数据量充足时可以试(1, 3)。

第三是min_df和max_df,这两个值控制哪些词进入特征集合。min_df=2表示至少在2条文本中出现过才保留,能过滤掉大量低频噪声;max_df=0.8表示在超过80%文本中都出现的词会被剔除,因为这些词往往没有区分度。

第四是sublinear_tf,它把词频映射到对数空间,可以缓解长文本里高频词对特征的压制。对长度差异很大的文本,这个参数能让结果更稳定,我一般直接设成True。

2.4 特征工程完整代码示例

下面这段代码展示了从原始文本到 TF-IDF 特征矩阵的完整过程。注意我把分词和停用词过滤都封装在了同一个函数里,这样保证后面模型训练和预测时特征空间一致。

import jieba import re from sklearn.feature_extraction.text import TfidfVectorizer STOP_WORDS = set() with open('stopwords.txt', 'r', encoding='utf-8') as f: for line in f: STOP_WORDS.add(line.strip()) def clean_text(text: str) -> str: text = re.sub(r'https?://\S+', '', text) text = re.sub(r'@\w+', '', text) text = re.sub(r'[\u4e00-\u9fa5A-Za-z0-9]', ' ', text) words = jieba.lcut(text) words = [w for w in words if w.strip() and w not in STOP_WORDS and len(w) > 1] return ' '.join(words) vectorizer = TfidfVectorizer( ngram_range=(1, 2), min_df=2, max_df=0.8, sublinear_tf=True, norm='l2', use_idf=True ) X_train = vectorizer.fit_transform(train_data['text'].apply(clean_text))

这段代码里有一个细节:re.sub(r'[\u4e00-\u9fa5A-Za-z0-9]', ' ', text)看起来有点反直觉,它的作用是把中英文和数字以外的字符(比如表情符号和特殊符号)替换成空格。你可能会问为什么不直接删除,原因是评论里偶尔会有“笑哭”“666”这样的混合表达,保留空格比直接删掉更安全。

这里再提醒一个典型错误:不要在向量化之前先把标签过滤掉,也不要把全量数据直接fit。特征应该在训练集上拟合,再用训练好的向量器去转换验证集和测试集,否则会发生信息泄露,导致验证结果虚高。我在后面的模型部分还会再强调这个问题。

3. 模型选择与调参:从朴素贝叶斯到融合模型

3.1 短文本场景为什么首选线性模型

拿到 TF-IDF 特征后,下一步就是选模型。很多新手一上来就上随机森林或者 XGBoost,结果在小样本短文本上效果并不理想,原因在于 TF-IDF 产出的特征是典型的高维稀疏向量,维度可能有几万,但每个样本里只有几十个非零值。对于这种数据,线性模型通常比树模型表现更好,因为线性模型能利用所有特征的权重信号,而树模型很难在这种稀疏空间里找到稳定的划分点。

我常用的基线模型是逻辑回归、朴素贝叶斯和线性支持向量机。逻辑回归天然输出概率,方便后续做阈值调整;朴素贝叶斯在短文本分类中往往出奇地稳健;线性支持向量机在小样本场景下经常拿到最高准确率。三者训练都很快,也很容易解释。

3.2 训练集和验证集的划分不能随便切

这里的“不能随便切”有两个层面。第一是必须做分层采样,也就是保证训练集和验证集中正负样本的比例与原始数据大致相同。直接train_test_split不指定stratify时,如果原始数据正负比例是 8:2,划分后很可能变成 9:1,模型会偏向多数类,产生的指标也不稳定。第二是划分必须发生在向量化之前还是之后?

我见过很多人先对整个数据集做 TF-IDFfit_transform,再切训练测试集,这会造成特征统计信息混入测试集。正确流程是:

from sklearn.model_selection import train_test_split X_raw_train, X_raw_val, y_train, y_val = train_test_split( train_data['text'], train_data['label'], test_size=0.2, stratify=train_data['label'], random_state=42 ) X_train = vectorizer.fit_transform(X_raw_train.apply(clean_text)) X_val = vectorizer.transform(X_raw_val.apply(clean_text))

注意这里vectorizer在训练集上fit_transform,在验证集上只做transform。这个顺序一旦颠倒,你得到的验证集效果就不是真实泛化能力,后面的调参全都是在自欺欺人。

3.3 调参经验:给新手的参考范围

基线模型跑通之后,调参顺序也有讲究。我会先调向量化参数,再调模型参数,因为模型再优秀,特征没有表达力也是白搭。

以TfidfVectorizer为例,优先尝试的是ngram_range和max_features。max_features限制特征数量,一般设在 5000 到 50000 之间。短文本场景中,特征数到两万以后收益递减。ngram_range可以先从(1, 2)试起,如果效果没涨,不要贸然上(1, 3),因为组合词会让特征空间爆炸式增长,还会引入大量低频过拟合特征。

模型侧,逻辑回归的C值可以搜0.1,1,10这几档;线性支持向量机同样调C,类别不平衡时优先打开class_weight='balanced';朴素贝叶斯基本没有太多参数,主要靠平滑参数alpha,默认为 1,但有些场景设成 0.1 反而更好。

我用一个简单实验对比了三种模型在模拟数据上的表现,参考结果如下:

模型准确率F1值训练时间
MultinomialNB0.8420.8380.4s
LogisticRegression0.8710.8661.8s
LinearSVC0.8820.8792.1s

从这个表可以看出来,线性支持向量机在这个场景上效果最好,但它的差距并没有到碾压的程度。真实业务里我一般会同时保留逻辑回归和线性支持向量机两个模型,一个用来解释原因,一个用来做预测。

3.4 多模型对比和结果解读

做对比实验时,不能只看准确率一个指标。对不平衡数据,准确率很可能失真,比如只有 10% 的负面样本,模型全部预测为正面,准确率也有 90%。所以分类任务里,我至少要同时看准确率、召回率、F1 值和混淆矩阵。F1 值是精确率和召回率的调和平均,适合在样本类别不均衡时评估综合能力。

此外还要留意训练集和验证集之间的分数差距。如果一个模型在训练集上准确率是 98%,验证集上只有 83%,说明模型过拟合了,这时候优先考虑减小max_features,增加min_df,或者把模型正则化参数调大,而不是盲目堆更多特征。

4. 实际项目中的高频问题与解法

4.1 标签不平衡:别让多数类吃掉少数类

所有真实文本数据集几乎都面临标签不平衡的问题,尤其是做负面情感分类、投诉识别这类任务,负样本可能只占极少数。解决不平衡的思路通常有三类:数据层面采样、算法层面加权、指标层面调整。

数据层面最简单的是用imbalanced-learn库里的RandomUnderSampler做欠采样,把多数类样本删到和少数类接近;或者用SMOTE做过采样,生成一部分合成样本。但 SMOTE 在 TF-IDF 稀疏特征上容易生成不存在的组合,我用它时默认只在特征维度不多的小数据集上尝试。算法层面最简单有效的是在逻辑回归和线性支持向量机里设置class_weight='balanced',框架会自动调整每个类别的权重,实测效果通常立竿见影。

指标层面同样重要。在正负样本比例严重失衡的时候,我把评价指标调整为 F1 分数或 AUC,并在调参时使用交叉验证,这样模型不会只顾着把多数类猜对。

4.2 文本长度差异大:先看看是不是长度在误导模型

短文本数据集中经常混着一些特别长的文本,比如一条评论里粘贴了一大段日志。如果不做处理,TF-IDF 归一化后,长文本中每个词的权重会被压得很低,导致模型倾向于把短文本判断为更强烈的类别。这里有一个很实用的手段:在把文本送入向量化之前,先对长度做分位数统计,用text.str.len()画一个直方图,看数据分布。如果尾部异常长,可以直接做截断。截断时不要把超过长度的文本直接丢弃,而是取前 N 个字符或前 N 个词,N 根据 90% 分位数来定。

还有一种不是办法的办法:把文本长度本身作为一个附加特征拼进特征矩阵,让模型自己去学长度和标签之间的关系。这个方法在评论数据里有时能带来一两个点的提升,代价是特征解释性变差,我一般在规则诊断时才会用。

4.3 模型跑完但效果差:先排查数据还是参数

遇到模型效果差,第一反应不该是马上改参数,而是按下面的顺序逐一排查。首先是数据泄漏,检查向量器是否在验证集上重新fit了,检查文本清洗函数里有没有用到标签信息,检查是否有重复样本在训练集和验证集里同时出现。其次是标签噪声,随便抽查几十条数据,看看是不是标签打错了,这个问题比想象中常见得多。再次是特征工程,是不是把大量无关词保留下来了,是不是停用词表误删了关键否定词。最后才是模型选型,很多时候问题在特征或者数据,调模型参数没有用。

我自己的经验是:遇到效果差,第一件事一定是打开几个中间产物,比如随机抽 500 条训练文本,打印出它们的词序列,再打印对应 TF-IDF 矩阵里权重最高的几个词,看看是否符合业务认知。如果这些词和人眼判断完全对不上,问题大概率在预处理,而不是模型。

4.4 快速上手的检查清单

  • 分词和停用词过滤是否封装成了统一函数?训练、验证、预测是否用了同一套逻辑?
  • 是否在训练集上完成向量器fit,在验证集和测试集上只使用transform?
  • 标签划分是否用了stratify?
  • 评价指标是否至少包含准确率、F1 和混淆矩阵?
  • 类别不平衡时是否设置class_weight或使用过采样/欠采样方法?
  • 文本长度是否做过异常值分析?

这套清单我在每个文本数据分析项目里都会过一遍,省下来的全是返工时间。

对我来说,文本数据分析里真正值钱的不是哪一次实验跑出了高分,而是一套稳定可复制的流程。很多时候你会发现,新拿到的项目只要按流程先做一个简单到极致的基线,很多问题在半小时内就暴露出来了,根本不需要一上来就上大规模模型。这也是我做文本数据分析(二)最想传达的东西:别让花哨的工具掩盖了基本功。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询