资讯动态

中文文本分类入门实践:从新闻数据集到TF-IDF与FastText

发布时间:2026/10/18 18:34:43 来源:尧图企业网站定制
简介源自今日头条新闻聚合平台的中文新闻文本数据集带有明确的类别标签面向NLP研究者、算法工程师及文本分类学习者可用于训练和评估多类新闻分类模型覆盖国际、国内、娱乐、体育等常见主题。资源压缩包共4个文件包括Python数据处理脚本、两份Markdown格式的使用说明文档以及一个核心数据压缩包整体大小约25.67MB。借助脚本和文档用户可以快速完成数据解压、格式查看与文本清洗进而开展分词、TF-IDF或词向量表示、模型构建与超参数调优等实验配套说明也降低了数据集的上手门槛。该数据既适合入门者系统了解文本分类全流程也适合进阶者作为基准数据验证不同模型效果或研究类别分布问题。目前已有1003人学习下载。1. 中文新闻文本分类数据集为什么拿它当 NLP 入门的第一份语料做中文文本分类最烦的不是模型不会写而是找不到一份量够、标签相对干净、能直接跑通全流程的数据集。这份新闻文本分类数据集目录名 toutiao-text-classfication-dataset-master就是专门解决这个问题的压缩包里是十几万条带分类标签的新闻标题覆盖民生、文化、娱乐、体育等常见资讯频道拿来做文本分类入门实验、特征工程验证、模型基线对比都合适。适合刚接触 NLP 的学生也适合要快速验证业务分类方案的开发者——先用它把整个管线打通再迁移到自己的数据上成本低很多。2. 数据集结构与读取先搞清楚标题和标签是怎么排布的2.1 压缩包里有什么文件清单与各自用途拿到 zip 后先别急着解压数据先看压缩包里的 readme.md 和 ss.md。常见情况下release 包会包含以下几个文件文件用途get_data.py数据下载与更新的脚本网络不通时可以手动下载数据包readme.md数据集说明包含字段格式、类别编号映射ss.md补充说明或示例不一定每个版本都有toutiao_cat_data.txt.zip真正的数据文件解压后是一个较大的 txt这里的重点是主数据文件是 toutiao_cat_data.txt不是 CSV。读它之前必须确认编码和分隔符。readme 里通常会写明每一列的含义常见做法是用 tab 分隔字段依次是新闻 ID、分类编号、标题、关键词、摘要描述。如果你拿到手的文件把 readme 弄丢了也别瞎猜用最原始的 head 命令看前几行head -5 toutiao_cat_data.txthead 输出会直接告诉你分隔符长什么样。如果是制表符在终端里看起来会有大段空白这时就能确认sep\t。这一步看起来不起眼却是后面所有工作的基础——把分隔符认错pandas 会把整行读成一列后面全乱套。2.2 用 pandas 读取数据分隔符、编码和行数的坑确认格式后用 pandas 读取就很直接。我一般先读前几千行验证而不是一把梭读全量避免文件坏了或分隔符不对时白等几分钟import pandas as pd column_names [news_id, label, title, keywords, desc] df pd.read_csv(toutiao_cat_data.txt, sep\t, headerNone, namescolumn_names, nrows5000, encodingutf-8) print(df.head()) print(df.info())代码说明sep\t指定制表符分隔headerNone表示源文件没有列名names手动指定列名这里五个列名是按常见格式给的具体以 readme 为准nrows5000先读五千条验证结构encoding先试 utf-8如果出现乱码或报UnicodeDecodeError再换成gbk或utf-8-sig。运行后如果 df 正常分出 5 列说明格式猜对了。如果只有一列大概率是文件实际用逗号或其他符号分隔回去看一眼 head 输出重新设 sep。确认格式后就可以全量读取15 万条文本数据占用的内存不大普通电脑都扛得住。但如果你在服务器上用 Jupyter 边跑边看内存占用会随变量缓存累积建议每跑完一步就释放中间变量比如del df或import gc; gc.collect()。我遇到过因为不清理变量前后跑了几十次实验把 16G 内存吃满的尴尬。2.3 类别分布先看清 15 个类有多少样本文本分类任务里标签分布直接影响模型效果和评估指标。先用 value_counts 统计每个类别的数量label_counts df[label].value_counts().sort_index() print(label_counts) label_counts.plot(kindbar, figsize(12, 4))从统计结果你能立刻看出有没有样本极少的类别比如某些类别不到几百条有没有多到离谱的类别比如某类占了三成。这个分布决定了后面要不要做采样、要不要用 class_weight、评估时看「准确率」还是「Macro-F1」。如果训练集和测试集划分时不按比例分层小类别很容易在测试集里缺失导致 F1 无法计算。除了数量还要确认标签编号是否连续。有些版本的数据 label 编号从 1 到 17中间可能有缺号有些把类别编号和文本分类的 15 类对应。为了避免后续 target_names 错位谨记先value_counts().sort_index()看全貌再对照 readme 里的映射表。我见过不少同学拿到数据直接train_test_split(test_size0.2)就开跑结果小类别在测试集里只剩几条评估结果波动很大。后面第四章会专门演示 stratify 参数先记住文本分类的类别分布几乎不可能均匀划分时必须按标签分层。3. 文本预处理与特征表示清洗、分词和 TF-IDF 参数怎么设3.1 标题清洗删 URL、删符号但别过度删除新闻标题里夹杂着 URL、#话题#、用户名、数字和全角标点。这些噪声对分类有干扰但也不是全得删。我的习惯是先用正则把这些明显噪声删掉再统一空格import re def clean_title(text): if not isinstance(text, str): return text re.sub(rhttps?://\S|www\.\S, , text) # 删 URL text re.sub(r#.?#, , text) # 删话题标签 text re.sub(r\S, , text) # 删 用户 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) # 非中英文数字变空格 text re.sub(r\s, , text).strip() return text df[clean_title] df[title].apply(clean_title)正则说明\S匹配非空白连续串URL 和 用户都可以一次匹配掉[\u4e00-\u9fa5]保留中文字符a-zA-Z0-9保留英文和数字其余全变成空格再压缩多余空格。注意这里保留数字是因为新闻标题里“2025新规”这类数字对类别判断有用如果你想验证数字是否影响效果可以再出一版全删数字的对照实验。有些同学会把所有数字也删掉这个要看场景。新闻标题里的年份、数量词有时和类别强相关比如“2025年养老金上调”容易被分到民生类数字删掉后反而丢失线索。所以我不建议模板化地全删留着做对比实验更有说服力。3.2 分词jieba 的默认模式就够用但要去停用词中文标题分词用 jieba 是主流做法。默认全模式会分出大量重复词用精确模式或搜索引擎模式都行我一般用 lcut 精确模式import jieba stopwords set() with open(stopwords.txt, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def tokenize(text): words jieba.lcut(text) return .join(w for w in words if w not in stopwords and len(w.strip()) 0) df[tokens] df[clean_title].apply(tokenize)说明stopwords.txt 可以自己维护一份包含“的、了、在、是”等常见虚词也可以在网上找现成的中文停用词表。分词结果用空格连接是为了后面 TfidfVectorizer 直接拿字符串作为输入。如果你发现某些领域词被切错比如“有机食品”被切成“有机”和“食品”可以把自定义词加入jieba.add_word()这在第四章会用到。分词粒度对结果影响很大。jieba 默认词典偏向通用语料新闻中的“双减”“碳中和”这类词如果被切碎特征表达力会下降。遇到验证集效果明显差于训练集时优先怀疑分词粒度而不是急着换模型。3.3 TfidfVectorizer 参数怎么设max_features 和 ngram_range 是核心TF-IDF 是文本分类的经典基线。sklearn 的 TfidfVectorizer 参数很多关键是控制特征维度和稀疏性from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features80000, ngram_range(1, 2), min_df2, max_df0.8, sublinear_tfTrue ) corpus df[tokens].tolist() X vectorizer.fit_transform(corpus) print(X.shape)参数说明max_features80000限制特征总数防止词表过大造成内存爆炸ngram_range(1,2)表示同时使用单个词和相邻两词组成的短语比如“人工智能”这种组合词会被拆成“人工”“智能”“人工智能”三个特征能抓到一部分词组语义min_df2过滤只在一条新闻里出现过的词避免长尾噪声max_df0.8过滤出现在 80% 以上文档里的词这类词基本都是公共词区分度低sublinear_tfTrue对词频做 log 压缩削弱高频词的绝对优势。这里最容易翻车的操作是X.toarray()把稀疏矩阵转成稠密矩阵。15 万条样本 × 8 万维特征转成稠密矩阵直接爆内存。正确做法是保持稀疏矩阵格式直接交给逻辑回归或 SVM 训练。3.4 第一个基线逻辑回归 TF-IDF特征搞定后用最简单的逻辑回归就能跑出一个不错的基线from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report y df[label].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) clf LogisticRegression(C1.0, max_iter1000) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(classification_report(y_test, y_pred, digits3))划分时stratifyy很重要它保证训练集和测试集中每个类别的比例一致避免小类别在测试集中缺失。逻辑回归默认采用 L2 正则C1.0表示正则强度适中如果你的验证集效果不稳定可以尝试把 C 调大到 3.0 或 10.0观察准确率和 F1 的变化。max_iter1000是因为高维稀疏特征下默认的 100 次迭代经常不收敛训练时会打印警告把它加到自己舒服的值为止。跑完之后分类报告里会有每个类别的 precision、recall、f1-score 以及 overall accuracy。以我的经验这个数据集上用 TF-IDF 逻辑回归准确率能做到 80% 以上macro-F1 通常也在 0.78 到 0.83 之间。这个数字不用羡慕它只是告诉你怎么往下一步调优。4. 分类模型训练从线性 SVM 到 FastText 的对比与调优4.1 为什么线性 SVM 比非线性核函数更适合新闻标题逻辑回归之后线性 SVM 是性价比最高的升级选择。原因是新闻标题经 TF-IDF 后特征维度高而且样本数量大线性分类器在这类数据上训练速度快、泛化能力不差。我见过有人在这个数据集上跑 RBF 核 SVM跑了半小时没出结果最后放弃。血泪经验是不要轻易上非线性核。scikit-learn 里推荐用LinearSVC而不是SVC(kernellinear)前者基于 liblinear能处理大规模稀疏数据后者基于 libsvm不适合稀疏高维大样本。代码上只有一行差别训练时间却有量级差异。4.2 LinearSVC 训练与参数调整from sklearn.svm import LinearSVC svm LinearSVC(C1.0, max_iter2000) svm.fit(X_train, y_train) y_pred_svm svm.predict(X_test) print(classification_report(y_test, y_pred_svm, digits3))LinearSVC 的核心参数是 C 和 loss。C 是正则化系数的倒数C 越小正则越强C 太大容易过拟合losssquared_hinge是默认值理论上收敛比hinge更稳训练也更平稳。如果你的数据量不大也可以试losshinge但训练时间会稍长。C 参数在 0.1 到 10 之间往往都是可用的具体取值带点玄学建议直接用验证集网格搜索别凭感觉。训练时如果看到ConvergenceWarning: Liblinear failed to converge不要慌两种处理方式增大 max_iter比如 4000 到 6000或者调小 C让模型没那么激进。我通常先调 C因为单纯增大 max_iter 在 liblinear 里可能只是把问题延后不解决本质的优化困难。4.3 指标选择准确率会骗人Macro-F1 更可信类别不平衡时模型会把高频类学好、低频类学差。准确率只统计总体预测正确的比例低频类全错也能有很高的准确率。评估多分类文本模型我习惯同时打印准确率和 macro-F1from sklearn.metrics import accuracy_score, f1_score print(accuracy:, accuracy_score(y_test, y_pred_svm)) print(macro-f1:, f1_score(y_test, y_pred_svm, averagemacro))macro-F1 是把每个类别的 F1 算出来再平均对每个类别一视同仁。如果你的 macro-F1 比 accuracy 低很多说明低频类别确实拉胯。这时可以去看分类报告里哪类 recall 特别低后续针对该类别补充数据或调整权重。4.4 FastText训练快但需要准备好输入格式深度学习模型不是必须一开始就上。FastText 作为文本分类利器优势是训练速度极快、支持 n-gram 特征、能在 CPU 上跑在你换 BERT 之前很值得做一次对比。使用 fasttext 库之前要把训练数据整理成它要求的格式每行是一个样本类别带__label__前缀后面跟着分词后的文本用空格隔开。import fasttext with open(fasttext_train.txt, w, encodingutf-8) as f: for tokens, label in zip(df[tokens], df[label]): line f__label__{label} {tokens}\n f.write(line) model fasttext.train_supervised( inputfasttext_train.txt, epoch20, lr0.5, wordNgrams2, minCount2 )参数说明epoch20表示训练轮数FastText 训练快20 轮也就几分钟lr0.5是初始学习率太大会震荡太小收敛慢wordNgrams2开启词二元组特征中文标题里“新能源汽车”切成“新能源”“汽车”后二元组能保留“新能源_汽车”的共现信息对分类帮助明显minCount2过滤低频词和 TF-IDF 的 min_df 同理。模型训完预测时注意 fasttext 返回的是带__label__前缀的结果result model.predict(新春 走基层 记者 探访 农村) print(result) # ((__label__5,), array([...]))要拿类别编号就取int(result[0][0].replace(__label__, ))。FastText 的调参空间比 TF-IDF 多wordNgrams 从 1 调到 2 往往就有明显增益再往上 3 会牺牲训练速度。另一个容易踩的点是训练集里如果有空行或空文本fasttext 会直接报错写文件前先过滤掉 tokens 为空的样本df df[df[tokens].str.strip() ! ]5. 避坑与常见问题标签错位、重复样本和内存爆炸5.1 标签编号与类别名错位分类报告里的类名全是错的现象训练出的模型没问题但打印 classification_report 时类别名和数字对不上看起来某个类别效果极差另一个类别又诡异的好。原因数据集的类别编号不是从 0 开始而是从 1 开始或者 readme 里的映射表和代码里的目标名单顺序不一致。很多人直接用了target_nameslist(range(1, 18))把编号当顺序于是整个报告张冠李戴。解决第一步跑sorted(df[label].unique())看真实类别编号。第二步去 readme 里找映射表构建一个字典例如{8: 娱乐, 5: 体育}再传入 classification_report 的 target_names。要养成习惯永远不要靠猜测把 label 数字映射到名字。5.2 训练集和测试集标题重复准确率虚高到吓人现象某同学做基线随机划分后用 TF-IDF 逻辑回归准确率直接到 96%但用同一个模型在另一批新数据上效果掉到 70%。他觉得模型没问题其实是训练集和测试集里有大量重复标题。原因原始数据集是从新闻站点爬下来的同一事件可能有多条相同标题也可能不同频道转载同一条新闻。随机划分时相同标题可能同时进入训练集和测试集模型相当于「背题」而不是「理解」。解决划分之前先对标题去重。最简单的方式是按标题分组保证同标题只落在一个集合里df df.drop_duplicates(subset[title], keepfirst) print(df.shape)如果标题重复率高去掉后样本量会减少几千到几万条这个取舍很值得。更稳妥的办法是给标题做一次 MD5 哈希把哈希值作为分组键做 GroupShuffleSplit再用索引去划分这样能保证同一标题的变体也不会串集。我在实验里直接用 drop_duplicates够用。5.3 jieba 分词全量跑太慢内存还持续上涨现象对 15 万条新闻标题做jieba.cut跑了十几分钟还没结束任务管理器里内存占用一直在涨最后可能直接把笔记本卡死。原因jieba 默认词典包含大量词加载就占了几十 MB而逐条把生成器转列表还会积累中间结果。另外jieba.cut每次调用都要重新检查词典线程上也有开销。解决先把停用词表和自定义词一次性加载好然后分批量处理不要一次全部做开启并行模式能显著提速但要注意 jieba.enable_parallel 在 Windows 下有时不生效Linux 下更稳定。import jieba jieba.enable_parallel(4) # 开启 4 线程并行Windows 慎用 def tokenize_batch(texts): return [ .join(w for w in jieba.lcut(t) if w not in stopwords) for t in texts] df[tokens] tokenize_batch(df[clean_title].tolist()) jieba.enable_parallel(0) # 用完记得关闭如果你发现分词结果里数字被错误切分可以加jieba.add_word(新能源汽车)这类自定义词把领域词固定下来。不过自定义词加太多会拖慢分词速度一般只加对分类影响大的词。5.4 TfidfVectorizer 内存爆炸甚至还没训练就 OOM现象vectorizer.fit_transform(corpus)正常但接下来X.toarray()直接把内存耗完Jupyter 内核直接死掉。原因稀疏矩阵存储经验是 8 万维特征 × 15 万条样本用稀疏矩阵只存非零元素一旦转稠密矩阵就要存储 120 亿个元素每个 float 8 字节那是几百 GB 的运算量。解决永远不要对 TF-IDF 结果调用 toarray。sklearn 的分类器都支持稀疏矩阵输入直接传 X_train 和 X_test。如果特征维度太大导致训练慢用max_features50000或30000重新试先出结果再谈提升。如果还是不放心可以用 HashingVectorizer 做特征哈希牺牲词频可解释性换内存from sklearn.feature_extraction.text import HashingVectorizer vectorizer HashingVectorizer(n_features2**20, alternate_signFalse) X vectorizer.fit_transform(corpus)HashingVectorizer 不存储词表n_features 可以手动定内存占用更可控缺点是你无法从模型系数里看出哪些词贡献大。5.5 准确率不低但 F1 很差原来是类别不平衡的锅现象某个类别样本只有几百条模型预测时几乎全预测成高频类结果这个类别的 recall 和 F1 惨不忍睹但整体准确率却有 85%。原因模型在优化的目标函数上对每个样本一视同仁高频类贡献的损失大自然被优先学习。低频类学不到足够的判别信息预测时容易被吞掉。解决两种常见修正。一是给分类器加 class_weightfrom sklearn.svm import LinearSVC svm LinearSVC(class_weightbalanced, C1.0, max_iter2000)二是做类别重采样但文本数据重采样容易改变标题分布不建议直接复制。更推荐的是按类别计算权重在评估时以 macro-F1 为准先不追求所有类一致把注意力放在提升低频类的 recall。如果你后续用深度学习可以在损失函数里对类别权重做矩阵乘法这是进阶玩法。6. 进阶用混淆矩阵和错误样本反推标签质量6.1 先跑一个快速模型把错误样本找出来微调 BERT 之前花十分钟做一次数据质量验证能省下后续很多无意义的调参。我习惯的做法是用线性 SVM 或逻辑回归先跑一遍然后找出预测错误的样本人工抽查。这个步骤表面看是浪费时间实际上能快速暴露标签噪声和特征泄漏。import pandas as pd err pd.DataFrame({ title: df.loc[test_index, title], true_label: y_test, pred_label: y_pred_svm }) err err[err[true_label] ! err[pred_label]] print(err.head(20))如果发现同一标题在不同行被标成两个类别说明原始数据标签有噪声如果错误样本集中在特定类别说明该类别的标题表达本身有歧义。这些发现比模型调参更有价值。6.2 用混淆矩阵定位易混淆类别from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred_svm) # cm 第 i 行是真实类别第 j 列是预测类别观察混淆矩阵中非对角线上的大数值往往能找到几组高频互混类别比如「民生」和「社会」这类语义高度重叠的频道。知道了这一点你可以去 readme 里确认两个类别是否真的能区分而不是盲目增加数据集规模。6.3 让数据验证成为固定习惯从那以后我每次拿到新数据集都会强制走一遍标签分布统计、标题去重、错误样本抽查。哪怕急着调模型也先跑完这三步再动手。这套流程帮我避开了无数次因数据问题导致的实验翻车也让我养成了「先怀疑数据再怀疑模型」的直觉。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑