☰
RAG与大语言模型:从年报文本到绿色全要素生产率实证证据
2026/10/6 3:14:53 网站建设 项目流程

简介:一套面向经管类研究者与研究生的完整代码与文档包,用于借助RAG技术和大语言模型分析A股上市公司年报,量化人工智能对企业绿色全要素生产率(GTFP)的影响。项目同时考虑融资约束异质性,并通过稳健性检验强化结论可靠性,内容覆盖年报爬取、RAG嵌入、大语言模型调用、数据处理、统计建模与图表呈现。资源共20个文件,压缩包大小2.29MB,以py脚本和ipynb笔记为主体,另有txt说明、json配置、docx附件和readme概述,分别承担数据抓取、年报转换、GPT评分、列表处理、LightGBM拟合、环境配置与背景说明等功能。借助完整脚本与文档,读者可复现或迁移该研究流程,理解AI应用如何作用于企业绿色生产效率,适合具备Python和计量经济学基础、希望用LLM推进实证研究的进阶用户。已有66人学习下载,特别适合高校实验室、课题组开展相关课题研究。

1. 从年报文本到AI-GTFP证据链:RAG和大语言模型在这里到底解决什么

当你想研究"人工智能应用对企业绿色全要素生产率(GTFP)的影响",第一步就卡在数据上:A股上市公司的年报里从来没有一列现成的"AI应用强度"数据。这个项目把RAG技术和大语言模型当作文本挖掘工具,把三千多份年报从PDF打散成可检索的语义块,再让模型按统一模板抽取AI应用相关字段,得到结构化指标后进入统计建模环节,测算GTFP、跑基准回归、做融资约束异质性和稳健性检验。适合的人群很明确:手里有年报文本和财务数据、想做实证研究、又不满足于用关键词字符串匹配来度量AI应用的研究者,或者量化与ESG方向的从业者。

2. 用RAG抽取AI应用指标:PDF解析、语义分块与结构化抽取的落地路径

2.1 为什么不能直接让大语言模型读整本年报

一份A股年报动辄两三百页,全文转成文本后有十万字以上。让大语言模型直接读完再回答"这家公司AI应用程度如何",会撞上三个现实问题:上下文窗口撑不满,长文本中间段落的注意力衰减会让模型漏掉关键信息;单份处理成本太高,一个样本几元到几十元,几千家公司做下来是一笔不小的开销;更麻烦的是结果不可控——模型通读全文后的打分标准无法稳定复现,换个提问方式结果就变。

RAG的思路是反过来的。先本地建一个向量索引,把年报切成小块,检索时只把与"人工智能应用"最相关的那几块文本拼进提示词。检索范围从十万字收缩到两千字左右,抽取依据明确,提示词里能带上"仅依据以下原文片段作答"的约束。这既降低了幻觉范围,也让每次抽取的证据来源可追溯。项目标题里把RAG和大语言模型并列,本质就是检索增强和抽取增强配合,前者负责把范围收窄,后者负责把非结构化文本翻译成统计建模需要的字段。

2.2 语义分块与章节路径保留:先抽目录再切文本

年报不是流水账,它有稳定结构:第一节重要提示、第二节公司简介、第三节管理层讨论与分析、第四节经营情况讨论……AI应用相关的披露绝大多数集中在管理层讨论与分析(MD&A)和董事会报告里。因此分块前第一步是解析PDF目录,锁定MD&A的起止页码。

import fitz # PyMuPDF,解析PDF的主力 doc = fitz.open("annual_report_2023.pdf") toc_candidates = [] # 年报前几页通常是目录,先扫描目录页码区域 for page_idx in range(3): page = doc[page_idx] text = page.get_text() for line in text.split("\n"): # 匹配形如 “第三节 管理层讨论与分析 ...... 12” 的目录行 if "管理层讨论与分析" in line and line.strip()[-3:].isdigit(): target_page = int(line.strip()[-3:]) toc_candidates.append(("mda", page_idx + target_page)) if "董事会报告" in line and line.strip()[-3:].isdigit(): target_page = int(line.strip()[-3:]) toc_candidates.append(("board", page_idx + target_page)) # 取最早出现的MD&A页码作为正文起点 mda_start = min([p for label, p in toc_candidates if label == "mda"], default=20) print(f"MD&A 章节从第 {mda_start} 页开始")

这段代码的价值在于先把文本处理范围缩小,而不是把三百页全文丢进分块器。MD&A章节通常占年报篇幅的四分之一到三分之一,却是AI应用信息密度最高的位置。注意目录页码和正文页码之间可能存在偏移——有些PDF的目录页不参与页码编排,所以我在匹配后加了page_idx + target_page的修正,实际遇到偏移时还需要人工校准一次。

拿到MD&A正文后,不能按固定字符数硬切。年报段落有明确语义边界,固定1000字切分很容易把"公司持续推进人工智能技术在质检环节的应用"和"但本年度尚未产生显著效益"切成两半,检索时只能召回前半句,模型就会漏掉否定信息。我的做法是保留段落边界:

import re def semantic_chunking(text, chunk_size=800, overlap=150): # 先按空行拆为段落,段落是年报里最小的语义单位 paragraphs = [p.strip() for p in re.split(r"\n\s*\n", text) if p.strip()] chunks, current = [], "" for para in paragraphs: # 如果单段超过chunk_size,按句子二次切分,保留句号边界 if len(para) > chunk_size: sentences = re.split(r"(?<=[。;])", para) for sent in sentences: if len(current) + len(sent) > chunk_size: chunks.append(current) current = sent[-overlap:] if overlap else "" else: current += sent else: if len(current) + len(para) > chunk_size: chunks.append(current) # overlap携带上一块的结尾,避免切断后丢失语义衔接 current = current[-overlap:] + "\n" + para if overlap else para else: current += "\n" + para if current: chunks.append(current) return chunks

这里的关键参数是overlap。年报文本的语义连续性比一般网页文章更强,150字符的overlap能保证切分边界两侧的上下文大部分保留。不要为了省向量存储空间把overlap设成0,检索召回时你会后悔。chunk_size取800来自经验值:中文年报一句话平均30到80字,800字大约能容纳10到15个完整句子,足够承载一个完整信息点的上下文,又不会让向量检索的精度下降。

分块时还要把元数据一起存上——公司代码、年报年份、所属章节、块序号。这在后面排查"某个AI指标为什么抽出来是空"时是救命信息。假如某家公司MD&A章节压根没被解析进来,你通过元数据就能快速定位是分块环节丢了,还是抽取环节漏了。

2.3 向量检索的TopK、阈值与中文嵌入模型选型

分块完成后进入向量化。中文年报文本的嵌入模型选择直接影响检索质量,我用下来bge系列和m3e的表现明显好于通用多语言模型,原因是年报里有大量财务术语和行业黑话,通用模型容易跑偏。本地部署还是调API看你现有算力:几千份年报总量不大,分块后大约十几万条向量,一台带16G内存的机器跑FAISS或ChromaDB绰绰有余。

检索时query怎么写很重要。我遇到过直接用"人工智能"去检索,召回的全是含这四个字的段落,但很多段落只是在政策背景里提到一句"国家推动人工智能发展",和这家公司的实际应用无关。更好的query是带具体场景的组合,比如"人工智能 应用 智能制造 能耗 绿色生产 研发投入"。检索参数上,TopK取5到8之间,别取太大——TopK太大会把大量弱相关片段塞进上下文,稀释真正有用的信息,让模型的判断精度下降。

import chromadb client = chromadb.PersistentClient(path="./report_vector_store") collection = client.get_or_create_collection( name="a_share_annual_reports", metadata={"hnsw:space": "cosine"} # 余弦距离,文本检索比L2稳定 ) collection.add( documents=chunks, ids=[f"{firm_id}_{year}_{idx}" for idx in range(len(chunks))], metadatas=[ {"firm": firm_id, "year": year, "section": section, "chunk_idx": idx} for idx in range(len(chunks)) ] ) query = "人工智能 应用实践 智能制造 绿色生产 能耗 研发" results = collection.query( query_texts=[query], n_results=6, include=["documents", "metadatas", "distances"] ) for doc, meta, dist in zip( results["documents"][0], results["metadatas"][0], results["distances"][0] ): print(f"距离 {dist:.4f} | {meta['firm']} {meta['year']} | {doc[:80]}")

距离阈值怎么设取决于嵌入模型。bge-large-zh的cosine距离在0.2到0.35之间通常对应强相关片段,但不要死守一个绝对值,先跑几十家公司抽样看分布再定。我的做法是TopK固定6,辅助加一个距离上限0.45,超过的直接丢弃,宁可少召回也不让低质量片段混进提示词。这个阈值如果设太紧,有些用词独特的公司会被漏掉——比如年报里写"数字化车间"而不写"人工智能",这恰恰是实际业务里最常见的错配。

2.4 用提示词模板与JSON输出把抽取结果变成回归变量

检索到的片段拼进提示词后,抽取任务要定义得足够窄。不要问"这家公司AI应用水平如何"这种开放问题,模型给出来的打分没有客观锚点。要拆成字段级的问题:是否提及AI应用、提到哪些具体应用场景、有没有涉及投入金额、原文依据是哪一句。

PROMPT_TEMPLATE = """你是金融文本分析专家。严格依据年报原文片段判断企业人工智能应用情况。 【原文片段】 {context} 【判断规则】 1. 只要片段中出现“人工智能、AI、机器学习、深度学习、智能算法”等相关词汇,ai_mentioned即为true 2. ai_application_areas列出具体应用领域,如智能制造、智能风控、智能运维 3. 如果片段中只提到“计划、将、拟、准备”等未来词,ai_investment_mentioned一律为false 4. 抽取evidence_evidence_sentence时必须原样复制原文句子,禁止改写 【输出格式】 仅输出JSON,不要输出任何解释: {{ "ai_mentioned": true, "ai_application_areas": ["智能制造", "智能质检"], "ai_investment_mentioned": false, "evidence_sentence": "报告期内,公司持续推进人工智能技术在智能质检领域的应用" }} """ def extract_ai_fields(prompt_text, model_api): response = model_api.chat( messages=[{"role": "user", "content": prompt_text}], response_format={"type": "json_object"}, # 强制JSON输出 temperature=0.1 # 抽取任务压低温度,提高可复现性 ) return json.loads(response.choices[0].message.content)

注意temperature设到0.1而不是0。设成0偶尔会触发模型对指令的过拟合,反而丢失一点灵活变通能力;0.1几乎不影响可复现性,却让输出格式稳定不少。JSON输出要依赖接口的response_format能力,如果用的是不支持强制JSON的模型,就在提示词里放一个JSON示例,再在解析失败时加两次重试。每家公司的年报片段可能抽出来多个应用场景,后处理时按"至少命中一个并提取到evidence_sentence"保留记录,作为统计建模阶段的AI基础变量——按人/公司/年构建的0-1变量,后续可以扩展成连续强度指标。

3. 统计建模主流程:绿色全要素生产率测算与面板回归设定

3.1 绿色全要素生产率的口径选择:SBM方向距离函数与GML指数

绿色全要素生产率相对传统TFP的差别在于,把非期望产出纳入测算框架。传统TFP只考核好产出,但高耗能、高排放换来的增长是"黑色增长"。学术界的标准口径是用SBM方向性距离函数搭配GML指数(Global Malmquist-Luenberger),投入侧选劳动力、资本存量、能源消耗,好产出用营业收入或工业增加值,坏产出用碳排放或工业污染物排放量。

测算工具上,我一般用MaxDEA软件做SBM-GML的指数计算,因为DEA的线性规划求解在Python里自己写很容易在规模放大的时候翻车。但如果只是验证思路,用简化实现跑通流程也够了:

import numpy as np def gml_index(data, year_t, year_t1): """ 简化版GTFP测算骨架——相邻两年Malmquist-Luenberger指数 data: [公司, 年份, 劳动L, 资本K, 能源E, 期望产出Y, 非期望产出B] 完整版用 MaxDEA 或 Stata 的 dea 命令,这里给出核心公式结构 """ L_t, K_t, E_t, Y_t, B_t = data[year_t] L_t1, K_t1, E_t1, Y_t1, B_t1 = data[year_t1] # GML指数在规模报酬可变下需要解四组方向距离函数 # D_t(x_t, y_t, b_t)、D_t(x_t1, y_t1, b_t1) 等 # 实际计算落在DEA线性规划上,这里用随机值模拟指数计算流程 d_t_t = 1.0 # D_t(x_t, y_t, b_t) d_t_t1 = 0.95 # D_t(x_{t+1}, y_{t+1}, b_{t+1}) d_t1_t = 1.02 # D_{t+1}(x_t, y_t, b_t) d_t1_t1 = 1.05 # GML = sqrt[ (D_t(x_{t+1},...)/D_t(x_t,...)) * (D_{t+1}(x_{t+1},...)/D_{t+1}(x_t,...)) ] gml = np.sqrt((d_t_t1 / d_t_t) * (d_t1_t1 / d_t1_t)) return gml

这段代码不是用来直接出结果的,它的作用是帮你理解GML指数的结构——它不是一个水平值,而是一个相对上一期的变动率,因此在后面建回归时,GTFP的t期值要和t期解释变量匹配,不能错位成同期截面。用MaxDEA算完后导出各公司各年度的GML累积指数,再以某一年为基期转化为水平值,进入面板。

另一个容易犯的错是GML指数可能出现无解的情况。当某家公司在某一年度投入产出数据异常,或者非期望产出恰好为零,DEA线性规划可能会解不出来。这一部分观测直接删除会损失信息,标准做法是检查原始数据是否有录入错误,确认无误后可以对该观测做插补,或者用Super-SBM模型处理极端效率值。

3.2 面板数据组装与变量定义:AI指标、GTFP与控制变量

核心解释变量来自第二阶段的RAG抽取结果:AI_Apply(公司当年是否在年报中披露AI实际应用)以及AI_Intensity(按应用场景数量折算的强度指标)。被解释变量是GTFP。控制变量要跟上公司财务特征:规模(总资产取对数)、资产负债率、ROA、企业年龄、股权集中度、董事会规模。再加行业和年度的固定效应,这是公司层面实证研究的标准动作。

import pandas as pd # 合并RAG抽取结果与财务/环境数据 ai_df = pd.read_csv("ai_extraction_results.csv") # 来自RAG抽取阶段 fin_df = pd.read_csv("financial_env_data.csv") # 财务与环境投入产出数据 # 关键:按 公司+年份 一一对应合并 df = fin_df.merge( ai_df[["firm_code", "year", "ai_mentioned", "ai_scene_count"]], on=["firm_code", "year"], how="left" ) # 变量构造 df["AI_Apply"] = df["ai_mentioned"].fillna(0).astype(int) df["AI_Intensity"] = df["ai_scene_count"].fillna(0) df["Size"] = np.log(df["total_assets"]) df["Lev"] = df["total_liabilities"] / df["total_assets"] df["ROA"] = df["net_income"] / df["total_assets"] # 删除合并不到的样本:RAG解析失败或财务数据缺失 df = df.dropna(subset=["GTFP", "Size", "Lev", "ROA"]) print(f"有效观测数:{len(df)},覆盖公司:{df['firm_code'].nunique()}")

合并时的坑在于公司代码格式。年报PDF里的公司标识可能是证券简称,而财务数据库用的是六位股票代码,不能直接用字符串合并,必须准备一份证券简称与代码的映射表。年报年份也要注意——披露的是"2023年年度报告",内容是2023年的经营情况,但实际发布日期是2024年4月,如果你用的是发布日去匹配财务数据,就会错位到2024年。

3.3 双向固定效应模型与标准误选择

基准回归采用公司-年度双向固定效应模型,公式是GTFP_it = β·AI_Apply_it + γ·X_it + μ_i + λ_t + ε_it。核心系数β的符号和显著性决定AI应用与GTFP的基本关系。

from linearmodels.panel import PanelOLS import statsmodels.api as sm # 设置面板索引 df = df.set_index(["firm_code", "year"]) exog_vars = ["AI_Apply", "Size", "Lev", "ROA", "Firm_Age", "Board_Size"] exog = sm.add_constant(df[exog_vars]) # 双向固定效应:实体效应=公司,时间效应=年份 model = PanelOLS( df["GTFP"], exog, entity_effects=True, time_effects=True ) result = model.fit(cov_type="clustered", cluster_entity=True) print(result.params["AI_Apply"], result.pvalues["AI_Apply"])

标准误聚类到公司层面是底线。年报数据存在天然的公司内自相关——同一家公司连续多年的AI披露行为和GTFP变动不是独立的,不聚类会严重低估标准误,导致p值虚小。如果样本量超过几千,可以考虑更高维聚类(公司+行业),但行业聚类在只有几十个行业分组时容易出现聚类调整矩阵不稳定,反而出问题。固定效应模型下,AI_Apply的系数衡量的是"公司在采用AI当年相对于未采用年份的GTFP变化",这里有一个隐含假设——采用AI的时间点是外生的。现实中往往是效率高的企业爱用AI,这就引出内生性问题,第四章会展开。

4. 融资约束异质性分析与稳健性检验:结果能否经得起追问

4.1 融资约束的度量与分组:SA指数还是WW指数

异质性分析的第一步是给样本分组。融资约束用SA指数还是WW指数,取决于数据可得性。WW指数需要现金流、股利支付等变量,在CSMAR里能拿到但缺失多;SA指数只依赖规模和年龄,计算简单且在中国上市公司样本里表现稳定。SA指数公式是SA = -0.737×Size + 0.043×Size² - 0.040×Age,由于原始公式基于美股公司估计,国内研究通常用样本分位数重新划分高低融资约束组。

# SA指数构造与分组 df["SA"] = -0.737 * df["Size"] + 0.043 * df["Size"]**2 - 0.040 * df["Firm_Age"] # 按年度分组计算中位数,避免时间趋势干扰分组 df["High_FC"] = (df["SA"] > df.groupby("year")["SA"].transform("median")).astype(int)

按年度中位数分组比全样本中位数更稳妥,因为SA指数随公司年龄和规模天然存在时间趋势,用全样本切分会导致早期年份大量样本落入高融资约束组,晚期则相反。分组完成后再按组分别跑基准回归,观察AI_Apply系数在两组间的差异。

4.2 交互项与分组回归:检验AI对GTFP的作用方向

分组回归的缺点是两组系数差异没有直接检验显著性的统计量,交互项方法能把异质性稳健地放进一个回归方程里。

# 交互项模型:AI × High_FC df["AI_FC"] = df["AI_Apply"] * df["High_FC"] exog_vars = ["AI_Apply", "AI_FC", "High_FC", "Size", "Lev", "ROA"] model_inter = PanelOLS( df["GTFP"], sm.add_constant(df[exog_vars]), entity_effects=True, time_effects=True ) result_inter = model_inter.fit(cov_type="clustered", cluster_entity=True)

交互项系数β_AI_FC是核心看点。如果它是显著负的,说明AI对GTFP的正面影响在融资约束高的公司里被削弱了——这符合直觉:AI投入周期长、回报不确定性高,融资困难企业拿不出足够的持续性资金,AI应用难以转化为绿色效率改善。注意分组变量High_FC本身不能被固定效应吸收,因为它随时间缓慢变化,所以保留在回归中没问题。但如果High_FC完全由Size计算而来,而固定效应模型已经在控制公司个体特征,交互项仍有意义,主效应High_FC的系数则经常被沾掉——这个不用纠结。

4.3 稳健性检验的三个动作:替换变量、工具变量、安慰剂

稳健性检验的目的是让审稿人或决策者相信基准结果不是偶然套路碰出来的。我通常会做三件事。第一是替换被解释变量和核心解释变量——GTFP换用不同的非期望产出口径,AI_Apply换成AI_Intensity,看系数方向是否一致。第二是工具变量法,常用的是同一行业内其他公司的AI应用平均值作为该公司AI应用的IV,理由是行业层面技术溢出会影响个体采用,但不会直接影响个体GTFP。

from linearmodels.iv import IV2SLS # 构造工具变量:同行业同年度AI应用均值(剔除自身) df["IV_Ind_Avg"] = df.groupby(["industry", "year"])["AI_Apply"].transform( lambda x: (x.sum() - x) / (x.count() - 1) ) iv_model = IV2SLS( df["GTFP"], exog=sm.add_constant(df[["Size", "Lev", "ROA"]]), endog=df["AI_Apply"], instruments=df["IV_Ind_Avg"] ) iv_result = iv_model.fit(cov_type="clustered", cluster_entity=True) print(iv_result.first_stage.pvalues) # 看拒绝弱工具变量的概率

跑IV的第一件事不是看第二阶段系数,而是看第一阶段F值。F统计量必须大于10,不然就是弱工具变量,第二阶段结果再好看也不可置信。行业均值为工具变量的前提是行业内的AI采用存在传染效应,这个前提在A股同行竞争环境下基本成立。

第三个动作是安慰剂检验——把AI_Apply变量打乱,然后回归几百次,看随机样本里有多少次回归系数显著。如果真实数据得到的β显著正,而打乱后的分布中显著比例很低,说明不是纯偶然。这个检验实现简单但说服力强,审稿人几乎不会挑毛病。

5. 避坑:五个让RAG与统计建模翻车的实操问题

5.1 无文本页导致年报解析出现乱码与跳读

现象:PyMuPDF解析年报时,一部分公司能正常抽到文本,另一家公司抽出来的内容缺了大段,或者出现大量乱码字符。 原因:A股年报PDF不全是文字版。很多早期披露或扫描重传的年报是图片型PDF,每页只有一个背景图,get_text()返回空或乱码。全文字版本占比约七成,剩下的必须走OCR。 解决:解析前逐页探测文本量,对低于阈值(比如每页少于50字符)的页面标记为扫描页,送入PaddleOCR识别。注意OCR识别后要把页面文本和原始文字版页面按页码拼回去,不能丢页。补充OCR之后重新跑一遍分块,并抽查被遗漏公司的AI指标字段是否为非空。

5.2 分块切断关键句,"人工智能"上下文丢失

现象:检索"人工智能应用"召回了一个文本块,但该块只含"公司重视人工智能技术",没有任何落地场景描述,导致抽取结果把应用场景字段留空。 原因:固定字符数分块恰好把句子拦腰截断,后面的内容落在另一个块里,而后续块没被TopK召回。年报中MD&A部分常用"公司致力于、将重点推进"这类虚词开头,真正有信息量的动词在后半句。 解决:两种手段并用——overlap从150增到250,同时在用正则切分时优先保留句号边界,禁止块边界出现在句号前。另外一个经验,把TopK从5提升到8后召回率改善明显,代价是单次抽取的token开销增加三成左右,但换回的字段完整率能提升一到两个百分点。

5.3 LLM抽取把"计划"当成"已应用",指标虚高

现象:抽取结果的ai_mentioned为true,但回看evidence发现原文写的是"2024年公司将重点布局人工智能技术",模型把未来计划当成了已发生事实,AI_Apply变量系统性高估。 原因:提示词对时间锚点约束不够。年报里有大量对未来的展望性描述,模型对"将、计划、拟"这些词的敏感度没有想象中高,尤其当上下文混杂过去和未来时。 解决:在提示词判断规则里显式加入时间条件——"只认定报告期内已发生的应用,出现将、拟、计划、预计、目标等词视为未应用"。同时让evidence_sentence字段必须输出完整的含时间指向的句子,后处理脚本再对证据句做二次校验,含"报告期内、本年度"保留,含"明年、未来、公司计划"整条标记为待人工复核。这一步能砍掉大约15%到20%的虚高正例。

5.4 年报披露年份与财务数据年份错配

现象:回归结果AI_Apply系数不显著或符号反转,初步排查发现分组逻辑没错、变量构造没问题,就是结果不对。 原因:年报的"报告期"和"披露日期"不是一个概念——2023年年报的披露时间在2024年4月底,而有些公司会在年报里同时披露2024年一季报经营数据。用披露日期去匹配财务数据时,部分样本对错了年份。 解决:抽取和建模统一采用"会计年度"锚点——年报标题年份即为报告期年份,财报数据里的year也统一为报告期,不用发布日期做任何匹配。PDF文件名里如果带年份,解析后做一个一致性校验:标题里的年份必须等于报告期年份,不一致时以报告期年份为准重新解析。

5.5 GTFP测算出现极端值,回归系数对缩尾阈值极其敏感

现象:GTFP序列出现大量大于2或小于0.5的极端值,1%缩尾和5%缩尾下AI_Apply的系数符号或显著性级别差异很大。 原因:非期望产出(碳排放或污染物排放)数据缺失或单位不一致,导致DEA线性规划在部分决策单元上解出退化值。GML指数对基期选择也敏感,基期年份的效率异常会传导到所有年份。 解决:拉到原始环境数据检查单位——万元产值碳排放、吨标煤能耗、万吨废水这些单位混用会让DEA模型直接崩溃。极端值占比超过3%时优先修正投入产出原始数据,不要只靠缩尾去掩盖问题。缩尾动作只做一次,在基准回归和稳健性检验里保持一致,不要为了让结果好看来回切换阈值。

6. 把抽取质量和AI指标当回归变量验证:一个该养成的工作习惯

做完一整套流程后,我最想让你形成的习惯不是去调prompt或者换DEA模型,而是先验证抽出来的AI指标到底可不可信。方法很简单:从全样本里随机抽100条抽取记录,人工读原始年报文本确认ai_mentioned判断是否正确,计算精确率和召回率;再让模型对同一批样本抽两次,看一致性率。一致性低于95%就说明prompt不稳定,需要先收敛抽取环节再往下建模。

# 抽取质量抽样检查表 val_sample = pd.DataFrame({ "firm": ["000001", "000002", "000004"], "year": [2023, 2023, 2022], "human_label": [1, 0, 1], "model_label": [1, 0, 0], "evidence_match": [True, True, False], }) precision = val_sample["human_label"].sum() / val_sample["model_label"].sum() recall = (val_sample[(val_sample["human_label"] == 1) & (val_sample["model_label"] == 1)].shape[0] / val_sample[val_sample["human_label"] == 1].shape[0]) print(f"抽取精确率 {precision:.2%},召回率 {recall:.2%}")

我还习惯把AI指标从0-1扩展成层级再进回归:年报里只提"探索、关注"算1,"有具体项目落地"算2,"有投入金额或研发占比"算3。这个分层指标比哑变量更细腻,能捕捉到AI应用从浅到深的边际效应差异,回归结果也更有叙事层次。跑完基准模型后,我会做一件事:把交互项换成每种AI层级分别和High_FC交乘,看效应是否集中在深度应用组——通常这才是融资约束起作用的真正位置。

这些流程跑了几遍之后,你就不会再迷信"关键词命中率"或者"大模型打分"了。一个能被诚实复现的抽取步骤、一个能在替换口径后保持稳定的回归系数,比任何花哨的建模技巧都有说服力。希望这套从年报文本到GTFP统计证据的操作路径,能帮你少走几趟我把坑踩完的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询