十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

THUCNews数据集实战:从TF-IDF到BERT的中文文本分类全流程

THUCNews数据集实战:从TF-IDF到BERT的中文文本分类全流程 简介中文自然语言处理中文本分类是一项基础而关键的任务其效果高度依赖数据质量与特征表达。对于新闻类语料如何从原始文本中构建干净、均衡的训练集并选择恰当的分类模型是许多开发者面临的共同挑战。THUCNews作为清华大学发布的大规模中文新闻语料包含84万篇文档、14个类别为中文文本分类提供了一流的实验基准。本文从数据清洗、编码统一等预处理步骤讲起系统对比TF-IDF加线性分类器、TextCNN以及BERT预训练模型在新闻分类中的表现与适用场景同时详解类别不均衡、标签错位等实践中的常见陷阱。无论你是刚入门NLP还是需要快速验证中文文本分类方案这套从数据到模型再到部署的完整路径都能为你提供直接可复用的工程经验。 搞中文文本分类的人大概率逃不过THUCNews这个数据集。我最早接触它的时候手头是一堆从各种渠道收集来的新闻txt标题和正文混在一起编码也是乱的搞得我一度以为中文文本分类的门槛全花在数据清洗上。后来用THUCNews把整套流程走通才意识到一个干净、规模够大的中文数据集到底有多重要。THUCNews是清华大学实验室整理发布的大规模中文新闻语料包含84万篇文档覆盖14个类别。它既能用来跑传统机器学习流程也适合作为深度学习文本分类的入门数据。这篇文章我会拆开聊清楚几个问题这个数据集的价值和边界在哪、从原始文件到能训练的数据集要经过哪些处理、用TF-IDF和预训练模型做分类各自该怎么落地以及实操中那些文档里不会写的坑。如果你正在准备中文NLP方向的项目或者手头有一批新闻类语料想快速验证分类效果这篇内容应该能帮你省下不少时间。1. THUCNews的价值边界它适合做什么不适合做什么1.1 84万篇文档到底意味着什么很多人对“84万篇”没有体感。拿常见数据集对比一下IMDB影评是5万条THUCNews的规模是它的十几倍今日头条短文本数据集大概在38万条左右THUCNews也明显更大。84万篇文档如果全部打印出来相当于一个中等规模图书馆的过刊数量。每个类别平均下来大约6万篇但这不是严格平均的。原始语料来自新闻频道不同频道的内容量天然不同所以下载后一定要先做类别分布统计而不是想当然认为每个类都是6万篇。从文本长度看新闻正文比短文本要长得多。平均一篇可能几百到上千字这对后续做截断、做特征提取都有影响。如果你准备用BERT这类模型输入长度限制会直接决定你要不要做“标题首句”的截断策略后面我会专门说。1.2 用它练手文本分类的优势中文NLP的数据集资源一直比英文少尤其是高质量、规模较大的监督语料。THUCNews解决了这个问题类别体系明确、数据量大、来源是新闻文稿所以语言相对规范。我用下来的感受是这个数据集非常适合做几类事情熟悉中文文本分类的完整流程从数据清洗、分词、特征工程到建模评估。对比不同分类模型的效果差异同一个数据集上跑TF-IDF、TextCNN、BERT结论很有参考价值。作为预训练模型的“起步语料”用它训练一个通用新闻分类器再迁移到垂直领域。对刚入门的人来说THUCNews最大的价值是让你不用把时间花在数据获取上把精力重点放在模型和处理流程上。1.3 常见的三个误区误区一是“数据量大就直接上深度学习”。84万篇看着多但原始txt文件需要清洗和统一编码有些下载源还会混入HTML标签和重复文章。不预处理直接喂给模型轻则损失精度重则训练时直接报错。误区二是“这个数据集可以解决所有新闻分类问题”。THUCNews的类目是频道层面的粗分类比如体育、财经、房产。如果你要做细粒度的“体育新闻里的足球/篮球/网球”这14个类目不够用。领域差异也很明显用THUCNews训练出来的模型直接扔到医疗新闻或法律文书上效果一定会崩。误区三是“只看准确率就觉得自己模型练好了”。新闻数据类别之间有重叠比如“科技”和“教育”都可能有“AI进校园”这类报道混淆矩阵比准确率更能说明问题。这点我在第5章会展开。2. 从压缩包到训练集预处理全链路实录2.1 数据下载与目录结构THUCNews官方原版可以从清华NLP实验室的THUCTC项目主页找到下载入口GitHub上也有很多镜像仓库。考虑到下载的人多建议用支持断点续传的工具比如aria2。# 以aria2为例下载压缩包 aria2c -x 8 -s 8 -d ./data 你的THUCNews下载链接 # 解压 unzip THUCNews.zip -d THUCNews解压后你会看到这样的目录结构THUCNews/ ├── 体育/ │ ├── 1.txt │ ├── 2.txt │ └── ... ├── 财经/ ├── 房产/ ├── 教育/ └── ...每个txt文件是一篇新闻文档。部分社区重新整理的版本会把所有文本合并成一行一条的格式比如train.txt每行是“标签\t文本内容”。拿到手先看README不同来源的结构差异很大别上来就写代码。2.2 清洗、去重与编码统一这是整个项目里最繁琐、也最容易被忽略的一步。我第一次跑的时候直接读文件结果在Windows上导出的txt用GBK编码Python读取直接抛UnicodeDecodeError所以第一步永远是统一编码。from pathlib import Path import chardet def read_text(path): raw Path(path).read_bytes() enc chardet.detect(raw)[encoding] or utf-8 return raw.decode(enc, errorsignore)用chardet做编码探测不是万无一失但比默认UTF-8硬读强得多。建议转换后统一存成UTF-8后面所有步骤都省心。文本清洗要处理的东西包括HTML标签、乱码字符、全角半角空格、连续的换行符。新闻语料里经常夹带“function(){}”之类的脚本残留直接去掉。示例如下import re def clean_text(text): text re.sub(r[^], , text) text re.sub(r[a-zA-Z0-9_], , text) # 按需过滤英文和数字 text re.sub(r\s, , text) return text.strip()去重这一步不能省。大数据集里重复新闻很常见尤其是转载类内容。简单做法是MD5去重seen set() unique_texts [] for text in all_texts: clean clean_text(text) if not clean: continue md5 hashlib.md5(clean.encode(utf-8)).hexdigest() if md5 not in seen: seen.add(md5) unique_texts.append(clean)MD5只能去掉完全重复的文本。如果两篇新闻只改了标题或首段MD5就无效了需要用到simhash或者MinHash做相似去重。我的建议是先用MD5快速清洗如果发现准确率上不去再考虑相似去重。2.3 类别分布与数据划分清洗完之后先统计每个类别的文档数量。这一步能帮你判断数据是否均衡以及后续应该用准确率还是F1作为主要指标。from collections import Counter counter Counter() for category in categories: counter[category] count_files(category) total sum(counter.values()) for cat, num in counter.most_common(): print(f{cat}: {num} ({num/total:.2%}))如果发现某个类别的样本特别少比如“彩票”只有2万篇其他类都是5万篇以上那就不要用全局准确率作为唯一指标需要重点关注少数类的召回率。数据划分我推荐按8:1:1的比例做分层抽样保证训练集、验证集、测试集里的类别分布和全集一致。用sklearn的train_test_split可以很方便地实现from sklearn.model_selection import train_test_split train_texts, temp_texts, train_labels, temp_labels train_test_split( texts, labels, test_size0.2, stratifylabels, random_state42 ) val_texts, test_texts, val_labels, test_labels train_test_split( temp_texts, temp_labels, test_size0.5, stratifytemp_labels, random_state42 )注意一定要设置stratifylabels否则类别分布会被打乱影响评估结果的可靠性。3. 第一条基线TF-IDF LinearSVC跑通全流程3.1 为什么先跑传统模型很多刚接触深度学习的人上来就把BERT拉满结果显卡不支持、训练时间几小时、最终效果还未必理想。我自己的习惯是先用一套传统方法在10分钟内跑出一个基线再决定值不值得上更重的模型。TF-IDF加线性分类器在文本分类上效果并不差尤其在THUCNews这种类目边界相对清晰的新闻数据上LinearSVC能跑到0.93以上的准确率。这个基线有几个作用验证数据清洗和划分是否正确。给后续模型提供一个对比基准。排查标签和数据是不是对得上。3.2 分词、特征与流水线中文文本不像英文天然有空格分隔所以分词是绕不开的步骤。最常用的工具是jieba。import jieba from sklearn.feature_extraction.text import TfidfVectorizer def tokenize(text): return .join(jieba.cut(text)) train_cut [tokenize(t) for t in train_texts] test_cut [tokenize(t) for t in test_texts] vectorizer TfidfVectorizer(max_features100000, ngram_range(1, 2)) X_train vectorizer.fit_transform(train_cut) X_test vectorizer.transform(test_cut)max_features设到10万是一个比较稳妥的选择既能控制内存又能保留足够的词和双词组合。ngram_range(1,2)可以把“投资基金”这种组合词考虑进来对财经类文本很有帮助。线性SVC用sklearn.svm.LinearSVC即可。注意这个模型对特征尺度敏感但TF-IDF特征本身已经是归一化的所以可以直接训练。from sklearn.svm import LinearSVC from sklearn.metrics import classification_report model LinearSVC(C1.0, max_iter2000) model.fit(X_train, train_labels) y_pred model.predict(X_test) print(classification_report(test_labels, y_pred, zero_division0))C是正则化强度的倒数默认1.0在大多数情况下表现不错。如果分类结果不理想可以试着调小到0.5或者调大到2.0一般不需要太精细地调。3.3 基线结果与经典混淆拿THUCNews的其中一个子集约20万训练样本实测TF-IDF LinearSVC的准确率通常在0.93到0.95之间。不同来源的具体结果有差异但总的来说这个基线是“不丢人”的。看classification_report时我一般会特别关注几个容易混淆的类别对混淆对原因科技 vs 教育“AI进课堂”“在线教育平台”这类报道在语义上重叠度高财经 vs 股票股票本来就是财经里最热的话题之一娱乐 vs 社会明星社会新闻经常跨类别如果你发现自己的混淆矩阵里这些类互相串不用太担心这是数据本身的特点不是模型坏了。4. 深度学习路线从TextCNN到预训练模型4.1 TextCNN为什么在中短文本上依然能打TextCNN是2014年提出来的模型结构非常简单词向量后接几组不同宽度的卷积核再接一个全局池化和全连接分类层。尽管后来有了各种注意力机制和TransformerTextCNN仍然没有被完全淘汰原因在于它有两个优势速度快训练成本低。能捕捉文本中的局部n-gram特征。THUCNews的新闻正文虽然长但很多类别的核心信息集中在标题和开头几段。用TextCNN加一个标题截断策略反而能去掉大量噪声效果和吃满整篇文本的复杂模型差别不大。4.2 词表构建与训练流程这里我用PyTorch写一个非常简化的TextCNN训练流程不做花哨处理重点是帮你理解结构。import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_size200, num_classes14, kernel_sizes[2, 3, 4], num_filters128): super().__init__() self.embedding nn.Embedding(vocab_size, embed_size, padding_idx0) self.convs nn.ModuleList([ nn.Conv1d(embed_size, num_filters, k, paddingk // 2) for k in kernel_sizes ]) self.fc nn.Linear(len(kernel_sizes) * num_filters, num_classes) self.dropout nn.Dropout(0.5) def forward(self, x): x self.embedding(x) # [B, L, embed] x x.transpose(1, 2) # [B, embed, L] conv_outs [torch.relu(conv(x)) for conv in self.convs] pool_outs [torch.max_pool1d(co, co.size(2)).squeeze(2) for co in conv_outs] x torch.cat(pool_outs, dim1) x self.fc(self.dropout(x)) return x训练时需要注意几点输入长度先做截断我建议取前200到300个token新闻标题和导语的信息密度通常最高。词表大小不要设太大8万到10万就够用了否则Embedding层会吃掉大量显存。batch size从64开始如果显存充足可以提到128。用交叉熵损失Adam优化器学习率1e-3。一个小的TextCNN在单张消费级显卡上训练几十万条样本只需要几十分钟到一两小时是非常适合做迭代实验的模型。4.3 用预训练模型做迁移学习如果你需要发论文或者追求更高精度直接用中文预训练模型做微调是更常见的选择。HuggingFace的transformers库把这个过程封装得很简单。from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name hfl/chinese-roberta-wwm-ext tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained( model_name, num_labels14 )数据集要做成Dataset对象每条样本包含input_ids、attention_mask、label。核心代码如下from transformers import Trainer, TrainingArguments training_args TrainingArguments( output_dir./thucnews_bert, num_train_epochs3, per_device_train_batch_size16, per_device_eval_batch_size32, learning_rate2e-5, evaluation_strategyepoch, save_strategyepoch, fp16True, # 有GPU时开启混合精度 logging_dir./logs, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_datasetval_dataset, ) trainer.train()BERT类模型在THUCNews上做到0.97以上的准确率是不奇怪的。但代价是训练时间明显增加显存占用至少需要8GB以上batch size 16加长文本。如果机器不够建议用MiniRBT或者albert-small-v2这类轻量模型精度损失不大速度提升明显。4.4 三种方案的实战对比我习惯把传统模型、TextCNN、预训练模型放一张表里对比方便按资源选型方案准确率参考训练耗时显存要求适用场景TF-IDF LinearSVC0.93-0.95几分钟无快速验证、CPU部署、教学演示TextCNN0.95-0.961-2小时低2GB大规模数据训练、线上低延迟推理BERT微调0.97数小时高8GB精度优先、离线分析、强特征迁移注意这个准确率是我在特定子集上的实测参考值你用自己的划分方式跑出来可能会略有浮动。重要的是选型逻辑先定你的约束是“时间”“资源”还是“精度”再选方案。5. 实操中必须避开的几个坑来自真实翻车经历5.1 编码问题你看到的“乱码”不一定是模型的问题有个朋友跑THUCNews预处理完成后准确率只有0.6排查了很久发现是数据读取的时候部分文件的编码识别错误大量文本变成了乱码。训练集里塞了一堆“锟斤拷”模型当然学不到东西。解决办法有两个方向一是读取时用chardet做编码探测二是提前做一次全量转码把所有文件统一成UTF-8。我强烈建议先转码再训练不要在每次读取时都做动态探测否则数据加载会成为瓶颈。from pathlib import Path for path in Path(THUCNews).rglob(*.txt): raw path.read_bytes() try: text raw.decode(utf-8) except UnicodeDecodeError: text raw.decode(gbk, errorsignore) path.write_text(text, encodingutf-8)5.2 标签映射错位一个隐蔽但致命的bug很多分类代码会这样写categories os.listdir(data_dir) categories.sort() label_map {c: i for i, c in enumerate(categories)}这个写法本身没问题问题在于如果某个目录名称包含中文sort()的排序结果在不同平台可能不一样。比如在macOS和Linux上中文排序结果可能不同导致同一个类别在两个环境里对应的label id不一致。更危险的是如果你提前用固定列表定义了类别比如[体育, 财经, 房产, ...]但目录扫描时多出一个隐藏文件或者漏了一个目录后面所有标签都会错位。我现在的做法是显式维护一个类别配置文件category_map { 体育: 0, 财经: 1, 房产: 2, # ... }然后再去读目录做一遍校验确保读到的类别集合和配置文件完全一致不一致就抛异常。这样能把bug扼杀在数据加载阶段。5.3 GPU显存溢出不是所有论文方案都能落地BERT微调时最常见的报错是CUDA out of memory。一个简单做法是减小max_length比如从512降到128。THUCNews的新闻正文虽然长但只取前128个token的标题和开头文本对分类任务来说损失并不大。这是最有效的省显存手段。另一个技巧是开启混合精度训练也就是fp16True。在RTX 30系以上显卡上这个操作能让显存占用几乎减半训练速度还更快。如果还是溢出用梯度累积来模拟更大的batchtraining_args TrainingArguments( per_device_train_batch_size8, gradient_accumulation_steps4, # 等效batch_size32 ... )实际batch size从8到32相当于每次只更新一次参数但梯度来自4个batch。效果上接近一次性用32条样本训练显存却只占原来的1/4。5.4 只看准确率会被数据分布欺骗THUCNews整体类别分布还算均匀但如果你抽了子集尤其是某些小众类别样本特别少时模型的准确率可能看起来很高实际对少数类一个都没分对。比如14个类别中某一个只有1%样本模型全部预测其他类准确率也有99%。所以我的评估清单里永远包含三样东西分类报告classification_report看每个类别的precision、recall、f1。混淆矩阵找出哪些类互相混淆。按类别统计的样本量和预测正确数。只有把这些都看一遍才算真正知道模型“会什么”和“不会什么”。6. 从数据集走向应用如何调整成你自己的分类器6.1 用THUCNews预训练再对垂直领域微调一个很常见的需求是手头有一批某垂直领域的新闻但标注量只有几千条直接训练模型效果不行。我的做法是先用THUCNews做“预训练任务”再在垂直领域上做微调。具体来说先用84万篇语料训练一个TextCNN或者微调一个BERT基础模型让模型先学会“新闻语言”的基本模式。然后把你的垂直领域数据拿过来冻结前面若干层只微调最后几层。这样做的好处是小数据不容易过拟合。训练速度快。比从零训练效果好很多。对BERT来说更简单的做法是在HuggingFace的通用中文预训练模型基础上拿你的垂直领域语料继续做masked language model训练然后再做分类微调。这个过程叫领域自适应预训练效果通常比直接分类微调要稳。6.2 没有标注数据时的几条出路很多人问过我连几千条标注都没有怎么办几个可行的思路第一弱监督。用关键词和规则自动生成一批弱标签数据。比如包涵“涨停”“基金净值”就标为“财经”虽然噪声大但作为冷启动数据够用。第二伪标签。先在有标注的小数据集上训练一个模型然后用这个模型预测无标注数据把置信度高的结果当作新训练数据。这个方法要控制置信度阈值别把低质量预测也吞进去。第三主动学习。先训练一个模型找出模型最不确定的样本交给人工标注如此迭代。这个过程可以把标注量压缩到随机抽样的一半甚至更低。6.3 部署一个简单的分类API模型训练完后部署成服务是另一个环节。我常用FastAPI加HuggingFace的pipeline快速包一个接口。from fastapi import FastAPI from pydantic import BaseModel from transformers import pipeline app FastAPI() classifier pipeline(text-classification, model./thucnews_bert, tokenizer./thucnews_bert) class Item(BaseModel): text: str app.post(/predict) def predict(item: Item): result classifier(item.text[:200])[0] label result[label] score result[score] return {label: label, score: score}启动方式很简单uvicorn api:app --host 0.0.0.0 --port 8000测试一下curl -X POST http://127.0.0.1:8000/predict \ -H Content-Type: application/json \ -d {text: 央行宣布降准银行板块集体走高}返回结果就是“财经”类置信度一般在0.9以上。6.4 后续可以怎么扩展THUCNews的14类体系只是起点。你可以把它当成一个试验场继续做几件事多标签分类一篇新闻可能同时涉及“科技”和“教育”把单标签改成多标签需要用BCEWithLogitsLoss替代CrossEntropyLoss。长文本建模新闻正文过长需要尝试层次注意力网络或者Longformer类模型。标题与正文的融合有些新闻标题高度概括正文则含有丰富上下文用双塔结构分别编码再融合往往比简单拼接效果好。如果你打算走得更远可以自己写爬虫采集行业新闻按同样的清洗、预处理、训练流程训练出一个属于你自己业务场景的分类器。到了这一步THUCNews就不再是一个需要“用完即弃”的数据集而是一套可复用的方法论样板。最后分享一个我自己的习惯不管用什么数据集先不急着写复杂模型前两三天时间应该全部花在数据体检上。看看类别分布、跑几条样本人工读一遍、确认标签映射没有错位。这些工作做得越扎实后面模型训练越顺畅。THUCNews最大的价值正是给你提供了一个能反复检验这套流程的优质语料。本文还有配套的精品资源点击获取
返回列表