首页 / 资讯中心 / 文章详情

RAG+LLM抽取年报AI变量,构建绿色全要素生产率实证模型

RAG+LLM抽取年报AI变量,构建绿色全要素生产率实证模型 ★ FEATURED ARTICLE
简介面向金融科技与环境经济交叉领域的研究者项目包演示了基于RAG与大语言模型分析A股上市公司年报的完整流程旨在量化评估人工智能对企业绿色全要素生产率GTFP的影响并引入融资约束异质性视角开展稳健性检验解决传统计量方法难以处理海量文本信息的痛点。项目共20个文件压缩包仅2.29MB核心为7个Python脚本与7个Jupyter Notebook覆盖年报爬取、格式转换、RAG嵌入生成、GPT打分、数据预处理、统计建模与可视化等环节另有json配置文件、txt说明文档、docx附赠材料与md说明文件便于对照理解模型参数与运行步骤其中py与ipynb相互衔接可分段复现各流程。已有66人学习下载适合经管类高年级学生、研究人员及企业数据分析师作为AI计量经济实证项目的代码模板。通过项目可直接获取年报文本处理与GTFP指标构建思路、调用大语言模型的具体实现、融资约束异质性分组分析及稳健性检验的代码范例同时附有浏览器驱动安装等环境配置说明可帮助使用者快速搭建同类研究框架缩短前期摸索时间。1. 这个项目到底在解决什么问题RAG只是前菜统计建模才是正餐把A股上市公司年报喂给大语言模型做RAG再把抽取结果拿去回归“人工智能对企业绿色全要素生产率的影响”——这个方案乍一听像把两个世界硬接到一起。我做过几年企业绿色效率方向的实证课题GTFP测算和面板回归本身都是成熟工具真正卡脖子的变量是“人工智能应用水平”财报不直接披露这个数相关表述散落在董事会报告、管理层讨论和研发投入明细里。手工读几千份年报不现实RAG加LLM恰好把非结构化文本到结构化变量的规模化抽取这条路走通了。但这个项目的难度不在RAG本身。更现实的问题是抽取出来的字段能不能经得起计量检验测量误差会不会衰减估计系数融资约束分组的样本截断会不会引入内生性。这套管线的价值在于把“文本分析”和“统计建模”焊在一起让AI变量不仅读得出来还用得进去。适合正在做ESG实证、绿色金融、数字经济方向论文或课题的人以及想用LLM给传统计量研究当数据前处理工具的一线从业者。2. 用RAG和大语言模型读年报先建知识库再要结构化答案2.1 为什么年报场景不能只靠长上下文硬读RAG的三个不可替代性年报场景第一个直觉方案是“把全文塞给大语言模型让它直接回答”。我劝你别这么干。A股年报动辄二三十万字三千家公司三年就是上万份文档token成本是一方面更麻烦的是幻觉和不可追溯。让LLM直接报“某公司AI投入金额”它很可能一本正经编一个数——年报里数字密集position encoding稍一错位十万和十亿就分不清了。RAG的本质是先检索再作答把答案锚定在原文片段上每一行抽取结果都能回溯到年报的某一页这对后续审稿、复核、对抗“黑匣子”质疑是决定性的。框架选型上Python生态用LangChain最顺手Java团队用langchain4j也能达到类似效果。有同学问agentic rag能不能提升抽取质量年报场景我不建议一上来就上多轮编排。RAG和MCP的区别也在这里MCP解决的是外部工具串联年报抽取是单轮定向问答复杂度不在工具链而在检索召回率和字段定义本身。2.2 建RAG知识库的最小链路PDF解析、文本切块、向量化入库年报PDF的解析质量直接决定后面所有环节。我用PyMuPDF按页提取文本过滤掉页眉页脚和过短行然后用递归字符切块器按段落粒度切开。这里有个“rag切块”的常见误区年报是高度模板化的文档按字符硬切容易把“研发投入”和“较上年增长”切成两半检索时必然漏召回。我一般把chunk_size设在800到1000个汉字之间overlap取120左右分隔符按段落优先import fitz # 按页提取年报文本并做粗清洗保留会计期间的上下文 def extract_pdf_text(path): doc fitz.open(path) pages [] for page in doc: text page.get_text(text) lines [line.strip() for line in text.splitlines() if len(line.strip()) 4] # 过滤页码和页眉碎片 pages.append(\n.join(lines)) return pages from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap120, separators[\n\n, \n, 。, ] # 优先按语义边界切 ) chunks [] for page_text in extract_pdf_text(2023_年度报告.pdf): chunks splitter.split_text(page_text)切完先别急着入库。我习惯先打印几条chunk看切点是否落在句子中间。前面过滤短行那一步很关键——年报页脚经常出现“XX股份有限公司 2023年年度报告”这种重复短句不滤掉会让检索结果里混入大量噪音片段向量检索时这些重复文本会抢占相似度排序的坑位。2.3 向量化入库与按字段定向检索一次查一个字段比一次问完更稳向量化我直接用FAISS落盘embedding模型按成本和数据敏感度选。如果用的是API embedding年报这种中文术语密度极高的文本建议先拿50条年报段落做一次小规模相似度抽查确认“人工智能”“研发投入”“环保设备”这些词的召回是否正常。from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings # 向量化后保存到本地后续年份增量更新时不必重新embedding全部语料 db FAISS.from_texts(chunks, OpenAIEmbeddings(modeltext-embedding-3-small)) db.save_local(annual_report_index) # 按字段定向检索每个待抽取字段一个独立query合并去重 retriever db.as_retriever(search_kwargs{k: 4}) query_list [人工智能 研发投入 项目 金额, 环保 投入 万元 本期, 发明专利 数量 累计 报告期] hit_docs [] for q in query_list: hit_docs retriever.invoke(q)检索参数里最容易被忽略的是top_k。年报里一个字段往往只在某个章节出现一次top_k设为4就够用设得太大反而把不相关段落带进来干扰抽取准确率。真正的“坑”藏在query设计上——“人工智能投入金额”这种query大概率命中管理层讨论章节但具体金额在“研发投入”明细表里所以我会拆成“人工智能 研发投入 项目 金额”这种不完整句法去撞相似度。2.4 字段抽取的Prompt设计JSON输出、温度归零、缺省返回null检索到的段落拼进Prompt用结构化输出约束LLM返回JSON。抽取类是纯信息搬运temperature必须设成0否则同一段文本跑两遍结果不一致对账时人会崩溃。规则上要写死三件事缺省返回null而不是编数、金额统一转成万元、小数保留两位。import json from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini, temperature0) extract_prompt 从下面的年报文本中抽取字段只输出JSON不要输出任何解释。 字段 - ai_investment: 人工智能相关研发投入金额单位万元没有则null - green_tech_desc: 是否有绿色技术/环保技术相关表述有则输出1无则0 - patent_num: 报告期末累计专利数没有则null 规则文本中明确提到的才填金额统一换算成万元不推测、不补全。 文本 {context} from langchain_core.output_parsers import JsonOutputParser def extract_fields(context_text): resp llm.invoke(extract_prompt.format(contextcontext_text[:3000])) return JsonOutputParser().parse(resp.content)注意我截断了context的前3000字。向量检索返回的多个chunk拼起来可能超过模型窗口但抽取字段只看相关段落即可截断顺序取“检索分数最高的排前面”避免LLM被无关文本带跑。这里如果把“文本没提到就返回null”这条规则漏了后面统计建模阶段缺失率会高得不像话而且缺失不是随机的——都是检索不到的地方缺失直接进回归会导致严重的样本选择偏误。2.5 向量模型和LLM怎么选云API还是本地部署大语言模型场景推荐选择原因一次跑几千份年报、预算有限API embedding 小型Chat模型效率高成本可控年报文本敏感、不能出内网本地部署大语言模型 中文embedding数据不出域但需GPU追求字段抽取的稳定性温度归零 结构化输出可复现对账一致已经上Java技术栈langchain4j的rag组件不必跨语言维护本地部署我常用bge-m3做embedding加Qwen系列做抽取中文年报术语命中率不输主流API。但本地部署的部署成本和推理速度要用数据说话一万份年报乘每份三个字段的抽取密度本地V100跑也要几个小时先算清账再决定。3. 从抽取结果到可回归的面板数据AI变量、GTFP与融资约束的口径3.1 GTFP的投入产出口径与数据边界SBM-DDF和GML的选择绿色全要素生产率这个变量核心是“同时考虑期望产出和非期望产出”的效率测度。常见做法是用SBM方向性距离函数配合GML指数计算。投入侧取年末固定资产净额、员工人数、能源消耗量期望产出取营业收入非期望产出用CO2排放量或工业SO2排放量。企业层面的能源和排放数据不是完整可得的很多实证课题用行业能源强度乘企业营收做近似或者匹配环保披露数据库。这里有个边界要坦白非期望产出数据缺失严重时GML指数算出来的GTFP会大量集中在1附近组内变异太小后面固定效应回归几乎没有识别力。如果遇到这种情况优先考虑用当期参比的M指数替代全局参比的GML或者改用两阶段法在第一阶段用DEA算效率、第二阶段再做面板回归。测算口径的一致性比“绝对精确”更重要因为你的目标是比较AI对绿色效率的边际影响而不是测准每一家企业的绝对效率。3.2 AI变量三种测度词频、投资额与专利基准和替换怎么排AI变量是这个项目里最需要拿捏的部分。三种常见测度各自的脾气完全不一样年报词频统计“人工智能、机器学习、深度学习、智能”等关键词在年报中出现的相对次数。覆盖最全没缺失但不区分“真实投入”和“讲故事”。AI投资额RAG抽取的人工智能相关研发投入。信息浓度高但年报披露口径混乱缺失率高。AI专利数人工智能相关专利。最硬核但存在申报滞后当年的专利反映的是两三年前的研发。我一般用词频做基准回归投资额和专利做稳健性替换。词频计算不需要复杂NLP用正则就能搞定import re import pandas as pd # df_text: 一列是股票代码一列是年报全文文本 keywords [人工智能, 机器学习, 深度学习, 大模型, 智能制造] df_text[ai_count] df_text[annual_report].apply( lambda txt: sum(len(re.findall(kw, txt)) for kw in keywords) ) # 用总字数归一化避免同一年报篇幅差异带来的伪相关 df_text[ai_freq] df_text[ai_count] / df_text[annual_report].str.len()词频这个测度最大的优点是零缺失。零缺失在计量上太重要了——只要变量构造逻辑一致审稿人不会质疑样本选择。缺点是它衡量的是“关注度”而非“投入”所以基准回归显著之后必须用投资额做稳健性否则结论会被批“AI讲故事也能提升效率吗”。3.3 融资约束的SA指数为什么不用KZ和WW融资约束异质性是标题里的核心分析维度。衡量融资约束的指数很多KZ和WW指数理论上更全面但都需要股息支付、托宾Q、现金流这些财务数据A股不少样本对不上。SA指数只需要企业规模和上市年龄两个变量数据完整度极高是实证界的“后悔药”式选择——简单到不会出错。SA指数公式是import numpy as np df[size] np.log(df[total_assets]) # 总资产取对数 df[age] df[year] - df[list_year] # 上市年限 # 标准SA指数公式负值越大代表融资约束越强 df[SA] -0.737 * df[size] 0.043 * df[size] ** 2 - 0.040 * df[age]实际分组时我习惯按SA中位数把样本切成高约束组和低约束组或者按年份分组各取三分之一做分位组。不要用全局固定切点——不同年份上市企业画像差异很大刚上市的小公司和老牌大厂混在一起切分组会严重不平衡。3.4 面板对齐的主要陷阱报告期口径与去重RAG抽取结果和财务数据合并时最大坑是“年度”定义不一致。年报封面印着“2023年年度报告”但财报披露的是2022财年数据。如果直接用报告文件年份和财务库的年份对齐AI变量会整体错位一年固定效应回归里这种系统性错位很难察觉因为方向一致但系数会大幅衰减。# annual_report字段表示披露日期报告期是上一财年末 df[report_year] df[annual_report].dt.year - 1 # 按股票代码和报告期年份关联财务面板 merged df_text.merge(df_financial, on[stkcd, report_year], howleft) # 同一codereport_year出现多行时只保留第一次抽取结果 merged merged.drop_duplicates(subset[stkcd, report_year], keepfirst)提示“报告期”是年报正文里最显眼的日期字段优先级永远高于文件名和封面年份。金融数据库里的year字段通常就是报告期年份直接用它join就行。4. 基准回归与融资约束异质性固定效应模型的参数选择和检验设计4.1 基准模型设定双向固定效应是底线基准模型是双向固定效应面板回归GTFP对AI变量回归控制变量放企业规模、资产负债率、现金流、产权性质。模型写成GTFP_it α β × AI_it γ × Controls_it μ_i λ_t ε_itμ_i是企业固定效应吸收行业、地区、产权性质等所有不随时间变化的特征λ_t是年份固定效应吸收宏观经济波动、政策冲击。随机效应在这个场景下基本不用考虑——企业个体特征和AI变量几乎必然相关随机效应估计不一致。跑Hausman检验大概率也是强烈拒绝原假设固定效应是底线不是选择。被企业固定效应吸收掉的变量要提前想清楚行业虚拟变量、是否国企这种常量进模型会被drop_absorbed自动弹掉不用慌说明它们的信息已经包含在μ_i里了。4.2 用PanelOLS跑基准回归面板索引、聚类标准误与结果解读我用linearmodels的PanelOLS跑双向固定效应代码和数据格式都相对清爽from linearmodels.panel import PanelOLS df_reg merged.set_index([stkcd, year]) exog_vars [ai_freq, size, leverage, cashflow] exog df_reg[exog_vars] mod PanelOLS( df_reg[gtfp], exog, entity_effectsTrue, # 企业固定效应 time_effectsTrue, # 年份固定效应 drop_absorbedTrue # 自动丢弃被吸收的常量变量 ) res mod.fit(cov_typeclustered, cluster_entityTrue) print(res)cluster_entityTrue表示按企业聚类标准误这是面板实证的标配——同一家企业的扰动项在不同年份必然相关不聚类标准误会严重低估t值会虚高。解读结果时首要看β的符号和显著性其次看经济显著性AI词频每提高一个标准差GTFP变动百分之几。很多论文只报t值不报经济意义审稿人追问时很可能哑火。4.3 融资约束异质性分组回归系数差异检验比交互项更直观异质性分析的两条技术路线交互项模型和分组回归。交互项写法是GTFP对AI、AI×HighFC、HighFC及控制变量回归好处是一个模型出结果坏处是HighFC的组内均值差异不明显时交互项解释起来比较绕。分组回归更直观——高约束组和低约束组各跑一遍然后检验AI系数在两组之间是否显著不同。分组后系数差异不能只看数值大小必须做统计检验。常见做法是费舍尔组合检验逻辑是把样本的真实分组标签打乱重抽很多次每次记录两组β差异的分布看真实差异落在分布的什么位置import numpy as np def group_diff(data, group_col, formula_vars): hi data[data[group_col] 1] lo data[data[group_col] 0] b_hi PanelOLS(hi[gtfp], hi[formula_vars], entity_effectsTrue).fit().params[0] b_lo PanelOLS(lo[gtfp], lo[formula_vars], entity_effectsTrue).fit().params[0] return b_hi - b_lo rng np.random.default_rng(42) real_diff group_diff(df_reg, high_fc, exog_vars) diff_draws [] labels df_reg[high_fc].to_numpy().copy() for _ in range(500): rng.shuffle(labels) # 打乱分组标签打破约束与变量关联 df_perm df_reg.copy() df_perm[high_fc] labels diff_draws.append(group_diff(df_perm, high_fc, exog_vars)) p_value (np.abs(diff_draws) np.abs(real_diff)).mean()分组回归有一个隐蔽问题分组依据SA指数本身和企业特征相关组内样本量差异大时低样本组的标准误会很大。如果低约束组只有几百家企业β不显著很可能是功效不足而不是“没有异质性”报告时要同时给出两组样本量。4.4 时滞与内生性的初筛滞后一期AI变量AI投入对绿色效率的影响存在传导时间。当年砸钱搞数字化当年未必见效。基准回归里顺手放一个滞后一期AI变量做初步检验既是对“同期反向因果”的缓解也是稳健性检验的前置df_reg[ai_freq_lag] df_reg.groupby(stkcd)[ai_freq].shift(1) # 滞后变量进回归注意有效样本量会减少一年滞后期损失样本是正常现象。如果滞后项系数比当期项还大说明AI对GTFP的影响存在递增趋势这个信息本身就有政策含义。工具变量的正式方案放到第5章滞后只能缓解同期性问题解决不了“企业预期未来效率高所以现在投AI”这种反向逻辑。5. 稳健性检验避坑手册三类翻车现场与对应解法5.1 稳健性三件套缩尾、聚类标准误与时滞实证论文里稳健性检验三件套是缩尾、聚类标准误、解释变量滞后。缩尾处理极端值对效率测算的干扰GTFP和AI词频都可能存在厚尾——个别企业词频是全行业十倍不处理直接回归一个点就撬动整条拟合线# 上下1%分位缩尾 for col in [gtfp, ai_freq, size, leverage]: lo, hi df_reg[col].quantile([0.01, 0.99]) df_reg[col] df_reg[col].clip(lo, hi)聚类标准误的层级选择也有讲究。按企业聚类是底线如果行业震荡明显可以考虑行业年份双维聚类。双维聚类标准误通常更大系数显著性下降是正常的——如果缩尾后显著性崩了说明原结果依赖极端值要如实报告而不是换聚类方式“调到显著”。5.2 替换测度AI换投资额与专利GTFP换M指数基准用词频稳健性必须换AI测度。替换逻辑是如果词频反映的是“讲故事”那么换成投资额真实投入后系数应该依然成立如果换了就不显著说明基准结果可能只是市场情绪或文字游戏的面板反映。GTFP测度也换一次从GML指数换成M指数或普通Malmquist看看结论对效率算法的敏感性。# 换AI测度后回归对比系数表 models {} for v in [ai_freq, ai_investment_log, ai_patent_log]: if v not in df_reg.columns: continue mod PanelOLS(df_reg[gtfp], df_reg[[v, size, leverage]], entity_effectsTrue, time_effectsTrue, drop_absorbedTrue) models[v] mod.fit(cov_typeclustered, cluster_entityTrue)换测度时要注意量纲差异。AI投资额是金额单位词频是万分比两者系数大小不可直接比看的是显著性和方向一致性。报告稳健性检验时把不同测度的估计系数画在同一张图里用置信区间覆盖情况说明结论稳定性比堆十个回归表更直观。5.3 内生性再进一步行业同群工具变量词频和GTFP之间的内生性有两个来源遗漏变量管理水平好的企业既爱用AI效率也高和反向因果效率高的企业有钱投AI。滞后项只能挡反向因果挡不住遗漏变量。常规做法是用行业内其他企业AI均值的“同群效应”做工具变量——个体企业受同行AI采用趋势影响但同行的平均AI水平不太可能直接影响这家企业的绿色效率。from linearmodels.iv import IVPanelOLS # 一阶段ai_freq对行业均值回归二阶段用拟合值做解释变量 df_reg[industry_ai_mean] df_reg.groupby([industry, year])[ai_freq].transform(mean) iv_mod IVPanelOLS.from_formula( gtfp ~ 1 size leverage EntityEffects TimeEffects [ai_freq ~ industry_ai_mean], df_reg ) iv_res iv_mod.fit(cov_typeclustered, cluster_entityTrue) # 第一阶段F值低于10说明工具变量太弱 print(iv_res.first_stage)注意同群工具变量要论证排他性——同行AI均值只能通过本企业AI采用影响绿色效率。如果行业存在共同的技术冲击比如某年全行业都在上数字化同时影响效率和AI采用这个工具变量就失效。稳健的做法是换工具变量组合做overidentifying检验哪怕只多一个工具变量也好过单工具变量完全无法检验。5.4 避坑清单从RAG到面板回归的五种翻车现场翻车现场一RAG抽取的AI投资额字段缺失率超过40%。现象merge后AI投资额大量NaN回归样本量骤减。原因检索召回不够投资额明细在年报“研发投入”章节里query却只搜“人工智能”。解决把检索query拆成“研发投入 本期 金额”“人工智能 资本化 费用化”等多路召回并允许对同一字段做二次抽取。翻车现场二LLM抽出的数值与人工复核系统性差一年。现象抽样对账发现AI投资额常见值是上一年度的。原因年报PDF中“上年同期数”和“本年数”同时出现Prompt没指定取哪一个。解决Prompt里写死“取报告期本年数不要上年同期和上年末余额”。翻车现场三GTFP大量等于1固定效应回归系数几乎为零。现象效率值组内变异趋近于零。原因非期望产出数据缺失严重SBM把所有样本推向前沿。解决换非期望产出口径或改用两阶段方法先DEA后Tobit/面板。翻车现场四加入企业固定效应后AI系数由正转负。现象混合OLS显著为正固定效应显著为负。原因AI词频的组内变异太小企业个体效应吸收了大部分信息剩下随时间变化的波动方向和效率变化相反。解决报告组内R平方和AI变量的组内标准差如果组内变异实在太小考虑换成行业×年份维度或改用一阶差分模型。翻车现场五工具变量第一阶段F统计量是9.2。现象F值略低于经验阈值10IV结果的大标准误让显著性消失。原因行业均值工具变量与个体AI采用的相关性不够强。解决换工具变量组合或改用控制函数法做稳健对照。6. 验证抽取质量的三道工序对账、抽样复核与置信度阈值6.1 与CSMAR字段和人工翻页复核做三方对账RAG抽取结果不能直接进回归先做一轮对账。我一般抽30到50家企业对“AI投资额、专利数、绿色技术有无”三个字段分别核对第一把手是拿CSMAR或Wind里已有的研发投入字段做近似比对第二把手是人工打开PDF原文定位到对应段落。字段抽样家数一致率典型偏差研发投入总额4092%上年与本年混淆累计专利数3087%“专利”vs“发明”口径不统一绿色技术表述4095%子公司文本混入一致率低于85%就要回头修Prompt或检索策略不要硬着头皮进回归。测量误差对系数的衰减作用不是玄学是真实存在的解释变量存在纯粹测量误差时估计系数会朝零衰减。RAG抽取的一致率每降一个百分点AI系数可能被压低多个百分点这在审稿意见里几乎必被追问。6.2 置信度阈值与缺失值策略给每一个抽取结果留原文证据我给RAG抽取管线加了一道工序凡输出数值的字段必须连带返回命中的原文摘要和页码。这样做有两层好处——复核不用重新查全文直接看抽取时用的上下文片段审稿人质疑数据质量时可以给出可核查的证据链。字段为null时也记录原因是检索无命中还是LLM判断“未提及”。这两种缺失在回归里含义完全不同。缺失率超过20%的字段不建议直接插补。插补只适用于“随机缺失”检索性缺失完全不随机——它取决于年报披露习惯而披露习惯和企业特征相关。如果某个字段缺失率居高不下正确做法是换一个测度而不是把缺失值填成均值继续跑。6.3 把整套管线打包成可复现的“数据血缘”我的习惯是把PDF解析切片、检索命中文段、LLM原始输出、人工复核记录四层全部落盘保存。这不是多此一举——统计建模的结果能不能复现一半取决于模型另一半取决于抽样和清洗。RAG抽取的随机性已经用temperature0压掉了但embedding模型的版本更新、切块策略微调都可能让抽取结果产生漂移。保留数据血缘意味着几个月后重新跑这个项目时你还能解释清楚每一个变量是怎么从年报变成数字的。做这类“文本抽取加计量实证”的项目我最大的教训是模型选型从来不是瓶颈字段颗粒度和口径一致性才是。AI测度差一个字结论可能从显著为正变成不显著报告期错位一年系数方向都可能反转。这个方向值得投入但请把三分之二的精力放在抽取质量的验证上回归本身反而很快。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站