首页 / 资讯中心 / 文章详情

NLP实战:中文新闻文本分类从分词到模型评估完整流程

NLP实战:中文新闻文本分类从分词到模型评估完整流程 ★ FEATURED ARTICLE
做NLP绕不开文本分类。最近我把以前带新人时踩过的坑重新整理了一遍发现最容易讲明白的切入点就是中文新闻文本分类数据好找、类别清楚、任务直观而且能把分词、向量化、建模、评估这一整条链路一次性走通。这篇是NLP实战系列的第一篇先不整花活从简单做起把原理和代码逐行过一遍适合刚学完Python基础、想动手做第一个完整项目的自然语言处理入门读者。既然是“实战”我默认你已经能跑Python脚本、装第三方库也大概知道机器学习里的训练集和测试集是什么意思。如果这些还没完全掌握也没关系后面每一步都会讲为什么要这么做代码怎么读跑出来的结果怎么判断好坏。我的目标很简单你跟着这篇文章走完一遍能独立跑通中文新闻分类并且能对结果做基本分析不是稀里糊涂复制完代码就完事。1. 项目整体设计与思路拆解1.1 为什么第一个实战选新闻分类文本分类是自然语言处理最经典的落地任务之一从垃圾邮件过滤、评论情感分析到工单自动分派本质上都是同一套东西给定一段文本让机器判断它属于哪个预定义的类别。中文新闻文本分类则是其中特别适合入门的一种。原因有三个。第一新闻语料容易获取主流开源数据集里都有整理好的样本每篇新闻都带了明确的分类标签省去了手动标注的成本。第二新闻类别通常比较规整比如财经、体育、娱乐、科技、健康这些大类彼此区分度较高不需要模型有太强的推理能力就能取得不错的效果新手能很快获得正反馈。第三新闻文本篇幅中等比动辄几百字的长文档简单又比几十字的短文本信息丰富用来练习分词、特征提取、模型训练恰好合适。这个项目能解决的问题也很具体你输入一篇没见过的新闻正文系统输出它最可能属于哪个类别。这个能力推而广之就是一切自动内容归档、信息流推荐、舆情分析的基础模块。1.2 技术路线怎么选先别急着上深度学习新手做中文文本分类很容易一上来就想用BERT、GPT这类预训练模型。我见过不少新人直接调了一个大模型跑测试准确率确实高但问他一两句为什么参数要这么设、模型在算什么就说不清楚了。这个思路对学习来说是有问题的直接跳到最复杂方案你会漏掉最基础也最核心的文本表示方法。这篇文章走的路线是“传统机器学习统计特征”先用分词工具把新闻切成词再用TF-IDF把词转成向量最后用朴素贝叶斯、逻辑回归这类轻量级模型做分类。这条路线的优势非常明显计算量小普通笔记本几十秒就能跑完训练每个环节都可解释出了问题能定位到是分词、特征还是模型本身的问题代码量少核心逻辑容易看懂数据量需求低几千条样本就能得到一个有参考价值的模型。深度学习不是不用而是应该放在你理解了文本分类的基本逻辑、能准确解读模型输出之后再去尝试。做项目最忌讳一步到位先把baseline跑通再逐步升级这才是工程实践里比较稳妥的节奏。2. 环境准备与数据预处理2.1 依赖安装与版本选择开始之前先把环境准备好。我建议用Python 3.8到3.11之间的版本太新的Python版本有时候会遇到个别库的二进制包没跟上处理起来反而麻烦。核心依赖只有四类安装命令一条就够pip install jieba scikit-learn pandas逐个说明用途jieba中文分词工具负责把连续的中文句子切成一个个有意义的词。它是目前社区使用最广泛的中文分词库之一开箱即用。scikit-learn机器学习库提供TF-IDF向量化、模型训练、评估指标等全套功能。我们不需要自己写算法细节但需要知道怎么正确调用。pandas数据处理库用来读文件、看统计数据、切分数据。安装完成后可以用一段很短的代码确认环境是否正常import jieba import pandas as pd import sklearn print(jieba, jieba.__version__) print(pandas, pd.__version__) print(sklearn, sklearn.__version__)能打印出版本号说明环境基本没问题。接下来准备数据。2.2 数据长什么样怎么读进来我用的是一份开源的中文新闻分类数据集文件格式是CSV每行包含两列一列是正文内容一列是分类标签。这是最常见的语料组织方式有的数据集是每类一个文件夹有的是JSON格式但最后都要整理成这种两列结构才能喂给模型。假设数据文件叫news.csv第一行是列名content, category用pandas读取import pandas as pd df pd.read_csv(news.csv, encodingutf-8) print(df.shape) print(df.head())这里最常踩的坑是编码问题。很多中文数据集保存成CSV时用的是utf-8但也有用gbk的老数据。如果读取时报UnicodeDecodeError就把编码改成encodinggbk试试。更稳妥的做法是用errorsignore兜底把无法解码的字符忽略掉但我不推荐一开始就这么干因为那会掩盖数据本身的问题。读取之后先看看标签分布print(df[category].value_counts())正常情况下你会看到每个类别样本数量差不多。如果发现某个类别样本特别少、另一个特别多这就是类别不平衡问题会在后面专门讲处理思路。对于入门项目可以先选样本量相对均衡的类别来做这样模型评估的指标会更好看也更容易判断模型本身有没有问题。2.3 数据清洗但别洗过头新手容易在数据清洗上花太多时间其实新闻文本相对干净真正需要处理的只有三类问题第一是空值和缺失值。有些数据文件在采集或转换过程中会产生空白行直接删掉就可以了df df.dropna(subset[content, category]) df df[df[content].str.strip() ! ]第二是文本里的换行符和多余空格。新闻正文里经常有多余换行分词时会影响效果统一替换成空格import re def clean_text(text): text re.sub(r\s, , text) # 连续空白符合并为单个空格 text text.strip() return text df[content] df[content].apply(clean_text)第三是过短的文本。如果一个样本去掉空格之后只剩几个字通常不包含足够分类信息直接过滤掉df df[df[content].str.len() 10]数据清洗的核心原则是“宁可少洗不要乱洗”。不要把文本里的数字、英文字母全删了因为新闻里可能出现“5G”、“AI”这类对分类有帮助的关键词。这个阶段的主要任务是让数据变得规整而不是把语言信息都剥掉。3. 分词、去停用词与特征工程3.1 jieba分词怎么用才合适中文文本不像英文那样天然用空格隔开词所以分词是绕不开的第一步。jieba提供了三种分词模式我实际使用下来精确模式是绝大多数情况下的默认选择import jieba text 这家企业的智能音箱产品在海外市场销量增长迅速 words jieba.lcut(text) # 精确模式返回列表 print(words)输出大概是[这家, 企业, 的, 智能, 音箱, 产品, 在, 海外市场, 销量, 增长, 迅速]。可以看到任务相关的主要词都被切出来了。全模式会把所有可能的词语组合都切出来比如“智能音箱”会同时切成“智能”、“音箱”、“能音”等结果里会有大量冗余不适合做分类特征。搜索引擎模式则是在全模式基础上保留较长的组合适合搜索引擎场景也不适合这里。分词之后我们要对每一篇新闻做同样的操作然后把词用空格拼接成字符串因为后续的TF-IDF向量化工具期望输入已经是空格分隔的词序列。def cut_to_str(text): words jieba.lcut(text) return .join(words) df[content_cut] df[content].apply(cut_to_str) print(df[content_cut].head())这里有个性能小技巧jieba第一次加载会初始化词典之后分词会变快。如果新闻数据有几万条逐条apply可能要等一会儿。你可以在分词前先jieba.initialize()预热也可以在读取数据后对文本顺序无所谓直接跑就行。3.2 TF-IDF的原理和关键参数分词完成后文本是词的序列但模型不能直接吃字符串必须转成数字向量。最直观的方式是“词袋模型”统计每篇文档里每个词出现了多少次得到一个高维稀疏向量。但纯词频有两个明显问题像“的”“是”“在”这类停用词出现频率极高却几乎没有类别区分能力有些通用词在各类新闻里都频繁出现对分类没什么贡献。TF-IDF就是在这个思路上做的改进。它的核心想法是一个词在一篇文章里出现次数多说明这个词对这篇文章重要但如果它在很多文章里都出现说明它常见重要性会被压低。具体公式分两部分。TF词频是词在文档中出现的次数通常用原始次数或归一化次数。IDF逆文档频率是IDF log(N / (1 df))其中N是语料中的文档总数df是包含该词的文档数量。词越常见df越大IDF越小。曾经的IDF计算公式是log(N / df)但为了避免分母为零sklearn里默认加了平滑项1 df最后TF-IDF就是把TF乘以IDF。这个原理必须理解透因为它直接关系到你怎么调参。sklearn里的TfidfVectorizer封装好了所有逻辑但有几个参数是入门必调的from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features10000, # 只保留语料中最重要的1万个词 ngram_range(1, 2), # 既保留单词也保留相邻词组合 min_df2 # 至少要在2篇文档中出现才保留 )max_features10000是为了控制特征维度。原数据可能有几十万个不同的词但很多词只出现一两次对分类没有帮助还会让训练变得很慢。取1万是比较保守的做法既留住了主要信息又不会让稀疏度太高。ngram_range(1, 2)可以让你保留“智能”这种单词也能保留“智能音箱”这种组合词。新闻文本里很多关键概念是复合词加进去对准确率通常有提升代价是特征维度会增加。min_df2则是把那些只在某一篇文档里出现一次的生僻词过滤掉。这些词基本是噪声留着只会让模型过拟合到个别样本上。3.3 把清洗到向量化串成一条流水线现在可以把前面所有步骤整合在一起。这一步我建议你写成一个个函数而不是一堆散落的代码方便后面反复调用。import re import jieba from sklearn.feature_extraction.text import TfidfVectorizer def clean_text(text): text re.sub(r\s, , text) return text.strip() def cut_to_str(text): words jieba.lcut(text) return .join(words) def build_pipeline(df): df df.copy() df[content] df[content].apply(clean_text) df[content_cut] df[content].apply(cut_to_str) vectorizer TfidfVectorizer( max_features10000, ngram_range(1, 2), min_df2 ) X vectorizer.fit_transform(df[content_cut]) y df[category].values return X, y, vectorizerfit_transform做的事情是学习语料中所有词的IDF值并完成词频统计和IDF加权最终返回一个稀疏矩阵。矩阵的行数等于样本数列数等于保留的特征数每一个单元格就是对应词在当前文档里的TF-IDF权重。到这一步你已经把原始新闻文本变成了模型可以学习的数据形态。接下来就可以进入正式的模型训练环节了。4. 模型训练与评估4.1 先跑通一个最朴素的baseline训练模型前必须先把数据划分成两部分训练集和测试集。训练集用来让模型学习文本和标签之间的关联测试集用来检验模型在没见过的数据上的表现。绝对不能拿同一批数据既训练又测试否则模型只是背答案到了真实场景会原形毕露。from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import accuracy_score X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) model MultinomialNB() model.fit(X_train, y_train) y_pred model.predict(X_test) print(准确率:, accuracy_score(y_test, y_pred))输出应该是类似准确率: 0.9x的数字。随机种子random_state42是为了保证每次运行划分方式一致方便复现和对比实验结果。stratifyy是分层抽样让训练集和测试集里各类别比例保持一致这在标签不均衡时很重要。朴素贝叶斯是这个任务里非常适合做baseline的算法。它基于一个很强的假设所有特征之间彼此独立。文本特征显然不满足这个假设但在实际应用中它往往表现得意外地好。因为新闻里高权重的词对于类别判断确实有很强的指示性比如“涨停”大概率指向财经“进球”大概率指向体育特征之间是否独立反而不影响最终排序。4.2 再上逻辑回归和线性SVM效果会更好朴素贝叶斯能跑通但它表达复杂关系的能力有限。为了验证“模型选对了能涨多少”我建议同时试另外两个模型逻辑回归和线性SVC。from sklearn.linear_model import LogisticRegression from sklearn.svm import LinearSVC from sklearn.metrics import classification_report models { logistic: LogisticRegression(max_iter1000), svm: LinearSVC() } for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) print(, name, ) print(classification_report(y_test, y_pred, zero_division0))classification_report会输出每个类别的精确率、召回率、F1分数以及整体准确率。这四个指标的含义需要分清精确率被模型判成某个类别的样本里有多少确实属于这个类别。召回率属于某个类别的样本里有多少被正确找出来了。F1分数精确率和召回率的调和平均两者都高时F1才高。准确率所有预测里预测正确的比例整体粗略指标。具体到新闻分类场景如果某个类别的召回率明显低于精确率说明模型对这个类别的识别太保守只把握高的点了如果精确率低、召回率高说明模型把大量别的类别的样本错分到了这个类别里。调试方向完全不一样。我自己在同样数据上跑下来的经验是逻辑回归和线性SVM通常都比朴素贝叶斯高2到3个百分点两者的差距不大。逻辑回归的优势是能输出概率方便后续展示置信度线性SVM在高维稀疏文本特征上表现也很稳。入门阶段可以优先用逻辑回归可解释性好后处理也方便。4.3 混淆矩阵怎么看能发现什么只看整体准确率和每个类别的各项指标还不够我强烈建议把混淆矩阵打印出来看看。它是一张表格行代表真实类别列代表模型预测类别对角线越集中说明分类越准确非对角线位置的数字则揭示了模型具体混淆了哪两类。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred) sns.heatmap(cm, annotTrue, fmtd, xticklabelsmodel.classes_, yticklabelsmodel.classes_) plt.show()比如“科技”和“财经”经常互相串这是因为科技新闻里往往包含企业上市、融资等财务信息如果“房产”和“汽车”出现错分可能是因为两者都属于消费类资讯常用词重叠度高。看到这些现象不要慌这是文本分类的正常特征后续可以通过增加训练数据、调整特征权重、加入领域词典来缓解。4.4 走一个完整的预测闭环调好了模型最后要落地到“输入新文本输出预测类别”。下面这段代码把向量化、模型训练、预测封装成一个类方便你拿到任何一条新闻去测试。import re import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB class NewsClassifier: def __init__(self): self.vectorizer TfidfVectorizer(max_features10000, ngram_range(1, 2), min_df2) self.model MultinomialNB() def _clean(self, text): text re.sub(r\s, , text) return text.strip() def _cut(self, text): return .join(jieba.lcut(text)) def train(self, df): texts df[content].apply(self._clean).apply(self._cut) self.X self.vectorizer.fit_transform(texts) self.model.fit(self.X, df[category]) def predict(self, text): text self._cut(self._clean(text)) x self.vectorizer.transform([text]) return self.model.predict(x)[0], self.model.predict_proba(x).max()用的时候拿一篇训练数据集里的真实新闻或者自己从网上找的新闻把正文传进去classifier NewsClassifier() classifier.train(df) sample 该公司今日发布了新款旗舰手机搭载最新的处理器和影像系统预计下个月全球开售。 label, prob classifier.predict(sample) print(label, prob)predict_proba返回的是各类别概率取最大值可以当置信度参考。在工程系统里这个方法很有用当置信度低于某个阈值时可以转人工处理而不是让系统强行输出一个很可能错误的类别。5. 常见问题与排查技巧实录5.1 文件读不进来或者乱码读CSV报UnicodeDecodeError是新手最常遇到的问题。解决方案分两步先确认数据本身的编码格式再用对应编码读取。Windows下有些老数据集默认是gbk或gb2312macOS和Linux下常用utf-8。可以在读文件之前用文本编辑器打开看一眼或者用二进制模式读取前几个字节推测。with open(news.csv, rb) as f: raw f.read(200) print(raw)如果开头能看到b\xff\xfe多半是utf-16如果普通中文正常显示再试utf-8和gbk。从工程角度讲统一在读取时转成utf-8再处理是最省心的做法。5.2 分词结果不理想jieba对通用新闻词汇分词效果不错但遇到领域术语、新词就会切错。比如“新能源汽车”可能会被切成“新能源”、“汽车”本来是一个完整概念被拆开了。轻度情况下不影响分类因为两个词仍然保留在特征里但严重的情况下会引入噪声。解决办法有两个。一是加自定义词典把业务专用词提前告诉jiebaimport jieba jieba.add_word(新能源汽车) jieba.add_word(液态金属)二是换用带词典文件的初始化方式适合一次性加载一大批领域词。分词是文本挖掘的地基如果发现准确率卡住不动回头检查一下关键类别的代表新闻是否被正确切分成有意义的词往往能找到突破口。5.3 内存占用太高程序卡死TF-IDF向量化后如果特征维度设得太高、样本量又大稀疏矩阵虽然比稠密矩阵省内存但整体占用还是可能让你本机卡顿。几个降内存的技巧优先级从高到低调低max_features从1万降到5000甚至3000调低ngram_range不使用(1, 2)改回(1, 1)提高min_df从2提到3或5使用dtypenp.float32降低每个存储单元的大小。如果数据量特别大还可以考虑用外存式的处理方式但那已经超出这篇入门文章的范畴了。先学会用小数据跑通再逐步扩大规模。5.4 准确率上不去先判断是欠拟合还是过拟合训练集准确率高、测试集准确率低是过拟合的可能性大。解决办法包括增加训练数据、降低特征维度、提高min_df、增加正则化参数。训练集和测试集准确率都低是欠拟合的可能性大要考虑增加特征维度、加入ngram_range(1, 2)、换更强的模型。在这篇项目的配置下新闻文本通常特征数量多但稀疏模型容量相对不足一般更容易遇到欠拟合而不是过拟合。所以如果发现准确率卡在0.7上下先把max_features调高一点再把逻辑回归换成线性SVM试一轮大概率会有改善。5.5 效果不稳定、每次跑结果不一样结果波动通常来自三方面数据划分随机性、模型训练随机性、分词顺序依赖。把train_test_split的random_state固定之后大部分波动都会消失。如果希望所有实验步骤都能复现建议把你用到的所有库的版本也固定下来这对后续调试非常有帮助。6. 从简单模型出发怎么继续深入6.1 从一开始就该搭好的评估脚手架我见过很多初学者做完一次分类就跑到下一步了其实评估环节值得反复打磨。建议你在项目一开始就建一个固定的脚本或函数把准确率、精确率、召回率、F1、混淆矩阵全部一次输出。这样每次改动数据、特征、模型都能快速看到变化方向和幅度不会用“手感”代替实验对比。后续如果要发论文、做对比实验这些评估结果也能直接复用。评估脚本需要包括以下部分数据集加载、清洗和分词、向量化、划分训练测试集、训练模型、逐类指标打印、混淆矩阵保存。这一个流程模板可以套用到几乎所有文本分类任务上。6.2 传统模型之后可以朝哪个方向走跑通传统机器学习模型你已经掌握了文本分类的完整链路接下来有两个自然的技术演进方向一是换更强的传统模型比如用XGBoost、LightGBM在TF-IDF特征上做分类往往比线性模型多一些提升但训练时间会变长特征维度的控制也需要更小心。二是转向深度学习。先学循环神经网络或TextCNN理解词嵌入的基本概念再学预训练模型如BERT及其各类蒸馏版本。这个跳跃的关键在于你仍然需要做同样的数据处理但文本表示从稀疏的TF-IDF向量变成了稠密的词向量模型也从线性模型变成了多层网络。不要因为这篇用了传统方法就觉得它过时很多工业级系统到现在仍然用“TF-IDF逻辑回归”作为第一版基线模型稳定可靠可解释出了问题容易排查。6.3 如果换一个分类场景这套流程怎么改这套流程本质上与具体领域无关。如果你下次想做一个电商评论情感分类只需要把数据换成评论和情感标签把类别从新闻分类改成正面、负面、中性三个标签其他几乎所有代码都能复用。如果你想做长文本分类可能要调整max_features和ngram_range并增加段落级别的结构化处理。真正需要从零思考的是数据的标注质量和类别的定义。新闻分类标签相对客观但情感标签经常带有主观歧义比如“这个产品还行”到底算正面还是中性不同标注者可能有不同判断。这时候模型的效果瓶颈往往不在算法而在你手里的训练数据本身。写在最后的一点体会做项目最怕在第一步就追求完美。如果把朴素贝叶斯跑通了哪怕准确率只有0.85也比那些搭了半天框架结果一个模型都没跑出来的人强得多。我在实际做这个项目的时候印象最深的不是哪个模型的准确率最高而是“把评估指标一次性打印出来”这个习惯帮我节省了大量时间。每次调整完特征或模型一眼就能看到哪个类别在变好、哪个类别在变差而不是靠感觉在那猜。如果你成功跑完了这篇的代码下一步建议自己动手做两件事一是换一份新闻数据集重新跑一遍全流程你不换数据就永远不知道自己中间有多少步骤是依赖固定的数据形态和标签分布的二是调一次参数故意把max_features设成100和50000观察准确率、训练时间、内存的变化。这些实验做一遍之后你对文本分类的理解会比只看十篇教程都更扎实。
阅读完成 · 觉得有帮助?
咨询建站