尧图精选

THUCNews中文文本分类实战:数据下载、TF-IDF与BERT微调全流程

🕒 发布时间:2026/10/2 2:51:05 📁 来源:尧图网络
简介THUCNews中文文本分类数据集是一份包含84万篇新闻文档、覆盖14个新闻类别的中文语料库面向自然语言处理学习者和研究者适用于文本分类、主题建模、情感分析等场景也常用于验证分类模型在跨领域文本上的泛化效果。压缩包内共有46个文件总大小约3.93MB主要文件类型为27个Python脚本、6个shell脚本、4个TSV数据文件、4个JSON配置文件、3个TXT文本以及LICENSE和Markdown说明文档。其中Python脚本覆盖数据预处理、词典构建、模型训练、BERT微调与蒸馏等环节shell脚本提供一键运行入口JSON与TSV用于标签映射和数据组织TXT包含停用词等辅助信息整体结构清晰便于直接复现实验。目前已有937人学习下载。附带的多分类器测试工程包含了从传统机器学习到深度学习的多种实现模块能够帮助读者快速对比不同算法在THUCNews上的表现节省自行搭建环境与编写代码的时间中文NLP项目开发与算法评测均具参考价值。1. THUCNews 是什么84 万篇新闻、14 个类别中文文本分类的省心起点做中文文本分类多数人卡在第一步的不是算法而是数据。THUCNews 是清华大学整理的中文新闻语料84 万篇文档、14 个类别覆盖体育、财经、房产、家居、教育、科技、社会、时尚、游戏、娱乐、星座、汽车、彩票、股票。规模足够把模型喂饱标签是单标签平铺结构不需要处理多标签和层级关系是中文文本分类里最省心的带标注数据集。它解决的是最现实的问题不用花几周爬新闻、洗正文、打标签。解压、读取、分词、建模一路走下来就能验证你的分类方案在真实新闻文本上能拿多少分。毕业设计、算法对比、内容打标可行性预研三条路都用得上。适合刚接触中文 NLP 的学生也适合要评估 TF-IDF 和 BERT 哪条路线划算的工程师。下面从下载讲到微调新手能照抄老手直接看参数和坑。2. THUCNews 数据集下载与目录读取两种渠道、一个核对脚本2.1 数据集下载官网 zip 的 403 处理与 Hugging Face 镜像THUCNews 没有统一的 release 包发布渠道最常见分发方式是清华大学 NLP 组官网页面上挂一个 zip 包。直接拿浏览器或 wget 去抓翻车概率不低最典型报错是 403 Forbidden。原因是下载地址对 Referer 校验很严裸请求会被当成盗链挡掉。常见做法是带上 Referer 头伪装成从官网页面点过去的请求wget --refererhttp://thuctc.thunlp.org/ \ --user-agentMozilla/5.0 \ --tries5 --timeout60 \ -O THUCNews.zip \ 官网实际下载链接命令里的官网实际下载链接要你从官网页面右键复制每个发布窗口的路径都可能不一样别照搬旧教程里的历史地址。--tries5和--timeout60是给大文件下载兜底跨网络下载几百 MB 的 zip 断流是常态没有重试策略就是在浪费生命。下载完先核对 zip 大小跟页面标注对不上就直接删掉重来别等解压到一半才发现包是坏的。Hugging Face 上有人传过处理好的 THUCNews 镜像用 huggingface-cli 下载支持断点续传pip install -U huggingface_hub huggingface-cli download --repo-type dataset --resume-download HF上的THUCNews仓库名 --local-dir ./thucnews_hf我一般优先走镜像原因很朴素断点续传对大 zip 太重要了。但镜像仓库的内容版本可能和官网不一致有的镜像做了 10 类裁剪有的直接转成了 parquet 格式。下载前先看仓库说明里的样本量和类别数别把一个裁剪版当成 14 类全量版用后面算指标全是糊涂账。提示无论从哪条路下载拿到手先做文件数核对14 个文件夹、总文档数在 84 万量级再往后走避免用了一个残缺副本还以为是自己的模型不行。2.2 目录布局14 个类别文件夹与单篇文档读取解压后看到的不是一张 CSV而是一个规整的目录树14 个文件夹文件夹名就是类别名。每个目录里是大量 .txt 文件一篇新闻一个文件文件名是新闻编号正文就是新闻原文标题和正文连在一起没有额外字段。这种文件夹即标签的设计对新手很友好不需要解析任何标注文件也省掉了像 YOLO 图像数据集那种逐张画框、导标注的体力活标签系统天然可见。读文件用 pathlib 就够了from pathlib import Path import random data_root Path(THUCNews) cat_names sorted([ p.name for p in data_root.iterdir() if p.is_dir() and p.name ! __MACOSX ]) print(类别数:, len(cat_names)) print(类别:, cat_names) sample_file random.choice(list((data_root / cat_names[0]).glob(*.txt))) print(抽样文件:, sample_file.name) print(sample_file.read_text(encodingutf-8, errorsignore)[:200])sorted保证类别顺序稳定不依赖文件系统返回顺序这是实验可复现的前提后面 label2id 也依赖这份顺序。过滤__MACOSX是必须的macOS 解压 zip 会生成这个隐藏目录不滤掉它会被当成第 15 个类别样本量统计直接出错。read_text用errorsignore是为了容忍个别坏字节。抽样出来的文本大概率是一段新闻开头如果看到篇首带来源xxx之类的字样那是原始抓取留下的不用特特处理对分类影响有限。2.3 标签编码与生成器读取别一次把 84 万篇全塞内存把文件夹名转成数字 id是训练模型前必须做的一次映射顺便把读取逻辑写成生成器label2id {name: idx for idx, name in enumerate(cat_names)} id2label {idx: name for name, idx in label2id.items()} def iter_documents(data_root, label2id): 逐条 yield (text, label)避免一次性加载全部文档 for name, label in label2id.items(): folder data_root / name for fp in folder.glob(*.txt): text fp.read_text(encodingutf-8, errorsignore).strip() if not text: continue yield text, label这里的核心边界在iter_documents用生成器 yield一次只在内存里留一条样本。84 万篇全文先算一笔账每篇平均几百到两千字全量原始文本就是 12 GB再去掉分词结果、向量矩阵的中间变量16G 内存的机器很容在预处理阶段被 kill。label2id依赖上一步 sorted 的cat_names训练脚本和后续推理脚本必须共用同一份映射否则类别错位是迟早的事。之后做切分、做 PyTorch Dataset都从这生成器取数据。3. 20 分钟跑通中文文本分类基线jieba 分词、TF-IDF 与逻辑回归3.1 预处理三件套jieba 分词、停用词过滤、单字剔除THUCNews 是中文新闻和英文最大的区别是词之间没有空格分词是第一道工序。常见做法是 jieba速度快新闻领域词典覆盖也够。分词之后还有两件事去停用词、过滤无意义 token顺序不能乱。import jieba # stopwords.txt 用哈工大停用词表一行一个词 STOP_WORDS set() for line in open(stopwords.txt, encodingutf-8): word line.strip() if word and not word.startswith(#): STOP_WORDS.add(word) def cleaner(raw_text): # 压缩连续换行和首尾空白保留段落语义 text .join(raw_text.split()) tokens [] for w in jieba.cut(text): w w.strip() if not w or w in STOP_WORDS: continue # 单字非数字才丢弃彩票、股票、汽车里的数字是有用特征 if len(w) 1 and not w.isdigit(): continue tokens.append(w) return .join(tokens)逻辑说明raw_text.split()再 join是同时完成首尾去空白和把\r\n连续换行压成空格直接replace(\n, )会把段落边界弄丢。jieba.cut用默认精确模式切出来的词做停用词过滤。单字丢弃规则里我特意放行了数字——THUCNews 里彩票、股票、汽车这几类样本大量出现号码、价格、排量数字统一丢单字等于把类别强信号砍掉。参数说明停用词表选什么直接影响结果我一般用哈工大停用词表再自增少量词。新闻记者这类几乎每篇都有、没有区分度的词可以加进去但股票汽车这种和类别强相关的词绝对不能进停用词表。另外分词错误是常态不要追求切词完美TF-IDF 对切得碎一点的容忍度比你想象的高。3.2 TF-IDF 特征化词表规模、ngram 范围与 fit 边界分词之后要把文本转成向量。TF-IDF 是文本分类最稳的基线特征sklearn 一行搞定from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features50000, # 词表上限84 万篇不去重词汇轻松破百万 ngram_range(1, 2), # 单字词 双词短语保留“新能源汽车”这类组合 min_df2, # 至少在 2 篇文档出现过滤单篇私货词 max_df0.8, # 去掉 80% 文档都出现的词停用词漏网之鱼 sublinear_tfTrue, # 词频用 1log(tf)弱化长新闻的高频词权重 ) train_vec vectorizer.fit_transform(train_texts) # 只 fit 训练集 val_vec vectorizer.transform(val_texts) # 验证集只 transform参数边界max_features决定内存和训练时间5 万是中庸值想冲精度可以慢慢加到 10 万但收益递减。ngram_range(1, 2)对中文尤其重要纯 unigram 会把新能源汽车裂成新能源汽车语义碎掉加到 (1, 3) 在新闻这类规范文本上提升有限特征维度涨得却很快。max_df0.8是个经验值THUCNews 里跨类高频词去掉后分类器能少学一堆无关共性。这里最关键的是 fit/transform 边界vectorizer 只能在 train 上fit_transformval 和 test 只能transform。如果手滑在全体数据上 fit 了验证分数虚高到没有参考价值这是文本分类里最常踩的泄漏之一后面避坑章单独讲。3.3 切分与训练一段能直接复现的完整代码把预处理、切分、训练、评测串起来这就是第一个能跑的成绩单from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report # 生成器落地为列表便于切分内存紧张就只保留编号索引 all_texts, all_labels [], [] for text, label in iter_documents(data_root, label2id): all_texts.append(cleaner(text)) all_labels.append(label) X_train, X_val, y_train, y_val train_test_split( all_texts, all_labels, test_size0.2, stratifyall_labels, # 按类别比例分层切分防止小类被抽干 random_state42, ) vec TfidfVectorizer( max_features50000, ngram_range(1, 2), min_df2, max_df0.8, sublinear_tfTrue, ) train_vec vec.fit_transform(X_train) val_vec vec.transform(X_val) clf LogisticRegression(C2.0, max_iter500, solverliblinear) clf.fit(train_vec, y_train) print(classification_report(y_val, clf.predict(val_vec), target_namescat_names, digits4))逻辑说明train_test_split里stratifyall_labels是后悔药参数——THUCNews 各文件夹样本量不完全均等不做分层切分验证集里星座、彩票这类偏少的类可能只剩十几条F1 波动大到没法看。random_state42固定切分保证每次实验可比这是后面调参不产生幻觉的前提。参数说明LogisticRegression用solverliblinear配合中小规模稀疏矩阵收敛快C2.0是我在这个数据集上的常用起点。C 是正则强度的倒数太大特征拟合过头太小欠拟合。max_iter500避免默认迭代次数出收敛告警。这一套在我机器上 5 万条子集约 1 分钟跑完全量也只要十几分钟准确率在 92%94% 区间浮动。分数就是基线后面任何模型低于这个数字先怀疑数据处理不是模型的问题。想再快一点可以换 fastText 路线gensim 里封装好了训练更快但准确率略低适合先探路。提示这个基线分数是整条链路的体检报告。如果连 LR 都到不了 90%优先查 3.1 的停用词表和 3.2 的 fit 边界。4. 14 类样本分布与评测准确率之外用混淆矩阵和 F1 验收4.1 先统计 14 类的样本分布再定切分策略14 个文件夹的文件数决定了后面所有策略。不要凭印象直接统计counts {} for name in cat_names: counts[name] len(list((data_root / name).glob(*.txt))) total sum(counts.values()) for name in cat_names: print(f{name:6s} {counts[name]:6d} {counts[name] / total:8.2%}) print(总文档数:, total)这段代码的输出决定你后面要不要 stratify、要不要做类别加权。不同发布版本分布有差异做过均衡抽样的版本每个类在 5 万6 万量级原始版本里娱乐、体育这类热门频道明显偏多星座、家居偏少。拿到数据先跑这段把结果存成 JSON后面每次改实验都对着这份分布看别拿印象代替数据。按分布选策略参考这张表分布情况切分与训练策略适用场景各类接近均衡普通随机切分stratify 可有可无教学、基准确认少数类明显偏少stratify class_weightbalanced少数类是业务重点时少数类占比低于 1%重采样或数据增强回译、同义词替换细粒度分类任务只想快速验证每类抽 500010000 篇做子集机器内存小、先跑通流程我一般建议先抽子集把整条链路走通再上全量。84 万篇跑 LR 基线不贵但接深度学习全量训 TextCNN 或 BERT 就贵了。子集跑通全量只是改路径和 batch size 的事千万别一上来就 84 万篇全量开训翻车了都不知道往哪查。4.2 混淆矩阵自动找出误判重灾区准确率在 14 类问题上会骗人。分布偏向某几类时全猜多数类也能有不错准确率。所以至少要看 macro-F1 和混淆矩阵import numpy as np from sklearn.metrics import confusion_matrix pred clf.predict(val_vec) cm confusion_matrix(y_val, pred) # 按行归一化再置零对角线剩下来的就是误判比例 cm_norm cm.astype(float) / cm.sum(axis1, keepdimsTrue) np.fill_diagonal(cm_norm, 0) confusions [] for i in range(len(cat_names)): for j in range(len(cat_names)): if i ! j and cm_norm[i][j] 0.01: confusions.append((cm_norm[i][j], cat_names[i], cat_names[j])) confusions.sort(reverseTrue) for ratio, true_cat, wrong_cat in confusions[:5]: print(f{true_cat} 被误判为 {wrong_cat}: {ratio:.2%})逻辑说明按行归一化后每行代表该类别里有多少比例被分到了哪一类对角线置零后留下的就是错分去向。THUCNews 上常见的重灾区是财经被误判成股票、娱乐和时尚互相串、社会和教育互相串因为这几对用词高度重叠。如果你的误判对和这个不太一样优先怀疑分词或停用词处理而不是数据集本身。4.3 建议的调参顺序词表、ngram、正则强度这个数据集调参顺序比数量重要。我的固定顺序先动分词和停用词12 个点的波动多半在这。检查误判对比如财经和股票串得厉害看看股票基金证券这些词有没有被误加进停用词表。再动max_features2 万加到 5 万、10 万观察验证集准确率曲线收益变平就停。然后动ngram_range(1,1) 到 (1,2) 通常能涨 1 个点以上(1,3) 在新闻语料上提升有限。最后动 C在 [0.5, 1.0, 2.0, 4.0] 上小网格搜索。判断过拟合还是欠拟合train 高、val 低是过拟合把 C 调小或把min_df调大train、val 都低是特征不够升max_features、加 ngram。这里的低没有绝对数字以你自己的基线为参照。比如 LR 到了 93%deep 模型只有 91%那就回到数据侧找问题别在模型上硬耗。5. 避坑THUCNews 从下载到评测的 5 个高频问题与排查5.1 下载 403 或断流Referer 与镜像续传现象wget 或浏览器下载 zip 报 403 Forbidden或者文件下载到 70% 断掉重试还是断在同一个位置。原因官网下载地址对 Referer 头有校验裸请求被当盗链拦截文件体积大弱网环境长连接容易被中间设备掐断。解决下载命令带--refererhttp://thuctc.thunlp.org/和--user-agent伪装成正常浏览请求优先走 Hugging Face 镜像用--resume-download断点续传下载完成后核对 zip 大小和解压后的文件总数。残缺包无论多可惜都要删掉重下留着只会污染后续所有实验。5.2 全量读取内存爆炸生成器和子集现象脚本在读取阶段内存飙升进程被系统杀掉或者电脑直接卡死。原因一次性把 84 万篇文本、分词结果、特征向量全部 load 进内存TF-IDF 阶段稀疏矩阵还会膨胀几十倍。解决文件读取用 2.3 节的生成器逐条 yieldTF-IDF 只保留稀疏矩阵不要.toarray()转稠密先抽每类 5000 篇跑通链路。另外TfidfVectorizer在 84 万篇上 fit 本身要几分钟属正常现象不是死机学会看日志而不是盯着光标转圈。5.3 空文件、乱码和异常字符现象某篇文档读出来是空的正文里有不可见字符分词结果里出现一堆孤立符号。原因THUCNews 来自线上新闻抓取个别页面抓取不完整形成空文档文件编码也并非全部是干净的 UTF-8。解决read_text统一加errorsignore空文本直接跳过分词前把连续空白压缩成一个空格。如果发现某个类别的空文件特别多统计一下占比超过 1% 就要考虑换镜像版本或换个下载来源。5.4 验证集泄漏特征化之前就先切分现象验证集分数高得离谱比如 99%一换到生产或新数据就崩到 85%。原因TfidfVectorizer或词表在全体数据上 fit验证集的词汇和 idf 统计已经进了特征学习模型等于提前见过验证集的信息。解决切分永远在特征化之前vectorizer 只fit_transform训练集验证集只transformjieba 的自定义词典同理只能用训练集统计出来的词表。这条适用于任何做统计特征的模型是文本分类里最常见的高分陷阱。5.5 实验不可复现种子、目录顺序与随机性现象同一个脚本两次运行结果不一样深度模型训练更是每次飘一点。原因train_test_split没固定 random_state文件夹迭代顺序依赖文件系统返回深度学习里 GPU 的非确定性算子没有禁用。解决文件夹名先 sorted 再做 label2id所有切分固定random_state42PyTorch 脚本开头设置全局种子并打开cudnn.deterministic。改任何参数之前先确认同一份代码两次跑的结果一致否则后面的调参对比全是幻觉。6. 进阶用预训练模型微调 THUCNews盯住最差类别 F16.1 把 THUCNews 组织成 PyTorch 数据集LR 基线跑到 93% 后想再往上走常见做法是用中文预训练模型微调效果拔尖的是bert-base-chinese想省显存可以选hfl/rbt3。这一步把前面的生成器接上一个 Dataset 类from torch.utils.data import Dataset from transformers import AutoTokenizer import torch tokenizer AutoTokenizer.from_pretrained(hfl/rbt3) class ThucNewsDataset(Dataset): def __init__(self, texts, labels, max_len128): self.texts texts self.labels labels self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): enc tokenizer( self.texts[idx], truncationTrue, max_lengthself.max_len, paddingmax_length, return_tensorspt, ) return { input_ids: enc[input_ids].squeeze(0), attention_mask: enc[attention_mask].squeeze(0), labels: torch.tensor(self.labels[idx]), }max_len128是性价比起点新闻的主题和结论基本在开头128 个 token 足以区分大部分类别。如果混淆矩阵里长文档类还在打架把max_len提到 256但训练时间也跟着涨。6.2 微调参数与训练曲线观察用 transformers 的 Trainer 省去手写训练循环from transformers import ( AutoModelForSequenceClassification, TrainingArguments, Trainer, ) from sklearn.metrics import f1_score, accuracy_score model AutoModelForSequenceClassification.from_pretrained( hfl/rbt3, num_labels14 ) def compute_metrics(eval_pred): logits, labels eval_pred preds logits.argmax(axis-1) return { acc: accuracy_score(labels, preds), f1: f1_score(labels, preds, averagemacro), } args TrainingArguments( output_dir./thucnews_bert, num_train_epochs2, per_device_train_batch_size64, learning_rate2e-5, warmup_ratio0.1, weight_decay0.01, evaluation_strategysteps, eval_steps500, save_strategyepoch, fp16True, metric_for_best_modelf1, load_best_model_at_endTrue, )参数说明learning_rate2e-5是中文分类微调的常见起点别用默认的 5e-5 甚至更大warmup_ratio0.1让前 10% 步数学习率线性爬升fp16只在支持的显卡上开CPU 跑就去掉。全量 84 万篇训 BERT 太贵我一般每类抽 1 万篇做验证跑通后再决定要不要加钱上全量。6.3 两个验证技巧第一个技巧盯最差类别 F1。BERT 微调后总分可能只比 LR 高 23 个点但逐类看 F1提升几乎集中在财经/股票这类纠缠类别上。如果最差类别的 F1 没动说明模型只是整体变强没解决真正的分界问题。第二个技巧保存模型按 F1 而不是准确率。metric_for_best_modelf1配合load_best_model_at_endTrue避免出现准确率最高但少数类别崩掉的歪模型。验证集上再做一次真实分布核对确认预测结果按业务价值的分布是对的再谈上线。我现在拿到 THUCNews 这种数据集第一件事永远是先统计分布、定切分、跑 LR 基线之后才谈要不要上预训练模型。见过太多团队在 BERT 上烧了几天算力最后发现掉点原因是验证集泄漏那就太亏了。希望你先跑通基线再进阶让这份 84 万篇的语料按你的预期出货希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →