【年报文本分析】Python+Pytorch微调BERT预训练模型,使用大语言模型完成文本分类任务——金星晔等(2024)《经济研究》大语言模型方法的复现

0 背景介绍

金星晔老师等在《经济研究》2024年第3期发表了一篇题为《企业数字化转型的测度难题:基于大语言模型的新方法与新发现》,使用ERNIE模型,替代了传统的以词频为依据的企业数字化转型、数字技术能力等一系列变量的测量方法。

金星晔,左从江,方明月,李涛,聂辉华.企业数字化转型的测度难题:基于大语言模型的新方法与新发现[J].经济研究,2024,59(3):34-53.

从一个计算机本科毕业,经管类硕士在读学生的视角来看,这篇论文在方法上的创新的确解决了词频方法的种种弊端,不过技术实现难度不大,就是用成熟的预训练模型,导入自己打好标签的语料库进行微调,再用训练好的模型完成文本分类任务,其实里边人工标注的时间成本才是最大的。

论文中使用的ERNIE模型也不一定在所有情境下都是最优的,百度飞桨其实也做的不成熟,目前来看使用Google最原始的BERT模型,使用Pytorch来的成熟。

因此,本篇面向零基础、弱基础的经管学生,提供一套易上手方法,使用Pytorch框架和BERT模型对该论文进行复现,同时也提供基于ERNIE模型的复现思路。

1 环境及依赖库

我使用的是windows10,显卡是cpu版本,在Anaconda3中的spyder5中运行,python版本是3.9.12,以上硬件配置和软件环境对代码运行的影响较小,自行下载最新版本安装运行即可。

最主要的依赖库有两个,一个是Pytorch(2.0.0)版本,提供基本的模型训练,另一个是transformers(4.26.1),提供基本的文本转换。注意,如果频繁出现关于transformers的报错,一般而言就是其版本太高,建议使用我所使用的版本。
下载方式:打开Anaconda3目录下的Anaconda Powershell Prompt,输入下方命令即可完成安装。

pip install torch==2.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install torcvisionh==0.15.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install torchaudio==2.0.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install transformers==4.26.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

在这里插入图片描述
如果已经提前安装过了这两个库,可以先用uninstall卸载掉这两个库
其他依赖库,例如pandas、numpy、os等,一般使用Anaconda3自带的版本即可。

2 使用示例数据集,跑通大语言模型代码

做这个工作之前,我试过了网上很多方法,由于环境很难配置、莫名其妙的报错无法解决、整个流程的不完整等原因,付出了大量的沉默成本,最后机缘巧合搜到了下方这篇博客,用一下午就完成了所有工作。

博客链接:【文本分类】利用bert-base-chinese训练自己的模型完成中文文本分类任务(pytorch实现)

2.1 源代码下载

为不侵犯该博主的知识产权成果,请进入上述博客,从该博客里的链接中下载所有代码、示例数据集以及预训练的中文Bert模型。

具体下载方式为:
1.点击下载仓库,访问该博主的github主页,如果访问不了使用csdn加速器的github加速功能,进入后下载打包好后的zip压缩包,解压。
2.在解压后的文件夹里新建一个空文件夹,命名为bert-base-chinese,点击bert-base-chinese镜像下载,下载该页面所有文件(包含4个大文件,耐心等待)至该文件夹。
3.在解压后的文件夹里新建一个空文件夹,命名为bert_checkpoint。

示例数据还是比较大的,体量有20w,如果机子的显卡不是gpu版本,运行速度会特别慢,没必要在示例数据上边浪费过多时间,因此可以进入THUCNews文件夹,打开里边的train.txt(训练集),test.txt(测试集),dev.txt(验证集),删除掉里边大部分的数据。
我的显卡是cpu,训练集保留了1500条左右,测试集和验证集保留了200条左右,epoch为5时训练时间在20分钟左右,准确率在0.84左右。不过要注意的是,测试集的文本是把一类全放在一起,并不是乱序的,所以我的做法是每个类保留25个句子,这样不会让准确率太低。

2.2 代码的运行步骤

  • 1.使用Spyder打开bert_train.py,运行。
    该文件里的训练超参数部分可以更改数值,建议只对epoch值进行修改。该过程可能要持续一段时间,控制台会输出训练过程。训练好的模型保存在bert_checkpoint文件夹中。其中best.pt是准确率最高的模型文件,last.pt是最后一轮 epoch得到的模型文件,如果最后一轮准确率最高,二者就完全相同。
  • 2.打开bert_test.py,运行。
    调用best.pt,运用验证集验证该模型的效果,输出该模型的准确率。
  • 3.打开bert_tuili.py,运行。
    调用best.pt,使用单个句子进行预测,验证该模型是否可以对其他句子进行分类,检验模型效果。如果出现了关于input函数的报错,可以直接将while True后的代码改为下方代码,在text里输入待预测句子即可。
text = '此处输入待预测的句子'
bert_input = tokenizer(text, padding='max_length', 
                       max_length = 35, 
                       truncation=True,
                       return_tensors="pt")
input_ids = bert_input['input_ids'].to(device)
masks = bert_input['attention_mask'].unsqueeze(1).to(device)
output = model(input_ids, masks)
pred = output.argmax(dim=1)
print(real_labels[pred])

3 更换预训练模型,寻找最优模型

金老师的论文里是对比ERNIE和BERT模型,以及传统的机器学习方法的准确率,从而确定使用ERNIE模型,所以在此提供基于ERNIE模型的复现思路。

其实使用不同模型,本质上只是预训练模型的不同,所以只需要更换预训练模型文件即可。

在下方链接的描述中,有作者提供的ERNIE的预训练模型文件的网盘地址,可以自行下载。

https://gitcode.com/649453932/Bert-Chinese-Text-Classification-Pytorch?utm_source=csdn_github_accelerator&isLogin=1
该地址其实是我第一次尝试的代码,跑通了但是缺少了最后预测的这一步

下载后,在代码的同级目录下,新建一个文件夹,命名为ERNIE,将下载好的预训练模型文件(pytorch_model.bin)放到该文件夹,,并从bert-base-chinese文件夹中复制一份其他文件至该文件夹即可。并新建一个文件夹,命名为ERNIE_checkpoint

随后将bert_get_data.py、bert_tuili.py中的bert_name改为’./ERNIE’,bert_train.py、bert_test.py、bert_tuili.py中的save_path全改为’./ERNIE_checkpoint’,重复2.2中的步骤即可。


以下部分目前我还没进行到,只做简单介绍,后续进行到了后再更新详细思路及代码。

4 获取上市公司年报文本,转换为txt

该步骤其实可以通过其他一些途径直接获取到txt文件,为保证本文完整性,故在此留下我基于巨潮资讯网获取年报pdf链接、批量下载和pdf转txt的博客地址,根据个人需求进行修改。

pdf链接获取:https://blog.csdn.net/weixin_43956523/article/details/137409841

从excel中批量下载:https://blog.csdn.net/weixin_43956523/article/details/136265883?spm=1001.2014.3001.5501

pdf转txt:https://blog.csdn.net/weixin_43956523/article/details/124217368

5 提取“MD&A”和董事会报告部分

一些做文本分析的经管类文章里在介绍时简单得用“MD&A”(即管理层讨论与分析)部分作为文本分析样本,但实际上在很多年报中并无叫该名的章节,可能还会叫董事会报告等一系列名称,所以按照下方文献的思路,重新编制代码,提取相应部分。
在这里插入图片描述

[1]姚加权,张锟澎,郭李鹏,等.人工智能如何提升企业生产效率?——基于劳动力技能结构调整的视角[J].管理世界,2024,40(02):101-116+133+117-122.

因代码较为复杂,完整代码的分模块分析见博客:
https://blog.csdn.net/weixin_43956523/article/details/140712274?spm=1001.2014.3001.5501

注意该代码需要结合个人数据情况修改,大概率无法一次跑通。

6 确定词典,构建待标记语句库

根据句号和分号分隔文本,并根据文献,确定自己的词典,借助Python内置的jieba中文分词库对语句进行分词,筛选出含有关键词的语句,构建待标记词库,保存为excel,具体代码思路详见注释。

import os
import pandas as pd
import jieba
import jieba.analyse
 
item=1 #保存到excel上的行号
fileList=os.listdir('finaltxt')#已提取完毕的txt文件目录
fileList.sort()
df=pd.DataFrame(columns=['code','time','year','sign','keyword','length','sentences'],index=range(1,45001))#预先设置列名,和45000行数,行数可自行估计设定
Dict=['','','']#录入关键词词典
stopwords = {}.fromkeys([line.rstrip() for line in open('stopwords.txt',encoding='utf-8')])#设置停用词词典

for i in Dict:
    jieba.add_word(i)# 向jieba内加入这些词语,防止被拆分   
for index,i in enumerate(fileList[:]):  
    name=i[:-4].split('_')
    with open('finaltxt\\'+i,'r',encoding='utf-8') as f: 
        text=f.read()
        text=text.replace('\n','')#删除换行符
        text=text.replace(';','。')#将分号统一换成句号
        textList=text.split('。') #按句号分割
        for i in textList:
            if len(i)> 300 :#可以设置字数上限,也可以删除此限制
                words=jieba.lcut(i)
                for word in words:
                    if word in iInnoDict:
                    	#向df里录入信息
                        df['code'][item]=name[0]
                        df['time'][item]=name[2]
                        df['year'][item]=name[3][:4]
                        df['keyword'][item]=word
                        df['length'][item]=len(i)
                        df['sentences'][item]=i
                        item+=1
                        break
                    else:
                        continue
        f.close()       
    print(str(index)+'完成')
df.to_excel('result.xlsx')

7 开始人工标注工作

这里只需要统一标记标准,安排研究人员标记即可。

切记要标记准确,不然会严重影响模型的分类性能。

篇幅最小,费时最多。

8 划分训练集、验证集、测试集,并按模型可读形式保存

在训练前,需要按照一定比例(代码中为8:1:1),需要把py文件和excel放在同一文件夹下,生成的txt文件也会在同个文件夹下

import pandas  as pd
import random

total=  #输入总标记条数
dtype={'sentences':str,'sign':str}
df=pd.read_excel('result.xlsx',sheet_name='sheet',dtype=dtype)#读取语料库文件
L1 = random.sample(range(total),total*0.8)#训练集
lastList1 = [x for x in range(total) if x not in L1]
L2 = random.sample(lastList1,total*0.1)#验证集
L3 = [x for x in range(total) if x not in L1 and x not in L2] #测试集     
train=''
test=''
dev=''
  
# 遍历每一行,按句子,\t,标记的格式保存到txt
for  i in range(total):
    if i in L1:
        train=train+df['sentences'][i]+'\t'+str(df['sign'][i])+'\n' 
    elif i in L2:
        test=test+df['sentences'][i]+'\t'+str(df['sign'][i])+'\n' 
    else:
        dev=dev+df['sentences'][i]+'\t'+str(df['sign'][i])+'\n'
         
with open('train.txt','w',encoding='utf-8') as w:   
      w.write(train)
with open('test.txt','w',encoding='utf-8') as w:   
      w.write(test)
with open('dev.txt','w',encoding='utf-8') as w:   
      w.write(dev)

9 微调预训练模型,计算常用指标,得到适应新任务的大语言模型

9.1 训练前操作

复制粘贴一遍原THUCNews文件夹,改成自定义的名字,将上一步骤产生的txt文件剪切到新文件夹下,替换掉原先的train.txt、val.txt、test.txt,并将class.txt中的信息,按照0到N的标记顺序,修改为新标签的含义。

打开bert_get_data.py,将下图中打码的部分,改成新文件夹自定义的名字,保证训练集、验证集、测试集的路径对应准确。

在这里插入图片描述

9.2 模型训练

在bert_tarin.py中将epoch(迭代次数)改成自己想要的次数,这是为了让模型多训练几次从而观察参数选择预测性能相对最优的模型。

同时把保存最优的模型这一块代码注释掉(原代码仅根据验证集的准确率选择最优模型,未考虑loss值和在测试集上的表现,过于直接),改为将每个模型都保存下来,以备进行测试集上的参数评估。

注意:若如此操作,需要留足400M×epoch的存储空间

save_model(f'model{epoch_num + 1}.pt')
        
'''      
# 保存最优的模型
if total_acc_val / len(dev_dataset) > best_dev_acc:
	best_dev_acc = total_acc_val / len(dev_dataset)
	save_model('best.pt')
'''

训练过程示例如下图,建议将每个epoch的四个参数都记录下来,作为模型评估标准

在这里插入图片描述

9.3 测试集上的指标计算

使用下述代码 ,可以依次计算每个epoch生成的模型的精准率、召回率、F1、F0.8四项指标,相关含义可以自行搜索,最重要的指标是精准率。

import os
from transformers import BertTokenizer
import torch
from bert_get_data import BertClassifier
import pandas as pd

bert_name = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(bert_name)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

save_path = './bert_checkpoint'
model = BertClassifier()
model.load_state_dict(torch.load(os.path.join(save_path, 'model1.pt')))#此处修改模型文件名
model = model.to(device)
model.eval()

real_labels = []
with open('./InclusiveInnovation/class.txt', 'r') as f:
    for row in f.readlines():
        real_labels.append(row.strip())
    f.close()

total1=0 #实际1的个数
pre1=0   #被预测为1的个数
acc1=0   #预测1对的个数
acc0=0   #预测0对的个数
totalsen=0

with open('./InclusiveInnovation/test.txt', 'r', encoding='utf-8') as f:
    lines=f.readlines()
    for line in lines:
        sentence,sign=line.split('\t')
        if sign[0]=='1':
            total1+=1
           
        bert_input = tokenizer(sentence, padding='max_length', 
                               max_length = 35, 
                               truncation=True,
                               return_tensors="pt")
        input_ids = bert_input['input_ids'].to(device)
        masks = bert_input['attention_mask'].unsqueeze(1).to(device)
        output = model(input_ids, masks)
        pred = output.argmax(dim=1)
        if real_labels[pred]=='xInclusiveInno':# 标签为0的实际名称
            totalsen+=1
            if sign[0]=='0':
                acc0+=1
            print(str(totalsen)+'完成,结果为no')
        elif real_labels[pred]=='sInclusiveInno':# 标签为1的实际名称
            pre1+=1
            totalsen+=1
            if sign[0]=='1':
                acc1+=1
            print(str(totalsen)+'完成,结果为yes')
print(f'1的总个数:{total1},被预测为1的个数:{pre1},预测1正确的个数:{acc1}')           
        
        
print(f'precision:{acc1/pre1:6f} \nrecall:{acc1/total1:6f} \nacc:{(acc1+acc0)/totalsen:6f} \nf1:{2*(acc1/pre1)*(acc1/total1)/(acc1/pre1+acc1/total1):6f} \nf0.8:{1.64*(acc1/pre1)*(acc1/total1)/(0.64*acc1/pre1+acc1/total1):6f}')#分别计算精准率、召回率、F1、F0.8

9.4 模型评价与选优

9.4.1 验证集上的表现

1.train loss 下降,val loss下降: 表明网络还在学习
2. train loss下降,val loss稳定:网络过拟合
3.train loss稳定,val loss下降:数据集有问题
4.train loss稳定,val loss稳定:可能已经收敛,或者学习遇到瓶颈,可以调小学习率试试
5.train loss上升,val loss上升:网络结构设计有问题,或者训练参数设置不当等,及时停止学习,调整代码

一般来说,如果到最后一次 ,模型的参数值仍比较稳定,那就选择最后一次的模型,如果中间出现了loss值的较大波动,则选择波动前的最后一次

参考:https://blog.csdn.net/qq_36230981/article/details/129216625

9.4.2 测试集上的表现

主要观察精准率的变化趋势 ,结合考虑其他指标进行最优选择。

10 批量文本预测

此处只需要改写bert_tuili.py文件,将其由单个预测变成批量预测,并将结果录入excel表格即可。大概速度在1分钟4-5份年报,如果年报较多可能速度会比较慢。

如果不是一次性运行完,可以将结果复制到新表中后关闭,随后调整start和end值进行下一阶段的运行

放一个我修改后的版本。

import os
from transformers import BertTokenizer
import torch
from bert_get_data import BertClassifier
import pandas as pd

#创建包容性创新句子空表
df=pd.DataFrame(columns=['code','year','time','sentence','sign'],index=range(1,4001))
fileList=os.listdir('finaltxt')
fileList.sort()


bert_name = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(bert_name)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
save_path = './bert_checkpoint'
model = BertClassifier()
model.load_state_dict(torch.load(os.path.join(save_path, 'model13.pt')))
model = model.to(device)
model.eval()

real_labels = []
with open('./InclusiveInnovation/class.txt', 'r') as f:
    for row in f.readlines():
        real_labels.append(row.strip())
    f.close()
    
index=1
start=0
end=2000
for i in range(start,end+1):
    with open('finaltxt\\'+fileList[i],'r',encoding='utf-8') as f: 
        text=f.read()
        text=text.replace(';','。')
        textList=text.split('。') 
        for t in textList:
            bert_input = tokenizer(t, padding='max_length', 
                                   max_length = 35, 
                                   truncation=True,
                                   return_tensors="pt")
            input_ids = bert_input['input_ids'].to(device)
            masks = bert_input['attention_mask'].unsqueeze(1).to(device)
            output = model(input_ids, masks)
            pred = output.argmax(dim=1)
            if real_labels[pred]=='InclusiveInno':
                words=fileList[i].split('_')
                df['code'][index]=words[0]
                df['time'][index]=words[2]
                df['year'][index]=words[3][:4]
                df['sign'][index]=1
                df['sentence'][index]=t
                print(str(index)+'完成')
                index+=1
    print(fileList[i]+'完成,'+str(i))
    if i%100==0:
        df.to_excel('sentence.xlsx')
df.to_excel(f'sentence_{start}to{end}.xlsx')
  • 32
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
使用Java运行PyTorch训练的多任务文本分类模型,需要进行以下步骤: 1.安装PyTorch和Java相关的库:首先需要安装PyTorch和Java相关的库,例如PyTorch Java API和Java Native Interface(JNI)库。 2.将训练好的模型导出为ONNX格式:使用PyTorch将训练好的模型导出为ONNX格式,以便Java代码可以加载和运行。 3.在Java中加载和运行模型使用Java代码加载ONNX格式的模型,并使用输入数据对其进行推理操作。 以下是一个简单的Java代码示例,用于加载和运行ONNX格式的多任务文本分类模型: ```java import ai.onnxruntime.*; public class TextClassification { public static void main(String[] args) throws Exception { // Load the ONNX model OrtEnvironment env = OrtEnvironment.getEnvironment(); OrtSession session = env.createSession("model.onnx"); // Define input and output tensors OrtSession.SessionOptions options = new OrtSession.SessionOptions(); OrtSession.Result result = session.run( new String[]{/* input tensor names */}, new OrtValue[]{/* input tensors */}, new String[]{/* output tensor names */}); // Get the output tensor values Object[] output = result.get(0).getValue(); // Process the output tensor values // ... } } ``` 在上述代码中,需要根据训练好的模型定义输入和输出的张量名称,并将输入数据作为张量传递给模型。然后通过`session.run()`方法运行模型,并获取输出张量的值进行进一步处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ryo_Yuki

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值