ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

今日头条新闻文本分类数据集处理全流程:清洗、建模与泄漏验证

今日头条新闻文本分类数据集处理全流程:清洗、建模与泄漏验证 简介今日头条中文新闻文本分类数据集是面向自然语言处理入门者的中文新闻分类语料覆盖国际、国内、娱乐、体育等多个类目适合用来练习文本分类全流程。整个压缩包共四份文件含两份说明文档、一个数据获取脚本和一个数据压缩包整体约二十五点六七兆字节。目前已有九百八十五人学习下载在中文文本分类练习数据中具备一定认可度。说明文档整理了数据集的字段结构、训练、验证、测试划分方式及类别标签说明方便快速上手数据压缩包内封装了后续建模所需的文本样本结合脚本可快速准备数据。拿到后可自行完成数据预处理、特征表示、模型构建与评估也可以尝试嵌入、微调等进阶方法是一份兼顾教学与实战的中文文本分类资源。1. 为什么“今日头条中文新闻文本分类数据集.zip”值得认真拆一遍一个从网盘拖下来的“今日头条中文新闻文本分类数据集.zip”解压后是几十万条带频道标签的新闻数据覆盖体育、财经、娱乐、科技等常见内容分类。它是中文短文本分类最常见的入门数据集之一很多博客用它演示朴素贝叶斯、TF-IDF、甚至 BERT 微调。可这个 zip 并非开箱即用字段用_!_分隔正文可能截断或为空标签存在长尾URL 里还藏着分类线索。这篇文章我会按自己处理这个数据集的流程从解压体检、清洗切分到训练基线和泄漏验证给出可以直接抄走的命令和代码。2. 解压与体检先摸清头条中文新闻数据集的真实结构拿到 zip 的第一步不是写模型而是确认里面到底有几个文件、什么编码、每行几列。常见做法是把压缩包解压到单独目录然后用file和head快速判断文本格式。mkdir -p toutiao_data unzip -q 今日头条中文新闻文本分类数据集.zip -d toutiao_data file toutiao_data/* head -n 3 toutiao_data/*.txt wc -l toutiao_data/*.txt这里unzip -q只输出解压过程中的错误避免刷屏-d指定目标目录。file能识别文本是 UTF-8 还是 GBK 编码以及有没有 BOM。head用来观察分隔符这个数据集大多使用_!_作为分隔符一行包含标签编号、分类名、URL、标题、关键词、正文共六个字段。wc -l统计总行数如果解压出来多个 txt可以用cat *.txt all.txt合并后再处理。如果直接解压提示文件名乱码常见原因是压缩包在 Windows 下用 GBK 记录文件名而当前系统是 UTF-8。此时可以安装unzip的替代工具或使用 Python 的zipfile在读取时手动重编码。另外注意压缩包是否是分卷格式如果同目录下存在.z01、.z02这类后缀文件必须把全部分卷放在同一个目录再对主压缩包执行unzip缺少任一分卷会直接报Invalid central directory之类的错误。2.1 用 Pandas 读取并确认字段确认分隔符之后用pandas.read_csv读取前几行是最快的验证方式。import pandas as pd df pd.read_csv( toutiao_data/toutiao_cat_data.txt, headerNone, sep_!_, names[label_id, category, url, title, keywords, content], nrows10, ) print(df[[label_id, category, title]].head())sep_!_表示按三个字符的分隔符切列headerNone表示源文件没有列名。names按顺序指定六个字段名这样后续代码不必再用下标访问。nrows10先只读 10 行防止一次性读入超大文件占用过多内存。确认格式无误后去掉nrows参数全量读取。更保险的做法是用zipfile先列出压缩包内的文件名和大小避免解压出隐藏的额外文件import zipfile with zipfile.ZipFile(今日头条中文新闻文本分类数据集.zip) as zf: for info in zf.infolist(): print(info.filename, info.file_size)infolist()返回每个压缩条目的元信息file_size是解压后大小。如果你只差几 KB 就能判断文件类型直接这样打印比unzip -l更省事尤其适合在 Jupyter Notebook 里做数据探查。2.2 用数据质量报告找出脏数据一个标准的头条新闻分类数据集中每条样本的字段质量参差不齐字段常见问题处理建议label_id存在未知编号对照 category 校验category类别名不一致如“体育”与“体育新闻”统一映射url有重复或丢失用于去重不作为特征title可能为空或只有标题党文本至少保留 5 个字符keywords大量为空单独校验是否泄漏content截断、HTML 标签残留清洗正文用下面的代码检查缺失值、重复行和类别分布print(df.isnull().sum()) print(df.duplicated(subset[title]).sum()) print(df[category].value_counts())df.isnull().sum()按列统计缺失值这里主要是keywords和content可能缺失。duplicated(subset[title])统计标题重复的样本新闻网站转载场景下重复率可能超过 3%。value_counts()展示每个类别的样本量头条这一版通常有 15 个左右的一级分类头部类别和尾部类别数量可能相差 5 倍。如果某些类别的label_id和category对应关系异常比如两个不同编号映射到同一个中文名建议直接以category为准重新映射。3. 清洗与切分把头条新闻文本数据整理成标准训练集洗数据决定分类任务的上限。第 2 章只是看了全貌这一章要解决三个问题标签怎么编码、文本怎么表示、数据怎么切分。3.1 标签编码与类别权重数据集的label_id本身是数字但数字之间有空隙直接用原始数值当作分类输出会让模型学到无意义的顺序关系。常见做法是用LabelEncoder重新编码。from sklearn.preprocessing import LabelEncoder df[label_id] df[label_id].astype(int) le LabelEncoder() y le.fit_transform(df[label_id]) # 保留标签映射后续预测结果要还原成中文类别名 label_name_map dict(zip(df[label_id], df[category])) class_names le.classes_LabelEncoder会按数字大小排序并压缩成 0 到n_classes-1的连续整数classes_保存这个映射。训练完成后用le.inverse_transform(pred)就能把 0、1、2 还原成原始标签编号。如果某个类别的样本量特别少例如只有 3000 条而多数类别有 30000 条需要记录类别权重。LogisticRegression(class_weightbalanced)会在损失函数里按样本量倒数放大少数类惩罚避免模型把所有样本都预测成头部类别。这个参数在后续训练代码里要保留。还需要检查category字符串本身的稳定性。有的发布版本使用“历史”有的使用“文化”直接用category作为标签文件会在不同版本间造成迁移困难。我习惯以label_id为准再手工维护一份label_id - 中文名的映射这样即使类别名变了标签语义仍保持一致。3.2 选择文本字段并做分词头条新闻数据集里最有信息量的字段通常是title和keywords而不是整篇content。短文本分类场景下标题已经包含了事件主体和情感倾向content反而会引入大量与分类无关的铺垫文字。我一般会把标题和关键词拼接正文截取前 80 个字符用于补充上下文。import jieba df[text] df[title].fillna() df[keywords].fillna() df[content].str[:80].fillna() df[text] df[text].apply(lambda x: .join(jieba.lcut(x)))jieba.lcut返回分词后的列表join成空格分隔的字符串这是 scikit-learn 的TfidfVectorizer最容易接收的格式。这里没有先去掉停用词原因是对新闻分类而言“今日”“记者”“报道”等词对判别频道贡献不大但它们对区分“娱乐八卦”和“时政新闻”仍有一定作用如果只保留高频词模型会更稳定。也可以不用分词现在的中文预训练模型直接吃原始字符但在传统机器学习方案里分词是必须的。分词错误会导致后续 TF-IDF 特征错位比如把“中国大陆”切成了“中国”和“大陆”对类别判断影响不大但会增加特征维度。头条新闻标题里的大量缩略语例如“进博会”“脱欧”会随着数据规模增长成为独立特征不必额外维护词典。3.3 按类别分层切分数据集切分训练集和验证集时不能直接shuffle因为真实新闻数据有强烈的发布时间相关性同一个事件可能在短时间内被多家媒体重复报道聚类效应会让随机切分的高分不可信。from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( df[text], y, test_size0.1, stratifyy, random_state42 ) print(X_train.shape, X_val.shape)stratifyy会保证切分前后各类别比例一致这个参数对不均衡数据集非常重要。random_state42固定随机种子复现实验结果。如果后续要更严谨建议按url中的域名做分组切分让同一个来源的新闻只出现在训练集或验证集避免模型记住来源特征。域名提取可以这样实现from urllib.parse import urlparse def get_domain(url): try: return urlparse(url).netloc except Exception: return unknown df[domain] df[url].astype(str).apply(get_domain)先用train_test_split切出验证集再把训练集按domain去重能显著降低分数虚高的概率。4. 训练一个能从零开始跑通的头条新闻文本分类模型这一章给出最小可复现的基准模型TF-IDF 加逻辑回归。它训练速度快调参直观还能作为后续 BERT 方案的对照。4.1 用 Pipeline 串联 TF-IDF 与逻辑回归from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline model Pipeline( [ ( tfidf, TfidfVectorizer(max_features20000, ngram_range(1, 2), sublinear_tfTrue), ), (clf, LogisticRegression(max_iter1000, C1.0, n_jobs-1)), ] ) model.fit(X_train, y_train)max_features20000限制词表大小头条标题的中文词数量大约在 10 万量级只看最高频的 2 万词能显著降低过拟合风险。ngram_range(1, 2)同时保留单词和双词组合能抓到“虚拟现实”“乡村振兴”这类有明确频道倾向的短语。sublinear_tfTrue对词频取1 log(tf)减弱长文本里高频词的影响力。C1.0是逻辑回归的正则化强度值越小正则越强头条这种高维稀疏特征场景一般不需要小于 0.1。如果你想把C也放进参数搜索可以直接在LogisticRegression里加class_weightbalanced处理少数类样本。注意n_jobs-1只对多项式或 OvR 多分类生效对默认的 Multinomial Logistic Regression 没有加速效果所以不要因为看到 CPU 没跑满就怀疑代码有问题。4.2 用宏平均 F1 而不是准确率评估类别不平衡会让准确率虚高如果 15 类中有 2 类占了 60%无脑预测这两类也能拿到 60% 的准确率。所以只看accuracy_score会误判模型好坏。from sklearn.metrics import classification_report y_pred model.predict(X_val) print(classification_report(y_val, y_pred, target_namesclass_names))输出的macro avg是每个类别的 F1 简单平均它给少数类同样的权重数值低于准确率时说明少数类被牺牲了。weighted avg则按样本量加权更接近真实业务感受。头条数据集上的合理基线是宏平均 F1 在 0.78 到 0.84 之间具体数值取决于你保留了多少字段以及验证集切分方式。如果weighted avg明显高于macro avg说明模型对高频类拟合得更好少数类仍有提升空间。4.3 参数组合与调优方向参数搜索范围说明max_features5000 ~ 50000太小欠拟合太大过拟合ngram_range(1,1) / (1,2) / (1,3)头条标题较短(1,2) 性价比最高sublinear_tfTrue / False对长正文有效单用标题时影响小C0.1 ~ 10逻辑回归正则强度class_weightNone / balanced少数类必须开调参时可以先用GridSearchCV在小样本上跑一遍确定量级后再全量搜索。不要把TfidfVectorizer的token_pattern改成纯数字否则会把“2019”和“2020”拆成一堆数字特征对文本分类没有帮助。得到满意的验证集分数后用joblib.dump(model, toutiao_text_clf.joblib)保存模型。之后的预测流程里只要把用户输入做同样的 jieba 分词再调用model.predict就能返回类别。import joblib joblib.dump(model, toutiao_text_clf.joblib) loaded_model joblib.load(toutiao_text_clf.joblib) new_text .join(jieba.lcut(国足今晚迎战日本队)) pred_id loaded_model.predict([new_text])[0] print(le.inverse_transform([pred_id]))这里joblib.dump会把整个 Pipeline 连同 TF-IDF 词表一起保存加载后不需要重新训练。预测时输入必须是字符串列表所以new_text用中括号包了一层。le.inverse_transform把模型输出的连续编号还原成原始label_id之后再用label_name_map转成中文类别名。5. 用“去掉关键词”验证头条文本分类模型是否真的学到了语义模型在验证集上的分数高不代表它学到了新闻语义。头条数据集的url和keywords字段里存在明显的标签泄漏URL 中的栏目名以及关键词本身常常直接透出分类结果。如果训练时把这些字段放进特征模型会依赖捷径部署时一旦拿不到这些字段分数会断崖下跌。5.1 单独用关键词训练测试泄漏程度from sklearn.model_selection import cross_val_score import numpy as np X_keywords df[keywords].fillna() kw_model LogisticRegression(max_iter1000, class_weightbalanced) scores cross_val_score(kw_model, X_keywords, y, cv3, scoringf1_macro) print(np.mean(scores))如果单独用关键词做特征得到的宏平均 F1 超过了 0.85说明关键词字段几乎是标签的投影。此时应回到第 4 章把df[text]里的keywords部分去掉只用标题和正文训练模型再对比分数。通常去掉关键词后,宏平均 F1 会下降 5 到 10 个百分点这是正常现象真正危险的是去掉后分数几乎不变那说明特征里还隐藏着更隐晦的泄漏源例如 URL 中的频道目录。5.2 抽取预测错误样本做人工校验自动化指标之外人工看错误样本是必要的。下面这段代码从验证集中挑出预测错误的样本输出标题、真实类别和预测类别。import random random.seed(2024) mis_idx [i for i, (t, p) in enumerate(zip(y_val, y_pred)) if t ! p] sample_idx random.sample(mis_idx, min(30, len(mis_idx))) for i in sample_idx: original_row df.iloc[i] print( original_row[title], le.inverse_transform([y_val[i]])[0], -, le.inverse_transform([y_pred[i]])[0], )常见的错误是“体育”和“娱乐”互混例如一条“詹姆斯晒出全家福”的新闻模型判断为娱乐是合理的因为标题里没有出现任何体育赛事信息。这类错误不能靠增加模型容量解决只能靠补充知识或改为多标签分类。最终发布前把模型在去掉keywords后的验证集上重新评估保留那个更保守的分数作为对外宣称的性能指标如果两个分数差距过大就把第 4 章 Pipeline 里的text字段拼接逻辑改为只用title再重新走一遍训练流程。本文还有配套的精品资源点击获取
返回列表