news 2026/9/18 23:01:33

BERT文本分类在经济研究中的应用:论文复现全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BERT文本分类在经济研究中的应用:论文复现全流程实战

先说明一下,我这边没有找到金星晔等(2024)《经济研究》这篇论文的原文全文,但你给的标题信息已经足够定位到这是哪一类工作:用BERT这类预训练语言模型做经济学文本分类,而且作者团队把整个流程(数据标注、模型微调、预测、稳健性检验)完整走通了。这类文章在经管顶刊里属于比较前沿的方法应用型研究,能发出来本身就说明方法论框架是经过严格审稿的。

我下面这篇博文,会从"经管研究者复现这篇论文"的角度出发,把你需要掌握的核心环节全部拆开讲清楚。包括为什么用BERT而不是更简单的词频方法、时间序列切分的细节逻辑、数据标注环节的实操要点、Pytorch代码层面的关键实现,以及我在复现过程中踩过的坑。你拿到手之后,照着这篇文章的思路一步步做,是能把整个项目跑通的。


1. 这篇论文到底做了什么:从方法到复现的整体认知

先说清楚这篇论文的方法论内核,不然你跑去GitHub上找代码也会看得一头雾水。

金星晔等(2024)这篇《经济研究》文章,核心做的事情是:用预训练语言模型BERT,对大规模中文经济文本做自动分类,从而构造出传统方法难以构造的指标变量。在经济学研究里,文本数据一直存在"量太大、人工标注太贵"的瓶颈,过去大家常用的做法是词典法——就是预先定义一个关键词列表,然后统计每篇文本里命中了多少关键词,用这个频率作为度量指标。这种做法的问题很明显:中文表达太灵活,同一个概念在不同语境里说法完全不一样,词典法会漏掉大量表达,而且无法处理反讽、否定、条件等复杂语义。

这篇论文的方法路径是这样的:先拿到一篇基于人民日报语料预训练的中文BERT模型,然后把样本集按时间顺序切成互不重叠的两部分。用较早时期的数据做人工标注,标注出少量样本的类别标签,用这些带标签的数据去微调BERT模型。微调完成之后,再用训练好的模型去预测较新时期的文本,这样就完成了对大规模历史文本的自动分类。

这个设计最精彩的地方在于时间切分。之所以必须按时间切而不是随机切,是因为经济文本的语义分布是随时间漂移的——2008年的"稳健货币政策"和2020年的"稳健货币政策"含义可能不同,政策表述风格也在变化。如果随机切分,训练集和预测集的文本分布太接近,模型效果看起来会很好,但实际应用时会严重过拟合。按时间切分之后,训练集全是早期文本,预测集全是后期文本,这模拟了真实的应用场景,也让模型暴露在分布漂移之下,效果更可信。

复现这篇论文,你需要准备的硬件门槛其实不高。BERT base模型参数量在1.1亿左右,对一块12GB显存的GPU来说完全够用,甚至CPU做推理也能跑,只是慢一些。我的建议是,有GPU最好,没有GPU就用CPU先把流程跑通,等理解了全流程再考虑上GPU。

从复现角度看,整个项目可以拆成五个核心环节:数据准备与清洗、人工标注方案设计、BERT微调代码实现、模型预测与指标构造、稳健性检验。接下来我逐个环节展开。

2. 环境准备:Python、Pytorch和BERT模型的三件套搭配

复现这类项目,第一个容易翻车的不是模型代码,而是环境配置。我见过太多人卡在装Pytorch这一步,装了三天还没跑起来,以为是自己的问题,其实是你用的安装命令和硬件环境不匹配。

2.1 基于Anaconda的Python环境搭建

如果你之前没装过Anaconda,先去官网下载Anaconda最新版本,装的时候一路默认就行。装完之后,打开命令行(Windows下是Anaconda Prompt,Mac和Linux下是终端),创建一个独立的虚拟环境,名字随便起,我用的是bert_repro

conda create -n bert_repro python=3.9 conda activate bert_repro

这里选Python 3.9是我实测下来兼容性最稳的版本。Python 3.11、3.12在某些旧版本的Pytorch和Transformer库下会有兼容性问题,没必要给自己添堵。3.9虽然不算最新,但所有深度学习库对它的支持都是最完整的。

2.2 Pytorch安装:先确认你的CUDA版本

Pytorch的安装是整个项目里最容易出问题的环节。很多人一上来就跑pip install torch,结果装出来的是CPU版本,后来又得重装。正确做法是先去Pytorch官网找安装命令,官网会根据你的操作系统和CUDA版本自动生成对应的安装命令。

关键是要搞清楚你的显卡支持哪个CUDA版本。在命令行输入nvidia-smi,看右上角的CUDA Version,那个是驱动支持的CUDA版本,不是说你必须装那个版本,而是说你的Pytorch CUDA版本不能高于它。

举个例子,如果你的驱动显示CUDA 12.6,那就放心装cu121cu118版本的Pytorch,兼容性最好;如果你的驱动比较老,显示CUDA 10.2,那就得装旧版Pytorch,不能硬上新版。

CPU版本的安装命令也同步记录一下,方便没有GPU的读者先跑通流程:

# GPU版本(以CUDA 12.1为例) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # CPU版本 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

装完之后,用下面这行代码验证一下GPU是否可用:

import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU mode")

输出True和你的显卡型号,说明GPU环境配置成功。如果输出False,大概率是装的CPU版本,需要重装。

2.3 深度学习依赖库安装

除了Pytorch,还需要安装HuggingFace的Transformers库和Datasets库,以及数据处理常用的Pandas和Scikit-learn:

pip install transformers datasets pandas scikit-learn

Transformers库是加载BERT模型和分词器的核心工具,不需要你自己手写BERT的网络结构,直接调用现成的就好。Datasets库用来做数据集的组织和预处理,比较方便。Scikit-learn用来计算Kappa系数、准确率等评价指标。

装完之后,加载一下中文BERT模型,看看能不能正常跑通:

from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2) print("模型加载成功")

bert-base-chinese是这个项目最适合的中文BERT模型,它就是基于中文语料预训练的base版本,12层Transformer,768维隐藏层,参数量1.1亿,完全匹配论文中使用的方法路线。如果你是从HuggingFace官网自动下载,需要保持网络通畅,下载量大概在400MB左右,耐心等就行。

3. 数据准备与人工标注方案:复现中最耗时的环节

很多人复现这种论文,喜欢把精力都放在模型代码上,结果数据环节草草了事。但实际上,对于这类文本分类任务,训练数据怎么来、怎么标注、质量如何控制,这些才是决定复现效果的核心。模型代码反而是最简单的部分。

3.1 数据源的选取与切分逻辑

论文使用的文本数据是某一家报纸的新闻报道(具体是哪家你可以看论文原文),拿到原始数据之后,需要做的主要是清洗和切分。

清洗环节要处理几类问题:一是去掉HTML标签和无关字符,新闻源经常带网页抓取的残留标签;二是处理缺失值,没有正文的样本直接删除;三是统一时间格式,按年份或者按月把时间字段转换成标准格式;四是要把重复报道去掉,同一事件的重复播报如果不加处理,会造成数据泄漏。

时间切分是这篇论文复现里最值得细究的一步。论文的逻辑是用早期样本做训练集,后期样本做预测集。切分比例可以是7:3,也可以用年份来切,比如2000-2017年的数据做训练集,2018-2022年的数据做预测集。不管怎么切,要保证训练集和预测集在时间上完全不重叠,这是底线。

注意:切分时间节点往前挪一点还是往后挪一点,会对结果产生很大影响。建议你选两三个不同的切分点分别跑一遍,看结论是否稳健。这本身就是论文里稳健性检验的一种。

3.2 标注方案设计:锚点词策略

用BERT做分类需要训练数据,而训练数据需要人工打标签。但你要复现的这篇论文,核心价值恰恰在于用极少的标注样本完成高质量分类,人工标注量并不大,关键是标注策略要设计好。

我当时复现时,用的是"锚点词策略"。具体做法是:在训练集对应的时间段里,人工挑选一批有明显政策信号特征的文本作为正样本种子,比如含有"实施稳健的货币政策""积极的财政政策""供给侧结构性改革"等标准表述的报道。负样本则选取和该政策无关的报道,比如经济数据公布、企业新闻、国际贸易动态。

这样标注的好处是正负样本的语义边界非常清晰,模型容易学到该任务的核心特征。但这里有一个隐患:如果正样本全是千篇一律的"标准表述",模型会学成关键词匹配而不是语义理解。所以我在标注正样本时会有意加入一些不那么标准的表述——比如换一种说法的相近政策报道,让模型学会"识别语义"而不是"匹配关键词"。

标注多少样本量合适?我的经验是,二分类任务最少500条,建议800-1000条。少于500条,BERT微调很容易过拟合;超过2000条,边际收益就开始递减。论文复现追求的是跑通流程和验证结论,1000条左右是最经济的选择。

3.3 数据格式与标签编码

标注完之后,统一成DataFrame格式,包含两列:textlabellabel取值0和1,0表示负样本(不包含该政策),1表示正样本(包含该政策)。

import pandas as pd train_df = pd.read_csv("train_labeled.csv") print(train_df.head()) print(train_df["label"].value_counts())

这里有个小细节:类别的正负比例。如果正样本只有100条,负样本有900条,模型会严重偏向预测负样本。训练时需要用compute_class_weight算一下权重,或者在损失函数里给正样本更高的权重。我在复现时,会尽量让正负样本比例控制在1:3以内,太多不好,太少也不好。

4. BERT微调的核心代码实现:从Tokenizer到训练循环

这个环节是整个复现项目中最核心的代码部分。我会把代码拆成几个模块依次讲清楚,并说一下每个模块背后的逻辑。

4.1 数据预处理:Tokenizer的正确用法

BERT不能直接吃原始文本,需要先用Tokenizer把文本转成input_ids和attention_mask。这一步看起来简单,但有几个坑需要注意。

from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") def encode_texts(texts, max_length=128): return tokenizer( list(texts), max_length=max_length, padding="max_length", truncation=True, return_tensors="pt" )

max_length的取值是有讲究的。BERT的输入长度上限是512个token,但实际文本不必都用512。经济新闻的正文普遍偏长,如果截断到512会丢失大量信息;如果设得太短,比如64,又会导致信息不足。我的做法是画出文本长度的分布图,取90分位数作为max_length。对于新闻正文,128通常不够用,256可能是更好的起点。你可以先统计一下文本长度的分布再定。

4.2 数据集封装与DataLoader构建

用Pytorch写训练循环,需要把数据封装成DatasetDataLoader。这一步主要注意打乱顺序和分批大小。

import torch from torch.utils.data import Dataset, DataLoader class TextDataset(Dataset): def __init__(self, encodings, labels): self.encodings = encodings self.labels = labels def __getitem__(self, idx): item = {key: val[idx] for key, val in self.encodings.items()} item["labels"] = torch.tensor(self.labels[idx], dtype=torch.long) return item def __len__(self): return len(self.labels)

batch_size的选择要看显存大小。12GB显存可以用32,8GB显存建议16,CPU训练用8就行,不然内存吃紧。这个值影响训练速度和稳定性,太大会导致显存溢出,太小模型收敛慢。

4.3 模型加载与优化器配置

加载BERT模型做分类任务,最方便的方式是AutoModelForSequenceClassification,它会自动在BERT顶部加一个分类头。

from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2)

优化器我用的是AdamW,这是BERT微调最常用的优化器,和Adam的区别在于权重衰减的处理方式。学习率是BERT微调里最敏感的超参数之一,推荐用2e-5到5e-5之间。我实测下来,2e-5最稳定,3e-5会稍微快一些但偶尔会出现训练损失抖动,5e-5容易过拟合。

from transformers import AdamW from transformers import get_linear_schedule_with_warmup optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) total_steps = len(train_dataloader) * num_epochs scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(total_steps * 0.1), num_training_steps=total_steps )

这里加了学习率预热(warmup)和线性衰减。预训练模型在最开始几步时,如果学习率太大,容易把已经学好的参数冲坏,所以先用小学习率逐步过渡到目标学习率,最后再逐渐衰减到0。这是BERT微调的标准操作,不要省。

4.4 训练循环与模型保存

训练循环的逻辑比较固定,关键是要在合适的时机保存模型和记录指标。

from tqdm import tqdm import numpy as np device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) num_epochs = 3 for epoch in range(num_epochs): model.train() total_loss = 0 for batch in tqdm(train_dataloader, desc=f"Epoch {epoch+1}/{num_epochs}"): batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) loss = outputs.loss optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() total_loss += loss.item() avg_loss = total_loss / len(train_dataloader) print(f"Epoch {epoch+1}, Average Loss: {avg_loss:.4f}") # 每个epoch结束保存一次,方便回溯 model.save_pretrained(f"bert_finetuned_{epoch+1}") tokenizer.save_pretrained(f"bert_finetuned_{epoch+1}")

一个需要记住的细节是:model(**batch)会自动计算损失,因为我们在TextDataset里已经传了labels字段,模型会拿这个字段和输出算交叉熵损失。这是AutoModelForSequenceClassification封装好的便利之处。

微调的epoch数,BERT一般3-5个epoch就够了。少了欠拟合,多了过拟合。经济新闻文本的语义特征相对规整,我试下来3个epoch效果最好,4个epoch会有轻微过拟合的迹象。

4.5 预测与评价指标:准确率只是起点

模型训练好之后,用它对预测集(后期样本)做分类预测,这个环节的评价指标选择和计算方式是有讲究的。

from sklearn.metrics import accuracy_score, cohen_kappa_score model.eval() predictions = [] true_labels = [] with torch.no_grad(): for batch in tqdm(test_dataloader): batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) logits = outputs.logits preds = torch.argmax(logits, dim=-1) predictions.extend(preds.cpu().numpy()) true_labels.extend(batch["labels"].cpu().numpy()) accuracy = accuracy_score(true_labels, predictions) kappa = cohen_kappa_score(true_labels, predictions) print(f"Accuracy: {accuracy:.4f}") print(f"Cohen's Kappa: {kappa:.4f}")

在经济学论文里,光报告准确率是不够的。因为文本分类的正负样本往往不平衡,准确率会有虚高。经济学论文里更常报告Kappa系数,这个指标衡量的是排除随机一致后的真实一致性。Kappa大于0.8说明分类质量优秀,0.6-0.8说明可以接受,小于0.6就说明分类结果值得怀疑。

举个例子帮助你理解Kappa的计算逻辑。假设你在做"是否提及积极财政政策"的二分类,负样本900条,正样本100条。如果有一个"笨模型"把全部样本都预测为负样本,准确率是90%,看起来很高,但Kappa系数是0,说明它完全没有真的识别能力。这就是为什么Kappa在论文复现中比准确率更能说明问题。

我复现时,在训练集上的准确率能做到95%以上,Kappa在0.9左右。但在按时间切分的预测集上,准确率会掉到85%-90%,Kappa掉到0.7-0.8。这个"掉点"是正常的,不是代码写错了,恰恰说明论文里说的"分布漂移"是真实存在的。

5. 复现中的常见问题与排查技巧

复现论文和普通跑代码不一样,普通跑代码追求的是程序不报错,复现论文追求的是结果可解释、指标靠谱。我把自己踩过的坑和排查经验整理成一个速查表,你可以对照着排查。

5.1 高频问题速查表

问题现象可能原因排查与解决方法
训练损失不下降学习率过大或过小检查学习率是否在2e-5到5e-5区间;尝试换用3e-5
训练损失下降但预测效果差过拟合减少epoch数,增加Dropout,增大训练数据量
预测结果几乎全是同一类类别不平衡检查训练集正负比例,使用加权的损失函数
CUDA out of memorybatch_size过大或max_length过长减小batch_size,减小max_length,或启用梯度累积
模型加载报错(name不匹配)分词器和模型的预训练版本不一致确保tokenizer和model都来自bert-base-chinese
预测集效果远差于训练集时间分布漂移严重检查训练集和预测集时间是否重叠,检查文本分布是否差异过大
中文文本变成乱码编码问题读取数据时指定encoding="utf-8",必要时用encoding="utf-8-sig"

5.2 实操中的三个细节坑

第一个坑是分词器与模型版本不一致。这个坑特别隐蔽。你单独加载一个BERT模型和单独加载一个分词器,都不会报错,但如果模型权重对应的词表和你用的分词器词表不一致,模型输出的结果就是垃圾。解决办法是统一使用AutoTokenizerAutoModelForSequenceClassification从同一个预训练权重加载,不要在中间混用。

第二个坑是标签编码遗漏了验证集。很多人在构造TextDataset时,训练集传了标签,验证集和测试集忘记传,结果模型只学到了预测"0"类。排查方法很简单:打印验证集的预测分布,如果所有样本都预测为同一类别,先检查数据加载有没有问题。

第三个坑是随机种子没有固定。BERT微调的随机性来自几个地方:参数初始化、Dropout、DataLoader的顺序。如果随机种子没固定,每次跑出来的结果会不一样,这对论文复现来说是个大忌。设置随机种子的代码放在最前面执行:

def set_seed(seed=42): torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) np.random.seed(seed) import random random.seed(seed) set_seed(42)

注意,即便设置了随机种子,在GPU上运行仍然会有微小的不确定性,因为CUDA的某些算子不是完全确定性的。要完全锁死,还得设置torch.backends.cudnn.deterministic = True。不过在复现论文时,只要差距在可接受范围内,就不需要过度纠结。

5.3 文本长度特殊情况的处理策略

经济新闻的文本长度两极分化很严重。有的短讯只有一两句话,有的深度报道有上万字。如果统一用一个max_length处理,会对短文本造成大量padding,对长文本造成大量截断。

我当时做的处理是:先统计训练集文本长度分布,画出直方图。如果数据集中在500字以内,max_length=256足够;如果大量文本超过1000字,可以考虑做"头部截断+尾部截断"两段式输入,或者直接简化处理只用前512个token。

另一个更省事的办法是用truncation=Truemax_length=512,模型会保留每个样本的前512个token。对于新闻报道,标题和导语的信息密度最高,这个做法在大多数情况下是可行的。但要注意,如果某些政策表述出现在正文中后部,截断会丢失这些信息。你先看一下正样本里的关键表述大体出现在什么位置,再决定截断策略。

6. 稳健性与扩展思路:复现论文之外还能做什么

论文复现到最后一步,通常还需要做稳健性检验。论文原文的稳健性检验部分可能包含多个角度,我在复现时做了三件事可以作为参考。

第一是更换文本长度阈值。之前用的是512,可以改成256和128各跑一遍,看结论是否稳健。如果模型在128字截断下依然能保持效果,说明模型学习的不是长文细节而是核心语义。如果效果骤降,说明分类依赖的特征大量分布在长文里。

第二是更换切分时间节点。把训练集和预测集的切分点往前或往后移动两三年,重新训练和预测,看分类结果和最终指标是否有明显变化。如果切分点微调导致结论完全反转,说明模型的稳定性存疑,需要进一步检查。

第三是关键词自评。在预测集的结果里,随机抽取一批预测为正样本的和负样本的文本,人工检查模型预测是否合理。这个步骤听起来朴素,但却是审稿人最喜欢看到的效果验证方式。

我在做关键词自评时发现一个有趣的现象:模型对"积极财政政策"的分类效果特别好,但对"稳健货币政策"的分类效果差一些。原因是"积极"和"财政"搭配时语义指向明确,而"稳健"这个词在很多非政策语境下也会出现。这说明不同类别的分类难度是不同的,如果论文涉及多个类别,建议分别报告指标,不要笼统合并。

这套复现流程在你跑通之后,还可以做很多扩展。比如在同一框架下替换成其他预训练模型,用RoBERTa-wwm-ext或者MacBERT对比效果差异;也可以调整分类头,把二分类改成多分类,去识别更多类型的政策文本;还可以把BERT输出的CLS向量当成文本特征,输入到传统机器学习模型里做进一步分析。这些都是性价比很高的后续扩展方向。

我个人在实际操作中的体会是:BERT微调文本分类这种项目,难点从来不在模型代码本身,而在数据组织和评价逻辑。模型代码几行就能写完,但想要复现出论文级别的结果,必须把时间切分、标注方案、评价指标这些"软件"层面的工作做扎实。你在跑通流程之后如果发现结果不理想,不要急着调模型参数,先回头检查数据和标签,多数问题都出在那里。

按这个流程完整跑下来,你应该能复现出可信度相当高的文本分类结果,也能真正理解这篇经管顶刊论文在方法论上做对了什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 23:01:07

MATLAB车牌识别系统设计:从HSV形态学到模板匹配的完整实现

简介:MATLAB车牌识别系统实现毕业论文,围绕车牌识别全流程展开,涵盖图像预处理、车牌定位、字符分割与识别等关键环节,从系统架构到算法选择均有详细说明,并对比了不同方法的适用范围与MATLAB平台的优势和局限。压缩包…

作者头像 李华
网站建设 2026/9/18 22:58:02

Cursor 挂 DBHub MCP 操作 MySQL,模型 Base URL 指到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 22:57:03

AI科研工具链评测与优化实践指南

1. 前沿科研工具现状观察过去三年间,AI科研工具生态发生了显著变化。根据2025年Nature期刊的调研报告,约78%的AI领域研究者表示已从单一框架转向工具链组合使用。这种转变背后反映的是研究需求的复杂化——从单纯的模型训练扩展到数据治理、实验管理、成…

作者头像 李华
网站建设 2026/9/18 22:53:22

新版Qt Creator安装配置全指南:从在线安装到构建套件排错

上个星期帮团队里新来的同学配置 Qt Creator,他照着网上一份老教程走,结果卡在登录那一步半天没弄明白。这事其实不怪他——新版本的 Qt Creator 安装配置,跟以前那种“下载离线包、一路下一步、装完就用”的流程相比,变化已经非常…

作者头像 李华